Compare commits

...

218 Commits
1.5.1 ... 1.6.1

Author SHA1 Message Date
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
Davidson Gomes
8fe75cd210 Merge branch 'release/1.5.4' 2023-10-09 20:43:40 -03:00
Davidson Gomes
303effebbc version: 1.5.4 2023-10-09 20:43:30 -03:00
Davidson Gomes
f32a34190d fix: adjusts logger 2023-10-09 20:17:01 -03:00
Davidson Gomes
8588ef1d8a fix: adjusts logger 2023-10-09 20:15:53 -03:00
Davidson Gomes
51ec4821f3 fix: adjusts logger 2023-10-09 20:14:50 -03:00
Davidson Gomes
957033a7bb ajustes 2023-10-09 19:57:26 -03:00
Davidson Gomes
62c74deac3 fix: Solved problem with duplicate messages in chatwoot 2023-10-09 18:00:14 -03:00
Davidson Gomes
29fd448998 Merge tag '1.5.3' into develop
* Swagger documentation
* Added base 64 sending option via webhook

* Remove rabbitmq queues when delete instances
* Improvement in restart instance to completely redo the connection
* Update node version: v20
* Correction of messages sent by the api and typebot not appearing in chatwoot
* Adjustment to start typebot, added startSession parameter
* Chatwoot now receives messages sent via api and typebot
* Fixed problem with starting with an input in typebot
* Added check to ensure variables are not empty before executing foreach in start typebot
2023-10-06 18:55:52 -03:00
Davidson Gomes
e55cb08a6a Merge branch 'release/1.5.3' 2023-10-06 18:55:44 -03:00
Davidson Gomes
047359e8dc version: 1.5.3 2023-10-06 18:55:36 -03:00
Davidson Gomes
eb814e181a adjusts in swagger 2023-10-06 18:45:26 -03:00
Davidson Gomes
857031ff5a adjusts in swagger 2023-10-06 18:05:37 -03:00
Davidson Gomes
d5eeb68714 feat: webhook base64 option 2023-10-05 17:05:41 -03:00
Davidson Gomes
966b287026 feat: webhook base64 option 2023-10-05 16:01:47 -03:00
Davidson Gomes
a348729109 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2023-10-05 15:59:59 -03:00
Davidson Gomes
1f29b7733e Merge pull request #163 from w3nder/develop
Fix: Variables null
2023-10-05 15:59:39 -03:00
Davidson Gomes
e26ae30f6f feat: webhook base64 option 2023-10-05 15:57:53 -03:00
Davidson Gomes
547943a05c Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2023-10-05 15:54:53 -03:00
Davidson Gomes
46aa229531 fix: adjusts in swagger 2023-10-05 15:54:46 -03:00
Davidson Gomes
28bd796289 Merge pull request #161 from moraisamilton/develop
Recuperar base64 de media enviada por webhook
2023-10-05 15:54:20 -03:00
Wender Teixeira
6a3f82ed7e Fix: Variables null 2023-10-05 11:43:18 -03:00
Amilton Morais
523f3301c0 Update wa.types.ts
Foi criado novas propriedades para recuperar Base64 da media enviada por webhook.
2023-10-03 17:20:14 -03:00
Amilton Morais
f085343a99 Update whatsapp.service.ts
Foi criado novo código para recuperar Base64 da media enviada por webhook.
2023-10-03 17:18:58 -03:00
Amilton Morais
f76a924700 Update webhook.service.ts
Foi criado novo variavel "webhook_base64:false' para retorno erro.
2023-10-03 17:13:58 -03:00
Amilton Morais
f8e3b76a4a Update webhook.model.ts
Foi criado novas propriedades para recuperar Base64 da media enviada por webhook.
2023-10-03 16:59:06 -03:00
Amilton Morais
b8f1e8a7ef Update instance.dto.ts
Foi criado novas propriedades "webhook_base64?: boolean;" para recuperar Base64 da media enviada por webhook.
2023-10-03 16:56:44 -03:00
Amilton Morais
d007fc49d8 Update instance.controller.ts
Foi criado novas propriedades para recuperar Base64 da media enviada por webhook.
2023-10-03 16:45:05 -03:00
Amilton Morais
f6d8ebd8d3 Update dev-env.yml
Inserido nova variável WEBHOOK_BASE64 para recuperar Base64 da media enviar por webhook.
2023-10-03 16:39:04 -03:00
Davidson Gomes
6ff9c4578a Fixed problem with starting with an input in typebot 2023-10-02 18:23:03 -03:00
Davidson Gomes
33acfe1464 chatwoot now receives messages sent via api and typebot 2023-10-02 17:59:54 -03:00
Davidson Gomes
f5eeb16bb1 Adjustment to start typebot, added startSession parameter 2023-10-02 17:05:22 -03:00
Davidson Gomes
c35c5faaa4 correction of messages sent by the api and typebot not appearing in chatwoot 2023-10-02 16:17:28 -03:00
Davidson Gomes
8425ebc13f Merge pull request #156 from EvolutionAPI/revert-154-evolution-api-1.5.2-develop-francis
Revert "Start Typebot com opção de ativar chatbot ou não"
2023-10-02 16:10:45 -03:00
Davidson Gomes
6fc37a4298 Revert "Start Typebot com opção de ativar chatbot ou não" 2023-10-02 19:10:34 +00:00
Davidson Gomes
99f3e77c12 Merge pull request #154 from francisbreit/evolution-api-1.5.2-develop-francis
Start Typebot com opção de ativar chatbot ou não
2023-10-02 16:10:29 -03:00
Davidson Gomes
a9c087c45f correction of messages sent by the api and typebot not appearing in chatwoot 2023-10-02 16:10:04 -03:00
Francis Breit
3f4333087f Start Typebot com opção de ativar chatbot ou não
Foi criada a variável enabled_typebot (não obrigatória)
enabled_typebot igual a true ou vazio: o comportamento do "startTypebot" atua como a nova funcionalidadede sessões persistentes (v1.5.2) onde o flow do Typebot disparado atua como chatbot.
enabled_typebot igual a false: o comportamento do "startTypebot" atua como era na Evolution v.1.5.1 onde o flow do Typebot disparado atua como mensagem simples, nao ativa o chatbot e funciona apenas como mensagens simples.
Obs1: Se setada como true ou se for omitida essa variável no comando start Typebot, enabled_typebot será assumida como true, ou seja, start typebot aciona o flow com o chatbot ativado, imediatamente após o envio, aguardando interação do contato.. Se, antes do acionamento do start typebot, tiver um outro chatbot ativo, o mesmo será substituído pelo novo, ora enviado.
Obs2: Se setada como false, dispara apenas como notificação e não ativa o bot. se tiver outro bot ativo na instancia o mesmo continuará ativo, após disparada a mensagem
2023-10-02 11:56:24 -03:00
Davidson Gomes
e1ac29683d wip: swagger 2023-10-02 09:24:53 -03:00
Davidson Gomes
5c74cbfe19 improvement in restart instance to completely redo the connection 2023-09-30 16:36:32 -03:00
Davidson Gomes
3fdb3fa673 fix: Remove rabbitmq queues when delete instances 2023-09-30 07:20:57 -03:00
Davidson Gomes
ba584974cb Merge tag '1.5.2' into develop
v
2023-09-28 17:58:59 -03:00
Davidson Gomes
bddd6408ac Merge branch 'release/1.5.2' 2023-09-28 17:58:55 -03:00
Davidson Gomes
413ad66a07 version: 1.5.2 2023-09-28 17:58:37 -03:00
Davidson Gomes
0ef5d884cc version: 1.5.2 2023-09-28 17:56:47 -03:00
Davidson Gomes
f6b6d23e93 Merge pull request #133 from francisbreit/fetch-instances
Correção de nao recebimento de midias armazenadas mo minio/s3 Update whatsapp.service.ts
2023-09-28 17:47:17 -03:00
Davidson Gomes
50be69f3d3 Merge pull request #132 from IsraelXabregas/chatwoot-model-fix-schema-db
Fix chatwootSchema in chatwoot model to store reopen_conversation and conversation_pending options
2023-09-28 17:45:13 -03:00
Davidson Gomes
5a75e4d5e6 Merge pull request #131 from matheuskshn/develop
Melhoria no método "startTypebot" para criar sessão persistente quando acionado
2023-09-28 17:44:00 -03:00
Davidson Gomes
ec463df9d6 Merge pull request #126 from francisbreit/main
Novo manager para Evo 1.5.1 - atualização Set Typebot
2023-09-28 17:42:06 -03:00
Francis Breit
b648334323 Corrigir nao recebimento de midias minio/s3 - finalizado- Update whatsapp.service.tsUpdate whatsapp.service.ts
Correção nao recebimento de midias com urls do minio/s3 .
Urls sem o nome da extensão da midia impediam o envio das mensagens por erro na detecção do mimetype.
Imagem e Video
2023-09-26 14:15:50 -03:00
Francis Breit
916972aeb1 Update whatsapp.service.ts 2023-09-26 13:52:11 -03:00
Francis Breit
3893e01af9 Corrigir nao recebimento de midias minio/s3 Update whatsapp.service.ts
Correção nao recebimento de midias com urls do minio/s3 .
Urls sem o nome da extensão da midia impediam o envio das mensagens por erro na detecção do mimetype.
2023-09-26 13:27:58 -03:00
Israel Xabregas Ramos
7835f32c8b Fix chatwootSchema in chatwoot model to store reopen_conversation and conversation_pending options 2023-09-25 20:45:14 -03:00
Matheus Gomes
37302244ee Update typebot.service.ts 2023-09-25 16:37:43 -03:00
Matheus Gomes
f1be7ddb83 Update typebot.service.ts 2023-09-25 16:22:41 -03:00
Matheus Gomes
7447a65a83 Update typebot.service.ts 2023-09-25 16:05:18 -03:00
Matheus Gomes
129009d602 Update typebot.service.ts 2023-09-25 15:55:12 -03:00
Matheus Gomes
c656bd6f4b Enhancement of Session Initialization in "StartTypebot" Function
Enhanced "StartTypebot" to initialize sessionIDs during the initial interaction, improving user experience on WhatsApp and preventing unexpected bot restarts.
2023-09-25 02:38:37 -03:00
Francis Breit
36528d7484 Inclusão de Listening From Me no est Typebot Update manager.json
Correção e inclusão de Listening From Me
2023-09-22 13:28:29 -03:00
Francis Breit
f9c85b6a67 Novo manager para Evo 1.5.1 - atualização Set Typebot
Incluída a opção da variável listening_from_me do Set Typebot
2023-09-21 10:04:00 -03:00
Davidson Gomes
4dfe6bdbe8 Merge pull request #122 from francisbreit/fetch-instances
Update monitor.service.ts - Evolution ap i 1.5.1 - Problemas ao ler / consultar instancias (FETCH_INSTANCES)
2023-09-20 15:09:28 -03:00
Francis Breit
ee343f2fa1 Update monitor.service.ts
Evolution ap i 1.5.1 - Problemas ao ler / consultar instancias (FETCH_INSTANCES)

Ao consultar instancias as mesmas nao apareciam, seja via manager, seja via postman
2023-09-20 11:13:43 -03:00
Davidson Gomes
1a1f5f85b2 Merge tag '1.5.1' into develop
* Added listening_from_me option in Set Typebot
* Added variables options in Start Typebot
* Added webhooks for typebot events
* Added ChamaAI integration
* Added webhook to send errors
* Added support for messaging with ads on chatwoot

* Fix looping connection messages in chatwoot
* Improved performance of fetch instances
2023-09-17 13:55:02 -03:00
70 changed files with 5520 additions and 592 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.

1
.gitignore vendored
View File

