Compare commits

..

156 Commits
1.5.4 ... 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
58 changed files with 2451 additions and 656 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,57 @@
# 1.6.1 (2023-12-22 11:43)
### Fixed
* Fixed Lid Messages
* Fixed sending variables to typebot
* Fixed sending variables from typebot
* Correction sending s3/minio media to chatwoot and typebot
* Fixed the problem with typebot closing at the end of the flow, now this is optional with the TYPEBOT_KEEP_OPEN variable
* Fixed chatwoot Bold, Italic and Underline formatting using Regex
* Added the sign_delimiter property to the Chatwoot configuration, allowing you to set a different delimiter for the signature. Default when not defined \n
* Include instance Id field in the instance configuration
* Fixed the pairing code
* Adjusts in typebot
* Fix the problem when disconnecting the instance and connecting again using mongodb
* Options to disable docs and manager
* When deleting a message in whatsapp, delete the message in chatwoot too
# 1.6.0 (2023-12-12 17:24)
### Feature
* Added AWS SQS Integration
* Added support for new typebot API
* Added endpoint sendPresence
* New Instance Manager
* Added auto_create to the chatwoot set to create the inbox automatically or not
* Added reply, delete and message reaction in chatwoot v3.3.1
### Fixed
* Adjusts in proxy
* Adjusts in start session for Typebot
* Added mimetype field when sending media
* Ajusts in validations to messages.upsert
* Fixed messages not received: error handling when updating contact in chatwoot
* Fix workaround to manage param data as an array in mongodb
* Removed await from webhook when sending a message
* Update typebot.service.ts - element.underline change ~ for *
* Removed api restart on receiving an error
* Fixes in mongodb and chatwoot
* Adjusted return from queries in mongodb
* Added restart instance when update profile picture
* Correction of chatwoot functioning with admin flows
* Fixed problem that did not generate qrcode with the chatwoot_conversation_pending option enabled
* Fixed issue where CSAT opened a new ticket when reopen_conversation was disabled
* Fixed issue sending contact to Chatwoot via iOS
### Integrations
* Chatwoot: v3.3.1
* Typebot: v2.20.0
# 1.5.4 (2023-10-09 20:43)
### Fixed
@@ -75,9 +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)
@@ -165,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)
@@ -181,7 +235,7 @@
### Integrations
- Chatwoot: v2.18.0
* Chatwoot: v2.18.0
# 1.3.1 (2023-07-20 07:48)
@@ -191,7 +245,7 @@
### Integrations
- Chatwoot: v2.18.0
* Chatwoot: v2.18.0
# 1.3.0 (2023-07-19 11:33)
@@ -228,7 +282,7 @@
### Integrations
- Chatwoot: v2.18.0
* Chatwoot: v2.18.0
# 1.2.2 (2023-07-15 09:36)
@@ -239,7 +293,7 @@
### Integrations
- Chatwoot: v2.18.0
* Chatwoot: v2.18.0
# 1.2.1 (2023-07-14 19:04)

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

View File

