Compare commits

...

399 Commits
1.6.1 ... 1.8.0

Author SHA1 Message Date
Davidson Gomes
05b5ae8a84 Merge branch 'release/1.8.0' 2024-05-27 16:36:03 -03:00
Davidson Gomes
2e9c14a0a8 fix: security fix in fetch instance with client key when not connected to mongodb 2024-05-27 16:35:59 -03:00
Davidson Gomes
3350cec589 fix: security fix in fetch instance with client key when not connected to mongodb 2024-05-27 16:35:55 -03:00
Davidson Gomes
bd7a42646b Merge tag '1.8.0' into develop
v
2024-05-27 16:24:29 -03:00
Davidson Gomes
2ae4ddee18 Merge branch 'release/1.8.0' 2024-05-27 16:24:25 -03:00
Davidson Gomes
2fb6e2b209 fix: Build in docker for linux/amd64, linux/arm64 platforms 2024-05-27 16:24:21 -03:00
Davidson Gomes
91f869b136 Merge tag '1.8.0' into develop
v
2024-05-27 16:17:30 -03:00
Davidson Gomes
1284a97625 Merge branch 'release/1.8.0' 2024-05-27 16:17:27 -03:00
Davidson Gomes
3a5cfbf36f fix: Build in docker for linux/amd64, linux/arm64 platforms 2024-05-27 16:17:22 -03:00
Davidson Gomes
47b8ecd43b Merge tag '1.8.0' into develop
v
2024-05-27 16:09:16 -03:00
Davidson Gomes
25ce1a4689 Merge branch 'release/1.8.0' 2024-05-27 16:09:12 -03:00
Davidson Gomes
a5156709ab fix: Build in docker for linux/amd64, linux/arm64 platforms 2024-05-27 16:09:07 -03:00
Davidson Gomes
fd2d37d862 Merge tag '1.8.0' into develop
v
2024-05-27 16:07:19 -03:00
Davidson Gomes
87bf433e9a Merge branch 'release/1.8.0' 2024-05-27 16:07:15 -03:00
Davidson Gomes
b5ec79daae fix: Build in docker for linux/amd64, linux/arm64 platforms 2024-05-27 16:07:03 -03:00
Davidson Gomes
3b19200997 Merge tag '1.7.6' into develop
v
2024-05-27 15:54:45 -03:00
Davidson Gomes
0ab040080c Merge branch 'release/1.7.6' 2024-05-27 15:54:42 -03:00
Davidson Gomes
5719896d18 fix: git actions 2024-05-27 15:54:31 -03:00
Davidson Gomes
e9e1cb6ebc Merge tag '1.7.6' into develop
v
2024-05-27 15:51:22 -03:00
Davidson Gomes
ceee6a7e74 Merge branch 'release/1.7.6' 2024-05-27 15:51:19 -03:00
Davidson Gomes
ebfc6d4fa5 fix: git actions 2024-05-27 15:51:06 -03:00
Davidson Gomes
0d62557a15 Merge pull request #609 from stack-app-br/main
chore: build docker image to platforms: linux/amd64,linux/arm64
2024-05-27 15:45:28 -03:00
Davidson Gomes
097c09328f Merge pull request #615 from deivisonrpg/fix-merge-brazil-contacts
fix(chatwoot): add merge_brazil_contacts flag to ChannelStartupService and add logs
2024-05-27 15:34:56 -03:00
Davidson Gomes
732103292b Merge pull request #618 from edisoncm-ti/develop
fix: Correction to Postgres connection string in environment files
2024-05-27 15:34:29 -03:00
edisoncm-ti
3191e9b450 fix: Correction to Postgres connection string in environment files 2024-05-27 15:14:52 -03:00
Davidson Gomes
bf88fdbb31 fix: adjust send presence 2024-05-27 12:57:18 -03:00
Davidson Gomes
4c3fb5e762 correction in message formatting when generated by AI as markdown in typebot 2024-05-27 09:27:42 -03:00
Davidson Gomes
e4b6f4ff0d correction in message formatting when generated by AI as markdown in typebot 2024-05-27 09:23:53 -03:00
Deivison Lincoln
ca0b0d4602 Add merge_brazil_contacts flag to ChannelStartupService 2024-05-25 10:09:37 -03:00
Deivison Lincoln
d7c3779ff7 fix: Add merge_brazil_contacts flag to ChannelStartupService 2024-05-25 10:05:04 -03:00
Davidson Gomes
f0d40eea15 New global mode for rabbitmq events 2024-05-24 18:48:54 -03:00
Davidson Gomes
68dcc1c86a adjusts in create instance 2024-05-23 11:56:52 -03:00
Davidson Gomes
f7dcab3736 Now in the manager, when logging in with the client's apikey, the listing only shows the instance corresponding to the provided apikey (only with MongoDB) 2024-05-23 11:31:45 -03:00
Davidson Gomes
2fcb476c50 Now in the manager, when logging in with the client's apikey, the listing only shows the instance corresponding to the provided apikey (only with MongoDB) 2024-05-23 11:30:47 -03:00
Antonio Milesi Bastos
44a7cd62c8 Merge pull request #1 from stack-app-br/releases/1.7.5
chore: build docker image to platforms: linux/amd64,linux/arm64
2024-05-21 11:56:53 -03:00
@milesibastos
f7f0f8251b chore: build docker image to platforms: linux/amd64,linux/arm64 2024-05-21 11:48:13 -03:00
Davidson Gomes
395b81a6ac Merge tag '1.7.5' into develop
v
2024-05-21 08:53:58 -03:00
Davidson Gomes
da06ed1185 Merge branch 'release/1.7.5' 2024-05-21 08:53:53 -03:00
Davidson Gomes
67afbd6a77 v1.7.5 2024-05-21 08:53:35 -03:00
Davidson Gomes
8fce53b4af fixed version whatsapp web for baileys 2024-05-21 08:38:08 -03:00
Davidson Gomes
65bba23519 update baileys version 2024-05-20 19:53:23 -03:00
Davidson Gomes
283b497788 downgrade baileys version 2024-05-20 19:39:56 -03:00
Davidson Gomes
8cc1b8daa8 Merge pull request #590 from Ckk3/fix-envvar-CLEAN_STORE_CLEANING_INTERVAL
Corrigindo variável inexistente do arquivo env.config.ts e substituindo pela correta CLEAN_STORE_CLEANING_INTERVAL
2024-05-09 14:15:09 -03:00
Davidson Gomes
9ccdb45a7a Merge pull request #587 from deivisonrpg/chatwoot-merge-contact-with-nine
feature(chatwoot): add merge_brazil_contacts function to solve nine digit in brazilian numbers
2024-05-09 14:12:31 -03:00
Davidson Gomes
f78d360c38 Merge pull request #586 from deivisonrpg/chatwoot-update-contact
refactor(chatwoot): optimize ChatwootService method for updating contact inform…
2024-05-09 14:11:07 -03:00
Ckk3
6144fbe856 changing CLEAN_STORE_CLEANING_TERMINAL to CLEAN_STORE_CLEANING_INTERVAL 2024-05-09 13:21:28 -03:00
Deivison Lincoln
8446be7646 fix(chatwoot): fix grupos 2024-05-08 16:47:21 -03:00
Deivison Lincoln
8875ab1e93 feat: add merge_brazil_contacts flag to instance controller 2024-05-08 15:26:56 -03:00
Deivison Lincoln
7c207a50e1 feat: add merge_brazil_contacts to swagger doc 2024-05-08 15:11:37 -03:00
Deivison Lincoln
dcc32479ff feature(chatwoot): add merge_brazil_contacts function to solve nine digit in brazilian numbers 2024-05-08 15:03:37 -03:00
Deivison Lincoln
09911c472d refactor: optimize ChatwootService method for updating contact information 2024-05-08 14:34:03 -03:00
Davidson Gomes
7a0149ee23 Merge pull request #571 from stack-app-br/main
chore: build docker image to platforms: linux/amd64, linux/arm64
2024-05-08 11:02:11 -03:00
Davidson Gomes
3ae8cf32b0 Merge pull request #584 from Al1st1c/fix/swagger-auth
Fix/swagger auth
2024-05-08 09:56:59 -03:00
Davidson Gomes
11cf947bbb Merge pull request #583 from deivisonrpg/chore-update-aws-sdk-v3
chore(aws sqs): update aws sdk v3
2024-05-08 09:56:24 -03:00
Davidson Gomes
60a20f61af Merge pull request #582 from deivisonrpg/hotfix-getOpenConversationByContact
fix(chatwoot): getOpenConversationByContact and init queries error
2024-05-08 09:55:08 -03:00
Davidson Gomes
e65c7b6bcf Merge pull request #577 from neanderdev/mark-chat-as-unread
feat: method to mark chat as unread
2024-05-08 09:54:37 -03:00
Al1stic
80c892aca3 fix env 2024-05-06 19:31:37 -03:00
dev2
39ee266598 fix: correcao swagger sem authkey 2024-05-06 19:28:57 -03:00
Deivison Lincoln
aa58d7744e chore: update AWS SDK dependency to version 3.569.0 2024-05-06 17:28:41 -03:00
Deivison Lincoln
ea3b0b3712 fix: optimize ChatwootService method for retrieving open conversations 2024-05-06 16:30:16 -03:00
Deivison Lincoln
d9d8707123 chore: update baileys to version 6.7.2 2024-05-06 16:29:37 -03:00
Neander de Souza
8e9a1e2ba5 feat: method to mark chat as unread 2024-05-06 09:08:36 -03:00
Davidson Gomes
e29b4865e8 update baileys 2024-05-01 19:20:40 -03:00
@milesibastos
4b60ca175d chore: build docker image to platforms: linux/amd64,linux/arm64 2024-04-30 17:48:35 -03:00
Davidson Gomes
2fbbc7b5a9 Merge tag '1.7.4' into develop
v
2024-04-28 09:47:24 -03:00
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
152 changed files with 8571 additions and 2545 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -0,0 +1,48 @@
name: Build Docker image
on:
push:
tags:
- "*.*.*"
jobs:
build_deploy:
name: Build and Deploy
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: atendai/evolution-api
tags: type=semver,pattern=v{{version}}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
id: docker_build
uses: docker/build-push-action@v5
with:
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}

View File

@@ -0,0 +1,48 @@
name: Build Docker image
on:
push:
branches:
- develop
jobs:
build_deploy:
name: Build and Deploy
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: atendai/evolution-api
tags: homolog
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
id: docker_build
uses: docker/build-push-action@v5
with:
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}

3
.gitignore vendored
View File

