Compare commits

...

695 Commits
1.4.8 ... 1.7.4

Author SHA1 Message Date
Davidson Gomes
633dbb82d3 Merge branch 'release/1.7.4' 2024-04-28 09:47:19 -03:00
Davidson Gomes
95907b3cea v 1.7.4 2024-04-28 09:47:06 -03:00
Davidson Gomes
14c210c771 fix: new version baileys 2024-04-28 09:41:22 -03:00
Davidson Gomes
00e7fcc46b changelog 2024-04-27 13:13:43 -03:00
Davidson Gomes
720efcbcbf Merge pull request #566 from judsonjuniorr/feat/cw-inbox-name
Chatwoot inbox name
2024-04-27 13:13:06 -03:00
Davidson Gomes
d95791cc18 changelog 2024-04-27 13:12:29 -03:00
Davidson Gomes
d45b7af3b6 fix: recovering messages 2024-04-27 13:11:33 -03:00
Davidson Gomes
0ad3acaf07 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2024-04-24 18:55:49 -03:00
Davidson Gomes
e27818ecda adjusts in integration 2024-04-24 18:55:38 -03:00
Davidson Gomes
ff21cf4d4c Merge pull request #558 from jaison-x/pr2
feat(chatwoot): send private message on error message sent from chatwoot
2024-04-23 19:11:10 -03:00
Davidson Gomes
dc19c7fdec Merge pull request #555 from jaison-x/pr
fix(chatwoot): fix bug when chatwoot params reopen_conversation and conversation_pending are enabled
2024-04-23 19:10:57 -03:00
jaison-x
6c8ffd8ec6 feat(chatwoot): send private message on error message sent from chatwoot 2024-04-23 17:26:22 -03:00
jaison-x
96fdb210be feat(chatwoot): send private message on error message sent from chatwoot 2024-04-23 17:15:41 -03:00
jaison-x
84ad8e0d6e feat(chatwoot): send private message on error message sent from chatwoot 2024-04-23 15:09:28 -03:00
Davidson Gomes
53361682f4 Recovering messages 2024-04-23 08:01:59 -03:00
jaison-x
0ee243f284 fix(chatwoot): fix bug when chatwoot params reopen_conversation and conversation_pending are enabled 2024-04-22 13:41:06 -03:00
Judson Cairo
26ff0b634f Revert "Update current inbox if exists"
This reverts commit f44ab0f678.
2024-04-21 16:49:15 -03:00
Judson Cairo
f44ab0f678 Update current inbox if exists 2024-04-21 16:46:49 -03:00
Judson Cairo
1f128747bb Fix inbox name on cw config 2024-04-21 16:37:27 -03:00
Judson Cairo
3bf975d90f Set chatwoot custom inbox name 2024-04-21 16:33:20 -03:00
Davidson Gomes
92f8951be4 Merge tag '2.3.9' into develop
v
2024-04-18 21:24:30 -03:00
Davidson Gomes
e071f56767 Merge branch 'release/2.3.9' 2024-04-18 21:24:27 -03:00
Davidson Gomes
c85619efcf fix: Recovering messages lost with redis cache 2024-04-18 17:33:09 -03:00
Davidson Gomes
27f9ae1e56 fix: Recovering messages lost with redis cache 2024-04-18 17:31:10 -03:00
Davidson Gomes
7449102d95 fix: Recovering messages lost with redis cache 2024-04-18 17:31:05 -03:00
Davidson Gomes
234a2c71b5 Merge pull request #543 from PauloAK/history-set-message-status
Adicionado message.status ao webhook MESSAGES_SET
2024-04-18 16:05:44 -03:00
Davidson Gomes
4dd5533202 fix: Adjusts in proxy on fetchAgent 2024-04-18 12:54:00 -03:00
Davidson Gomes
a4d1740754 fix: Adjusts in proxy on fetchAgent 2024-04-18 12:53:55 -03:00
Davidson Gomes
1a2ea1c38a Merge tag '1.7.3' into develop
* Revert fix audio encoding
* Recovering messages lost with redis cache
* Adjusts in redis for save instances
* Adjusts in proxy
* Revert pull request #523
* Added instance name on logs
* Added support for Spanish
* Fix error: invalid operator. The allowed operators for identifier are equal_to,not_equal_to in chatwoot
2024-04-18 12:12:40 -03:00
Davidson Gomes
371ec9b8d5 Merge branch 'release/1.7.3' 2024-04-18 12:12:27 -03:00
Davidson Gomes
5e288d57ea changelog 2024-04-18 12:12:11 -03:00
Davidson Gomes
61bd5b3484 fix: Adjusts in redis for save instances 2024-04-18 10:39:24 -03:00
Paulo Kramer
bb974e10f5 Added message status to the messaging-history.set 2024-04-18 01:34:52 -03:00
Davidson Gomes
4ed1335f89 changelog 2024-04-17 19:58:31 -03:00
Davidson Gomes
4274c7afdb Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2024-04-17 18:48:11 -03:00
Davidson Gomes
27f67142c8 changelog 2024-04-17 18:46:50 -03:00
Davidson Gomes
ac8aba6ba4 Merge pull request #542 from nestordavalos/develop
 feat: add support for Spanish
2024-04-17 18:45:38 -03:00
Davidson Gomes
07ff61c070 changelog 2024-04-17 18:25:35 -03:00
nestordavalos
3645ac6704 Merge branch 'develop' of https://github.com/EvolutionAPI/evolution-api into develop 2024-04-17 17:20:42 -04:00
nestordavalos
9764719245 feat: add support for Spanish 2024-04-17 17:20:37 -04:00
Davidson Gomes
b3aeed7fc1 fix: retry messages 2024-04-17 18:14:11 -03:00
Davidson Gomes
5ae5d8546e fix: retry messages 2024-04-17 18:10:43 -03:00
Davidson Gomes
d190d8b1af Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2024-04-17 12:43:55 -03:00
Davidson Gomes
c45538684d fix: fix audio encoding 2024-04-17 12:43:49 -03:00
Davidson Gomes
575e7bf4c4 Merge pull request #539 from jaison-x/pr2
fix: this.localProxy.proxy can be undefined
2024-04-17 12:08:54 -03:00
jaison-x
a4338d8472 fix: this.localProxy.proxy can be undefined 2024-04-17 11:03:55 -03:00
Davidson Gomes
7d542cf115 Merge pull request #538 from jaison-x/pr
fix(chatwoot): error: invalid operator. The allowed operators for identifier are [equal_to,not_equal_to]
2024-04-17 11:03:35 -03:00
jaison-x
9c9a542bbe fix(chatwoot): error: invalid operator. The allowed operators for identifier are [equal_to,not_equal_to]
In chatwoot version 3.8 we can't use the filter operator 'contains' for the field 'identifier'
2024-04-17 10:50:25 -03:00
Davidson Gomes
b6c56551bc fix: Revert pull request #523 2024-04-16 19:13:25 -03:00
Davidson Gomes
1379228196 fix: Revert pull request #523 2024-04-16 19:12:03 -03:00
Davidson Gomes
d6e19b9273 adjusts in v1.7.3 2024-04-16 19:08:44 -03:00
Davidson Gomes
5e0e4051dc Merge tag '1.7.2' into develop
* Mobile connection via sms (test)

* Adjusts in redis
* Send global event in websocket
* Adjusts in proxy
* Fix audio encoding
* Fix conversation read on chatwoot version 3.7
* Fix when receiving/sending messages from whatsapp desktop with ephemeral messages enabled
* Changed returned sessions on typebot status change
* Reorganization of files and folders
2024-04-12 17:32:30 -03:00
Davidson Gomes
8caf3a0a7b Merge branch 'release/1.7.2' 2024-04-12 17:32:18 -03:00
Davidson Gomes
96ae97664c v 1.7.2 2024-04-12 17:31:58 -03:00
Davidson Gomes
901b121695 fix: reorganization of files and folders 2024-04-12 17:28:02 -03:00
Davidson Gomes
3e88c9f0c7 fix: reorganization of files and folders 2024-04-12 17:23:28 -03:00
Davidson Gomes
94f5c130bf fix: reorganization of files and folders 2024-04-12 17:22:11 -03:00
Davidson Gomes
eb04c3f113 fix: reorganization of files and folders 2024-04-12 17:15:13 -03:00
Davidson Gomes
8ece6fb998 fix: reorganization of files and folders 2024-04-12 17:13:15 -03:00
Davidson Gomes
794213b5c6 fix: quoted messages 2024-04-12 15:53:06 -03:00
Davidson Gomes
ef4a9ab66b fix: retry messages 2024-04-12 15:11:04 -03:00
Davidson Gomes
86e978faad fix: send contact.upsert via webhook 2024-04-12 12:51:13 -03:00
Davidson Gomes
d5d8f2a318 fix: send contact.upsert via webhook 2024-04-12 12:33:31 -03:00
Davidson Gomes
4ca1fad335 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2024-04-12 12:00:24 -03:00
Davidson Gomes
0edf6bdcb1 fix: audio encoding 2024-04-12 12:00:14 -03:00
Davidson Gomes
c2839ddd56 Merge pull request #528 from jaison-x/pr
fix(chatwoot): fix conversation read on chatwoot version 3.7
2024-04-12 11:48:37 -03:00
jaison-x
28581f88b2 fix(chatwoot): fix conversation read on chatwoot version 3.7
if contact has more than one conversation, we could get the wrong conversation.
2024-04-12 11:30:19 -03:00
Davidson Gomes
833c625d18 Merge pull request #527 from jaison-x/pr
fix(chatwoot): fix when receiving/sending messages from whatsapp desktop with ephemeral messages enabled
2024-04-12 11:22:13 -03:00
Davidson Gomes
6e2a3a410a fix: audio encoding 2024-04-12 11:20:18 -03:00
jaison-x
3e85af9589 fix(chatwoot): fix when receiving/sending messages from whatsapp desktop with ephemeral messages enabled 2024-04-12 10:59:28 -03:00
Davidson Gomes
589dec52a3 ajustes proxy 2024-04-09 16:52:22 -03:00
Davidson Gomes
a5477509bd adjusts in redis 2024-04-09 07:43:20 -03:00
Davidson Gomes
5d951a96b5 adjusts in redis 2024-04-09 07:40:45 -03:00
Davidson Gomes
76552f6ae5 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2024-04-09 07:31:46 -03:00
Davidson Gomes
e153515847 adjusts in redis 2024-04-09 07:31:37 -03:00
Davidson Gomes
3bc692d894 adjusts in redis 2024-04-09 07:30:15 -03:00
Davidson Gomes
73360d66cd Merge pull request #523 from gabrielgranado/main
changed returned sessions on typebot status change
2024-04-09 06:46:24 -03:00
Davidson Gomes
04575d8051 adjusts in websocket 2024-04-09 06:44:59 -03:00
Joao Gabriel
9aba51cb45 changed returned sessions on typebot status change 2024-04-08 22:55:12 -03:00
Davidson Gomes
1172b6c871 mobile connection 2024-04-08 19:09:59 -03:00
Davidson Gomes
240f01f378 Merge tag '1.7.1' into develop
v
2024-04-03 10:45:55 -03:00
Davidson Gomes
5975117636 Merge branch 'release/1.7.1' 2024-04-03 10:45:51 -03:00
Davidson Gomes
51b285f665 v 1.7.1 2024-04-03 10:45:29 -03:00
Davidson Gomes
b3958d4735 Merge tag '1.7.1' into develop
v
2024-04-03 10:20:14 -03:00
Davidson Gomes
128119d494 Merge branch 'release/1.7.1' 2024-04-03 10:20:09 -03:00
Davidson Gomes
324dc01699 v 1.7.1 2024-04-03 10:19:59 -03:00
Davidson Gomes
f11d468e31 ajustes wa business 2024-04-03 08:14:56 -03:00
Davidson Gomes
aa5ed13752 ajustes wa business 2024-04-03 07:36:51 -03:00
Davidson Gomes
10ce7da18d changelog 2024-04-03 07:15:23 -03:00
Davidson Gomes
73bd55dfac changelog 2024-04-03 07:06:41 -03:00
Davidson Gomes
db10f81d53 Merge pull request #507 from Azzybot/patch-7
Reconhecer tipos de mensagens
2024-04-03 07:06:23 -03:00
Davidson Gomes
717aac4438 Merge pull request #506 from Azzybot/patch-6
Recurso para coletar tipo de mensagem
2024-04-03 07:06:01 -03:00
Davidson Gomes
077a464481 Merge pull request #505 from Azzybot/patch-5
Removido obrigatoriedade de descrição dos rows do sendList
2024-04-03 07:05:09 -03:00
Davidson Gomes
9bf18592fc ajustes wa oficial 2024-04-03 07:01:35 -03:00
Luis-Fernando
677e196c96 Ajuste Metodo getMessage 2024-04-02 20:43:58 -03:00
Luis-Fernando
19e6896f0d Reconhecer tipos de mensagens
Adicionado metodos para capturar diferentes tipos de mensagens alem do tipo texto.

Incluido:
AUDIO, VIDEO, IMAGEM, DOCUMENTOS E VISUALIZAÇÃO ÚNICA, Etc...
2024-04-02 18:59:08 -03:00
Luis-Fernando
ba537baab2 Recurso para coletar tipo de mensagem
Variável {{messageType}} adicionada para ser usada dentro do fluxo e a partir disso usar condições para fluxo seguir de acordo com o tipo de mensagem recebida:

Exemplo:
if {{messageTyp}} = audioMessage
if {{messageTyp}} = videoMessage
if {{messageTyp}} = imageMessage
if {{messageTyp}} = document
if {{messageTyp}} = conversation

Entre outras...

Exemplo:

