Compare commits

...

128 Commits
1.7.0 ... 1.7.5

Author SHA1 Message Date
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
143 changed files with 1809 additions and 1046 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -2,63 +2,52 @@ name: Build Docker image
on:
push:
tags: ['v*']
branches:
- develop
- main
tags:
- v*
workflow_dispatch:
jobs:
build-amd:
build:
runs-on: ubuntu-latest
env:
GIT_REF: ${{ github.head_ref || github.ref_name }} # ref_name to get tags/branches
permissions:
contents: read
packages: write
steps:
- name: Check out the repo
uses: actions/checkout@v3
- name: Checkout
uses: actions/checkout@v4
- name: Extract existing image metadata
id: image-meta
uses: docker/metadata-action@v4
with:
images: atendai/evolution-api
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v2
- name: set docker tag
run: |
echo "DOCKER_TAG=ghcr.io/atendai/evolution-api:$GIT_REF" >> $GITHUB_ENV
- name: replace docker tag if main
if: github.ref_name == 'main'
run: |
echo "DOCKER_TAG=ghcr.io/atendai/evolution-api:latest" >> $GITHUB_ENV
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push AMD image
uses: docker/build-push-action@v4
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
labels: ${{ steps.image-meta.outputs.labels }}
platforms: linux/amd64
push: true
build-arm:
runs-on: buildjet-4vcpu-ubuntu-2204-arm
steps:
- name: Check out the repo
uses: actions/checkout@v3
- name: Extract existing image metadata
id: image-meta
uses: docker/metadata-action@v4
with:
images: atendai/evolution-api
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push ARM image
uses: docker/build-push-action@v4
with:
context: .
labels: ${{ steps.image-meta.outputs.labels }}
platforms: linux/arm64
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ env.DOCKER_TAG }}

View File

@@ -1,3 +1,64 @@
# 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

View File

@@ -33,7 +33,10 @@ CLEAN_STORE_CHATS=true
# Permanent data storage
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
# Choose the data you want to save in the application's database or store
@@ -43,16 +46,13 @@ DATABASE_SAVE_MESSAGE_UPDATE=false
DATABASE_SAVE_DATA_CONTACTS=false
DATABASE_SAVE_DATA_CHATS=false
REDIS_ENABLED=false
REDIS_URI=redis://redis:6379
REDIS_PREFIX_KEY=evdocker
RABBITMQ_ENABLED=false
RABBITMQ_RABBITMQ_MODE=global
RABBITMQ_EXCHANGE_NAME=evolution_exchange
RABBITMQ_URI=amqp://guest:guest@rabbitmq:5672
WEBSOCKET_ENABLED=false
WEBSOCKET_GLOBAL_EVENTS=false
WA_BUSINESS_TOKEN_WEBHOOK=evolution
WA_BUSINESS_URL=https://graph.facebook.com
@@ -128,6 +128,14 @@ CHATWOOT_MESSAGE_READ=false # false | true
CHATWOOT_IMPORT_DATABASE_CONNECTION_URI=postgres://user:password@hostname:port/dbname
CHATWOOT_IMPORT_DATABASE_PLACEHOLDER_MEDIA_MESSAGE=true
CACHE_REDIS_ENABLED=false
CACHE_REDIS_URI=redis://redis:6379
CACHE_REDIS_PREFIX_KEY=evolution
CACHE_REDIS_TTL=604800
CACHE_REDIS_SAVE_INSTANCES=false
CACHE_LOCAL_ENABLED=false
CACHE_LOCAL_TTL=604800
# Defines an authentication type for the api
# We recommend using the apikey because it will allow you to use a custom token,
# 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

@@ -33,7 +33,10 @@ CLEAN_STORE_CHATS=true
# Permanent data storage
DATABASE_ENABLED=true
DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true
DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin &
readPreference=primary &
ssl=false &
directConnection=true
DATABASE_CONNECTION_DB_PREFIX_NAME=evolution
# Choose the data you want to save in the application's database or store
@@ -43,10 +46,6 @@ DATABASE_SAVE_MESSAGE_UPDATE=false
DATABASE_SAVE_DATA_CONTACTS=false
DATABASE_SAVE_DATA_CHATS=false
REDIS_ENABLED=true
REDIS_URI=redis://redis:6379
REDIS_PREFIX_KEY=evolution
# Global Webhook Settings
# Each instance's Webhook URL and events will be requested at the time it is created
## Define a global webhook that will listen for enabled events from all instances
@@ -87,6 +86,14 @@ CONFIG_SESSION_PHONE_NAME=chrome
# Set qrcode display limit
QRCODE_LIMIT=30
CACHE_REDIS_ENABLED=false
CACHE_REDIS_URI=redis://redis:6379
CACHE_REDIS_PREFIX_KEY=evolution
CACHE_REDIS_TTL=604800
CACHE_REDIS_SAVE_INSTANCES=false
CACHE_LOCAL_ENABLED=false
CACHE_LOCAL_TTL=604800
# Defines an authentication type for the api
# We recommend using the apikey because it will allow you to use a custom token,
# if you use jwt, a random token will be generated and may be expired and you will have to generate a new token

View File