@@ -39,6 +39,7 @@ docker-compose.yaml
/test/ /test/
/src/env.yml /src/env.yml
/store /store
*.env
/temp/* /temp/*

View File

@@ -5,7 +5,9 @@
"editor.smoothScrolling": true, "editor.smoothScrolling": true,
"editor.tabSize": 2, "editor.tabSize": 2,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true, "source.fixAll.eslint": "explicit",
"source.fixAll": true "source.fixAll": "explicit"
} },
"prisma-smart-formatter.typescript.defaultFormatter": "esbenp.prettier-vscode",
"prisma-smart-formatter.prisma.defaultFormatter": "Prisma.prisma"
} }

View File

@@ -1,3 +1,92 @@
# 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
* Baileys logger typing issue resolved
* Solved problem with duplicate messages in chatwoot
# 1.5.3 (2023-10-06 18:55)
### Feature
* Swagger documentation
* Added base 64 sending option via webhook
### Fixed
* Remove rabbitmq queues when delete instances
* Improvement in restart instance to completely redo the connection
* Update node version: v20
* Correction of messages sent by the api and typebot not appearing in chatwoot
* Adjustment to start typebot, added startSession parameter
* Chatwoot now receives messages sent via api and typebot
* Fixed problem with starting with an input in typebot
* Added check to ensure variables are not empty before executing foreach in start typebot
# 1.5.2 (2023-09-28 17:56)
### Fixed
* Fix chatwootSchema in chatwoot model to store reopen_conversation and conversation_pending options
* Problem resolved when sending files from minio to typebot
* Improvement in the "startTypebot" method to create persistent session when triggered
* New manager for Evo 1.5.2 - Set Typebot update
* Resolved problems when reading/querying instances
# 1.5.1 (2023-09-17 13:50) # 1.5.1 (2023-09-17 13:50)
### Feature ### Feature
@@ -40,9 +129,9 @@
### Integrations ### Integrations
- Chatwoot: v2.18.0 - v3.0.0 * Chatwoot: v2.18.0 - v3.0.0
- Typebot: v2.16.0 * Typebot: v2.16.0
- Manager Evolution API * Manager Evolution API
# 1.4.8 (2023-07-27 10:27) # 1.4.8 (2023-07-27 10:27)
@@ -130,7 +219,7 @@
### Integrations ### 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) # 1.3.2 (2023-07-21 17:19)
@@ -146,7 +235,7 @@
### Integrations ### Integrations
- Chatwoot: v2.18.0 * Chatwoot: v2.18.0
# 1.3.1 (2023-07-20 07:48) # 1.3.1 (2023-07-20 07:48)
@@ -156,7 +245,7 @@
### Integrations ### Integrations
- Chatwoot: v2.18.0 * Chatwoot: v2.18.0
# 1.3.0 (2023-07-19 11:33) # 1.3.0 (2023-07-19 11:33)
@@ -193,7 +282,7 @@
### Integrations ### Integrations
- Chatwoot: v2.18.0 * Chatwoot: v2.18.0
# 1.2.2 (2023-07-15 09:36) # 1.2.2 (2023-07-15 09:36)
@@ -204,7 +293,7 @@
### Integrations ### Integrations
- Chatwoot: v2.18.0 * Chatwoot: v2.18.0
# 1.2.1 (2023-07-14 19:04) # 1.2.1 (2023-07-14 19:04)

View File

@@ -51,6 +51,12 @@ RABBITMQ_URI=amqp://guest:guest@rabbitmq:5672
WEBSOCKET_ENABLED=false WEBSOCKET_ENABLED=false
SQS_ENABLED=false
SQS_ACCESS_KEY_ID=
SQS_SECRET_ACCESS_KEY=
SQS_ACCOUNT_ID=
SQS_REGION=
# Global Webhook Settings # Global Webhook Settings
# Each instance's Webhook URL and events will be requested at the time it is created # 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 ## Define a global webhook that will listen for enabled events from all instances
@@ -99,6 +105,10 @@ CONFIG_SESSION_PHONE_NAME=Chrome
QRCODE_LIMIT=30 QRCODE_LIMIT=30
QRCODE_COLOR=#198754 QRCODE_COLOR=#198754
# old | latest
TYPEBOT_API_VERSION=latest
TYPEBOT_KEEP_OPEN=false
# Defines an authentication type for the api # Defines an authentication type for the api
# We recommend using the apikey because it will allow you to use a custom token, # 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 # if you use jwt, a random token will be generated and may be expired and you will have to generate a new token

View File

@@ -1,6 +1,6 @@
FROM node:16.18-alpine FROM node:20.7.0-alpine AS builder
LABEL version="1.5.1" description="Api to control whatsapp features through http requests." LABEL version="1.6.1" description="Api to control whatsapp features through http requests."
LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes" LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
LABEL contact="contato@agenciadgcode.com" LABEL contact="contato@agenciadgcode.com"
@@ -11,9 +11,19 @@ WORKDIR /evolution
COPY ./package.json . 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 TZ=America/Sao_Paulo
ENV DOCKER_ENV=true ENV DOCKER_ENV=true
ENV SERVER_TYPE=http
ENV SERVER_PORT=8080
ENV SERVER_URL=http://localhost:8080 ENV SERVER_URL=http://localhost:8080
ENV CORS_ORIGIN=* ENV CORS_ORIGIN=*
@@ -56,12 +66,20 @@ ENV RABBITMQ_URI=amqp://guest:guest@rabbitmq:5672
ENV WEBSOCKET_ENABLED=false ENV WEBSOCKET_ENABLED=false
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_URL=
ENV WEBHOOK_GLOBAL_ENABLED=false ENV WEBHOOK_GLOBAL_ENABLED=false
ENV WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=false ENV WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=false
ENV WEBHOOK_EVENTS_APPLICATION_STARTUP=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_QRCODE_UPDATED=true
ENV WEBHOOK_EVENTS_MESSAGES_SET=true ENV WEBHOOK_EVENTS_MESSAGES_SET=true
ENV WEBHOOK_EVENTS_MESSAGES_UPSERT=true ENV WEBHOOK_EVENTS_MESSAGES_UPSERT=true
@@ -98,6 +116,8 @@ ENV CONFIG_SESSION_PHONE_NAME=Chrome
ENV QRCODE_LIMIT=30 ENV QRCODE_LIMIT=30
ENV QRCODE_COLOR=#198754 ENV QRCODE_COLOR=#198754
ENV TYPEBOT_API_VERSION=latest
ENV AUTHENTICATION_TYPE=apikey ENV AUTHENTICATION_TYPE=apikey
ENV AUTHENTICATION_API_KEY=B6D711FCDE4D4FD5936544120E713976 ENV AUTHENTICATION_API_KEY=B6D711FCDE4D4FD5936544120E713976
@@ -114,10 +134,8 @@ ENV AUTHENTICATION_INSTANCE_CHATWOOT_ACCOUNT_ID=1
ENV AUTHENTICATION_INSTANCE_CHATWOOT_TOKEN=123456 ENV AUTHENTICATION_INSTANCE_CHATWOOT_TOKEN=123456
ENV AUTHENTICATION_INSTANCE_CHATWOOT_URL=<url> ENV AUTHENTICATION_INSTANCE_CHATWOOT_URL=<url>
RUN npm install WORKDIR /evolution
COPY . . COPY --from=builder /evolution .
RUN npm run build
CMD [ "node", "./dist/src/main.js" ] CMD [ "node", "./dist/src/main.js" ]

File diff suppressed because one or more lines are too long

View File

@@ -39,12 +39,13 @@ This code was produced based on the baileys library and it is still under develo
</a> </a>
</div> </div>
#### Buy me coffe #### Buy me coffe - PIX
<div align="center"> <div align="center">
<a href="https://bmc.link/evolutionapi" target="_blank" rel="noopener noreferrer"> <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> </a>
<p><b>CHAVE PIX (Telefone):</b> (74)99987-9409</p>
</div> </div>
</br> </br>

View File

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

View File

@@ -1,6 +1,6 @@
{ {
"name": "evolution-api", "name": "evolution-api",
"version": "1.5.1", "version": "1.6.1",
"description": "Rest api for communication with WhatsApp", "description": "Rest api for communication with WhatsApp",
"main": "./dist/src/main.js", "main": "./dist/src/main.js",
"scripts": { "scripts": {
@@ -46,8 +46,9 @@
"@figuro/chatwoot-sdk": "^1.1.16", "@figuro/chatwoot-sdk": "^1.1.16",
"@hapi/boom": "^10.0.1", "@hapi/boom": "^10.0.1",
"@sentry/node": "^7.59.2", "@sentry/node": "^7.59.2",
"@whiskeysockets/baileys": "^6.4.0", "@whiskeysockets/baileys": "github:PurpShell/Baileys#combined",
"amqplib": "^0.10.3", "amqplib": "^0.10.3",
"aws-sdk": "^2.1499.0",
"axios": "^1.3.5", "axios": "^1.3.5",
"class-validator": "^0.13.2", "class-validator": "^0.13.2",
"compression": "^1.7.4", "compression": "^1.7.4",
@@ -55,6 +56,7 @@
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"eventemitter2": "^6.4.9", "eventemitter2": "^6.4.9",
"evolution-manager": "^0.4.11",
"exiftool-vendored": "^22.0.0", "exiftool-vendored": "^22.0.0",
"express": "^4.18.2", "express": "^4.18.2",
"express-async-errors": "^3.1.1", "express-async-errors": "^3.1.1",
@@ -78,8 +80,9 @@
"sharp": "^0.30.7", "sharp": "^0.30.7",
"socket.io": "^4.7.1", "socket.io": "^4.7.1",
"socks-proxy-agent": "^8.0.1", "socks-proxy-agent": "^8.0.1",
"swagger-ui-express": "^5.0.0",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"whatsapp-web.js": "^1.22.1" "yamljs": "^0.3.0"
}, },
"devDependencies": { "devDependencies": {
"@types/compression": "^1.7.2", "@types/compression": "^1.7.2",

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

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 { load } from 'js-yaml';
import { join } from 'path'; 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 HttpMethods = 'POST' | 'GET' | 'PUT' | 'DELETE';
export type Cors = { export type Cors = {
@@ -66,16 +72,22 @@ export type Rabbitmq = {
URI: string; URI: string;
}; };
export type Sqs = {
ENABLED: boolean;
ACCESS_KEY_ID: string;
SECRET_ACCESS_KEY: string;
ACCOUNT_ID: string;
REGION: string;
};
export type Websocket = { export type Websocket = {
ENABLED: boolean; ENABLED: boolean;
}; };
export type Chatwoot = {
USE_REPLY_ID: boolean;
};
export type EventsWebhook = { export type EventsWebhook = {
APPLICATION_STARTUP: boolean; APPLICATION_STARTUP: boolean;
INSTANCE_CREATE: boolean;
INSTANCE_DELETE: boolean;
QRCODE_UPDATED: boolean; QRCODE_UPDATED: boolean;
MESSAGES_SET: boolean; MESSAGES_SET: boolean;
MESSAGES_UPSERT: boolean; MESSAGES_UPSERT: boolean;
@@ -124,6 +136,7 @@ export type SslConf = { PRIVKEY: string; FULLCHAIN: string };
export type Webhook = { GLOBAL?: GlobalWebhook; EVENTS: EventsWebhook }; export type Webhook = { GLOBAL?: GlobalWebhook; EVENTS: EventsWebhook };
export type ConfigSessionPhone = { CLIENT: string; NAME: string }; export type ConfigSessionPhone = { CLIENT: string; NAME: string };
export type QrCode = { LIMIT: number; COLOR: string }; export type QrCode = { LIMIT: number; COLOR: string };
export type Typebot = { API_VERSION: string; KEEP_OPEN: boolean };
export type Production = boolean; export type Production = boolean;
export interface Env { export interface Env {
@@ -135,15 +148,16 @@ export interface Env {
DATABASE: Database; DATABASE: Database;
REDIS: Redis; REDIS: Redis;
RABBITMQ: Rabbitmq; RABBITMQ: Rabbitmq;
SQS: Sqs;
WEBSOCKET: Websocket; WEBSOCKET: Websocket;
LOG: Log; LOG: Log;
DEL_INSTANCE: DelInstance; DEL_INSTANCE: DelInstance;
WEBHOOK: Webhook; WEBHOOK: Webhook;
CONFIG_SESSION_PHONE: ConfigSessionPhone; CONFIG_SESSION_PHONE: ConfigSessionPhone;
QRCODE: QrCode; QRCODE: QrCode;
TYPEBOT: Typebot;
AUTHENTICATION: Auth; AUTHENTICATION: Auth;
PRODUCTION?: Production; PRODUCTION?: Production;
CHATWOOT?: Chatwoot;
} }
export type Key = keyof Env; export type Key = keyof Env;
@@ -163,8 +177,8 @@ export class ConfigService {
this.env = !(process.env?.DOCKER_ENV === 'true') ? this.envYaml() : this.envProcess(); this.env = !(process.env?.DOCKER_ENV === 'true') ? this.envYaml() : this.envProcess();
this.env.PRODUCTION = process.env?.NODE_ENV === 'PROD'; this.env.PRODUCTION = process.env?.NODE_ENV === 'PROD';
if (process.env?.DOCKER_ENV === 'true') { if (process.env?.DOCKER_ENV === 'true') {
this.env.SERVER.TYPE = 'http'; this.env.SERVER.TYPE = process.env.SERVER_TYPE as 'http' | 'http';
this.env.SERVER.PORT = 8080; this.env.SERVER.PORT = Number.parseInt(process.env.SERVER_PORT) || 8080;
} }
} }
@@ -175,9 +189,11 @@ export class ConfigService {
private envProcess(): Env { private envProcess(): Env {
return { return {
SERVER: { 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, PORT: Number.parseInt(process.env.SERVER_PORT) || 8080,
URL: process.env.SERVER_URL, URL: process.env.SERVER_URL,
DISABLE_DOCS: process.env?.SERVER_DISABLE_DOCS === 'true',
DISABLE_MANAGER: process.env?.SERVER_DISABLE_MANAGER === 'true',
}, },
CORS: { CORS: {
ORIGIN: process.env.CORS_ORIGIN.split(',') || ['*'], ORIGIN: process.env.CORS_ORIGIN.split(',') || ['*'],
@@ -226,6 +242,13 @@ export class ConfigService {
ENABLED: process.env?.RABBITMQ_ENABLED === 'true', ENABLED: process.env?.RABBITMQ_ENABLED === 'true',
URI: process.env.RABBITMQ_URI || '', 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: { WEBSOCKET: {
ENABLED: process.env?.WEBSOCKET_ENABLED === 'true', ENABLED: process.env?.WEBSOCKET_ENABLED === 'true',
}, },
@@ -254,6 +277,8 @@ export class ConfigService {
}, },
EVENTS: { EVENTS: {
APPLICATION_STARTUP: process.env?.WEBHOOK_EVENTS_APPLICATION_STARTUP === 'true', 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', QRCODE_UPDATED: process.env?.WEBHOOK_EVENTS_QRCODE_UPDATED === 'true',
MESSAGES_SET: process.env?.WEBHOOK_EVENTS_MESSAGES_SET === 'true', MESSAGES_SET: process.env?.WEBHOOK_EVENTS_MESSAGES_SET === 'true',
MESSAGES_UPSERT: process.env?.WEBHOOK_EVENTS_MESSAGES_UPSERT === 'true', MESSAGES_UPSERT: process.env?.WEBHOOK_EVENTS_MESSAGES_UPSERT === 'true',
@@ -289,6 +314,10 @@ export class ConfigService {
LIMIT: Number.parseInt(process.env.QRCODE_LIMIT) || 30, LIMIT: Number.parseInt(process.env.QRCODE_LIMIT) || 30,
COLOR: process.env.QRCODE_COLOR || '#198754', COLOR: process.env.QRCODE_COLOR || '#198754',
}, },
TYPEBOT: {
API_VERSION: process.env?.TYPEBOT_API_VERSION || 'old',
KEEP_OPEN: process.env.TYPEBOT_KEEP_OPEN === 'true',
},
AUTHENTICATION: { AUTHENTICATION: {
TYPE: process.env.AUTHENTICATION_TYPE as 'apikey', TYPE: process.env.AUTHENTICATION_TYPE as 'apikey',
API_KEY: { API_KEY: {
@@ -302,9 +331,6 @@ export class ConfigService {
SECRET: process.env.AUTHENTICATION_JWT_SECRET || 'L=0YWt]b2w[WF>#>:&E`', 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, stderr: process.stderr.fd,
error, error,
}); });
process.exit(1);
}); });
process.on('unhandledRejection', (error, origin) => { process.on('unhandledRejection', (error, origin) => {
@@ -18,6 +17,5 @@ export function onUnexpectedError() {
stderr: process.stderr.fd, stderr: process.stderr.fd,
error, error,
}); });
process.exit(1);
}); });
} }

View File

@@ -9,6 +9,9 @@ SERVER:
TYPE: http # https TYPE: http # https
PORT: 8080 # 443 PORT: 8080 # 443
URL: localhost URL: localhost
DISABLE_MANAGER: false
DISABLE_DOCS: false
CORS: CORS:
ORIGIN: ORIGIN:
@@ -83,6 +86,13 @@ RABBITMQ:
ENABLED: false ENABLED: false
URI: "amqp://guest:guest@localhost:5672" URI: "amqp://guest:guest@localhost:5672"
SQS:
ENABLED: true
ACCESS_KEY_ID: ""
SECRET_ACCESS_KEY: ""
ACCOUNT_ID: ""
REGION: "us-east-1"
WEBSOCKET: WEBSOCKET:
ENABLED: false ENABLED: false
@@ -139,6 +149,10 @@ QRCODE:
LIMIT: 30 LIMIT: 30
COLOR: "#198754" COLOR: "#198754"
TYPEBOT:
API_VERSION: 'old' # old | latest
KEEP_OPEN: false
# Defines an authentication type for the api # Defines an authentication type for the api
# We recommend using the apikey because it will allow you to use a custom token, # 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 # if you use jwt, a random token will be generated and may be expired and you will have to generate a new token
@@ -154,7 +168,3 @@ AUTHENTICATION:
JWT: JWT:
EXPIRIN_IN: 0 # seconds - 3600s === 1h | zero (0) - never expires EXPIRIN_IN: 0 # seconds - 3600s === 1h | zero (0) - never expires
SECRET: L=0YWt]b2w[WF>#>:&E` SECRET: L=0YWt]b2w[WF>#>:&E`
# Configure to chatwoot
CHATWOOT:
USE_REPLY_ID: false

17
src/docs/swagger.conf.ts Normal file
View File

@@ -0,0 +1,17 @@
import { Router } from 'express';
import { join } from 'path';
import swaggerUi from 'swagger-ui-express';
import YAML from 'yamljs';
const document = YAML.load(join(process.cwd(), 'src', 'docs', 'swagger.yaml'));
const router = Router();
export const swaggerRouter = router.use('/docs', swaggerUi.serve).get(
'/docs',
swaggerUi.setup(document, {
customCssUrl: '/css/dark-theme-swagger.css',
customSiteTitle: 'Evolution API',
customfavIcon: '/images/logo.svg',
}),
);

2597
src/docs/swagger.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -71,3 +71,30 @@ export const initQueues = (instanceName: string, events: string[]) => {
amqp.bindQueue(queueName, exchangeName, event); amqp.bindQueue(queueName, exchangeName, event);
}); });
}; };
export const removeQueues = (instanceName: string, events: string[]) => {
if (!events || !events.length) return;
const channel = getAMQP();
const queues = events.map((event) => {
return `${event.replace(/_/g, '.').toLowerCase()}`;
});
const exchangeName = instanceName ?? 'evolution_exchange';
queues.forEach((event) => {
const amqp = getAMQP();
amqp.assertExchange(exchangeName, 'topic', {
durable: true,
autoDelete: false,
});
const queueName = `${instanceName}.${event}`;
amqp.deleteQueue(queueName);
});
channel.deleteExchange(exchangeName);
};

View File

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

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

View File

@@ -25,7 +25,14 @@ export async function useMultiFileAuthStateDb(
const writeData = async (data: any, key: string): Promise<any> => { const writeData = async (data: any, key: string): Promise<any> => {
try { try {
await client.connect(); 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, upsert: true,
}); });
} catch (error) { } catch (error) {
@@ -36,7 +43,10 @@ export async function useMultiFileAuthStateDb(
const readData = async (key: string): Promise<any> => { const readData = async (key: string): Promise<any> => {
try { try {
await client.connect(); 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); const creds = JSON.stringify(data);
return JSON.parse(creds, BufferJSON.reviver); return JSON.parse(creds, BufferJSON.reviver);
} catch (error) { } catch (error) {
@@ -91,7 +101,7 @@ export async function useMultiFileAuthStateDb(
}, },
}, },
saveCreds: async () => { saveCreds: async () => {
return writeData(creds, 'creds'); return await writeData(creds, 'creds');
}, },
}; };
} }

View File

@@ -149,6 +149,16 @@ export const textMessageSchema: JSONSchema7 = {
required: ['textMessage', 'number'], required: ['textMessage', 'number'],
}; };
export const presenceSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
number: { ...numberDefinition },
options: { ...optionsSchema, required: ['presence', 'delay'] },
},
required: ['options', 'number'],
};
export const pollMessageSchema: JSONSchema7 = { export const pollMessageSchema: JSONSchema7 = {
$id: v4(), $id: v4(),
type: 'object', type: 'object',
@@ -879,8 +889,10 @@ export const chatwootSchema: JSONSchema7 = {
token: { type: 'string' }, token: { type: 'string' },
url: { type: 'string' }, url: { type: 'string' },
sign_msg: { type: 'boolean', enum: [true, false] }, sign_msg: { type: 'boolean', enum: [true, false] },
sign_delimiter: { type: ['string', 'null'] },
reopen_conversation: { type: 'boolean', enum: [true, false] }, reopen_conversation: { type: 'boolean', enum: [true, false] },
conversation_pending: { type: 'boolean', enum: [true, false] }, conversation_pending: { type: 'boolean', enum: [true, false] },
auto_create: { type: 'boolean', enum: [true, false] },
}, },
required: ['enabled', 'account_id', 'token', 'url', 'sign_msg', 'reopen_conversation', 'conversation_pending'], required: ['enabled', 'account_id', 'token', 'url', 'sign_msg', 'reopen_conversation', 'conversation_pending'],
...isNotEmpty('account_id', 'token', 'url', 'sign_msg', 'reopen_conversation', 'conversation_pending'), ...isNotEmpty('account_id', 'token', 'url', 'sign_msg', 'reopen_conversation', 'conversation_pending'),
@@ -987,6 +999,49 @@ export const rabbitmqSchema: JSONSchema7 = {
...isNotEmpty('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',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
'TYPEBOT_CHANGE_STATUS',
'CHAMA_AI_ACTION',
],
},
},
},
required: ['enabled'],
...isNotEmpty('enabled'),
};
export const typebotSchema: JSONSchema7 = { export const typebotSchema: JSONSchema7 = {
$id: v4(), $id: v4(),
type: 'object', type: 'object',

View File

@@ -9,6 +9,7 @@ import {
ProfilePictureDto, ProfilePictureDto,
ProfileStatusDto, ProfileStatusDto,
ReadMessageDto, ReadMessageDto,
SendPresenceDto,
WhatsAppNumberDto, WhatsAppNumberDto,
} from '../dto/chat.dto'; } from '../dto/chat.dto';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../dto/instance.dto';
@@ -77,6 +78,11 @@ export class ChatController {
return await this.waMonitor.waInstances[instanceName].fetchChats(); 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) { public async fetchPrivacySettings({ instanceName }: InstanceDto) {
logger.verbose('requested fetchPrivacySettings from ' + instanceName + ' instance'); logger.verbose('requested fetchPrivacySettings from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].fetchPrivacySettings(); return await this.waMonitor.waInstances[instanceName].fetchPrivacySettings();

View File

@@ -5,13 +5,18 @@ import { Logger } from '../../config/logger.config';
import { BadRequestException } from '../../exceptions'; import { BadRequestException } from '../../exceptions';
import { ChatwootDto } from '../dto/chatwoot.dto'; import { ChatwootDto } from '../dto/chatwoot.dto';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../dto/instance.dto';
import { RepositoryBroker } from '../repository/repository.manager';
import { ChatwootService } from '../services/chatwoot.service'; import { ChatwootService } from '../services/chatwoot.service';
import { waMonitor } from '../whatsapp.module'; import { waMonitor } from '../whatsapp.module';
const logger = new Logger('ChatwootController'); const logger = new Logger('ChatwootController');
export class 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) { public async createChatwoot(instance: InstanceDto, data: ChatwootDto) {
logger.verbose('requested createChatwoot from ' + instance.instanceName + ' instance'); logger.verbose('requested createChatwoot from ' + instance.instanceName + ' instance');
@@ -32,6 +37,7 @@ export class ChatwootController {
if (data.sign_msg !== true && data.sign_msg !== false) { if (data.sign_msg !== true && data.sign_msg !== false) {
throw new BadRequestException('sign_msg is required'); throw new BadRequestException('sign_msg is required');
} }
if (data.sign_msg === false) data.sign_delimiter = null;
} }
if (!data.enabled) { if (!data.enabled) {
@@ -40,13 +46,15 @@ export class ChatwootController {
data.token = ''; data.token = '';
data.url = ''; data.url = '';
data.sign_msg = false; data.sign_msg = false;
data.sign_delimiter = null;
data.reopen_conversation = false; data.reopen_conversation = false;
data.conversation_pending = false; data.conversation_pending = false;
data.auto_create = false;
} }
data.name_inbox = instance.instanceName; 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; const urlServer = this.configService.get<HttpServer>('SERVER').URL;
@@ -64,7 +72,7 @@ export class ChatwootController {
const urlServer = this.configService.get<HttpServer>('SERVER').URL; const urlServer = this.configService.get<HttpServer>('SERVER').URL;
if (Object.keys(result).length === 0) { if (Object.keys(result || {}).length === 0) {
return { return {
enabled: false, enabled: false,
url: '', url: '',
@@ -86,7 +94,7 @@ export class ChatwootController {
public async receiveWebhook(instance: InstanceDto, data: any) { public async receiveWebhook(instance: InstanceDto, data: any) {
logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance'); logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance');
const chatwootService = new ChatwootService(waMonitor, this.configService); const chatwootService = new ChatwootService(waMonitor, this.configService, this.repository);
return chatwootService.receiveWebhook(instance, data); return chatwootService.receiveWebhook(instance, data);
} }

View File

@@ -1,6 +1,7 @@
import { delay } from '@whiskeysockets/baileys'; import { delay } from '@whiskeysockets/baileys';
import { isURL } from 'class-validator'; import { isURL } from 'class-validator';
import EventEmitter2 from 'eventemitter2'; import EventEmitter2 from 'eventemitter2';
import { v4 } from 'uuid';
import { ConfigService, HttpServer } from '../../config/env.config'; import { ConfigService, HttpServer } from '../../config/env.config';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../config/logger.config';
@@ -11,13 +12,15 @@ import { RepositoryBroker } from '../repository/repository.manager';
import { AuthService, OldToken } from '../services/auth.service'; import { AuthService, OldToken } from '../services/auth.service';
import { ChatwootService } from '../services/chatwoot.service'; import { ChatwootService } from '../services/chatwoot.service';
import { WAMonitoringService } from '../services/monitor.service'; import { WAMonitoringService } from '../services/monitor.service';
import { ProxyService } from '../services/proxy.service';
import { RabbitmqService } from '../services/rabbitmq.service'; import { RabbitmqService } from '../services/rabbitmq.service';
import { SettingsService } from '../services/settings.service'; import { SettingsService } from '../services/settings.service';
import { SqsService } from '../services/sqs.service';
import { TypebotService } from '../services/typebot.service'; import { TypebotService } from '../services/typebot.service';
import { WebhookService } from '../services/webhook.service'; import { WebhookService } from '../services/webhook.service';
import { WebsocketService } from '../services/websocket.service'; import { WebsocketService } from '../services/websocket.service';
import { WAStartupService } from '../services/whatsapp.service'; import { WAStartupService } from '../services/whatsapp.service';
import { wa } from '../types/wa.types'; import { Events, wa } from '../types/wa.types';
export class InstanceController { export class InstanceController {
constructor( constructor(
@@ -31,6 +34,8 @@ export class InstanceController {
private readonly settingsService: SettingsService, private readonly settingsService: SettingsService,
private readonly websocketService: WebsocketService, private readonly websocketService: WebsocketService,
private readonly rabbitmqService: RabbitmqService, private readonly rabbitmqService: RabbitmqService,
private readonly proxyService: ProxyService,
private readonly sqsService: SqsService,
private readonly typebotService: TypebotService, private readonly typebotService: TypebotService,
private readonly cache: RedisCache, private readonly cache: RedisCache,
) {} ) {}
@@ -41,6 +46,7 @@ export class InstanceController {
instanceName, instanceName,
webhook, webhook,
webhook_by_events, webhook_by_events,
webhook_base64,
events, events,
qrcode, qrcode,
number, number,
@@ -61,6 +67,8 @@ export class InstanceController {
websocket_events, websocket_events,
rabbitmq_enabled, rabbitmq_enabled,
rabbitmq_events, rabbitmq_events,
sqs_enabled,
sqs_events,
typebot_url, typebot_url,
typebot, typebot,
typebot_expire, typebot_expire,
@@ -68,6 +76,7 @@ export class InstanceController {
typebot_delay_message, typebot_delay_message,
typebot_unknown_message, typebot_unknown_message,
typebot_listening_from_me, typebot_listening_from_me,
proxy,
}: InstanceDto) { }: InstanceDto) {
try { try {
this.logger.verbose('requested createInstance from ' + instanceName + ' instance'); this.logger.verbose('requested createInstance from ' + instanceName + ' instance');
@@ -79,6 +88,13 @@ export class InstanceController {
const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache); const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache);
instance.instanceName = instanceName; instance.instanceName = instanceName;
const instanceId = v4();
instance.sendDataWebhook(Events.INSTANCE_CREATE, {
instanceName,
instanceId: instanceId,
});
this.logger.verbose('instance: ' + instance.instanceName + ' created'); this.logger.verbose('instance: ' + instance.instanceName + ' created');
this.waMonitor.waInstances[instance.instanceName] = instance; this.waMonitor.waInstances[instance.instanceName] = instance;
@@ -88,6 +104,7 @@ export class InstanceController {
const hash = await this.authService.generateHash( const hash = await this.authService.generateHash(
{ {
instanceName: instance.instanceName, instanceName: instance.instanceName,
instanceId: instanceId,
}, },
token, token,
); );
@@ -139,6 +156,7 @@ export class InstanceController {
url: webhook, url: webhook,
events: newEvents, events: newEvents,
webhook_by_events, webhook_by_events,
webhook_base64,
}); });
webhookEvents = (await this.webhookService.find(instance)).events; webhookEvents = (await this.webhookService.find(instance)).events;
@@ -241,6 +259,69 @@ export class InstanceController {
} }
} }
if (proxy) {
this.logger.verbose('creating proxy');
try {
this.proxyService.create(
instance,
{
enabled: true,
proxy,
},
false,
);
} catch (error) {
this.logger.log(error);
}
}
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',
'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) { if (typebot_url) {
try { try {
if (!isURL(typebot_url, { require_tld: false })) { if (!isURL(typebot_url, { require_tld: false })) {
@@ -268,7 +349,7 @@ export class InstanceController {
const settings: wa.LocalSettings = { const settings: wa.LocalSettings = {
reject_call: reject_call || false, reject_call: reject_call || false,
msg_call: msg_call || '', msg_call: msg_call || '',
groups_ignore: groups_ignore || false, groups_ignore: groups_ignore || true,
always_online: always_online || false, always_online: always_online || false,
read_messages: read_messages || false, read_messages: read_messages || false,
read_status: read_status || false, read_status: read_status || false,
@@ -291,12 +372,14 @@ export class InstanceController {
const result = { const result = {
instance: { instance: {
instanceName: instance.instanceName, instanceName: instance.instanceName,
instanceId: instanceId,
status: 'created', status: 'created',
}, },
hash, hash,
webhook: { webhook: {
webhook, webhook,
webhook_by_events, webhook_by_events,
webhook_base64,
events: webhookEvents, events: webhookEvents,
}, },
websocket: { websocket: {
@@ -307,6 +390,10 @@ export class InstanceController {
enabled: rabbitmq_enabled, enabled: rabbitmq_enabled,
events: rabbitmqEvents, events: rabbitmqEvents,
}, },
sqs: {
enabled: sqs_enabled,
events: sqsEvents,
},
typebot: { typebot: {
enabled: typebot_url ? true : false, enabled: typebot_url ? true : false,
url: typebot_url, url: typebot_url,
@@ -319,6 +406,7 @@ export class InstanceController {
}, },
settings, settings,
qrcode: getQrcode, qrcode: getQrcode,
proxy,
}; };
this.logger.verbose('instance created'); this.logger.verbose('instance created');
@@ -368,15 +456,8 @@ export class InstanceController {
number, number,
reopen_conversation: chatwoot_reopen_conversation || false, reopen_conversation: chatwoot_reopen_conversation || false,
conversation_pending: chatwoot_conversation_pending || false, conversation_pending: chatwoot_conversation_pending || false,
auto_create: true,
}); });
this.chatwootService.initInstanceChatwoot(
instance,
instance.instanceName.split('-cwId-')[0],
`${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
qrcode,
number,
);
} catch (error) { } catch (error) {
this.logger.log(error); this.logger.log(error);
} }
@@ -384,12 +465,14 @@ export class InstanceController {
return { return {
instance: { instance: {
instanceName: instance.instanceName, instanceName: instance.instanceName,
instanceId: instanceId,
status: 'created', status: 'created',
}, },
hash, hash,
webhook: { webhook: {
webhook, webhook,
webhook_by_events, webhook_by_events,
webhook_base64,
events: webhookEvents, events: webhookEvents,
}, },
websocket: { websocket: {
@@ -400,6 +483,10 @@ export class InstanceController {
enabled: rabbitmq_enabled, enabled: rabbitmq_enabled,
events: rabbitmqEvents, events: rabbitmqEvents,
}, },
sqs: {
enabled: sqs_enabled,
events: sqsEvents,
},
typebot: { typebot: {
enabled: typebot_url ? true : false, enabled: typebot_url ? true : false,
url: typebot_url, url: typebot_url,
@@ -423,6 +510,7 @@ export class InstanceController {
name_inbox: instance.instanceName, name_inbox: instance.instanceName,
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`, webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
}, },
proxy,
}; };
} catch (error) { } catch (error) {
this.logger.error(error.message[0]); this.logger.error(error.message[0]);
@@ -475,10 +563,19 @@ export class InstanceController {
try { try {
this.logger.verbose('requested restartInstance from ' + instanceName + ' instance'); this.logger.verbose('requested restartInstance from ' + instanceName + ' instance');
this.logger.verbose('logging out instance: ' + instanceName); const instance = this.waMonitor.waInstances[instanceName];
this.waMonitor.waInstances[instanceName]?.client?.ws?.close(); const state = instance?.connectionStatus?.state;
return { status: 'SUCCESS', error: false, response: { message: 'Instance restarted' } }; switch (state) {
case 'open':
this.logger.verbose('logging out instance: ' + instanceName);
await instance.reloadConnection();
await delay(2000);
return await this.connectionState({ instanceName });
default:
return await this.connectionState({ instanceName });
}
} catch (error) { } catch (error) {
this.logger.error(error); this.logger.error(error);
} }
@@ -494,11 +591,13 @@ export class InstanceController {
}; };
} }
public async fetchInstances({ instanceName }: InstanceDto) { public async fetchInstances({ instanceName, instanceId }: InstanceDto) {
if (instanceName) { if (instanceName) {
this.logger.verbose('requested fetchInstances from ' + instanceName + ' instance'); this.logger.verbose('requested fetchInstances from ' + instanceName + ' instance');
this.logger.verbose('instanceName: ' + instanceName); this.logger.verbose('instanceName: ' + instanceName);
return this.waMonitor.instanceInfo(instanceName); return this.waMonitor.instanceInfo(instanceName);
} else if (instanceId) {
return this.waMonitor.instanceInfoById(instanceId);
} }
this.logger.verbose('requested fetchInstances (all instances)'); this.logger.verbose('requested fetchInstances (all instances)');
@@ -534,19 +633,23 @@ export class InstanceController {
throw new BadRequestException('The "' + instanceName + '" instance needs to be disconnected'); throw new BadRequestException('The "' + instanceName + '" instance needs to be disconnected');
} }
try { try {
this.waMonitor.waInstances[instanceName]?.removeRabbitmqQueues();
if (instance.state === 'connecting') { if (instance.state === 'connecting') {
this.logger.verbose('logging out instance: ' + instanceName); this.logger.verbose('logging out instance: ' + instanceName);
await this.logout({ 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); this.logger.verbose('deleting instance: ' + instanceName);
this.waMonitor.waInstances[instanceName].sendDataWebhook(Events.INSTANCE_DELETE, {
instanceName,
instanceId: (await this.repository.auth.find(instanceName))?.instanceId,
});
delete this.waMonitor.waInstances[instanceName]; delete this.waMonitor.waInstances[instanceName];
this.eventEmitter.emit('remove.instance', instanceName, 'inner'); this.eventEmitter.emit('remove.instance', instanceName, 'inner');
return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } }; return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } };
}
} catch (error) { } catch (error) {
throw new BadRequestException(error.toString()); throw new BadRequestException(error.toString());
} }

View File

@@ -19,6 +19,7 @@ export class SettingsController {
public async findSettings(instance: InstanceDto) { public async findSettings(instance: InstanceDto) {
logger.verbose('requested findSettings from ' + instance.instanceName + ' instance'); 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,56 @@
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',
'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,17 +0,0 @@
import { Request, Response } from 'express';
import { ConfigService } from '../../config/env.config';
import { HttpStatus } from '../routers/index.router';
import { WAMonitoringService } from '../services/monitor.service';
export class ViewsController {
constructor(private readonly waMonit: WAMonitoringService, private readonly configService: ConfigService) {}
public async manager(request: Request, response: Response) {
try {
return response.status(HttpStatus.OK).render('manager');
} catch (error) {
console.log('ERROR: ', error);
}
}
}

View File

@@ -1,4 +1,4 @@
import { proto, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '@whiskeysockets/baileys'; import { proto, WAPresence, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '@whiskeysockets/baileys';
export class OnWhatsAppDto { 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 name?: string) {}
@@ -83,3 +83,20 @@ export class DeleteMessage {
remoteJid: string; remoteJid: string;
participant?: 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;
};
}

View File

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

View File

@@ -1,10 +1,12 @@
export class InstanceDto { export class InstanceDto {
instanceName: string; instanceName: string;
instanceId?: string;
qrcode?: boolean; qrcode?: boolean;
number?: string; number?: string;
token?: string; token?: string;
webhook?: string; webhook?: string;
webhook_by_events?: boolean; webhook_by_events?: boolean;
webhook_base64?: boolean;
events?: string[]; events?: string[];
reject_call?: boolean; reject_call?: boolean;
msg_call?: string; msg_call?: string;
@@ -22,6 +24,8 @@ export class InstanceDto {
websocket_events?: string[]; websocket_events?: string[];
rabbitmq_enabled?: boolean; rabbitmq_enabled?: boolean;
rabbitmq_events?: string[]; rabbitmq_events?: string[];
sqs_enabled?: boolean;
sqs_events?: string[];
typebot_url?: string; typebot_url?: string;
typebot?: string; typebot?: string;
typebot_expire?: number; typebot_expire?: number;
@@ -29,6 +33,5 @@ export class InstanceDto {
typebot_delay_message?: number; typebot_delay_message?: number;
typebot_unknown_message?: string; typebot_unknown_message?: string;
typebot_listening_from_me?: boolean; typebot_listening_from_me?: boolean;
proxy_enabled?: boolean; proxy?: string;
proxy_proxy?: string;
} }

View File

@@ -46,9 +46,13 @@ class PollMessage {
values: string[]; values: string[];
messageSecret?: Uint8Array; messageSecret?: Uint8Array;
} }
export class SendTextDto extends Metadata { export class SendTextDto extends Metadata {
textMessage: TextMessage; textMessage: TextMessage;
} }
export class SendPresence extends Metadata {
textMessage: TextMessage;
}
export class SendStatusDto extends Metadata { export class SendStatusDto extends Metadata {
statusMessage: StatusMessage; statusMessage: StatusMessage;
@@ -61,6 +65,7 @@ export class SendPollDto extends Metadata {
export type MediaType = 'image' | 'document' | 'video' | 'audio'; export type MediaType = 'image' | 'document' | 'video' | 'audio';
export class MediaMessage { export class MediaMessage {
mediatype: MediaType; mediatype: MediaType;
mimetype?: string;
caption?: string; caption?: string;
// for document // for document
fileName?: string; fileName?: string;

View File

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

View File

@@ -4,6 +4,13 @@ export class Session {
status?: string; status?: string;
createdAt?: number; createdAt?: number;
updateAt?: number; updateAt?: number;
prefilledVariables?: PrefilledVariables;
}
export class PrefilledVariables {
remoteJid?: string;
pushName?: string;
additionalData?: { [key: string]: any };
} }
export class TypebotDto { export class TypebotDto {

View File

@@ -3,4 +3,5 @@ export class WebhookDto {
url?: string; url?: string;
events?: string[]; events?: string[];
webhook_by_events?: boolean; webhook_by_events?: boolean;
webhook_base64?: boolean;
} }

View File

@@ -65,6 +65,7 @@ export async function instanceLoggedGuard(req: Request, _: Response, next: NextF
} }
if (waMonitor.waInstances[instance.instanceName]) { if (waMonitor.waInstances[instance.instanceName]) {
waMonitor.waInstances[instance.instanceName]?.removeRabbitmqQueues();
delete waMonitor.waInstances[instance.instanceName]; delete waMonitor.waInstances[instance.instanceName];
} }
} }

View File

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

View File

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

View File

@@ -7,6 +7,7 @@ export * from './message.model';
export * from './proxy.model'; export * from './proxy.model';
export * from './rabbitmq.model'; export * from './rabbitmq.model';
export * from './settings.model'; export * from './settings.model';
export * from './sqs.model';
export * from './typebot.model'; export * from './typebot.model';
export * from './webhook.model'; export * from './webhook.model';
export * from './websocket.model'; export * from './websocket.model';

View File

@@ -10,6 +10,12 @@ class Key {
participant?: string; participant?: string;
} }
class ChatwootMessage {
messageId?: number;
inboxId?: number;
conversationId?: number;
}
export class MessageRaw { export class MessageRaw {
_id?: string; _id?: string;
key?: Key; key?: Key;
@@ -22,6 +28,7 @@ export class MessageRaw {
source?: 'android' | 'web' | 'ios'; source?: 'android' | 'web' | 'ios';
source_id?: string; source_id?: string;
source_reply_id?: string; source_reply_id?: string;
chatwoot?: ChatwootMessage;
} }
const messageSchema = new Schema<MessageRaw>({ const messageSchema = new Schema<MessageRaw>({
@@ -39,8 +46,18 @@ const messageSchema = new Schema<MessageRaw>({
source: { type: String, minlength: 3, enum: ['android', 'web', 'ios'] }, source: { type: String, minlength: 3, enum: ['android', 'web', 'ios'] },
messageTimestamp: { type: Number, required: true }, messageTimestamp: { type: Number, required: true },
owner: { type: String, required: true, minlength: 1 }, owner: { type: String, required: true, minlength: 1 },
chatwoot: {
messageId: { type: Number },
inboxId: { type: Number },
conversationId: { type: Number },
},
}); });
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 const MessageModel = dbserver?.model(MessageRaw.name, messageSchema, 'messages');
export type IMessageModel = typeof MessageModel; export type IMessageModel = typeof MessageModel;

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

@@ -8,6 +8,11 @@ class Session {
status?: string; status?: string;
createdAt?: number; createdAt?: number;
updateAt?: number; updateAt?: number;
prefilledVariables?: {
remoteJid?: string;
pushName?: string;
additionalData?: { [key: string]: any };
};
} }
export class TypebotRaw { export class TypebotRaw {
@@ -40,6 +45,11 @@ const typebotSchema = new Schema<TypebotRaw>({
status: { type: String, required: true }, status: { type: String, required: true },
createdAt: { type: Number, required: true }, createdAt: { type: Number, required: true },
updateAt: { type: Number, required: true }, updateAt: { type: Number, required: true },
prefilledVariables: {
remoteJid: { type: String, required: false },
pushName: { type: String, required: false },
additionalData: { type: Schema.Types.Mixed, required: false },
},
}, },
], ],
}); });

View File

@@ -8,6 +8,7 @@ export class WebhookRaw {
enabled?: boolean; enabled?: boolean;
events?: string[]; events?: string[];
webhook_by_events?: boolean; webhook_by_events?: boolean;
webhook_base64?: boolean;
} }
const webhookSchema = new Schema<WebhookRaw>({ const webhookSchema = new Schema<WebhookRaw>({
@@ -16,6 +17,7 @@ const webhookSchema = new Schema<WebhookRaw>({
enabled: { type: Boolean, required: true }, enabled: { type: Boolean, required: true },
events: { type: [String], required: true }, events: { type: [String], required: true },
webhook_by_events: { type: Boolean, required: true }, webhook_by_events: { type: Boolean, required: true },
webhook_base64: { type: Boolean, required: true },
}); });
export const WebhookModel = dbserver?.model(WebhookRaw.name, webhookSchema, 'webhook'); export const WebhookModel = dbserver?.model(WebhookRaw.name, webhookSchema, 'webhook');

View File

@@ -19,6 +19,7 @@ export class AuthRepository extends Repository {
public async create(data: AuthRaw, instance: string): Promise<IInsert> { public async create(data: AuthRaw, instance: string): Promise<IInsert> {
try { try {
this.logger.verbose('creating auth'); this.logger.verbose('creating auth');
if (this.dbSettings.ENABLED) { if (this.dbSettings.ENABLED) {
this.logger.verbose('saving auth to db'); this.logger.verbose('saving auth to db');
const insert = await this.authModel.replaceOne({ _id: instance }, { ...data }, { upsert: true }); const insert = await this.authModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
@@ -62,4 +63,20 @@ export class AuthRepository extends Repository {
return {}; 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;
}
}
} }

View File

@@ -91,11 +91,13 @@ export class MessageRepository extends Repository {
this.logger.verbose('finding messages'); this.logger.verbose('finding messages');
if (this.dbSettings.ENABLED) { if (this.dbSettings.ENABLED) {
this.logger.verbose('finding messages in db'); this.logger.verbose('finding messages in db');
if (query?.where?.key) { for (const [o, p] of Object.entries(query?.where)) {
for (const [k, v] of Object.entries(query.where.key)) { if (typeof p === 'object' && p !== null && !Array.isArray(p)) {
query.where['key.' + k] = v; for (const [k, v] of Object.entries(p)) {
query.where[`${o}.${k}`] = v;
}
delete query.where[o];
} }
delete query?.where?.key;
} }
return await this.messageModel return await this.messageModel
@@ -144,4 +146,55 @@ export class MessageRepository extends Repository {
return []; 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);
}
}
} }

View File

@@ -14,6 +14,7 @@ import { MessageUpRepository } from './messageUp.repository';
import { ProxyRepository } from './proxy.repository'; import { ProxyRepository } from './proxy.repository';
import { RabbitmqRepository } from './rabbitmq.repository'; import { RabbitmqRepository } from './rabbitmq.repository';
import { SettingsRepository } from './settings.repository'; import { SettingsRepository } from './settings.repository';
import { SqsRepository } from './sqs.repository';
import { TypebotRepository } from './typebot.repository'; import { TypebotRepository } from './typebot.repository';
import { WebhookRepository } from './webhook.repository'; import { WebhookRepository } from './webhook.repository';
import { WebsocketRepository } from './websocket.repository'; import { WebsocketRepository } from './websocket.repository';
@@ -28,6 +29,7 @@ export class RepositoryBroker {
public readonly settings: SettingsRepository, public readonly settings: SettingsRepository,
public readonly websocket: WebsocketRepository, public readonly websocket: WebsocketRepository,
public readonly rabbitmq: RabbitmqRepository, public readonly rabbitmq: RabbitmqRepository,
public readonly sqs: SqsRepository,
public readonly typebot: TypebotRepository, public readonly typebot: TypebotRepository,
public readonly proxy: ProxyRepository, public readonly proxy: ProxyRepository,
public readonly chamaai: ChamaaiRepository, public readonly chamaai: ChamaaiRepository,
@@ -63,6 +65,7 @@ export class RepositoryBroker {
const settingsDir = join(storePath, 'settings'); const settingsDir = join(storePath, 'settings');
const websocketDir = join(storePath, 'websocket'); const websocketDir = join(storePath, 'websocket');
const rabbitmqDir = join(storePath, 'rabbitmq'); const rabbitmqDir = join(storePath, 'rabbitmq');
const sqsDir = join(storePath, 'sqs');
const typebotDir = join(storePath, 'typebot'); const typebotDir = join(storePath, 'typebot');
const proxyDir = join(storePath, 'proxy'); const proxyDir = join(storePath, 'proxy');
const chamaaiDir = join(storePath, 'chamaai'); const chamaaiDir = join(storePath, 'chamaai');
@@ -108,6 +111,10 @@ export class RepositoryBroker {
this.logger.verbose('creating rabbitmq dir: ' + rabbitmqDir); this.logger.verbose('creating rabbitmq dir: ' + rabbitmqDir);
fs.mkdirSync(rabbitmqDir, { recursive: true }); 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)) { if (!fs.existsSync(typebotDir)) {
this.logger.verbose('creating typebot dir: ' + typebotDir); this.logger.verbose('creating typebot dir: ' + typebotDir);
fs.mkdirSync(typebotDir, { recursive: true }); fs.mkdirSync(typebotDir, { 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

@@ -7,6 +7,7 @@ import {
deleteMessageSchema, deleteMessageSchema,
messageUpSchema, messageUpSchema,
messageValidateSchema, messageValidateSchema,
presenceSchema,
privacySettingsSchema, privacySettingsSchema,
profileNameSchema, profileNameSchema,
profilePictureSchema, profilePictureSchema,
@@ -26,6 +27,7 @@ import {
ProfilePictureDto, ProfilePictureDto,
ProfileStatusDto, ProfileStatusDto,
ReadMessageDto, ReadMessageDto,
SendPresenceDto,
WhatsAppNumberDto, WhatsAppNumberDto,
} from '../dto/chat.dto'; } from '../dto/chat.dto';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../dto/instance.dto';
@@ -228,6 +230,22 @@ export class ChatRouter extends RouterBroker {
return res.status(HttpStatus.OK).json(response); 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 // Profile routes
.get(this.routerPath('fetchPrivacySettings'), ...guards, async (req, res) => { .get(this.routerPath('fetchPrivacySettings'), ...guards, async (req, res) => {
logger.verbose('request received in fetchPrivacySettings'); logger.verbose('request received in fetchPrivacySettings');

View File

@@ -13,6 +13,7 @@ import { ProxyRouter } from './proxy.router';
import { RabbitmqRouter } from './rabbitmq.router'; import { RabbitmqRouter } from './rabbitmq.router';
import { MessageRouter } from './sendMessage.router'; import { MessageRouter } from './sendMessage.router';
import { SettingsRouter } from './settings.router'; import { SettingsRouter } from './settings.router';
import { SqsRouter } from './sqs.router';
import { TypebotRouter } from './typebot.router'; import { TypebotRouter } from './typebot.router';
import { ViewsRouter } from './view.router'; import { ViewsRouter } from './view.router';
import { WebhookRouter } from './webhook.router'; import { WebhookRouter } from './webhook.router';
@@ -30,20 +31,25 @@ enum HttpStatus {
const router = Router(); const router = Router();
const authType = configService.get<Auth>('AUTHENTICATION').TYPE; const authType = configService.get<Auth>('AUTHENTICATION').TYPE;
const serverConfig = configService.get('SERVER');
const guards = [instanceExistsGuard, instanceLoggedGuard, authGuard[authType]]; const guards = [instanceExistsGuard, instanceLoggedGuard, authGuard[authType]];
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8')); const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
if (!serverConfig.DISABLE_MANAGER) router.use('/manager', new ViewsRouter().router);
router router
.get('/', (req, res) => { .get('/', (req, res) => {
res.status(HttpStatus.OK).json({ res.status(HttpStatus.OK).json({
status: HttpStatus.OK, status: HttpStatus.OK,
message: 'Welcome to the Evolution API, it is working!', message: 'Welcome to the Evolution API, it is working!',
version: packageJson.version, version: packageJson.version,
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('/instance', new InstanceRouter(configService, ...guards).router)
.use('/manager', new ViewsRouter().router)
.use('/message', new MessageRouter(...guards).router) .use('/message', new MessageRouter(...guards).router)
.use('/chat', new ChatRouter(...guards).router) .use('/chat', new ChatRouter(...guards).router)
.use('/group', new GroupRouter(...guards).router) .use('/group', new GroupRouter(...guards).router)
@@ -52,6 +58,7 @@ router
.use('/settings', new SettingsRouter(...guards).router) .use('/settings', new SettingsRouter(...guards).router)
.use('/websocket', new WebsocketRouter(...guards).router) .use('/websocket', new WebsocketRouter(...guards).router)
.use('/rabbitmq', new RabbitmqRouter(...guards).router) .use('/rabbitmq', new RabbitmqRouter(...guards).router)
.use('/sqs', new SqsRouter(...guards).router)
.use('/typebot', new TypebotRouter(...guards).router) .use('/typebot', new TypebotRouter(...guards).router)
.use('/proxy', new ProxyRouter(...guards).router) .use('/proxy', new ProxyRouter(...guards).router)
.use('/chamaai', new ChamaaiRouter(...guards).router); .use('/chamaai', new ChamaaiRouter(...guards).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 { Router } from 'express';
import fs from 'fs';
import mime from 'mime-types';
import { RouterBroker } from '../abstract/abstract.router'; import { RouterBroker } from '../abstract/abstract.router';
import { viewsController } from '../whatsapp.module';
export class ViewsRouter extends RouterBroker { export class ViewsRouter extends RouterBroker {
constructor() { constructor() {
super(); super();
this.router.get('/', (req, res) => { const basePath = 'evolution-manager/dist';
return viewsController.manager(req, res);
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

@@ -46,7 +46,10 @@ export class AuthService {
this.logger.verbose('JWT token created: ' + token); 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'); 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); 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'); this.logger.verbose('APIKEY saved in database');

View File

@@ -6,12 +6,15 @@ import Jimp from 'jimp';
import mimeTypes from 'mime-types'; import mimeTypes from 'mime-types';
import path from 'path'; import path from 'path';
import { ConfigService } from '../../config/env.config'; import { ConfigService, HttpServer } from '../../config/env.config';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../config/logger.config';
import { ROOT_DIR } from '../../config/path.config'; import { ROOT_DIR } from '../../config/path.config';
import { ChatwootDto } from '../dto/chatwoot.dto'; import { ChatwootDto } from '../dto/chatwoot.dto';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../dto/instance.dto';
import { SendAudioDto, SendMediaDto, SendTextDto } from '../dto/sendMessage.dto'; import { Options, Quoted, SendAudioDto, SendMediaDto, SendTextDto } from '../dto/sendMessage.dto';
import { MessageRaw } from '../models';
import { RepositoryBroker } from '../repository/repository.manager';
import { Events } from '../types/wa.types';
import { WAMonitoringService } from './monitor.service'; import { WAMonitoringService } from './monitor.service';
export class ChatwootService { export class ChatwootService {
@@ -22,7 +25,11 @@ export class ChatwootService {
private provider: any; private provider: any;
constructor(private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService) { constructor(
private readonly waMonitor: WAMonitoringService,
private readonly configService: ConfigService,
private readonly repository: RepositoryBroker,
) {
this.messageCache = new Set(); this.messageCache = new Set();
} }
@@ -52,8 +59,7 @@ export class ChatwootService {
private async getProvider(instance: InstanceDto) { private async getProvider(instance: InstanceDto) {
this.logger.verbose('get provider to instance: ' + instance.instanceName); this.logger.verbose('get provider to instance: ' + instance.instanceName);
try { const provider = await this.waMonitor.waInstances[instance.instanceName]?.findChatwoot();
const provider = await this.waMonitor.waInstances[instance.instanceName].findChatwoot();
if (!provider) { if (!provider) {
this.logger.warn('provider not found'); this.logger.warn('provider not found');
@@ -63,14 +69,16 @@ export class ChatwootService {
this.logger.verbose('provider found'); this.logger.verbose('provider found');
return provider; return provider;
} catch (error) { // try {
this.logger.error('provider not found'); // } catch (error) {
return null; // this.logger.error('provider not found');
} // return null;
// }
} }
private async clientCw(instance: InstanceDto) { private async clientCw(instance: InstanceDto) {
this.logger.verbose('get client to instance: ' + instance.instanceName); this.logger.verbose('get client to instance: ' + instance.instanceName);
const provider = await this.getProvider(instance); const provider = await this.getProvider(instance);
if (!provider) { if (!provider) {
@@ -97,11 +105,24 @@ export class ChatwootService {
return client; return client;
} }
public create(instance: InstanceDto, data: ChatwootDto) { public async create(instance: InstanceDto, data: ChatwootDto) {
this.logger.verbose('create chatwoot: ' + instance.instanceName); this.logger.verbose('create chatwoot: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setChatwoot(data);
await this.waMonitor.waInstances[instance.instanceName].setChatwoot(data);
this.logger.verbose('chatwoot created'); this.logger.verbose('chatwoot created');
if (data.auto_create) {
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
await this.initInstanceChatwoot(
instance,
instance.instanceName.split('-cwId-')[0],
`${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
true,
data.number,
);
}
return data; return data;
} }
@@ -229,10 +250,6 @@ export class ChatwootService {
inbox_id: inboxId.toString(), inbox_id: inboxId.toString(),
}; };
if (this.provider.conversation_pending) {
data['status'] = 'pending';
}
const conversation = await client.conversations.create({ const conversation = await client.conversations.create({
accountId: this.provider.account_id, accountId: this.provider.account_id,
data, data,
@@ -338,6 +355,7 @@ export class ChatwootService {
} }
this.logger.verbose('update contact in chatwoot'); this.logger.verbose('update contact in chatwoot');
try {
const contact = await client.contacts.update({ const contact = await client.contacts.update({
accountId: this.provider.account_id, accountId: this.provider.account_id,
id, id,
@@ -346,6 +364,9 @@ export class ChatwootService {
this.logger.verbose('contact updated'); this.logger.verbose('contact updated');
return contact; return contact;
} catch (error) {
this.logger.error(error);
}
} }
public async findContact(instance: InstanceDto, phoneNumber: string) { public async findContact(instance: InstanceDto, phoneNumber: string) {
@@ -488,6 +509,9 @@ export class ChatwootService {
avatar_url: picture_url.profilePictureUrl || null, avatar_url: picture_url.profilePictureUrl || null,
}); });
} }
if (!contact) {
contact = await this.findContact(instance, chatId);
}
} else { } else {
const jid = isGroup ? null : body.key.remoteJid; const jid = isGroup ? null : body.key.remoteJid;
contact = await this.createContact( contact = await this.createContact(
@@ -619,6 +643,7 @@ export class ChatwootService {
encoding: string; encoding: string;
filename: string; filename: string;
}[], }[],
messageBody?: any,
) { ) {
this.logger.verbose('create message to instance: ' + instance.instanceName); this.logger.verbose('create message to instance: ' + instance.instanceName);
@@ -629,6 +654,8 @@ export class ChatwootService {
return null; return null;
} }
const replyToIds = await this.getReplyToIds(messageBody, instance);
this.logger.verbose('create message in chatwoot'); this.logger.verbose('create message in chatwoot');
const message = await client.messages.create({ const message = await client.messages.create({
accountId: this.provider.account_id, accountId: this.provider.account_id,
@@ -638,6 +665,9 @@ export class ChatwootService {
message_type: messageType, message_type: messageType,
attachments: attachments, attachments: attachments,
private: privateMessage || false, private: privateMessage || false,
content_attributes: {
...replyToIds,
},
}, },
}); });
@@ -733,6 +763,8 @@ export class ChatwootService {
file: string, file: string,
messageType: 'incoming' | 'outgoing' | undefined, messageType: 'incoming' | 'outgoing' | undefined,
content?: string, content?: string,
instance?: InstanceDto,
messageBody?: any,
) { ) {
this.logger.verbose('send data to chatwoot'); this.logger.verbose('send data to chatwoot');
@@ -749,6 +781,16 @@ export class ChatwootService {
this.logger.verbose('temp file found'); this.logger.verbose('temp file found');
data.append('attachments[]', createReadStream(file)); data.append('attachments[]', createReadStream(file));
if (messageBody && instance) {
const replyToIds = await this.getReplyToIds(messageBody, instance);
if (replyToIds.in_reply_to || replyToIds.in_reply_to_external_id) {
data.append('content_attributes', {
...replyToIds,
});
}
}
this.logger.verbose('get client to instance: ' + this.provider.instanceName); this.logger.verbose('get client to instance: ' + this.provider.instanceName);
const config = { const config = {
method: 'post', method: 'post',
@@ -869,7 +911,7 @@ export class ChatwootService {
} }
} }
public async sendAttachment(waInstance: any, number: string, media: any, caption?: string) { public async sendAttachment(waInstance: any, number: string, media: any, caption?: string, options?: Options) {
this.logger.verbose('send attachment to instance: ' + waInstance.instanceName); this.logger.verbose('send attachment to instance: ' + waInstance.instanceName);
try { try {
@@ -879,7 +921,11 @@ export class ChatwootService {
const fileName = decodeURIComponent(parts[parts.length - 1]); const fileName = decodeURIComponent(parts[parts.length - 1]);
this.logger.verbose('file name: ' + fileName); this.logger.verbose('file name: ' + fileName);
const mimeType = mimeTypes.lookup(fileName).toString(); const response = await axios.get(media, {
responseType: 'arraybuffer',
});
const mimeType = response.headers['content-type'];
this.logger.verbose('mime type: ' + mimeType); this.logger.verbose('mime type: ' + mimeType);
let type = 'document'; let type = 'document';
@@ -911,13 +957,14 @@ export class ChatwootService {
options: { options: {
delay: 1200, delay: 1200,
presence: 'recording', presence: 'recording',
...options,
}, },
}; };
await waInstance?.audioWhatsapp(data); const messageSent = await waInstance?.audioWhatsapp(data, true);
this.logger.verbose('audio sent'); this.logger.verbose('audio sent');
return; return messageSent;
} }
this.logger.verbose('send media to instance: ' + waInstance.instanceName); this.logger.verbose('send media to instance: ' + waInstance.instanceName);
@@ -931,6 +978,7 @@ export class ChatwootService {
options: { options: {
delay: 1200, delay: 1200,
presence: 'composing', presence: 'composing',
...options,
}, },
}; };
@@ -939,10 +987,10 @@ export class ChatwootService {
data.mediaMessage.caption = caption; data.mediaMessage.caption = caption;
} }
await waInstance?.mediaMessage(data); const messageSent = await waInstance?.mediaMessage(data, true);
this.logger.verbose('media sent'); this.logger.verbose('media sent');
return; return messageSent;
} catch (error) { } catch (error) {
this.logger.error(error); this.logger.error(error);
} }
@@ -961,15 +1009,46 @@ export class ChatwootService {
} }
this.logger.verbose('check if is bot'); this.logger.verbose('check if is bot');
if (!body?.conversation || body.private || body.event === 'message_updated') return { message: 'bot' }; if (
!body?.conversation ||
body.private ||
(body.event === 'message_updated' && !body.content_attributes?.deleted)
) {
return { message: 'bot' };
}
this.logger.verbose('check if is group'); this.logger.verbose('check if is group');
const chatId = const chatId =
body.conversation.meta.sender?.phone_number?.replace('+', '') || body.conversation.meta.sender?.identifier; body.conversation.meta.sender?.phone_number?.replace('+', '') || body.conversation.meta.sender?.identifier;
const messageReceived = body.content; // Chatwoot to Whatsapp
const senderName = body?.sender?.name; const messageReceived = body.content
? body.content
.replaceAll(/(?<!\*)\*((?!\s)([^\n*]+?)(?<!\s))\*(?!\*)/g, '_$1_') // Substitui * por _
.replaceAll(/\*{2}((?!\s)([^\n*]+?)(?<!\s))\*{2}/g, '*$1*') // Substitui ** por *
.replaceAll(/~{2}((?!\s)([^\n*]+?)(?<!\s))~{2}/g, '~$1~') // Substitui ~~ por ~
.replaceAll(/(?<!`)`((?!\s)([^`*]+?)(?<!\s))`(?!`)/g, '```$1```') // Substitui ` por ```
: body.content;
const senderName = body?.sender?.available_name || body?.sender?.name;
const waInstance = this.waMonitor.waInstances[instance.instanceName]; const waInstance = this.waMonitor.waInstances[instance.instanceName];
this.logger.verbose('check if is a message deletion');
if (body.event === 'message_updated' && body.content_attributes?.deleted) {
const message = await this.repository.message.find({
where: {
owner: instance.instanceName,
chatwoot: {
messageId: body.id,
},
},
limit: 1,
});
if (message.length && message[0].key?.id) {
await waInstance?.client.sendMessage(message[0].key.remoteJid, { delete: message[0].key });
}
return { message: 'bot' };
}
if (chatId === '123456' && body.message_type === 'outgoing') { if (chatId === '123456' && body.message_type === 'outgoing') {
this.logger.verbose('check if is command'); this.logger.verbose('check if is command');
@@ -980,6 +1059,10 @@ export class ChatwootService {
const state = waInstance?.connectionStatus?.state; const state = waInstance?.connectionStatus?.state;
if (state !== 'open') { if (state !== 'open') {
if (state === 'close') {
this.logger.verbose('request cleaning up instance: ' + instance.instanceName);
// await this.waMonitor.cleaningUp(instance.instanceName);
}
this.logger.verbose('connect to whatsapp'); this.logger.verbose('connect to whatsapp');
const number = command.split(':')[1]; const number = command.split(':')[1];
await waInstance.connectToWhatsapp(number); await waInstance.connectToWhatsapp(number);
@@ -1043,7 +1126,13 @@ export class ChatwootService {
if (senderName === null || senderName === undefined) { if (senderName === null || senderName === undefined) {
formatText = messageReceived; formatText = messageReceived;
} else { } else {
formatText = this.provider.sign_msg ? `*${senderName}:*\n${messageReceived}` : messageReceived; const formattedDelimiter = this.provider.sign_delimiter
? this.provider.sign_delimiter.replaceAll('\\n', '\n')
: '\n';
const textToConcat = this.provider.sign_msg ? [`*${senderName}:*`] : [];
textToConcat.push(messageReceived);
formatText = textToConcat.join(formattedDelimiter);
} }
for (const message of body.conversation.messages) { for (const message of body.conversation.messages) {
@@ -1057,7 +1146,30 @@ export class ChatwootService {
formatText = null; formatText = null;
} }
await this.sendAttachment(waInstance, chatId, attachment.data_url, formatText); const options: Options = {
quoted: await this.getQuotedMessage(body, instance),
};
const messageSent = await this.sendAttachment(
waInstance,
chatId,
attachment.data_url,
formatText,
options,
);
this.updateChatwootMessageId(
{
...messageSent,
owner: instance.instanceName,
},
{
messageId: body.id,
inboxId: body.inbox?.id,
conversationId: body.conversation?.id,
},
instance,
);
} }
} else { } else {
this.logger.verbose('message is text'); this.logger.verbose('message is text');
@@ -1071,10 +1183,24 @@ export class ChatwootService {
options: { options: {
delay: 1200, delay: 1200,
presence: 'composing', presence: 'composing',
quoted: await this.getQuotedMessage(body, instance),
}, },
}; };
await waInstance?.textMessage(data); const messageSent = await waInstance?.textMessage(data, true);
this.updateChatwootMessageId(
{
...messageSent,
owner: instance.instanceName,
},
{
messageId: body.id,
inboxId: body.inbox?.id,
conversationId: body.conversation?.id,
},
instance,
);
} }
} }
} }
@@ -1106,6 +1232,78 @@ export class ChatwootService {
} }
} }
private updateChatwootMessageId(
message: MessageRaw,
chatwootMessageIds: MessageRaw['chatwoot'],
instance: InstanceDto,
) {
if (!chatwootMessageIds.messageId || !message?.key?.id) {
return;
}
message.chatwoot = chatwootMessageIds;
this.repository.message.update([message], instance.instanceName, true);
}
private async getMessageByKeyId(instance: InstanceDto, keyId: string): Promise<MessageRaw> {
const messages = await this.repository.message.find({
where: {
key: {
id: keyId,
},
owner: instance.instanceName,
},
limit: 1,
});
return messages.length ? messages[0] : null;
}
private async getReplyToIds(
msg: any,
instance: InstanceDto,
): Promise<{ in_reply_to: string; in_reply_to_external_id: string }> {
let inReplyTo = null;
let inReplyToExternalId = null;
if (msg) {
inReplyToExternalId = msg.message?.extendedTextMessage?.contextInfo?.stanzaId;
if (inReplyToExternalId) {
const message = await this.getMessageByKeyId(instance, inReplyToExternalId);
if (message?.chatwoot?.messageId) {
inReplyTo = message.chatwoot.messageId;
}
}
}
return {
in_reply_to: inReplyTo,
in_reply_to_external_id: inReplyToExternalId,
};
}
private async getQuotedMessage(msg: any, instance: InstanceDto): Promise<Quoted> {
if (msg?.content_attributes?.in_reply_to) {
const message = await this.repository.message.find({
where: {
chatwoot: {
messageId: msg?.content_attributes?.in_reply_to,
},
owner: instance.instanceName,
},
limit: 1,
});
if (message.length && message[0]?.key?.id) {
return {
key: message[0].key,
message: message[0].message,
};
}
}
return null;
}
private isMediaMessage(message: any) { private isMediaMessage(message: any) {
this.logger.verbose('check if is media message'); this.logger.verbose('check if is media message');
const media = [ const media = [
@@ -1132,13 +1330,25 @@ export class ChatwootService {
thumbnailUrl: string; thumbnailUrl: string;
sourceUrl: string; sourceUrl: string;
} }
const adsMessage: AdsMessage | undefined = msg.extendedTextMessage?.contextInfo.externalAdReply; const adsMessage: AdsMessage | undefined = msg.extendedTextMessage?.contextInfo?.externalAdReply;
this.logger.verbose('Get ads message if it exist'); this.logger.verbose('Get ads message if it exist');
adsMessage && this.logger.verbose('Ads message: ' + adsMessage); adsMessage && this.logger.verbose('Ads message: ' + adsMessage);
return adsMessage; return adsMessage;
} }
private getReactionMessage(msg: any) {
interface ReactionMessage {
key: MessageRaw['key'];
text: string;
}
const reactionMessage: ReactionMessage | undefined = msg?.reactionMessage;
this.logger.verbose('Get reaction message if it exists');
reactionMessage && this.logger.verbose('Reaction message: ' + reactionMessage);
return reactionMessage;
}
private getTypeMessage(msg: any) { private getTypeMessage(msg: any) {
this.logger.verbose('get type message'); this.logger.verbose('get type message');
@@ -1205,6 +1415,11 @@ export class ChatwootService {
formattedContact += `\n**number ${numberCount}:** ${phoneNumber}`; formattedContact += `\n**number ${numberCount}:** ${phoneNumber}`;
numberCount++; numberCount++;
} }
if (key.includes('TEL')) {
const phoneNumber = contactInfo[key];
formattedContact += `\n**number:** ${phoneNumber}`;
numberCount++;
}
}); });
this.logger.verbose('message content: ' + formattedContact); this.logger.verbose('message content: ' + formattedContact);
@@ -1233,6 +1448,11 @@ export class ChatwootService {
formattedContact += `\n**number ${numberCount}:** ${phoneNumber}`; formattedContact += `\n**number ${numberCount}:** ${phoneNumber}`;
numberCount++; numberCount++;
} }
if (key.includes('TEL')) {
const phoneNumber = contactInfo[key];
formattedContact += `\n**number:** ${phoneNumber}`;
numberCount++;
}
}); });
return formattedContact; return formattedContact;
@@ -1265,13 +1485,6 @@ export class ChatwootService {
public async eventWhatsapp(event: string, instance: InstanceDto, body: any) { public async eventWhatsapp(event: string, instance: InstanceDto, body: any) {
this.logger.verbose('event whatsapp to instance: ' + instance.instanceName); this.logger.verbose('event whatsapp to instance: ' + instance.instanceName);
try { try {
const client = await this.clientCw(instance);
if (!client) {
this.logger.warn('client not found');
return null;
}
const waInstance = this.waMonitor.waInstances[instance.instanceName]; const waInstance = this.waMonitor.waInstances[instance.instanceName];
if (!waInstance) { if (!waInstance) {
@@ -1279,7 +1492,14 @@ export class ChatwootService {
return null; return null;
} }
if (event === 'messages.upsert') { const client = await this.clientCw(instance);
if (!client) {
this.logger.warn('client not found');
return null;
}
if (event === 'messages.upsert' || event === 'send.message') {
this.logger.verbose('event messages.upsert'); this.logger.verbose('event messages.upsert');
if (body.key.remoteJid === 'status@broadcast') { if (body.key.remoteJid === 'status@broadcast') {
@@ -1288,13 +1508,30 @@ export class ChatwootService {
} }
this.logger.verbose('get conversation message'); this.logger.verbose('get conversation message');
const bodyMessage = await this.getConversationMessage(body.message);
// Whatsapp to Chatwoot
const originalMessage = await this.getConversationMessage(body.message);
const bodyMessage = originalMessage
? originalMessage
.replaceAll(/\*((?!\s)([^\n*]+?)(?<!\s))\*/g, '**$1**')
.replaceAll(/_((?!\s)([^\n_]+?)(?<!\s))_/g, '*$1*')
.replaceAll(/~((?!\s)([^\n~]+?)(?<!\s))~/g, '~~$1~~')
: originalMessage;
this.logger.verbose('body message: ' + bodyMessage);
if (bodyMessage && bodyMessage.includes('Por favor, classifique esta conversa, http')) {
this.logger.verbose('conversation is closed');
return;
}
const isMedia = this.isMediaMessage(body.message); const isMedia = this.isMediaMessage(body.message);
const adsMessage = this.getAdsMessage(body.message); const adsMessage = this.getAdsMessage(body.message);
if (!bodyMessage && !isMedia) { const reactionMessage = this.getReactionMessage(body.message);
if (!bodyMessage && !isMedia && !reactionMessage) {
this.logger.warn('no body message found'); this.logger.warn('no body message found');
return; return;
} }
@@ -1353,7 +1590,7 @@ export class ChatwootService {
} }
this.logger.verbose('send data to chatwoot'); this.logger.verbose('send data to chatwoot');
const send = await this.sendData(getConversation, fileName, messageType, content); const send = await this.sendData(getConversation, fileName, messageType, content, instance, body);
if (!send) { if (!send) {
this.logger.warn('message not sent'); this.logger.warn('message not sent');
@@ -1374,7 +1611,7 @@ export class ChatwootService {
this.logger.verbose('message is not group'); this.logger.verbose('message is not group');
this.logger.verbose('send data to chatwoot'); this.logger.verbose('send data to chatwoot');
const send = await this.sendData(getConversation, fileName, messageType, bodyMessage); const send = await this.sendData(getConversation, fileName, messageType, bodyMessage, instance, body);
if (!send) { if (!send) {
this.logger.warn('message not sent'); this.logger.warn('message not sent');
@@ -1394,6 +1631,35 @@ export class ChatwootService {
} }
} }
this.logger.verbose('check if has ReactionMessage');
if (reactionMessage) {
this.logger.verbose('send data to chatwoot');
if (reactionMessage.text) {
const send = await this.createMessage(
instance,
getConversation,
reactionMessage.text,
messageType,
false,
[],
{
message: { extendedTextMessage: { contextInfo: { stanzaId: reactionMessage.key.id } } },
},
);
if (!send) {
this.logger.warn('message not sent');
return;
}
this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`);
this.messageCache = this.loadMessageCache();
this.messageCache.add(send.id.toString());
this.logger.verbose('save message cache');
this.saveMessageCache();
}
return;
}
this.logger.verbose('check if has Ads Message'); this.logger.verbose('check if has Ads Message');
if (adsMessage) { if (adsMessage) {
this.logger.verbose('message is from Ads'); this.logger.verbose('message is from Ads');
@@ -1436,6 +1702,8 @@ export class ChatwootService {
fileName, fileName,
messageType, messageType,
`${bodyMessage}\n\n\n**${title}**\n${description}\n${adsMessage.sourceUrl}`, `${bodyMessage}\n\n\n**${title}**\n${description}\n${adsMessage.sourceUrl}`,
instance,
body,
); );
if (!send) { if (!send) {
@@ -1471,7 +1739,7 @@ export class ChatwootService {
} }
this.logger.verbose('send data to chatwoot'); this.logger.verbose('send data to chatwoot');
const send = await this.createMessage(instance, getConversation, content, messageType); const send = await this.createMessage(instance, getConversation, content, messageType, false, [], body);
if (!send) { if (!send) {
this.logger.warn('message not sent'); this.logger.warn('message not sent');
@@ -1492,7 +1760,7 @@ export class ChatwootService {
this.logger.verbose('message is not group'); this.logger.verbose('message is not group');
this.logger.verbose('send data to chatwoot'); this.logger.verbose('send data to chatwoot');
const send = await this.createMessage(instance, getConversation, bodyMessage, messageType); const send = await this.createMessage(instance, getConversation, bodyMessage, messageType, false, [], body);
if (!send) { if (!send) {
this.logger.warn('message not sent'); this.logger.warn('message not sent');
@@ -1512,6 +1780,25 @@ export class ChatwootService {
} }
} }
if (event === Events.MESSAGES_DELETE) {
this.logger.verbose('deleting message from instance: ' + instance.instanceName);
if (!body?.key?.id) {
this.logger.warn('message id not found');
return;
}
const message = await this.getMessageByKeyId(instance, body.key.id);
if (message?.chatwoot?.messageId && message?.chatwoot?.conversationId) {
this.logger.verbose('deleting message in chatwoot. Message id: ' + body.key.id);
return await client.messages.delete({
accountId: this.provider.account_id,
conversationId: message.chatwoot.conversationId,
messageId: message.chatwoot.messageId,
});
}
}
if (event === 'status.instance') { if (event === 'status.instance') {
this.logger.verbose('event status.instance'); this.logger.verbose('event status.instance');
const data = body; const data = body;
@@ -1528,16 +1815,18 @@ export class ChatwootService {
await this.createBotMessage(instance, msgStatus, 'incoming'); await this.createBotMessage(instance, msgStatus, 'incoming');
} }
// if (event === 'connection.update') { if (event === 'connection.update') {
// this.logger.verbose('event connection.update'); this.logger.verbose('event connection.update');
// if (body.status === 'open') { if (body.status === 'open') {
// const msgConnection = `🚀 Connection successfully established!`; // if we have qrcode count then we understand that a new connection was established
if (this.waMonitor.waInstances[instance.instanceName].qrCode.count > 0) {
// this.logger.verbose('send message to chatwoot'); const msgConnection = `🚀 Connection successfully established!`;
// await this.createBotMessage(instance, msgConnection, 'incoming'); this.logger.verbose('send message to chatwoot');
// } await this.createBotMessage(instance, msgConnection, 'incoming');
// } }
}
}
if (event === 'qrcode.updated') { if (event === 'qrcode.updated') {
this.logger.verbose('event qrcode.updated'); this.logger.verbose('event qrcode.updated');

View File

@@ -7,16 +7,23 @@ import { join } from 'path';
import { Auth, ConfigService, Database, DelInstance, HttpServer, Redis } from '../../config/env.config'; import { Auth, ConfigService, Database, DelInstance, HttpServer, Redis } from '../../config/env.config';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../config/logger.config';
import { INSTANCE_DIR, STORE_DIR } from '../../config/path.config'; import { INSTANCE_DIR, STORE_DIR } from '../../config/path.config';
import { NotFoundException } from '../../exceptions';
import { dbserver } from '../../libs/db.connect'; import { dbserver } from '../../libs/db.connect';
import { RedisCache } from '../../libs/redis.client'; import { RedisCache } from '../../libs/redis.client';
import { import {
AuthModel, AuthModel,
ChamaaiModel,
// ChatModel,
ChatwootModel, ChatwootModel,
ContactModel, // ContactModel,
MessageModel, // MessageModel,
MessageUpModel, // MessageUpModel,
ProxyModel,
RabbitmqModel,
SettingsModel, SettingsModel,
TypebotModel,
WebhookModel, WebhookModel,
WebsocketModel,
} from '../models'; } from '../models';
import { RepositoryBroker } from '../repository/repository.manager'; import { RepositoryBroker } from '../repository/repository.manager';
import { WAStartupService } from './whatsapp.service'; import { WAStartupService } from './whatsapp.service';
@@ -32,7 +39,7 @@ export class WAMonitoringService {
this.removeInstance(); this.removeInstance();
this.noConnection(); this.noConnection();
this.delInstanceFiles(); // this.delInstanceFiles();
Object.assign(this.db, configService.get<Database>('DATABASE')); Object.assign(this.db, configService.get<Database>('DATABASE'));
Object.assign(this.redis, configService.get<Redis>('REDIS')); Object.assign(this.redis, configService.get<Redis>('REDIS'));
@@ -63,8 +70,10 @@ export class WAMonitoringService {
await this.waInstances[instance]?.client?.logout('Log out instance: ' + instance); await this.waInstances[instance]?.client?.logout('Log out instance: ' + instance);
this.waInstances[instance]?.client?.ws?.close(); this.waInstances[instance]?.client?.ws?.close();
this.waInstances[instance]?.client?.end(undefined); this.waInstances[instance]?.client?.end(undefined);
this.waInstances[instance]?.removeRabbitmqQueues();
delete this.waInstances[instance]; delete this.waInstances[instance];
} else { } else {
this.waInstances[instance]?.removeRabbitmqQueues();
delete this.waInstances[instance]; delete this.waInstances[instance];
this.eventEmitter.emit('remove.instance', instance, 'inner'); this.eventEmitter.emit('remove.instance', instance, 'inner');
} }
@@ -75,57 +84,161 @@ export class WAMonitoringService {
public async instanceInfo(instanceName?: string) { public async instanceInfo(instanceName?: string) {
this.logger.verbose('get instance info'); this.logger.verbose('get instance info');
if (instanceName && !this.waInstances[instanceName]) {
throw new NotFoundException(`Instance "${instanceName}" not found`);
}
const instances: any[] = [];
for await (const [key, value] of Object.entries(this.waInstances)) {
if (value) {
this.logger.verbose('get instance info: ' + key);
let chatwoot: any;
const urlServer = this.configService.get<HttpServer>('SERVER').URL; const urlServer = this.configService.get<HttpServer>('SERVER').URL;
const instances: any[] = await Promise.all(
Object.entries(this.waInstances).map(async ([key, value]) => {
const status = value?.connectionStatus?.state || 'unknown';
if (status === 'unknown') {
return null;
}
if (status === 'open') {
this.logger.verbose('instance: ' + key + ' - connectionStatus: open');
}
const instanceData: any = {
instance: {
instanceName: key,
owner: value.wuid,
profileName: (await value.getProfileName()) || 'not loaded',
profilePictureUrl: value.profilePictureUrl,
profileStatus: (await value.getProfileStatus()) || '',
status: status,
},
};
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
instanceData.instance.serverUrl = urlServer;
instanceData.instance.apikey = (await this.repository.auth.find(key))?.apikey;
const findChatwoot = await this.waInstances[key].findChatwoot(); const findChatwoot = await this.waInstances[key].findChatwoot();
if (findChatwoot && findChatwoot.enabled) { if (findChatwoot && findChatwoot.enabled) {
instanceData.instance.chatwoot = { chatwoot = {
...findChatwoot, ...findChatwoot,
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(key)}`, webhook_url: `${urlServer}/chatwoot/webhook/${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,
profileStatus: (await value.getProfileStatus()) || '',
status: value.connectionStatus.state,
},
};
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['chatwoot'] = chatwoot;
} }
return instanceData; instances.push(instanceData);
}), } else {
).then((results) => results.filter((instance) => instance !== null)); this.logger.verbose('instance: ' + key + ' - connectionStatus: ' + value.connectionStatus.state);
const instanceData = {
instance: {
instanceName: key,
instanceId: (await this.repository.auth.find(key))?.instanceId,
status: value.connectionStatus.state,
},
};
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['chatwoot'] = chatwoot;
}
instances.push(instanceData);
}
}
}
this.logger.verbose('return instance info: ' + instances.length); this.logger.verbose('return instance info: ' + instances.length);
if (instanceName) { return instances.find((i) => i.instance.instanceName === instanceName) ?? instances;
const instance = instances.find((i) => i.instance.instanceName === instanceName);
return instance || [];
} }
return instances; public async instanceInfoById(instanceId?: string) {
this.logger.verbose('get instance info');
const instanceName = await this.repository.auth.findInstanceNameById(instanceId);
if (!instanceName) {
throw new NotFoundException(`Instance "${instanceId}" not found`);
}
if (instanceName && !this.waInstances[instanceName]) {
throw new NotFoundException(`Instance "${instanceName}" not found`);
}
const instances: any[] = [];
for await (const [key, value] of Object.entries(this.waInstances)) {
if (value) {
this.logger.verbose('get instance info: ' + key);
let chatwoot: any;
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
const findChatwoot = await this.waInstances[key].findChatwoot();
if (findChatwoot && findChatwoot.enabled) {
chatwoot = {
...findChatwoot,
webhook_url: `${urlServer}/chatwoot/webhook/${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,
profileStatus: (await value.getProfileStatus()) || '',
status: value.connectionStatus.state,
},
};
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['chatwoot'] = chatwoot;
}
instances.push(instanceData);
} else {
this.logger.verbose('instance: ' + key + ' - connectionStatus: ' + value.connectionStatus.state);
const instanceData = {
instance: {
instanceName: key,
instanceId: (await this.repository.auth.find(key))?.instanceId,
status: value.connectionStatus.state,
},
};
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['chatwoot'] = chatwoot;
}
instances.push(instanceData);
}
}
}
this.logger.verbose('return instance info: ' + instances.length);
return instances.find((i) => i.instance.instanceName === instanceName) ?? instances;
} }
private delInstanceFiles() { private delInstanceFiles() {
@@ -164,6 +277,7 @@ export class WAMonitoringService {
public async cleaningUp(instanceName: string) { public async cleaningUp(instanceName: string) {
this.logger.verbose('cleaning up instance: ' + instanceName); this.logger.verbose('cleaning up instance: ' + instanceName);
if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) { if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
this.logger.verbose('cleaning up instance in database: ' + instanceName); this.logger.verbose('cleaning up instance in database: ' + instanceName);
await this.repository.dbServer.connect(); await this.repository.dbServer.connect();
@@ -210,13 +324,19 @@ export class WAMonitoringService {
this.logger.verbose('cleaning store database instance: ' + instanceName); this.logger.verbose('cleaning store database instance: ' + instanceName);
await AuthModel.deleteMany({ owner: instanceName }); // await ChatModel.deleteMany({ owner: instanceName });
await ContactModel.deleteMany({ owner: instanceName }); // await ContactModel.deleteMany({ owner: instanceName });
await MessageModel.deleteMany({ owner: instanceName }); // await MessageUpModel.deleteMany({ owner: instanceName });
await MessageUpModel.deleteMany({ owner: instanceName }); // await MessageModel.deleteMany({ owner: instanceName });
await AuthModel.deleteMany({ _id: instanceName }); await AuthModel.deleteMany({ _id: instanceName });
await WebhookModel.deleteMany({ _id: instanceName }); await WebhookModel.deleteMany({ _id: instanceName });
await ChatwootModel.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 SettingsModel.deleteMany({ _id: instanceName });
return; return;
@@ -242,7 +362,6 @@ export class WAMonitoringService {
const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache); const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache);
instance.instanceName = name; instance.instanceName = name;
this.logger.verbose('Instance loaded: ' + name); this.logger.verbose('Instance loaded: ' + name);
await instance.connectToWhatsapp(); await instance.connectToWhatsapp();
this.logger.verbose('connectToWhatsapp: ' + name); this.logger.verbose('connectToWhatsapp: ' + name);

View File

@@ -9,9 +9,9 @@ export class ProxyService {
private readonly logger = new Logger(ProxyService.name); private readonly logger = new Logger(ProxyService.name);
public create(instance: InstanceDto, data: ProxyDto) { public create(instance: InstanceDto, data: ProxyDto, reload = true) {
this.logger.verbose('create proxy: ' + instance.instanceName); this.logger.verbose('create proxy: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setProxy(data); this.waMonitor.waInstances[instance.instanceName].setProxy(data, reload);
return { proxy: { ...instance, proxy: data } }; return { proxy: { ...instance, proxy: data } };
} }

View File

@@ -26,7 +26,7 @@ export class SettingsService {
return result; return result;
} catch (error) { } 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: [] };
}
}
}

View File

@@ -1,5 +1,7 @@
import axios from 'axios'; import axios from 'axios';
import EventEmitter2 from 'eventemitter2';
import { ConfigService, Typebot } from '../../config/env.config';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../dto/instance.dto';
import { Session, TypebotDto } from '../dto/typebot.dto'; import { Session, TypebotDto } from '../dto/typebot.dto';
@@ -8,7 +10,18 @@ import { Events } from '../types/wa.types';
import { WAMonitoringService } from './monitor.service'; import { WAMonitoringService } from './monitor.service';
export class TypebotService { export class TypebotService {
constructor(private readonly waMonitor: WAMonitoringService) {} constructor(
private readonly waMonitor: WAMonitoringService,
private readonly configService: ConfigService,
private readonly eventEmitter: EventEmitter2,
) {
this.eventEmitter.on('typebot:end', async (data) => {
const keep_open = this.configService.get<Typebot>('TYPEBOT').KEEP_OPEN;
if (keep_open) return;
await this.clearSessions(data.instance, data.remoteJid);
});
}
private readonly logger = new Logger(TypebotService.name); private readonly logger = new Logger(TypebotService.name);
@@ -47,7 +60,7 @@ export class TypebotService {
findData.sessions.splice(findData.sessions.indexOf(session), 1); findData.sessions.splice(findData.sessions.indexOf(session), 1);
const typebotData = { const typebotData = {
enabled: true, enabled: findData.enabled,
url: findData.url, url: findData.url,
typebot: findData.typebot, typebot: findData.typebot,
expire: findData.expire, expire: findData.expire,
@@ -68,10 +81,24 @@ export class TypebotService {
session.status = status; session.status = status;
} }
}); });
} else if (status === 'paused') {
const session: Session = {
remoteJid: remoteJid,
sessionId: Math.floor(Math.random() * 10000000000).toString(),
status: status,
createdAt: Date.now(),
updateAt: Date.now(),
prefilledVariables: {
remoteJid: remoteJid,
pushName: '',
additionalData: {},
},
};
findData.sessions.push(session);
} }
const typebotData = { const typebotData = {
enabled: true, enabled: findData.enabled,
url: findData.url, url: findData.url,
typebot: findData.typebot, typebot: findData.typebot,
expire: findData.expire, expire: findData.expire,
@@ -95,31 +122,117 @@ export class TypebotService {
return { typebot: { ...instance, typebot: typebotData } }; return { typebot: { ...instance, typebot: typebotData } };
} }
public async clearSessions(instance: InstanceDto, remoteJid: string) {
const findTypebot = await this.find(instance);
const sessions = (findTypebot.sessions as Session[]) ?? [];
const sessionWithRemoteJid = sessions.filter((session) => session.remoteJid === remoteJid);
if (sessionWithRemoteJid.length > 0) {
sessionWithRemoteJid.forEach((session) => {
sessions.splice(sessions.indexOf(session), 1);
});
const typebotData = {
enabled: findTypebot.enabled,
url: findTypebot.url,
typebot: findTypebot.typebot,
expire: findTypebot.expire,
keyword_finish: findTypebot.keyword_finish,
delay_message: findTypebot.delay_message,
unknown_message: findTypebot.unknown_message,
listening_from_me: findTypebot.listening_from_me,
sessions,
};
this.create(instance, typebotData);
return sessions;
}
return sessions;
}
public async startTypebot(instance: InstanceDto, data: any) { public async startTypebot(instance: InstanceDto, data: any) {
if (data.remoteJid === 'status@broadcast') return;
const remoteJid = data.remoteJid; const remoteJid = data.remoteJid;
const url = data.url; const url = data.url;
const typebot = data.typebot; const typebot = data.typebot;
const startSession = data.startSession;
const variables = data.variables; const variables = data.variables;
const findTypebot = await this.find(instance);
const expire = findTypebot.expire;
const keyword_finish = findTypebot.keyword_finish;
const delay_message = findTypebot.delay_message;
const unknown_message = findTypebot.unknown_message;
const listening_from_me = findTypebot.listening_from_me;
const prefilledVariables = { const prefilledVariables = {
remoteJid: remoteJid, remoteJid: remoteJid,
instanceName: instance.instanceName,
}; };
variables.forEach((variable) => { if (variables?.length) {
variables.forEach((variable: { name: string | number; value: string }) => {
prefilledVariables[variable.name] = variable.value; prefilledVariables[variable.name] = variable.value;
}); });
}
if (startSession) {
const newSessions = await this.clearSessions(instance, remoteJid);
const response = await this.createNewSession(instance, {
enabled: findTypebot.enabled,
url: url,
typebot: typebot,
remoteJid: remoteJid,
expire: expire,
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
listening_from_me: listening_from_me,
sessions: newSessions,
prefilledVariables: prefilledVariables,
});
if (response.sessionId) {
await this.sendWAMessage(instance, remoteJid, response.messages, response.input, response.clientSideActions);
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, {
remoteJid: remoteJid,
url: url,
typebot: typebot,
prefilledVariables: prefilledVariables,
sessionId: `${response.sessionId}`,
});
} else {
throw new Error('Session ID not found in response');
}
} else {
const id = Math.floor(Math.random() * 10000000000).toString(); const id = Math.floor(Math.random() * 10000000000).toString();
const reqData = { try {
sessionId: id, const version = this.configService.get<Typebot>('TYPEBOT').API_VERSION;
let url: string;
let reqData: {};
if (version === 'latest') {
url = `${data.url}/api/v1/typebots/${data.typebot}/startChat`;
reqData = {
prefilledVariables: prefilledVariables,
};
} else {
url = `${data.url}/api/v1/sendMessage`;
reqData = {
startParams: { startParams: {
typebot: data.typebot, publicId: data.typebot,
prefilledVariables: prefilledVariables, prefilledVariables: prefilledVariables,
}, },
}; };
}
const request = await axios.post(data.url + '/api/v1/sendMessage', reqData); const request = await axios.post(url, reqData);
await this.sendWAMessage( await this.sendWAMessage(
instance, instance,
@@ -136,6 +249,11 @@ export class TypebotService {
variables: variables, variables: variables,
sessionId: id, sessionId: id,
}); });
} catch (error) {
this.logger.error(error);
return;
}
}
return { return {
typebot: { typebot: {
@@ -144,7 +262,7 @@ export class TypebotService {
url: url, url: url,
remoteJid: remoteJid, remoteJid: remoteJid,
typebot: typebot, typebot: typebot,
variables: variables, prefilledVariables: prefilledVariables,
}, },
}, },
}; };
@@ -187,32 +305,58 @@ export class TypebotService {
} }
public async createNewSession(instance: InstanceDto, data: any) { public async createNewSession(instance: InstanceDto, data: any) {
if (data.remoteJid === 'status@broadcast') return;
const id = Math.floor(Math.random() * 10000000000).toString(); const id = Math.floor(Math.random() * 10000000000).toString();
const reqData = {
sessionId: id, try {
startParams: { const version = this.configService.get<Typebot>('TYPEBOT').API_VERSION;
typebot: data.typebot, let url: string;
let reqData: {};
if (version === 'latest') {
url = `${data.url}/api/v1/typebots/${data.typebot}/startChat`;
reqData = {
prefilledVariables: { prefilledVariables: {
...data.prefilledVariables,
remoteJid: data.remoteJid, remoteJid: data.remoteJid,
pushName: data.pushName, pushName: data.pushName || data.prefilledVariables?.pushName || '',
instanceName: instance.instanceName,
},
};
} else {
url = `${data.url}/api/v1/sendMessage`;
reqData = {
startParams: {
publicId: data.typebot,
prefilledVariables: {
...data.prefilledVariables,
remoteJid: data.remoteJid,
pushName: data.pushName || data.prefilledVariables?.pushName || '',
instanceName: instance.instanceName, instanceName: instance.instanceName,
}, },
}, },
}; };
}
const request = await axios.post(url, reqData);
const request = await axios.post(data.url + '/api/v1/sendMessage', reqData); if (request?.data?.sessionId) {
if (request.data.sessionId) {
data.sessions.push({ data.sessions.push({
remoteJid: data.remoteJid, remoteJid: data.remoteJid,
sessionId: `${id}-${request.data.sessionId}`, sessionId: `${id}-${request.data.sessionId}`,
status: 'opened', status: 'opened',
createdAt: Date.now(), createdAt: Date.now(),
updateAt: Date.now(), updateAt: Date.now(),
prefilledVariables: {
...data.prefilledVariables,
remoteJid: data.remoteJid,
pushName: data.pushName || '',
instanceName: instance.instanceName,
},
}); });
const typebotData = { const typebotData = {
enabled: true, enabled: data.enabled,
url: data.url, url: data.url,
typebot: data.typebot, typebot: data.typebot,
expire: data.expire, expire: data.expire,
@@ -225,8 +369,11 @@ export class TypebotService {
this.create(instance, typebotData); this.create(instance, typebotData);
} }
return request.data; return request.data;
} catch (error) {
this.logger.error(error);
return;
}
} }
public async sendWAMessage( public async sendWAMessage(
@@ -236,11 +383,15 @@ export class TypebotService {
input: any[], input: any[],
clientSideActions: any[], clientSideActions: any[],
) { ) {
processMessages(this.waMonitor.waInstances[instance.instanceName], messages, input, clientSideActions).catch( processMessages(
(err) => { this.waMonitor.waInstances[instance.instanceName],
messages,
input,
clientSideActions,
this.eventEmitter,
).catch((err) => {
console.error('Erro ao processar mensagens:', err); console.error('Erro ao processar mensagens:', err);
}, });
);
function findItemAndGetSecondsToWait(array, targetId) { function findItemAndGetSecondsToWait(array, targetId) {
if (!array) return null; if (!array) return null;
@@ -253,7 +404,7 @@ export class TypebotService {
return null; return null;
} }
async function processMessages(instance, messages, input, clientSideActions) { async function processMessages(instance, messages, input, clientSideActions, eventEmitter) {
for (const message of messages) { for (const message of messages) {
const wait = findItemAndGetSecondsToWait(clientSideActions, message.id); const wait = findItemAndGetSecondsToWait(clientSideActions, message.id);
@@ -263,12 +414,30 @@ export class TypebotService {
let linkPreview = false; let linkPreview = false;
for (const richText of message.content.richText) { for (const richText of message.content.richText) {
if (richText.type === 'variable') {
for (const child of richText.children) {
for (const grandChild of child.children) {
formattedText += grandChild.text;
}
}
} else {
for (const element of richText.children) { for (const element of richText.children) {
let text = ''; let text = '';
if (element.text) {
if (element.type === 'inline-variable') {
for (const child of element.children) {
for (const grandChild of child.children) {
text += grandChild.text;
}
}
} else if (element.text) {
text = element.text; text = element.text;
} }
// if (element.text) {
// text = element.text;
// }
if (element.bold) { if (element.bold) {
text = `*${text}*`; text = `*${text}*`;
} }
@@ -278,7 +447,7 @@ export class TypebotService {
} }
if (element.underline) { if (element.underline) {
text = `~${text}~`; text = `*${text}*`;
} }
if (element.url) { if (element.url) {
@@ -289,6 +458,7 @@ export class TypebotService {
formattedText += text; formattedText += text;
} }
}
formattedText += '\n'; formattedText += '\n';
} }
@@ -374,6 +544,11 @@ export class TypebotService {
}, },
}); });
} }
} else {
eventEmitter.emit('typebot:end', {
instance: instance,
remoteJid: remoteJid,
});
} }
} }
} }
@@ -391,6 +566,7 @@ export class TypebotService {
const session = sessions.find((session) => session.remoteJid === remoteJid); const session = sessions.find((session) => session.remoteJid === remoteJid);
try {
if (session && expire && expire > 0) { if (session && expire && expire > 0) {
const now = Date.now(); const now = Date.now();
@@ -399,9 +575,10 @@ export class TypebotService {
const diffInMinutes = Math.floor(diff / 1000 / 60); const diffInMinutes = Math.floor(diff / 1000 / 60);
if (diffInMinutes > expire) { if (diffInMinutes > expire) {
sessions.splice(sessions.indexOf(session), 1); const newSessions = await this.clearSessions(instance, remoteJid);
const data = await this.createNewSession(instance, { const data = await this.createNewSession(instance, {
enabled: findTypebot.enabled,
url: url, url: url,
typebot: typebot, typebot: typebot,
expire: expire, expire: expire,
@@ -409,13 +586,84 @@ export class TypebotService {
delay_message: delay_message, delay_message: delay_message,
unknown_message: unknown_message, unknown_message: unknown_message,
listening_from_me: listening_from_me, listening_from_me: listening_from_me,
sessions: sessions, sessions: newSessions,
remoteJid: remoteJid, remoteJid: remoteJid,
pushName: msg.pushName, pushName: msg.pushName,
}); });
await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions); await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions);
if (data.messages.length === 0) {
const content = this.getConversationMessage(msg.message);
if (!content) {
if (unknown_message) {
this.waMonitor.waInstances[instance.instanceName].textMessage({
number: remoteJid.split('@')[0],
options: {
delay: delay_message || 1000,
presence: 'composing',
},
textMessage: {
text: unknown_message,
},
});
}
return;
}
if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) {
const newSessions = await this.clearSessions(instance, remoteJid);
const typebotData = {
enabled: findTypebot.enabled,
url: url,
typebot: typebot,
expire: expire,
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
listening_from_me: listening_from_me,
sessions: newSessions,
};
this.create(instance, typebotData);
return;
}
try {
const version = this.configService.get<Typebot>('TYPEBOT').API_VERSION;
let urlTypebot: string;
let reqData: {};
if (version === 'latest') {
urlTypebot = `${url}/api/v1/sessions/${data.sessionId}/continueChat`;
reqData = {
message: content,
};
} else {
urlTypebot = `${url}/api/v1/sendMessage`;
reqData = {
message: content,
sessionId: data.sessionId,
};
}
const request = await axios.post(urlTypebot, reqData);
await this.sendWAMessage(
instance,
remoteJid,
request.data.messages,
request.data.input,
request.data.clientSideActions,
);
} catch (error) {
this.logger.error(error);
return;
}
}
return; return;
} }
} }
@@ -426,6 +674,7 @@ export class TypebotService {
if (!session) { if (!session) {
const data = await this.createNewSession(instance, { const data = await this.createNewSession(instance, {
enabled: findTypebot.enabled,
url: url, url: url,
typebot: typebot, typebot: typebot,
expire: expire, expire: expire,
@@ -440,6 +689,76 @@ export class TypebotService {
await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions); await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions);
if (data.messages.length === 0) {
const content = this.getConversationMessage(msg.message);
if (!content) {
if (unknown_message) {
this.waMonitor.waInstances[instance.instanceName].textMessage({
number: remoteJid.split('@')[0],
options: {
delay: delay_message || 1000,
presence: 'composing',
},
textMessage: {
text: unknown_message,
},
});
}
return;
}
if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) {
sessions.splice(sessions.indexOf(session), 1);
const typebotData = {
enabled: findTypebot.enabled,
url: url,
typebot: typebot,
expire: expire,
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
listening_from_me: listening_from_me,
sessions,
};
this.create(instance, typebotData);
return;
}
let request: any;
try {
const version = this.configService.get<Typebot>('TYPEBOT').API_VERSION;
let urlTypebot: string;
let reqData: {};
if (version === 'latest') {
urlTypebot = `${url}/api/v1/sessions/${data.sessionId}/continueChat`;
reqData = {
message: content,
};
} else {
urlTypebot = `${url}/api/v1/sendMessage`;
reqData = {
message: content,
sessionId: data.sessionId,
};
}
request = await axios.post(urlTypebot, reqData);
await this.sendWAMessage(
instance,
remoteJid,
request.data.messages,
request.data.input,
request.data.clientSideActions,
);
} catch (error) {
this.logger.error(error);
return;
}
}
return; return;
} }
@@ -450,7 +769,7 @@ export class TypebotService {
}); });
const typebotData = { const typebotData = {
enabled: true, enabled: findTypebot.enabled,
url: url, url: url,
typebot: typebot, typebot: typebot,
expire: expire, expire: expire,
@@ -481,11 +800,11 @@ export class TypebotService {
return; return;
} }
if (content.toLowerCase() === keyword_finish.toLowerCase()) { if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) {
sessions.splice(sessions.indexOf(session), 1); sessions.splice(sessions.indexOf(session), 1);
const typebotData = { const typebotData = {
enabled: true, enabled: findTypebot.enabled,
url: url, url: url,
typebot: typebot, typebot: typebot,
expire: expire, expire: expire,
@@ -501,12 +820,22 @@ export class TypebotService {
return; return;
} }
const reqData = { const version = this.configService.get<Typebot>('TYPEBOT').API_VERSION;
let urlTypebot: string;
let reqData: {};
if (version === 'latest') {
urlTypebot = `${url}/api/v1/sessions/${session.sessionId.split('-')[1]}/continueChat`;
reqData = {
message: content,
};
} else {
urlTypebot = `${url}/api/v1/sendMessage`;
reqData = {
message: content, message: content,
sessionId: session.sessionId.split('-')[1], sessionId: session.sessionId.split('-')[1],
}; };
}
const request = await axios.post(url + '/api/v1/sendMessage', reqData); const request = await axios.post(urlTypebot, reqData);
await this.sendWAMessage( await this.sendWAMessage(
instance, instance,
@@ -517,5 +846,9 @@ export class TypebotService {
); );
return; return;
} catch (error) {
this.logger.error(error);
return;
}
} }
} }

View File

@@ -26,7 +26,7 @@ export class WebhookService {
return result; return result;
} catch (error) { } catch (error) {
return { enabled: false, url: '', events: [], webhook_by_events: false }; return { enabled: false, url: '', events: [], webhook_by_events: false, webhook_base64: false };
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,8 @@ import { AuthenticationState, WAConnectionState } from '@whiskeysockets/baileys'
export enum Events { export enum Events {
APPLICATION_STARTUP = 'application.startup', APPLICATION_STARTUP = 'application.startup',
INSTANCE_CREATE = 'instance.create',
INSTANCE_DELETE = 'instance.delete',
QRCODE_UPDATED = 'qrcode.updated', QRCODE_UPDATED = 'qrcode.updated',
CONNECTION_UPDATE = 'connection.update', CONNECTION_UPDATE = 'connection.update',
STATUS_INSTANCE = 'status.instance', STATUS_INSTANCE = 'status.instance',
@@ -50,6 +52,7 @@ export declare namespace wa {
url?: string; url?: string;
events?: string[]; events?: string[];
webhook_by_events?: boolean; webhook_by_events?: boolean;
webhook_base64?: boolean;
}; };
export type LocalChatwoot = { export type LocalChatwoot = {
@@ -83,6 +86,11 @@ export declare namespace wa {
events?: string[]; events?: string[];
}; };
export type LocalSqs = {
enabled?: boolean;
events?: string[];
};
type Session = { type Session = {
remoteJid?: string; remoteJid?: string;
sessionId?: string; sessionId?: string;

View File

@@ -12,8 +12,8 @@ import { ProxyController } from './controllers/proxy.controller';
import { RabbitmqController } from './controllers/rabbitmq.controller'; import { RabbitmqController } from './controllers/rabbitmq.controller';
import { SendMessageController } from './controllers/sendMessage.controller'; import { SendMessageController } from './controllers/sendMessage.controller';
import { SettingsController } from './controllers/settings.controller'; import { SettingsController } from './controllers/settings.controller';
import { SqsController } from './controllers/sqs.controller';
import { TypebotController } from './controllers/typebot.controller'; import { TypebotController } from './controllers/typebot.controller';
import { ViewsController } from './controllers/views.controller';
import { WebhookController } from './controllers/webhook.controller'; import { WebhookController } from './controllers/webhook.controller';
import { WebsocketController } from './controllers/websocket.controller'; import { WebsocketController } from './controllers/websocket.controller';
import { import {
@@ -27,6 +27,7 @@ import {
ProxyModel, ProxyModel,
RabbitmqModel, RabbitmqModel,
SettingsModel, SettingsModel,
SqsModel,
TypebotModel, TypebotModel,
WebhookModel, WebhookModel,
WebsocketModel, WebsocketModel,
@@ -42,6 +43,7 @@ import { ProxyRepository } from './repository/proxy.repository';
import { RabbitmqRepository } from './repository/rabbitmq.repository'; import { RabbitmqRepository } from './repository/rabbitmq.repository';
import { RepositoryBroker } from './repository/repository.manager'; import { RepositoryBroker } from './repository/repository.manager';
import { SettingsRepository } from './repository/settings.repository'; import { SettingsRepository } from './repository/settings.repository';
import { SqsRepository } from './repository/sqs.repository';
import { TypebotRepository } from './repository/typebot.repository'; import { TypebotRepository } from './repository/typebot.repository';
import { WebhookRepository } from './repository/webhook.repository'; import { WebhookRepository } from './repository/webhook.repository';
import { WebsocketRepository } from './repository/websocket.repository'; import { WebsocketRepository } from './repository/websocket.repository';
@@ -52,6 +54,7 @@ import { WAMonitoringService } from './services/monitor.service';
import { ProxyService } from './services/proxy.service'; import { ProxyService } from './services/proxy.service';
import { RabbitmqService } from './services/rabbitmq.service'; import { RabbitmqService } from './services/rabbitmq.service';
import { SettingsService } from './services/settings.service'; import { SettingsService } from './services/settings.service';
import { SqsService } from './services/sqs.service';
import { TypebotService } from './services/typebot.service'; import { TypebotService } from './services/typebot.service';
import { WebhookService } from './services/webhook.service'; import { WebhookService } from './services/webhook.service';
import { WebsocketService } from './services/websocket.service'; import { WebsocketService } from './services/websocket.service';
@@ -68,6 +71,7 @@ const websocketRepository = new WebsocketRepository(WebsocketModel, configServic
const proxyRepository = new ProxyRepository(ProxyModel, configService); const proxyRepository = new ProxyRepository(ProxyModel, configService);
const chamaaiRepository = new ChamaaiRepository(ChamaaiModel, configService); const chamaaiRepository = new ChamaaiRepository(ChamaaiModel, configService);
const rabbitmqRepository = new RabbitmqRepository(RabbitmqModel, configService); const rabbitmqRepository = new RabbitmqRepository(RabbitmqModel, configService);
const sqsRepository = new SqsRepository(SqsModel, configService);
const chatwootRepository = new ChatwootRepository(ChatwootModel, configService); const chatwootRepository = new ChatwootRepository(ChatwootModel, configService);
const settingsRepository = new SettingsRepository(SettingsModel, configService); const settingsRepository = new SettingsRepository(SettingsModel, configService);
const authRepository = new AuthRepository(AuthModel, configService); const authRepository = new AuthRepository(AuthModel, configService);
@@ -82,6 +86,7 @@ export const repository = new RepositoryBroker(
settingsRepository, settingsRepository,
websocketRepository, websocketRepository,
rabbitmqRepository, rabbitmqRepository,
sqsRepository,
typebotRepository, typebotRepository,
proxyRepository, proxyRepository,
chamaaiRepository, chamaaiRepository,
@@ -96,7 +101,7 @@ export const waMonitor = new WAMonitoringService(eventEmitter, configService, re
const authService = new AuthService(configService, waMonitor, repository); const authService = new AuthService(configService, waMonitor, repository);
const typebotService = new TypebotService(waMonitor); const typebotService = new TypebotService(waMonitor, configService, eventEmitter);
export const typebotController = new TypebotController(typebotService); export const typebotController = new TypebotController(typebotService);
@@ -120,9 +125,13 @@ const rabbitmqService = new RabbitmqService(waMonitor);
export const rabbitmqController = new RabbitmqController(rabbitmqService); export const rabbitmqController = new RabbitmqController(rabbitmqService);
const chatwootService = new ChatwootService(waMonitor, configService); const sqsService = new SqsService(waMonitor);
export const chatwootController = new ChatwootController(chatwootService, configService); export const sqsController = new SqsController(sqsService);
const chatwootService = new ChatwootService(waMonitor, configService, repository);
export const chatwootController = new ChatwootController(chatwootService, configService, repository);
const settingsService = new SettingsService(waMonitor); const settingsService = new SettingsService(waMonitor);
@@ -139,10 +148,11 @@ export const instanceController = new InstanceController(
settingsService, settingsService,
websocketService, websocketService,
rabbitmqService, rabbitmqService,
proxyService,
sqsService,
typebotService, typebotService,
cache, cache,
); );
export const viewsController = new ViewsController(waMonitor, configService);
export const sendMessageController = new SendMessageController(waMonitor); export const sendMessageController = new SendMessageController(waMonitor);
export const chatController = new ChatController(waMonitor); export const chatController = new ChatController(waMonitor);
export const groupController = new GroupController(waMonitor); export const groupController = new GroupController(waMonitor);

110
views/manager-wip.hbs Normal file
View File

@@ -0,0 +1,110 @@
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="https://evolution-api.com/files/evolution-api-favicon.png" type="image/x-icon">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"
integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
<title>Instance Manager</title>
</head>
<body>
<div class="container mt-4">
<!-- Botão para abrir o modal de adicionar nova instância -->
<button class="btn btn-primary mb-3" data-toggle="modal" data-target="#actionModal" data-action="add">Nova
Instância</button>
<!-- Tabela de instâncias -->
<table class="table table-bordered">
<thead>
<tr>
<th>Nome da Instância</th>
<th>Status</th>
<th>API Key</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
<!-- Iterando sobre as instâncias e preenchendo a tabela -->
{{#each instances}}
<tr>
<td>{{this.instance.instanceName}}</td>
<td>{{this.instance.status}}</td>
<td>{{this.instance.apikey}}</td>
<td>
<!-- Dropdown de ações para cada instância -->
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="actionDropdown"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Ações
</button>
<div class="dropdown-menu" aria-labelledby="actionDropdown">
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#actionModal"
data-action="connect">Connect</a>
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#actionModal"
data-action="restart">Restart</a>
<!-- Adicione mais itens de ação aqui -->
<!-- ... -->
</div>
</div>
</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
<!-- Modal de ações -->
<div class="modal fade" id="actionModal" tabindex="-1" role="dialog" aria-labelledby="actionModalLabel"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="actionModalLabel">Ação</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Fechar</button>
<button type="button" class="btn btn-primary">Salvar</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.min.js"
integrity="sha384-+sLIOodYLS7CIrQpBjl+C7nPvqq+FbNUBDunl/OZv93DB7Ln/533i8e/mZXLi/P+"
crossorigin="anonymous"></script>
<script>
$(document).ready(function(){
$('#actionModal').on('show.bs.modal', function(event) {
var button = $(event.relatedTarget);
var action = button.data('action');
console.log(action);
if (action === 'connect') {
} else if (action === 'restart') {
}
});
})
</script>
</body>
</html>

View File

@@ -11,7 +11,7 @@
<body> <body>
<iframe src="https://app.smith.dgcode.com.br/app/evolutionapi-public/home-64ca60783615e270291978b4?embed=true" frameborder="0" style="width: 100%; height: 100vh;"></iframe> <iframe src="https://manager.evolution-api.com" frameborder="0" style="width: 100%; height: 100vh;"></iframe>
</body> </body>