![VideoCapture_20240402-144703](https://github.com/EvolutionAPI/evolution-api/assets/136400011/60b74721-9f14-4d60-99f2-369c42cd60de)
![VideoCapture_20240402-144739](https://github.com/EvolutionAPI/evolution-api/assets/136400011/f4febd4b-c42e-4678-bfd4-f1aa076c92f5)
![VideoCapture_20240402-144724](https://github.com/EvolutionAPI/evolution-api/assets/136400011/7eb0ba56-a05a-4015-8863-76e29e0613f6)
2024-04-02 18:37:00 -03:00
Luis-Fernando
2b8b6b086b Removido obrigatoriedade de descrição dos rows do sendList
Removida a obrigatoriedade da descrição do rows para ser algo opcional.

Isso permite os "circulos" das opções ficarem mais alinhados com os titulos sem a necessidade de adicionar espaço em branco caso não queira adicionar a descrição.

{
"number": "559999999999 (Recipient number with Country Code)",
"options": {
"delay": 1200,
"presence": "composing"
},
"listMessage": {
"title": "List Title",
"description": "List description",
"buttonText": "Click Here",
"footerText": "footer list\nhttps://examplelink.com.br",
"sections": [
{
"title": "Row tilte 01",
"rows": [
{
"title": "Title row 01",
"description": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s,",
"rowId": "rowId 001"
},
{
"title": "Title row 02",
"description": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s,",
"rowId": "rowId 002"
}
]
}
}

Depois:

{
"number": "559999999999 (Recipient number with Country Code)",
"options": {
"delay": 1200,
"presence": "composing"
},
"listMessage": {
"title": "List Title",
"description": "List description",
"buttonText": "Click Here",
"footerText": "footer list\nhttps://examplelink.com.br",
"sections": [
{
"title": "Row tilte 01",
"rows": [
{
"title": "Title row 01",
// "description": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s,", //podendo remover por completo como foi feito no Title row 02 abaixo ou apenas comentar a descrição
"rowId": "rowId 001"
},
{
"title": "Title row 02",
"rowId": "rowId 002"
}
]
}
}
2024-04-02 17:59:54 -03:00
Davidson Gomes
cf3ec2b601 ajustes 2024-04-02 16:05:14 -03:00
Davidson Gomes
35eaa7852c Merge tag '1.7.0' into develop
v
2024-03-27 16:36:29 -03:00
Davidson Gomes
b87558d301 Merge branch 'release/1.7.0' 2024-03-27 16:36:26 -03:00
Davidson Gomes
9f1003e94e rabbitmq 2024-03-27 15:05:00 -03:00
Davidson Gomes
73c003907b rabbitmq 2024-03-27 14:54:51 -03:00
Davidson Gomes
b65d292af4 Merge tag '1.7.0' into develop
v
2024-03-27 14:26:06 -03:00
Davidson Gomes
43bdbf1018 Merge branch 'release/1.7.0' 2024-03-27 14:26:03 -03:00
Davidson Gomes
ec7986420d rabbitmq 2024-03-27 13:21:39 -03:00
Davidson Gomes
0b23153316 Merge tag '1.7.0' into develop
v
2024-03-27 12:30:15 -03:00
Davidson Gomes
ad4f5e5e65 Merge branch 'release/1.7.0' 2024-03-27 12:30:11 -03:00
Davidson Gomes
1d48532146 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2024-03-27 09:54:22 -03:00
Davidson Gomes
950803b2aa rabbitmq 2024-03-27 09:54:05 -03:00
Davidson Gomes
a41d92f4da Merge pull request #479 from dv336699/develop
feat(endpoint): add setPresence endpoint
2024-03-24 06:44:15 -03:00
Davidson Gomes
70d4eb393f Merge tag '1.7.0' into develop
* Added update message endpoint
* Add translate capabilities to QRMessages in CW
* Join in Group by Invite Code
* Read messages from whatsapp in chatwoot
* Add support to use use redis in cacheservice
* Add support for labels
* Command to clearcache from chatwoot inbox
* Whatsapp Cloud API Oficial

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

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

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

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

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

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

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

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

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

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

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

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

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

* Fix looping connection messages in chatwoot
* Improved performance of fetch instances
2023-09-17 13:55:02 -03:00
Davidson Gomes
bd64b0c884 Merge branch 'release/1.5.1' 2023-09-17 13:54:49 -03:00
Davidson Gomes
e1c8928ed9 Added support for messaging with ads on chatwoot 2023-09-17 13:52:54 -03:00
Davidson Gomes
1f98940445 Added support for messaging with ads on chatwoot 2023-09-17 13:48:42 -03:00
Davidson Gomes
707aa22a6d Added support for messaging with ads on chatwoot 2023-09-17 13:48:27 -03:00
Davidson Gomes
32da15fa8a Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2023-09-17 13:35:58 -03:00
Davidson Gomes
97cd6e289a Merge pull request #111 from AdsonCicilioti/ads-preview-1.5.1
Initial support for preview and reference from Ads messages on Chatwoot
2023-09-17 13:35:28 -03:00
Davidson Gomes
def6576fb9 Merge branch 'develop' into ads-preview-1.5.1 2023-09-17 13:35:18 -03:00
Davidson Gomes
196c10b253 Merge pull request #108 from raimartinsb/EvolutionAPI-develop
[Improvement] - Send and Update source_id of messages to chatwoot
2023-09-17 13:32:27 -03:00
AdsonCicilioti
cfdca38d59 add: fixed crop (cover) ads imge thumbnail with Jimp library 2023-09-13 00:04:02 -03:00
AdsonCicilioti
ccd90a69ee add: initial support for preview and reference from messages with Ads metadata 2023-09-12 13:43:17 -03:00
AdsonCicilioti
c16f962d2b chore: compreensive var name for conversation term 2023-09-12 13:39:40 -03:00
Davidson Gomes
8fccf69ceb Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2023-09-11 09:39:13 -03:00
Davidson Gomes
adc8833670 fix: Debug Bad Request 2023-09-11 09:39:09 -03:00
Davidson Gomes
ecbf90ded8 Merge pull request #99 from moskoweb/bug-bad-request
fix: Debug Bad Request
2023-09-11 09:38:28 -03:00
raimartinsb
c5d2d7782a Merge branch 'develop' of https://github.com/EvolutionAPI/evolution-api into EvolutionAPI-develop 2023-09-11 08:08:18 -03:00
raimartinsb
72dae22ef4 Configuration to update source_id only when desired 2023-09-11 07:44:09 -03:00
Alan Mosko
9a9cd41009 wip 2023-09-06 16:24:13 -03:00
Alan Mosko
0c4c8162c2 wip 2023-09-06 16:18:48 -03:00
Alan Mosko
d47cc5d5f4 wip 2023-09-06 16:11:59 -03:00
raimartinsb
cb942e512d improved message update 2023-09-06 13:28:42 -03:00
raimartinsb
bd0a479645 added source_id in media messages 2023-09-06 09:11:15 -03:00
Davidson Gomes
cd0da914f4 Merge pull request #96 from moskoweb/fetch-profile
Get Info JID Correct
2023-09-05 21:42:21 -03:00
Davidson Gomes
7a7e72897a Merge pull request #95 from w3nder/develop
Melhorando o desempenho no carregamento da instância.
2023-09-05 21:41:29 -03:00
Alan Mosko
c1542054c9 Merge branch 'develop' into fetch-profile 2023-09-05 17:47:41 -03:00
Alan Mosko
250e67e7ae Update whatsapp.service.ts 2023-09-05 17:44:47 -03:00
Wender Teixeira
cdbe839b35 Update monitor.service.ts 2023-09-05 11:49:02 -03:00
Wender Teixeira
240a77dcce Update monitor.service.ts
Fix monitor
2023-09-05 11:48:38 -03:00
Wender Teixeira
1dc5bb8bd1 Update monitor.service.ts 2023-09-05 11:47:24 -03:00
Davidson Gomes
23534da27d fix: added delay in chatwoot receive webhook 2023-09-05 08:39:51 -03:00
Wender Teixeira
8652d4031c Update monitor.service.ts 2023-09-04 19:00:42 -03:00
Davidson Gomes
384e311c7a fix: added delay in chatwoot receive webhook 2023-09-04 16:48:04 -03:00
Davidson Gomes
2791f88b4c fix: added delay in chatwoot receive webhook 2023-09-04 16:13:43 -03:00
Wender Teixeira
402d5af19a Update monitor.service.ts 2023-09-04 15:18:32 -03:00
Wender Teixeira
b554d8c19c Update monitor.service.ts (loadInstances)
Melhorando é organizando o loadInstances
2023-09-04 15:11:23 -03:00
Wender Teixeira
7d9dd64303 Update monitor.service.ts
Isso permite que as operações assíncronas sejam executadas em paralelo
2023-09-04 15:06:18 -03:00
Davidson Gomes
41bea8931f test: process exit when errors 2023-09-04 13:08:16 -03:00
Davidson Gomes
f83d8de476 test: process exit when errors 2023-09-04 13:00:16 -03:00
Davidson Gomes
af2a652098 Merge pull request #90 from moskoweb/patch-6
fix: Group Create
2023-09-01 07:02:10 -03:00
Davidson Gomes
a3c911263d Merge pull request #92 from francisbreit/main
fix: Update manager.json
2023-09-01 06:52:02 -03:00
Davidson Gomes
836c677837 Merge branch 'develop' into main 2023-09-01 06:51:40 -03:00
Davidson Gomes
7e4dbfdd7e fix: Added log when send event to rabbitMQ and Websocket 2023-08-31 18:24:30 -03:00
Davidson Gomes
6eda556242 fix: create rabbitmq queues on set config 2023-08-31 17:10:19 -03:00
Davidson Gomes
3ea454c7ed fix: create rabbitmq queues on set config 2023-08-31 17:01:09 -03:00
Davidson Gomes
9123d7014d fix: create rabbitmq queues on set config 2023-08-31 16:49:50 -03:00
Francis Breit
3ea3abe81a Update manager.json
- Added the following settings for instances: Set Typebot and Typebot Change Session Status.
- Fixed bug in Webhook settings
2023-08-31 07:55:53 -03:00
raimartinsb
7289c3d7f9 Add source_id and update message 2023-08-31 07:52:36 -03:00
Alan Mosko
d00e1df29c [Melhoria] Group Create
Verifica todos os participantes e pega apenas os que existe no WhatsApp
2023-08-30 15:09:03 -03:00
Davidson Gomes
16a18c4f22 fix: update error.config.ts 2023-08-30 13:27:47 -03:00
Davidson Gomes
30b156d92f Merge pull request #89 from w3nder/develop
Update error.config.ts
2023-08-30 13:25:22 -03:00
Davidson Gomes
931f9d33e4 Merge pull request #87 from moskoweb/phone-name
fix: navigator client name
2023-08-30 13:24:40 -03:00
Wender Teixeira
e61fe4d092 Update error.config.ts
encerramento controlado para lidar com erros não capturados no aplicativo.
2023-08-30 12:38:53 -03:00
Davidson Gomes
5bc33ac654 Merge pull request #88 from moskoweb/patch-5
Correção de Erro
2023-08-29 21:00:41 -03:00
Alan Mosko
878ba5a869 Correção de Erro
```This comparison appears to be unintentional because the types 'string' and 'number' have no overlap.```
2023-08-29 18:57:31 -03:00
Alan Mosko
92632b2b96 wip 2023-08-29 14:29:50 -03:00
Davidson Gomes
d803a9e298 Merge pull request #85 from w3nder/develop
Fix set event rabbitmq/websocket
2023-08-29 11:29:14 -03:00
Davidson Gomes
b9f67533dd Merge pull request #83 from moskoweb/patch-4
fix: DEL_INSTANCE
2023-08-29 11:27:57 -03:00
Davidson Gomes
7ade78bedf Merge pull request #78 from AdsonCicilioti/docker-net-declaration
clean: docker network declaration and build of the `evolution/api` image
2023-08-29 11:27:10 -03:00
Davidson Gomes
b4eca48f4d Merge pull request #59 from unilogica/main
Bugfix Internal Error 500: fetchInstances (apikey not found)
2023-08-29 11:24:03 -03:00
Davidson Gomes
2cf8e317fa Merge pull request #57 from helioelias/main
Fix: For webhook to work with localhost
2023-08-29 11:15:08 -03:00
Wender Teixeira
821a422ab5 Fix set event rabbitmq/websocket 2023-08-28 23:26:40 -03:00
Alan Mosko
91c7b4f2cd Correção DEL_INSTANCE 2023-08-28 09:59:04 -03:00
AdsonCicilioti
adb43ec5b3 clean: correct docker network declaration 2023-08-26 02:53:02 -03:00
AdsonCicilioti
c9721d7bc9 clean: docker build will be remove in future releases / build into compose file 2023-08-26 02:36:45 -03:00
AdsonCicilioti
bbc2b8a396 clean: declarative bridge network instead shellscript check 2023-08-26 00:29:58 -03:00
Davidson Gomes
54cfa67d52 Added webhook to send errors 2023-08-25 09:31:13 -03:00
Davidson Gomes
9b72b3e332 Added webhook to send errors 2023-08-25 09:01:48 -03:00
Davidson Gomes
c03919be2d Added ChamaAI integration 2023-08-23 08:58:49 -03:00
Davidson Gomes
706cc6f49c Added webhooks for typebot events 2023-08-23 08:15:53 -03:00
Davidson Gomes
10c7e81e02 Added webhooks for typebot events 2023-08-23 07:54:46 -03:00
Davidson Gomes
03637b2d4d Added listening_from_me option in Set Typebot 2023-08-23 07:27:40 -03:00
Davidson Gomes
b7218a05be feat: Inegration with Chama AI 2023-08-19 15:13:35 -03:00
Davidson Gomes
a2cd57d9c6 feat: Inegration with Chama AI 2023-08-19 10:42:41 -03:00
Unilógica
001849eeaa Merge branch 'EvolutionAPI:main' into main 2023-08-18 18:02:53 -03:00
Davidson Gomes
bf09a70096 Merge tag '1.5.0' into develop
* New instance manager in /manager route
* Added extra files for chatwoot and appsmith
* Added Get Last Message and Archive for Chat
* Added env var QRCODE_COLOR
* Added websocket to send events
* Added rabbitmq to send events
* Added Typebot integration
* Added proxy endpoint
* Added send and date_time in webhook data

* Solved problem when disconnecting from the instance the instance was deleted
* Encoded spaces in chatwoot webhook
* Adjustment in the saving of contacts, saving the information of the number and Jid
* Update Dockerfile
* If you pass empty events in create instance and set webhook it is understood as all
* Fixed issue that did not output base64 averages
* Messages sent by the api now arrive in chatwoot

- Chatwoot: v2.18.0 - v3.0.0
- Typebot: v2.16.0
- Manager Evolution API
2023-08-18 12:49:12 -03:00
Davidson Gomes
530aec92a9 Merge branch 'release/1.5.0' 2023-08-18 12:48:53 -03:00
Davidson Gomes
deb8f2a0b7 Added send and date_time in webhook data 2023-08-18 12:48:30 -03:00
Davidson Gomes
5c247e3d2c Added send and date_time in webhook data 2023-08-18 12:46:47 -03:00
Davidson Gomes
04b9a070c4 Added send and date_time in webhook data 2023-08-18 12:46:46 -03:00
Davidson Gomes
dd2caf720c Added Typebot integration 2023-08-18 12:24:05 -03:00
Davidson Gomes
0d16a7aab0 Added Typebot integration 2023-08-18 11:59:04 -03:00
Davidson Gomes
31325d0999 Added Typebot integration 2023-08-18 11:58:41 -03:00
Davidson Gomes
6f99784224 Added Typebot integration 2023-08-18 11:38:50 -03:00
Davidson Gomes
201e6f7e7b Added rabbitmq to send events 2023-08-18 10:38:32 -03:00
Davidson Gomes
c4d41134b8 Messages sent by the api now arrive in chatwoot 2023-08-18 09:33:17 -03:00
Davidson Gomes
680c92ecec Messages sent by the api now arrive in chatwoot 2023-08-18 09:25:34 -03:00
Davidson Gomes
deb07d2b7f Messages sent by the api now arrive in chatwoot 2023-08-18 08:44:13 -03:00
Davidson Gomes
3a14fc373a adjust params rabbitmq 2023-08-17 18:19:46 -03:00
Allyson de Paula
29e429a02e Merge branch 'main' of https://github.com/unilogica/evolution-api 2023-08-17 13:07:05 -03:00
Allyson de Paula
907a0ee135 Proxy test 2023-08-12 15:05:10 -03:00
Unilógica
22a03f77ab Merge pull request #1 from unilogica/develop
Develop
2023-08-12 15:01:54 -03:00
Unilógica
9761b10bf6 Merge branch 'EvolutionAPI:develop' into develop 2023-08-12 12:41:15 -03:00
Davidson Gomes
c364d3fdca feat: Added Typebot integration 2023-08-11 20:49:39 -03:00
Davidson Gomes
07ad5756eb fix: Fixed issue that did not output base64 averages 2023-08-09 16:57:23 -03:00
Allyson de Paula
6e401eecde Bugfix Internal Error 500: fetchInstances (apikey not found) 2023-08-09 16:56:38 -03:00
Helio Elias
f32e259d2f Fix isURL function to by pass url with localhost, when webhook is enable 2023-08-08 21:06:58 -03:00
Helio Elias
83ed0e6454 Merge branch 'EvolutionAPI:main' into main 2023-08-08 21:02:19 -03:00
Davidson Gomes
da568e4ea5 feat: Added version in logs 2023-08-07 19:46:34 -03:00
Davidson Gomes
ad819bf3ba feat: Added Typebot integration 2023-08-07 19:03:15 -03:00
Davidson Gomes
b502ebd23a feat: Added Typebot integration 2023-08-07 18:54:12 -03:00
Davidson Gomes
7c5d94c19e feat: Added Typebot integration 2023-08-07 15:43:57 -03:00
Davidson Gomes
a16b5f4644 feat: added proxy endpoint 2023-08-07 12:10:53 -03:00
Davidson Gomes
f3cb8c531b feat: added proxy endpoint 2023-08-07 12:10:33 -03:00
Davidson Gomes
469e696ab7 Added Typebot integration 2023-08-04 16:15:59 -03:00
Davidson Gomes
d99ccd9df6 Added Typebot integration 2023-08-04 15:03:08 -03:00
Davidson Gomes
3b3118d764 Added Typebot integration 2023-08-04 15:00:55 -03:00
Davidson Gomes
84386847e2 readme buy me coffe 2023-08-03 07:32:29 -03:00
Davidson Gomes
0da3d100b3 feat: Added rabbitmq to send events 2023-08-02 21:33:59 -03:00
Davidson Gomes
56d621bab8 feat: Added rabbitmq to send events 2023-08-02 21:14:21 -03:00
Davidson Gomes
f1571b5f66 feat: Added rabbitmq to send events 2023-08-02 17:41:15 -03:00
Davidson Gomes
55f8e179af feat: Added rabbitmq to send events 2023-08-02 17:39:07 -03:00
Davidson Gomes
ab5289a136 feat: Added rabbitmq to send events 2023-08-02 17:29:09 -03:00
Davidson Gomes
e05c48979d feat: Added rabbitmq to send events 2023-08-02 17:28:52 -03:00
Davidson Gomes
24c880343b feat: Added websocket with lib socket.io 2023-08-02 16:11:19 -03:00
Davidson Gomes
b3b4ee7a28 fix: If you pass empty events in create instance and set webhook it is understood as all 2023-08-02 14:48:02 -03:00
Helio Elias
e26b440a66 Merge pull request #1 from helioelias/fix/dockers
Fix/dockers
2023-08-02 13:29:13 -03:00
Helio Elias
d6194316e1 Fix Dockers 2023-08-02 13:25:00 -03:00
Davidson Gomes
341148612f feat: Added websocket with lib socket.io 2023-08-02 13:21:15 -03:00
Davidson Gomes
79864e97d6 feat: Added websocket with lib socket.io 2023-08-02 13:08:30 -03:00
Davidson Gomes
ed5e66e430 fix: Update view manager 2023-08-01 18:01:29 -03:00
Davidson Gomes
074a861fb4 fix: Update Dockerfile 2023-07-31 16:02:40 -03:00
Davidson Gomes
b88656829e feat: Added env var QRCODE_COLOR 2023-07-31 15:51:05 -03:00
Davidson Gomes
aefe6a5943 feat: Added Get Last Message and Archive for Chat 2023-07-31 15:30:48 -03:00
Davidson Gomes
3d743f8498 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2023-07-31 15:30:33 -03:00
Davidson Gomes
0186fff67d feat: Added Get Last Message and Archive for Chat
Added Get Last Message and Archive for Chat
2023-07-31 15:30:26 -03:00
Davidson Gomes
38f61cdf75 fix: Update Dockerfile 2023-07-31 15:28:00 -03:00
Davidson Gomes
84366002db fix: Update Dockerfile 2023-07-31 15:27:47 -03:00
Davidson Gomes
eff5bb74b5 Merge pull request #50 from EvolutionAPI/revert-43-docker
Revert "Update Dockerfile"
2023-07-31 15:26:00 -03:00
Davidson Gomes
2614088fae Revert "Update Dockerfile" 2023-07-31 15:25:48 -03:00
Davidson Gomes
3699e04db9 Merge pull request #43 from moskoweb/docker
Update Dockerfile
2023-07-31 15:25:09 -03:00
Davidson Gomes
0dca009c01 fix: Adjustment in the saving of contacts, saving the information of the number and Jid 2023-07-31 13:42:37 -03:00
Davidson Gomes
1b39eb1a23 add: Added extra files for chatwoot and appsmith 2023-07-31 13:13:41 -03:00
Davidson Gomes
2637aebb7f fix: Encoded spaces in chatwoot webhook 2023-07-31 12:34:01 -03:00
Davidson Gomes
8afcfde078 add: Added extra files for chatwoot and appsmith 2023-07-31 12:26:50 -03:00
Davidson Gomes
9ea1eaf3ed fix: Encoded spaces in chatwoot webhook 2023-07-31 10:55:24 -03:00
Davidson Gomes
66d06afaf7 feat: New instance manager in /manager route 2023-07-30 11:03:12 -03:00
Davidson Gomes
cffcca9722 fix: adjust in instance name in chatwoot 2023-07-29 11:30:37 -03:00
Davidson Gomes
fe2b9774d8 fix: Solved problem when disconnecting from the instance the instance was deleted 2023-07-29 11:09:56 -03:00
Alan Mosko
3d02fabef4 Update Dockerfile 2023-07-27 10:47:26 -03:00
Davidson Gomes
f89c2b1f63 Merge tag '1.4.8' into develop
* Fixed error return bug
2023-07-27 10:28:47 -03:00
Alan Mosko
1ce30f8431 Update whatsapp.service.ts 2023-07-27 10:20:18 -03:00
Alan Mosko
3ae6944307 Update whatsapp.service.ts 2023-07-27 09:24:38 -03:00
Alan Mosko
52533d4b38 Adição de Get Last Message e Archive por Chat 2023-07-27 09:23:44 -03:00
Helio Elias
04a6f7c954 fix docker and create docker-compose with all services 2023-07-21 15:02:09 +00:00
181 changed files with 18511 additions and 3604 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

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

4
.gitignore vendored
View File

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

11
.vscode/settings.json vendored
View File

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

View File

@@ -1,3 +1,231 @@
# 1.7.4 (2024-04-28 09:46)
### Fixed
* Adjusts in proxy on fetchAgent
* Recovering messages lost with redis cache
* Log when init redis cache service
* Recovering messages lost with redis cache
* Chatwoot inbox name
* Update Baileys version
# 1.7.3 (2024-04-18 12:07)
### Fixed
* Revert fix audio encoding
* Recovering messages lost with redis cache
* Adjusts in redis for save instances
* Adjusts in proxy
* Revert pull request #523
* Added instance name on logs
* Added support for Spanish
* Fix error: invalid operator. The allowed operators for identifier are equal_to,not_equal_to in chatwoot
# 1.7.2 (2024-04-12 17:31)
### Feature
* Mobile connection via sms (test)
### Fixed
* Adjusts in redis
* Send global event in websocket
* Adjusts in proxy
* Fix audio encoding
* Fix conversation read on chatwoot version 3.7
* Fix when receiving/sending messages from whatsapp desktop with ephemeral messages enabled
* Changed returned sessions on typebot status change
* Reorganization of files and folders
# 1.7.1 (2024-04-03 10:19)
### Fixed
* Correction when sending files with captions on Whatsapp Business
* Correction in receiving messages with response on WhatsApp Business
* Correction when sending a reaction to a message on WhatsApp Business
* Correction of receiving reactions on WhatsApp business
* Removed mandatory description of rows from sendList
* Feature to collect message type in typebot
# 1.7.0 (2024-03-11 18:23)
### Feature
* Added update message endpoint
* Add translate capabilities to QRMessages in CW
* Join in Group by Invite Code
* Read messages from whatsapp in chatwoot
* Add support to use use redis in cacheservice
* Add support for labels
* Command to clearcache from chatwoot inbox
* Whatsapp Cloud API Oficial
### Fixed
* Proxy configuration improvements
* Correction in sending lists
* Adjust in webhook_base64
* Correction in typebot text formatting
* Correction in chatwoot text formatting and render list message
* Only use a axios request to get file mimetype if necessary
* When possible use the original file extension
* When receiving a file from whatsapp, use the original filename in chatwoot if possible
* Remove message ids cache in chatwoot to use chatwoot's api itself
* Adjusts the quoted message, now has contextInfo in the message Raw
* Collecting responses with text or numbers in Typebot
* Added sendList endpoint to swagger documentation
* Implemented a function to synchronize message deletions on WhatsApp, automatically reflecting in Chatwoot.
* Improvement on numbers validation
* Fix polls in message sending
* Sending status message
* Message 'connection successfully' spamming
* Invalidate the conversation cache if reopen_conversation is false and the conversation was resolved
* Fix looping when deleting a message in chatwoot
* When receiving a file from whatsapp, use the original filename in chatwoot if possible
* Correction in the sendList Function
* Implement contact upsert in messaging-history.set
* Improve proxy error handling
* Refactor fetching participants for group in WhatsApp service
* Fixed problem where the typebot final keyword did not work
* Typebot's wait now pauses the flow and composing is defined by the delay_message parameter in set typebot
* Composing over 20s now loops until finished
# 1.6.1 (2023-12-22 11:43)
### Fixed
* Fixed Lid Messages
* Fixed sending variables to typebot
* Fixed sending variables from typebot
* Correction sending s3/minio media to chatwoot and typebot
* Fixed the problem with typebot closing at the end of the flow, now this is optional with the TYPEBOT_KEEP_OPEN variable
* Fixed chatwoot Bold, Italic and Underline formatting using Regex
* Added the sign_delimiter property to the Chatwoot configuration, allowing you to set a different delimiter for the signature. Default when not defined \n
* Include instance Id field in the instance configuration
* Fixed the pairing code
* Adjusts in typebot
* Fix the problem when disconnecting the instance and connecting again using mongodb
* Options to disable docs and manager
* When deleting a message in whatsapp, delete the message in chatwoot too
# 1.6.0 (2023-12-12 17:24)
### Feature
* Added AWS SQS Integration
* Added support for new typebot API
* Added endpoint sendPresence
* New Instance Manager
* Added auto_create to the chatwoot set to create the inbox automatically or not
* Added reply, delete and message reaction in chatwoot v3.3.1
### Fixed
* Adjusts in proxy
* Adjusts in start session for Typebot
* Added mimetype field when sending media
* Ajusts in validations to messages.upsert
* Fixed messages not received: error handling when updating contact in chatwoot
* Fix workaround to manage param data as an array in mongodb
* Removed await from webhook when sending a message
* Update typebot.service.ts - element.underline change ~ for *
* Removed api restart on receiving an error
* Fixes in mongodb and chatwoot
* Adjusted return from queries in mongodb
* Added restart instance when update profile picture
* Correction of chatwoot functioning with admin flows
* Fixed problem that did not generate qrcode with the chatwoot_conversation_pending option enabled
* Fixed issue where CSAT opened a new ticket when reopen_conversation was disabled
* Fixed issue sending contact to Chatwoot via iOS
### Integrations
* Chatwoot: v3.3.1
* Typebot: v2.20.0
# 1.5.4 (2023-10-09 20:43)
### Fixed
* Baileys logger typing issue resolved
* Solved problem with duplicate messages in chatwoot
# 1.5.3 (2023-10-06 18:55)
### Feature
* Swagger documentation
* Added base 64 sending option via webhook
### Fixed
* Remove rabbitmq queues when delete instances
* Improvement in restart instance to completely redo the connection
* Update node version: v20
* Correction of messages sent by the api and typebot not appearing in chatwoot
* Adjustment to start typebot, added startSession parameter
* Chatwoot now receives messages sent via api and typebot
* Fixed problem with starting with an input in typebot
* Added check to ensure variables are not empty before executing foreach in start typebot
# 1.5.2 (2023-09-28 17:56)
### Fixed
* Fix chatwootSchema in chatwoot model to store reopen_conversation and conversation_pending options
* Problem resolved when sending files from minio to typebot
* Improvement in the "startTypebot" method to create persistent session when triggered
* New manager for Evo 1.5.2 - Set Typebot update
* Resolved problems when reading/querying instances
# 1.5.1 (2023-09-17 13:50)
### Feature
* Added listening_from_me option in Set Typebot
* Added variables options in Start Typebot
* Added webhooks for typebot events
* Added ChamaAI integration
* Added webhook to send errors
* Added support for messaging with ads on chatwoot
### Fixed
* Fix looping connection messages in chatwoot
* Improved performance of fetch instances
# 1.5.0 (2023-08-18 12:47)
### Feature
* New instance manager in /manager route
* Added extra files for chatwoot and appsmith
* Added Get Last Message and Archive for Chat
* Added env var QRCODE_COLOR
* Added websocket to send events
* Added rabbitmq to send events
* Added Typebot integration
* Added proxy endpoint
* Added send and date_time in webhook data
### Fixed
* Solved problem when disconnecting from the instance the instance was deleted
* Encoded spaces in chatwoot webhook
* Adjustment in the saving of contacts, saving the information of the number and Jid
* Update Dockerfile
* If you pass empty events in create instance and set webhook it is understood as all
* Fixed issue that did not output base64 averages
* Messages sent by the api now arrive in chatwoot
### Integrations
* Chatwoot: v2.18.0 - v3.0.0
* Typebot: v2.16.0
* Manager Evolution API
# 1.4.8 (2023-07-27 10:27)
### Fixed
@@ -84,7 +312,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)
@@ -100,7 +328,7 @@
### Integrations
- Chatwoot: v2.18.0
* Chatwoot: v2.18.0
# 1.3.1 (2023-07-20 07:48)
@@ -110,7 +338,7 @@
### Integrations
- Chatwoot: v2.18.0
* Chatwoot: v2.18.0
# 1.3.0 (2023-07-19 11:33)
@@ -147,7 +375,7 @@
### Integrations
- Chatwoot: v2.18.0
* Chatwoot: v2.18.0
# 1.2.2 (2023-07-15 09:36)
@@ -158,7 +386,7 @@
### Integrations
- Chatwoot: v2.18.0
* Chatwoot: v2.18.0
# 1.2.1 (2023-07-14 19:04)

View File

@@ -16,6 +16,7 @@ LOG_BAILEYS=error
# Default time: 5 minutes
# If you don't even want an expiration, enter the value false
DEL_INSTANCE=false
DEL_TEMP_INSTANCES=true # Delete instances with status closed on start
# Temporary data storage
STORE_MESSAGES=true
@@ -31,8 +32,11 @@ CLEAN_STORE_CONTACTS=true
CLEAN_STORE_CHATS=true
# Permanent data storage
DATABASE_ENABLED=true
DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true
DATABASE_ENABLED=false
DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin &
readPreference=primary &
ssl=false &
directConnection=true
DATABASE_CONNECTION_DB_PREFIX_NAME=evdocker
# Choose the data you want to save in the application's database or store
@@ -42,9 +46,24 @@ DATABASE_SAVE_MESSAGE_UPDATE=false
DATABASE_SAVE_DATA_CONTACTS=false
DATABASE_SAVE_DATA_CHATS=false
REDIS_ENABLED=true
REDIS_URI=redis://redis:6379
REDIS_PREFIX_KEY=evdocker
RABBITMQ_ENABLED=false
RABBITMQ_RABBITMQ_MODE=global
RABBITMQ_EXCHANGE_NAME=evolution_exchange
RABBITMQ_URI=amqp://guest:guest@rabbitmq:5672
WEBSOCKET_ENABLED=false
WEBSOCKET_GLOBAL_EVENTS=false
WA_BUSINESS_TOKEN_WEBHOOK=evolution
WA_BUSINESS_URL=https://graph.facebook.com
WA_BUSINESS_VERSION=v18.0
WA_BUSINESS_LANGUAGE=pt_BR
SQS_ENABLED=false
SQS_ACCESS_KEY_ID=
SQS_SECRET_ACCESS_KEY=
SQS_ACCOUNT_ID=
SQS_REGION=
# Global Webhook Settings
# Each instance's Webhook URL and events will be requested at the time it is created
@@ -73,17 +92,49 @@ WEBHOOK_EVENTS_GROUPS_UPSERT=true
WEBHOOK_EVENTS_GROUPS_UPDATE=true
WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
WEBHOOK_EVENTS_CONNECTION_UPDATE=true
WEBHOOK_EVENTS_LABELS_EDIT=true
WEBHOOK_EVENTS_LABELS_ASSOCIATION=true
WEBHOOK_EVENTS_CALL=true
# This event fires every time a new token is requested via the refresh route
WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
# This events is used with Typebot
WEBHOOK_EVENTS_TYPEBOT_START=false
WEBHOOK_EVENTS_TYPEBOT_CHANGE_STATUS=false
# This event is used with Chama AI
WEBHOOK_EVENTS_CHAMA_AI_ACTION=false
# This event is used to send errors
WEBHOOK_EVENTS_ERRORS=false
WEBHOOK_EVENTS_ERRORS_WEBHOOK=
# Name that will be displayed on smartphone connection
CONFIG_SESSION_PHONE_CLIENT=EvolutionAPI
# Browser Name = chrome | firefox | edge | opera | safari
CONFIG_SESSION_PHONE_NAME=chrome
# Browser Name = Chrome | Firefox | Edge | Opera | Safari
CONFIG_SESSION_PHONE_NAME=Chrome
# Set qrcode display limit
QRCODE_LIMIT=30
QRCODE_COLOR=#198754
# old | latest
TYPEBOT_API_VERSION=latest
TYPEBOT_KEEP_OPEN=false
#Chatwoot
# If you leave this option as false, when deleting the message for everyone on WhatsApp, it will not be deleted on Chatwoot.
CHATWOOT_MESSAGE_DELETE=false # false | true
# If you leave this option as true, when sending a message in Chatwoot, the client's last message will be marked as read on WhatsApp.
CHATWOOT_MESSAGE_READ=false # false | true
# This db connection is used to import messages from whatsapp to chatwoot database
CHATWOOT_IMPORT_DATABASE_CONNECTION_URI=postgres://user:password@hostname:port/dbname
CHATWOOT_IMPORT_DATABASE_PLACEHOLDER_MEDIA_MESSAGE=true
CACHE_REDIS_ENABLED=false
CACHE_REDIS_URI=redis://redis:6379
CACHE_REDIS_PREFIX_KEY=evolution
CACHE_REDIS_TTL=604800
CACHE_REDIS_SAVE_INSTANCES=false
CACHE_LOCAL_ENABLED=false
CACHE_LOCAL_TTL=604800
# Defines an authentication type for the api
# We recommend using the apikey because it will allow you to use a custom token,
@@ -98,3 +149,5 @@ AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES=true
# seconds - 3600s ===1h | zero (0) - never expires
AUTHENTICATION_JWT_EXPIRIN_IN=0
AUTHENTICATION_JWT_SECRET='L=0YWt]b2w[WF>#>:&E`'
LANGUAGE=en # pt-BR, en

View File

@@ -0,0 +1,22 @@
version: '3.3'
services:
api:
container_name: evolution_api
image: atendai/evolution-api
restart: always
ports:
- 8080:8080
volumes:
- evolution_instances:/evolution/instances
- evolution_store:/evolution/store
env_file:
- .env
command: ['node', './dist/src/main.js']
expose:
- 8080
volumes:
evolution_instances:
evolution_store:

View File

@@ -0,0 +1,119 @@
# Server URL - Set your application url
SERVER_URL='http://localhost:8080'
# Cors - * for all or set separate by commas - ex.: 'yourdomain1.com, yourdomain2.com'
CORS_ORIGIN='*'
CORS_METHODS='POST,GET,PUT,DELETE'
CORS_CREDENTIALS=true
# Determine the logs to be displayed
LOG_LEVEL='ERROR,WARN,DEBUG,INFO,LOG,VERBOSE,DARK,WEBHOOKS'
LOG_COLOR=true
# Log Baileys - "fatal" | "error" | "warn" | "info" | "debug" | "trace"
LOG_BAILEYS=error
# Determine how long the instance should be deleted from memory in case of no connection.
# Default time: 5 minutes
# If you don't even want an expiration, enter the value false
DEL_INSTANCE=false
DEL_TEMP_INSTANCES=true # Delete instances with status closed on start
# Temporary data storage
STORE_MESSAGES=true
STORE_MESSAGE_UP=true
STORE_CONTACTS=true
STORE_CHATS=true
# Set Store Interval in Seconds (7200 = 2h)
CLEAN_STORE_CLEANING_INTERVAL=7200
CLEAN_STORE_MESSAGES=true
CLEAN_STORE_MESSAGE_UP=true
CLEAN_STORE_CONTACTS=true
CLEAN_STORE_CHATS=true
# Permanent data storage
DATABASE_ENABLED=true
DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin &
readPreference=primary &
ssl=false &
directConnection=true
DATABASE_CONNECTION_DB_PREFIX_NAME=evolution
# Choose the data you want to save in the application's database or store
DATABASE_SAVE_DATA_INSTANCE=false
DATABASE_SAVE_DATA_NEW_MESSAGE=false
DATABASE_SAVE_MESSAGE_UPDATE=false
DATABASE_SAVE_DATA_CONTACTS=false
DATABASE_SAVE_DATA_CHATS=false
# 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
WEBHOOK_GLOBAL_URL=''
WEBHOOK_GLOBAL_ENABLED=false
# With this option activated, you work with a url per webhook event, respecting the global url and the name of each event
WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=false
## Set the events you want to hear
WEBHOOK_EVENTS_APPLICATION_STARTUP=false
WEBHOOK_EVENTS_QRCODE_UPDATED=true
WEBHOOK_EVENTS_MESSAGES_SET=true
WEBHOOK_EVENTS_MESSAGES_UPSERT=true
WEBHOOK_EVENTS_MESSAGES_UPDATE=true
WEBHOOK_EVENTS_MESSAGES_DELETE=true
WEBHOOK_EVENTS_SEND_MESSAGE=true
WEBHOOK_EVENTS_CONTACTS_SET=true
WEBHOOK_EVENTS_CONTACTS_UPSERT=true
WEBHOOK_EVENTS_CONTACTS_UPDATE=true
WEBHOOK_EVENTS_PRESENCE_UPDATE=true
WEBHOOK_EVENTS_CHATS_SET=true
WEBHOOK_EVENTS_CHATS_UPSERT=true
WEBHOOK_EVENTS_CHATS_UPDATE=true
WEBHOOK_EVENTS_CHATS_DELETE=true
WEBHOOK_EVENTS_GROUPS_UPSERT=true
WEBHOOK_EVENTS_GROUPS_UPDATE=true
WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
WEBHOOK_EVENTS_CONNECTION_UPDATE=true
WEBHOOK_EVENTS_LABELS_EDIT=true
WEBHOOK_EVENTS_LABELS_ASSOCIATION=true
# This event fires every time a new token is requested via the refresh route
WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
# Name that will be displayed on smartphone connection
CONFIG_SESSION_PHONE_CLIENT='Evolution API'
# Browser Name = chrome | firefox | edge | opera | safari
CONFIG_SESSION_PHONE_NAME=chrome
# Set qrcode display limit
QRCODE_LIMIT=30
CACHE_REDIS_ENABLED=false
CACHE_REDIS_URI=redis://redis:6379
CACHE_REDIS_PREFIX_KEY=evolution
CACHE_REDIS_TTL=604800
CACHE_REDIS_SAVE_INSTANCES=false
CACHE_LOCAL_ENABLED=false
CACHE_LOCAL_TTL=604800
# 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
# jwt or 'apikey'
AUTHENTICATION_TYPE='apikey'
## Define a global apikey to access all instances.
### OBS: This key must be inserted in the request header to create an instance.
AUTHENTICATION_API_KEY='B6D711FCDE4D4FD5936544120E713976'
AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES=true
## Set the secret key to encrypt and decrypt your token and its expiration time
# seconds - 3600s ===1h | zero (0) - never expires
AUTHENTICATION_JWT_EXPIRIN_IN=0
AUTHENTICATION_JWT_SECRET='L0YWtjb2w554WFqPG'
# Set the instance name and webhook url to create an instance in init the application
# With this option activated, you work with a url per webhook event, respecting the local url and the name of each event
# container or server
AUTHENTICATION_INSTANCE_MODE=server
# if you are using container mode, set the container name and the webhook url to default instance
AUTHENTICATION_INSTANCE_NAME=evolution
AUTHENTICATION_INSTANCE_WEBHOOK_URL=''
AUTHENTICATION_INSTANCE_CHATWOOT_ACCOUNT_ID=1
AUTHENTICATION_INSTANCE_CHATWOOT_TOKEN=123456
AUTHENTICATION_INSTANCE_CHATWOOT_URL=''

View File

@@ -0,0 +1,91 @@
version: '3.3'
services:
mongodb:
container_name: mongodb
image: mongo
restart: on-failure
ports:
- 27017:27017
environment:
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=root
- PUID=1000
- PGID=1000
volumes:
- evolution_mongodb_data:/data/db
- evolution_mongodb_configdb:/data/configdb
expose:
- 27017
mongo-express:
container_name: mongodb-express
image: mongo-express
restart: on-failure
ports:
- 8081:8081
depends_on:
- mongodb
environment:
ME_CONFIG_BASICAUTH_USERNAME: root
ME_CONFIG_BASICAUTH_PASSWORD: root
ME_CONFIG_MONGODB_SERVER: mongodb
ME_CONFIG_MONGODB_ADMINUSERNAME: root
ME_CONFIG_MONGODB_ADMINPASSWORD: root
links:
- mongodb
redis:
container_name: redis
image: redis:latest
restart: on-failure
ports:
- 6379:6379
command: >
redis-server
--port 6379
--appendonly yes
volumes:
- evolution_redis:/data
rebrow:
container_name: rebrow
image: marian/rebrow
restart: on-failure
depends_on:
- redis
ports:
- 5001:5001
links:
- redis
api:
container_name: evolution_api
image: atendai/evolution-api
restart: always
depends_on:
- mongodb
- redis
ports:
- 8080:8080
volumes:
- evolution_instances:/evolution/instances
- evolution_store:/evolution/store
env_file:
- .env
command: ['node', './dist/src/main.js']
expose:
- 8080
volumes:
evolution_mongodb_data:
evolution_mongodb_configdb:
evolution_redis:
evolution_instances:
evolution_store:
networks:
evolution-net:
external: true

View File

@@ -35,7 +35,8 @@ volumes:
evolution_mongodb_data:
evolution_mongodb_configdb:
networks:
evolution-net:
external: true
name: evolution-net
driver: bridge

View File

@@ -5,9 +5,7 @@ services:
image: redis:latest
container_name: redis
command: >
redis-server
--port 6379
--appendonly yes
redis-server --port 6379 --appendonly yes
volumes:
- evolution_redis:/data
ports:
@@ -16,6 +14,8 @@ services:
volumes:
evolution_redis:
networks:
evolution-net:
external: true
name: evolution-net
driver: bridge

View File

@@ -1,18 +1,29 @@
FROM node:16.18-alpine
FROM node:20.7.0-alpine AS builder
LABEL version="1.1.3" description="Api to control whatsapp features through http requests."
LABEL version="1.7.4" description="Api to control whatsapp features through http requests."
LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
LABEL contact="contato@agenciadgcode.com"
RUN apk update && apk upgrade && \
apk add --no-cache git
apk add --no-cache git tzdata ffmpeg wget curl
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=*
@@ -24,6 +35,7 @@ ENV LOG_COLOR=true
ENV LOG_BAILEYS=error
ENV DEL_INSTANCE=false
ENV DEL_TEMP_INSTANCES=true
ENV STORE_MESSAGES=true
ENV STORE_MESSAGE_UP=true
@@ -46,16 +58,33 @@ ENV DATABASE_SAVE_MESSAGE_UPDATE=false
ENV DATABASE_SAVE_DATA_CONTACTS=false
ENV DATABASE_SAVE_DATA_CHATS=false
ENV REDIS_ENABLED=false
ENV REDIS_URI=redis://redis:6379
ENV REDIS_PREFIX_KEY=evolution
ENV RABBITMQ_ENABLED=false
ENV RABBITMQ_MODE=global
ENV RABBITMQ_EXCHANGE_NAME=evolution_exchange
ENV RABBITMQ_URI=amqp://guest:guest@rabbitmq:5672
ENV WEBHOOK_GLOBAL_URL=<url>
ENV WEBSOCKET_ENABLED=false
ENV WEBSOCKET_GLOBAL_EVENTS=false
ENV WA_BUSINESS_TOKEN_WEBHOOK=evolution
ENV WA_BUSINESS_URL=https://graph.facebook.com
ENV WA_BUSINESS_VERSION=v18.0
ENV WA_BUSINESS_LANGUAGE=pt_BR
ENV SQS_ENABLED=false
ENV SQS_ACCESS_KEY_ID=
ENV SQS_SECRET_ACCESS_KEY=
ENV SQS_ACCOUNT_ID=
ENV SQS_REGION=
ENV WEBHOOK_GLOBAL_URL=
ENV WEBHOOK_GLOBAL_ENABLED=false
ENV WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=false
ENV WEBHOOK_EVENTS_APPLICATION_STARTUP=false
ENV WEBHOOK_EVENTS_INSTANCE_CREATE=false
ENV WEBHOOK_EVENTS_INSTANCE_DELETE=false
ENV WEBHOOK_EVENTS_QRCODE_UPDATED=true
ENV WEBHOOK_EVENTS_MESSAGES_SET=true
ENV WEBHOOK_EVENTS_MESSAGES_UPSERT=true
@@ -74,14 +103,35 @@ ENV WEBHOOK_EVENTS_GROUPS_UPSERT=true
ENV WEBHOOK_EVENTS_GROUPS_UPDATE=true
ENV WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
ENV WEBHOOK_EVENTS_CONNECTION_UPDATE=true
ENV WEBHOOK_EVENTS_LABELS_EDIT=true
ENV WEBHOOK_EVENTS_LABELS_ASSOCIATION=true
ENV WEBHOOK_EVENTS_CALL=true
ENV WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
ENV WEBHOOK_EVENTS_TYPEBOT_START=false
ENV WEBHOOK_EVENTS_TYPEBOT_CHANGE_STATUS=false
ENV WEBHOOK_EVENTS_CHAMA_AI_ACTION=false
ENV WEBHOOK_EVENTS_ERRORS=false
ENV WEBHOOK_EVENTS_ERRORS_WEBHOOK=
ENV CONFIG_SESSION_PHONE_CLIENT=EvolutionAPI
ENV CONFIG_SESSION_PHONE_NAME=chrome
ENV CONFIG_SESSION_PHONE_NAME=Chrome
ENV QRCODE_LIMIT=30
ENV QRCODE_COLOR=#198754
ENV TYPEBOT_API_VERSION=latest
ENV CACHE_REDIS_ENABLED=false
ENV CACHE_REDIS_URI=redis://redis:6379
ENV CACHE_REDIS_PREFIX_KEY=evolution
ENV CACHE_REDIS_TTL=604800
ENV CACHE_REDIS_SAVE_INSTANCES=false
ENV CACHE_LOCAL_ENABLED=false
ENV CACHE_LOCAL_TTL=604800
ENV AUTHENTICATION_TYPE=apikey
@@ -99,10 +149,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" ]

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,241 @@
{
"name": "[Evolution] Configurar Admin",
"nodes": [
{
"parameters": {
"values": {
"string": [
{
"name": "api_access_token",
"value": "CHATWOOT_ADMIN_USER_TOKEN"
},
{
"name": "chatwoot_url",
"value": "https://CHATWOOT_URL"
},
{
"name": "n8n_url",
"value": "https://N8N_URL"
},
{
"name": "organization",
"value": "ORGANIZATION_NAME"
},
{
"name": "logo",
"value": "ORGANIZATION_LOGO"
}
]
},
"options": {}
},
"id": "7a89a538-2cae-4032-8896-09627c07bc68",
"name": "Info Base",
"type": "n8n-nodes-base.set",
"typeVersion": 2,
"position": [
620,
480
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/1/contacts/",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Info Base').item.json[\"api_access_token\"] }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"inbox_id\": {{ $('Cria Inbox Start').item.json[\"id\"] }},\n \"name\": \"Bot {{ $('Info Base').item.json[\"organization\"] }}\",\n \"phone_number\": \"+123456\",\n \"avatar_url\": \"{{ $('Info Base').item.json[\"logo\"] }}\"\n}",
"options": {}
},
"id": "12a39df3-6b95-4f83-a0bc-50b25adaca7f",
"name": "Cria Contato Bot",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
1020,
480
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/1/inboxes/",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Info Base').item.json[\"api_access_token\"] }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"name\": \"Start {{ $('Info Base').item.json[\"organization\"] }}\",\n \"channel\": {\n \"type\": \"api\",\n \"website_url\": \"\"\n }\n}",
"options": {}
},
"id": "bed7c54d-e232-4fe4-9584-0515e9679868",
"name": "Cria Inbox Start",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
820,
480
]
},
{
"parameters": {},
"id": "36ada769-a757-4193-989b-0cc4ea504b80",
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
420,
480
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/1/automation_rules/",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Info Base').item.json[\"api_access_token\"] }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"name\": \"Create Company Chatwoot\",\n \"description\": \"Create Company Chatwoot\",\n \"event_name\": \"message_created\",\n \"active\": true,\n \"actions\": \n [\n {\n \"action_name\": \"send_webhook_event\",\n \"action_params\": [\"{{ $('Info Base').item.json[\"n8n_url\"] }}/webhook/criadorchatwoot\"]\n }\n ],\n \"conditions\": \n [\n {\n \"attribute_key\": \"content\",\n \"filter_operator\": \"contains\",\n \"query_operator\": \"and\",\n \"values\": [\"Tema Criador de Empresa:\"]\n },\n {\n \"attribute_key\": \"phone_number\",\n \"filter_operator\": \"equal_to\",\n \"values\": [\"+123456\"]\n }\n ]\n}",
"options": {}
},
"id": "f5bbb285-71a8-4c58-a4d7-e56002d697f0",
"name": "Cria Automação Empresas",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
1220,
480
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/1/automation_rules/",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Info Base').item.json[\"api_access_token\"] }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"name\": \"Create Inbox {{ $('Info Base').item.json[\"organization\"] }}\",\n \"description\": \"Create Inbox {{ $('Info Base').item.json[\"organization\"] }}\",\n \"event_name\": \"message_created\",\n \"active\": true,\n \"actions\": \n [\n {\n \"action_name\": \"send_webhook_event\",\n \"action_params\": [\"{{ $('Info Base').item.json[\"n8n_url\"] }}/webhook/inbox_whatsapp?utoken={{ $('Info Base').item.json[\"api_access_token\"] }}&organization={{ $('Info Base').item.json[\"organization\"] }}\"]\n }\n ],\n \"conditions\": \n [\n {\n \"attribute_key\": \"content\",\n \"filter_operator\": \"contains\",\n \"query_operator\": \"and\",\n \"values\": [\"start:\"]\n },\n \n {\n \"attribute_key\": \"phone_number\",\n \"filter_operator\": \"equal_to\",\n \"query_operator\": \"or\",\n \"values\": [\"+123456\"]\n },\n\n\n {\n \"attribute_key\": \"content\",\n \"filter_operator\": \"contains\",\n \"query_operator\": \"and\",\n \"values\": [\"new_instance:\"]\n },\n {\n \"attribute_key\": \"phone_number\",\n \"filter_operator\": \"equal_to\",\n \"values\": [\"+123456\"]\n }\n ]\n}",
"options": {}
},
"id": "a36bebdc-a318-40a2-8532-c7f476f8adb7",
"name": "Cria Automação Inboxes",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
1420,
480
]
},
{
"parameters": {
"content": "## Workflow Para Configurar admin\n**Aqui você prepara o Chatwoot Principal com um usuário (Superadmin) que poderá criar empresas e caixas de entrada**\n**Instruções**\n**No node Info Base, configure as variáveis de seu Chatwoot e N8N**\n**Obs: A variável api_access_token é o token do usuário que irá poder criar as empresas**",
"width": 894.6435495898575
},
"id": "db66e867-e9f4-452d-b521-725eeac652c8",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
420,
280
]
}
],
"pinData": {},
"connections": {
"Info Base": {
"main": [
[
{
"node": "Cria Inbox Start",
"type": "main",
"index": 0
}
]
]
},
"Cria Contato Bot": {
"main": [
[
{
"node": "Cria Automação Empresas",
"type": "main",
"index": 0
}
]
]
},
"Cria Inbox Start": {
"main": [
[
{
"node": "Cria Contato Bot",
"type": "main",
"index": 0
}
]
]
},
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Info Base",
"type": "main",
"index": 0
}
]
]
},
"Cria Automação Empresas": {
"main": [
[
{
"node": "Cria Automação Inboxes",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {},
"versionId": "78f155dc-7809-4bfc-9282-63f49b07fc4d",
"id": "BSATyGpGWLR4ZwNm",
"meta": {
"instanceId": "4ff16e963c7f5197d7e99e6239192860914312fea0ce2a9a7fd14d74a0a0e906"
},
"tags": []
}

View File

@@ -0,0 +1,456 @@
{
"name": "[Evolution] Criador de Empresas",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "criadorchatwoot",
"options": {}
},
"id": "5a47c10a-e43c-4fa5-baad-4b6cc511bfcd",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
1420,
860
],
"webhookId": "6fe428e3-1752-453c-9358-abf18b793387"
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/platform/api/v1/accounts",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Info Base').item.json[\"api_access_token\"] }}"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "name",
"value": "={{ $json.name_company }}"
},
{
"name": "locale",
"value": "pt_BR"
}
]
},
"options": {}
},
"id": "8295c119-3a96-424e-9386-43d75f6816f5",
"name": "Cria Conta",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
2020,
860
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/platform/api/v1/users",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Info Base').item.json[\"api_access_token\"] }}"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "name",
"value": "={{ $('Info Base').item.json.name_admin }}"
},
{
"name": "email",
"value": "={{ $('Info Base').item.json[\"email\"] }}"
},
{
"name": "password",
"value": "={{ $('Info Base').item.json[\"password\"] }}"
}
]
},
"options": {}
},
"id": "4fe5007a-3a6b-490a-a446-e45cc168189f",
"name": "Cria Usuario",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
2220,
860
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/platform/api/v1/accounts/{{ $node[\"Cria Conta\"].json[\"id\"] }}/account_users",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Info Base').item.json[\"api_access_token\"] }}"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "user_id",
"value": "={{ $node[\"Cria Usuario\"].json[\"id\"] }}"
},
{
"name": "role",
"value": "administrator"
}
]
},
"options": {}
},
"id": "848c55e2-5678-4291-9602-c94d994da95b",
"name": "Add Usuario a Conta",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
2420,
860
]
},
{
"parameters": {
"fromEmail": "={{ $('Info Base').item.json[\"from_email\"] }}",
"toEmail": "={{ $('LimpaDados').item.json.email }}",
"subject": "=Bem vindo à {{ $('Info Base').item.json[\"organization\"] }}",
"text": "=Olá seja bem vindo:\n\nAbaixo segue seus dados de acesso:\n\nURL: {{ $('Info Base').item.json[\"chatwoot_url\"] }}\n\nuser: {{ $('LimpaDados').item.json[\"email\"] }}\n\nSenha: {{ $('LimpaDados').item.json[\"password\"] }}",
"options": {}
},
"id": "27f3b24f-1cf2-4d0d-a354-ecba066059f6",
"name": "Send Email",
"type": "n8n-nodes-base.emailSend",
"typeVersion": 2,
"position": [
3220,
860
],
"credentials": {
"smtp": {
"id": "6BxluEUV8zrXKoVG",
"name": "[Dgcode] SMTP"
}
}
},
{
"parameters": {
"values": {
"string": [
{
"name": "api_access_token",
"value": "CHATWOOT_PLATFORM_TOKEN"
},
{
"name": "chatwoot_url",
"value": "https://CHATWOOT_URL"
},
{
"name": "n8n_url",
"value": "https://N8N_URL"
},
{
"name": "organization",
"value": "ORGANIZATION_NAME"
},
{
"name": "logo",
"value": "ORGANIZATION_LOGO"
},
{
"name": "from_email",
"value": "FROM_EMAIL"
},
{
"name": "name",
"value": "={{ $json.name_company }}"
},
{
"name": "email",
"value": "={{ $json.email }}"
},
{
"name": "password",
"value": "={{ $json.password }}"
},
{
"name": "name_company",
"value": "={{ $json.name_company }}"
}
]
},
"options": {}
},
"id": "38b4069d-e51e-4db7-933f-941b1be6d124",
"name": "Info Base",
"type": "n8n-nodes-base.set",
"typeVersion": 2,
"position": [
1820,
860
]
},
{
"parameters": {
"keepOnlySet": true,
"values": {
"string": [
{
"name": "name_admin",
"value": "={{$node[\"Webhook\"].json[\"body\"][\"messages\"][0][\"content\"].match(/Nome Usuario Administrador: ([^\\n]+)/)[1];}}"
},
{
"name": "name_company",
"value": "={{$node[\"Webhook\"].json[\"body\"][\"messages\"][0][\"content\"].match(/Nome da Empresa: ([^\\n]+)/)[1];}}"
},
{
"name": "email",
"value": "={{$node[\"Webhook\"].json[\"body\"][\"messages\"][0][\"content\"].match(/Email: ([^\\s]+)/)[1];}}"
},
{
"name": "password",
"value": "={{$node[\"Webhook\"].json[\"body\"][\"messages\"][0][\"content\"].match(/Senha: ([^\\s]+)/)[1];}}"
}
]
},
"options": {}
},
"id": "28e29e73-aadc-49ca-bd6d-b57ee0160a21",
"name": "LimpaDados",
"type": "n8n-nodes-base.set",
"typeVersion": 2,
"position": [
1620,
860
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/{{ $('Add Usuario a Conta').item.json.account_id }}/contacts/",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Cria Usuario').item.json.access_token }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"inbox_id\": {{ $('Cria Inbox Start').item.json[\"id\"] }},\n \"name\": \"Bot {{ $('Info Base').item.json[\"organization\"] }}\",\n \"phone_number\": \"+123456\",\n \"avatar_url\": \"{{ $('Info Base').item.json[\"logo\"] }}\"\n}",
"options": {}
},
"id": "bb671443-bdb4-4f56-99af-f0baef246a3e",
"name": "Cria Contato Bot",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
2820,
860
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/{{ $('Add Usuario a Conta').item.json.account_id }}/automation_rules/",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Cria Usuario').item.json.access_token }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"name\": \"Create Inbox {{ $('Info Base').item.json[\"organization\"] }}\",\n \"description\": \"Create Inbox {{ $('Info Base').item.json[\"organization\"] }}\",\n \"event_name\": \"message_created\",\n \"active\": true,\n \"actions\": \n [\n {\n \"action_name\": \"send_webhook_event\",\n \"action_params\": [\"{{ $('Info Base').item.json[\"n8n_url\"] }}/webhook/inbox_whatsapp?utoken={{ $('Cria Usuario').item.json.access_token }}&organization={{ $('Info Base').item.json[\"organization\"] }}\"]\n }\n ],\n \"conditions\": \n [\n {\n \"attribute_key\": \"content\",\n \"filter_operator\": \"contains\",\n \"query_operator\": \"and\",\n \"values\": [\"start:\"]\n },\n {\n \"attribute_key\": \"phone_number\",\n \"filter_operator\": \"equal_to\",\n \"query_operator\": \"or\",\n \"values\": [\"+123456\"]\n },\n {\n \"attribute_key\": \"content\",\n \"filter_operator\": \"contains\",\n \"query_operator\": \"and\",\n \"values\": [\"new_instance:\"]\n },\n {\n \"attribute_key\": \"phone_number\",\n \"filter_operator\": \"equal_to\",\n \"values\": [\"+123456\"]\n }\n ]\n}",
"options": {}
},
"id": "e016a2af-b212-4e00-a3ff-8cd03530aa06",
"name": "Cria Automação",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
3020,
860
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/{{ $json.account_id }}/inboxes/",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Cria Usuario').item.json.access_token }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"name\": \"Start {{ $('Info Base').item.json[\"organization\"] }}\",\n \"channel\": {\n \"type\": \"api\",\n \"website_url\": \"\"\n }\n}",
"options": {}
},
"id": "d3c42148-8920-4c98-a874-eb7113f2dd22",
"name": "Cria Inbox Start",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
2620,
860
]
},
{
"parameters": {
"content": "## Workflow Criador de Empresas\n**Cria Contas (Empresas) e Usuários através de tema**\n**Instruções**\n**No node Info Base, configure as variáveis de seu Chatwoot e N8N**\n**Obs: A variável api_access_token é o token PlatformApp encontrado no acesso ao Super Admin**\n**Tema para criar novas empresa:**\n\nTema Criador de Empresa:\n\nNome Usuario Administrador: Joao Linhares\nNome da Empresa: Oficina Linhates\nEmail: machineteste24@gmail.com\nSenha: Mfcd62!!",
"height": 304.02684563758396,
"width": 1129.7777777777778
},
"id": "d07516c0-4c8e-43ab-ba86-c8d063b09be5",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
1420,
520
]
}
],
"pinData": {},
"connections": {
"Webhook": {
"main": [
[
{
"node": "LimpaDados",
"type": "main",
"index": 0
}
]
]
},
"Cria Conta": {
"main": [
[
{
"node": "Cria Usuario",
"type": "main",
"index": 0
}
]
]
},
"Cria Usuario": {
"main": [
[
{
"node": "Add Usuario a Conta",
"type": "main",
"index": 0
}
]
]
},
"Add Usuario a Conta": {
"main": [
[
{
"node": "Cria Inbox Start",
"type": "main",
"index": 0
}
]
]
},
"Info Base": {
"main": [
[
{
"node": "Cria Conta",
"type": "main",
"index": 0
}
]
]
},
"LimpaDados": {
"main": [
[
{
"node": "Info Base",
"type": "main",
"index": 0
}
]
]
},
"Cria Contato Bot": {
"main": [
[
{
"node": "Cria Automação",
"type": "main",
"index": 0
}
]
]
},
"Cria Automação": {
"main": [
[
{
"node": "Send Email",
"type": "main",
"index": 0
}
]
]
},
"Cria Inbox Start": {
"main": [
[
{
"node": "Cria Contato Bot",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {},
"versionId": "3ffd6d3f-6966-4de4-af8f-1fda464bc1b8",
"id": "79R6qQDtfyCwgYjJ",
"meta": {
"instanceId": "4ff16e963c7f5197d7e99e6239192860914312fea0ce2a9a7fd14d74a0a0e906"
},
"tags": []
}

View File

@@ -0,0 +1,510 @@
{
"name": "[Evolution] Criador de Inbox",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "inbox_whatsapp",
"options": {}
},
"id": "8205b929-73e9-456a-9b0d-e1474991663a",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
320,
300
],
"webhookId": "cf37002d-3869-4bb1-af3a-739fdd3c1756"
},
{
"parameters": {
"method": "POST",
"url": "={{ $json.evolution_url }}/instance/create",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "apikey",
"value": "={{ $json.global_api_key }}"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "instanceName",
"value": "={{ $json.instance_name }}"
},
{
"name": "qrcode",
"value": "={{ $json.qrcode }}"
},
{
"name": "chatwoot_account_id",
"value": "={{ $json.chatwoot_account_id }}"
},
{
"name": "chatwoot_token",
"value": "={{ $json.chatwoot_token }}"
},
{
"name": "chatwoot_url",
"value": "={{ $json.chatwoot_url }}"
},
{
"name": "chatwoot_sign_msg",
"value": "={{ $json.chatwoot_sign_msg }}"
},
{
"name": "chatwoot_reopen_conversation",
"value": "={{ $json.chatwoot_reopen_conversation }}"
},
{
"name": "chatwoot_conversation_pending",
"value": "={{ $json.chatwoot_conversation_pending }}"
},
{
"name": "reject_call",
"value": "={{ $json.reject_call }}"
},
{
"name": "msg_call",
"value": "={{ $json.msg_call }}"
},
{
"name": "groups_ignore",
"value": "={{ $json.groups_ignore }}"
},
{
"name": "always_online",
"value": "={{ $json.always_online }}"
},
{
"name": "read_messages",
"value": "={{ $json.read_messages }}"
},
{
"name": "read_status",
"value": "={{ $json.read_status }}"
}
]
},
"options": {}
},
"id": "275aa370-2fdb-42f4-844a-2fb3051301bd",
"name": "Cria Instancia",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [
760,
300
]
},
{
"parameters": {
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/{{ $('Info Base').item.json[\"chatwoot_account_id\"] }}/inboxes/",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Info Base').item.json.chatwoot_token }}"
}
]
},
"options": {}
},
"id": "e4650812-ba0a-4f72-8bd8-a235eca4b2de",
"name": "Lista Inboxes",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
980,
300
]
},
{
"parameters": {
"content": "## Workflow Para Criar Inbox\n**Aqui você configura a comunicação entre o chatwoot e a Evolution API para criar novas instâncias a partir do chatwoot**\n**Instruções**\n**No node Info Base, configure as variáveis de seu Chatwoot e Evolution API**",
"width": 1129.7777777777778
},
"id": "aa763d9e-d973-44fc-8399-277bb24718a5",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
320,
80
]
},
{
"parameters": {
"keepOnlySet": true,
"values": {
"string": [
{
"name": "chatwoot_url",
"value": "CHATWOOT_URL"
},
{
"name": "evolution_url",
"value": "EVOLUTION_URL"
},
{
"name": "global_api_key",
"value": "EVOLUTION_GLOBAL_API_KEY"
},
{
"name": "organization",
"value": "={{ $json.query.organization }}"
},
{
"name": "instance_name",
"value": "={{ $json.body.messages[0].content.split(':')[1] }}-cwId-{{ $json.body.messages[0].account_id }}"
},
{
"name": "chatwoot_token",
"value": "={{ $json.query.utoken }}"
},
{
"name": "msg_call",
"value": "Não aceitamos chamadas, por favor deixe uma mensagem!"
}
],
"boolean": [
{
"name": "qrcode",
"value": true
},
{
"name": "chatwoot_sign_msg",
"value": true
},
{
"name": "chatwoot_reopen_conversation",
"value": true
},
{
"name": "chatwoot_conversation_pending"
},
{
"name": "reject_call",
"value": true
},
{
"name": "groups_ignore"
},
{
"name": "always_online",
"value": true
},
{
"name": "read_messages",
"value": true
},
{
"name": "read_status"
}
],
"number": [
{
"name": "chatwoot_account_id",
"value": "={{ $json.body.messages[0].account_id }}"
}
]
},
"options": {}
},
"id": "297df325-ecc4-4a34-817c-092d16d5753b",
"name": "Info Base",
"type": "n8n-nodes-base.set",
"typeVersion": 2,
"position": [
540,
300
]
},
{
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $json.name }}",
"value2": "=Start {{ $('Info Base').item.json[\"organization\"] }}"
}
]
}
},
"id": "a8d955e6-ac51-4316-aeec-09d4d65e943a",
"name": "é Start Inbox?",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
1660,
200
]
},
{
"parameters": {
"batchSize": 1,
"options": {}
},
"id": "0d2d2194-aa4a-4241-9022-217d88bb581f",
"name": "Split In Batches",
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 2,
"position": [
1420,
300
]
},
{
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $json.name }}",
"value2": "={{ $('Webhook').item.json[\"body\"][\"messages\"][0][\"content\"].split(':')[1] }}"
}
]
}
},
"id": "0bfbc2cb-eff5-423c-bd3a-b266aaf6a943",
"name": "é_pre-existente?",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
1900,
340
]
},
{
"parameters": {
"method": "PATCH",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/{{ $('Info Base').item.json[\"chatwoot_account_id\"] }}/inboxes/{{ $json.id }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Info Base').item.json.chatwoot_token }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n\"channel\": {\n\"webhook_url\": \"{{ $('Info Base').item.json[\"evolution_url\"] }}/chatwoot/webhook/{{ encodeURIComponent($('Info Base').item.json[\"instance_name\"]) }}\"\n}\n}",
"options": {}
},
"id": "fb589456-5566-4a45-96a7-75986d0aa1d5",
"name": "Update_webhook_url",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
2120,
340
]
},
{
"parameters": {
"method": "DELETE",
"url": "={{ $('Info Base').item.json[\"chatwoot_url\"] }}/api/v1/accounts/{{ $('Info Base').item.json[\"chatwoot_account_id\"] }}/inboxes/{{ $json.id }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "api_access_token",
"value": "={{ $('Info Base').item.json.chatwoot_token }}"
}
]
},
"options": {}
},
"id": "e6094941-410f-496c-9c9c-7b95fd9349af",
"name": "Deleta Inbox Start",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
1900,
100
]
},
{
"parameters": {},
"id": "8cf9a78f-9e8a-4288-9d7b-801790af68d5",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [
1660,
400
]
},
{
"parameters": {
"fieldToSplitOut": "payload",
"options": {}
},
"id": "9468896a-5f86-4598-9d20-e8f495cae859",
"name": "Ajusta lista",
"type": "n8n-nodes-base.itemLists",
"typeVersion": 2.2,
"position": [
1200,
300
]
}
],
"pinData": {},
"connections": {
"Webhook": {
"main": [
[
{
"node": "Info Base",
"type": "main",
"index": 0
}
]
]
},
"Lista Inboxes": {
"main": [
[
{
"node": "Ajusta lista",
"type": "main",
"index": 0
}
]
]
},
"Cria Instancia": {
"main": [
[
{
"node": "Lista Inboxes",
"type": "main",
"index": 0
}
]
]
},
"Info Base": {
"main": [
[
{
"node": "Cria Instancia",
"type": "main",
"index": 0
}
]
]
},
"é Start Inbox?": {
"main": [
[
{
"node": "Deleta Inbox Start",
"type": "main",
"index": 0
}
],
[
{
"node": "é_pre-existente?",
"type": "main",
"index": 0
}
]
]
},
"Split In Batches": {
"main": [
[
{
"node": "é Start Inbox?",
"type": "main",
"index": 0
}
],
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
},
"é_pre-existente?": {
"main": [
[
{
"node": "Update_webhook_url",
"type": "main",
"index": 0
}
],
[
{
"node": "Split In Batches",
"type": "main",
"index": 0
}
]
]
},
"Update_webhook_url": {
"main": [
[
{
"node": "Split In Batches",
"type": "main",
"index": 0
}
]
]
},
"Deleta Inbox Start": {
"main": [
[
{
"node": "Split In Batches",
"type": "main",
"index": 0
}
]
]
},
"Ajusta lista": {
"main": [
[
{
"node": "Split In Batches",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {},
"versionId": "ab910349-b559-4738-9ac6-de6b06d6bbce",
"id": "ByW2ccjR4XPrOyio",
"meta": {
"instanceId": "4ff16e963c7f5197d7e99e6239192860914312fea0ce2a9a7fd14d74a0a0e906"
},
"tags": []
}

File diff suppressed because one or more lines are too long

View File

@@ -7,7 +7,8 @@
[![Postman Collection](https://img.shields.io/badge/Postman-Collection-orange)](https://evolution-api.com/postman)
[![Documentation](https://img.shields.io/badge/Documentation-Official-green)](https://doc.evolution-api.com)
[![License](https://img.shields.io/badge/license-GPL--3.0-orange)](./LICENSE)
[![Support](https://img.shields.io/badge/Buy%20me-coffe-orange)](https://app.picpay.com/user/davidsongomes1998)
[![Support](https://img.shields.io/badge/Donation-picpay-green)](https://app.picpay.com/user/davidsongomes1998)
[![Support](https://img.shields.io/badge/Buy%20me-coffe-orange)](https://bmc.link/evolutionapi)
</div>
@@ -34,8 +35,17 @@ This code was produced based on the baileys library and it is still under develo
<div align="center">
<a href="https://app.picpay.com/user/davidsongomes1998" target="_blank" rel="noopener noreferrer">
<img src="./public/images/picpay-image.png" style="width: 50% !important;">
<img src="./public/images/picpay-qr.jpeg" style="width: 50% !important;">
</a>
</div>
#### Buy me coffe - PIX
<div align="center">
<a href="https://bmc.link/evolutionapi" target="_blank" rel="noopener noreferrer">
<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

@@ -4,6 +4,7 @@ services:
api:
container_name: evolution_api
image: evolution/api:local
build: .
restart: always
ports:
- 8080:8080
@@ -24,5 +25,5 @@ volumes:
networks:
evolution-net:
external: true
name: evolution-net
driver: bridge

View File

@@ -4,6 +4,7 @@ services:
api:
container_name: evolution_api
image: evolution/api:local
build: .
restart: always
ports:
- 8080:8080
@@ -75,5 +76,5 @@ volumes:
networks:
evolution-net:
external: true
name: evolution-net
driver: bridge

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
@@ -24,5 +24,5 @@ volumes:
networks:
evolution-net:
external: true
name: evolution-net
driver: bridge

View File

@@ -1,13 +0,0 @@
#!/bin/bash
NET='evolution-net'
IMAGE='evolution/api:local'
if !(docker network ls | grep ${NET} > /dev/null)
then
docker network create -d bridge ${NET}
fi
docker build -t ${IMAGE} .
docker compose up -d

View File

@@ -1,6 +1,6 @@
{
"name": "evolution-api",
"version": "1.4.8",
"version": "1.7.4",
"description": "Rest api for communication with WhatsApp",
"main": "./dist/src/main.js",
"scripts": {
@@ -43,37 +43,52 @@
"dependencies": {
"@adiwajshing/keyed-db": "^0.2.4",
"@ffmpeg-installer/ffmpeg": "^1.1.0",
"@figuro/chatwoot-sdk": "^1.1.14",
"@figuro/chatwoot-sdk": "^1.1.16",
"@hapi/boom": "^10.0.1",
"@sentry/node": "^7.59.2",
"@whiskeysockets/baileys": "github:EvolutionAPI/Baileys",
"axios": "^1.3.5",
"class-validator": "^0.13.2",
"amqplib": "^0.10.3",
"aws-sdk": "^2.1499.0",
"axios": "^1.6.5",
"baileys": "^6.7.0",
"class-validator": "^0.14.1",
"compression": "^1.7.4",
"cors": "^2.8.5",
"cross-env": "^7.0.3",
"dayjs": "^1.11.7",
"eventemitter2": "^6.4.9",
"evolution-manager": "^0.4.13",
"exiftool-vendored": "^22.0.0",
"express": "^4.18.2",
"express-async-errors": "^3.1.1",
"fluent-ffmpeg": "^2.1.2",
"form-data": "^4.0.0",
"hbs": "^4.2.0",
"https-proxy-agent": "^7.0.2",
"i18next": "^23.7.19",
"jimp": "^0.16.13",
"join": "^3.0.0",
"js-yaml": "^4.1.0",
"jsonschema": "^1.4.1",
"jsonwebtoken": "^8.5.1",
"jsonwebtoken": "^9.0.2",
"libphonenumber-js": "^1.10.39",
"link-preview-js": "^3.0.4",
"mongoose": "^6.10.5",
"node-cache": "^5.1.2",
"node-mime-types": "^1.1.0",
"node-windows": "^1.0.0-beta.8",
"parse-bmfont-xml": "^1.1.4",
"pg": "^8.11.3",
"pino": "^8.11.0",
"proxy-agent": "^6.2.1",
"qrcode": "^1.5.1",
"qrcode-terminal": "^0.12.0",
"redis": "^4.6.5",
"sharp": "^0.30.7",
"uuid": "^9.0.0"
"sharp": "^0.32.2",
"socket.io": "^4.7.1",
"socks-proxy-agent": "^8.0.1",
"swagger-ui-express": "^5.0.0",
"uuid": "^9.0.0",
"xml2js": "^0.6.2",
"yamljs": "^0.3.0"
},
"devDependencies": {
"@types/compression": "^1.7.2",
@@ -83,6 +98,7 @@
"@types/jsonwebtoken": "^8.5.9",
"@types/mime-types": "^2.1.1",
"@types/node": "^18.15.11",
"@types/node-windows": "^0.1.2",
"@types/qrcode": "^1.5.0",
"@types/qrcode-terminal": "^0.12.0",
"@types/uuid": "^8.3.4",

BIN
public/images/bmc_qr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,754 @@
import { delay } from 'baileys';
import { isURL } from 'class-validator';
import EventEmitter2 from 'eventemitter2';
import { v4 } from 'uuid';
import { ConfigService, HttpServer, WaBusiness } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { BadRequestException, InternalServerErrorException } from '../../exceptions';
import { InstanceDto, SetPresenceDto } from '../dto/instance.dto';
import { ChatwootService } from '../integrations/chatwoot/services/chatwoot.service';
import { RabbitmqService } from '../integrations/rabbitmq/services/rabbitmq.service';
import { SqsService } from '../integrations/sqs/services/sqs.service';
import { TypebotService } from '../integrations/typebot/services/typebot.service';
import { WebsocketService } from '../integrations/websocket/services/websocket.service';
import { RepositoryBroker } from '../repository/repository.manager';
import { AuthService, OldToken } from '../services/auth.service';
import { CacheService } from '../services/cache.service';
import { BaileysStartupService } from '../services/channels/whatsapp.baileys.service';
import { BusinessStartupService } from '../services/channels/whatsapp.business.service';
import { IntegrationService } from '../services/integration.service';
import { WAMonitoringService } from '../services/monitor.service';
import { SettingsService } from '../services/settings.service';
import { WebhookService } from '../services/webhook.service';
import { Events, Integration, wa } from '../types/wa.types';
import { ProxyController } from './proxy.controller';
export class InstanceController {
constructor(
private readonly waMonitor: WAMonitoringService,
private readonly configService: ConfigService,
private readonly repository: RepositoryBroker,
private readonly eventEmitter: EventEmitter2,
private readonly authService: AuthService,
private readonly webhookService: WebhookService,
private readonly chatwootService: ChatwootService,
private readonly settingsService: SettingsService,
private readonly websocketService: WebsocketService,
private readonly rabbitmqService: RabbitmqService,
private readonly sqsService: SqsService,
private readonly typebotService: TypebotService,
private readonly integrationService: IntegrationService,
private readonly proxyService: ProxyController,
private readonly cache: CacheService,
private readonly chatwootCache: CacheService,
private readonly messagesLostCache: CacheService,
) {}
private readonly logger = new Logger(InstanceController.name);
public async createInstance({
instanceName,
webhook,
webhook_by_events,
webhook_base64,
events,
qrcode,
number,
mobile,
integration,
token,
chatwoot_account_id,
chatwoot_token,
chatwoot_url,
chatwoot_sign_msg,
chatwoot_reopen_conversation,
chatwoot_conversation_pending,
chatwoot_import_contacts,
chatwoot_name_inbox,
chatwoot_import_messages,
chatwoot_days_limit_import_messages,
reject_call,
msg_call,
groups_ignore,
always_online,
read_messages,
read_status,
sync_full_history,
websocket_enabled,
websocket_events,
rabbitmq_enabled,
rabbitmq_events,
sqs_enabled,
sqs_events,
typebot_url,
typebot,
typebot_expire,
typebot_keyword_finish,
typebot_delay_message,
typebot_unknown_message,
typebot_listening_from_me,
proxy,
}: InstanceDto) {
try {
this.logger.verbose('requested createInstance from ' + instanceName + ' instance');
this.logger.verbose('checking duplicate token');
await this.authService.checkDuplicateToken(token);
if (!token && integration === Integration.WHATSAPP_BUSINESS) {
throw new BadRequestException('token is required');
}
this.logger.verbose('creating instance');
let instance: BaileysStartupService | BusinessStartupService;
if (integration === Integration.WHATSAPP_BUSINESS) {
instance = new BusinessStartupService(
this.configService,
this.eventEmitter,
this.repository,
this.cache,
this.chatwootCache,
this.messagesLostCache,
);
} else {
instance = new BaileysStartupService(
this.configService,
this.eventEmitter,
this.repository,
this.cache,
this.chatwootCache,
this.messagesLostCache,
);
}
await this.waMonitor.saveInstance({ integration, instanceName, token, number, mobile });
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;
this.waMonitor.delInstanceTime(instance.instanceName);
this.logger.verbose('generating hash');
const hash = await this.authService.generateHash(
{
instanceName: instance.instanceName,
instanceId: instanceId,
},
token,
);
this.logger.verbose('hash: ' + hash + ' generated');
let webhookEvents: string[];
if (webhook) {
if (!isURL(webhook, { require_tld: false })) {
throw new BadRequestException('Invalid "url" property in webhook');
}
this.logger.verbose('creating webhook');
try {
let newEvents: string[] = [];
if (events.length === 0) {
newEvents = [
'APPLICATION_STARTUP',
'QRCODE_UPDATED',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_UPDATE',
'MESSAGES_DELETE',
'SEND_MESSAGE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'PRESENCE_UPDATE',
'CHATS_SET',
'CHATS_UPSERT',
'CHATS_UPDATE',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
'TYPEBOT_CHANGE_STATUS',
'CHAMA_AI_ACTION',
];
} else {
newEvents = events;
}
this.webhookService.create(instance, {
enabled: true,
url: webhook,
events: newEvents,
webhook_by_events,
webhook_base64,
});
webhookEvents = (await this.webhookService.find(instance)).events;
} catch (error) {
this.logger.log(error);
}
}
let websocketEvents: string[];
if (websocket_enabled) {
this.logger.verbose('creating websocket');
try {
let newEvents: string[] = [];
if (websocket_events.length === 0) {
newEvents = [
'APPLICATION_STARTUP',
'QRCODE_UPDATED',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_UPDATE',
'MESSAGES_DELETE',
'SEND_MESSAGE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'PRESENCE_UPDATE',
'CHATS_SET',
'CHATS_UPSERT',
'CHATS_UPDATE',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
'TYPEBOT_CHANGE_STATUS',
'CHAMA_AI_ACTION',
];
} else {
newEvents = websocket_events;
}
this.websocketService.create(instance, {
enabled: true,
events: newEvents,
});
websocketEvents = (await this.websocketService.find(instance)).events;
} catch (error) {
this.logger.log(error);
}
}
let rabbitmqEvents: string[];
if (rabbitmq_enabled) {
this.logger.verbose('creating rabbitmq');
try {
let newEvents: string[] = [];
if (rabbitmq_events.length === 0) {
newEvents = [
'APPLICATION_STARTUP',
'QRCODE_UPDATED',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_UPDATE',
'MESSAGES_DELETE',
'SEND_MESSAGE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'PRESENCE_UPDATE',
'CHATS_SET',
'CHATS_UPSERT',
'CHATS_UPDATE',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
'TYPEBOT_CHANGE_STATUS',
'CHAMA_AI_ACTION',
];
} else {
newEvents = rabbitmq_events;
}
this.rabbitmqService.create(instance, {
enabled: true,
events: newEvents,
});
rabbitmqEvents = (await this.rabbitmqService.find(instance)).events;
} 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',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
'TYPEBOT_CHANGE_STATUS',
'CHAMA_AI_ACTION',
];
} else {
newEvents = sqs_events;
}
this.sqsService.create(instance, {
enabled: true,
events: newEvents,
});
sqsEvents = (await this.sqsService.find(instance)).events;
} catch (error) {
this.logger.log(error);
}
}
if (proxy) {
const testProxy = await this.proxyService.testProxy(proxy);
if (!testProxy) {
throw new BadRequestException('Invalid proxy');
}
await this.proxyService.createProxy(instance, {
enabled: true,
proxy,
});
}
if (typebot_url) {
try {
if (!isURL(typebot_url, { require_tld: false })) {
throw new BadRequestException('Invalid "url" property in typebot_url');
}
this.logger.verbose('creating typebot');
this.typebotService.create(instance, {
enabled: true,
url: typebot_url,
typebot: typebot,
expire: typebot_expire,
keyword_finish: typebot_keyword_finish,
delay_message: typebot_delay_message,
unknown_message: typebot_unknown_message,
listening_from_me: typebot_listening_from_me,
});
} catch (error) {
this.logger.log(error);
}
}
this.logger.verbose('creating settings');
const settings: wa.LocalSettings = {
reject_call: reject_call || false,
msg_call: msg_call || '',
groups_ignore: groups_ignore || true,
always_online: always_online || false,
read_messages: read_messages || false,
read_status: read_status || false,
sync_full_history: sync_full_history ?? false,
};
this.logger.verbose('settings: ' + JSON.stringify(settings));
this.settingsService.create(instance, settings);
let webhook_wa_business = null,
access_token_wa_business = '';
if (integration === Integration.WHATSAPP_BUSINESS) {
if (!number) {
throw new BadRequestException('number is required');
}
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
webhook_wa_business = `${urlServer}/webhook/whatsapp/${encodeURIComponent(instance.instanceName)}`;
access_token_wa_business = this.configService.get<WaBusiness>('WA_BUSINESS').TOKEN_WEBHOOK;
}
this.integrationService.create(instance, {
integration,
number,
token,
});
if (!chatwoot_account_id || !chatwoot_token || !chatwoot_url) {
let getQrcode: wa.QrCode;
if (qrcode) {
this.logger.verbose('creating qrcode');
await instance.connectToWhatsapp(number, mobile);
await delay(5000);
getQrcode = instance.qrCode;
}
const result = {
instance: {
instanceName: instance.instanceName,
instanceId: instanceId,
integration: integration,
webhook_wa_business,
access_token_wa_business,
status: 'created',
},
hash,
webhook: {
webhook,
webhook_by_events,
webhook_base64,
events: webhookEvents,
},
websocket: {
enabled: websocket_enabled,
events: websocketEvents,
},
rabbitmq: {
enabled: rabbitmq_enabled,
events: rabbitmqEvents,
},
sqs: {
enabled: sqs_enabled,
events: sqsEvents,
},
typebot: {
enabled: typebot_url ? true : false,
url: typebot_url,
typebot,
expire: typebot_expire,
keyword_finish: typebot_keyword_finish,
delay_message: typebot_delay_message,
unknown_message: typebot_unknown_message,
listening_from_me: typebot_listening_from_me,
},
settings,
qrcode: getQrcode,
};
this.logger.verbose('instance created');
this.logger.verbose(result);
return result;
}
if (!chatwoot_account_id) {
throw new BadRequestException('account_id is required');
}
if (!chatwoot_token) {
throw new BadRequestException('token is required');
}
if (!chatwoot_url) {
throw new BadRequestException('url is required');
}
if (!isURL(chatwoot_url, { require_tld: false })) {
throw new BadRequestException('Invalid "url" property in chatwoot');
}
if (chatwoot_sign_msg !== true && chatwoot_sign_msg !== false) {
throw new BadRequestException('sign_msg is required');
}
if (chatwoot_reopen_conversation !== true && chatwoot_reopen_conversation !== false) {
throw new BadRequestException('reopen_conversation is required');
}
if (chatwoot_conversation_pending !== true && chatwoot_conversation_pending !== false) {
throw new BadRequestException('conversation_pending is required');
}
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
try {
this.chatwootService.create(instance, {
enabled: true,
account_id: chatwoot_account_id,
token: chatwoot_token,
url: chatwoot_url,
sign_msg: chatwoot_sign_msg || false,
name_inbox: chatwoot_name_inbox ?? instance.instanceName.split('-cwId-')[0],
number,
reopen_conversation: chatwoot_reopen_conversation || false,
conversation_pending: chatwoot_conversation_pending || false,
import_contacts: chatwoot_import_contacts ?? true,
import_messages: chatwoot_import_messages ?? true,
days_limit_import_messages: chatwoot_days_limit_import_messages ?? 60,
auto_create: true,
});
} catch (error) {
this.logger.log(error);
}
return {
instance: {
instanceName: instance.instanceName,
instanceId: instanceId,
integration: integration,
webhook_wa_business,
access_token_wa_business,
status: 'created',
},
hash,
webhook: {
webhook,
webhook_by_events,
webhook_base64,
events: webhookEvents,
},
websocket: {
enabled: websocket_enabled,
events: websocketEvents,
},
rabbitmq: {
enabled: rabbitmq_enabled,
events: rabbitmqEvents,
},
sqs: {
enabled: sqs_enabled,
events: sqsEvents,
},
typebot: {
enabled: typebot_url ? true : false,
url: typebot_url,
typebot,
expire: typebot_expire,
keyword_finish: typebot_keyword_finish,
delay_message: typebot_delay_message,
unknown_message: typebot_unknown_message,
listening_from_me: typebot_listening_from_me,
},
settings,
chatwoot: {
enabled: true,
account_id: chatwoot_account_id,
token: chatwoot_token,
url: chatwoot_url,
sign_msg: chatwoot_sign_msg || false,
reopen_conversation: chatwoot_reopen_conversation || false,
conversation_pending: chatwoot_conversation_pending || false,
import_contacts: chatwoot_import_contacts ?? true,
import_messages: chatwoot_import_messages ?? true,
days_limit_import_messages: chatwoot_days_limit_import_messages || 60,
number,
name_inbox: chatwoot_name_inbox ?? instance.instanceName,
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
},
};
} catch (error) {
this.logger.error(error.message[0]);
throw new BadRequestException(error.message[0]);
}
}
public async connectToWhatsapp({ instanceName, number = null, mobile = null }: InstanceDto) {
try {
this.logger.verbose('requested connectToWhatsapp from ' + instanceName + ' instance');
const instance = this.waMonitor.waInstances[instanceName];
const state = instance?.connectionStatus?.state;
this.logger.verbose('state: ' + state);
if (!state) {
throw new BadRequestException('The "' + instanceName + '" instance does not exist');
}
if (state == 'open') {
return await this.connectionState({ instanceName });
}
if (state == 'connecting') {
return instance.qrCode;
}
if (state == 'close') {
this.logger.verbose('connecting');
await instance.connectToWhatsapp(number, mobile);
await delay(5000);
return instance.qrCode;
}
return {
instance: {
instanceName: instanceName,
status: state,
},
qrcode: instance?.qrCode,
};
} catch (error) {
this.logger.error(error);
}
}
public async restartInstance({ instanceName }: InstanceDto) {
try {
this.logger.verbose('requested restartInstance from ' + instanceName + ' instance');
const instance = this.waMonitor.waInstances[instanceName];
const state = instance?.connectionStatus?.state;
switch (state) {
case 'open':
this.logger.verbose('logging out instance: ' + instanceName);
instance.clearCacheChatwoot();
await instance.reloadConnection();
await delay(2000);
return await this.connectionState({ instanceName });
default:
return await this.connectionState({ instanceName });
}
} catch (error) {
this.logger.error(error);
}
}
public async registerMobileCode({ instanceName }: InstanceDto, { mobileCode }: any) {
try {
this.logger.verbose('requested registerMobileCode from ' + instanceName + ' instance');
const instance = this.waMonitor.waInstances[instanceName];
console.log('mobileCode', mobileCode);
await instance.receiveMobileCode(mobileCode);
return { status: 'SUCCESS', error: false, response: { message: 'Mobile code registered' } };
} catch (error) {
this.logger.error(error);
}
}
public async connectionState({ instanceName }: InstanceDto) {
this.logger.verbose('requested connectionState from ' + instanceName + ' instance');
return {
instance: {
instanceName: instanceName,
state: this.waMonitor.waInstances[instanceName]?.connectionStatus?.state,
},
};
}
public async fetchInstances({ instanceName, instanceId, number }: InstanceDto) {
if (instanceName) {
this.logger.verbose('requested fetchInstances from ' + instanceName + ' instance');
this.logger.verbose('instanceName: ' + instanceName);
return this.waMonitor.instanceInfo(instanceName);
} else if (instanceId || number) {
return this.waMonitor.instanceInfoById(instanceId, number);
}
this.logger.verbose('requested fetchInstances (all instances)');
return this.waMonitor.instanceInfo();
}
public async setPresence({ instanceName }: InstanceDto, data: SetPresenceDto) {
this.logger.verbose('requested sendPresence from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].setPresence(data);
}
public async logout({ instanceName }: InstanceDto) {
this.logger.verbose('requested logout from ' + instanceName + ' instance');
const { instance } = await this.connectionState({ instanceName });
if (instance.state === 'close') {
throw new BadRequestException('The "' + instanceName + '" instance is not connected');
}
try {
this.waMonitor.waInstances[instanceName]?.logoutInstance();
return { status: 'SUCCESS', error: false, response: { message: 'Instance logged out' } };
} catch (error) {
throw new InternalServerErrorException(error.toString());
}
}
public async deleteInstance({ instanceName }: InstanceDto) {
this.logger.verbose('requested deleteInstance from ' + instanceName + ' instance');
const { instance } = await this.connectionState({ instanceName });
if (instance.state === 'open') {
throw new BadRequestException('The "' + instanceName + '" instance needs to be disconnected');
}
try {
this.waMonitor.waInstances[instanceName]?.removeRabbitmqQueues();
this.waMonitor.waInstances[instanceName]?.clearCacheChatwoot();
if (instance.state === 'connecting') {
this.logger.verbose('logging out instance: ' + instanceName);
await this.logout({ instanceName });
}
this.logger.verbose('deleting instance: ' + instanceName);
try {
this.waMonitor.waInstances[instanceName].sendDataWebhook(Events.INSTANCE_DELETE, {
instanceName,
instanceId: (await this.repository.auth.find(instanceName))?.instanceId,
});
} catch (error) {
this.logger.error(error);
}
delete this.waMonitor.waInstances[instanceName];
this.eventEmitter.emit('remove.instance', instanceName, 'inner');
return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } };
} catch (error) {
throw new BadRequestException(error.toString());
}
}
public async refreshToken(_: InstanceDto, oldToken: OldToken) {
this.logger.verbose('requested refreshToken');
return await this.authService.refreshToken(oldToken);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,72 @@
import { isURL } from 'class-validator';
import { Logger } from '../../config/logger.config';
import { BadRequestException } from '../../exceptions';
import { InstanceDto } from '../dto/instance.dto';
import { WebhookDto } from '../dto/webhook.dto';
import { WAMonitoringService } from '../services/monitor.service';
import { WebhookService } from '../services/webhook.service';
const logger = new Logger('WebhookController');
export class WebhookController {
constructor(private readonly webhookService: WebhookService, private readonly waMonitor: WAMonitoringService) {}
public async createWebhook(instance: InstanceDto, data: WebhookDto) {
logger.verbose('requested createWebhook from ' + instance.instanceName + ' instance');
if (!isURL(data.url, { require_tld: false })) {
throw new BadRequestException('Invalid "url" property');
}
data.enabled = data.enabled ?? true;
if (!data.enabled) {
logger.verbose('webhook disabled');
data.url = '';
data.events = [];
} else if (data.events.length === 0) {
logger.verbose('webhook events empty');
data.events = [
'APPLICATION_STARTUP',
'QRCODE_UPDATED',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_UPDATE',
'MESSAGES_DELETE',
'SEND_MESSAGE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'PRESENCE_UPDATE',
'CHATS_SET',
'CHATS_UPSERT',
'CHATS_UPDATE',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
'TYPEBOT_CHANGE_STATUS',
'CHAMA_AI_ACTION',
];
}
return this.webhookService.create(instance, data);
}
public async findWebhook(instance: InstanceDto) {
logger.verbose('requested findWebhook from ' + instance.instanceName + ' instance');
return this.webhookService.find(instance);
}
public async receiveWebhook(instance: InstanceDto, data: any) {
logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance');
return await this.waMonitor.waInstances[instance.instanceName].connectToWhatsapp(data);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,52 @@
import { WAPresence } from 'baileys';
import { ProxyDto } from './proxy.dto';
export class InstanceDto {
instanceName: string;
instanceId?: string;
qrcode?: boolean;
number?: string;
mobile?: boolean;
integration?: string;
token?: string;
webhook?: string;
webhook_by_events?: boolean;
webhook_base64?: boolean;
events?: string[];
reject_call?: boolean;
msg_call?: string;
groups_ignore?: boolean;
always_online?: boolean;
read_messages?: boolean;
read_status?: boolean;
sync_full_history?: boolean;
chatwoot_account_id?: string;
chatwoot_token?: string;
chatwoot_url?: string;
chatwoot_sign_msg?: boolean;
chatwoot_reopen_conversation?: boolean;
chatwoot_conversation_pending?: boolean;
chatwoot_import_contacts?: boolean;
chatwoot_import_messages?: boolean;
chatwoot_days_limit_import_messages?: number;
chatwoot_name_inbox?: string;
websocket_enabled?: boolean;
websocket_events?: string[];
rabbitmq_enabled?: boolean;
rabbitmq_events?: string[];
sqs_enabled?: boolean;
sqs_events?: string[];
typebot_url?: string;
typebot?: string;
typebot_expire?: number;
typebot_keyword_finish?: string;
typebot_delay_message?: number;
typebot_unknown_message?: string;
typebot_listening_from_me?: boolean;
proxy?: ProxyDto['proxy'];
}
export class SetPresenceDto {
presence: WAPresence;
}

View File

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

12
src/api/dto/label.dto.ts Normal file
View File

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

12
src/api/dto/proxy.dto.ts Normal file
View File

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

View File

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

View File

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

View File

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

View File

@@ -7,8 +7,8 @@ import { Auth, configService } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { ForbiddenException, UnauthorizedException } from '../../exceptions';
import { InstanceDto } from '../dto/instance.dto';
import { repository } from '../server.module';
import { JwtPayload } from '../services/auth.service';
import { repository } from '../whatsapp.module';
const logger = new Logger('GUARD');
@@ -55,7 +55,7 @@ async function jwtGuard(req: Request, res: Response, next: NextFunction) {
}
}
async function apikey(req: Request, res: Response, next: NextFunction) {
async function apikey(req: Request, _: Response, next: NextFunction) {
const env = configService.get<Auth>('AUTHENTICATION').API_KEY;
const key = req.get('apikey');

View File

@@ -2,33 +2,43 @@ import { NextFunction, Request, Response } from 'express';
import { existsSync } from 'fs';
import { join } from 'path';
import { configService, Database, Redis } from '../../config/env.config';
import { CacheConf, configService, Database } from '../../config/env.config';
import { INSTANCE_DIR } from '../../config/path.config';
import { dbserver } from '../../db/db.connect';
import { BadRequestException, ForbiddenException, NotFoundException } from '../../exceptions';
import {
BadRequestException,
ForbiddenException,
InternalServerErrorException,
NotFoundException,
} from '../../exceptions';
import { dbserver } from '../../libs/db.connect';
import { InstanceDto } from '../dto/instance.dto';
import { cache, waMonitor } from '../whatsapp.module';
import { cache, waMonitor } from '../server.module';
async function getInstance(instanceName: string) {
const db = configService.get<Database>('DATABASE');
const redisConf = configService.get<Redis>('REDIS');
try {
const db = configService.get<Database>('DATABASE');
const cacheConf = configService.get<CacheConf>('CACHE');
const exists = !!waMonitor.waInstances[instanceName];
const exists = !!waMonitor.waInstances[instanceName];
if (redisConf.ENABLED) {
const keyExists = await cache.keyExists();
return exists || keyExists;
if (cacheConf.REDIS.ENABLED && cacheConf.REDIS.SAVE_INSTANCES) {
const keyExists = await cache.has(instanceName);
return exists || keyExists;
}
if (db.ENABLED) {
const collection = dbserver
.getClient()
.db(db.CONNECTION.DB_PREFIX_NAME + '-instances')
.collection(instanceName);
return exists || (await collection.find({}).toArray()).length > 0;
}
return exists || existsSync(join(INSTANCE_DIR, instanceName));
} catch (error) {
throw new InternalServerErrorException(error?.toString());
}
if (db.ENABLED) {
const collection = dbserver
.getClient()
.db(db.CONNECTION.DB_PREFIX_NAME + '-instances')
.collection(instanceName);
return exists || (await collection.find({}).toArray()).length > 0;
}
return exists || existsSync(join(INSTANCE_DIR, instanceName));
}
export async function instanceExistsGuard(req: Request, _: Response, next: NextFunction) {
@@ -56,6 +66,7 @@ export async function instanceLoggedGuard(req: Request, _: Response, next: NextF
}
if (waMonitor.waInstances[instance.instanceName]) {
waMonitor.waInstances[instance.instanceName]?.removeRabbitmqQueues();
delete waMonitor.waInstances[instance.instanceName];
}
}

View File

@@ -0,0 +1,29 @@
import { Logger } from '../../../../config/logger.config';
import { InstanceDto } from '../../../dto/instance.dto';
import { ChamaaiDto } from '../dto/chamaai.dto';
import { ChamaaiService } from '../services/chamaai.service';
const logger = new Logger('ChamaaiController');
export class ChamaaiController {
constructor(private readonly chamaaiService: ChamaaiService) {}
public async createChamaai(instance: InstanceDto, data: ChamaaiDto) {
logger.verbose('requested createChamaai from ' + instance.instanceName + ' instance');
if (!data.enabled) {
logger.verbose('chamaai disabled');
data.url = '';
data.token = '';
data.waNumber = '';
data.answerByAudio = false;
}
return this.chamaaiService.create(instance, data);
}
public async findChamaai(instance: InstanceDto) {
logger.verbose('requested findChamaai from ' + instance.instanceName + ' instance');
return this.chamaaiService.find(instance);
}
}

View File

@@ -0,0 +1,7 @@
export class ChamaaiDto {
enabled: boolean;
url: string;
token: string;
waNumber: string;
answerByAudio: boolean;
}

View File

@@ -0,0 +1,24 @@
import { Schema } from 'mongoose';
import { dbserver } from '../../../../libs/db.connect';
export class ChamaaiRaw {
_id?: string;
enabled?: boolean;
url?: string;
token?: string;
waNumber?: string;
answerByAudio?: boolean;
}
const chamaaiSchema = new Schema<ChamaaiRaw>({
_id: { type: String, _id: true },
enabled: { type: Boolean, required: true },
url: { type: String, required: true },
token: { type: String, required: true },
waNumber: { type: String, required: true },
answerByAudio: { type: Boolean, required: true },
});
export const ChamaaiModel = dbserver?.model(ChamaaiRaw.name, chamaaiSchema, 'chamaai');
export type IChamaaiModel = typeof ChamaaiModel;

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 { ChamaaiRaw, IChamaaiModel } from '../../../models';
export class ChamaaiRepository extends Repository {
constructor(private readonly chamaaiModel: IChamaaiModel, private readonly configService: ConfigService) {
super(configService);
}
private readonly logger = new Logger('ChamaaiRepository');
public async create(data: ChamaaiRaw, instance: string): Promise<IInsert> {
try {
this.logger.verbose('creating chamaai');
if (this.dbSettings.ENABLED) {
this.logger.verbose('saving chamaai to db');
const insert = await this.chamaaiModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
this.logger.verbose('chamaai saved to db: ' + insert.modifiedCount + ' chamaai');
return { insertCount: insert.modifiedCount };
}
this.logger.verbose('saving chamaai to store');
this.writeStore<ChamaaiRaw>({
path: join(this.storePath, 'chamaai'),
fileName: instance,
data,
});
this.logger.verbose('chamaai saved to store in path: ' + join(this.storePath, 'chamaai') + '/' + instance);
this.logger.verbose('chamaai created');
return { insertCount: 1 };
} catch (error) {
return error;
}
}
public async find(instance: string): Promise<ChamaaiRaw> {
try {
this.logger.verbose('finding chamaai');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding chamaai in db');
return await this.chamaaiModel.findOne({ _id: instance });
}
this.logger.verbose('finding chamaai in store');
return JSON.parse(
readFileSync(join(this.storePath, 'chamaai', instance + '.json'), {
encoding: 'utf-8',
}),
) as ChamaaiRaw;
} catch (error) {
return {};
}
}
}

View File

@@ -0,0 +1,52 @@
import { RequestHandler, Router } from 'express';
import { Logger } from '../../../../config/logger.config';
import { chamaaiSchema, instanceNameSchema } from '../../../../validate/validate.schema';
import { RouterBroker } from '../../../abstract/abstract.router';
import { InstanceDto } from '../../../dto/instance.dto';
import { HttpStatus } from '../../../routes/index.router';
import { chamaaiController } from '../../../server.module';
import { ChamaaiDto } from '../dto/chamaai.dto';
const logger = new Logger('ChamaaiRouter');
export class ChamaaiRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.post(this.routerPath('set'), ...guards, async (req, res) => {
logger.verbose('request received in setChamaai');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<ChamaaiDto>({
request: req,
schema: chamaaiSchema,
ClassRef: ChamaaiDto,
execute: (instance, data) => chamaaiController.createChamaai(instance, data),
});
res.status(HttpStatus.CREATED).json(response);
})
.get(this.routerPath('find'), ...guards, async (req, res) => {
logger.verbose('request received in findChamaai');
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) => chamaaiController.findChamaai(instance),
});
res.status(HttpStatus.OK).json(response);
});
}
public readonly router = Router();
}

View File

@@ -0,0 +1,230 @@
import axios from 'axios';
import { writeFileSync } from 'fs';
import path from 'path';
import { ConfigService, HttpServer } from '../../../../config/env.config';
import { Logger } from '../../../../config/logger.config';
import { InstanceDto } from '../../../dto/instance.dto';
import { ChamaaiRaw } from '../../../models';
import { WAMonitoringService } from '../../../services/monitor.service';
import { Events } from '../../../types/wa.types';
import { ChamaaiDto } from '../dto/chamaai.dto';
export class ChamaaiService {
constructor(private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService) {}
private readonly logger = new Logger(ChamaaiService.name);
public create(instance: InstanceDto, data: ChamaaiDto) {
this.logger.verbose('create chamaai: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setChamaai(data);
return { chamaai: { ...instance, chamaai: data } };
}
public async find(instance: InstanceDto): Promise<ChamaaiRaw> {
try {
this.logger.verbose('find chamaai: ' + instance.instanceName);
const result = await this.waMonitor.waInstances[instance.instanceName].findChamaai();
if (Object.keys(result).length === 0) {
throw new Error('Chamaai not found');
}
return result;
} catch (error) {
return { enabled: false, url: '', token: '', waNumber: '', answerByAudio: false };
}
}
private getTypeMessage(msg: any) {
this.logger.verbose('get type message');
const types = {
conversation: msg.conversation,
extendedTextMessage: msg.extendedTextMessage?.text,
};
this.logger.verbose('type message: ' + types);
return types;
}
private getMessageContent(types: any) {
this.logger.verbose('get message content');
const typeKey = Object.keys(types).find((key) => types[key] !== undefined);
const result = typeKey ? types[typeKey] : undefined;
this.logger.verbose('message content: ' + result);
return result;
}
private getConversationMessage(msg: any) {
this.logger.verbose('get conversation message');
const types = this.getTypeMessage(msg);
const messageContent = this.getMessageContent(types);
this.logger.verbose('conversation message: ' + messageContent);
return messageContent;
}
private calculateTypingTime(text: string) {
const wordsPerMinute = 100;
const wordCount = text.split(' ').length;
const typingTimeInMinutes = wordCount / wordsPerMinute;
const typingTimeInMilliseconds = typingTimeInMinutes * 60;
return typingTimeInMilliseconds;
}
private convertToMilliseconds(count: number) {
const averageCharactersPerSecond = 15;
const characterCount = count;
const speakingTimeInSeconds = characterCount / averageCharactersPerSecond;
return speakingTimeInSeconds;
}
private getRegexPatterns() {
const patternsToCheck = [
'.*atend.*humano.*',
'.*falar.*com.*um.*humano.*',
'.*fala.*humano.*',
'.*atend.*humano.*',
'.*fala.*atend.*',
'.*preciso.*ajuda.*',
'.*quero.*suporte.*',
'.*preciso.*assiste.*',
'.*ajuda.*atend.*',
'.*chama.*atendente.*',
'.*suporte.*urgente.*',
'.*atend.*por.*favor.*',
'.*quero.*falar.*com.*alguém.*',
'.*falar.*com.*um.*humano.*',
'.*transfer.*humano.*',
'.*transfer.*atend.*',
'.*equipe.*humano.*',
'.*suporte.*humano.*',
];
const regexPatterns = patternsToCheck.map((pattern) => new RegExp(pattern, 'iu'));
return regexPatterns;
}
public async sendChamaai(instance: InstanceDto, remoteJid: string, msg: any) {
const content = this.getConversationMessage(msg.message);
const msgType = msg.messageType;
const find = await this.find(instance);
const url = find.url;
const token = find.token;
const waNumber = find.waNumber;
const answerByAudio = find.answerByAudio;
if (!content && msgType !== 'audioMessage') {
return;
}
let data;
let endpoint;
if (msgType === 'audioMessage') {
const downloadBase64 = await this.waMonitor.waInstances[instance.instanceName].getBase64FromMediaMessage({
message: {
...msg,
},
});
const random = Math.random().toString(36).substring(7);
const nameFile = `${random}.ogg`;
const fileData = Buffer.from(downloadBase64.base64, 'base64');
const fileName = `${path.join(
this.waMonitor.waInstances[instance.instanceName].storePath,
'temp',
`${nameFile}`,
)}`;
writeFileSync(fileName, fileData, 'utf8');
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
const url = `${urlServer}/store/temp/${nameFile}`;
data = {
waNumber: waNumber,
audioUrl: url,
queryNumber: remoteJid.split('@')[0],
answerByAudio: answerByAudio,
};
endpoint = 'processMessageAudio';
} else {
data = {
waNumber: waNumber,
question: content,
queryNumber: remoteJid.split('@')[0],
answerByAudio: answerByAudio,
};
endpoint = 'processMessageText';
}
const request = await axios.post(`${url}/${endpoint}`, data, {
headers: {
Authorization: `${token}`,
},
});
const answer = request.data?.answer;
const type = request.data?.type;
const characterCount = request.data?.characterCount;
if (answer) {
if (type === 'text') {
this.waMonitor.waInstances[instance.instanceName].textMessage({
number: remoteJid.split('@')[0],
options: {
delay: this.calculateTypingTime(answer) * 1000 || 1000,
presence: 'composing',
linkPreview: false,
quoted: {
key: msg.key,
message: msg.message,
},
},
textMessage: {
text: answer,
},
});
}
if (type === 'audio') {
this.waMonitor.waInstances[instance.instanceName].audioWhatsapp({
number: remoteJid.split('@')[0],
options: {
delay: characterCount ? this.convertToMilliseconds(characterCount) * 1000 || 1000 : 1000,
presence: 'recording',
encoding: true,
},
audioMessage: {
audio: answer,
},
});
}
if (this.getRegexPatterns().some((pattern) => pattern.test(answer))) {
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.CHAMA_AI_ACTION, {
remoteJid: remoteJid,
message: msg,
answer: answer,
action: 'transfer',
});
}
}
}
}

View File

@@ -0,0 +1,35 @@
import { JSONSchema7 } from 'json-schema';
import { v4 } from 'uuid';
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
const properties = {};
propertyNames.forEach(
(property) =>
(properties[property] = {
minLength: 1,
description: `The "${property}" cannot be empty`,
}),
);
return {
if: {
propertyNames: {
enum: [...propertyNames],
},
},
then: { properties },
};
};
export const chamaaiSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
enabled: { type: 'boolean', enum: [true, false] },
url: { type: 'string' },
token: { type: 'string' },
waNumber: { type: 'string' },
answerByAudio: { type: 'boolean', enum: [true, false] },
},
required: ['enabled', 'url', 'token', 'waNumber', 'answerByAudio'],
...isNotEmpty('enabled', 'url', 'token', 'waNumber', 'answerByAudio'),
};

View File

@@ -1,17 +1,24 @@
import { isURL } from 'class-validator';
import { ConfigService, HttpServer } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { BadRequestException } from '../../exceptions';
import { CacheEngine } from '../../../../cache/cacheengine';
import { ConfigService, HttpServer } from '../../../../config/env.config';
import { Logger } from '../../../../config/logger.config';
import { BadRequestException } from '../../../../exceptions';
import { InstanceDto } from '../../../dto/instance.dto';
import { RepositoryBroker } from '../../../repository/repository.manager';
import { waMonitor } from '../../../server.module';
import { CacheService } from '../../../services/cache.service';
import { ChatwootDto } from '../dto/chatwoot.dto';
import { InstanceDto } from '../dto/instance.dto';
import { ChatwootService } from '../services/chatwoot.service';
import { waMonitor } from '../whatsapp.module';
const logger = new Logger('ChatwootController');
export class ChatwootController {
constructor(private readonly chatwootService: ChatwootService, private readonly configService: ConfigService) {}
constructor(
private readonly chatwootService: ChatwootService,
private readonly configService: ConfigService,
private readonly repository: RepositoryBroker,
) {}
public async createChatwoot(instance: InstanceDto, data: ChatwootDto) {
logger.verbose('requested createChatwoot from ' + instance.instanceName + ' instance');
@@ -32,6 +39,7 @@ export class ChatwootController {
if (data.sign_msg !== true && data.sign_msg !== false) {
throw new BadRequestException('sign_msg is required');
}
if (data.sign_msg === false) data.sign_delimiter = null;
}
if (!data.enabled) {
@@ -40,19 +48,27 @@ export class ChatwootController {
data.token = '';
data.url = '';
data.sign_msg = false;
data.sign_delimiter = null;
data.reopen_conversation = false;
data.conversation_pending = false;
data.import_contacts = false;
data.import_messages = false;
data.days_limit_import_messages = 0;
data.auto_create = false;
data.name_inbox = '';
}
data.name_inbox = instance.instanceName;
if (!data.name_inbox || data.name_inbox === '') {
data.name_inbox = instance.instanceName;
}
const result = this.chatwootService.create(instance, data);
const result = await this.chatwootService.create(instance, data);
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
const response = {
...result,
webhook_url: `${urlServer}/chatwoot/webhook/${instance.instanceName}`,
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
};
return response;
@@ -64,7 +80,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: '',
@@ -78,7 +94,7 @@ export class ChatwootController {
const response = {
...result,
webhook_url: `${urlServer}/chatwoot/webhook/${instance.instanceName}`,
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
};
return response;
@@ -86,14 +102,10 @@ export class ChatwootController {
public async receiveWebhook(instance: InstanceDto, data: any) {
logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance');
const chatwootService = new ChatwootService(waMonitor, this.configService);
const chatwootCache = new CacheService(new CacheEngine(this.configService, ChatwootService.name).getEngine());
const chatwootService = new ChatwootService(waMonitor, this.configService, this.repository, chatwootCache);
return chatwootService.receiveWebhook(instance, data);
}
public async newInstance(data: any) {
const chatwootService = new ChatwootService(waMonitor, this.configService);
return chatwootService.newInstance(data);
}
}

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
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 { ChatwootRaw, IChatwootModel } from '../models';
import { ConfigService } from '../../../../config/env.config';
import { Logger } from '../../../../config/logger.config';
import { IInsert, Repository } from '../../../abstract/abstract.repository';
import { ChatwootRaw, IChatwootModel } from '../../../models';
export class ChatwootRepository extends Repository {
constructor(private readonly chatwootModel: IChatwootModel, private readonly configService: ConfigService) {

View File

@@ -1,13 +1,12 @@
import { RequestHandler, Router } from 'express';
import { Logger } from '../../config/logger.config';
import { chatwootSchema, instanceNameSchema } from '../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router';
import { Logger } from '../../../../config/logger.config';
import { chatwootSchema, instanceNameSchema } from '../../../../validate/validate.schema';
import { RouterBroker } from '../../../abstract/abstract.router';
import { InstanceDto } from '../../../dto/instance.dto';
import { HttpStatus } from '../../../routes/index.router';
import { chatwootController } from '../../../server.module';
import { ChatwootDto } from '../dto/chatwoot.dto';
import { InstanceDto } from '../dto/instance.dto';
// import { ChatwootService } from '../services/chatwoot.service';
import { chatwootController } from '../whatsapp.module';
import { HttpStatus } from './index.router';
const logger = new Logger('ChatwootRouter');

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,43 @@
import { JSONSchema7 } from 'json-schema';
import { v4 } from 'uuid';
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
const properties = {};
propertyNames.forEach(
(property) =>
(properties[property] = {
minLength: 1,
description: `The "${property}" cannot be empty`,
}),
);
return {
if: {
propertyNames: {
enum: [...propertyNames],
},
},
then: { properties },
};
};
export const chatwootSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
enabled: { type: 'boolean', enum: [true, false] },
account_id: { type: 'string' },
token: { type: 'string' },
url: { type: 'string' },
sign_msg: { type: 'boolean', enum: [true, false] },
sign_delimiter: { type: ['string', 'null'] },
name_inbox: { type: ['string', 'null'] },
reopen_conversation: { type: 'boolean', enum: [true, false] },
conversation_pending: { type: 'boolean', enum: [true, false] },
auto_create: { type: 'boolean', enum: [true, false] },
import_contacts: { type: 'boolean', enum: [true, false] },
import_messages: { type: 'boolean', enum: [true, false] },
days_limit_import_messages: { type: 'number' },
},
required: ['enabled', 'account_id', 'token', 'url', 'sign_msg', 'reopen_conversation', 'conversation_pending'],
...isNotEmpty('account_id', 'token', 'url', 'sign_msg', 'reopen_conversation', 'conversation_pending'),
};

View File

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

View File

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

View File

@@ -0,0 +1,100 @@
import * as amqp from 'amqplib/callback_api';
import { configService, Rabbitmq } from '../../../../config/env.config';
import { Logger } from '../../../../config/logger.config';
const logger = new Logger('AMQP');
let amqpChannel: amqp.Channel | null = null;
export const initAMQP = () => {
return new Promise<void>((resolve, reject) => {
const uri = configService.get<Rabbitmq>('RABBITMQ').URI;
amqp.connect(uri, (error, connection) => {
if (error) {
reject(error);
return;
}
connection.createChannel((channelError, channel) => {
if (channelError) {
reject(channelError);
return;
}
const exchangeName = 'evolution_exchange';
channel.assertExchange(exchangeName, 'topic', {
durable: true,
autoDelete: false,
});
amqpChannel = channel;
logger.info('AMQP initialized');
resolve();
});
});
});
};
export const getAMQP = (): amqp.Channel | null => {
return amqpChannel;
};
export const initQueues = (instanceName: string, events: string[]) => {
if (!events || !events.length) return;
const queues = events.map((event) => {
return `${event.replace(/_/g, '.').toLowerCase()}`;
});
queues.forEach((event) => {
const amqp = getAMQP();
const exchangeName = instanceName ?? 'evolution_exchange';
amqp.assertExchange(exchangeName, 'topic', {
durable: true,
autoDelete: false,
});
const queueName = `${instanceName}.${event}`;
amqp.assertQueue(queueName, {
durable: true,
autoDelete: false,
arguments: {
'x-queue-type': 'quorum',
},
});
amqp.bindQueue(queueName, exchangeName, event);
});
};
export const removeQueues = (instanceName: string, events: string[]) => {
if (!events || !events.length) return;
const channel = getAMQP();
const queues = events.map((event) => {
return `${event.replace(/_/g, '.').toLowerCase()}`;
});
const exchangeName = instanceName ?? 'evolution_exchange';
queues.forEach((event) => {
const amqp = getAMQP();
amqp.assertExchange(exchangeName, 'topic', {
durable: true,
autoDelete: false,
});
const queueName = `${instanceName}.${event}`;
amqp.deleteQueue(queueName);
});
channel.deleteExchange(exchangeName);
};

View File

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

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 { IRabbitmqModel, RabbitmqRaw } from '../../../models';
export class RabbitmqRepository extends Repository {
constructor(private readonly rabbitmqModel: IRabbitmqModel, private readonly configService: ConfigService) {
super(configService);
}
private readonly logger = new Logger('RabbitmqRepository');
public async create(data: RabbitmqRaw, instance: string): Promise<IInsert> {
try {
this.logger.verbose('creating rabbitmq');
if (this.dbSettings.ENABLED) {
this.logger.verbose('saving rabbitmq to db');
const insert = await this.rabbitmqModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
this.logger.verbose('rabbitmq saved to db: ' + insert.modifiedCount + ' rabbitmq');
return { insertCount: insert.modifiedCount };
}
this.logger.verbose('saving rabbitmq to store');
this.writeStore<RabbitmqRaw>({
path: join(this.storePath, 'rabbitmq'),
fileName: instance,
data,
});
this.logger.verbose('rabbitmq saved to store in path: ' + join(this.storePath, 'rabbitmq') + '/' + instance);
this.logger.verbose('rabbitmq created');
return { insertCount: 1 };
} catch (error) {
return error;
}
}
public async find(instance: string): Promise<RabbitmqRaw> {
try {
this.logger.verbose('finding rabbitmq');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding rabbitmq in db');
return await this.rabbitmqModel.findOne({ _id: instance });
}
this.logger.verbose('finding rabbitmq in store');
return JSON.parse(
readFileSync(join(this.storePath, 'rabbitmq', instance + '.json'), {
encoding: 'utf-8',
}),
) as RabbitmqRaw;
} catch (error) {
return {};
}
}
}

View File

@@ -0,0 +1,52 @@
import { RequestHandler, Router } from 'express';
import { Logger } from '../../../../config/logger.config';
import { instanceNameSchema, rabbitmqSchema } from '../../../../validate/validate.schema';
import { RouterBroker } from '../../../abstract/abstract.router';
import { InstanceDto } from '../../../dto/instance.dto';
import { HttpStatus } from '../../../routes/index.router';
import { rabbitmqController } from '../../../server.module';
import { RabbitmqDto } from '../dto/rabbitmq.dto';
const logger = new Logger('RabbitmqRouter');
export class RabbitmqRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.post(this.routerPath('set'), ...guards, async (req, res) => {
logger.verbose('request received in setRabbitmq');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<RabbitmqDto>({
request: req,
schema: rabbitmqSchema,
ClassRef: RabbitmqDto,
execute: (instance, data) => rabbitmqController.createRabbitmq(instance, data),
});
res.status(HttpStatus.CREATED).json(response);
})
.get(this.routerPath('find'), ...guards, async (req, res) => {
logger.verbose('request received in findRabbitmq');
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) => rabbitmqController.findRabbitmq(instance),
});
res.status(HttpStatus.OK).json(response);
});
}
public readonly router = Router();
}

View File

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

View File

@@ -0,0 +1,66 @@
import { JSONSchema7 } from 'json-schema';
import { v4 } from 'uuid';
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
const properties = {};
propertyNames.forEach(
(property) =>
(properties[property] = {
minLength: 1,
description: `The "${property}" cannot be empty`,
}),
);
return {
if: {
propertyNames: {
enum: [...propertyNames],
},
},
then: { properties },
};
};
export const rabbitmqSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
enabled: { type: 'boolean', enum: [true, false] },
events: {
type: 'array',
minItems: 0,
items: {
type: 'string',
enum: [
'APPLICATION_STARTUP',
'QRCODE_UPDATED',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_UPDATE',
'MESSAGES_DELETE',
'SEND_MESSAGE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'PRESENCE_UPDATE',
'CHATS_SET',
'CHATS_UPSERT',
'CHATS_UPDATE',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
'TYPEBOT_CHANGE_STATUS',
'CHAMA_AI_ACTION',
],
},
},
},
required: ['enabled'],
...isNotEmpty('enabled'),
};

View File

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

View File

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

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

@@ -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

@@ -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

@@ -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 { HttpStatus } from '../../../routes/index.router';
import { sqsController } from '../../../server.module';
import { SqsDto } from '../dto/sqs.dto';
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

@@ -0,0 +1,35 @@
import { Logger } from '../../../../config/logger.config';
import { InstanceDto } from '../../../dto/instance.dto';
import { SqsRaw } from '../../../models';
import { WAMonitoringService } from '../../../services/monitor.service';
import { SqsDto } from '../dto/sqs.dto';
import { initQueues } from '../libs/sqs.server';
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

@@ -0,0 +1,66 @@
import { JSONSchema7 } from 'json-schema';
import { v4 } from 'uuid';
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
const properties = {};
propertyNames.forEach(
(property) =>
(properties[property] = {
minLength: 1,
description: `The "${property}" cannot be empty`,
}),
);
return {
if: {
propertyNames: {
enum: [...propertyNames],
},
},
then: { properties },
};
};
export const sqsSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
enabled: { type: 'boolean', enum: [true, false] },
events: {
type: 'array',
minItems: 0,
items: {
type: 'string',
enum: [
'APPLICATION_STARTUP',
'QRCODE_UPDATED',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_UPDATE',
'MESSAGES_DELETE',
'SEND_MESSAGE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'PRESENCE_UPDATE',
'CHATS_SET',
'CHATS_UPSERT',
'CHATS_UPDATE',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
'TYPEBOT_CHANGE_STATUS',
'CHAMA_AI_ACTION',
],
},
},
},
required: ['enabled'],
...isNotEmpty('enabled'),
};

View File

@@ -0,0 +1,46 @@
import { Logger } from '../../../../config/logger.config';
import { InstanceDto } from '../../../dto/instance.dto';
import { TypebotDto } from '../dto/typebot.dto';
import { TypebotService } from '../services/typebot.service';
const logger = new Logger('TypebotController');
export class TypebotController {
constructor(private readonly typebotService: TypebotService) {}
public async createTypebot(instance: InstanceDto, data: TypebotDto) {
logger.verbose('requested createTypebot from ' + instance.instanceName + ' instance');
if (!data.enabled) {
logger.verbose('typebot disabled');
data.url = '';
data.typebot = '';
data.expire = 0;
data.sessions = [];
} else {
const saveData = await this.typebotService.find(instance);
if (saveData.enabled) {
logger.verbose('typebot enabled');
data.sessions = saveData.sessions;
}
}
return this.typebotService.create(instance, data);
}
public async findTypebot(instance: InstanceDto) {
logger.verbose('requested findTypebot from ' + instance.instanceName + ' instance');
return this.typebotService.find(instance);
}
public async changeStatus(instance: InstanceDto, data: any) {
logger.verbose('requested changeStatus from ' + instance.instanceName + ' instance');
return this.typebotService.changeStatus(instance, data);
}
public async startTypebot(instance: InstanceDto, data: any) {
logger.verbose('requested startTypebot from ' + instance.instanceName + ' instance');
return this.typebotService.startTypebot(instance, data);
}
}

View File

@@ -0,0 +1,27 @@
export class Session {
remoteJid?: string;
sessionId?: string;
status?: string;
createdAt?: number;
updateAt?: number;
prefilledVariables?: PrefilledVariables;
}
export class PrefilledVariables {
remoteJid?: string;
pushName?: string;
messageType?: string;
additionalData?: { [key: string]: any };
}
export class TypebotDto {
enabled?: boolean;
url: string;
typebot?: string;
expire?: number;
keyword_finish?: string;
delay_message?: number;
unknown_message?: string;
listening_from_me?: boolean;
sessions?: Session[];
}

View File

@@ -0,0 +1,58 @@
import { Schema } from 'mongoose';
import { dbserver } from '../../../../libs/db.connect';
class Session {
remoteJid?: string;
sessionId?: string;
status?: string;
createdAt?: number;
updateAt?: number;
prefilledVariables?: {
remoteJid?: string;
pushName?: string;
additionalData?: { [key: string]: any };
};
}
export class TypebotRaw {
_id?: string;
enabled?: boolean;
url: string;
typebot?: string;
expire?: number;
keyword_finish?: string;
delay_message?: number;
unknown_message?: string;
listening_from_me?: boolean;
sessions?: Session[];
}
const typebotSchema = new Schema<TypebotRaw>({
_id: { type: String, _id: true },
enabled: { type: Boolean, required: true },
url: { type: String, required: true },
typebot: { type: String, required: true },
expire: { type: Number, required: true },
keyword_finish: { type: String, required: true },
delay_message: { type: Number, required: true },
unknown_message: { type: String, required: true },
listening_from_me: { type: Boolean, required: true },
sessions: [
{
remoteJid: { type: String, required: true },
sessionId: { type: String, required: true },
status: { type: String, required: true },
createdAt: { type: Number, required: true },
updateAt: { type: Number, required: true },
prefilledVariables: {
remoteJid: { type: String, required: false },
pushName: { type: String, required: false },
additionalData: { type: Schema.Types.Mixed, required: false },
},
},
],
});
export const TypebotModel = dbserver?.model(TypebotRaw.name, typebotSchema, 'typebot');
export type ITypebotModel = typeof TypebotModel;

View File

@@ -0,0 +1,68 @@
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 { ITypebotModel, TypebotRaw } from '../../../models';
export class TypebotRepository extends Repository {
constructor(private readonly typebotModel: ITypebotModel, private readonly configService: ConfigService) {
super(configService);
}
private readonly logger = new Logger('TypebotRepository');
public async create(data: TypebotRaw, instance: string): Promise<IInsert> {
try {
this.logger.verbose('creating typebot');
if (this.dbSettings.ENABLED) {
this.logger.verbose('saving typebot to db');
const insert = await this.typebotModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
this.logger.verbose('typebot saved to db: ' + insert.modifiedCount + ' typebot');
return { insertCount: insert.modifiedCount };
}
this.logger.verbose('saving typebot to store');
this.writeStore<TypebotRaw>({
path: join(this.storePath, 'typebot'),
fileName: instance,
data,
});
this.logger.verbose('typebot saved to store in path: ' + join(this.storePath, 'typebot') + '/' + instance);
this.logger.verbose('typebot created');
return { insertCount: 1 };
} catch (error) {
return error;
}
}
public async find(instance: string): Promise<TypebotRaw> {
try {
this.logger.verbose('finding typebot');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding typebot in db');
return await this.typebotModel.findOne({ _id: instance });
}
this.logger.verbose('finding typebot in store');
return JSON.parse(
readFileSync(join(this.storePath, 'typebot', instance + '.json'), {
encoding: 'utf-8',
}),
) as TypebotRaw;
} catch (error) {
return {
enabled: false,
url: '',
typebot: '',
expire: 0,
sessions: [],
};
}
}
}

View File

@@ -0,0 +1,89 @@
import { RequestHandler, Router } from 'express';
import { Logger } from '../../../../config/logger.config';
import {
instanceNameSchema,
typebotSchema,
typebotStartSchema,
typebotStatusSchema,
} from '../../../../validate/validate.schema';
import { RouterBroker } from '../../../abstract/abstract.router';
import { InstanceDto } from '../../../dto/instance.dto';
import { HttpStatus } from '../../../routes/index.router';
import { typebotController } from '../../../server.module';
import { TypebotDto } from '../dto/typebot.dto';
const logger = new Logger('TypebotRouter');
export class TypebotRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.post(this.routerPath('set'), ...guards, async (req, res) => {
logger.verbose('request received in setTypebot');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<TypebotDto>({
request: req,
schema: typebotSchema,
ClassRef: TypebotDto,
execute: (instance, data) => typebotController.createTypebot(instance, data),
});
res.status(HttpStatus.CREATED).json(response);
})
.get(this.routerPath('find'), ...guards, async (req, res) => {
logger.verbose('request received in findTypebot');
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) => typebotController.findTypebot(instance),
});
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('changeStatus'), ...guards, async (req, res) => {
logger.verbose('request received in changeStatusTypebot');
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: typebotStatusSchema,
ClassRef: InstanceDto,
execute: (instance, data) => typebotController.changeStatus(instance, data),
});
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('start'), ...guards, async (req, res) => {
logger.verbose('request received in startTypebot');
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: typebotStartSchema,
ClassRef: InstanceDto,
execute: (instance, data) => typebotController.startTypebot(instance, data),
});
res.status(HttpStatus.OK).json(response);
});
}
public readonly router = Router();
}