@@ -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,8 +59,7 @@ 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');
@@ -63,14 +69,16 @@ export class ChatwootService {
this.logger.verbose('provider found');
return provider;
} catch (error) {
this.logger.error('provider not found');
return null;
}
// 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,6 +355,7 @@ export class ChatwootService {
}
this.logger.verbose('update contact in chatwoot');
try {
const contact = await client.contacts.update({
accountId: this.provider.account_id,
id,
@@ -346,6 +364,9 @@ export class ChatwootService {
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, true);
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, true);
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, true);
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,14 +212,27 @@ export class TypebotService {
} else {
const id = Math.floor(Math.random() * 10000000000).toString();
const reqData = {
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: prefilledVariables,
};
} else {
url = `${data.url}/api/v1/sendMessage`;
reqData = {
startParams: {
typebot: data.typebot,
publicId: data.typebot,
prefilledVariables: prefilledVariables,
},
};
const request = await axios.post(data.url + '/api/v1/sendMessage', reqData);
}
const request = await axios.post(url, reqData);
await this.sendWAMessage(
instance,
@@ -174,6 +249,10 @@ export class TypebotService {
variables: variables,
sessionId: id,
});
} catch (error) {
this.logger.error(error);
return;
}
}
return {
@@ -226,22 +305,42 @@ 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,
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 || '',
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);
const request = await axios.post(data.url + '/api/v1/sendMessage', reqData);
if (request.data.sessionId) {
if (request?.data?.sessionId) {
data.sessions.push({
remoteJid: data.remoteJid,
sessionId: `${id}-${request.data.sessionId}`,
@@ -257,7 +356,7 @@ export class TypebotService {
});
const typebotData = {
enabled: true,
enabled: data.enabled,
url: data.url,
typebot: data.typebot,
expire: data.expire,
@@ -270,8 +369,11 @@ export class TypebotService {
this.create(instance, typebotData);
}
return request.data;
} catch (error) {
this.logger.error(error);
return;
}
}
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) => {
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,12 +414,30 @@ export class TypebotService {
let linkPreview = false;
for (const richText of message.content.richText) {
if (richText.type === 'variable') {
for (const child of richText.children) {
for (const grandChild of child.children) {
formattedText += grandChild.text;
}
}
} else {
for (const element of richText.children) {
let text = '';
if (element.text) {
if (element.type === 'inline-variable') {
for (const child of element.children) {
for (const grandChild of child.children) {
text += grandChild.text;
}
}
} else if (element.text) {
text = element.text;
}
// if (element.text) {
// text = element.text;
// }
if (element.bold) {
text = `*${text}*`;
}
@@ -323,7 +447,7 @@ export class TypebotService {
}
if (element.underline) {
text = `~${text}~`;
text = `*${text}*`;
}
if (element.url) {
@@ -334,6 +458,7 @@ export class TypebotService {
formattedText += text;
}
}
formattedText += '\n';
}
@@ -419,6 +544,11 @@ export class TypebotService {
},
});
}
} else {
eventEmitter.emit('typebot:end', {
instance: instance,
remoteJid: remoteJid,
});
}
}
}
@@ -436,6 +566,7 @@ export class TypebotService {
const session = sessions.find((session) => session.remoteJid === remoteJid);
try {
if (session && expire && expire > 0) {
const now = Date.now();
@@ -444,9 +575,10 @@ export class TypebotService {
const diffInMinutes = Math.floor(diff / 1000 / 60);
if (diffInMinutes > expire) {
sessions.splice(sessions.indexOf(session), 1);
const newSessions = await this.clearSessions(instance, remoteJid);
const data = await this.createNewSession(instance, {
enabled: findTypebot.enabled,
url: url,
typebot: typebot,
expire: expire,
@@ -454,7 +586,7 @@ export class TypebotService {
delay_message: delay_message,
unknown_message: unknown_message,
listening_from_me: listening_from_me,
sessions: sessions,
sessions: newSessions,
remoteJid: remoteJid,
pushName: msg.pushName,
});
@@ -481,10 +613,10 @@ export class TypebotService {
}
if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) {
sessions.splice(sessions.indexOf(session), 1);
const newSessions = await this.clearSessions(instance, remoteJid);
const typebotData = {
enabled: true,
enabled: findTypebot.enabled,
url: url,
typebot: typebot,
expire: expire,
@@ -492,7 +624,7 @@ export class TypebotService {
delay_message: delay_message,
unknown_message: unknown_message,
listening_from_me: listening_from_me,
sessions,
sessions: newSessions,
};
this.create(instance, typebotData);
@@ -500,14 +632,25 @@ export class TypebotService {
return;
}
const reqData = {
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(url + '/api/v1/sendMessage', reqData);
const request = await axios.post(urlTypebot, reqData);
console.log('request', request);
await this.sendWAMessage(
instance,
remoteJid,
@@ -515,6 +658,10 @@ export class TypebotService {
request.data.input,
request.data.clientSideActions,
);
} catch (error) {
this.logger.error(error);
return;
}
}
return;
@@ -527,6 +674,7 @@ export class TypebotService {
if (!session) {
const data = await this.createNewSession(instance, {
enabled: findTypebot.enabled,
url: url,
typebot: typebot,
expire: expire,
@@ -564,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,
@@ -580,14 +728,25 @@ export class TypebotService {
return;
}
const reqData = {
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,
@@ -595,6 +754,10 @@ export class TypebotService {
request.data.input,
request.data.clientSideActions,
);
} catch (error) {
this.logger.error(error);
return;
}
}
return;
}
@@ -606,7 +769,7 @@ export class TypebotService {
});
const typebotData = {
enabled: true,
enabled: findTypebot.enabled,
url: url,
typebot: typebot,
expire: expire,
@@ -641,7 +804,7 @@ export class TypebotService {
sessions.splice(sessions.indexOf(session), 1);
const typebotData = {
enabled: true,
enabled: findTypebot.enabled,
url: url,
typebot: typebot,
expire: expire,
@@ -657,12 +820,22 @@ export class TypebotService {
return;
}
const reqData = {
const version = this.configService.get<Typebot>('TYPEBOT').API_VERSION;
let urlTypebot: string;
let reqData: {};
if (version === 'latest') {
urlTypebot = `${url}/api/v1/sessions/${session.sessionId.split('-')[1]}/continueChat`;
reqData = {
message: content,
};
} else {
urlTypebot = `${url}/api/v1/sendMessage`;
reqData = {
message: content,
sessionId: session.sessionId.split('-')[1],
};
const request = await axios.post(url + '/api/v1/sendMessage', reqData);
}
const request = await axios.post(urlTypebot, reqData);
await this.sendWAMessage(
instance,
@@ -672,6 +845,10 @@ export class TypebotService {
request.data.clientSideActions,
);
return;
} catch (error) {
this.logger.error(error);
return;
}
}
}

View File

@@ -17,6 +17,7 @@ import makeWASocket, {
getContentType,
getDevice,
GroupMetadata,
isJidBroadcast,
isJidGroup,
isJidUser,
makeCacheableSignalKeyStore,
@@ -60,6 +61,7 @@ import {
Log,
QrCode,
Redis,
Sqs,
Webhook,
Websocket,
} from '../../config/env.config';
@@ -70,6 +72,7 @@ import { getAMQP, removeQueues } from '../../libs/amqp.server';
import { dbserver } from '../../libs/db.connect';
import { RedisCache } from '../../libs/redis.client';
import { getIO } from '../../libs/socket.server';
import { getSQS, removeQueues as removeQueuesSQS } from '../../libs/sqs.server';
import { useMultiFileAuthStateDb } from '../../utils/use-multi-file-auth-state-db';
import { useMultiFileAuthStateRedisDb } from '../../utils/use-multi-file-auth-state-redis-db';
import {
@@ -81,6 +84,7 @@ import {
OnWhatsAppDto,
PrivacySettingDto,
ReadMessageDto,
SendPresenceDto,
WhatsAppNumberDto,
} from '../dto/chat.dto';
import {
@@ -113,7 +117,7 @@ import {
SendTextDto,
StatusMessage,
} from '../dto/sendMessage.dto';
import { ChamaaiRaw, ProxyRaw, RabbitmqRaw, SettingsRaw, TypebotRaw } from '../models';
import { ChamaaiRaw, ProxyRaw, RabbitmqRaw, SettingsRaw, SqsRaw, TypebotRaw } from '../models';
import { ChatRaw } from '../models/chat.model';
import { ChatwootRaw } from '../models/chatwoot.model';
import { ContactRaw } from '../models/contact.model';
@@ -128,9 +132,10 @@ import { Events, MessageSubtype, TypeMediaMessage, wa } from '../types/wa.types'
import { waMonitor } from '../whatsapp.module';
import { ChamaaiService } from './chamaai.service';
import { ChatwootService } from './chatwoot.service';
//import { SocksProxyAgent } from './socks-proxy-agent';
import { TypebotService } from './typebot.service';
const retryCache = {};
export class WAStartupService {
constructor(
private readonly configService: ConfigService,
@@ -151,6 +156,7 @@ export class WAStartupService {
private readonly localSettings: wa.LocalSettings = {};
private readonly localWebsocket: wa.LocalWebsocket = {};
private readonly localRabbitmq: wa.LocalRabbitmq = {};
private readonly localSqs: wa.LocalSqs = {};
public readonly localTypebot: wa.LocalTypebot = {};
private readonly localProxy: wa.LocalProxy = {};
private readonly localChamaai: wa.LocalChamaai = {};
@@ -163,9 +169,9 @@ export class WAStartupService {
private phoneNumber: string;
private chatwootService = new ChatwootService(waMonitor, this.configService);
private chatwootService = new ChatwootService(waMonitor, this.configService, this.repository);
private typebotService = new TypebotService(waMonitor);
private typebotService = new TypebotService(waMonitor, this.configService, this.eventEmitter);
private chamaaiService = new ChamaaiService(waMonitor, this.configService);
@@ -208,6 +214,7 @@ export class WAStartupService {
public async getProfileName() {
this.logger.verbose('Getting profile name');
let profileName = this.client.user?.name ?? this.client.user?.verifiedName;
if (!profileName) {
this.logger.verbose('Profile name not found, trying to get from database');
@@ -258,6 +265,7 @@ export class WAStartupService {
pairingCode: this.instance.qrcode?.pairingCode,
code: this.instance.qrcode?.code,
base64: this.instance.qrcode?.base64,
count: this.instance.qrcode?.count,
};
}
@@ -302,7 +310,14 @@ export class WAStartupService {
this.logger.verbose(`Webhook url: ${data.url}`);
this.logger.verbose(`Webhook events: ${data.events}`);
return data;
return {
enabled: data.enabled,
url: data.url,
events: data.events,
webhook_by_events: data.webhook_by_events,
webhook_base64: data.webhook_base64,
};
}
private async loadChatwoot() {
@@ -346,10 +361,12 @@ export class WAStartupService {
this.logger.verbose(`Chatwoot url: ${data.url}`);
this.logger.verbose(`Chatwoot inbox name: ${data.name_inbox}`);
this.logger.verbose(`Chatwoot sign msg: ${data.sign_msg}`);
this.logger.verbose(`Chatwoot sign delimiter: ${data.sign_delimiter}`);
this.logger.verbose(`Chatwoot reopen conversation: ${data.reopen_conversation}`);
this.logger.verbose(`Chatwoot conversation pending: ${data.conversation_pending}`);
Object.assign(this.localChatwoot, data);
Object.assign(this.localChatwoot, { ...data, sign_delimiter: data.sign_msg ? data.sign_delimiter : null });
this.logger.verbose('Chatwoot set');
}
@@ -367,10 +384,21 @@ export class WAStartupService {
this.logger.verbose(`Chatwoot url: ${data.url}`);
this.logger.verbose(`Chatwoot inbox name: ${data.name_inbox}`);
this.logger.verbose(`Chatwoot sign msg: ${data.sign_msg}`);
this.logger.verbose(`Chatwoot sign delimiter: ${data.sign_delimiter}`);
this.logger.verbose(`Chatwoot reopen conversation: ${data.reopen_conversation}`);
this.logger.verbose(`Chatwoot conversation pending: ${data.conversation_pending}`);
return data;
return {
enabled: data.enabled,
account_id: data.account_id,
token: data.token,
url: data.url,
name_inbox: data.name_inbox,
sign_msg: data.sign_msg,
sign_delimiter: data.sign_delimiter || null,
reopen_conversation: data.reopen_conversation,
conversation_pending: data.conversation_pending,
};
}
private async loadSettings() {
@@ -427,7 +455,14 @@ export class WAStartupService {
this.logger.verbose(`Settings always_online: ${data.always_online}`);
this.logger.verbose(`Settings read_messages: ${data.read_messages}`);
this.logger.verbose(`Settings read_status: ${data.read_status}`);
return data;
return {
reject_call: data.reject_call,
msg_call: data.msg_call,
groups_ignore: data.groups_ignore,
always_online: data.always_online,
read_messages: data.read_messages,
read_status: data.read_status,
};
}
private async loadWebsocket() {
@@ -461,7 +496,10 @@ export class WAStartupService {
}
this.logger.verbose(`Websocket events: ${data.events}`);
return data;
return {
enabled: data.enabled,
events: data.events,
};
}
private async loadRabbitmq() {
@@ -495,7 +533,10 @@ export class WAStartupService {
}
this.logger.verbose(`Rabbitmq events: ${data.events}`);
return data;
return {
enabled: data.enabled,
events: data.events,
};
}
public async removeRabbitmqQueues() {
@@ -506,6 +547,51 @@ export class WAStartupService {
}
}
private async loadSqs() {
this.logger.verbose('Loading sqs');
const data = await this.repository.sqs.find(this.instanceName);
this.localSqs.enabled = data?.enabled;
this.logger.verbose(`Sqs enabled: ${this.localSqs.enabled}`);
this.localSqs.events = data?.events;
this.logger.verbose(`Sqs events: ${this.localSqs.events}`);
this.logger.verbose('Sqs loaded');
}
public async setSqs(data: SqsRaw) {
this.logger.verbose('Setting sqs');
await this.repository.sqs.create(data, this.instanceName);
this.logger.verbose(`Sqs events: ${data.events}`);
Object.assign(this.localSqs, data);
this.logger.verbose('Sqs set');
}
public async findSqs() {
this.logger.verbose('Finding sqs');
const data = await this.repository.sqs.find(this.instanceName);
if (!data) {
this.logger.verbose('Sqs not found');
throw new NotFoundException('Sqs not found');
}
this.logger.verbose(`Sqs events: ${data.events}`);
return {
enabled: data.enabled,
events: data.events,
};
}
public async removeSqsQueues() {
this.logger.verbose('Removing sqs');
if (this.localSqs.enabled) {
removeQueuesSQS(this.instanceName, this.localSqs.events);
}
}
private async loadTypebot() {
this.logger.verbose('Loading typebot');
const data = await this.repository.typebot.find(this.instanceName);
@@ -561,7 +647,17 @@ export class WAStartupService {
throw new NotFoundException('Typebot not found');
}
return data;
return {
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,
};
}
private async loadProxy() {
@@ -577,14 +673,16 @@ export class WAStartupService {
this.logger.verbose('Proxy loaded');
}
public async setProxy(data: ProxyRaw) {
public async setProxy(data: ProxyRaw, reload = true) {
this.logger.verbose('Setting proxy');
await this.repository.proxy.create(data, this.instanceName);
this.logger.verbose(`Proxy proxy: ${data.proxy}`);
Object.assign(this.localProxy, data);
this.logger.verbose('Proxy set');
this.client?.ws?.close();
if (reload) {
this.reloadConnection();
}
}
public async findProxy() {
@@ -596,7 +694,10 @@ export class WAStartupService {
throw new NotFoundException('Proxy not found');
}
return data;
return {
enabled: data.enabled,
proxy: data.proxy,
};
}
private async loadChamaai() {
@@ -642,7 +743,13 @@ export class WAStartupService {
throw new NotFoundException('Chamaai not found');
}
return data;
return {
enabled: data.enabled,
url: data.url,
token: data.token,
waNumber: data.waNumber,
answerByAudio: data.answerByAudio,
};
}
public async sendDataWebhook<T = any>(event: Events, data: T, local = true) {
@@ -650,6 +757,7 @@ export class WAStartupService {
const webhookLocal = this.localWebhook.events;
const websocketLocal = this.localWebsocket.events;
const rabbitmqLocal = this.localRabbitmq.events;
const sqsLocal = this.localSqs.events;
const serverUrl = this.configService.get<HttpServer>('SERVER').URL;
const we = event.replace(/[.-]/gm, '_').toUpperCase();
const transformedWe = we.replace(/_/gm, '-').toLowerCase();
@@ -722,6 +830,76 @@ export class WAStartupService {
}
}
if (this.localSqs.enabled) {
const sqs = getSQS();
if (sqs) {
if (Array.isArray(sqsLocal) && sqsLocal.includes(we)) {
const eventFormatted = `${event.replace('.', '_').toLowerCase()}`;
const queueName = `${this.instanceName}_${eventFormatted}.fifo`;
const sqsConfig = this.configService.get<Sqs>('SQS');
const sqsUrl = `https://sqs.${sqsConfig.REGION}.amazonaws.com/${sqsConfig.ACCOUNT_ID}/${queueName}`;
const message = {
event,
instance: this.instance.name,
data,
server_url: serverUrl,
date_time: now,
sender: this.wuid,
};
if (expose && instanceApikey) {
message['apikey'] = instanceApikey;
}
const params = {
MessageBody: JSON.stringify(message),
MessageGroupId: 'evolution',
MessageDeduplicationId: `${this.instanceName}_${eventFormatted}_${Date.now()}`,
QueueUrl: sqsUrl,
};
sqs.sendMessage(params, (err, data) => {
if (err) {
this.logger.error({
local: WAStartupService.name + '.sendData-SQS',
message: err?.message,
hostName: err?.hostname,
code: err?.code,
stack: err?.stack,
name: err?.name,
url: queueName,
server_url: serverUrl,
});
} else {
if (this.configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS')) {
const logData = {
local: WAStartupService.name + '.sendData-SQS',
event,
instance: this.instance.name,
data,
server_url: serverUrl,
apikey: (expose && instanceApikey) || null,
date_time: now,
sender: this.wuid,
};
if (expose && instanceApikey) {
logData['apikey'] = instanceApikey;
}
this.logger.log(logData);
}
}
});
}
}
}
if (this.configService.get<Websocket>('WEBSOCKET')?.ENABLED && this.localWebsocket.enabled) {
this.logger.verbose('Sending data to websocket on channel: ' + this.instance.name);
if (Array.isArray(websocketLocal) && websocketLocal.includes(we)) {
@@ -1059,12 +1237,18 @@ export class WAStartupService {
this.logger.verbose('Connection opened');
this.instance.wuid = this.client.user.id.replace(/:\d+/, '');
this.instance.profilePictureUrl = (await this.profilePicture(this.instance.wuid)).profilePictureUrl;
const formattedWuid = this.instance.wuid.split('@')[0].padEnd(30, ' ');
const formattedName = this.instance.name;
this.logger.info(
`
┌──────────────────────────────┐
│ CONNECTED TO WHATSAPP │
└──────────────────────────────┘`.replace(/^ +/gm, ' '),
);
this.logger.info(`
wuid: ${formattedWuid}
name: ${formattedName}
`);
if (this.localChatwoot.enabled) {
this.chatwootService.eventWhatsapp(
@@ -1167,6 +1351,7 @@ export class WAStartupService {
this.loadSettings();
this.loadWebsocket();
this.loadRabbitmq();
this.loadSqs();
this.loadTypebot();
this.loadProxy();
this.loadChamaai();
@@ -1182,12 +1367,23 @@ export class WAStartupService {
let options;
if (this.localProxy.enabled) {
this.logger.verbose('Proxy enabled');
this.logger.info('Proxy enabled: ' + this.localProxy.proxy);
if (this.localProxy.proxy.includes('proxyscrape')) {
const response = await axios.get(this.localProxy.proxy);
const text = response.data;
const proxyUrls = text.split('\r\n');
const rand = Math.floor(Math.random() * Math.floor(proxyUrls.length));
const proxyUrl = 'http://' + proxyUrls[rand];
options = {
agent: new ProxyAgent(proxyUrl as any),
};
} else {
options = {
agent: new ProxyAgent(this.localProxy.proxy as any),
fetchAgent: new ProxyAgent(this.localProxy.proxy as any),
};
}
}
const socketConfig: UserFacingSocketConfig = {
...options,
@@ -1197,19 +1393,26 @@ export class WAStartupService {
},
logger: P({ level: this.logBaileys }),
printQRInTerminal: false,
browser,
browser: number ? ['Chrome (Linux)', session.NAME, release()] : browser,
version,
markOnlineOnConnect: this.localSettings.always_online,
retryRequestDelayMs: 10,
connectTimeoutMs: 60_000,
qrTimeout: 40_000,
defaultQueryTimeoutMs: undefined,
emitOwnEvents: false,
shouldIgnoreJid: (jid) => {
const isGroupJid = this.localSettings.groups_ignore && isJidGroup(jid);
const isBroadcast = !this.localSettings.read_status && isJidBroadcast(jid);
return isGroupJid || isBroadcast;
},
msgRetryCounterCache: this.msgRetryCounterCache,
getMessage: async (key) => (await this.getMessage(key)) as Promise<proto.IMessage>,
generateHighQualityLinkPreview: true,
syncFullHistory: true,
syncFullHistory: false,
userDevicesCache: this.userDevicesCache,
transactionOpts: { maxCommitRetries: 1, delayBetweenTriesMs: 10 },
transactionOpts: { maxCommitRetries: 10, delayBetweenTriesMs: 10 },
patchMessageBeforeSending: (message) => {
const requiresPatch = !!(message.buttonsMessage || message.listMessage || message.templateMessage);
if (requiresPatch) {
@@ -1277,19 +1480,26 @@ export class WAStartupService {
},
logger: P({ level: this.logBaileys }),
printQRInTerminal: false,
browser,
browser: this.phoneNumber ? ['Chrome (Linux)', session.NAME, release()] : browser,
version,
markOnlineOnConnect: this.localSettings.always_online,
retryRequestDelayMs: 10,
connectTimeoutMs: 60_000,
qrTimeout: 40_000,
defaultQueryTimeoutMs: undefined,
emitOwnEvents: false,
shouldIgnoreJid: (jid) => {
const isGroupJid = this.localSettings.groups_ignore && isJidGroup(jid);
const isBroadcast = !this.localSettings.read_status && isJidBroadcast(jid);
return isGroupJid || isBroadcast;
},
msgRetryCounterCache: this.msgRetryCounterCache,
getMessage: async (key) => (await this.getMessage(key)) as Promise<proto.IMessage>,
generateHighQualityLinkPreview: true,
syncFullHistory: true,
syncFullHistory: false,
userDevicesCache: this.userDevicesCache,
transactionOpts: { maxCommitRetries: 1, delayBetweenTriesMs: 10 },
transactionOpts: { maxCommitRetries: 10, delayBetweenTriesMs: 10 },
patchMessageBeforeSending: (message) => {
const requiresPatch = !!(message.buttonsMessage || message.listMessage || message.templateMessage);
if (requiresPatch) {
@@ -1339,10 +1549,10 @@ export class WAStartupService {
}
this.logger.verbose('Sending data to webhook in event CHATS_UPSERT');
await this.sendDataWebhook(Events.CHATS_UPSERT, chatsRaw);
this.sendDataWebhook(Events.CHATS_UPSERT, chatsRaw);
this.logger.verbose('Inserting chats in database');
await this.repository.chat.insert(chatsRaw, this.instance.name, database.SAVE_DATA.CHATS);
this.repository.chat.insert(chatsRaw, this.instance.name, database.SAVE_DATA.CHATS);
},
'chats.update': async (
@@ -1360,7 +1570,7 @@ export class WAStartupService {
});
this.logger.verbose('Sending data to webhook in event CHATS_UPDATE');
await this.sendDataWebhook(Events.CHATS_UPDATE, chatsRaw);
this.sendDataWebhook(Events.CHATS_UPDATE, chatsRaw);
},
'chats.delete': async (chats: string[]) => {
@@ -1375,7 +1585,7 @@ export class WAStartupService {
);
this.logger.verbose('Sending data to webhook in event CHATS_DELETE');
await this.sendDataWebhook(Events.CHATS_DELETE, [...chats]);
this.sendDataWebhook(Events.CHATS_DELETE, [...chats]);
},
};
@@ -1404,10 +1614,10 @@ export class WAStartupService {
}
this.logger.verbose('Sending data to webhook in event CONTACTS_UPSERT');
await this.sendDataWebhook(Events.CONTACTS_UPSERT, contactsRaw);
this.sendDataWebhook(Events.CONTACTS_UPSERT, contactsRaw);
this.logger.verbose('Inserting contacts in database');
await this.repository.contact.insert(contactsRaw, this.instance.name, database.SAVE_DATA.CONTACTS);
this.repository.contact.insert(contactsRaw, this.instance.name, database.SAVE_DATA.CONTACTS);
},
'contacts.update': async (contacts: Partial<Contact>[], database: Database) => {
@@ -1425,10 +1635,10 @@ export class WAStartupService {
}
this.logger.verbose('Sending data to webhook in event CONTACTS_UPDATE');
await this.sendDataWebhook(Events.CONTACTS_UPDATE, contactsRaw);
this.sendDataWebhook(Events.CONTACTS_UPDATE, contactsRaw);
this.logger.verbose('Updating contacts in database');
await this.repository.contact.update(contactsRaw, this.instance.name, database.SAVE_DATA.CONTACTS);
this.repository.contact.update(contactsRaw, this.instance.name, database.SAVE_DATA.CONTACTS);
},
};
@@ -1458,10 +1668,10 @@ export class WAStartupService {
});
this.logger.verbose('Sending data to webhook in event CHATS_SET');
await this.sendDataWebhook(Events.CHATS_SET, chatsRaw);
this.sendDataWebhook(Events.CHATS_SET, chatsRaw);
this.logger.verbose('Inserting chats in database');
await this.repository.chat.insert(chatsRaw, this.instance.name, database.SAVE_DATA.CHATS);
this.repository.chat.insert(chatsRaw, this.instance.name, database.SAVE_DATA.CHATS);
}
const messagesRaw: MessageRaw[] = [];
@@ -1508,14 +1718,12 @@ export class WAStartupService {
database: Database,
settings: SettingsRaw,
) => {
try {
this.logger.verbose('Event received: messages.upsert');
const received = messages[0];
for (const received of messages) {
if (
type !== 'notify' ||
!received?.message ||
(type !== 'notify' && type !== 'append') ||
received.message?.protocolMessage ||
received.message.senderKeyDistributionMessage ||
received.message?.pollUpdateMessage
) {
this.logger.verbose('message rejected');
@@ -1535,7 +1743,7 @@ export class WAStartupService {
if (
(this.localWebhook.webhook_base64 === true && received?.message.documentMessage) ||
received?.message.imageMessage
received?.message?.imageMessage
) {
const buffer = await downloadMediaMessage(
{ key: received.key, message: received?.message },
@@ -1546,7 +1754,6 @@ export class WAStartupService {
reuploadRequest: this.client.updateMediaMessage,
},
);
console.log(buffer);
messageRaw = {
key: received.key,
pushName: received.pushName,
@@ -1582,18 +1789,31 @@ export class WAStartupService {
this.logger.log(messageRaw);
this.logger.verbose('Sending data to webhook in event MESSAGES_UPSERT');
await this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw);
this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw);
if (this.localChatwoot.enabled) {
await this.chatwootService.eventWhatsapp(
if (this.localChatwoot.enabled && !received.key.id.includes('@broadcast')) {
const chatwootSentMessage = await this.chatwootService.eventWhatsapp(
Events.MESSAGES_UPSERT,
{ instanceName: this.instance.name },
messageRaw,
);
if (chatwootSentMessage?.id) {
messageRaw.chatwoot = {
messageId: chatwootSentMessage.id,
inboxId: chatwootSentMessage.inbox_id,
conversationId: chatwootSentMessage.conversation_id,
};
}
}
if (this.localTypebot.enabled) {
const typebotSessionRemoteJid = this.localTypebot.sessions?.find(
(session) => session.remoteJid === received.key.remoteJid,
);
if ((this.localTypebot.enabled && type === 'notify') || typebotSessionRemoteJid) {
if (!(this.localTypebot.listening_from_me === false && messageRaw.key.fromMe === true)) {
if (messageRaw.messageType !== 'reactionMessage')
await this.typebotService.sendTypebot(
{ instanceName: this.instance.name },
messageRaw.key.remoteJid,
@@ -1602,7 +1822,7 @@ export class WAStartupService {
}
}
if (this.localChamaai.enabled && messageRaw.key.fromMe === false) {
if (this.localChamaai.enabled && messageRaw.key.fromMe === false && type === 'notify') {
await this.chamaaiService.sendChamaai(
{ instanceName: this.instance.name },
messageRaw.key.remoteJid,
@@ -1640,7 +1860,7 @@ export class WAStartupService {
};
this.logger.verbose('Sending data to webhook in event CONTACTS_UPDATE');
await this.sendDataWebhook(Events.CONTACTS_UPDATE, contactRaw);
this.sendDataWebhook(Events.CONTACTS_UPDATE, contactRaw);
if (this.localChatwoot.enabled) {
await this.chatwootService.eventWhatsapp(
@@ -1658,10 +1878,14 @@ export class WAStartupService {
this.logger.verbose('Contact not found in database');
this.logger.verbose('Sending data to webhook in event CONTACTS_UPSERT');
await this.sendDataWebhook(Events.CONTACTS_UPSERT, contactRaw);
this.sendDataWebhook(Events.CONTACTS_UPSERT, contactRaw);
this.logger.verbose('Inserting contact in database');
await this.repository.contact.insert([contactRaw], this.instance.name, database.SAVE_DATA.CONTACTS);
this.repository.contact.insert([contactRaw], this.instance.name, database.SAVE_DATA.CONTACTS);
}
} catch (error) {
this.logger.error(error);
}
},
'messages.update': async (args: WAMessageUpdate[], database: Database, settings: SettingsRaw) => {
@@ -1705,7 +1929,7 @@ export class WAStartupService {
this.logger.verbose('Message deleted');
this.logger.verbose('Sending data to webhook in event MESSAGE_DELETE');
await this.sendDataWebhook(Events.MESSAGES_DELETE, key);
this.sendDataWebhook(Events.MESSAGES_DELETE, key);
const message: MessageUpdateRaw = {
...key,
@@ -1722,6 +1946,15 @@ export class WAStartupService {
this.instance.name,
database.SAVE_DATA.MESSAGE_UPDATE,
);
if (this.localChatwoot.enabled) {
this.chatwootService.eventWhatsapp(
Events.MESSAGES_DELETE,
{ instanceName: this.instance.name },
{ key: key },
);
}
return;
}
@@ -1736,10 +1969,10 @@ export class WAStartupService {
this.logger.verbose(message);
this.logger.verbose('Sending data to webhook in event MESSAGES_UPDATE');
await this.sendDataWebhook(Events.MESSAGES_UPDATE, message);
this.sendDataWebhook(Events.MESSAGES_UPDATE, message);
this.logger.verbose('Inserting message in database');
await this.repository.messageUpdate.insert([message], this.instance.name, database.SAVE_DATA.MESSAGE_UPDATE);
this.repository.messageUpdate.insert([message], this.instance.name, database.SAVE_DATA.MESSAGE_UPDATE);
}
}
},
@@ -1788,7 +2021,7 @@ export class WAStartupService {
this.client.rejectCall(call.id, call.from);
}
if (settings?.msg_call.trim().length > 0 && call.status == 'offer') {
if (settings?.msg_call?.trim().length > 0 && call.status == 'offer') {
this.logger.verbose('Sending message in call');
const msg = await this.client.sendMessage(call.from, {
text: settings.msg_call,
@@ -1824,12 +2057,27 @@ export class WAStartupService {
if (events['messages.upsert']) {
this.logger.verbose('Listening event: messages.upsert');
const payload = events['messages.upsert'];
if (payload.messages.find((a) => a?.messageStubType === 2)) {
const msg = payload.messages[0];
retryCache[msg.key.id] = msg;
return;
}
this.messageHandle['messages.upsert'](payload, database, settings);
}
if (events['messages.update']) {
this.logger.verbose('Listening event: messages.update');
const payload = events['messages.update'];
payload.forEach((message) => {
if (retryCache[message.key.id]) {
this.client.ev.emit('messages.upsert', {
messages: [message],
type: 'notify',
});
delete retryCache[message.key.id];
return;
}
});
this.messageHandle['messages.update'](payload, database, settings);
}
@@ -1934,8 +2182,8 @@ export class WAStartupService {
private createJid(number: string): string {
this.logger.verbose('Creating jid with number: ' + number);
if (number.includes('@g.us') || number.includes('@s.whatsapp.net')) {
this.logger.verbose('Number already contains @g.us or @s.whatsapp.net');
if (number.includes('@g.us') || number.includes('@s.whatsapp.net') || number.includes('@lid')) {
this.logger.verbose('Number already contains @g.us or @s.whatsapp.net or @lid');
return number;
}
@@ -2159,6 +2407,20 @@ export class WAStartupService {
!message['conversation'] &&
sender !== 'status@broadcast'
) {
if (message['reactionMessage']) {
this.logger.verbose('Sending reaction');
return await this.client.sendMessage(
sender,
{
react: {
text: message['reactionMessage']['text'],
key: message['reactionMessage']['key'],
},
} as unknown as AnyMessageContent,
option as unknown as MiscMessageGenerationOptions,
);
}
if (!message['audio']) {
this.logger.verbose('Sending message');
return await this.client.sendMessage(
@@ -2174,7 +2436,6 @@ export class WAStartupService {
);
}
}
if (message['conversation']) {
this.logger.verbose('Sending message');
return await this.client.sendMessage(
@@ -2222,7 +2483,7 @@ export class WAStartupService {
this.logger.log(messageRaw);
this.logger.verbose('Sending data to webhook in event SEND_MESSAGE');
await this.sendDataWebhook(Events.SEND_MESSAGE, messageRaw);
this.sendDataWebhook(Events.SEND_MESSAGE, messageRaw);
if (this.localChatwoot.enabled && !isChatwoot) {
this.chatwootService.eventWhatsapp(Events.SEND_MESSAGE, { instanceName: this.instance.name }, messageRaw);
@@ -2248,6 +2509,38 @@ export class WAStartupService {
return this.stateConnection;
}
public async sendPresence(data: SendPresenceDto) {
try {
const { number } = data;
this.logger.verbose(`Check if number "${number}" is WhatsApp`);
const isWA = (await this.whatsappNumber({ numbers: [number] }))?.shift();
this.logger.verbose(`Exists: "${isWA.exists}" | jid: ${isWA.jid}`);
if (!isWA.exists && !isJidGroup(isWA.jid) && !isWA.jid.includes('@broadcast')) {
throw new BadRequestException(isWA);
}
const sender = isWA.jid;
this.logger.verbose('Sending presence');
await this.client.presenceSubscribe(sender);
this.logger.verbose('Subscribing to presence');
await this.client.sendPresenceUpdate(data.options?.presence ?? 'composing', sender);
this.logger.verbose('Sending presence update: ' + data.options?.presence ?? 'composing');
await delay(data.options.delay);
this.logger.verbose('Set delay: ' + data.options.delay);
await this.client.sendPresenceUpdate('paused', sender);
this.logger.verbose('Sending presence update: paused');
} catch (error) {
this.logger.error(error);
throw new BadRequestException(error.toString());
}
}
// Send Message Controller
public async textMessage(data: SendTextDto, isChatwoot = false) {
this.logger.verbose('Sending text message');
@@ -2434,11 +2727,17 @@ export class WAStartupService {
let mimetype: string;
if (mediaMessage.mimetype) {
mimetype = mediaMessage.mimetype;
} else {
if (isURL(mediaMessage.media)) {
mimetype = getMIMEType(mediaMessage.media);
const response = await axios.get(mediaMessage.media, { responseType: 'arraybuffer' });
mimetype = response.headers['content-type'];
} else {
mimetype = getMIMEType(mediaMessage.fileName);
}
}
this.logger.verbose('Mimetype: ' + mimetype);
@@ -3062,7 +3361,16 @@ export class WAStartupService {
public async fetchPrivacySettings() {
this.logger.verbose('Fetching privacy settings');
return await this.client.fetchPrivacySettings();
const privacy = await this.client.fetchPrivacySettings();
return {
readreceipts: privacy.readreceipts,
profile: privacy.profile,
status: privacy.status,
online: privacy.online,
last: privacy.last,
groupadd: privacy.groupadd,
};
}
public async updatePrivacySettings(settings: PrivacySettingDto) {
@@ -3086,7 +3394,7 @@ export class WAStartupService {
await this.client.updateGroupsAddPrivacy(settings.privacySettings.groupadd);
this.logger.verbose('Groups add privacy updated');
this.client?.ws?.close();
this.reloadConnection();
return {
update: 'success',
@@ -3174,9 +3482,12 @@ export class WAStartupService {
} else {
throw new BadRequestException('"profilePicture" must be a url or a base64');
}
await this.client.updateProfilePicture(this.instance.wuid, pic);
this.logger.verbose('Profile picture updated');
this.reloadConnection();
return { update: 'success' };
} catch (error) {
throw new InternalServerErrorException('Error updating profile picture', error.toString());
@@ -3188,6 +3499,8 @@ export class WAStartupService {
try {
await this.client.removeProfilePicture(this.instance.wuid);
this.reloadConnection();
return { update: 'success' };
} catch (error) {
throw new InternalServerErrorException('Error removing profile picture', error.toString());
@@ -3302,7 +3615,7 @@ export class WAStartupService {
subject: group.subject,
subjectOwner: group.subjectOwner,
subjectTime: group.subjectTime,
size: group.size,
size: group.participants.length,
creation: group.creation,
owner: group.owner,
desc: group.desc,

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);