@@ -4,6 +4,8 @@
/Docker/.env /Docker/.env
.vscode
# Logs # Logs
logs/**.json logs/**.json
*.log *.log
@@ -45,3 +47,4 @@ docker-compose.yaml
.DS_Store .DS_Store
*.DS_Store *.DS_Store
.tool-versions

View File

@@ -9,5 +9,8 @@
"source.fixAll": "explicit" "source.fixAll": "explicit"
}, },
"prisma-smart-formatter.typescript.defaultFormatter": "esbenp.prettier-vscode", "prisma-smart-formatter.typescript.defaultFormatter": "esbenp.prettier-vscode",
"prisma-smart-formatter.prisma.defaultFormatter": "Prisma.prisma" "prisma-smart-formatter.prisma.defaultFormatter": "Prisma.prisma",
"i18n-ally.localesPaths": [
"store/messages"
]
} }

View File

@@ -1,3 +1,119 @@
# 1.8.0 (2024-05-27 16:10)
### Feature
* Now in the manager, when logging in with the client's apikey, the listing only shows the instance corresponding to the provided apikey (only with MongoDB)
* New global mode for rabbitmq events
* Build in docker for linux/amd64, linux/arm64 platforms
### Fixed
* Correction in message formatting when generated by AI as markdown in typebot
* Security fix in fetch instance with client key when not connected to mongodb
# 1.7.5 (2024-05-21 08:50)
### Fixed
* Add merge_brazil_contacts function to solve nine digit in brazilian numbers
* Optimize ChatwootService method for updating contact
* Fix swagger auth
* Update aws sdk v3
* Fix getOpenConversationByContact and init queries error
* Method to mark chat as unread
* Added environment variable to manually select the WhatsApp web version for the baileys lib (optional)
# 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) # 1.6.1 (2023-12-22 11:43)
### Fixed ### Fixed

View File

@@ -16,6 +16,7 @@ LOG_BAILEYS=error
# Default time: 5 minutes # Default time: 5 minutes
# If you don't even want an expiration, enter the value false # If you don't even want an expiration, enter the value false
DEL_INSTANCE=false DEL_INSTANCE=false
DEL_TEMP_INSTANCES=true # Delete instances with status closed on start
# Temporary data storage # Temporary data storage
STORE_MESSAGES=true STORE_MESSAGES=true
@@ -32,7 +33,10 @@ CLEAN_STORE_CHATS=true
# Permanent data storage # Permanent data storage
DATABASE_ENABLED=false DATABASE_ENABLED=false
DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin &
readPreference=primary &
ssl=false &
directConnection=true
DATABASE_CONNECTION_DB_PREFIX_NAME=evdocker DATABASE_CONNECTION_DB_PREFIX_NAME=evdocker
# Choose the data you want to save in the application's database or store # Choose the data you want to save in the application's database or store
@@ -42,14 +46,42 @@ DATABASE_SAVE_MESSAGE_UPDATE=false
DATABASE_SAVE_DATA_CONTACTS=false DATABASE_SAVE_DATA_CONTACTS=false
DATABASE_SAVE_DATA_CHATS=false DATABASE_SAVE_DATA_CHATS=false
REDIS_ENABLED=false
REDIS_URI=redis://redis:6379
REDIS_PREFIX_KEY=evdocker
RABBITMQ_ENABLED=false RABBITMQ_ENABLED=false
RABBITMQ_URI=amqp://guest:guest@rabbitmq:5672 RABBITMQ_URI=amqp://guest:guest@rabbitmq:5672
RABBITMQ_EXCHANGE_NAME=evolution_exchange
RABBITMQ_GLOBAL_ENABLED=false
RABBITMQ_EVENTS_APPLICATION_STARTUP=false
RABBITMQ_EVENTS_QRCODE_UPDATED=true
RABBITMQ_EVENTS_MESSAGES_SET=true
RABBITMQ_EVENTS_MESSAGES_UPSERT=true
RABBITMQ_EVENTS_MESSAGES_UPDATE=true
RABBITMQ_EVENTS_MESSAGES_DELETE=true
RABBITMQ_EVENTS_SEND_MESSAGE=true
RABBITMQ_EVENTS_CONTACTS_SET=true
RABBITMQ_EVENTS_CONTACTS_UPSERT=true
RABBITMQ_EVENTS_CONTACTS_UPDATE=true
RABBITMQ_EVENTS_PRESENCE_UPDATE=true
RABBITMQ_EVENTS_CHATS_SET=true
RABBITMQ_EVENTS_CHATS_UPSERT=true
RABBITMQ_EVENTS_CHATS_UPDATE=true
RABBITMQ_EVENTS_CHATS_DELETE=true
RABBITMQ_EVENTS_GROUPS_UPSERT=true
RABBITMQ_EVENTS_GROUPS_UPDATE=true
RABBITMQ_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
RABBITMQ_EVENTS_CONNECTION_UPDATE=true
RABBITMQ_EVENTS_LABELS_EDIT=true
RABBITMQ_EVENTS_LABELS_ASSOCIATION=true
RABBITMQ_EVENTS_CALL=true
RABBITMQ_EVENTS_TYPEBOT_START=false
RABBITMQ_EVENTS_TYPEBOT_CHANGE_STATUS=false
WEBSOCKET_ENABLED=false 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_ENABLED=false
SQS_ACCESS_KEY_ID= SQS_ACCESS_KEY_ID=
@@ -84,6 +116,8 @@ WEBHOOK_EVENTS_GROUPS_UPSERT=true
WEBHOOK_EVENTS_GROUPS_UPDATE=true WEBHOOK_EVENTS_GROUPS_UPDATE=true
WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
WEBHOOK_EVENTS_CONNECTION_UPDATE=true WEBHOOK_EVENTS_CONNECTION_UPDATE=true
WEBHOOK_EVENTS_LABELS_EDIT=true
WEBHOOK_EVENTS_LABELS_ASSOCIATION=true
WEBHOOK_EVENTS_CALL=true WEBHOOK_EVENTS_CALL=true
# This event fires every time a new token is requested via the refresh route # This event fires every time a new token is requested via the refresh route
WEBHOOK_EVENTS_NEW_JWT_TOKEN=false WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
@@ -109,6 +143,23 @@ QRCODE_COLOR=#198754
TYPEBOT_API_VERSION=latest TYPEBOT_API_VERSION=latest
TYPEBOT_KEEP_OPEN=false 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?sslmode=disable
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 # Defines an authentication type for the api
# We recommend using the apikey because it will allow you to use a custom token, # We recommend using the apikey because it will allow you to use a custom token,
# if you use jwt, a random token will be generated and may be expired and you will have to generate a new token # if you use jwt, a random token will be generated and may be expired and you will have to generate a new token
@@ -122,3 +173,5 @@ AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES=true
# seconds - 3600s ===1h | zero (0) - never expires # seconds - 3600s ===1h | zero (0) - never expires
AUTHENTICATION_JWT_EXPIRIN_IN=0 AUTHENTICATION_JWT_EXPIRIN_IN=0
AUTHENTICATION_JWT_SECRET='L=0YWt]b2w[WF>#>:&E`' AUTHENTICATION_JWT_SECRET='L=0YWt]b2w[WF>#>:&E`'
LANGUAGE=en # pt-BR, en

View File

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

View File

@@ -16,6 +16,7 @@ LOG_BAILEYS=error
# Default time: 5 minutes # Default time: 5 minutes
# If you don't even want an expiration, enter the value false # If you don't even want an expiration, enter the value false
DEL_INSTANCE=false DEL_INSTANCE=false
DEL_TEMP_INSTANCES=true # Delete instances with status closed on start
# Temporary data storage # Temporary data storage
STORE_MESSAGES=true STORE_MESSAGES=true
@@ -32,7 +33,10 @@ CLEAN_STORE_CHATS=true
# Permanent data storage # Permanent data storage
DATABASE_ENABLED=true DATABASE_ENABLED=true
DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin &
readPreference=primary &
ssl=false &
directConnection=true
DATABASE_CONNECTION_DB_PREFIX_NAME=evolution DATABASE_CONNECTION_DB_PREFIX_NAME=evolution
# Choose the data you want to save in the application's database or store # Choose the data you want to save in the application's database or store
@@ -42,10 +46,6 @@ DATABASE_SAVE_MESSAGE_UPDATE=false
DATABASE_SAVE_DATA_CONTACTS=false DATABASE_SAVE_DATA_CONTACTS=false
DATABASE_SAVE_DATA_CHATS=false DATABASE_SAVE_DATA_CHATS=false
REDIS_ENABLED=true
REDIS_URI=redis://redis:6379
REDIS_PREFIX_KEY=evolution
# Global Webhook Settings # Global Webhook Settings
# Each instance's Webhook URL and events will be requested at the time it is created # Each instance's Webhook URL and events will be requested at the time it is created
## Define a global webhook that will listen for enabled events from all instances ## Define a global webhook that will listen for enabled events from all instances
@@ -73,6 +73,8 @@ WEBHOOK_EVENTS_GROUPS_UPSERT=true
WEBHOOK_EVENTS_GROUPS_UPDATE=true WEBHOOK_EVENTS_GROUPS_UPDATE=true
WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
WEBHOOK_EVENTS_CONNECTION_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 # This event fires every time a new token is requested via the refresh route
WEBHOOK_EVENTS_NEW_JWT_TOKEN=false WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
@@ -84,6 +86,14 @@ CONFIG_SESSION_PHONE_NAME=chrome
# Set qrcode display limit # Set qrcode display limit
QRCODE_LIMIT=30 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 # Defines an authentication type for the api
# We recommend using the apikey because it will allow you to use a custom token, # We recommend using the apikey because it will allow you to use a custom token,
# if you use jwt, a random token will be generated and may be expired and you will have to generate a new token # if you use jwt, a random token will be generated and may be expired and you will have to generate a new token

View File

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

View File

@@ -1,6 +1,6 @@
FROM node:20.7.0-alpine AS builder FROM node:20.7.0-alpine AS builder
LABEL version="1.6.1" description="Api to control whatsapp features through http requests." LABEL version="1.8.0" description="Api to control whatsapp features through http requests."
LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes" LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
LABEL contact="contato@agenciadgcode.com" LABEL contact="contato@agenciadgcode.com"
@@ -35,6 +35,7 @@ ENV LOG_COLOR=true
ENV LOG_BAILEYS=error ENV LOG_BAILEYS=error
ENV DEL_INSTANCE=false ENV DEL_INSTANCE=false
ENV DEL_TEMP_INSTANCES=true
ENV STORE_MESSAGES=true ENV STORE_MESSAGES=true
ENV STORE_MESSAGE_UP=true ENV STORE_MESSAGE_UP=true
@@ -57,14 +58,44 @@ ENV DATABASE_SAVE_MESSAGE_UPDATE=false
ENV DATABASE_SAVE_DATA_CONTACTS=false ENV DATABASE_SAVE_DATA_CONTACTS=false
ENV DATABASE_SAVE_DATA_CHATS=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_ENABLED=false
ENV RABBITMQ_URI=amqp://guest:guest@rabbitmq:5672 ENV RABBITMQ_URI=amqp://guest:guest@rabbitmq:5672
ENV RABBITMQ_EXCHANGE_NAME=evolution_exchange
ENV RABBITMQ_GLOBAL_ENABLED=false
ENV RABBITMQ_EVENTS_APPLICATION_STARTUP=false
ENV RABBITMQ_EVENTS_INSTANCE_CREATE=false
ENV RABBITMQ_EVENTS_INSTANCE_DELETE=false
ENV RABBITMQ_EVENTS_QRCODE_UPDATED=true
ENV RABBITMQ_EVENTS_MESSAGES_SET=true
ENV RABBITMQ_EVENTS_MESSAGES_UPSERT=true
ENV RABBITMQ_EVENTS_MESSAGES_UPDATE=true
ENV RABBITMQ_EVENTS_MESSAGES_DELETE=true
ENV RABBITMQ_EVENTS_SEND_MESSAGE=true
ENV RABBITMQ_EVENTS_CONTACTS_SET=true
ENV RABBITMQ_EVENTS_CONTACTS_UPSERT=true
ENV RABBITMQ_EVENTS_CONTACTS_UPDATE=true
ENV RABBITMQ_EVENTS_PRESENCE_UPDATE=true
ENV RABBITMQ_EVENTS_CHATS_SET=true
ENV RABBITMQ_EVENTS_CHATS_UPSERT=true
ENV RABBITMQ_EVENTS_CHATS_UPDATE=true
ENV RABBITMQ_EVENTS_CHATS_DELETE=true
ENV RABBITMQ_EVENTS_GROUPS_UPSERT=true
ENV RABBITMQ_EVENTS_GROUPS_UPDATE=true
ENV RABBITMQ_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
ENV RABBITMQ_EVENTS_CONNECTION_UPDATE=true
ENV RABBITMQ_EVENTS_LABELS_EDIT=true
ENV RABBITMQ_EVENTS_LABELS_ASSOCIATION=true
ENV RABBITMQ_EVENTS_CALL=true
ENV RABBITMQ_EVENTS_TYPEBOT_START=false
ENV RABBITMQ_EVENTS_TYPEBOT_CHANGE_STATUS=false
ENV WEBSOCKET_ENABLED=false 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_ENABLED=false
ENV SQS_ACCESS_KEY_ID= ENV SQS_ACCESS_KEY_ID=
@@ -98,6 +129,8 @@ ENV WEBHOOK_EVENTS_GROUPS_UPSERT=true
ENV WEBHOOK_EVENTS_GROUPS_UPDATE=true ENV WEBHOOK_EVENTS_GROUPS_UPDATE=true
ENV WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true ENV WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
ENV WEBHOOK_EVENTS_CONNECTION_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_CALL=true
ENV WEBHOOK_EVENTS_NEW_JWT_TOKEN=false ENV WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
@@ -118,6 +151,14 @@ ENV QRCODE_COLOR=#198754
ENV TYPEBOT_API_VERSION=latest 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 ENV AUTHENTICATION_TYPE=apikey
ENV AUTHENTICATION_API_KEY=B6D711FCDE4D4FD5936544120E713976 ENV AUTHENTICATION_API_KEY=B6D711FCDE4D4FD5936544120E713976

View File

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

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

View File

@@ -1,8 +1,10 @@
import { Logger } from '../../config/logger.config'; import { Logger } from '../../config/logger.config';
import { import {
ArchiveChatDto, ArchiveChatDto,
BlockUserDto,
DeleteMessage, DeleteMessage,
getBase64FromMediaMessageDto, getBase64FromMediaMessageDto,
MarkChatUnreadDto,
NumberDto, NumberDto,
PrivacySettingDto, PrivacySettingDto,
ProfileNameDto, ProfileNameDto,
@@ -10,6 +12,7 @@ import {
ProfileStatusDto, ProfileStatusDto,
ReadMessageDto, ReadMessageDto,
SendPresenceDto, SendPresenceDto,
UpdateMessageDto,
WhatsAppNumberDto, WhatsAppNumberDto,
} from '../dto/chat.dto'; } from '../dto/chat.dto';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../dto/instance.dto';
@@ -38,6 +41,11 @@ export class ChatController {
return await this.waMonitor.waInstances[instanceName].archiveChat(data); return await this.waMonitor.waInstances[instanceName].archiveChat(data);
} }
public async markChatUnread({ instanceName }: InstanceDto, data: MarkChatUnreadDto) {
logger.verbose('requested markChatUnread from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].markChatUnread(data);
}
public async deleteMessage({ instanceName }: InstanceDto, data: DeleteMessage) { public async deleteMessage({ instanceName }: InstanceDto, data: DeleteMessage) {
logger.verbose('requested deleteMessage from ' + instanceName + ' instance'); logger.verbose('requested deleteMessage from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].deleteMessage(data); return await this.waMonitor.waInstances[instanceName].deleteMessage(data);
@@ -117,4 +125,14 @@ export class ChatController {
logger.verbose('requested removeProfilePicture from ' + instanceName + ' instance'); logger.verbose('requested removeProfilePicture from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].removeProfilePicture(); 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 { Logger } from '../../config/logger.config';
import { import {
AcceptGroupInvite,
CreateGroupDto, CreateGroupDto,
GetParticipant, GetParticipant,
GroupDescriptionDto, GroupDescriptionDto,
@@ -65,6 +66,11 @@ export class GroupController {
return await this.waMonitor.waInstances[instance.instanceName].sendInvite(data); 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) { public async revokeInviteCode(instance: InstanceDto, groupJid: GroupJid) {
logger.verbose('requested revokeInviteCode from ' + instance.instanceName + ' instance'); logger.verbose('requested revokeInviteCode from ' + instance.instanceName + ' instance');
return await this.waMonitor.waInstances[instance.instanceName].revokeInviteCode(groupJid); return await this.waMonitor.waInstances[instance.instanceName].revokeInviteCode(groupJid);

View File

@@ -3,24 +3,26 @@ import { isURL } from 'class-validator';
import EventEmitter2 from 'eventemitter2'; import EventEmitter2 from 'eventemitter2';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { ConfigService, HttpServer } from '../../config/env.config'; import { Auth, ConfigService, HttpServer, WaBusiness } from '../../config/env.config';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../config/logger.config';
import { BadRequestException, InternalServerErrorException } from '../../exceptions'; import { BadRequestException, InternalServerErrorException, UnauthorizedException } from '../../exceptions';
import { RedisCache } from '../../libs/redis.client'; import { InstanceDto, SetPresenceDto } from '../dto/instance.dto';
import { InstanceDto } 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 { RepositoryBroker } from '../repository/repository.manager';
import { AuthService, OldToken } from '../services/auth.service'; import { AuthService, OldToken } from '../services/auth.service';
import { ChatwootService } from '../services/chatwoot.service'; import { 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 { WAMonitoringService } from '../services/monitor.service';
import { ProxyService } from '../services/proxy.service';
import { RabbitmqService } from '../services/rabbitmq.service';
import { SettingsService } from '../services/settings.service'; import { SettingsService } from '../services/settings.service';
import { SqsService } from '../services/sqs.service';
import { TypebotService } from '../services/typebot.service';
import { WebhookService } from '../services/webhook.service'; import { WebhookService } from '../services/webhook.service';
import { WebsocketService } from '../services/websocket.service'; import { Events, Integration, wa } from '../types/wa.types';
import { WAStartupService } from '../services/whatsapp.service'; import { ProxyController } from './proxy.controller';
import { Events, wa } from '../types/wa.types';
export class InstanceController { export class InstanceController {
constructor( constructor(
@@ -34,10 +36,13 @@ export class InstanceController {
private readonly settingsService: SettingsService, private readonly settingsService: SettingsService,
private readonly websocketService: WebsocketService, private readonly websocketService: WebsocketService,
private readonly rabbitmqService: RabbitmqService, private readonly rabbitmqService: RabbitmqService,
private readonly proxyService: ProxyService,
private readonly sqsService: SqsService, private readonly sqsService: SqsService,
private readonly typebotService: TypebotService, private readonly typebotService: TypebotService,
private readonly cache: RedisCache, 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); private readonly logger = new Logger(InstanceController.name);
@@ -50,6 +55,8 @@ export class InstanceController {
events, events,
qrcode, qrcode,
number, number,
mobile,
integration,
token, token,
chatwoot_account_id, chatwoot_account_id,
chatwoot_token, chatwoot_token,
@@ -57,12 +64,18 @@ export class InstanceController {
chatwoot_sign_msg, chatwoot_sign_msg,
chatwoot_reopen_conversation, chatwoot_reopen_conversation,
chatwoot_conversation_pending, chatwoot_conversation_pending,
chatwoot_import_contacts,
chatwoot_name_inbox,
chatwoot_merge_brazil_contacts,
chatwoot_import_messages,
chatwoot_days_limit_import_messages,
reject_call, reject_call,
msg_call, msg_call,
groups_ignore, groups_ignore,
always_online, always_online,
read_messages, read_messages,
read_status, read_status,
sync_full_history,
websocket_enabled, websocket_enabled,
websocket_events, websocket_events,
rabbitmq_enabled, rabbitmq_enabled,
@@ -84,8 +97,34 @@ export class InstanceController {
this.logger.verbose('checking duplicate token'); this.logger.verbose('checking duplicate token');
await this.authService.checkDuplicateToken(token); await this.authService.checkDuplicateToken(token);
if (!token && integration === Integration.WHATSAPP_BUSINESS) {
throw new BadRequestException('token is required');
}
this.logger.verbose('creating instance'); this.logger.verbose('creating instance');
const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache); let instance: BaileysStartupService | BusinessStartupService;
if (integration === Integration.WHATSAPP_BUSINESS) {
instance = new BusinessStartupService(
this.configService,
this.eventEmitter,
this.repository,
this.cache,
this.chatwootCache,
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; instance.instanceName = instanceName;
const instanceId = v4(); const instanceId = v4();
@@ -142,6 +181,8 @@ export class InstanceController {
'GROUP_UPDATE', 'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE', 'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE', 'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL', 'CALL',
'NEW_JWT_TOKEN', 'NEW_JWT_TOKEN',
'TYPEBOT_START', 'TYPEBOT_START',
@@ -192,6 +233,8 @@ export class InstanceController {
'GROUP_UPDATE', 'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE', 'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE', 'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL', 'CALL',
'NEW_JWT_TOKEN', 'NEW_JWT_TOKEN',
'TYPEBOT_START', 'TYPEBOT_START',
@@ -239,6 +282,8 @@ export class InstanceController {
'GROUP_UPDATE', 'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE', 'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE', 'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL', 'CALL',
'NEW_JWT_TOKEN', 'NEW_JWT_TOKEN',
'TYPEBOT_START', 'TYPEBOT_START',
@@ -259,22 +304,6 @@ export class InstanceController {
} }
} }
if (proxy) {
this.logger.verbose('creating proxy');
try {
this.proxyService.create(
instance,
{
enabled: true,
proxy,
},
false,
);
} catch (error) {
this.logger.log(error);
}
}
let sqsEvents: string[]; let sqsEvents: string[];
if (sqs_enabled) { if (sqs_enabled) {
@@ -302,6 +331,8 @@ export class InstanceController {
'GROUP_UPDATE', 'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE', 'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE', 'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL', 'CALL',
'NEW_JWT_TOKEN', 'NEW_JWT_TOKEN',
'TYPEBOT_START', 'TYPEBOT_START',
@@ -322,6 +353,18 @@ export class InstanceController {
} }
} }
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) { if (typebot_url) {
try { try {
if (!isURL(typebot_url, { require_tld: false })) { if (!isURL(typebot_url, { require_tld: false })) {
@@ -353,18 +396,36 @@ export class InstanceController {
always_online: always_online || false, always_online: always_online || false,
read_messages: read_messages || false, read_messages: read_messages || false,
read_status: read_status || false, read_status: read_status || false,
sync_full_history: sync_full_history ?? false,
}; };
this.logger.verbose('settings: ' + JSON.stringify(settings)); this.logger.verbose('settings: ' + JSON.stringify(settings));
this.settingsService.create(instance, 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) { if (!chatwoot_account_id || !chatwoot_token || !chatwoot_url) {
let getQrcode: wa.QrCode; let getQrcode: wa.QrCode;
if (qrcode) { if (qrcode) {
this.logger.verbose('creating qrcode'); this.logger.verbose('creating qrcode');
await instance.connectToWhatsapp(number); await instance.connectToWhatsapp(number, mobile);
await delay(5000); await delay(5000);
getQrcode = instance.qrCode; getQrcode = instance.qrCode;
} }
@@ -373,6 +434,9 @@ export class InstanceController {
instance: { instance: {
instanceName: instance.instanceName, instanceName: instance.instanceName,
instanceId: instanceId, instanceId: instanceId,
integration: integration,
webhook_wa_business,
access_token_wa_business,
status: 'created', status: 'created',
}, },
hash, hash,
@@ -406,7 +470,6 @@ export class InstanceController {
}, },
settings, settings,
qrcode: getQrcode, qrcode: getQrcode,
proxy,
}; };
this.logger.verbose('instance created'); this.logger.verbose('instance created');
@@ -452,10 +515,14 @@ export class InstanceController {
token: chatwoot_token, token: chatwoot_token,
url: chatwoot_url, url: chatwoot_url,
sign_msg: chatwoot_sign_msg || false, sign_msg: chatwoot_sign_msg || false,
name_inbox: instance.instanceName.split('-cwId-')[0], name_inbox: chatwoot_name_inbox ?? instance.instanceName.split('-cwId-')[0],
number, number,
reopen_conversation: chatwoot_reopen_conversation || false, reopen_conversation: chatwoot_reopen_conversation || false,
conversation_pending: chatwoot_conversation_pending || false, conversation_pending: chatwoot_conversation_pending || false,
import_contacts: chatwoot_import_contacts ?? true,
merge_brazil_contacts: chatwoot_merge_brazil_contacts ?? false,
import_messages: chatwoot_import_messages ?? true,
days_limit_import_messages: chatwoot_days_limit_import_messages ?? 60,
auto_create: true, auto_create: true,
}); });
} catch (error) { } catch (error) {
@@ -466,6 +533,9 @@ export class InstanceController {
instance: { instance: {
instanceName: instance.instanceName, instanceName: instance.instanceName,
instanceId: instanceId, instanceId: instanceId,
integration: integration,
webhook_wa_business,
access_token_wa_business,
status: 'created', status: 'created',
}, },
hash, hash,
@@ -506,11 +576,14 @@ export class InstanceController {
sign_msg: chatwoot_sign_msg || false, sign_msg: chatwoot_sign_msg || false,
reopen_conversation: chatwoot_reopen_conversation || false, reopen_conversation: chatwoot_reopen_conversation || false,
conversation_pending: chatwoot_conversation_pending || false, conversation_pending: chatwoot_conversation_pending || false,
merge_brazil_contacts: chatwoot_merge_brazil_contacts ?? false,
import_contacts: chatwoot_import_contacts ?? true,
import_messages: chatwoot_import_messages ?? true,
days_limit_import_messages: chatwoot_days_limit_import_messages || 60,
number, number,
name_inbox: instance.instanceName, name_inbox: chatwoot_name_inbox ?? instance.instanceName,
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`, webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
}, },
proxy,
}; };
} catch (error) { } catch (error) {
this.logger.error(error.message[0]); this.logger.error(error.message[0]);
@@ -518,7 +591,7 @@ export class InstanceController {
} }
} }
public async connectToWhatsapp({ instanceName, number = null }: InstanceDto) { public async connectToWhatsapp({ instanceName, number = null, mobile = null }: InstanceDto) {
try { try {
this.logger.verbose('requested connectToWhatsapp from ' + instanceName + ' instance'); this.logger.verbose('requested connectToWhatsapp from ' + instanceName + ' instance');
@@ -541,7 +614,7 @@ export class InstanceController {
if (state == 'close') { if (state == 'close') {
this.logger.verbose('connecting'); this.logger.verbose('connecting');
await instance.connectToWhatsapp(number); await instance.connectToWhatsapp(number, mobile);
await delay(5000); await delay(5000);
return instance.qrCode; return instance.qrCode;
@@ -569,6 +642,7 @@ export class InstanceController {
switch (state) { switch (state) {
case 'open': case 'open':
this.logger.verbose('logging out instance: ' + instanceName); this.logger.verbose('logging out instance: ' + instanceName);
instance.clearCacheChatwoot();
await instance.reloadConnection(); await instance.reloadConnection();
await delay(2000); await delay(2000);
@@ -581,6 +655,20 @@ export class InstanceController {
} }
} }
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) { public async connectionState({ instanceName }: InstanceDto) {
this.logger.verbose('requested connectionState from ' + instanceName + ' instance'); this.logger.verbose('requested connectionState from ' + instanceName + ' instance');
return { return {
@@ -591,19 +679,39 @@ export class InstanceController {
}; };
} }
public async fetchInstances({ instanceName, instanceId }: InstanceDto) { public async fetchInstances({ instanceName, instanceId, number }: InstanceDto, key: string) {
if (instanceName) { const env = this.configService.get<Auth>('AUTHENTICATION').API_KEY;
this.logger.verbose('requested fetchInstances from ' + instanceName + ' instance');
this.logger.verbose('instanceName: ' + instanceName); let name = instanceName;
return this.waMonitor.instanceInfo(instanceName); let arrayReturn = false;
} else if (instanceId) {
return this.waMonitor.instanceInfoById(instanceId); if (env.KEY !== key) {
const instanceByKey = await this.repository.auth.findByKey(key);
if (instanceByKey) {
name = instanceByKey._id;
arrayReturn = true;
} else {
throw new UnauthorizedException();
}
}
if (name) {
this.logger.verbose('requested fetchInstances from ' + name + ' instance');
this.logger.verbose('instanceName: ' + name);
return this.waMonitor.instanceInfo(name, arrayReturn);
} else if (instanceId || number) {
return this.waMonitor.instanceInfoById(instanceId, number);
} }
this.logger.verbose('requested fetchInstances (all instances)'); this.logger.verbose('requested fetchInstances (all instances)');
return this.waMonitor.instanceInfo(); 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) { public async logout({ instanceName }: InstanceDto) {
this.logger.verbose('requested logout from ' + instanceName + ' instance'); this.logger.verbose('requested logout from ' + instanceName + ' instance');
const { instance } = await this.connectionState({ instanceName }); const { instance } = await this.connectionState({ instanceName });
@@ -613,11 +721,7 @@ export class InstanceController {
} }
try { try {
this.logger.verbose('logging out instance: ' + instanceName); this.waMonitor.waInstances[instanceName]?.logoutInstance();
await this.waMonitor.waInstances[instanceName]?.client?.logout('Log out instance: ' + instanceName);
this.logger.verbose('close connection instance: ' + instanceName);
this.waMonitor.waInstances[instanceName]?.client?.ws?.close();
return { status: 'SUCCESS', error: false, response: { message: 'Instance logged out' } }; return { status: 'SUCCESS', error: false, response: { message: 'Instance logged out' } };
} catch (error) { } catch (error) {
@@ -634,6 +738,7 @@ export class InstanceController {
} }
try { try {
this.waMonitor.waInstances[instanceName]?.removeRabbitmqQueues(); this.waMonitor.waInstances[instanceName]?.removeRabbitmqQueues();
this.waMonitor.waInstances[instanceName]?.clearCacheChatwoot();
if (instance.state === 'connecting') { if (instance.state === 'connecting') {
this.logger.verbose('logging out instance: ' + instanceName); this.logger.verbose('logging out instance: ' + instanceName);
@@ -643,10 +748,15 @@ export class InstanceController {
this.logger.verbose('deleting instance: ' + instanceName); this.logger.verbose('deleting instance: ' + instanceName);
this.waMonitor.waInstances[instanceName].sendDataWebhook(Events.INSTANCE_DELETE, { try {
instanceName, this.waMonitor.waInstances[instanceName].sendDataWebhook(Events.INSTANCE_DELETE, {
instanceId: (await this.repository.auth.find(instanceName))?.instanceId, instanceName,
}); instanceId: (await this.repository.auth.find(instanceName))?.instanceId,
});
} catch (error) {
this.logger.error(error);
}
delete this.waMonitor.waInstances[instanceName]; delete this.waMonitor.waInstances[instanceName];
this.eventEmitter.emit('remove.instance', instanceName, 'inner'); this.eventEmitter.emit('remove.instance', instanceName, 'inner');
return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } }; return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } };

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, SendReactionDto,
SendStatusDto, SendStatusDto,
SendStickerDto, SendStickerDto,
SendTemplateDto,
SendTextDto, SendTextDto,
} from '../dto/sendMessage.dto'; } from '../dto/sendMessage.dto';
import { WAMonitoringService } from '../services/monitor.service'; import { WAMonitoringService } from '../services/monitor.service';
@@ -28,6 +29,11 @@ export class SendMessageController {
return await this.waMonitor.waInstances[instanceName].textMessage(data); 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) { public async sendMedia({ instanceName }: InstanceDto, data: SendMediaDto) {
logger.verbose('requested sendMedia from ' + instanceName + ' instance'); 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 { Logger } from '../../config/logger.config';
// import { BadRequestException } from '../../exceptions';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../dto/instance.dto';
import { SettingsDto } from '../dto/settings.dto'; import { SettingsDto } from '../dto/settings.dto';
import { SettingsService } from '../services/settings.service'; import { SettingsService } from '../services/settings.service';

View File

@@ -4,12 +4,13 @@ import { Logger } from '../../config/logger.config';
import { BadRequestException } from '../../exceptions'; import { BadRequestException } from '../../exceptions';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../dto/instance.dto';
import { WebhookDto } from '../dto/webhook.dto'; import { WebhookDto } from '../dto/webhook.dto';
import { WAMonitoringService } from '../services/monitor.service';
import { WebhookService } from '../services/webhook.service'; import { WebhookService } from '../services/webhook.service';
const logger = new Logger('WebhookController'); const logger = new Logger('WebhookController');
export class WebhookController { export class WebhookController {
constructor(private readonly webhookService: WebhookService) {} constructor(private readonly webhookService: WebhookService, private readonly waMonitor: WAMonitoringService) {}
public async createWebhook(instance: InstanceDto, data: WebhookDto) { public async createWebhook(instance: InstanceDto, data: WebhookDto) {
logger.verbose('requested createWebhook from ' + instance.instanceName + ' instance'); logger.verbose('requested createWebhook from ' + instance.instanceName + ' instance');
@@ -46,6 +47,8 @@ export class WebhookController {
'GROUP_UPDATE', 'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE', 'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE', 'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL', 'CALL',
'NEW_JWT_TOKEN', 'NEW_JWT_TOKEN',
'TYPEBOT_START', 'TYPEBOT_START',
@@ -61,4 +64,9 @@ export class WebhookController {
logger.verbose('requested findWebhook from ' + instance.instanceName + ' instance'); logger.verbose('requested findWebhook from ' + instance.instanceName + ' instance');
return this.webhookService.find(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, WAPresence, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '@whiskeysockets/baileys'; import { proto, WAPresence, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '@whiskeysockets/baileys';
export class OnWhatsAppDto { export class OnWhatsAppDto {
constructor(public readonly jid: string, public readonly exists: boolean, public readonly name?: string) {} constructor(
public readonly jid: string,
public readonly exists: boolean,
public readonly number: string,
public readonly name?: string,
) {}
} }
export class getBase64FromMediaMessageDto { export class getBase64FromMediaMessageDto {
@@ -26,8 +31,12 @@ export class NumberBusiness {
message?: string; message?: string;
description?: string; description?: string;
email?: string; email?: string;
websites?: string[];
website?: string[]; website?: string[];
address?: string; address?: string;
about?: string;
vertical?: string;
profilehandle?: string;
} }
export class ProfileNameDto { export class ProfileNameDto {
@@ -64,6 +73,11 @@ export class ArchiveChatDto {
archive: boolean; archive: boolean;
} }
export class MarkChatUnreadDto {
lastMessage?: LastMessage;
chat?: string;
}
class PrivacySetting { class PrivacySetting {
readreceipts: WAReadReceiptsValue; readreceipts: WAReadReceiptsValue;
profile: WAPrivacyValue; profile: WAPrivacyValue;
@@ -100,3 +114,14 @@ export class SendPresenceDto extends Metadata {
delay: number; 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; inviteCode: string;
} }
export class AcceptGroupInvite {
inviteCode: string;
}
export class GroupSendInvite { export class GroupSendInvite {
groupJid: string; groupJid: string;
description: string; description: string;

View File

@@ -1,8 +1,14 @@
import { WAPresence } from '@whiskeysockets/baileys';
import { ProxyDto } from './proxy.dto';
export class InstanceDto { export class InstanceDto {
instanceName: string; instanceName: string;
instanceId?: string; instanceId?: string;
qrcode?: boolean; qrcode?: boolean;
number?: string; number?: string;
mobile?: boolean;
integration?: string;
token?: string; token?: string;
webhook?: string; webhook?: string;
webhook_by_events?: boolean; webhook_by_events?: boolean;
@@ -14,12 +20,18 @@ export class InstanceDto {
always_online?: boolean; always_online?: boolean;
read_messages?: boolean; read_messages?: boolean;
read_status?: boolean; read_status?: boolean;
sync_full_history?: boolean;
chatwoot_account_id?: string; chatwoot_account_id?: string;
chatwoot_token?: string; chatwoot_token?: string;
chatwoot_url?: string; chatwoot_url?: string;
chatwoot_sign_msg?: boolean; chatwoot_sign_msg?: boolean;
chatwoot_reopen_conversation?: boolean; chatwoot_reopen_conversation?: boolean;
chatwoot_conversation_pending?: boolean; chatwoot_conversation_pending?: boolean;
chatwoot_merge_brazil_contacts?: boolean;
chatwoot_import_contacts?: boolean;
chatwoot_import_messages?: boolean;
chatwoot_days_limit_import_messages?: number;
chatwoot_name_inbox?: string;
websocket_enabled?: boolean; websocket_enabled?: boolean;
websocket_events?: string[]; websocket_events?: string[];
rabbitmq_enabled?: boolean; rabbitmq_enabled?: boolean;
@@ -33,5 +45,9 @@ export class InstanceDto {
typebot_delay_message?: number; typebot_delay_message?: number;
typebot_unknown_message?: string; typebot_unknown_message?: string;
typebot_listening_from_me?: boolean; typebot_listening_from_me?: boolean;
proxy?: string; 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

@@ -142,6 +142,16 @@ export class ContactMessage {
email?: string; email?: string;
url?: string; url?: string;
} }
export class TemplateMessage {
name: string;
language: string;
components: any;
}
export class SendTemplateDto extends Metadata {
templateMessage: TemplateMessage;
}
export class SendContactDto extends Metadata { export class SendContactDto extends Metadata {
contactMessage: ContactMessage[]; contactMessage: ContactMessage[];
} }

View File

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

View File

@@ -3,12 +3,12 @@ import { NextFunction, Request, Response } from 'express';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import { name } from '../../../package.json'; import { name } from '../../../package.json';
import { Auth, configService } from '../../config/env.config'; import { Auth, configService, Database } from '../../config/env.config';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../config/logger.config';
import { ForbiddenException, UnauthorizedException } from '../../exceptions'; import { ForbiddenException, UnauthorizedException } from '../../exceptions';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../dto/instance.dto';
import { repository } from '../server.module';
import { JwtPayload } from '../services/auth.service'; import { JwtPayload } from '../services/auth.service';
import { repository } from '../whatsapp.module';
const logger = new Logger('GUARD'); const logger = new Logger('GUARD');
@@ -58,6 +58,11 @@ async function jwtGuard(req: Request, res: Response, next: NextFunction) {
async function apikey(req: Request, _: Response, next: NextFunction) { async function apikey(req: Request, _: Response, next: NextFunction) {
const env = configService.get<Auth>('AUTHENTICATION').API_KEY; const env = configService.get<Auth>('AUTHENTICATION').API_KEY;
const key = req.get('apikey'); const key = req.get('apikey');
const db = configService.get<Database>('DATABASE');
if (!key) {
throw new UnauthorizedException();
}
if (env.KEY === key) { if (env.KEY === key) {
return next(); return next();
@@ -66,12 +71,21 @@ async function apikey(req: Request, _: Response, next: NextFunction) {
if ((req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) && !key) { if ((req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) && !key) {
throw new ForbiddenException('Missing global api key', 'The global api key must be set'); throw new ForbiddenException('Missing global api key', 'The global api key must be set');
} }
const param = req.params as unknown as InstanceDto;
try { try {
const param = req.params as unknown as InstanceDto; if (param?.instanceName) {
const instanceKey = await repository.auth.find(param.instanceName); const instanceKey = await repository.auth.find(param.instanceName);
if (instanceKey.apikey === key) { if (instanceKey?.apikey === key) {
return next(); return next();
}
} else {
if (req.originalUrl.includes('/instance/fetchInstances') && db.ENABLED) {
const instanceByKey = await repository.auth.findByKey(key);
if (instanceByKey) {
return next();
}
}
} }
} catch (error) { } catch (error) {
logger.error(error); logger.error(error);

View File

@@ -2,7 +2,7 @@ import { NextFunction, Request, Response } from 'express';
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import { join } from 'path'; 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 { INSTANCE_DIR } from '../../config/path.config';
import { import {
BadRequestException, BadRequestException,
@@ -12,17 +12,18 @@ import {
} from '../../exceptions'; } from '../../exceptions';
import { dbserver } from '../../libs/db.connect'; import { dbserver } from '../../libs/db.connect';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../dto/instance.dto';
import { cache, waMonitor } from '../whatsapp.module'; import { cache, waMonitor } from '../server.module';
async function getInstance(instanceName: string) { async function getInstance(instanceName: string) {
try { try {
const db = configService.get<Database>('DATABASE'); const db = configService.get<Database>('DATABASE');
const redisConf = configService.get<Redis>('REDIS'); const cacheConf = configService.get<CacheConf>('CACHE');
const exists = !!waMonitor.waInstances[instanceName]; const exists = !!waMonitor.waInstances[instanceName];
if (redisConf.ENABLED) { if (cacheConf.REDIS.ENABLED && cacheConf.REDIS.SAVE_INSTANCES) {
const keyExists = await cache.keyExists(); const keyExists = await cache.has(instanceName);
return exists || keyExists; return exists || keyExists;
} }

View File

@@ -1,6 +1,6 @@
import { Logger } from '../../config/logger.config'; import { Logger } from '../../../../config/logger.config';
import { InstanceDto } from '../../../dto/instance.dto';
import { ChamaaiDto } from '../dto/chamaai.dto'; import { ChamaaiDto } from '../dto/chamaai.dto';
import { InstanceDto } from '../dto/instance.dto';
import { ChamaaiService } from '../services/chamaai.service'; import { ChamaaiService } from '../services/chamaai.service';
const logger = new Logger('ChamaaiController'); const logger = new Logger('ChamaaiController');

View File

@@ -1,6 +1,6 @@
import { Schema } from 'mongoose'; import { Schema } from 'mongoose';
import { dbserver } from '../../libs/db.connect'; import { dbserver } from '../../../../libs/db.connect';
export class ChamaaiRaw { export class ChamaaiRaw {
_id?: string; _id?: string;

View File

@@ -1,10 +1,10 @@
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { join } from 'path'; import { join } from 'path';
import { ConfigService } from '../../config/env.config'; import { ConfigService } from '../../../../config/env.config';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../../../config/logger.config';
import { IInsert, Repository } from '../abstract/abstract.repository'; import { IInsert, Repository } from '../../../abstract/abstract.repository';
import { ChamaaiRaw, IChamaaiModel } from '../models'; import { ChamaaiRaw, IChamaaiModel } from '../../../models';
export class ChamaaiRepository extends Repository { export class ChamaaiRepository extends Repository {
constructor(private readonly chamaaiModel: IChamaaiModel, private readonly configService: ConfigService) { constructor(private readonly chamaaiModel: IChamaaiModel, private readonly configService: ConfigService) {

View File

@@ -1,12 +1,12 @@
import { RequestHandler, Router } from 'express'; import { RequestHandler, Router } from 'express';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../../../config/logger.config';
import { chamaaiSchema, instanceNameSchema } from '../../validate/validate.schema'; import { chamaaiSchema, instanceNameSchema } from '../../../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router'; 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'; import { ChamaaiDto } from '../dto/chamaai.dto';
import { InstanceDto } from '../dto/instance.dto';
import { chamaaiController } from '../whatsapp.module';
import { HttpStatus } from './index.router';
const logger = new Logger('ChamaaiRouter'); const logger = new Logger('ChamaaiRouter');

View File

@@ -2,13 +2,13 @@ import axios from 'axios';
import { writeFileSync } from 'fs'; import { writeFileSync } from 'fs';
import path from 'path'; import path from 'path';
import { ConfigService, HttpServer } from '../../config/env.config'; import { ConfigService, HttpServer } from '../../../../config/env.config';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../../../config/logger.config';
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'; import { ChamaaiDto } from '../dto/chamaai.dto';
import { InstanceDto } from '../dto/instance.dto';
import { ChamaaiRaw } from '../models';
import { Events } from '../types/wa.types';
import { WAMonitoringService } from './monitor.service';
export class ChamaaiService { export class ChamaaiService {
constructor(private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService) {} constructor(private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService) {}

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,13 +1,15 @@
import { isURL } from 'class-validator'; import { isURL } from 'class-validator';
import { ConfigService, HttpServer } from '../../config/env.config'; import { CacheEngine } from '../../../../cache/cacheengine';
import { Logger } from '../../config/logger.config'; import { ConfigService, HttpServer } from '../../../../config/env.config';
import { BadRequestException } from '../../exceptions'; 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 { ChatwootDto } from '../dto/chatwoot.dto';
import { InstanceDto } from '../dto/instance.dto';
import { RepositoryBroker } from '../repository/repository.manager';
import { ChatwootService } from '../services/chatwoot.service'; import { ChatwootService } from '../services/chatwoot.service';
import { waMonitor } from '../whatsapp.module';
const logger = new Logger('ChatwootController'); const logger = new Logger('ChatwootController');
@@ -49,10 +51,17 @@ export class ChatwootController {
data.sign_delimiter = null; data.sign_delimiter = null;
data.reopen_conversation = false; data.reopen_conversation = false;
data.conversation_pending = false; data.conversation_pending = false;
data.import_contacts = false;
data.import_messages = false;
data.merge_brazil_contacts = false;
data.days_limit_import_messages = 0;
data.auto_create = false; 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 = await this.chatwootService.create(instance, data); const result = await this.chatwootService.create(instance, data);
@@ -94,7 +103,9 @@ export class ChatwootController {
public async receiveWebhook(instance: InstanceDto, data: any) { public async receiveWebhook(instance: InstanceDto, data: any) {
logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance'); logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance');
const chatwootService = new ChatwootService(waMonitor, this.configService, this.repository);
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); return chatwootService.receiveWebhook(instance, data);
} }

View File

@@ -9,5 +9,9 @@ export class ChatwootDto {
number?: string; number?: string;
reopen_conversation?: boolean; reopen_conversation?: boolean;
conversation_pending?: boolean; conversation_pending?: boolean;
merge_brazil_contacts?: boolean;
import_contacts?: boolean;
import_messages?: boolean;
days_limit_import_messages?: number;
auto_create?: boolean; 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 { Schema } from 'mongoose';
import { dbserver } from '../../libs/db.connect'; import { dbserver } from '../../../../libs/db.connect';
export class ChatwootRaw { export class ChatwootRaw {
_id?: string; _id?: string;
@@ -14,6 +14,10 @@ export class ChatwootRaw {
number?: string; number?: string;
reopen_conversation?: boolean; reopen_conversation?: boolean;
conversation_pending?: boolean; conversation_pending?: boolean;
merge_brazil_contacts?: boolean;
import_contacts?: boolean;
import_messages?: boolean;
days_limit_import_messages?: number;
} }
const chatwootSchema = new Schema<ChatwootRaw>({ const chatwootSchema = new Schema<ChatwootRaw>({
@@ -28,6 +32,10 @@ const chatwootSchema = new Schema<ChatwootRaw>({
number: { type: String, required: true }, number: { type: String, required: true },
reopen_conversation: { type: Boolean, required: true }, reopen_conversation: { type: Boolean, required: true },
conversation_pending: { type: Boolean, required: true }, conversation_pending: { type: Boolean, required: true },
merge_brazil_contacts: { 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'); export const ChatwootModel = dbserver?.model(ChatwootRaw.name, chatwootSchema, 'chatwoot');

View File

@@ -1,10 +1,10 @@
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { join } from 'path'; import { join } from 'path';
import { ConfigService } from '../../config/env.config'; import { ConfigService } from '../../../../config/env.config';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../../../config/logger.config';
import { IInsert, Repository } from '../abstract/abstract.repository'; import { IInsert, Repository } from '../../../abstract/abstract.repository';
import { ChatwootRaw, IChatwootModel } from '../models'; import { ChatwootRaw, IChatwootModel } from '../../../models';
export class ChatwootRepository extends Repository { export class ChatwootRepository extends Repository {
constructor(private readonly chatwootModel: IChatwootModel, private readonly configService: ConfigService) { constructor(private readonly chatwootModel: IChatwootModel, private readonly configService: ConfigService) {

View File

@@ -1,13 +1,12 @@
import { RequestHandler, Router } from 'express'; import { RequestHandler, Router } from 'express';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../../../config/logger.config';
import { chatwootSchema, instanceNameSchema } from '../../validate/validate.schema'; import { chatwootSchema, instanceNameSchema } from '../../../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router'; 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 { 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'); const logger = new Logger('ChatwootRouter');

View File

@@ -0,0 +1,472 @@
import { inbox } from '@figuro/chatwoot-sdk';
import { proto } from '@whiskeysockets/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,44 @@
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] },
merge_brazil_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

@@ -1,5 +1,5 @@
import { Logger } from '../../config/logger.config'; import { Logger } from '../../../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../../../dto/instance.dto';
import { RabbitmqDto } from '../dto/rabbitmq.dto'; import { RabbitmqDto } from '../dto/rabbitmq.dto';
import { RabbitmqService } from '../services/rabbitmq.service'; import { RabbitmqService } from '../services/rabbitmq.service';
@@ -38,6 +38,8 @@ export class RabbitmqController {
'GROUP_UPDATE', 'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE', 'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE', 'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL', 'CALL',
'NEW_JWT_TOKEN', 'NEW_JWT_TOKEN',
'TYPEBOT_START', 'TYPEBOT_START',

View File

@@ -1,7 +1,7 @@
import * as amqp from 'amqplib/callback_api'; import * as amqp from 'amqplib/callback_api';
import { configService, Rabbitmq } from '../config/env.config'; import { configService, Rabbitmq } from '../../../../config/env.config';
import { Logger } from '../config/logger.config'; import { Logger } from '../../../../config/logger.config';
const logger = new Logger('AMQP'); const logger = new Logger('AMQP');
@@ -42,6 +42,41 @@ export const getAMQP = (): amqp.Channel | null => {
return amqpChannel; return amqpChannel;
}; };
export const initGlobalQueues = () => {
logger.info('Initializing global queues');
const events = configService.get<Rabbitmq>('RABBITMQ').EVENTS;
if (!events) {
logger.warn('No events to initialize on AMQP');
return;
}
const eventKeys = Object.keys(events);
eventKeys.forEach((event) => {
if (events[event] === false) return;
const queueName = `${event.replace(/_/g, '.').toLowerCase()}`;
const amqp = getAMQP();
const exchangeName = 'evolution_exchange';
amqp.assertExchange(exchangeName, 'topic', {
durable: true,
autoDelete: false,
});
amqp.assertQueue(queueName, {
durable: true,
autoDelete: false,
arguments: {
'x-queue-type': 'quorum',
},
});
amqp.bindQueue(queueName, exchangeName, event);
});
};
export const initQueues = (instanceName: string, events: string[]) => { export const initQueues = (instanceName: string, events: string[]) => {
if (!events || !events.length) return; if (!events || !events.length) return;

View File

@@ -1,6 +1,6 @@
import { Schema } from 'mongoose'; import { Schema } from 'mongoose';
import { dbserver } from '../../libs/db.connect'; import { dbserver } from '../../../../libs/db.connect';
export class RabbitmqRaw { export class RabbitmqRaw {
_id?: string; _id?: string;

View File

@@ -1,10 +1,10 @@
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { join } from 'path'; import { join } from 'path';
import { ConfigService } from '../../config/env.config'; import { ConfigService } from '../../../../config/env.config';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../../../config/logger.config';
import { IInsert, Repository } from '../abstract/abstract.repository'; import { IInsert, Repository } from '../../../abstract/abstract.repository';
import { IRabbitmqModel, RabbitmqRaw } from '../models'; import { IRabbitmqModel, RabbitmqRaw } from '../../../models';
export class RabbitmqRepository extends Repository { export class RabbitmqRepository extends Repository {
constructor(private readonly rabbitmqModel: IRabbitmqModel, private readonly configService: ConfigService) { constructor(private readonly rabbitmqModel: IRabbitmqModel, private readonly configService: ConfigService) {

View File

@@ -1,12 +1,12 @@
import { RequestHandler, Router } from 'express'; import { RequestHandler, Router } from 'express';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../../../config/logger.config';
import { instanceNameSchema, rabbitmqSchema } from '../../validate/validate.schema'; import { instanceNameSchema, rabbitmqSchema } from '../../../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router'; import { RouterBroker } from '../../../abstract/abstract.router';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../../../dto/instance.dto';
import { HttpStatus } from '../../../routes/index.router';
import { rabbitmqController } from '../../../server.module';
import { RabbitmqDto } from '../dto/rabbitmq.dto'; import { RabbitmqDto } from '../dto/rabbitmq.dto';
import { rabbitmqController } from '../whatsapp.module';
import { HttpStatus } from './index.router';
const logger = new Logger('RabbitmqRouter'); const logger = new Logger('RabbitmqRouter');

View File

@@ -1,9 +1,9 @@
import { Logger } from '../../config/logger.config'; import { Logger } from '../../../../config/logger.config';
import { initQueues } from '../../libs/amqp.server'; import { InstanceDto } from '../../../dto/instance.dto';
import { InstanceDto } from '../dto/instance.dto'; import { RabbitmqRaw } from '../../../models';
import { WAMonitoringService } from '../../../services/monitor.service';
import { RabbitmqDto } from '../dto/rabbitmq.dto'; import { RabbitmqDto } from '../dto/rabbitmq.dto';
import { RabbitmqRaw } from '../models'; import { initQueues } from '../libs/amqp.server';
import { WAMonitoringService } from './monitor.service';
export class RabbitmqService { export class RabbitmqService {
constructor(private readonly waMonitor: WAMonitoringService) {} constructor(private readonly waMonitor: WAMonitoringService) {}

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

@@ -1,5 +1,5 @@
import { Logger } from '../../config/logger.config'; import { Logger } from '../../../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../../../dto/instance.dto';
import { SqsDto } from '../dto/sqs.dto'; import { SqsDto } from '../dto/sqs.dto';
import { SqsService } from '../services/sqs.service'; import { SqsService } from '../services/sqs.service';
@@ -38,6 +38,8 @@ export class SqsController {
'GROUP_UPDATE', 'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE', 'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE', 'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL', 'CALL',
'NEW_JWT_TOKEN', 'NEW_JWT_TOKEN',
'TYPEBOT_START', 'TYPEBOT_START',

View File

@@ -1,7 +1,7 @@
import { SQS } from 'aws-sdk'; import { SQS } from '@aws-sdk/client-sqs';
import { configService, Sqs } from '../config/env.config'; import { configService, Sqs } from '../../../../config/env.config';
import { Logger } from '../config/logger.config'; import { Logger } from '../../../../config/logger.config';
const logger = new Logger('SQS'); const logger = new Logger('SQS');
@@ -12,8 +12,11 @@ export const initSQS = () => {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
const awsConfig = configService.get<Sqs>('SQS'); const awsConfig = configService.get<Sqs>('SQS');
sqs = new SQS({ sqs = new SQS({
accessKeyId: awsConfig.ACCESS_KEY_ID, credentials: {
secretAccessKey: awsConfig.SECRET_ACCESS_KEY, accessKeyId: awsConfig.ACCESS_KEY_ID,
secretAccessKey: awsConfig.SECRET_ACCESS_KEY,
},
region: awsConfig.REGION, region: awsConfig.REGION,
}); });

View File

@@ -1,6 +1,6 @@
import { Schema } from 'mongoose'; import { Schema } from 'mongoose';
import { dbserver } from '../../libs/db.connect'; import { dbserver } from '../../../../libs/db.connect';
export class SqsRaw { export class SqsRaw {
_id?: string; _id?: string;

View File

@@ -1,10 +1,10 @@
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { join } from 'path'; import { join } from 'path';
import { ConfigService } from '../../config/env.config'; import { ConfigService } from '../../../../config/env.config';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../../../config/logger.config';
import { IInsert, Repository } from '../abstract/abstract.repository'; import { IInsert, Repository } from '../../../abstract/abstract.repository';
import { ISqsModel, SqsRaw } from '../models'; import { ISqsModel, SqsRaw } from '../../../models';
export class SqsRepository extends Repository { export class SqsRepository extends Repository {
constructor(private readonly sqsModel: ISqsModel, private readonly configService: ConfigService) { constructor(private readonly sqsModel: ISqsModel, private readonly configService: ConfigService) {

View File

@@ -1,12 +1,12 @@
import { RequestHandler, Router } from 'express'; import { RequestHandler, Router } from 'express';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../../../config/logger.config';
import { instanceNameSchema, sqsSchema } from '../../validate/validate.schema'; import { instanceNameSchema, sqsSchema } from '../../../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router'; import { RouterBroker } from '../../../abstract/abstract.router';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../../../dto/instance.dto';
import { HttpStatus } from '../../../routes/index.router';
import { sqsController } from '../../../server.module';
import { SqsDto } from '../dto/sqs.dto'; import { SqsDto } from '../dto/sqs.dto';
import { sqsController } from '../whatsapp.module';
import { HttpStatus } from './index.router';
const logger = new Logger('SqsRouter'); const logger = new Logger('SqsRouter');

View File

@@ -1,9 +1,9 @@
import { Logger } from '../../config/logger.config'; import { Logger } from '../../../../config/logger.config';
import { initQueues } from '../../libs/sqs.server'; import { InstanceDto } from '../../../dto/instance.dto';
import { InstanceDto } from '../dto/instance.dto'; import { SqsRaw } from '../../../models';
import { WAMonitoringService } from '../../../services/monitor.service';
import { SqsDto } from '../dto/sqs.dto'; import { SqsDto } from '../dto/sqs.dto';
import { SqsRaw } from '../models'; import { initQueues } from '../libs/sqs.server';
import { WAMonitoringService } from './monitor.service';
export class SqsService { export class SqsService {
constructor(private readonly waMonitor: WAMonitoringService) {} constructor(private readonly waMonitor: WAMonitoringService) {}

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

@@ -1,5 +1,5 @@
import { Logger } from '../../config/logger.config'; import { Logger } from '../../../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../../../dto/instance.dto';
import { TypebotDto } from '../dto/typebot.dto'; import { TypebotDto } from '../dto/typebot.dto';
import { TypebotService } from '../services/typebot.service'; import { TypebotService } from '../services/typebot.service';

View File

@@ -10,6 +10,7 @@ export class Session {
export class PrefilledVariables { export class PrefilledVariables {
remoteJid?: string; remoteJid?: string;
pushName?: string; pushName?: string;
messageType?: string;
additionalData?: { [key: string]: any }; additionalData?: { [key: string]: any };
} }

View File

@@ -1,6 +1,6 @@
import { Schema } from 'mongoose'; import { Schema } from 'mongoose';
import { dbserver } from '../../libs/db.connect'; import { dbserver } from '../../../../libs/db.connect';
class Session { class Session {
remoteJid?: string; remoteJid?: string;

View File

@@ -1,10 +1,10 @@
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { join } from 'path'; import { join } from 'path';
import { ConfigService } from '../../config/env.config'; import { ConfigService } from '../../../../config/env.config';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../../../config/logger.config';
import { IInsert, Repository } from '../abstract/abstract.repository'; import { IInsert, Repository } from '../../../abstract/abstract.repository';
import { ITypebotModel, TypebotRaw } from '../models'; import { ITypebotModel, TypebotRaw } from '../../../models';
export class TypebotRepository extends Repository { export class TypebotRepository extends Repository {
constructor(private readonly typebotModel: ITypebotModel, private readonly configService: ConfigService) { constructor(private readonly typebotModel: ITypebotModel, private readonly configService: ConfigService) {

View File

@@ -1,17 +1,17 @@
import { RequestHandler, Router } from 'express'; import { RequestHandler, Router } from 'express';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../../../config/logger.config';
import { import {
instanceNameSchema, instanceNameSchema,
typebotSchema, typebotSchema,
typebotStartSchema, typebotStartSchema,
typebotStatusSchema, typebotStatusSchema,
} from '../../validate/validate.schema'; } from '../../../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router'; import { RouterBroker } from '../../../abstract/abstract.router';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../../../dto/instance.dto';
import { HttpStatus } from '../../../routes/index.router';
import { typebotController } from '../../../server.module';
import { TypebotDto } from '../dto/typebot.dto'; import { TypebotDto } from '../dto/typebot.dto';
import { typebotController } from '../whatsapp.module';
import { HttpStatus } from './index.router';
const logger = new Logger('TypebotRouter'); const logger = new Logger('TypebotRouter');

View File

@@ -1,13 +1,13 @@
import axios from 'axios'; import axios from 'axios';
import EventEmitter2 from 'eventemitter2'; import EventEmitter2 from 'eventemitter2';
import { ConfigService, Typebot } from '../../config/env.config'; import { ConfigService, Typebot } from '../../../../config/env.config';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../../../dto/instance.dto';
import { MessageRaw } from '../../../models';
import { WAMonitoringService } from '../../../services/monitor.service';
import { Events } from '../../../types/wa.types';
import { Session, TypebotDto } from '../dto/typebot.dto'; import { Session, TypebotDto } from '../dto/typebot.dto';
import { MessageRaw } from '../models';
import { Events } from '../types/wa.types';
import { WAMonitoringService } from './monitor.service';
export class TypebotService { export class TypebotService {
constructor( constructor(
@@ -270,15 +270,27 @@ export class TypebotService {
private getTypeMessage(msg: any) { private getTypeMessage(msg: any) {
this.logger.verbose('get type message'); this.logger.verbose('get type message');
const types = { const types = {
conversation: msg.conversation, conversation: msg.conversation,
extendedTextMessage: msg.extendedTextMessage?.text, 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,
}; };
this.logger.verbose('type message: ' + types); const messageType = Object.keys(types).find((key) => types[key] !== undefined) || 'unknown';
return types; this.logger.verbose('Type message: ' + JSON.stringify(types));
return { ...types, messageType };
} }
private getMessageContent(types: any) { private getMessageContent(types: any) {
@@ -304,6 +316,101 @@ export class TypebotService {
return 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) { public async createNewSession(instance: InstanceDto, data: any) {
if (data.remoteJid === 'status@broadcast') return; if (data.remoteJid === 'status@broadcast') return;
const id = Math.floor(Math.random() * 10000000000).toString(); const id = Math.floor(Math.random() * 10000000000).toString();
@@ -389,6 +496,7 @@ export class TypebotService {
input, input,
clientSideActions, clientSideActions,
this.eventEmitter, this.eventEmitter,
applyFormatting,
).catch((err) => { ).catch((err) => {
console.error('Erro ao processar mensagens:', err); console.error('Erro ao processar mensagens:', err);
}); });
@@ -404,72 +512,81 @@ export class TypebotService {
return null; return null;
} }
async function processMessages(instance, messages, input, clientSideActions, eventEmitter) { function applyFormatting(element) {
for (const message of messages) { let text = '';
const wait = findItemAndGetSecondsToWait(clientSideActions, message.id);
if (element.text) {
text += element.text;
}
if (element.children && element.type !== 'a') {
for (const child of element.children) {
text += applyFormatting(child);
}
}
if (element.type === 'p') {
text = text.trim() + '\n';
}
if (element.type === 'ol') {
text =
'\n' +
text
.split('\n')
.map((line, index) => (line ? `${index + 1}. ${line}` : ''))
.join('\n');
}
if (element.type === 'li') {
text = text
.split('\n')
.map((line) => (line ? ` ${line}` : ''))
.join('\n');
}
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') { if (message.type === 'text') {
let formattedText = ''; let formattedText = '';
let linkPreview = false;
for (const richText of message.content.richText) { for (const richText of message.content.richText) {
if (richText.type === 'variable') { for (const element of richText.children) {
for (const child of richText.children) { formattedText += applyFormatting(element);
for (const grandChild of child.children) {
formattedText += grandChild.text;
}
}
} else {
for (const element of richText.children) {
let text = '';
if (element.type === 'inline-variable') {
for (const child of element.children) {
for (const grandChild of child.children) {
text += grandChild.text;
}
}
} else if (element.text) {
text = element.text;
}
// if (element.text) {
// text = element.text;
// }
if (element.bold) {
text = `*${text}*`;
}
if (element.italic) {
text = `_${text}_`;
}
if (element.underline) {
text = `*${text}*`;
}
if (element.url) {
const linkText = element.children[0].text;
text = `[${linkText}](${element.url})`;
linkPreview = true;
}
formattedText += text;
}
} }
formattedText += '\n'; formattedText += '\n';
} }
formattedText = formattedText.replace(/\n$/, ''); formattedText = formattedText.replace(/\*\*/g, '').replace(/__/, '').replace(/~~/, '').replace(/\n$/, '');
await instance.textMessage({ await instance.textMessage({
number: remoteJid.split('@')[0], number: remoteJid.split('@')[0],
options: { options: {
delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000, delay: instance.localTypebot.delay_message || 1000,
presence: 'composing', presence: 'composing',
linkPreview: linkPreview,
}, },
textMessage: { textMessage: {
text: formattedText, text: formattedText,
@@ -481,7 +598,7 @@ export class TypebotService {
await instance.mediaMessage({ await instance.mediaMessage({
number: remoteJid.split('@')[0], number: remoteJid.split('@')[0],
options: { options: {
delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000, delay: instance.localTypebot.delay_message || 1000,
presence: 'composing', presence: 'composing',
}, },
mediaMessage: { mediaMessage: {
@@ -495,7 +612,7 @@ export class TypebotService {
await instance.mediaMessage({ await instance.mediaMessage({
number: remoteJid.split('@')[0], number: remoteJid.split('@')[0],
options: { options: {
delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000, delay: instance.localTypebot.delay_message || 1000,
presence: 'composing', presence: 'composing',
}, },
mediaMessage: { mediaMessage: {
@@ -509,7 +626,7 @@ export class TypebotService {
await instance.audioWhatsapp({ await instance.audioWhatsapp({
number: remoteJid.split('@')[0], number: remoteJid.split('@')[0],
options: { options: {
delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000, delay: instance.localTypebot.delay_message || 1000,
presence: 'recording', presence: 'recording',
encoding: true, encoding: true,
}, },
@@ -518,6 +635,12 @@ export class TypebotService {
}, },
}); });
} }
const wait = findItemAndGetSecondsToWait(clientSideActions, message.id);
if (wait) {
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
}
} }
if (input) { if (input) {
@@ -535,9 +658,8 @@ export class TypebotService {
await instance.textMessage({ await instance.textMessage({
number: remoteJid.split('@')[0], number: remoteJid.split('@')[0],
options: { options: {
delay: 1200, delay: instance.localTypebot.delay_message || 1000,
presence: 'composing', presence: 'composing',
linkPreview: false,
}, },
textMessage: { textMessage: {
text: formattedText, text: formattedText,
@@ -563,6 +685,7 @@ export class TypebotService {
const delay_message = findTypebot.delay_message; const delay_message = findTypebot.delay_message;
const unknown_message = findTypebot.unknown_message; const unknown_message = findTypebot.unknown_message;
const listening_from_me = findTypebot.listening_from_me; const listening_from_me = findTypebot.listening_from_me;
const messageType = this.getTypeMessage(msg.message).messageType;
const session = sessions.find((session) => session.remoteJid === remoteJid); const session = sessions.find((session) => session.remoteJid === remoteJid);
@@ -685,6 +808,9 @@ export class TypebotService {
sessions: sessions, sessions: sessions,
remoteJid: remoteJid, remoteJid: remoteJid,
pushName: msg.pushName, pushName: msg.pushName,
prefilledVariables: {
messageType: messageType,
},
}); });
await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions); await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions);
@@ -709,7 +835,7 @@ export class TypebotService {
} }
if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) { if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) {
sessions.splice(sessions.indexOf(session), 1); const newSessions = await this.clearSessions(instance, remoteJid);
const typebotData = { const typebotData = {
enabled: findTypebot.enabled, enabled: findTypebot.enabled,
@@ -720,7 +846,7 @@ export class TypebotService {
delay_message: delay_message, delay_message: delay_message,
unknown_message: unknown_message, unknown_message: unknown_message,
listening_from_me: listening_from_me, listening_from_me: listening_from_me,
sessions, sessions: newSessions,
}; };
this.create(instance, typebotData); this.create(instance, typebotData);
@@ -801,7 +927,7 @@ export class TypebotService {
} }
if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) { if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) {
sessions.splice(sessions.indexOf(session), 1); const newSessions = await this.clearSessions(instance, remoteJid);
const typebotData = { const typebotData = {
enabled: findTypebot.enabled, enabled: findTypebot.enabled,
@@ -812,7 +938,7 @@ export class TypebotService {
delay_message: delay_message, delay_message: delay_message,
unknown_message: unknown_message, unknown_message: unknown_message,
listening_from_me: listening_from_me, listening_from_me: listening_from_me,
sessions, sessions: newSessions,
}; };
this.create(instance, typebotData); this.create(instance, typebotData);

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

@@ -1,5 +1,5 @@
import { Logger } from '../../config/logger.config'; import { Logger } from '../../../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../../../dto/instance.dto';
import { WebsocketDto } from '../dto/websocket.dto'; import { WebsocketDto } from '../dto/websocket.dto';
import { WebsocketService } from '../services/websocket.service'; import { WebsocketService } from '../services/websocket.service';
@@ -38,6 +38,8 @@ export class WebsocketController {
'GROUP_UPDATE', 'GROUP_UPDATE',
'GROUP_PARTICIPANTS_UPDATE', 'GROUP_PARTICIPANTS_UPDATE',
'CONNECTION_UPDATE', 'CONNECTION_UPDATE',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CALL', 'CALL',
'NEW_JWT_TOKEN', 'NEW_JWT_TOKEN',
'TYPEBOT_START', 'TYPEBOT_START',

View File

@@ -1,8 +1,8 @@
import { Server } from 'http'; import { Server } from 'http';
import { Server as SocketIO } from 'socket.io'; import { Server as SocketIO } from 'socket.io';
import { configService, Cors, Websocket } from '../config/env.config'; import { configService, Cors, Websocket } from '../../../../config/env.config';
import { Logger } from '../config/logger.config'; import { Logger } from '../../../../config/logger.config';
const logger = new Logger('Socket'); const logger = new Logger('Socket');

View File

@@ -1,6 +1,6 @@
import { Schema } from 'mongoose'; import { Schema } from 'mongoose';
import { dbserver } from '../../libs/db.connect'; import { dbserver } from '../../../../libs/db.connect';
export class WebsocketRaw { export class WebsocketRaw {
_id?: string; _id?: string;

View File

@@ -1,10 +1,10 @@
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { join } from 'path'; import { join } from 'path';
import { ConfigService } from '../../config/env.config'; import { ConfigService } from '../../../../config/env.config';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../../../config/logger.config';
import { IInsert, Repository } from '../abstract/abstract.repository'; import { IInsert, Repository } from '../../../abstract/abstract.repository';
import { IWebsocketModel, WebsocketRaw } from '../models'; import { IWebsocketModel, WebsocketRaw } from '../../../models';
export class WebsocketRepository extends Repository { export class WebsocketRepository extends Repository {
constructor(private readonly websocketModel: IWebsocketModel, private readonly configService: ConfigService) { constructor(private readonly websocketModel: IWebsocketModel, private readonly configService: ConfigService) {

View File

@@ -1,12 +1,12 @@
import { RequestHandler, Router } from 'express'; import { RequestHandler, Router } from 'express';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../../../config/logger.config';
import { instanceNameSchema, websocketSchema } from '../../validate/validate.schema'; import { instanceNameSchema, websocketSchema } from '../../../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router'; import { RouterBroker } from '../../../abstract/abstract.router';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../../../dto/instance.dto';
import { HttpStatus } from '../../../routes/index.router';
import { websocketController } from '../../../server.module';
import { WebsocketDto } from '../dto/websocket.dto'; import { WebsocketDto } from '../dto/websocket.dto';
import { websocketController } from '../whatsapp.module';
import { HttpStatus } from './index.router';
const logger = new Logger('WebsocketRouter'); const logger = new Logger('WebsocketRouter');

View File

@@ -1,8 +1,8 @@
import { Logger } from '../../config/logger.config'; import { Logger } from '../../../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../../../dto/instance.dto';
import { WebsocketRaw } from '../../../models';
import { WAMonitoringService } from '../../../services/monitor.service';
import { WebsocketDto } from '../dto/websocket.dto'; import { WebsocketDto } from '../dto/websocket.dto';
import { WebsocketRaw } from '../models';
import { WAMonitoringService } from './monitor.service';
export class WebsocketService { export class WebsocketService {
constructor(private readonly waMonitor: WAMonitoringService) {} constructor(private readonly waMonitor: WAMonitoringService) {}

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'),
};

View File

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

View File

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

15
src/api/models/index.ts Normal file
View File

@@ -0,0 +1,15 @@
export * from '../integrations/chamaai/models/chamaai.model';
export * from '../integrations/chatwoot/models/chatwoot.model';
export * from '../integrations/rabbitmq/models/rabbitmq.model';
export * from '../integrations/sqs/models/sqs.model';
export * from '../integrations/typebot/models/typebot.model';
export * from '../integrations/websocket/models/websocket.model';
export * from './auth.model';
export * from './chat.model';
export * from './contact.model';
export * from './integration.model';
export * from './label.model';
export * from './message.model';
export * from './proxy.model';
export * from './settings.model';
export * from './webhook.model';

View File

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

View File

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

View File

@@ -14,6 +14,8 @@ class ChatwootMessage {
messageId?: number; messageId?: number;
inboxId?: number; inboxId?: number;
conversationId?: number; conversationId?: number;
contactInbox?: { sourceId: string };
isRead?: boolean;
} }
export class MessageRaw { export class MessageRaw {
@@ -25,12 +27,22 @@ export class MessageRaw {
messageType?: string; messageType?: string;
messageTimestamp?: number | Long.Long; messageTimestamp?: number | Long.Long;
owner: string; owner: string;
source?: 'android' | 'web' | 'ios'; source?: 'android' | 'web' | 'ios' | 'unknown' | 'desktop';
source_id?: string; source_id?: string;
source_reply_id?: string; source_reply_id?: string;
chatwoot?: ChatwootMessage; chatwoot?: ChatwootMessage;
contextInfo?: any;
status?: wa.StatusMessage | any;
} }
type MessageRawBoolean<T> = {
[P in keyof T]?: 0 | 1;
};
export type MessageRawSelect = Omit<Omit<MessageRawBoolean<MessageRaw>, 'key'>, 'chatwoot'> & {
key?: MessageRawBoolean<Key>;
chatwoot?: MessageRawBoolean<ChatwootMessage>;
};
const messageSchema = new Schema<MessageRaw>({ const messageSchema = new Schema<MessageRaw>({
_id: { type: String, _id: true }, _id: { type: String, _id: true },
key: { key: {
@@ -43,13 +55,15 @@ const messageSchema = new Schema<MessageRaw>({
participant: { type: String }, participant: { type: String },
messageType: { type: String }, messageType: { type: String },
message: { type: Object }, message: { type: Object },
source: { type: String, minlength: 3, enum: ['android', 'web', 'ios'] }, source: { type: String, minlength: 3, enum: ['android', 'web', 'ios', 'unknown', 'desktop'] },
messageTimestamp: { type: Number, required: true }, messageTimestamp: { type: Number, required: true },
owner: { type: String, required: true, minlength: 1 }, owner: { type: String, required: true, minlength: 1 },
chatwoot: { chatwoot: {
messageId: { type: Number }, messageId: { type: Number },
inboxId: { type: Number }, inboxId: { type: Number },
conversationId: { type: Number }, conversationId: { type: Number },
contactInbox: { type: Object },
isRead: { type: Boolean },
}, },
}); });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,20 +4,22 @@ import { join } from 'path';
import { Auth, ConfigService, Database } from '../../config/env.config'; import { Auth, ConfigService, Database } from '../../config/env.config';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../config/logger.config';
import { ChamaaiRepository } from '../integrations/chamaai/repository/chamaai.repository';
import { ChatwootRepository } from '../integrations/chatwoot/repository/chatwoot.repository';
import { RabbitmqRepository } from '../integrations/rabbitmq/repository/rabbitmq.repository';
import { SqsRepository } from '../integrations/sqs/repository/sqs.repository';
import { TypebotRepository } from '../integrations/typebot/repository/typebot.repository';
import { WebsocketRepository } from '../integrations/websocket/repository/websocket.repository';
import { AuthRepository } from './auth.repository'; import { AuthRepository } from './auth.repository';
import { ChamaaiRepository } from './chamaai.repository';
import { ChatRepository } from './chat.repository'; import { ChatRepository } from './chat.repository';
import { ChatwootRepository } from './chatwoot.repository';
import { ContactRepository } from './contact.repository'; import { ContactRepository } from './contact.repository';
import { IntegrationRepository } from './integration.repository';
import { LabelRepository } from './label.repository';
import { MessageRepository } from './message.repository'; import { MessageRepository } from './message.repository';
import { MessageUpRepository } from './messageUp.repository'; import { MessageUpRepository } from './messageUp.repository';
import { ProxyRepository } from './proxy.repository'; import { ProxyRepository } from './proxy.repository';
import { RabbitmqRepository } from './rabbitmq.repository';
import { SettingsRepository } from './settings.repository'; import { SettingsRepository } from './settings.repository';
import { SqsRepository } from './sqs.repository';
import { TypebotRepository } from './typebot.repository';
import { WebhookRepository } from './webhook.repository'; import { WebhookRepository } from './webhook.repository';
import { WebsocketRepository } from './websocket.repository';
export class RepositoryBroker { export class RepositoryBroker {
constructor( constructor(
public readonly message: MessageRepository, public readonly message: MessageRepository,
@@ -33,7 +35,9 @@ export class RepositoryBroker {
public readonly typebot: TypebotRepository, public readonly typebot: TypebotRepository,
public readonly proxy: ProxyRepository, public readonly proxy: ProxyRepository,
public readonly chamaai: ChamaaiRepository, public readonly chamaai: ChamaaiRepository,
public readonly integration: IntegrationRepository,
public readonly auth: AuthRepository, public readonly auth: AuthRepository,
public readonly labels: LabelRepository,
private configService: ConfigService, private configService: ConfigService,
dbServer?: MongoClient, dbServer?: MongoClient,
) { ) {
@@ -69,6 +73,7 @@ export class RepositoryBroker {
const typebotDir = join(storePath, 'typebot'); const typebotDir = join(storePath, 'typebot');
const proxyDir = join(storePath, 'proxy'); const proxyDir = join(storePath, 'proxy');
const chamaaiDir = join(storePath, 'chamaai'); const chamaaiDir = join(storePath, 'chamaai');
const integrationDir = join(storePath, 'integration');
const tempDir = join(storePath, 'temp'); const tempDir = join(storePath, 'temp');
if (!fs.existsSync(authDir)) { if (!fs.existsSync(authDir)) {
@@ -127,6 +132,10 @@ export class RepositoryBroker {
this.logger.verbose('creating chamaai dir: ' + chamaaiDir); this.logger.verbose('creating chamaai dir: ' + chamaaiDir);
fs.mkdirSync(chamaaiDir, { recursive: true }); fs.mkdirSync(chamaaiDir, { recursive: true });
} }
if (!fs.existsSync(integrationDir)) {
this.logger.verbose('creating integration dir: ' + integrationDir);
fs.mkdirSync(integrationDir, { recursive: true });
}
if (!fs.existsSync(tempDir)) { if (!fs.existsSync(tempDir)) {
this.logger.verbose('creating temp dir: ' + tempDir); this.logger.verbose('creating temp dir: ' + tempDir);
fs.mkdirSync(tempDir, { recursive: true }); fs.mkdirSync(tempDir, { recursive: true });

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