View File

@@ -0,0 +1,966 @@
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 { MessageRaw } from '../../../models';
import { WAMonitoringService } from '../../../services/monitor.service';
import { Events } from '../../../types/wa.types';
import { Session, TypebotDto } from '../dto/typebot.dto';
export class TypebotService {
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);
public create(instance: InstanceDto, data: TypebotDto) {
this.logger.verbose('create typebot: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setTypebot(data);
return { typebot: { ...instance, typebot: data } };
}
public async find(instance: InstanceDto): Promise<TypebotDto> {
try {
this.logger.verbose('find typebot: ' + instance.instanceName);
const result = await this.waMonitor.waInstances[instance.instanceName].findTypebot();
if (Object.keys(result).length === 0) {
throw new Error('Typebot not found');
}
return result;
} catch (error) {
return { enabled: false, url: '', typebot: '', expire: 0, sessions: [] };
}
}
public async changeStatus(instance: InstanceDto, data: any) {
const remoteJid = data.remoteJid;
const status = data.status;
const findData = await this.find(instance);
const session = findData.sessions.find((session) => session.remoteJid === remoteJid);
if (session) {
if (status === 'closed') {
findData.sessions.splice(findData.sessions.indexOf(session), 1);
const typebotData = {
enabled: findData.enabled,
url: findData.url,
typebot: findData.typebot,
expire: findData.expire,
keyword_finish: findData.keyword_finish,
delay_message: findData.delay_message,
unknown_message: findData.unknown_message,
listening_from_me: findData.listening_from_me,
sessions: findData.sessions,
};
this.create(instance, typebotData);
return { typebot: { ...instance, typebot: typebotData } };
}
findData.sessions.map((session) => {
if (session.remoteJid === remoteJid) {
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: findData.enabled,
url: findData.url,
typebot: findData.typebot,
expire: findData.expire,
keyword_finish: findData.keyword_finish,
delay_message: findData.delay_message,
unknown_message: findData.unknown_message,
listening_from_me: findData.listening_from_me,
sessions: findData.sessions,
};
this.create(instance, typebotData);
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_CHANGE_STATUS, {
remoteJid: remoteJid,
status: status,
url: findData.url,
typebot: findData.typebot,
session,
});
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 expire = findTypebot.expire;
const keyword_finish = findTypebot.keyword_finish;
const delay_message = findTypebot.delay_message;
const unknown_message = findTypebot.unknown_message;
const listening_from_me = findTypebot.listening_from_me;
const prefilledVariables = {
remoteJid: remoteJid,
instanceName: instance.instanceName,
};
if (variables?.length) {
variables.forEach((variable: { name: string | number; value: string }) => {
prefilledVariables[variable.name] = variable.value;
});
}
if (startSession) {
const newSessions = await this.clearSessions(instance, remoteJid);
const response = await this.createNewSession(instance, {
enabled: findTypebot.enabled,
url: url,
typebot: typebot,
remoteJid: remoteJid,
expire: expire,
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
listening_from_me: listening_from_me,
sessions: newSessions,
prefilledVariables: prefilledVariables,
});
if (response.sessionId) {
await this.sendWAMessage(instance, remoteJid, response.messages, response.input, response.clientSideActions);
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, {
remoteJid: remoteJid,
url: url,
typebot: typebot,
prefilledVariables: prefilledVariables,
sessionId: `${response.sessionId}`,
});
} else {
throw new Error('Session ID not found in response');
}
} else {
const id = Math.floor(Math.random() * 10000000000).toString();
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: {
publicId: data.typebot,
prefilledVariables: prefilledVariables,
},
};
}
const request = await axios.post(url, reqData);
await this.sendWAMessage(
instance,
remoteJid,
request.data.messages,
request.data.input,
request.data.clientSideActions,
);
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, {
remoteJid: remoteJid,
url: url,
typebot: typebot,
variables: variables,
sessionId: id,
});
} catch (error) {
this.logger.error(error);
return;
}
}
return {
typebot: {
...instance,
typebot: {
url: url,
remoteJid: remoteJid,
typebot: typebot,
prefilledVariables: prefilledVariables,
},
},
};
}
private getTypeMessage(msg: any) {
this.logger.verbose('get type message');
const types = {
conversation: msg.conversation,
extendedTextMessage: msg.extendedTextMessage?.text,
audioMessage: msg.audioMessage?.url,
imageMessage: msg.imageMessage?.url,
videoMessage: msg.videoMessage?.url,
documentMessage: msg.documentMessage?.fileName,
contactMessage: msg.contactMessage?.displayName,
locationMessage: msg.locationMessage?.degreesLatitude,
viewOnceMessageV2:
msg.viewOnceMessageV2?.message?.imageMessage?.url ||
msg.viewOnceMessageV2?.message?.videoMessage?.url ||
msg.viewOnceMessageV2?.message?.audioMessage?.url,
listResponseMessage: msg.listResponseMessage?.singleSelectReply?.selectedRowId,
responseRowId: msg.listResponseMessage?.singleSelectReply?.selectedRowId,
};
const messageType = Object.keys(types).find((key) => types[key] !== undefined) || 'unknown';
this.logger.verbose('Type message: ' + JSON.stringify(types));
return { ...types, messageType };
}
private getMessageContent(types: any) {
this.logger.verbose('get message content');
const typeKey = Object.keys(types).find((key) => types[key] !== undefined);
const result = typeKey ? types[typeKey] : undefined;
this.logger.verbose('message content: ' + result);
return result;
}
private getConversationMessage(msg: any) {
this.logger.verbose('get conversation message');
const types = this.getTypeMessage(msg);
const messageContent = this.getMessageContent(types);
this.logger.verbose('conversation message: ' + messageContent);
return messageContent;
}
private getAudioMessageContent(msg: any) {
this.logger.verbose('get audio message content');
const types = this.getTypeMessage(msg);
const audioContent = types.audioMessage;
this.logger.verbose('audio message URL: ' + audioContent);
return audioContent;
}
private getImageMessageContent(msg: any) {
this.logger.verbose('get image message content');
const types = this.getTypeMessage(msg);
const imageContent = types.imageMessage;
this.logger.verbose('image message URL: ' + imageContent);
return imageContent;
}
private getVideoMessageContent(msg: any) {
this.logger.verbose('get video message content');
const types = this.getTypeMessage(msg);
const videoContent = types.videoMessage;
this.logger.verbose('video message URL: ' + videoContent);
return videoContent;
}
private getDocumentMessageContent(msg: any) {
this.logger.verbose('get document message content');
const types = this.getTypeMessage(msg);
const documentContent = types.documentMessage;
this.logger.verbose('document message fileName: ' + documentContent);
return documentContent;
}
private getContactMessageContent(msg: any) {
this.logger.verbose('get contact message content');
const types = this.getTypeMessage(msg);
const contactContent = types.contactMessage;
this.logger.verbose('contact message displayName: ' + contactContent);
return contactContent;
}
private getLocationMessageContent(msg: any) {
this.logger.verbose('get location message content');
const types = this.getTypeMessage(msg);
const locationContent = types.locationMessage;
this.logger.verbose('location message degreesLatitude: ' + locationContent);
return locationContent;
}
private getViewOnceMessageV2Content(msg: any) {
this.logger.verbose('get viewOnceMessageV2 content');
const types = this.getTypeMessage(msg);
const viewOnceContent = types.viewOnceMessageV2;
this.logger.verbose('viewOnceMessageV2 URL: ' + viewOnceContent);
return viewOnceContent;
}
private getListResponseMessageContent(msg: any) {
this.logger.verbose('get listResponseMessage content');
const types = this.getTypeMessage(msg);
const listResponseContent = types.listResponseMessage || types.responseRowId;
this.logger.verbose('listResponseMessage selectedRowId: ' + listResponseContent);
return listResponseContent;
}
public async createNewSession(instance: InstanceDto, data: any) {
if (data.remoteJid === 'status@broadcast') return;
const id = Math.floor(Math.random() * 10000000000).toString();
try {
const version = this.configService.get<Typebot>('TYPEBOT').API_VERSION;
let url: string;
let reqData: {};
if (version === 'latest') {
url = `${data.url}/api/v1/typebots/${data.typebot}/startChat`;
reqData = {
prefilledVariables: {
...data.prefilledVariables,
remoteJid: data.remoteJid,
pushName: data.pushName || data.prefilledVariables?.pushName || '',
instanceName: instance.instanceName,
},
};
} else {
url = `${data.url}/api/v1/sendMessage`;
reqData = {
startParams: {
publicId: data.typebot,
prefilledVariables: {
...data.prefilledVariables,
remoteJid: data.remoteJid,
pushName: data.pushName || data.prefilledVariables?.pushName || '',
instanceName: instance.instanceName,
},
},
};
}
const request = await axios.post(url, reqData);
if (request?.data?.sessionId) {
data.sessions.push({
remoteJid: data.remoteJid,
sessionId: `${id}-${request.data.sessionId}`,
status: 'opened',
createdAt: Date.now(),
updateAt: Date.now(),
prefilledVariables: {
...data.prefilledVariables,
remoteJid: data.remoteJid,
pushName: data.pushName || '',
instanceName: instance.instanceName,
},
});
const typebotData = {
enabled: 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,
};
this.create(instance, typebotData);
}
return request.data;
} catch (error) {
this.logger.error(error);
return;
}
}
public async sendWAMessage(
instance: InstanceDto,
remoteJid: string,
messages: any[],
input: any[],
clientSideActions: any[],
) {
processMessages(
this.waMonitor.waInstances[instance.instanceName],
messages,
input,
clientSideActions,
this.eventEmitter,
applyFormatting,
).catch((err) => {
console.error('Erro ao processar mensagens:', err);
});
function findItemAndGetSecondsToWait(array, targetId) {
if (!array) return null;
for (const item of array) {
if (item.lastBubbleBlockId === targetId) {
return item.wait?.secondsToWaitFor;
}
}
return null;
}
function applyFormatting(element) {
let text = '';
if (element.text) {
text += element.text;
}
if (
element.children &&
(element.type === 'p' ||
element.type === 'a' ||
element.type === 'inline-variable' ||
element.type === 'variable')
) {
for (const child of element.children) {
text += applyFormatting(child);
}
}
let formats = '';
if (element.bold) {
formats += '*';
}
if (element.italic) {
formats += '_';
}
if (element.underline) {
formats += '~';
}
let formattedText = `${formats}${text}${formats.split('').reverse().join('')}`;
if (element.url) {
formattedText = element.children[0]?.text ? `[${formattedText}]\n(${element.url})` : `${element.url}`;
}
return formattedText;
}
async function processMessages(instance, messages, input, clientSideActions, eventEmitter, applyFormatting) {
for (const message of messages) {
if (message.type === 'text') {
let formattedText = '';
for (const richText of message.content.richText) {
for (const element of richText.children) {
formattedText += applyFormatting(element);
}
formattedText += '\n';
}
formattedText = formattedText.replace(/\*\*/g, '').replace(/__/, '').replace(/~~/, '').replace(/\n$/, '');
await instance.textMessage({
number: remoteJid.split('@')[0],
options: {
delay: instance.localTypebot.delay_message || 1000,
presence: 'composing',
},
textMessage: {
text: formattedText,
},
});
}
if (message.type === 'image') {
await instance.mediaMessage({
number: remoteJid.split('@')[0],
options: {
delay: instance.localTypebot.delay_message || 1000,
presence: 'composing',
},
mediaMessage: {
mediatype: 'image',
media: message.content.url,
},
});
}
if (message.type === 'video') {
await instance.mediaMessage({
number: remoteJid.split('@')[0],
options: {
delay: instance.localTypebot.delay_message || 1000,
presence: 'composing',
},
mediaMessage: {
mediatype: 'video',
media: message.content.url,
},
});
}
if (message.type === 'audio') {
await instance.audioWhatsapp({
number: remoteJid.split('@')[0],
options: {
delay: instance.localTypebot.delay_message || 1000,
presence: 'recording',
encoding: true,
},
audioMessage: {
audio: message.content.url,
},
});
}
const wait = findItemAndGetSecondsToWait(clientSideActions, message.id);
if (wait) {
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
}
}
if (input) {
if (input.type === 'choice input') {
let formattedText = '';
const items = input.items;
for (const item of items) {
formattedText += `▶️ ${item.content}\n`;
}
formattedText = formattedText.replace(/\n$/, '');
await instance.textMessage({
number: remoteJid.split('@')[0],
options: {
delay: instance.localTypebot.delay_message || 1000,
presence: 'composing',
},
textMessage: {
text: formattedText,
},
});
}
} else {
eventEmitter.emit('typebot:end', {
instance: instance,
remoteJid: remoteJid,
});
}
}
}
public async sendTypebot(instance: InstanceDto, remoteJid: string, msg: MessageRaw) {
const findTypebot = await this.find(instance);
const url = findTypebot.url;
const typebot = findTypebot.typebot;
const sessions = (findTypebot.sessions as Session[]) ?? [];
const expire = findTypebot.expire;
const keyword_finish = findTypebot.keyword_finish;
const delay_message = findTypebot.delay_message;
const unknown_message = findTypebot.unknown_message;
const listening_from_me = findTypebot.listening_from_me;
const messageType = this.getTypeMessage(msg.message).messageType;
const session = sessions.find((session) => session.remoteJid === remoteJid);
try {
if (session && expire && expire > 0) {
const now = Date.now();
const diff = now - session.updateAt;
const diffInMinutes = Math.floor(diff / 1000 / 60);
if (diffInMinutes > expire) {
const newSessions = await this.clearSessions(instance, remoteJid);
const data = await this.createNewSession(instance, {
enabled: findTypebot.enabled,
url: url,
typebot: typebot,
expire: expire,
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
listening_from_me: listening_from_me,
sessions: newSessions,
remoteJid: remoteJid,
pushName: msg.pushName,
});
await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions);
if (data.messages.length === 0) {
const content = this.getConversationMessage(msg.message);
if (!content) {
if (unknown_message) {
this.waMonitor.waInstances[instance.instanceName].textMessage({
number: remoteJid.split('@')[0],
options: {
delay: delay_message || 1000,
presence: 'composing',
},
textMessage: {
text: unknown_message,
},
});
}
return;
}
if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) {
const newSessions = await this.clearSessions(instance, remoteJid);
const typebotData = {
enabled: findTypebot.enabled,
url: url,
typebot: typebot,
expire: expire,
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
listening_from_me: listening_from_me,
sessions: newSessions,
};
this.create(instance, typebotData);
return;
}
try {
const version = this.configService.get<Typebot>('TYPEBOT').API_VERSION;
let urlTypebot: string;
let reqData: {};
if (version === 'latest') {
urlTypebot = `${url}/api/v1/sessions/${data.sessionId}/continueChat`;
reqData = {
message: content,
};
} else {
urlTypebot = `${url}/api/v1/sendMessage`;
reqData = {
message: content,
sessionId: data.sessionId,
};
}
const request = await axios.post(urlTypebot, reqData);
await this.sendWAMessage(
instance,
remoteJid,
request.data.messages,
request.data.input,
request.data.clientSideActions,
);
} catch (error) {
this.logger.error(error);
return;
}
}
return;
}
}
if (session && session.status !== 'opened') {
return;
}
if (!session) {
const data = await this.createNewSession(instance, {
enabled: findTypebot.enabled,
url: url,
typebot: typebot,
expire: expire,
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
listening_from_me: listening_from_me,
sessions: sessions,
remoteJid: remoteJid,
pushName: msg.pushName,
prefilledVariables: {
messageType: messageType,
},
});
await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions);
if (data.messages.length === 0) {
const content = this.getConversationMessage(msg.message);
if (!content) {
if (unknown_message) {
this.waMonitor.waInstances[instance.instanceName].textMessage({
number: remoteJid.split('@')[0],
options: {
delay: delay_message || 1000,
presence: 'composing',
},
textMessage: {
text: unknown_message,
},
});
}
return;
}
if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) {
const newSessions = await this.clearSessions(instance, remoteJid);
const typebotData = {
enabled: findTypebot.enabled,
url: url,
typebot: typebot,
expire: expire,
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
listening_from_me: listening_from_me,
sessions: newSessions,
};
this.create(instance, typebotData);
return;
}
let request: any;
try {
const version = this.configService.get<Typebot>('TYPEBOT').API_VERSION;
let urlTypebot: string;
let reqData: {};
if (version === 'latest') {
urlTypebot = `${url}/api/v1/sessions/${data.sessionId}/continueChat`;
reqData = {
message: content,
};
} else {
urlTypebot = `${url}/api/v1/sendMessage`;
reqData = {
message: content,
sessionId: data.sessionId,
};
}
request = await axios.post(urlTypebot, reqData);
await this.sendWAMessage(
instance,
remoteJid,
request.data.messages,
request.data.input,
request.data.clientSideActions,
);
} catch (error) {
this.logger.error(error);
return;
}
}
return;
}
sessions.map((session) => {
if (session.remoteJid === remoteJid) {
session.updateAt = Date.now();
}
});
const typebotData = {
enabled: findTypebot.enabled,
url: url,
typebot: typebot,
expire: expire,
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
listening_from_me: listening_from_me,
sessions,
};
this.create(instance, typebotData);
const content = this.getConversationMessage(msg.message);
if (!content) {
if (unknown_message) {
this.waMonitor.waInstances[instance.instanceName].textMessage({
number: remoteJid.split('@')[0],
options: {
delay: delay_message || 1000,
presence: 'composing',
},
textMessage: {
text: unknown_message,
},
});
}
return;
}
if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) {
const newSessions = await this.clearSessions(instance, remoteJid);
const typebotData = {
enabled: findTypebot.enabled,
url: url,
typebot: typebot,
expire: expire,
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
listening_from_me: listening_from_me,
sessions: newSessions,
};
this.create(instance, typebotData);
return;
}
const version = this.configService.get<Typebot>('TYPEBOT').API_VERSION;
let urlTypebot: string;
let reqData: {};
if (version === 'latest') {
urlTypebot = `${url}/api/v1/sessions/${session.sessionId.split('-')[1]}/continueChat`;
reqData = {
message: content,
};
} else {
urlTypebot = `${url}/api/v1/sendMessage`;
reqData = {
message: content,
sessionId: session.sessionId.split('-')[1],
};
}
const request = await axios.post(urlTypebot, reqData);
await this.sendWAMessage(
instance,
remoteJid,
request.data.messages,
request.data.input,
request.data.clientSideActions,
);
return;
} catch (error) {
this.logger.error(error);
return;
}
}
}