@@ -1,6 +1,6 @@
FROM node:20.7.0-alpine AS builder
LABEL version="1.7.0" description="Api to control whatsapp features through http requests."
LABEL version="1.7.5" description="Api to control whatsapp features through http requests."
LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
LABEL contact="contato@agenciadgcode.com"
@@ -58,16 +58,13 @@ ENV DATABASE_SAVE_MESSAGE_UPDATE=false
ENV DATABASE_SAVE_DATA_CONTACTS=false
ENV DATABASE_SAVE_DATA_CHATS=false
ENV REDIS_ENABLED=false
ENV REDIS_URI=redis://redis:6379
ENV REDIS_PREFIX_KEY=evolution
ENV RABBITMQ_ENABLED=false
ENV RABBITMQ_MODE=global
ENV RABBITMQ_EXCHANGE_NAME=evolution_exchange
ENV RABBITMQ_URI=amqp://guest:guest@rabbitmq:5672
ENV WEBSOCKET_ENABLED=false
ENV WEBSOCKET_GLOBAL_EVENTS=false
ENV WA_BUSINESS_TOKEN_WEBHOOK=evolution
ENV WA_BUSINESS_URL=https://graph.facebook.com
@@ -128,6 +125,14 @@ ENV QRCODE_COLOR=#198754
ENV TYPEBOT_API_VERSION=latest
ENV CACHE_REDIS_ENABLED=false
ENV CACHE_REDIS_URI=redis://redis:6379
ENV CACHE_REDIS_PREFIX_KEY=evolution
ENV CACHE_REDIS_TTL=604800
ENV CACHE_REDIS_SAVE_INSTANCES=false
ENV CACHE_LOCAL_ENABLED=false
ENV CACHE_LOCAL_TTL=604800
ENV AUTHENTICATION_TYPE=apikey
ENV AUTHENTICATION_API_KEY=B6D711FCDE4D4FD5936544120E713976

View File

