Compare commits

..

164 Commits
1.5.3 ... 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
60 changed files with 2586 additions and 660 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/
/src/env.yml
/store
*.env
/temp/*

View File

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

View File

@@ -1,3 +1,64 @@
# 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
@@ -68,9 +129,9 @@
### Integrations
- Chatwoot: v2.18.0 - v3.0.0
- Typebot: v2.16.0
- Manager Evolution API
* Chatwoot: v2.18.0 - v3.0.0
* Typebot: v2.16.0
* Manager Evolution API
# 1.4.8 (2023-07-27 10:27)
@@ -123,7 +184,7 @@
### Fixed
* Fixed validation is set settings
* Fixed validation is set settings
* Adjusts in group validations
* Ajusts in sticker message to chatwoot
@@ -158,7 +219,7 @@
### Integrations
- Chatwoot: v2.18.0 - v3.0.0 (Beta)
* Chatwoot: v2.18.0 - v3.0.0 (Beta)
# 1.3.2 (2023-07-21 17:19)
@@ -174,7 +235,7 @@
### Integrations
- Chatwoot: v2.18.0
* Chatwoot: v2.18.0
# 1.3.1 (2023-07-20 07:48)
@@ -184,7 +245,7 @@
### Integrations
- Chatwoot: v2.18.0
* Chatwoot: v2.18.0
# 1.3.0 (2023-07-19 11:33)
@@ -200,7 +261,7 @@
* Translation set to default (english) in chatwoot
### Fixed
* Fixed error to send message in large groups
* Docker files adjusted
* Fixed in the postman collection the webhookByEvent parameter by webhook_by_events
@@ -221,7 +282,7 @@
### Integrations
- Chatwoot: v2.18.0
* Chatwoot: v2.18.0
# 1.2.2 (2023-07-15 09:36)
@@ -232,7 +293,7 @@
### Integrations
- Chatwoot: v2.18.0
* Chatwoot: v2.18.0
# 1.2.1 (2023-07-14 19:04)
@@ -388,4 +449,4 @@
* Sending the local webhook url as destination in the webhook data for webhook redirection
* Startup modes, server or container
* Server Mode works normally as everyone is used to
* Container mode made to use one instance per container, when starting the application an instance is already created and the qrcode is generated and it starts sending webhook without having to call it manually, it only allows one instance at a time.
* Container mode made to use one instance per container, when starting the application an instance is already created and the qrcode is generated and it starts sending webhook without having to call it manually, it only allows one instance at a time.

View File

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

View File

@@ -1,6 +1,6 @@
FROM node:20.7.0-alpine
FROM node:20.7.0-alpine AS builder
LABEL version="1.5.3" 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 contact="contato@agenciadgcode.com"
@@ -11,9 +11,19 @@ WORKDIR /evolution
COPY ./package.json .
RUN npm install
COPY . .
RUN npm run build
FROM node:20.7.0-alpine AS final
ENV TZ=America/Sao_Paulo
ENV DOCKER_ENV=true
ENV SERVER_TYPE=http
ENV SERVER_PORT=8080
ENV SERVER_URL=http://localhost:8080
ENV CORS_ORIGIN=*
@@ -56,12 +66,20 @@ ENV RABBITMQ_URI=amqp://guest:guest@rabbitmq:5672
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_ENABLED=false
ENV WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=false
ENV WEBHOOK_EVENTS_APPLICATION_STARTUP=false
ENV WEBHOOK_EVENTS_INSTANCE_CREATE=false
ENV WEBHOOK_EVENTS_INSTANCE_DELETE=false
ENV WEBHOOK_EVENTS_QRCODE_UPDATED=true
ENV WEBHOOK_EVENTS_MESSAGES_SET=true
ENV WEBHOOK_EVENTS_MESSAGES_UPSERT=true
@@ -98,6 +116,8 @@ ENV CONFIG_SESSION_PHONE_NAME=Chrome
ENV QRCODE_LIMIT=30
ENV QRCODE_COLOR=#198754
ENV TYPEBOT_API_VERSION=latest
ENV AUTHENTICATION_TYPE=apikey
ENV AUTHENTICATION_API_KEY=B6D711FCDE4D4FD5936544120E713976
@@ -114,10 +134,8 @@ ENV AUTHENTICATION_INSTANCE_CHATWOOT_ACCOUNT_ID=1
ENV AUTHENTICATION_INSTANCE_CHATWOOT_TOKEN=123456
ENV AUTHENTICATION_INSTANCE_CHATWOOT_URL=<url>
RUN npm install
WORKDIR /evolution
COPY . .
RUN npm run build
COPY --from=builder /evolution .
CMD [ "node", "./dist/src/main.js" ]

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "evolution-api",
"version": "1.5.3",
"version": "1.6.1",
"description": "Rest api for communication with WhatsApp",
"main": "./dist/src/main.js",
"scripts": {
@@ -46,8 +46,9 @@
"@figuro/chatwoot-sdk": "^1.1.16",
"@hapi/boom": "^10.0.1",
"@sentry/node": "^7.59.2",
"@whiskeysockets/baileys": "^6.5.0",
"@whiskeysockets/baileys": "github:PurpShell/Baileys#combined",
"amqplib": "^0.10.3",
"aws-sdk": "^2.1499.0",
"axios": "^1.3.5",
"class-validator": "^0.13.2",
"compression": "^1.7.4",
@@ -55,6 +56,7 @@
"cross-env": "^7.0.3",
"dayjs": "^1.11.7",
"eventemitter2": "^6.4.9",
"evolution-manager": "^0.4.11",
"exiftool-vendored": "^22.0.0",
"express": "^4.18.2",
"express-async-errors": "^3.1.1",

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

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

View File

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

View File

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

View File

@@ -25,7 +25,7 @@ info:
</font>
[![Run in Postman](https://run.pstmn.io/button.svg)](https://god.gw.postman.com/run-collection/26869335-5546d063-156b-4529-915f-909dd628c090?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D26869335-5546d063-156b-4529-915f-909dd628c090%26entityType%3Dcollection%26workspaceId%3D339a4ee7-378b-45c9-b5b8-fd2c0a9c2442)
version: 1.5.2
version: 1.6.1
contact:
name: DavidsonGomes
email: contato@agenciadgcode.com

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
import { delay } from '@whiskeysockets/baileys';
import { isURL } from 'class-validator';
import EventEmitter2 from 'eventemitter2';
import { v4 } from 'uuid';
import { ConfigService, HttpServer } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
@@ -11,13 +12,15 @@ import { RepositoryBroker } from '../repository/repository.manager';
import { AuthService, OldToken } from '../services/auth.service';
import { ChatwootService } from '../services/chatwoot.service';
import { WAMonitoringService } from '../services/monitor.service';
import { ProxyService } from '../services/proxy.service';
import { RabbitmqService } from '../services/rabbitmq.service';
import { SettingsService } from '../services/settings.service';
import { SqsService } from '../services/sqs.service';
import { TypebotService } from '../services/typebot.service';
import { WebhookService } from '../services/webhook.service';
import { WebsocketService } from '../services/websocket.service';
import { WAStartupService } from '../services/whatsapp.service';
import { wa } from '../types/wa.types';
import { Events, wa } from '../types/wa.types';
export class InstanceController {
constructor(
@@ -31,6 +34,8 @@ export class InstanceController {
private readonly settingsService: SettingsService,
private readonly websocketService: WebsocketService,
private readonly rabbitmqService: RabbitmqService,
private readonly proxyService: ProxyService,
private readonly sqsService: SqsService,
private readonly typebotService: TypebotService,
private readonly cache: RedisCache,
) {}
@@ -62,6 +67,8 @@ export class InstanceController {
websocket_events,
rabbitmq_enabled,
rabbitmq_events,
sqs_enabled,
sqs_events,
typebot_url,
typebot,
typebot_expire,
@@ -69,6 +76,7 @@ export class InstanceController {
typebot_delay_message,
typebot_unknown_message,
typebot_listening_from_me,
proxy,
}: InstanceDto) {
try {
this.logger.verbose('requested createInstance from ' + instanceName + ' instance');
@@ -80,6 +88,13 @@ export class InstanceController {
const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache);
instance.instanceName = instanceName;
const instanceId = v4();
instance.sendDataWebhook(Events.INSTANCE_CREATE, {
instanceName,
instanceId: instanceId,
});
this.logger.verbose('instance: ' + instance.instanceName + ' created');
this.waMonitor.waInstances[instance.instanceName] = instance;
@@ -89,6 +104,7 @@ export class InstanceController {
const hash = await this.authService.generateHash(
{
instanceName: instance.instanceName,
instanceId: instanceId,
},
token,
);
@@ -243,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) {
try {
if (!isURL(typebot_url, { require_tld: false })) {
@@ -270,7 +349,7 @@ export class InstanceController {
const settings: wa.LocalSettings = {
reject_call: reject_call || false,
msg_call: msg_call || '',
groups_ignore: groups_ignore || false,
groups_ignore: groups_ignore || true,
always_online: always_online || false,
read_messages: read_messages || false,
read_status: read_status || false,
@@ -293,6 +372,7 @@ export class InstanceController {
const result = {
instance: {
instanceName: instance.instanceName,
instanceId: instanceId,
status: 'created',
},
hash,
@@ -310,6 +390,10 @@ export class InstanceController {
enabled: rabbitmq_enabled,
events: rabbitmqEvents,
},
sqs: {
enabled: sqs_enabled,
events: sqsEvents,
},
typebot: {
enabled: typebot_url ? true : false,
url: typebot_url,
@@ -322,6 +406,7 @@ export class InstanceController {
},
settings,
qrcode: getQrcode,
proxy,
};
this.logger.verbose('instance created');
@@ -371,15 +456,8 @@ export class InstanceController {
number,
reopen_conversation: chatwoot_reopen_conversation || 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) {
this.logger.log(error);
}
@@ -387,6 +465,7 @@ export class InstanceController {
return {
instance: {
instanceName: instance.instanceName,
instanceId: instanceId,
status: 'created',
},
hash,
@@ -404,6 +483,10 @@ export class InstanceController {
enabled: rabbitmq_enabled,
events: rabbitmqEvents,
},
sqs: {
enabled: sqs_enabled,
events: sqsEvents,
},
typebot: {
enabled: typebot_url ? true : false,
url: typebot_url,
@@ -427,6 +510,7 @@ export class InstanceController {
name_inbox: instance.instanceName,
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
},
proxy,
};
} catch (error) {
this.logger.error(error.message[0]);
@@ -507,11 +591,13 @@ export class InstanceController {
};
}
public async fetchInstances({ instanceName }: InstanceDto) {
public async fetchInstances({ instanceName, instanceId }: InstanceDto) {
if (instanceName) {
this.logger.verbose('requested fetchInstances from ' + instanceName + ' instance');
this.logger.verbose('instanceName: ' + instanceName);
return this.waMonitor.instanceInfo(instanceName);
} else if (instanceId) {
return this.waMonitor.instanceInfoById(instanceId);
}
this.logger.verbose('requested fetchInstances (all instances)');
@@ -553,15 +639,17 @@ export class InstanceController {
this.logger.verbose('logging out instance: ' + instanceName);
await this.logout({ instanceName });
delete this.waMonitor.waInstances[instanceName];
return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } };
} else {
this.logger.verbose('deleting instance: ' + instanceName);
delete this.waMonitor.waInstances[instanceName];
this.eventEmitter.emit('remove.instance', instanceName, 'inner');
return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } };
}
this.logger.verbose('deleting instance: ' + instanceName);
this.waMonitor.waInstances[instanceName].sendDataWebhook(Events.INSTANCE_DELETE, {
instanceName,
instanceId: (await this.repository.auth.find(instanceName))?.instanceId,
});
delete this.waMonitor.waInstances[instanceName];
this.eventEmitter.emit('remove.instance', instanceName, 'inner');
return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } };
} catch (error) {
throw new BadRequestException(error.toString());
}

View File

@@ -19,6 +19,7 @@ export class SettingsController {
public async findSettings(instance: InstanceDto) {
logger.verbose('requested findSettings from ' + instance.instanceName + ' instance');
return this.settingsService.find(instance);
const settings = this.settingsService.find(instance);
return settings;
}
}

View File

@@ -0,0 +1,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 {
constructor(public readonly jid: string, public readonly exists: boolean, public readonly name?: string) {}
@@ -83,3 +83,20 @@ export class DeleteMessage {
remoteJid: string;
participant?: string;
}
export class Options {
delay?: number;
presence?: WAPresence;
}
class OptionsMessage {
options: Options;
}
export class Metadata extends OptionsMessage {
number: string;
}
export class SendPresenceDto extends Metadata {
options: {
presence: WAPresence;
delay: number;
};
}

View File

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

View File

@@ -1,5 +1,6 @@
export class InstanceDto {
instanceName: string;
instanceId?: string;
qrcode?: boolean;
number?: string;
token?: string;
@@ -23,6 +24,8 @@ export class InstanceDto {
websocket_events?: string[];
rabbitmq_enabled?: boolean;
rabbitmq_events?: string[];
sqs_enabled?: boolean;
sqs_events?: string[];
typebot_url?: string;
typebot?: string;
typebot_expire?: number;
@@ -30,6 +33,5 @@ export class InstanceDto {
typebot_delay_message?: number;
typebot_unknown_message?: string;
typebot_listening_from_me?: boolean;
proxy_enabled?: boolean;
proxy_proxy?: string;
proxy?: string;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

@@ -19,6 +19,7 @@ export class AuthRepository extends Repository {
public async create(data: AuthRaw, instance: string): Promise<IInsert> {
try {
this.logger.verbose('creating auth');
if (this.dbSettings.ENABLED) {
this.logger.verbose('saving auth to db');
const insert = await this.authModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
@@ -62,4 +63,20 @@ export class AuthRepository extends Repository {
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');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding messages in db');
if (query?.where?.key) {
for (const [k, v] of Object.entries(query.where.key)) {
query.where['key.' + k] = v;
for (const [o, p] of Object.entries(query?.where)) {
if (typeof p === 'object' && p !== null && !Array.isArray(p)) {
for (const [k, v] of Object.entries(p)) {
query.where[`${o}.${k}`] = v;
}
delete query.where[o];
}
delete query?.where?.key;
}
return await this.messageModel
@@ -144,4 +146,55 @@ export class MessageRepository extends Repository {
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 { RabbitmqRepository } from './rabbitmq.repository';
import { SettingsRepository } from './settings.repository';
import { SqsRepository } from './sqs.repository';
import { TypebotRepository } from './typebot.repository';
import { WebhookRepository } from './webhook.repository';
import { WebsocketRepository } from './websocket.repository';
@@ -28,6 +29,7 @@ export class RepositoryBroker {
public readonly settings: SettingsRepository,
public readonly websocket: WebsocketRepository,
public readonly rabbitmq: RabbitmqRepository,
public readonly sqs: SqsRepository,
public readonly typebot: TypebotRepository,
public readonly proxy: ProxyRepository,
public readonly chamaai: ChamaaiRepository,
@@ -63,6 +65,7 @@ export class RepositoryBroker {
const settingsDir = join(storePath, 'settings');
const websocketDir = join(storePath, 'websocket');
const rabbitmqDir = join(storePath, 'rabbitmq');
const sqsDir = join(storePath, 'sqs');
const typebotDir = join(storePath, 'typebot');
const proxyDir = join(storePath, 'proxy');
const chamaaiDir = join(storePath, 'chamaai');
@@ -108,6 +111,10 @@ export class RepositoryBroker {
this.logger.verbose('creating rabbitmq dir: ' + rabbitmqDir);
fs.mkdirSync(rabbitmqDir, { recursive: true });
}
if (!fs.existsSync(sqsDir)) {
this.logger.verbose('creating sqs dir: ' + sqsDir);
fs.mkdirSync(sqsDir, { recursive: true });
}
if (!fs.existsSync(typebotDir)) {
this.logger.verbose('creating typebot dir: ' + typebotDir);
fs.mkdirSync(typebotDir, { recursive: true });

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,12 +6,15 @@ import Jimp from 'jimp';
import mimeTypes from 'mime-types';
import path from 'path';
import { ConfigService } from '../../config/env.config';
import { ConfigService, HttpServer } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { ROOT_DIR } from '../../config/path.config';
import { ChatwootDto } from '../dto/chatwoot.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';
export class ChatwootService {
@@ -22,7 +25,11 @@ export class ChatwootService {
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();
}
@@ -52,25 +59,26 @@ export class ChatwootService {
private async getProvider(instance: InstanceDto) {
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) {
this.logger.warn('provider not found');
return null;
}
this.logger.verbose('provider found');
return provider;
} catch (error) {
this.logger.error('provider not found');
if (!provider) {
this.logger.warn('provider not found');
return null;
}
this.logger.verbose('provider found');
return provider;
// try {
// } catch (error) {
// this.logger.error('provider not found');
// return null;
// }
}
private async clientCw(instance: InstanceDto) {
this.logger.verbose('get client to instance: ' + instance.instanceName);
const provider = await this.getProvider(instance);
if (!provider) {
@@ -97,11 +105,24 @@ export class ChatwootService {
return client;
}
public create(instance: InstanceDto, data: ChatwootDto) {
public async create(instance: InstanceDto, data: ChatwootDto) {
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');
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;
}
@@ -229,10 +250,6 @@ export class ChatwootService {
inbox_id: inboxId.toString(),
};
if (this.provider.conversation_pending) {
data['status'] = 'pending';
}
const conversation = await client.conversations.create({
accountId: this.provider.account_id,
data,
@@ -338,14 +355,18 @@ export class ChatwootService {
}
this.logger.verbose('update contact in chatwoot');
const contact = await client.contacts.update({
accountId: this.provider.account_id,
id,
data,
});
try {
const contact = await client.contacts.update({
accountId: this.provider.account_id,
id,
data,
});
this.logger.verbose('contact updated');
return contact;
this.logger.verbose('contact updated');
return contact;
} catch (error) {
this.logger.error(error);
}
}
public async findContact(instance: InstanceDto, phoneNumber: string) {
@@ -488,6 +509,9 @@ export class ChatwootService {
avatar_url: picture_url.profilePictureUrl || null,
});
}
if (!contact) {
contact = await this.findContact(instance, chatId);
}
} else {
const jid = isGroup ? null : body.key.remoteJid;
contact = await this.createContact(
@@ -619,6 +643,7 @@ export class ChatwootService {
encoding: string;
filename: string;
}[],
messageBody?: any,
) {
this.logger.verbose('create message to instance: ' + instance.instanceName);
@@ -629,6 +654,8 @@ export class ChatwootService {
return null;
}
const replyToIds = await this.getReplyToIds(messageBody, instance);
this.logger.verbose('create message in chatwoot');
const message = await client.messages.create({
accountId: this.provider.account_id,
@@ -638,6 +665,9 @@ export class ChatwootService {
message_type: messageType,
attachments: attachments,
private: privateMessage || false,
content_attributes: {
...replyToIds,
},
},
});
@@ -733,6 +763,8 @@ export class ChatwootService {
file: string,
messageType: 'incoming' | 'outgoing' | undefined,
content?: string,
instance?: InstanceDto,
messageBody?: any,
) {
this.logger.verbose('send data to chatwoot');
@@ -749,6 +781,16 @@ export class ChatwootService {
this.logger.verbose('temp file found');
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);
const config = {
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);
try {
@@ -879,7 +921,11 @@ export class ChatwootService {
const fileName = decodeURIComponent(parts[parts.length - 1]);
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);
let type = 'document';
@@ -911,13 +957,14 @@ export class ChatwootService {
options: {
delay: 1200,
presence: 'recording',
...options,
},
};
await waInstance?.audioWhatsapp(data);
const messageSent = await waInstance?.audioWhatsapp(data, true);
this.logger.verbose('audio sent');
return;
return messageSent;
}
this.logger.verbose('send media to instance: ' + waInstance.instanceName);
@@ -931,6 +978,7 @@ export class ChatwootService {
options: {
delay: 1200,
presence: 'composing',
...options,
},
};
@@ -939,10 +987,10 @@ export class ChatwootService {
data.mediaMessage.caption = caption;
}
await waInstance?.mediaMessage(data);
const messageSent = await waInstance?.mediaMessage(data, true);
this.logger.verbose('media sent');
return;
return messageSent;
} catch (error) {
this.logger.error(error);
}
@@ -961,15 +1009,46 @@ export class ChatwootService {
}
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');
const chatId =
body.conversation.meta.sender?.phone_number?.replace('+', '') || body.conversation.meta.sender?.identifier;
const messageReceived = body.content;
const senderName = body?.sender?.name;
// Chatwoot to Whatsapp
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];
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') {
this.logger.verbose('check if is command');
@@ -980,6 +1059,10 @@ export class ChatwootService {
const state = waInstance?.connectionStatus?.state;
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');
const number = command.split(':')[1];
await waInstance.connectToWhatsapp(number);
@@ -1043,7 +1126,13 @@ export class ChatwootService {
if (senderName === null || senderName === undefined) {
formatText = messageReceived;
} 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) {
@@ -1057,7 +1146,30 @@ export class ChatwootService {
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 {
this.logger.verbose('message is text');
@@ -1071,10 +1183,24 @@ export class ChatwootService {
options: {
delay: 1200,
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) {
this.logger.verbose('check if is media message');
const media = [
@@ -1139,6 +1337,18 @@ export class ChatwootService {
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) {
this.logger.verbose('get type message');
@@ -1205,6 +1415,11 @@ export class ChatwootService {
formattedContact += `\n**number ${numberCount}:** ${phoneNumber}`;
numberCount++;
}
if (key.includes('TEL')) {
const phoneNumber = contactInfo[key];
formattedContact += `\n**number:** ${phoneNumber}`;
numberCount++;
}
});
this.logger.verbose('message content: ' + formattedContact);
@@ -1233,6 +1448,11 @@ export class ChatwootService {
formattedContact += `\n**number ${numberCount}:** ${phoneNumber}`;
numberCount++;
}
if (key.includes('TEL')) {
const phoneNumber = contactInfo[key];
formattedContact += `\n**number:** ${phoneNumber}`;
numberCount++;
}
});
return formattedContact;
@@ -1265,13 +1485,6 @@ export class ChatwootService {
public async eventWhatsapp(event: string, instance: InstanceDto, body: any) {
this.logger.verbose('event whatsapp to instance: ' + instance.instanceName);
try {
const client = await this.clientCw(instance);
if (!client) {
this.logger.warn('client not found');
return null;
}
const waInstance = this.waMonitor.waInstances[instance.instanceName];
if (!waInstance) {
@@ -1279,6 +1492,13 @@ export class ChatwootService {
return null;
}
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');
@@ -1288,13 +1508,30 @@ export class ChatwootService {
}
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 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');
return;
}
@@ -1353,7 +1590,7 @@ export class ChatwootService {
}
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) {
this.logger.warn('message not sent');
@@ -1374,7 +1611,7 @@ export class ChatwootService {
this.logger.verbose('message is not group');
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) {
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');
if (adsMessage) {
this.logger.verbose('message is from Ads');
@@ -1436,6 +1702,8 @@ export class ChatwootService {
fileName,
messageType,
`${bodyMessage}\n\n\n**${title}**\n${description}\n${adsMessage.sourceUrl}`,
instance,
body,
);
if (!send) {
@@ -1471,7 +1739,7 @@ export class ChatwootService {
}
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) {
this.logger.warn('message not sent');
@@ -1492,7 +1760,7 @@ export class ChatwootService {
this.logger.verbose('message is not group');
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) {
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') {
this.logger.verbose('event status.instance');
const data = body;
@@ -1528,16 +1815,18 @@ export class ChatwootService {
await this.createBotMessage(instance, msgStatus, 'incoming');
}
// if (event === 'connection.update') {
// this.logger.verbose('event connection.update');
if (event === 'connection.update') {
this.logger.verbose('event connection.update');
// if (body.status === 'open') {
// const msgConnection = `🚀 Connection successfully established!`;
// this.logger.verbose('send message to chatwoot');
// await this.createBotMessage(instance, msgConnection, 'incoming');
// }
// }
if (body.status === 'open') {
// if we have qrcode count then we understand that a new connection was established
if (this.waMonitor.waInstances[instance.instanceName].qrCode.count > 0) {
const msgConnection = `🚀 Connection successfully established!`;
this.logger.verbose('send message to chatwoot');
await this.createBotMessage(instance, msgConnection, 'incoming');
}
}
}
if (event === 'qrcode.updated') {
this.logger.verbose('event qrcode.updated');

View File

@@ -12,12 +12,18 @@ import { dbserver } from '../../libs/db.connect';
import { RedisCache } from '../../libs/redis.client';
import {
AuthModel,
ChamaaiModel,
// ChatModel,
ChatwootModel,
ContactModel,
MessageModel,
MessageUpModel,
// ContactModel,
// MessageModel,
// MessageUpModel,
ProxyModel,
RabbitmqModel,
SettingsModel,
TypebotModel,
WebhookModel,
WebsocketModel,
} from '../models';
import { RepositoryBroker } from '../repository/repository.manager';
import { WAStartupService } from './whatsapp.service';
@@ -33,7 +39,7 @@ export class WAMonitoringService {
this.removeInstance();
this.noConnection();
this.delInstanceFiles();
// this.delInstanceFiles();
Object.assign(this.db, configService.get<Database>('DATABASE'));
Object.assign(this.redis, configService.get<Redis>('REDIS'));
@@ -106,6 +112,7 @@ export class WAMonitoringService {
const instanceData = {
instance: {
instanceName: key,
instanceId: (await this.repository.auth.find(key))?.instanceId,
owner: value.wuid,
profileName: (await value.getProfileName()) || 'not loaded',
profilePictureUrl: value.profilePictureUrl,
@@ -117,7 +124,7 @@ export class WAMonitoringService {
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
instanceData.instance['serverUrl'] = this.configService.get<HttpServer>('SERVER').URL;
instanceData.instance['apikey'] = (await this.repository.auth.find(key)).apikey;
instanceData.instance['apikey'] = (await this.repository.auth.find(key))?.apikey;
instanceData.instance['chatwoot'] = chatwoot;
}
@@ -129,6 +136,7 @@ export class WAMonitoringService {
const instanceData = {
instance: {
instanceName: key,
instanceId: (await this.repository.auth.find(key))?.instanceId,
status: value.connectionStatus.state,
},
};
@@ -136,7 +144,89 @@ export class WAMonitoringService {
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
instanceData.instance['serverUrl'] = this.configService.get<HttpServer>('SERVER').URL;
instanceData.instance['apikey'] = (await this.repository.auth.find(key)).apikey;
instanceData.instance['apikey'] = (await this.repository.auth.find(key))?.apikey;
instanceData.instance['chatwoot'] = chatwoot;
}
instances.push(instanceData);
}
}
}
this.logger.verbose('return instance info: ' + instances.length);
return instances.find((i) => i.instance.instanceName === instanceName) ?? 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;
}
@@ -187,6 +277,7 @@ export class WAMonitoringService {
public async cleaningUp(instanceName: string) {
this.logger.verbose('cleaning up instance: ' + instanceName);
if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
this.logger.verbose('cleaning up instance in database: ' + instanceName);
await this.repository.dbServer.connect();
@@ -233,13 +324,19 @@ export class WAMonitoringService {
this.logger.verbose('cleaning store database instance: ' + instanceName);
await AuthModel.deleteMany({ owner: instanceName });
await ContactModel.deleteMany({ owner: instanceName });
await MessageModel.deleteMany({ owner: instanceName });
await MessageUpModel.deleteMany({ owner: instanceName });
// await ChatModel.deleteMany({ owner: instanceName });
// await ContactModel.deleteMany({ owner: instanceName });
// await MessageUpModel.deleteMany({ owner: instanceName });
// await MessageModel.deleteMany({ owner: instanceName });
await AuthModel.deleteMany({ _id: instanceName });
await WebhookModel.deleteMany({ _id: instanceName });
await ChatwootModel.deleteMany({ _id: instanceName });
await ChamaaiModel.deleteMany({ _id: instanceName });
await ProxyModel.deleteMany({ _id: instanceName });
await RabbitmqModel.deleteMany({ _id: instanceName });
await TypebotModel.deleteMany({ _id: instanceName });
await WebsocketModel.deleteMany({ _id: instanceName });
await SettingsModel.deleteMany({ _id: instanceName });
return;
@@ -265,7 +362,6 @@ export class WAMonitoringService {
const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache);
instance.instanceName = name;
this.logger.verbose('Instance loaded: ' + name);
await instance.connectToWhatsapp();
this.logger.verbose('connectToWhatsapp: ' + name);

View File

@@ -9,9 +9,9 @@ export class ProxyService {
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.waMonitor.waInstances[instance.instanceName].setProxy(data);
this.waMonitor.waInstances[instance.instanceName].setProxy(data, reload);
return { proxy: { ...instance, proxy: data } };
}

View File

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

View File

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

View File

@@ -1,5 +1,7 @@
import axios from 'axios';
import EventEmitter2 from 'eventemitter2';
import { ConfigService, Typebot } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto';
import { Session, TypebotDto } from '../dto/typebot.dto';
@@ -8,7 +10,18 @@ import { Events } from '../types/wa.types';
import { WAMonitoringService } from './monitor.service';
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);
@@ -47,7 +60,7 @@ export class TypebotService {
findData.sessions.splice(findData.sessions.indexOf(session), 1);
const typebotData = {
enabled: true,
enabled: findData.enabled,
url: findData.url,
typebot: findData.typebot,
expire: findData.expire,
@@ -68,10 +81,24 @@ export class TypebotService {
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 = {
enabled: true,
enabled: findData.enabled,
url: findData.url,
typebot: findData.typebot,
expire: findData.expire,
@@ -95,14 +122,46 @@ export class TypebotService {
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) {
if (data.remoteJid === 'status@broadcast') return;
const remoteJid = data.remoteJid;
const url = data.url;
const typebot = data.typebot;
const startSession = data.startSession;
const variables = data.variables;
const findTypebot = await this.find(instance);
const sessions = (findTypebot.sessions as Session[]) ?? [];
const expire = findTypebot.expire;
const keyword_finish = findTypebot.keyword_finish;
const delay_message = findTypebot.delay_message;
@@ -121,7 +180,10 @@ export class TypebotService {
}
if (startSession) {
const newSessions = await this.clearSessions(instance, remoteJid);
const response = await this.createNewSession(instance, {
enabled: findTypebot.enabled,
url: url,
typebot: typebot,
remoteJid: remoteJid,
@@ -130,7 +192,7 @@ export class TypebotService {
delay_message: delay_message,
unknown_message: unknown_message,
listening_from_me: listening_from_me,
sessions: sessions,
sessions: newSessions,
prefilledVariables: prefilledVariables,
});
@@ -150,30 +212,47 @@ export class TypebotService {
} else {
const id = Math.floor(Math.random() * 10000000000).toString();
const reqData = {
startParams: {
typebot: data.typebot,
prefilledVariables: prefilledVariables,
},
};
try {
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`;
const request = await axios.post(data.url + '/api/v1/sendMessage', reqData);
reqData = {
prefilledVariables: prefilledVariables,
};
} else {
url = `${data.url}/api/v1/sendMessage`;
await this.sendWAMessage(
instance,
remoteJid,
request.data.messages,
request.data.input,
request.data.clientSideActions,
);
reqData = {
startParams: {
publicId: data.typebot,
prefilledVariables: prefilledVariables,
},
};
}
const request = await axios.post(url, reqData);
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, {
remoteJid: remoteJid,
url: url,
typebot: typebot,
variables: variables,
sessionId: id,
});
await this.sendWAMessage(
instance,
remoteJid,
request.data.messages,
request.data.input,
request.data.clientSideActions,
);
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, {
remoteJid: remoteJid,
url: url,
typebot: typebot,
variables: variables,
sessionId: id,
});
} catch (error) {
this.logger.error(error);
return;
}
}
return {
@@ -226,52 +305,75 @@ export class TypebotService {
}
public async createNewSession(instance: InstanceDto, data: any) {
if (data.remoteJid === 'status@broadcast') return;
const id = Math.floor(Math.random() * 10000000000).toString();
const reqData = {
startParams: {
typebot: data.typebot,
prefilledVariables: {
...data.prefilledVariables,
try {
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: {
...data.prefilledVariables,
remoteJid: data.remoteJid,
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,
},
},
};
}
const request = await axios.post(url, reqData);
if (request?.data?.sessionId) {
data.sessions.push({
remoteJid: data.remoteJid,
pushName: data.pushName || '',
instanceName: instance.instanceName,
},
},
};
sessionId: `${id}-${request.data.sessionId}`,
status: 'opened',
createdAt: Date.now(),
updateAt: Date.now(),
prefilledVariables: {
...data.prefilledVariables,
remoteJid: data.remoteJid,
pushName: data.pushName || '',
instanceName: instance.instanceName,
},
});
const request = await axios.post(data.url + '/api/v1/sendMessage', reqData);
const typebotData = {
enabled: data.enabled,
url: data.url,
typebot: data.typebot,
expire: data.expire,
keyword_finish: data.keyword_finish,
delay_message: data.delay_message,
unknown_message: data.unknown_message,
listening_from_me: data.listening_from_me,
sessions: data.sessions,
};
if (request.data.sessionId) {
data.sessions.push({
remoteJid: data.remoteJid,
sessionId: `${id}-${request.data.sessionId}`,
status: 'opened',
createdAt: Date.now(),
updateAt: Date.now(),
prefilledVariables: {
...data.prefilledVariables,
remoteJid: data.remoteJid,
pushName: data.pushName || '',
instanceName: instance.instanceName,
},
});
const typebotData = {
enabled: true,
url: data.url,
typebot: data.typebot,
expire: data.expire,
keyword_finish: data.keyword_finish,
delay_message: data.delay_message,
unknown_message: data.unknown_message,
listening_from_me: data.listening_from_me,
sessions: data.sessions,
};
this.create(instance, typebotData);
this.create(instance, typebotData);
}
return request.data;
} catch (error) {
this.logger.error(error);
return;
}
return request.data;
}
public async sendWAMessage(
@@ -281,11 +383,15 @@ export class TypebotService {
input: any[],
clientSideActions: any[],
) {
processMessages(this.waMonitor.waInstances[instance.instanceName], messages, input, clientSideActions).catch(
(err) => {
console.error('Erro ao processar mensagens:', err);
},
);
processMessages(
this.waMonitor.waInstances[instance.instanceName],
messages,
input,
clientSideActions,
this.eventEmitter,
).catch((err) => {
console.error('Erro ao processar mensagens:', err);
});
function findItemAndGetSecondsToWait(array, targetId) {
if (!array) return null;
@@ -298,7 +404,7 @@ export class TypebotService {
return null;
}
async function processMessages(instance, messages, input, clientSideActions) {
async function processMessages(instance, messages, input, clientSideActions, eventEmitter) {
for (const message of messages) {
const wait = findItemAndGetSecondsToWait(clientSideActions, message.id);
@@ -308,31 +414,50 @@ export class TypebotService {
let linkPreview = false;
for (const richText of message.content.richText) {
for (const element of richText.children) {
let text = '';
if (element.text) {
text = element.text;
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) {
let text = '';
if (element.bold) {
text = `*${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;
}
// if (element.text) {
// text = element.text;
// }
if (element.bold) {
text = `*${text}*`;
}
if (element.italic) {
text = `_${text}_`;
}
if (element.underline) {
text = `*${text}*`;
}
if (element.url) {
const linkText = element.children[0].text;
text = `[${linkText}](${element.url})`;
linkPreview = true;
}
formattedText += text;
}
if (element.italic) {
text = `_${text}_`;
}
if (element.underline) {
text = `~${text}~`;
}
if (element.url) {
const linkText = element.children[0].text;
text = `[${linkText}](${element.url})`;
linkPreview = true;
}
formattedText += text;
}
formattedText += '\n';
}
@@ -419,6 +544,11 @@ export class TypebotService {
},
});
}
} else {
eventEmitter.emit('typebot:end', {
instance: instance,
remoteJid: remoteJid,
});
}
}
}
@@ -436,17 +566,115 @@ export class TypebotService {
const session = sessions.find((session) => session.remoteJid === remoteJid);
if (session && expire && expire > 0) {
const now = Date.now();
try {
if (session && expire && expire > 0) {
const now = Date.now();
const diff = now - session.updateAt;
const diff = now - session.updateAt;
const diffInMinutes = Math.floor(diff / 1000 / 60);
const diffInMinutes = Math.floor(diff / 1000 / 60);
if (diffInMinutes > expire) {
sessions.splice(sessions.indexOf(session), 1);
if (diffInMinutes > expire) {
const newSessions = await this.clearSessions(instance, remoteJid);
const data = await this.createNewSession(instance, {
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,
remoteJid: remoteJid,
pushName: msg.pushName,
});
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;
}
}
if (session && session.status !== 'opened') {
return;
}
if (!session) {
const data = await this.createNewSession(instance, {
enabled: findTypebot.enabled,
url: url,
typebot: typebot,
expire: expire,
@@ -484,7 +712,7 @@ export class TypebotService {
sessions.splice(sessions.indexOf(session), 1);
const typebotData = {
enabled: true,
enabled: findTypebot.enabled,
url: url,
typebot: typebot,
expire: expire,
@@ -500,148 +728,48 @@ export class TypebotService {
return;
}
const reqData = {
message: content,
sessionId: data.sessionId,
};
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);
const request = await axios.post(url + '/api/v1/sendMessage', reqData);
console.log('request', request);
await this.sendWAMessage(
instance,
remoteJid,
request.data.messages,
request.data.input,
request.data.clientSideActions,
);
await this.sendWAMessage(
instance,
remoteJid,
request.data.messages,
request.data.input,
request.data.clientSideActions,
);
} catch (error) {
this.logger.error(error);
return;
}
}
return;
}
}
if (session && session.status !== 'opened') {
return;
}
if (!session) {
const data = await this.createNewSession(instance, {
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: sessions,
remoteJid: remoteJid,
pushName: msg.pushName,
sessions.map((session) => {
if (session.remoteJid === remoteJid) {
session.updateAt = Date.now();
}
});
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: true,
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;
}
const reqData = {
message: content,
sessionId: data.sessionId,
};
const request = await axios.post(url + '/api/v1/sendMessage', reqData);
console.log('request', request);
await this.sendWAMessage(
instance,
remoteJid,
request.data.messages,
request.data.input,
request.data.clientSideActions,
);
}
return;
}
sessions.map((session) => {
if (session.remoteJid === remoteJid) {
session.updateAt = Date.now();
}
});
const typebotData = {
enabled: true,
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);
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: true,
enabled: findTypebot.enabled,
url: url,
typebot: typebot,
expire: expire,
@@ -654,24 +782,73 @@ export class TypebotService {
this.create(instance, typebotData);
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;
}
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,
sessionId: session.sessionId.split('-')[1],
};
}
const request = await axios.post(urlTypebot, reqData);
await this.sendWAMessage(
instance,
remoteJid,
request.data.messages,
request.data.input,
request.data.clientSideActions,
);
return;
} catch (error) {
this.logger.error(error);
return;
}
const reqData = {
message: content,
sessionId: session.sessionId.split('-')[1],
};
const request = await axios.post(url + '/api/v1/sendMessage', reqData);
await this.sendWAMessage(
instance,
remoteJid,
request.data.messages,
request.data.input,
request.data.clientSideActions,
);
return;
}
}

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 {
APPLICATION_STARTUP = 'application.startup',
INSTANCE_CREATE = 'instance.create',
INSTANCE_DELETE = 'instance.delete',
QRCODE_UPDATED = 'qrcode.updated',
CONNECTION_UPDATE = 'connection.update',
STATUS_INSTANCE = 'status.instance',
@@ -84,6 +86,11 @@ export declare namespace wa {
events?: string[];
};
export type LocalSqs = {
enabled?: boolean;
events?: string[];
};
type Session = {
remoteJid?: string;
sessionId?: string;

View File

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