View File

@@ -0,0 +1,60 @@
import { JSONSchema7 } from 'json-schema';
import { v4 } from 'uuid';
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
const properties = {};
propertyNames.forEach(
(property) =>
(properties[property] = {
minLength: 1,
description: `The "${property}" cannot be empty`,
}),
);
return {
if: {
propertyNames: {
enum: [...propertyNames],
},
},
then: { properties },
};
};
export const typebotSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
enabled: { type: 'boolean', enum: [true, false] },
url: { type: 'string' },
typebot: { type: 'string' },
expire: { type: 'integer' },
delay_message: { type: 'integer' },
unknown_message: { type: 'string' },
listening_from_me: { type: 'boolean', enum: [true, false] },
},
required: ['enabled', 'url', 'typebot', 'expire', 'delay_message', 'unknown_message', 'listening_from_me'],
...isNotEmpty('enabled', 'url', 'typebot', 'expire', 'delay_message', 'unknown_message', 'listening_from_me'),
};
export const typebotStatusSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
remoteJid: { type: 'string' },
status: { type: 'string', enum: ['opened', 'closed', 'paused'] },
},
required: ['remoteJid', 'status'],
...isNotEmpty('remoteJid', 'status'),
};
export const typebotStartSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
remoteJid: { type: 'string' },
url: { type: 'string' },
typebot: { type: 'string' },
},
required: ['remoteJid', 'url', 'typebot'],
...isNotEmpty('remoteJid', 'url', 'typebot'),
};