@@ -1,6 +1,6 @@
{
"name": "evolution-api",
"version": "1.7.0",
"version": "1.7.5",
"description": "Rest api for communication with WhatsApp",
"main": "./dist/src/main.js",
"scripts": {
@@ -46,10 +46,10 @@
"@figuro/chatwoot-sdk": "^1.1.16",
"@hapi/boom": "^10.0.1",
"@sentry/node": "^7.59.2",
"@whiskeysockets/baileys": "6.6.0",
"amqplib": "^0.10.3",
"aws-sdk": "^2.1499.0",
"@aws-sdk/client-sqs": "^3.569.0",
"axios": "^1.6.5",
"@whiskeysockets/baileys": "^6.7.2",
"class-validator": "^0.14.1",
"compression": "^1.7.4",
"cors": "^2.8.5",
@@ -60,6 +60,7 @@
"exiftool-vendored": "^22.0.0",
"express": "^4.18.2",
"express-async-errors": "^3.1.1",
"fluent-ffmpeg": "^2.1.2",
"form-data": "^4.0.0",
"hbs": "^4.2.0",
"https-proxy-agent": "^7.0.2",

View File

@@ -1,13 +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

@@ -4,6 +4,7 @@ import {
BlockUserDto,
DeleteMessage,
getBase64FromMediaMessageDto,
MarkChatUnreadDto,
NumberDto,
PrivacySettingDto,
ProfileNameDto,
@@ -40,6 +41,11 @@ export class ChatController {
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) {
logger.verbose('requested deleteMessage from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].deleteMessage(data);

View File

@@ -6,23 +6,23 @@ import { v4 } from 'uuid';
import { ConfigService, HttpServer, WaBusiness } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { BadRequestException, InternalServerErrorException } from '../../exceptions';
import { RedisCache } from '../../libs/redis.client';
import { InstanceDto, SetPresenceDto } from '../dto/instance.dto';
import { ChatwootService } from '../integrations/chatwoot/services/chatwoot.service';
import { RabbitmqService } from '../integrations/rabbitmq/services/rabbitmq.service';
import { SqsService } from '../integrations/sqs/services/sqs.service';
import { TypebotService } from '../integrations/typebot/services/typebot.service';
import { WebsocketService } from '../integrations/websocket/services/websocket.service';
import { RepositoryBroker } from '../repository/repository.manager';
import { AuthService, OldToken } from '../services/auth.service';
import { CacheService } from '../services/cache.service';
import { ChatwootService } from '../services/chatwoot.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 { RabbitmqService } from '../services/rabbitmq.service';
import { SettingsService } from '../services/settings.service';
import { SqsService } from '../services/sqs.service';
import { TypebotService } from '../services/typebot.service';
import { WebhookService } from '../services/webhook.service';
import { WebsocketService } from '../services/websocket.service';
import { BaileysStartupService } from '../services/whatsapp.baileys.service';
import { BusinessStartupService } from '../services/whatsapp.business.service';
import { Events, Integration, wa } from '../types/wa.types';
import { ProxyController } from './proxy.controller';
export class InstanceController {
constructor(
@@ -39,8 +39,10 @@ export class InstanceController {
private readonly sqsService: SqsService,
private readonly typebotService: TypebotService,
private readonly integrationService: IntegrationService,
private readonly cache: RedisCache,
private readonly proxyService: ProxyController,
private readonly cache: CacheService,
private readonly chatwootCache: CacheService,
private readonly messagesLostCache: CacheService,
) {}
private readonly logger = new Logger(InstanceController.name);
@@ -53,6 +55,7 @@ export class InstanceController {
events,
qrcode,
number,
mobile,
integration,
token,
chatwoot_account_id,
@@ -62,6 +65,8 @@ export class InstanceController {
chatwoot_reopen_conversation,
chatwoot_conversation_pending,
chatwoot_import_contacts,
chatwoot_name_inbox,
chatwoot_merge_brazil_contacts,
chatwoot_import_messages,
chatwoot_days_limit_import_messages,
reject_call,
@@ -84,6 +89,7 @@ export class InstanceController {
typebot_delay_message,
typebot_unknown_message,
typebot_listening_from_me,
proxy,
}: InstanceDto) {
try {
this.logger.verbose('requested createInstance from ' + instanceName + ' instance');
@@ -104,6 +110,7 @@ export class InstanceController {
this.repository,
this.cache,
this.chatwootCache,
this.messagesLostCache,
);
} else {
instance = new BaileysStartupService(
@@ -112,10 +119,11 @@ export class InstanceController {
this.repository,
this.cache,
this.chatwootCache,
this.messagesLostCache,
);
}
await this.waMonitor.saveInstance({ integration, instanceName, token, number });
await this.waMonitor.saveInstance({ integration, instanceName, token, number, mobile });
instance.instanceName = instanceName;
@@ -345,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) {
try {
if (!isURL(typebot_url, { require_tld: false })) {
@@ -405,7 +425,7 @@ export class InstanceController {
if (qrcode) {
this.logger.verbose('creating qrcode');
await instance.connectToWhatsapp(number);
await instance.connectToWhatsapp(number, mobile);
await delay(5000);
getQrcode = instance.qrCode;
}
@@ -495,11 +515,12 @@ export class InstanceController {
token: chatwoot_token,
url: chatwoot_url,
sign_msg: chatwoot_sign_msg || false,
name_inbox: instance.instanceName.split('-cwId-')[0],
name_inbox: chatwoot_name_inbox ?? instance.instanceName.split('-cwId-')[0],
number,
reopen_conversation: chatwoot_reopen_conversation || false,
conversation_pending: chatwoot_conversation_pending || false,
import_contacts: chatwoot_import_contacts ?? true,
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,
@@ -555,11 +576,12 @@ export class InstanceController {
sign_msg: chatwoot_sign_msg || false,
reopen_conversation: chatwoot_reopen_conversation || 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,
name_inbox: instance.instanceName,
name_inbox: chatwoot_name_inbox ?? instance.instanceName,
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
},
};
@@ -569,7 +591,7 @@ export class InstanceController {
}
}
public async connectToWhatsapp({ instanceName, number = null }: InstanceDto) {
public async connectToWhatsapp({ instanceName, number = null, mobile = null }: InstanceDto) {
try {
this.logger.verbose('requested connectToWhatsapp from ' + instanceName + ' instance');
@@ -592,7 +614,7 @@ export class InstanceController {
if (state == 'close') {
this.logger.verbose('connecting');
await instance.connectToWhatsapp(number);
await instance.connectToWhatsapp(number, mobile);
await delay(5000);
return instance.qrCode;
@@ -633,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) {
this.logger.verbose('requested connectionState from ' + instanceName + ' instance');
return {

View File

@@ -46,7 +46,7 @@ export class ProxyController {
return this.proxyService.find(instance);
}
private async testProxy(proxy: ProxyDto['proxy']) {
public async testProxy(proxy: ProxyDto['proxy']) {
logger.verbose('requested testProxy');
try {
const serverIp = await axios.get('https://icanhazip.com/');

View File

@@ -73,6 +73,11 @@ export class ArchiveChatDto {
archive: boolean;
}
export class MarkChatUnreadDto {
lastMessage?: LastMessage;
chat?: string;
}
class PrivacySetting {
readreceipts: WAReadReceiptsValue;
profile: WAPrivacyValue;

View File

@@ -1,10 +1,13 @@
import { WAPresence } from "@whiskeysockets/baileys";
import { WAPresence } from '@whiskeysockets/baileys';
import { ProxyDto } from './proxy.dto';
export class InstanceDto {
instanceName: string;
instanceId?: string;
qrcode?: boolean;
number?: string;
mobile?: boolean;
integration?: string;
token?: string;
webhook?: string;
@@ -24,9 +27,11 @@ export class InstanceDto {
chatwoot_sign_msg?: boolean;
chatwoot_reopen_conversation?: 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_events?: string[];
rabbitmq_enabled?: boolean;
@@ -40,7 +45,7 @@ export class InstanceDto {
typebot_delay_message?: number;
typebot_unknown_message?: string;
typebot_listening_from_me?: boolean;
proxy?: string;
proxy?: ProxyDto['proxy'];
}
export class SetPresenceDto {

View File

@@ -7,8 +7,8 @@ import { Auth, configService } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { ForbiddenException, UnauthorizedException } from '../../exceptions';
import { InstanceDto } from '../dto/instance.dto';
import { repository } from '../server.module';
import { JwtPayload } from '../services/auth.service';
import { repository } from '../whatsapp.module';
const logger = new Logger('GUARD');

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,13 +2,13 @@ import axios from 'axios';
import { writeFileSync } from 'fs';
import path from 'path';
import { ConfigService, HttpServer } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { ConfigService, HttpServer } from '../../../../config/env.config';
import { Logger } from '../../../../config/logger.config';
import { InstanceDto } from '../../../dto/instance.dto';
import { ChamaaiRaw } from '../../../models';
import { WAMonitoringService } from '../../../services/monitor.service';
import { Events } from '../../../types/wa.types';
import { ChamaaiDto } from '../dto/chamaai.dto';
import { InstanceDto } from '../dto/instance.dto';
import { ChamaaiRaw } from '../models';
import { Events } from '../types/wa.types';
import { WAMonitoringService } from './monitor.service';
export class ChamaaiService {
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,15 +1,15 @@
import { isURL } from 'class-validator';
import { ConfigService, HttpServer } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { BadRequestException } from '../../exceptions';
import { CacheEngine } from '../../libs/cacheengine';
import { CacheEngine } from '../../../../cache/cacheengine';
import { ConfigService, HttpServer } from '../../../../config/env.config';
import { Logger } from '../../../../config/logger.config';
import { BadRequestException } from '../../../../exceptions';
import { InstanceDto } from '../../../dto/instance.dto';
import { RepositoryBroker } from '../../../repository/repository.manager';
import { waMonitor } from '../../../server.module';
import { CacheService } from '../../../services/cache.service';
import { ChatwootDto } from '../dto/chatwoot.dto';
import { InstanceDto } from '../dto/instance.dto';
import { RepositoryBroker } from '../repository/repository.manager';
import { CacheService } from '../services/cache.service';
import { ChatwootService } from '../services/chatwoot.service';
import { waMonitor } from '../whatsapp.module';
const logger = new Logger('ChatwootController');
@@ -53,11 +53,15 @@ export class ChatwootController {
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.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);

View File

@@ -9,6 +9,7 @@ export class ChatwootDto {
number?: string;
reopen_conversation?: boolean;
conversation_pending?: boolean;
merge_brazil_contacts?: boolean;
import_contacts?: boolean;
import_messages?: boolean;
days_limit_import_messages?: number;

View File

@@ -1,7 +1,7 @@
import postgresql from 'pg';
import { Chatwoot, configService } from '../config/env.config';
import { Logger } from '../config/logger.config';
import { Chatwoot, configService } from '../../../../config/env.config';
import { Logger } from '../../../../config/logger.config';
const { Pool } = postgresql;

View File

@@ -1,6 +1,6 @@
import { Schema } from 'mongoose';
import { dbserver } from '../../libs/db.connect';
import { dbserver } from '../../../../libs/db.connect';
export class ChatwootRaw {
_id?: string;
@@ -14,6 +14,7 @@ export class ChatwootRaw {
number?: string;
reopen_conversation?: boolean;
conversation_pending?: boolean;
merge_brazil_contacts?: boolean;
import_contacts?: boolean;
import_messages?: boolean;
days_limit_import_messages?: number;
@@ -31,6 +32,7 @@ const chatwootSchema = new Schema<ChatwootRaw>({
number: { type: String, required: true },
reopen_conversation: { 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 },

View File

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

View File

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

View File

@@ -1,5 +1,14 @@
import ChatwootClient, { ChatwootAPIConfig, contact, conversation, generic_id, inbox } from '@figuro/chatwoot-sdk';
import ChatwootClient, {
ChatwootAPIConfig,
contact,
contact_inboxes,
conversation,
conversation_show,
generic_id,
inbox,
} from '@figuro/chatwoot-sdk';
import { request as chatwootRequest } from '@figuro/chatwoot-sdk/dist/core/request';
import { proto } from '@whiskeysockets/baileys';
import axios from 'axios';
import FormData from 'form-data';
import { createReadStream, unlinkSync, writeFileSync } from 'fs';
@@ -7,18 +16,18 @@ import Jimp from 'jimp';
import mimeTypes from 'mime-types';
import path from 'path';
import { Chatwoot, ConfigService, HttpServer } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { chatwootImport } from '../../utils/chatwoot-import-helper';
import i18next from '../../utils/i18n';
import { ICache } from '../abstract/abstract.cache';
import { Chatwoot, ConfigService, HttpServer } from '../../../../config/env.config';
import { Logger } from '../../../../config/logger.config';
import i18next from '../../../../utils/i18n';
import { ICache } from '../../../abstract/abstract.cache';
import { InstanceDto } from '../../../dto/instance.dto';
import { Options, Quoted, SendAudioDto, SendMediaDto, SendTextDto } from '../../../dto/sendMessage.dto';
import { ChatwootRaw, ContactRaw, MessageRaw } from '../../../models';
import { RepositoryBroker } from '../../../repository/repository.manager';
import { WAMonitoringService } from '../../../services/monitor.service';
import { Events } from '../../../types/wa.types';
import { ChatwootDto } from '../dto/chatwoot.dto';
import { InstanceDto } from '../dto/instance.dto';
import { Options, Quoted, SendAudioDto, SendMediaDto, SendTextDto } from '../dto/sendMessage.dto';
import { ChatwootRaw, ContactRaw, MessageRaw } from '../models';
import { RepositoryBroker } from '../repository/repository.manager';
import { Events } from '../types/wa.types';
import { WAMonitoringService } from './monitor.service';
import { chatwootImport } from '../utils/chatwoot-import-helper';
export class ChatwootService {
private readonly logger = new Logger(ChatwootService.name);
@@ -77,12 +86,14 @@ export class ChatwootService {
return client;
}
public getClientCwConfig(): ChatwootAPIConfig {
public getClientCwConfig(): ChatwootAPIConfig & { name_inbox: string; merge_brazil_contacts: boolean } {
return {
basePath: this.provider.url,
with_credentials: true,
credentials: 'include',
token: this.provider.token,
name_inbox: this.provider.name_inbox,
merge_brazil_contacts: this.provider.merge_brazil_contacts,
};
}
@@ -102,7 +113,7 @@ export class ChatwootService {
await this.initInstanceChatwoot(
instance,
instance.instanceName.split('-cwId-')[0],
data.name_inbox ?? instance.instanceName.split('-cwId-')[0],
`${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
true,
data.number,
@@ -408,10 +419,49 @@ export class ChatwootService {
}
}
private async mergeBrazilianContacts(contacts: any[]) {
try {
//sdk chatwoot não tem função merge
this.logger.verbose('merging contacts');
const contact = await chatwootRequest(this.getClientCwConfig(), {
method: 'POST',
url: `/api/v1/accounts/${this.provider.account_id}/actions/contact_merge`,
body: {
base_contact_id: contacts.find((contact) => contact.phone_number.length === 14)?.id,
mergee_contact_id: contacts.find((contact) => contact.phone_number.length === 13)?.id,
},
});
return contact;
} catch {
this.logger.error('Error merging contacts');
return null;
}
}
private findContactInContactList(contacts: any[], query: string) {
const phoneNumbers = this.getNumbers(query);
const searchableFields = this.getSearchableFields();
// eslint-disable-next-line prettier/prettier
if(contacts.length === 2 && this.getClientCwConfig().merge_brazil_contacts && query.startsWith('+55')){
const contact = this.mergeBrazilianContacts(contacts);
if (contact) {
return contact;
}
}
const phone = phoneNumbers.reduce(
(savedNumber, number) => (number.length > savedNumber.length ? number : savedNumber),
'',
);
const contact_with9 = contacts.find((contact) => contact.phone_number === phone);
if (contact_with9) {
return contact_with9;
}
for (const contact of contacts) {
for (const field of searchableFields) {
if (contact[field] && phoneNumbers.includes(contact[field])) {
@@ -439,7 +489,7 @@ export class ChatwootService {
}
private getSearchableFields() {
return ['phone_number', 'identifier'];
return ['phone_number'];
}
private getFilterPayload(query: string) {
@@ -453,7 +503,7 @@ export class ChatwootService {
const queryOperator = fieldsToSearch.length - 1 === index1 && numbers.length - 1 === index2 ? null : 'OR';
filterPayload.push({
attribute_key: field,
filter_operator: field == 'phone_number' ? 'equal_to' : 'contains',
filter_operator: 'equal_to',
values: [number.replace('+', '')],
query_operator: queryOperator,
});
@@ -551,53 +601,44 @@ export class ChatwootService {
const picture_url = await this.waMonitor.waInstances[instance.instanceName].profilePicture(chatId);
const findContact = await this.findContact(instance, chatId);
let contact = await this.findContact(instance, chatId);
let contact: any;
if (body.key.fromMe) {
if (findContact) {
contact = await this.updateContact(instance, findContact.id, {
avatar_url: picture_url.profilePictureUrl || null,
});
} else {
const jid = isGroup ? null : body.key.remoteJid;
contact = await this.createContact(
instance,
chatId,
filterInbox.id,
isGroup,
nameContact,
picture_url.profilePictureUrl || null,
jid,
);
if (contact) {
if (!body.key.fromMe) {
const waProfilePictureFile =
picture_url?.profilePictureUrl?.split('#')[0].split('?')[0].split('/').pop() || '';
const chatwootProfilePictureFile = contact?.thumbnail?.split('#')[0].split('?')[0].split('/').pop() || '';
const pictureNeedsUpdate = waProfilePictureFile !== chatwootProfilePictureFile;
const nameNeedsUpdate =
!contact.name ||
contact.name === chatId ||
(`+${chatId}`.startsWith('+55')
? this.getNumbers(`+${chatId}`).some(
(v) => contact.name === v || contact.name === v.substring(3) || contact.name === v.substring(1),
)
: false);
const contactNeedsUpdate = pictureNeedsUpdate || nameNeedsUpdate;
if (contactNeedsUpdate) {
this.logger.verbose('update contact in chatwoot');
contact = await this.updateContact(instance, contact.id, {
...(nameNeedsUpdate && { name: nameContact }),
...(waProfilePictureFile === '' && { avatar: null }),
...(pictureNeedsUpdate && { avatar_url: picture_url?.profilePictureUrl }),
});
}
}
} else {
if (findContact) {
if (!findContact.name || findContact.name === chatId) {
contact = await this.updateContact(instance, findContact.id, {
name: nameContact,
avatar_url: picture_url.profilePictureUrl || null,
});
} else {
contact = await this.updateContact(instance, findContact.id, {
avatar_url: picture_url.profilePictureUrl || null,
});
}
if (!contact) {
contact = await this.findContact(instance, chatId);
}
} else {
const jid = isGroup ? null : body.key.remoteJid;
contact = await this.createContact(
instance,
chatId,
filterInbox.id,
isGroup,
nameContact,
picture_url.profilePictureUrl || null,
jid,
);
}
const jid = isGroup ? null : body.key.remoteJid;
contact = await this.createContact(
instance,
chatId,
filterInbox.id,
isGroup,
nameContact,
picture_url.profilePictureUrl || null,
jid,
);
}
if (!contact) {
@@ -607,20 +648,13 @@ export class ChatwootService {
const contactId = contact?.payload?.id || contact?.payload?.contact?.id || contact?.id;
if (!body.key.fromMe && contact.name === chatId && nameContact !== chatId) {
this.logger.verbose('update contact name in chatwoot');
await this.updateContact(instance, contactId, {
name: nameContact,
});
}
this.logger.verbose('get contact conversations in chatwoot');
const contactConversations = (await client.contacts.listConversations({
accountId: this.provider.account_id,
id: contactId,
})) as any;
if (contactConversations) {
if (contactConversations?.payload?.length) {
let conversation: any;
if (this.provider.reopen_conversation) {
conversation = contactConversations.payload.find((conversation) => conversation.inbox_id == filterInbox.id);
@@ -702,7 +736,7 @@ export class ChatwootService {
}
this.logger.verbose('find inbox by name');
const findByName = inbox.payload.find((inbox) => inbox.name === instance.instanceName.split('-cwId-')[0]);
const findByName = inbox.payload.find((inbox) => inbox.name === this.getClientCwConfig().name_inbox);
if (!findByName) {
this.logger.warn('inbox not found');
@@ -779,26 +813,15 @@ export class ChatwootService {
return null;
}
const payload = [
['inbox_id', inbox.id.toString()],
['contact_id', contact.id.toString()],
['status', 'open'],
];
const conversations = (await client.contacts.listConversations({
accountId: this.provider.account_id,
id: contact.id,
})) as any;
return (
(
(await client.conversations.filter({
accountId: this.provider.account_id,
payload: payload.map((item, i, payload) => {
return {
attribute_key: item[0],
filter_operator: 'equal_to',
values: [item[1]],
query_operator: i < payload.length - 1 ? 'AND' : null,
};
}),
})) as { payload: conversation[] }
).payload[0] || undefined
conversations.payload.find(
(conversation) => conversation.inbox_id === inbox.id && conversation.status === 'open',
) || undefined
);
}
@@ -1098,6 +1121,26 @@ export class ChatwootService {
}
}
public async onSendMessageError(instance: InstanceDto, conversation: number, error?: string) {
const client = await this.clientCw(instance);
if (!client) {
return;
}
client.messages.create({
accountId: this.provider.account_id,
conversationId: conversation,
data: {
content: i18next.t('cw.message.notsent', {
error: error?.length > 0 ? `_${error}_` : '',
}),
message_type: 'outgoing',
private: true,
},
});
}
public async receiveWebhook(instance: InstanceDto, body: any) {
try {
await new Promise((resolve) => setTimeout(resolve, 500));
@@ -1266,6 +1309,11 @@ export class ChatwootService {
return { message: 'bot' };
}
if (!waInstance && body.conversation?.id) {
this.onSendMessageError(instance, body.conversation?.id, 'Instance not found');
return { message: 'bot' };
}
this.logger.verbose('Format message to send');
let formatText: string;
if (senderName === null || senderName === undefined) {
@@ -1302,6 +1350,9 @@ export class ChatwootService {
formatText,
options,
);
if (!messageSent && body.conversation?.id) {
this.onSendMessageError(instance, body.conversation?.id);
}
this.updateChatwootMessageId(
{
@@ -1335,23 +1386,34 @@ export class ChatwootService {
},
};
const messageSent = await waInstance?.textMessage(data, true);
let messageSent: MessageRaw | proto.WebMessageInfo;
try {
messageSent = await waInstance?.textMessage(data, true);
if (!messageSent) {
throw new Error('Message not sent');
}
this.updateChatwootMessageId(
{
...messageSent,
owner: instance.instanceName,
},
{
messageId: body.id,
inboxId: body.inbox?.id,
conversationId: body.conversation?.id,
contactInbox: {
sourceId: body.conversation?.contact_inbox?.source_id,
this.updateChatwootMessageId(
{
...messageSent,
owner: instance.instanceName,
},
},
instance,
);
{
messageId: body.id,
inboxId: body.inbox?.id,
conversationId: body.conversation?.id,
contactInbox: {
sourceId: body.conversation?.contact_inbox?.source_id,
},
},
instance,
);
} catch (error) {
if (!messageSent && body.conversation?.id) {
this.onSendMessageError(instance, body.conversation?.id, error.toString());
}
throw error;
}
}
}
@@ -1774,6 +1836,13 @@ export class ChatwootService {
return;
}
// fix when receiving/sending messages from whatsapp desktop with ephemeral messages enabled
if (body.message?.ephemeralMessage?.message) {
body.message = {
...body.message?.ephemeralMessage?.message,
};
}
this.logger.verbose('get conversation message');
// Whatsapp to Chatwoot
@@ -2123,12 +2192,13 @@ export class ChatwootService {
};
if (!sourceId && inbox) {
const contact = (await this.findContact(
instance,
this.getNumberFromRemoteJid(body.key.remoteJid),
)) as contact;
const contactInbox = contact?.contact_inboxes?.find((contactInbox) => contactInbox?.inbox?.id === inbox.id);
sourceId = contactInbox?.source_id;
const conversation = (await client.conversations.get({
accountId: this.provider.account_id,
conversationId: conversationId,
})) as conversation_show & {
last_non_activity_message: { conversation: { contact_inbox: contact_inboxes } };
};
sourceId = conversation.last_non_activity_message?.conversation?.contact_inbox?.source_id;
}
if (sourceId && inbox?.inbox_identifier) {

View File

@@ -1,12 +1,12 @@
import { inbox } from '@figuro/chatwoot-sdk';
import { proto } from '@whiskeysockets/baileys';
import { Chatwoot, configService } from '../config/env.config';
import { Logger } from '../config/logger.config';
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 { InstanceDto } from '../whatsapp/dto/instance.dto';
import { ChatwootRaw, ContactRaw, MessageRaw } from '../whatsapp/models';
import { ChatwootService } from '../whatsapp/services/chatwoot.service';
import { ChatwootService } from '../services/chatwoot.service';
type ChatwootUser = {
user_type: string;

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 { InstanceDto } from '../dto/instance.dto';
import { Logger } from '../../../../config/logger.config';
import { InstanceDto } from '../../../dto/instance.dto';
import { RabbitmqDto } from '../dto/rabbitmq.dto';
import { RabbitmqService } from '../services/rabbitmq.service';

View File

@@ -1,7 +1,7 @@
import * as amqp from 'amqplib/callback_api';
import { configService, Rabbitmq } from '../config/env.config';
import { Logger } from '../config/logger.config';
import { configService, Rabbitmq } from '../../../../config/env.config';
import { Logger } from '../../../../config/logger.config';
const logger = new Logger('AMQP');

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
import { Logger } from '../../config/logger.config';
import { initQueues } from '../../libs/amqp.server';
import { InstanceDto } from '../dto/instance.dto';
import { Logger } from '../../../../config/logger.config';
import { InstanceDto } from '../../../dto/instance.dto';
import { RabbitmqRaw } from '../../../models';
import { WAMonitoringService } from '../../../services/monitor.service';
import { RabbitmqDto } from '../dto/rabbitmq.dto';
import { RabbitmqRaw } from '../models';
import { WAMonitoringService } from './monitor.service';
import { initQueues } from '../libs/amqp.server';
export class RabbitmqService {
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 { InstanceDto } from '../dto/instance.dto';
import { Logger } from '../../../../config/logger.config';
import { InstanceDto } from '../../../dto/instance.dto';
import { SqsDto } from '../dto/sqs.dto';
import { SqsService } from '../services/sqs.service';

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,13 @@
import axios from 'axios';
import EventEmitter2 from 'eventemitter2';
import { ConfigService, Typebot } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto';
import { ConfigService, Typebot } from '../../../../config/env.config';
import { Logger } from '../../../../config/logger.config';
import { InstanceDto } from '../../../dto/instance.dto';
import { MessageRaw } from '../../../models';
import { WAMonitoringService } from '../../../services/monitor.service';
import { Events } from '../../../types/wa.types';
import { Session, TypebotDto } from '../dto/typebot.dto';
import { MessageRaw } from '../models';
import { Events } from '../types/wa.types';
import { WAMonitoringService } from './monitor.service';
export class TypebotService {
constructor(
@@ -270,16 +270,27 @@ export class TypebotService {
private getTypeMessage(msg: any) {
this.logger.verbose('get type message');
const types = {
conversation: msg.conversation,
extendedTextMessage: msg.extendedTextMessage?.text,
audioMessage: msg.audioMessage?.url,
imageMessage: msg.imageMessage?.url,
videoMessage: msg.videoMessage?.url,
documentMessage: msg.documentMessage?.fileName,
contactMessage: msg.contactMessage?.displayName,
locationMessage: msg.locationMessage?.degreesLatitude,
viewOnceMessageV2:
msg.viewOnceMessageV2?.message?.imageMessage?.url ||
msg.viewOnceMessageV2?.message?.videoMessage?.url ||
msg.viewOnceMessageV2?.message?.audioMessage?.url,
listResponseMessage: msg.listResponseMessage?.singleSelectReply?.selectedRowId,
responseRowId: msg.listResponseMessage?.singleSelectReply?.selectedRowId,
};
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) {
@@ -305,6 +316,101 @@ export class TypebotService {
return messageContent;
}
private getAudioMessageContent(msg: any) {
this.logger.verbose('get audio message content');
const types = this.getTypeMessage(msg);
const audioContent = types.audioMessage;
this.logger.verbose('audio message URL: ' + audioContent);
return audioContent;
}
private getImageMessageContent(msg: any) {
this.logger.verbose('get image message content');
const types = this.getTypeMessage(msg);
const imageContent = types.imageMessage;
this.logger.verbose('image message URL: ' + imageContent);
return imageContent;
}
private getVideoMessageContent(msg: any) {
this.logger.verbose('get video message content');
const types = this.getTypeMessage(msg);
const videoContent = types.videoMessage;
this.logger.verbose('video message URL: ' + videoContent);
return videoContent;
}
private getDocumentMessageContent(msg: any) {
this.logger.verbose('get document message content');
const types = this.getTypeMessage(msg);
const documentContent = types.documentMessage;
this.logger.verbose('document message fileName: ' + documentContent);
return documentContent;
}
private getContactMessageContent(msg: any) {
this.logger.verbose('get contact message content');
const types = this.getTypeMessage(msg);
const contactContent = types.contactMessage;
this.logger.verbose('contact message displayName: ' + contactContent);
return contactContent;
}
private getLocationMessageContent(msg: any) {
this.logger.verbose('get location message content');
const types = this.getTypeMessage(msg);
const locationContent = types.locationMessage;
this.logger.verbose('location message degreesLatitude: ' + locationContent);
return locationContent;
}
private getViewOnceMessageV2Content(msg: any) {
this.logger.verbose('get viewOnceMessageV2 content');
const types = this.getTypeMessage(msg);
const viewOnceContent = types.viewOnceMessageV2;
this.logger.verbose('viewOnceMessageV2 URL: ' + viewOnceContent);
return viewOnceContent;
}
private getListResponseMessageContent(msg: any) {
this.logger.verbose('get listResponseMessage content');
const types = this.getTypeMessage(msg);
const listResponseContent = types.listResponseMessage || types.responseRowId;
this.logger.verbose('listResponseMessage selectedRowId: ' + listResponseContent);
return listResponseContent;
}
public async createNewSession(instance: InstanceDto, data: any) {
if (data.remoteJid === 'status@broadcast') return;
const id = Math.floor(Math.random() * 10000000000).toString();
@@ -565,6 +671,7 @@ export class TypebotService {
const delay_message = findTypebot.delay_message;
const unknown_message = findTypebot.unknown_message;
const listening_from_me = findTypebot.listening_from_me;
const messageType = this.getTypeMessage(msg.message).messageType;
const session = sessions.find((session) => session.remoteJid === remoteJid);
@@ -687,6 +794,9 @@ export class TypebotService {
sessions: sessions,
remoteJid: remoteJid,
pushName: msg.pushName,
prefilledVariables: {
messageType: messageType,
},
});
await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions);

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 { InstanceDto } from '../dto/instance.dto';
import { Logger } from '../../../../config/logger.config';
import { InstanceDto } from '../../../dto/instance.dto';
import { WebsocketDto } from '../dto/websocket.dto';
import { WebsocketService } from '../services/websocket.service';

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
import { Logger } from '../../config/logger.config';
import { InstanceDto } from '../dto/instance.dto';
import { Logger } from '../../../../config/logger.config';
import { InstanceDto } from '../../../dto/instance.dto';
import { WebsocketRaw } from '../../../models';
import { WAMonitoringService } from '../../../services/monitor.service';
import { WebsocketDto } from '../dto/websocket.dto';
import { WebsocketRaw } from '../models';
import { WAMonitoringService } from './monitor.service';
export class WebsocketService {
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'),
};

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

@@ -9,12 +9,12 @@ export class IntegrationRaw {
token?: string;
}
const sqsSchema = new Schema<IntegrationRaw>({
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, sqsSchema, 'integration');
export const IntegrationModel = dbserver?.model(IntegrationRaw.name, integrationSchema, 'integration');
export type IntegrationModel = typeof IntegrationModel;

View File

@@ -32,6 +32,7 @@ export class MessageRaw {
source_reply_id?: string;
chatwoot?: ChatwootMessage;
contextInfo?: any;
status?: wa.StatusMessage | any;
}
type MessageRawBoolean<T> = {

View File

@@ -4,22 +4,22 @@ import { join } from 'path';
import { Auth, ConfigService, Database } from '../../config/env.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 { ChamaaiRepository } from './chamaai.repository';
import { ChatRepository } from './chat.repository';
import { ChatwootRepository } from './chatwoot.repository';
import { ContactRepository } from './contact.repository';
import { IntegrationRepository } from './integration.repository';
import { LabelRepository } from './label.repository';
import { MessageRepository } from './message.repository';
import { MessageUpRepository } from './messageUp.repository';
import { ProxyRepository } from './proxy.repository';
import { RabbitmqRepository } from './rabbitmq.repository';
import { SettingsRepository } from './settings.repository';
import { SqsRepository } from './sqs.repository';
import { TypebotRepository } from './typebot.repository';
import { WebhookRepository } from './webhook.repository';
import { WebsocketRepository } from './websocket.repository';
export class RepositoryBroker {
constructor(
public readonly message: MessageRepository,

View File

@@ -6,10 +6,10 @@ import {
blockUserSchema,
contactValidateSchema,
deleteMessageSchema,
markChatUnreadSchema,
messageUpSchema,
messageValidateSchema,
presenceSchema,
presenceOnlySchema,
privacySettingsSchema,
profileNameSchema,
profilePictureSchema,
@@ -25,6 +25,7 @@ import {
BlockUserDto,
DeleteMessage,
getBase64FromMediaMessageDto,
MarkChatUnreadDto,
NumberDto,
PrivacySettingDto,
ProfileNameDto,
@@ -39,7 +40,7 @@ import { InstanceDto } from '../dto/instance.dto';
import { ContactQuery } from '../repository/contact.repository';
import { MessageQuery } from '../repository/message.repository';
import { MessageUpQuery } from '../repository/messageUp.repository';
import { chatController } from '../whatsapp.module';
import { chatController } from '../server.module';
import { HttpStatus } from './index.router';
const logger = new Logger('ChatRouter');
@@ -99,6 +100,23 @@ export class ChatRouter extends RouterBroker {
return res.status(HttpStatus.CREATED).json(response);
})
.put(this.routerPath('markChatUnread'), ...guards, async (req, res) => {
logger.verbose('request received in markChatUnread');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<MarkChatUnreadDto>({
request: req,
schema: markChatUnreadSchema,
ClassRef: MarkChatUnreadDto,
execute: (instance, data) => chatController.markChatUnread(instance, data),
});
return res.status(HttpStatus.CREATED).json(response);
})
.delete(this.routerPath('deleteMessageForEveryone'), ...guards, async (req, res) => {
logger.verbose('request received in deleteMessageForEveryone');
logger.verbose('request body: ');

View File

@@ -30,7 +30,7 @@ import {
GroupUpdateParticipantDto,
GroupUpdateSettingDto,
} from '../dto/group.dto';
import { groupController } from '../whatsapp.module';
import { groupController } from '../server.module';
import { HttpStatus } from './index.router';
const logger = new Logger('GroupRouter');

View File

@@ -4,21 +4,21 @@ import fs from 'fs';
import { Auth, configService } from '../../config/env.config';
import { authGuard } from '../guards/auth.guard';
import { instanceExistsGuard, instanceLoggedGuard } from '../guards/instance.guard';
import { ChamaaiRouter } from './chamaai.router';
import { ChamaaiRouter } from '../integrations/chamaai/routes/chamaai.router';
import { ChatwootRouter } from '../integrations/chatwoot/routes/chatwoot.router';
import { RabbitmqRouter } from '../integrations/rabbitmq/routes/rabbitmq.router';
import { SqsRouter } from '../integrations/sqs/routes/sqs.router';
import { TypebotRouter } from '../integrations/typebot/routes/typebot.router';
import { WebsocketRouter } from '../integrations/websocket/routes/websocket.router';
import { ChatRouter } from './chat.router';
import { ChatwootRouter } from './chatwoot.router';
import { GroupRouter } from './group.router';
import { InstanceRouter } from './instance.router';
import { LabelRouter } from './label.router';
import { ProxyRouter } from './proxy.router';
import { RabbitmqRouter } from './rabbitmq.router';
import { MessageRouter } from './sendMessage.router';
import { SettingsRouter } from './settings.router';
import { SqsRouter } from './sqs.router';
import { TypebotRouter } from './typebot.router';
import { ViewsRouter } from './view.router';
import { WebhookRouter } from './webhook.router';
import { WebsocketRouter } from './websocket.router';
enum HttpStatus {
OK = 200,

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