View File

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

View File

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

View File

@@ -0,0 +1,44 @@
import { Server } from 'http';
import { Server as SocketIO } from 'socket.io';
import { configService, Cors, Websocket } from '../../../../config/env.config';
import { Logger } from '../../../../config/logger.config';
const logger = new Logger('Socket');
let io: SocketIO;
const cors = configService.get<Cors>('CORS').ORIGIN;
export const initIO = (httpServer: Server) => {
if (configService.get<Websocket>('WEBSOCKET')?.ENABLED) {
io = new SocketIO(httpServer, {
cors: {
origin: cors,
},
});
io.on('connection', (socket) => {
logger.info('User connected');
socket.on('disconnect', () => {
logger.info('User disconnected');
});
});
logger.info('Socket.io initialized');
return io;
}
return null;
};
export const getIO = (): SocketIO => {
logger.verbose('Getting Socket.io');
if (!io) {
logger.error('Socket.io not initialized');
throw new Error('Socket.io not initialized');
}
return io;
};

View File

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

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 { IWebsocketModel, WebsocketRaw } from '../../../models';
export class WebsocketRepository extends Repository {
constructor(private readonly websocketModel: IWebsocketModel, private readonly configService: ConfigService) {
super(configService);
}
private readonly logger = new Logger('WebsocketRepository');
public async create(data: WebsocketRaw, instance: string): Promise<IInsert> {
try {
this.logger.verbose('creating websocket');
if (this.dbSettings.ENABLED) {
this.logger.verbose('saving websocket to db');
const insert = await this.websocketModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
this.logger.verbose('websocket saved to db: ' + insert.modifiedCount + ' websocket');
return { insertCount: insert.modifiedCount };
}
this.logger.verbose('saving websocket to store');
this.writeStore<WebsocketRaw>({
path: join(this.storePath, 'websocket'),
fileName: instance,
data,
});
this.logger.verbose('websocket saved to store in path: ' + join(this.storePath, 'websocket') + '/' + instance);
this.logger.verbose('websocket created');
return { insertCount: 1 };
} catch (error) {
return error;
}
}
public async find(instance: string): Promise<WebsocketRaw> {
try {
this.logger.verbose('finding websocket');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding websocket in db');
return await this.websocketModel.findOne({ _id: instance });
}
this.logger.verbose('finding websocket in store');
return JSON.parse(
readFileSync(join(this.storePath, 'websocket', instance + '.json'), {
encoding: 'utf-8',
}),
) as WebsocketRaw;
} catch (error) {
return {};
}
}
}

View File

@@ -0,0 +1,52 @@
import { RequestHandler, Router } from 'express';
import { Logger } from '../../../../config/logger.config';
import { instanceNameSchema, websocketSchema } from '../../../../validate/validate.schema';
import { RouterBroker } from '../../../abstract/abstract.router';
import { InstanceDto } from '../../../dto/instance.dto';
import { HttpStatus } from '../../../routes/index.router';
import { websocketController } from '../../../server.module';
import { WebsocketDto } from '../dto/websocket.dto';
const logger = new Logger('WebsocketRouter');
export class WebsocketRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.post(this.routerPath('set'), ...guards, async (req, res) => {
logger.verbose('request received in setWebsocket');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<WebsocketDto>({
request: req,
schema: websocketSchema,
ClassRef: WebsocketDto,
execute: (instance, data) => websocketController.createWebsocket(instance, data),
});
res.status(HttpStatus.CREATED).json(response);
})
.get(this.routerPath('find'), ...guards, async (req, res) => {
logger.verbose('request received in findWebsocket');
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) => websocketController.findWebsocket(instance),
});
res.status(HttpStatus.OK).json(response);
});
}
public readonly router = Router();
}

View File

@@ -0,0 +1,33 @@
import { Logger } from '../../../../config/logger.config';
import { InstanceDto } from '../../../dto/instance.dto';
import { WebsocketRaw } from '../../../models';
import { WAMonitoringService } from '../../../services/monitor.service';
import { WebsocketDto } from '../dto/websocket.dto';
export class WebsocketService {
constructor(private readonly waMonitor: WAMonitoringService) {}
private readonly logger = new Logger(WebsocketService.name);
public create(instance: InstanceDto, data: WebsocketDto) {
this.logger.verbose('create websocket: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setWebsocket(data);
return { websocket: { ...instance, websocket: data } };
}
public async find(instance: InstanceDto): Promise<WebsocketRaw> {
try {
this.logger.verbose('find websocket: ' + instance.instanceName);
const result = await this.waMonitor.waInstances[instance.instanceName].findWebsocket();
if (Object.keys(result).length === 0) {
throw new Error('Websocket not found');
}
return result;
} catch (error) {
return { enabled: false, events: [] };
}
}
}

View File

@@ -0,0 +1,66 @@
import { JSONSchema7 } from 'json-schema';
import { v4 } from 'uuid';
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
const properties = {};
propertyNames.forEach(
(property) =>
(properties[property] = {
minLength: 1,
description: `The "${property}" cannot be empty`,
}),
);
return {
if: {
propertyNames: {
enum: [...propertyNames],
},
},
then: { properties },
};
};
export const websocketSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
enabled: { type: 'boolean', enum: [true, false] },
events: {
type: 'array',
minItems: 0,
items: {
type: 'string',
enum: [
'APPLICATION_STARTUP',
'QRCODE_UPDATED',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_UPDATE',
'MESSAGES_DELETE',
'SEND_MESSAGE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'PRESENCE_UPDATE',
'CHATS_SET',
'CHATS_UPSERT',
'CHATS_UPDATE',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
'TYPEBOT_CHANGE_STATUS',
'CHAMA_AI_ACTION',
],
},
},
},
required: ['enabled'],
...isNotEmpty('enabled'),
};

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