mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-12-19 11:52:20 -06:00
Compare commits
301 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b87558d301 | ||
|
|
9f1003e94e | ||
|
|
73c003907b | ||
|
|
b65d292af4 | ||
|
|
43bdbf1018 | ||
|
|
ec7986420d | ||
|
|
0b23153316 | ||
|
|
ad4f5e5e65 | ||
|
|
1d48532146 | ||
|
|
950803b2aa | ||
|
|
a41d92f4da | ||
|
|
70d4eb393f | ||
|
|
9f162127d0 | ||
|
|
4cd30dabc4 | ||
|
|
8f78728863 | ||
|
|
e7c5d33d50 | ||
|
|
5400f31acb | ||
|
|
e58f1d778e | ||
|
|
5f5db2011e | ||
|
|
aea4d89f62 | ||
|
|
ccda17d704 | ||
|
|
4ab6c438cf | ||
|
|
219e63c226 | ||
|
|
ee1e4623a3 | ||
|
|
5982212903 | ||
|
|
32e6ecb12d | ||
|
|
3b774558f4 | ||
|
|
072171da18 | ||
|
|
8292221790 | ||
|
|
901954de33 | ||
|
|
286fe03500 | ||
|
|
9c4f02634b | ||
|
|
434f39de1f | ||
|
|
e72b543f38 | ||
|
|
826e818802 | ||
|
|
cbf846d32f | ||
|
|
55811a39a5 | ||
|
|
a81f5953fc | ||
|
|
bd09328ec2 | ||
|
|
17ecc88f80 | ||
|
|
7bc4c7b36e | ||
|
|
79189ac5d7 | ||
|
|
060c4e6e72 | ||
|
|
8f7c518487 | ||
|
|
12761cbfce | ||
|
|
2b30c273a4 | ||
|
|
03684a893d | ||
|
|
fef27111b9 | ||
|
|
b04ed66686 | ||
|
|
f5df8bca20 | ||
|
|
060af66c09 | ||
|
|
a6adbd61db | ||
|
|
bab58054f7 | ||
|
|
e27e990cd6 | ||
|
|
196c2e0ed8 | ||
|
|
499bd4328a | ||
|
|
e389047aaf | ||
|
|
da04ff284b | ||
|
|
10b48aed97 | ||
|
|
ac6e9ae994 | ||
|
|
7dd589fb6c | ||
|
|
6b930058d1 | ||
|
|
aef3495a79 | ||
|
|
da341a95c6 | ||
|
|
bca5ec6482 | ||
|
|
f0a0cb7269 | ||
|
|
238b7618b4 | ||
|
|
4f206f67c9 | ||
|
|
fa513bf784 | ||
|
|
a68b0b3878 | ||
|
|
249489e697 | ||
|
|
e2c67d7dae | ||
|
|
703bc310a7 | ||
|
|
4caecfa680 | ||
|
|
fd4fde2543 | ||
|
|
763c5de03f | ||
|
|
4e160a46dd | ||
|
|
56aaf18663 | ||
|
|
df841aed27 | ||
|
|
d4372a0332 | ||
|
|
1595da789d | ||
|
|
7e65cb1d19 | ||
|
|
a931ee7afb | ||
|
|
bc6944736e | ||
|
|
16c2e28e9c | ||
|
|
ab89ef8db3 | ||
|
|
9ef14d11f8 | ||
|
|
e753990da3 | ||
|
|
f469c2e65d | ||
|
|
0525501b87 | ||
|
|
3a37fd9d32 | ||
|
|
b095c9dfb9 | ||
|
|
cd00effcfe | ||
|
|
1cd0334ccd | ||
|
|
3b1a16844e | ||
|
|
6995e8a451 | ||
|
|
1dd0f319fc | ||
|
|
5ce96369cf | ||
|
|
0791d78e28 | ||
|
|
cecbb7c34e | ||
|
|
525daff5fe | ||
|
|
b2c51b4b8c | ||
|
|
ffddb05ba3 | ||
|
|
4bb81b9a41 | ||
|
|
3df4e8d2ba | ||
|
|
3edef873bc | ||
|
|
c04b5cb851 | ||
|
|
fabd717d92 | ||
|
|
945bcf5fad | ||
|
|
d35d755379 | ||
|
|
a2622cb38e | ||
|
|
b58fd78450 | ||
|
|
e8d32066b4 | ||
|
|
6d3779eb83 | ||
|
|
c7bed04c80 | ||
|
|
b0e956cfa9 | ||
|
|
8608b7cded | ||
|
|
23f1b4ac03 | ||
|
|
27900c214f | ||
|
|
704701e251 | ||
|
|
34c53d352a | ||
|
|
32026d1fd4 | ||
|
|
35cdce0d52 | ||
|
|
e59098cf61 | ||
|
|
d8ca480b19 | ||
|
|
803b123ade | ||
|
|
cffb673fba | ||
|
|
1f6535d61b | ||
|
|
8a5ebe83a3 | ||
|
|
cc17d61016 | ||
|
|
0547ff719c | ||
|
|
c130846fe8 | ||
|
|
b3adde3a7a | ||
|
|
54603002a6 | ||
|
|
b995cdfc32 | ||
|
|
b09546577a | ||
|
|
6e84c1dc4c | ||
|
|
f41f3aaba8 | ||
|
|
8fa61e4632 | ||
|
|
7dacd752d3 | ||
|
|
7439d2401d | ||
|
|
dfb1ee0c56 | ||
|
|
3da73b821d | ||
|
|
66b82ac10a | ||
|
|
058acc5042 | ||
|
|
cdf822291f | ||
|
|
3f9e872e1c | ||
|
|
94aa3067f6 | ||
|
|
bff8064597 | ||
|
|
6b2d4e2585 | ||
|
|
56dfc2ae82 | ||
|
|
535d5ee47f | ||
|
|
838905f3dd | ||
|
|
59f5208c5c | ||
|
|
32a1a715ea | ||
|
|
3755d3870e | ||
|
|
0edc8a9284 | ||
|
|
5449d63602 | ||
|
|
dfa72fd6af | ||
|
|
0e50da324b | ||
|
|
679f8118f6 | ||
|
|
804d177782 | ||
|
|
eb96c9fece | ||
|
|
ed6c50621c | ||
|
|
7f74de07ed | ||
|
|
9e5bf93580 | ||
|
|
9d685da12d | ||
|
|
6bb40eb502 | ||
|
|
1ceee572cf | ||
|
|
391ffea4ab | ||
|
|
5292e569d9 | ||
|
|
82e111f1be | ||
|
|
1e9b3a1e42 | ||
|
|
35520d85a2 | ||
|
|
e19e37eef4 | ||
|
|
814795f566 | ||
|
|
1a633dcf10 | ||
|
|
c9b0e66641 | ||
|
|
440ff2f3ea | ||
|
|
96be63f50b | ||
|
|
69a323691f | ||
|
|
4357fcf7ef | ||
|
|
5f79513617 | ||
|
|
ba3ccbbc9a | ||
|
|
f182436673 | ||
|
|
793b907fe6 | ||
|
|
c75dfcd499 | ||
|
|
29a48f7914 | ||
|
|
095a8aa9dd | ||
|
|
2d8b5f04e9 | ||
|
|
ba9f97bc3e | ||
|
|
ed51f44ee8 | ||
|
|
933d787108 | ||
|
|
0bc12733a3 | ||
|
|
8cfd9c44ab | ||
|
|
3ddff84be0 | ||
|
|
afb5361a1b | ||
|
|
f376047632 | ||
|
|
7373eea842 | ||
|
|
a446df4620 | ||
|
|
306c6dd21d | ||
|
|
380ba8860c | ||
|
|
6c8c8ddcfc | ||
|
|
0c09c5e140 | ||
|
|
b761fba8cb | ||
|
|
dfceda3942 | ||
|
|
85d1825236 | ||
|
|
8f8c7e26c7 | ||
|
|
181768d91f | ||
|
|
2dcd4d8fd3 | ||
|
|
d909550134 | ||
|
|
c564ec41e2 | ||
|
|
af94a0e174 | ||
|
|
fcd8815fca | ||
|
|
5bd3f28117 | ||
|
|
384bde333e | ||
|
|
d06ec604b9 | ||
|
|
d8ce23f2a0 | ||
|
|
3ccb983377 | ||
|
|
19fb9fcd31 | ||
|
|
5f4a1b96ce | ||
|
|
6983f385fc | ||
|
|
2aadd1cac5 | ||
|
|
bfa7d429bd | ||
|
|
3238150b92 | ||
|
|
3603571967 | ||
|
|
7c2a8c0abb | ||
|
|
8b5f73badd | ||
|
|
ff57fd3d23 | ||
|
|
49aa1ea17c | ||
|
|
7430897085 | ||
|
|
82894a1c4f | ||
|
|
3bb4217a45 | ||
|
|
dfc8330035 | ||
|
|
45e03d87c7 | ||
|
|
3e358e5d26 | ||
|
|
dc5dae04eb | ||
|
|
9128b1f47d | ||
|
|
16a8226ba7 | ||
|
|
9ecaf3199d | ||
|
|
a44cd0373e | ||
|
|
2c0b629302 | ||
|
|
89a37a1771 | ||
|
|
cadc038966 | ||
|
|
07e8449379 | ||
|
|
71e908ad1a | ||
|
|
035c85b775 | ||
|
|
a87b753151 | ||
|
|
edaa4aff7e | ||
|
|
be02610349 | ||
|
|
1f65731165 | ||
|
|
fb61fb7849 | ||
|
|
060a945aea | ||
|
|
2797250f34 | ||
|
|
97b3f9b3c7 | ||
|
|
9363301d2a | ||
|
|
d93a826e28 | ||
|
|
244fe0835e | ||
|
|
5f1a5d6589 | ||
|
|
4e27c22292 | ||
|
|
53ee270096 | ||
|
|
a00ad20c08 | ||
|
|
e8764dd1c6 | ||
|
|
a869d38499 | ||
|
|
7bf7c96587 | ||
|
|
72b857a92f | ||
|
|
380d6a43a5 | ||
|
|
076f2b492e | ||
|
|
547f981c47 | ||
|
|
8bfc62a3b2 | ||
|
|
d39776a314 | ||
|
|
35641d0543 | ||
|
|
038cd6f149 | ||
|
|
38978dd447 | ||
|
|
fb24b7eaa7 | ||
|
|
b4ce45bc4b | ||
|
|
8d04198309 | ||
|
|
da796347c4 | ||
|
|
2d6a29664a | ||
|
|
4ba5cfceaf | ||
|
|
7cc324e1c0 | ||
|
|
cf89601269 | ||
|
|
c07e23bf8d | ||
|
|
5aa89d85f3 | ||
|
|
4ed1edf53d | ||
|
|
1be1326b52 | ||
|
|
42ae7d1568 | ||
|
|
b781c83545 | ||
|
|
e48cea18e7 | ||
|
|
ff82987144 | ||
|
|
f612a45550 | ||
|
|
182dce4840 | ||
|
|
4e41e072d6 | ||
|
|
a369c16db8 | ||
|
|
1fc820787a | ||
|
|
d3a83ba89e | ||
|
|
20fb66e2f7 | ||
|
|
a44646161b | ||
|
|
87027ea2d0 | ||
|
|
c9757cbb4b | ||
|
|
9a8f4aefe0 |
38
.github/ISSUE_TEMPLATE/-en--bug-report.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/-en--bug-report.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: "[EN] Bug report"
|
||||
about: Create a report to help us improve
|
||||
title: "[EN][BUG]"
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Title: [Brief Description of the Bug]
|
||||
|
||||
#### Description:
|
||||
Describe in detail the problem you encountered. Include any relevant context that may help understand the origin of the bug.
|
||||
|
||||
#### Steps to Reproduce:
|
||||
1. List the steps necessary to reproduce the problem.
|
||||
2. Try to be as specific as possible.
|
||||
3. If the problem occurs in a specific scenario, describe it here.
|
||||
|
||||
#### Expected Behavior:
|
||||
Describe what you expected to happen when following the steps above.
|
||||
|
||||
#### Current Behavior:
|
||||
Explain what actually happens when you follow the steps above.
|
||||
|
||||
#### Screenshots/Videos:
|
||||
If possible, add screenshots or videos illustrating the problem. This can be extremely helpful in understanding the issue.
|
||||
|
||||
#### Environment:
|
||||
- **Server:** [e.g., Ubuntu 18.04]
|
||||
- **API Version:** [e.g., 1.5.4]
|
||||
- **Other Hardware/Software Specifications:** [e.g., CPU, GPU]
|
||||
|
||||
#### Submitting Logs:
|
||||
Please attach logs that may be related to the problem. If the logs contain sensitive information, consider sending them privately to one of the project maintainers.
|
||||
|
||||
#### Additional Notes:
|
||||
Include here any other information that you think might be useful in understanding or resolving the bug.
|
||||
28
.github/ISSUE_TEMPLATE/-en--feature-request.md
vendored
Normal file
28
.github/ISSUE_TEMPLATE/-en--feature-request.md
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: "[EN] Feature request"
|
||||
about: Suggest an idea for the API
|
||||
title: "[EN][FEAT]"
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Title: [Brief Description of Feature Request]
|
||||
|
||||
#### Detailed Description:
|
||||
Clearly and in detail, describe the functionality you wish to be implemented. Explain how this fits into the context of the project.
|
||||
|
||||
#### Rationale:
|
||||
Explain why this functionality would be useful for the project. This helps in understanding the importance and priority of the request.
|
||||
|
||||
#### Usage Examples:
|
||||
Provide specific examples of how this feature could be used. This can include scenarios or use cases where the feature would be particularly beneficial.
|
||||
|
||||
#### Possible Implementations:
|
||||
If you have ideas on how this feature might be implemented, please share them here. This is not mandatory but can be helpful for the development team.
|
||||
|
||||
#### Impact on the Project:
|
||||
Discuss how this new feature could impact other parts of the project, if applicable.
|
||||
|
||||
#### Additional Notes:
|
||||
Any other information you believe is relevant to your request.
|
||||
38
.github/ISSUE_TEMPLATE/-pt--reportar-bug.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/-pt--reportar-bug.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: "[PT] Reportar bug"
|
||||
about: Reportar um problema
|
||||
title: "[PT][BUG]"
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Título: [Breve Descrição do Bug]
|
||||
|
||||
#### Descrição:
|
||||
Descreva detalhadamente o problema que você encontrou. Inclua qualquer contexto relevante que possa ajudar a entender a origem do bug.
|
||||
|
||||
#### Passos para Reproduzir:
|
||||
1. Liste os passos necessários para reproduzir o problema.
|
||||
2. Tente ser o mais específico possível.
|
||||
3. Se o problema ocorrer em um cenário específico, descreva-o aqui.
|
||||
|
||||
#### Comportamento Esperado:
|
||||
Descreva o que você esperava que acontecesse quando seguisse os passos acima.
|
||||
|
||||
#### Comportamento Atual:
|
||||
Explique o que realmente acontece quando você segue os passos acima.
|
||||
|
||||
#### Capturas de Tela/Vídeos:
|
||||
Se possível, adicione capturas de tela ou vídeos que ilustrem o problema. Isso pode ser extremamente útil para entender o problema.
|
||||
|
||||
#### Ambiente:
|
||||
- **Servidor:** [ex: Ubuntu 18.04]
|
||||
- **Versão da API:** [ex: 1.5.4]
|
||||
- **Outras Especificações de Hardware/Software:** [ex: CPU, GPU]
|
||||
|
||||
#### Envio de Logs:
|
||||
Por favor, anexe os logs que possam estar relacionados ao problema. Se os logs contiverem informações sensíveis, considere enviá-los de forma privada para um dos mantenedores do projeto.
|
||||
|
||||
#### Notas Adicionais:
|
||||
Inclua aqui qualquer outra informação que você ache que possa ser útil para entender ou resolver o bug.
|
||||
28
.github/ISSUE_TEMPLATE/-pt--solicitar-recurso.md
vendored
Normal file
28
.github/ISSUE_TEMPLATE/-pt--solicitar-recurso.md
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: "[PT] Solicitar recurso"
|
||||
about: Sugira novos recursos para a API
|
||||
title: "[PT][FEAT]"
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Título: [Breve Descrição da Solicitação de Recurso]
|
||||
|
||||
#### Descrição Detalhada:
|
||||
Descreva claramente e em detalhes a funcionalidade que você deseja que seja implementada. Explique como isso se encaixa no contexto do projeto.
|
||||
|
||||
#### Racional:
|
||||
Explique por que essa funcionalidade seria útil para o projeto. Isso ajuda a entender a importância e a prioridade da solicitação.
|
||||
|
||||
#### Exemplos de Uso:
|
||||
Forneça exemplos específicos de como essa funcionalidade poderia ser utilizada. Isso pode incluir cenários ou casos de uso onde a funcionalidade seria particularmente benéfica.
|
||||
|
||||
#### Possíveis Implementações:
|
||||
Se você tem ideias sobre como essa funcionalidade pode ser implementada, por favor, compartilhe-as aqui. Isso não é obrigatório, mas pode ser útil para a equipe de desenvolvimento.
|
||||
|
||||
#### Impacto no Projeto:
|
||||
Discuta como essa nova funcionalidade poderia impactar outras partes do projeto, se aplicável.
|
||||
|
||||
#### Notas Adicionais:
|
||||
Qualquer outra informação que você acredita ser relevante para a sua solicitação.
|
||||
64
.github/workflows/publish_docker_image.yml
vendored
Normal file
64
.github/workflows/publish_docker_image.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
name: Build Docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['v*']
|
||||
|
||||
jobs:
|
||||
build-amd:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Extract existing image metadata
|
||||
id: image-meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: atendai/evolution-api
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and push AMD image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
labels: ${{ steps.image-meta.outputs.labels }}
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
|
||||
build-arm:
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204-arm
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Extract existing image metadata
|
||||
id: image-meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: atendai/evolution-api
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and push ARM image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
labels: ${{ steps.image-meta.outputs.labels }}
|
||||
platforms: linux/arm64
|
||||
push: true
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -4,6 +4,8 @@
|
||||
|
||||
/Docker/.env
|
||||
|
||||
.vscode
|
||||
|
||||
# Logs
|
||||
logs/**.json
|
||||
*.log
|
||||
@@ -39,8 +41,10 @@ docker-compose.yaml
|
||||
/test/
|
||||
/src/env.yml
|
||||
/store
|
||||
*.env
|
||||
|
||||
/temp/*
|
||||
|
||||
.DS_Store
|
||||
*.DS_Store
|
||||
*.DS_Store
|
||||
.tool-versions
|
||||
|
||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -9,5 +9,8 @@
|
||||
"source.fixAll": "explicit"
|
||||
},
|
||||
"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"
|
||||
]
|
||||
}
|
||||
86
CHANGELOG.md
86
CHANGELOG.md
@@ -1,12 +1,69 @@
|
||||
# 1.6.1 (develop)
|
||||
# 1.7.0 (2024-03-11 18:23)
|
||||
|
||||
### Feature
|
||||
|
||||
* Added update message endpoint
|
||||
* Add translate capabilities to QRMessages in CW
|
||||
* Join in Group by Invite Code
|
||||
* Read messages from whatsapp in chatwoot
|
||||
* Add support to use use redis in cacheservice
|
||||
* Add support for labels
|
||||
* Command to clearcache from chatwoot inbox
|
||||
* Whatsapp Cloud API Oficial
|
||||
|
||||
### Fixed
|
||||
|
||||
* Proxy configuration improvements
|
||||
* Correction in sending lists
|
||||
* Adjust in webhook_base64
|
||||
* Correction in typebot text formatting
|
||||
* Correction in chatwoot text formatting and render list message
|
||||
* Only use a axios request to get file mimetype if necessary
|
||||
* When possible use the original file extension
|
||||
* When receiving a file from whatsapp, use the original filename in chatwoot if possible
|
||||
* Remove message ids cache in chatwoot to use chatwoot's api itself
|
||||
* Adjusts the quoted message, now has contextInfo in the message Raw
|
||||
* Collecting responses with text or numbers in Typebot
|
||||
* Added sendList endpoint to swagger documentation
|
||||
* Implemented a function to synchronize message deletions on WhatsApp, automatically reflecting in Chatwoot.
|
||||
* Improvement on numbers validation
|
||||
* Fix polls in message sending
|
||||
* Sending status message
|
||||
* Message 'connection successfully' spamming
|
||||
* Invalidate the conversation cache if reopen_conversation is false and the conversation was resolved
|
||||
* Fix looping when deleting a message in chatwoot
|
||||
* When receiving a file from whatsapp, use the original filename in chatwoot if possible
|
||||
* Correction in the sendList Function
|
||||
* Implement contact upsert in messaging-history.set
|
||||
* Improve proxy error handling
|
||||
* Refactor fetching participants for group in WhatsApp service
|
||||
* Fixed problem where the typebot final keyword did not work
|
||||
* Typebot's wait now pauses the flow and composing is defined by the delay_message parameter in set typebot
|
||||
* Composing over 20s now loops until finished
|
||||
|
||||
# 1.6.1 (2023-12-22 11:43)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed Lid Messages
|
||||
* Fixed sending variables to typebot
|
||||
* Fixed sending variables from typebot
|
||||
* Correction sending s3/minio media to chatwoot and typebot
|
||||
* Fixed the problem with typebot closing at the end of the flow, now this is optional with the TYPEBOT_KEEP_OPEN variable
|
||||
* Fixed chatwoot Bold, Italic and Underline formatting using Regex
|
||||
* Added the sign_delimiter property to the Chatwoot configuration, allowing you to set a different delimiter for the signature. Default when not defined \n
|
||||
* Include instance Id field in the instance configuration
|
||||
* Fixed the pairing code
|
||||
* Adjusts in typebot
|
||||
* Fix the problem when disconnecting the instance and connecting again using mongodb
|
||||
* Options to disable docs and manager
|
||||
* When deleting a message in whatsapp, delete the message in chatwoot too
|
||||
|
||||
|
||||
# 1.6.0 (2023-12-12 17:24)
|
||||
|
||||
### Feature
|
||||
|
||||
* Added AWS SQS Integration
|
||||
* Added support for new typebot API
|
||||
* Added endpoint sendPresence
|
||||
@@ -24,7 +81,6 @@
|
||||
* Fix workaround to manage param data as an array in mongodb
|
||||
* Removed await from webhook when sending a message
|
||||
* Update typebot.service.ts - element.underline change ~ for *
|
||||
* Adjusts in proxy
|
||||
* Removed api restart on receiving an error
|
||||
* Fixes in mongodb and chatwoot
|
||||
* Adjusted return from queries in mongodb
|
||||
@@ -36,8 +92,8 @@
|
||||
|
||||
### Integrations
|
||||
|
||||
- Chatwoot: v3.3.1
|
||||
- Typebot: v2.20.0
|
||||
* Chatwoot: v3.3.1
|
||||
* Typebot: v2.20.0
|
||||
|
||||
# 1.5.4 (2023-10-09 20:43)
|
||||
|
||||
@@ -116,9 +172,9 @@
|
||||
|
||||
### Integrations
|
||||
|
||||
- Chatwoot: v2.18.0 - v3.0.0
|
||||
- Typebot: v2.16.0
|
||||
- Manager Evolution API
|
||||
* Chatwoot: v2.18.0 - v3.0.0
|
||||
* Typebot: v2.16.0
|
||||
* Manager Evolution API
|
||||
|
||||
# 1.4.8 (2023-07-27 10:27)
|
||||
|
||||
@@ -171,7 +227,7 @@
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed validation is set settings
|
||||
* Fixed validation is set settings
|
||||
* Adjusts in group validations
|
||||
* Ajusts in sticker message to chatwoot
|
||||
|
||||
@@ -206,7 +262,7 @@
|
||||
|
||||
### Integrations
|
||||
|
||||
- Chatwoot: v2.18.0 - v3.0.0 (Beta)
|
||||
* Chatwoot: v2.18.0 - v3.0.0 (Beta)
|
||||
|
||||
# 1.3.2 (2023-07-21 17:19)
|
||||
|
||||
@@ -222,7 +278,7 @@
|
||||
|
||||
### Integrations
|
||||
|
||||
- Chatwoot: v2.18.0
|
||||
* Chatwoot: v2.18.0
|
||||
|
||||
# 1.3.1 (2023-07-20 07:48)
|
||||
|
||||
@@ -232,7 +288,7 @@
|
||||
|
||||
### Integrations
|
||||
|
||||
- Chatwoot: v2.18.0
|
||||
* Chatwoot: v2.18.0
|
||||
|
||||
# 1.3.0 (2023-07-19 11:33)
|
||||
|
||||
@@ -248,7 +304,7 @@
|
||||
* Translation set to default (english) in chatwoot
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
* Fixed error to send message in large groups
|
||||
* Docker files adjusted
|
||||
* Fixed in the postman collection the webhookByEvent parameter by webhook_by_events
|
||||
@@ -269,7 +325,7 @@
|
||||
|
||||
### Integrations
|
||||
|
||||
- Chatwoot: v2.18.0
|
||||
* Chatwoot: v2.18.0
|
||||
|
||||
# 1.2.2 (2023-07-15 09:36)
|
||||
|
||||
@@ -280,7 +336,7 @@
|
||||
|
||||
### Integrations
|
||||
|
||||
- Chatwoot: v2.18.0
|
||||
* Chatwoot: v2.18.0
|
||||
|
||||
# 1.2.1 (2023-07-14 19:04)
|
||||
|
||||
@@ -436,4 +492,4 @@
|
||||
* Sending the local webhook url as destination in the webhook data for webhook redirection
|
||||
* Startup modes, server or container
|
||||
* Server Mode works normally as everyone is used to
|
||||
* Container mode made to use one instance per container, when starting the application an instance is already created and the qrcode is generated and it starts sending webhook without having to call it manually, it only allows one instance at a time.
|
||||
* Container mode made to use one instance per container, when starting the application an instance is already created and the qrcode is generated and it starts sending webhook without having to call it manually, it only allows one instance at a time.
|
||||
|
||||
@@ -16,6 +16,7 @@ LOG_BAILEYS=error
|
||||
# Default time: 5 minutes
|
||||
# If you don't even want an expiration, enter the value false
|
||||
DEL_INSTANCE=false
|
||||
DEL_TEMP_INSTANCES=true # Delete instances with status closed on start
|
||||
|
||||
# Temporary data storage
|
||||
STORE_MESSAGES=true
|
||||
@@ -47,10 +48,17 @@ 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
|
||||
|
||||
WA_BUSINESS_TOKEN_WEBHOOK=evolution
|
||||
WA_BUSINESS_URL=https://graph.facebook.com
|
||||
WA_BUSINESS_VERSION=v18.0
|
||||
WA_BUSINESS_LANGUAGE=pt_BR
|
||||
|
||||
SQS_ENABLED=false
|
||||
SQS_ACCESS_KEY_ID=
|
||||
SQS_SECRET_ACCESS_KEY=
|
||||
@@ -84,6 +92,8 @@ WEBHOOK_EVENTS_GROUPS_UPSERT=true
|
||||
WEBHOOK_EVENTS_GROUPS_UPDATE=true
|
||||
WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
|
||||
WEBHOOK_EVENTS_CONNECTION_UPDATE=true
|
||||
WEBHOOK_EVENTS_LABELS_EDIT=true
|
||||
WEBHOOK_EVENTS_LABELS_ASSOCIATION=true
|
||||
WEBHOOK_EVENTS_CALL=true
|
||||
# This event fires every time a new token is requested via the refresh route
|
||||
WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
|
||||
@@ -107,6 +117,16 @@ QRCODE_COLOR=#198754
|
||||
|
||||
# old | latest
|
||||
TYPEBOT_API_VERSION=latest
|
||||
TYPEBOT_KEEP_OPEN=false
|
||||
|
||||
#Chatwoot
|
||||
# If you leave this option as false, when deleting the message for everyone on WhatsApp, it will not be deleted on Chatwoot.
|
||||
CHATWOOT_MESSAGE_DELETE=false # false | true
|
||||
# If you leave this option as true, when sending a message in Chatwoot, the client's last message will be marked as read on WhatsApp.
|
||||
CHATWOOT_MESSAGE_READ=false # false | true
|
||||
# This db connection is used to import messages from whatsapp to chatwoot database
|
||||
CHATWOOT_IMPORT_DATABASE_CONNECTION_URI=postgres://user:password@hostname:port/dbname
|
||||
CHATWOOT_IMPORT_DATABASE_PLACEHOLDER_MEDIA_MESSAGE=true
|
||||
|
||||
# Defines an authentication type for the api
|
||||
# We recommend using the apikey because it will allow you to use a custom token,
|
||||
@@ -121,3 +141,5 @@ AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES=true
|
||||
# seconds - 3600s ===1h | zero (0) - never expires
|
||||
AUTHENTICATION_JWT_EXPIRIN_IN=0
|
||||
AUTHENTICATION_JWT_SECRET='L=0YWt]b2w[WF>#>:&E`'
|
||||
|
||||
LANGUAGE=en # pt-BR, en
|
||||
@@ -4,7 +4,7 @@ services:
|
||||
|
||||
api:
|
||||
container_name: evolution_api
|
||||
image: davidsongomes/evolution-api
|
||||
image: atendai/evolution-api
|
||||
restart: always
|
||||
ports:
|
||||
- 8080:8080
|
||||
@@ -19,4 +19,4 @@ services:
|
||||
|
||||
volumes:
|
||||
evolution_instances:
|
||||
evolution_store:
|
||||
evolution_store:
|
||||
|
||||
@@ -16,6 +16,7 @@ LOG_BAILEYS=error
|
||||
# Default time: 5 minutes
|
||||
# If you don't even want an expiration, enter the value false
|
||||
DEL_INSTANCE=false
|
||||
DEL_TEMP_INSTANCES=true # Delete instances with status closed on start
|
||||
|
||||
# Temporary data storage
|
||||
STORE_MESSAGES=true
|
||||
@@ -73,6 +74,8 @@ WEBHOOK_EVENTS_GROUPS_UPSERT=true
|
||||
WEBHOOK_EVENTS_GROUPS_UPDATE=true
|
||||
WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
|
||||
WEBHOOK_EVENTS_CONNECTION_UPDATE=true
|
||||
WEBHOOK_EVENTS_LABELS_EDIT=true
|
||||
WEBHOOK_EVENTS_LABELS_ASSOCIATION=true
|
||||
# This event fires every time a new token is requested via the refresh route
|
||||
WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
|
||||
|
||||
@@ -62,7 +62,7 @@ services:
|
||||
|
||||
api:
|
||||
container_name: evolution_api
|
||||
image: davidsongomes/evolution-api
|
||||
image: atendai/evolution-api
|
||||
restart: always
|
||||
depends_on:
|
||||
- mongodb
|
||||
@@ -88,4 +88,4 @@ volumes:
|
||||
networks:
|
||||
evolution-net:
|
||||
external: true
|
||||
|
||||
|
||||
|
||||
32
Dockerfile
32
Dockerfile
@@ -1,6 +1,6 @@
|
||||
FROM node:20.7.0-alpine
|
||||
FROM node:20.7.0-alpine AS builder
|
||||
|
||||
LABEL version="1.6.0" description="Api to control whatsapp features through http requests."
|
||||
LABEL version="1.7.0" description="Api to control whatsapp features through http requests."
|
||||
LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
|
||||
LABEL contact="contato@agenciadgcode.com"
|
||||
|
||||
@@ -11,9 +11,19 @@ WORKDIR /evolution
|
||||
|
||||
COPY ./package.json .
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
FROM node:20.7.0-alpine AS final
|
||||
|
||||
ENV TZ=America/Sao_Paulo
|
||||
ENV DOCKER_ENV=true
|
||||
|
||||
ENV SERVER_TYPE=http
|
||||
ENV SERVER_PORT=8080
|
||||
ENV SERVER_URL=http://localhost:8080
|
||||
|
||||
ENV CORS_ORIGIN=*
|
||||
@@ -25,6 +35,7 @@ ENV LOG_COLOR=true
|
||||
ENV LOG_BAILEYS=error
|
||||
|
||||
ENV DEL_INSTANCE=false
|
||||
ENV DEL_TEMP_INSTANCES=true
|
||||
|
||||
ENV STORE_MESSAGES=true
|
||||
ENV STORE_MESSAGE_UP=true
|
||||
@@ -52,10 +63,17 @@ 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 WA_BUSINESS_TOKEN_WEBHOOK=evolution
|
||||
ENV WA_BUSINESS_URL=https://graph.facebook.com
|
||||
ENV WA_BUSINESS_VERSION=v18.0
|
||||
ENV WA_BUSINESS_LANGUAGE=pt_BR
|
||||
|
||||
ENV SQS_ENABLED=false
|
||||
ENV SQS_ACCESS_KEY_ID=
|
||||
ENV SQS_SECRET_ACCESS_KEY=
|
||||
@@ -68,6 +86,8 @@ ENV WEBHOOK_GLOBAL_ENABLED=false
|
||||
ENV WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=false
|
||||
|
||||
ENV WEBHOOK_EVENTS_APPLICATION_STARTUP=false
|
||||
ENV WEBHOOK_EVENTS_INSTANCE_CREATE=false
|
||||
ENV WEBHOOK_EVENTS_INSTANCE_DELETE=false
|
||||
ENV WEBHOOK_EVENTS_QRCODE_UPDATED=true
|
||||
ENV WEBHOOK_EVENTS_MESSAGES_SET=true
|
||||
ENV WEBHOOK_EVENTS_MESSAGES_UPSERT=true
|
||||
@@ -86,6 +106,8 @@ ENV WEBHOOK_EVENTS_GROUPS_UPSERT=true
|
||||
ENV WEBHOOK_EVENTS_GROUPS_UPDATE=true
|
||||
ENV WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
|
||||
ENV WEBHOOK_EVENTS_CONNECTION_UPDATE=true
|
||||
ENV WEBHOOK_EVENTS_LABELS_EDIT=true
|
||||
ENV WEBHOOK_EVENTS_LABELS_ASSOCIATION=true
|
||||
ENV WEBHOOK_EVENTS_CALL=true
|
||||
|
||||
ENV WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
|
||||
@@ -122,10 +144,8 @@ ENV AUTHENTICATION_INSTANCE_CHATWOOT_ACCOUNT_ID=1
|
||||
ENV AUTHENTICATION_INSTANCE_CHATWOOT_TOKEN=123456
|
||||
ENV AUTHENTICATION_INSTANCE_CHATWOOT_URL=<url>
|
||||
|
||||
RUN npm install
|
||||
WORKDIR /evolution
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm run build
|
||||
COPY --from=builder /evolution .
|
||||
|
||||
CMD [ "node", "./dist/src/main.js" ]
|
||||
|
||||
@@ -3,7 +3,7 @@ version: '3.3'
|
||||
services:
|
||||
api:
|
||||
container_name: evolution_api
|
||||
image: davidsongomes/evolution-api:latest
|
||||
image: atendai/evolution-api:latest
|
||||
restart: always
|
||||
ports:
|
||||
- 8080:8080
|
||||
|
||||
21
package.json
21
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "evolution-api",
|
||||
"version": "1.6.0",
|
||||
"version": "1.7.0",
|
||||
"description": "Rest api for communication with WhatsApp",
|
||||
"main": "./dist/src/main.js",
|
||||
"scripts": {
|
||||
@@ -46,42 +46,47 @@
|
||||
"@figuro/chatwoot-sdk": "^1.1.16",
|
||||
"@hapi/boom": "^10.0.1",
|
||||
"@sentry/node": "^7.59.2",
|
||||
"@whiskeysockets/baileys": "^6.5.0",
|
||||
"@whiskeysockets/baileys": "6.6.0",
|
||||
"amqplib": "^0.10.3",
|
||||
"aws-sdk": "^2.1499.0",
|
||||
"axios": "^1.3.5",
|
||||
"class-validator": "^0.13.2",
|
||||
"axios": "^1.6.5",
|
||||
"class-validator": "^0.14.1",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"cross-env": "^7.0.3",
|
||||
"dayjs": "^1.11.7",
|
||||
"eventemitter2": "^6.4.9",
|
||||
"evolution-manager": "^0.4.4",
|
||||
"evolution-manager": "^0.4.13",
|
||||
"exiftool-vendored": "^22.0.0",
|
||||
"express": "^4.18.2",
|
||||
"express-async-errors": "^3.1.1",
|
||||
"form-data": "^4.0.0",
|
||||
"hbs": "^4.2.0",
|
||||
"https-proxy-agent": "^7.0.2",
|
||||
"i18next": "^23.7.19",
|
||||
"jimp": "^0.16.13",
|
||||
"join": "^3.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonschema": "^1.4.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"libphonenumber-js": "^1.10.39",
|
||||
"link-preview-js": "^3.0.4",
|
||||
"mongoose": "^6.10.5",
|
||||
"node-cache": "^5.1.2",
|
||||
"node-mime-types": "^1.1.0",
|
||||
"node-windows": "^1.0.0-beta.8",
|
||||
"parse-bmfont-xml": "^1.1.4",
|
||||
"pg": "^8.11.3",
|
||||
"pino": "^8.11.0",
|
||||
"proxy-agent": "^6.3.0",
|
||||
"qrcode": "^1.5.1",
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"redis": "^4.6.5",
|
||||
"sharp": "^0.30.7",
|
||||
"sharp": "^0.32.2",
|
||||
"socket.io": "^4.7.1",
|
||||
"socks-proxy-agent": "^8.0.1",
|
||||
"swagger-ui-express": "^5.0.0",
|
||||
"uuid": "^9.0.0",
|
||||
"xml2js": "^0.6.2",
|
||||
"yamljs": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -3,7 +3,13 @@ import { readFileSync } from 'fs';
|
||||
import { load } from 'js-yaml';
|
||||
import { join } from 'path';
|
||||
|
||||
export type HttpServer = { TYPE: 'http' | 'https'; PORT: number; URL: string };
|
||||
export type HttpServer = {
|
||||
TYPE: 'http' | 'https';
|
||||
PORT: number;
|
||||
URL: string;
|
||||
DISABLE_DOCS: boolean;
|
||||
DISABLE_MANAGER: boolean;
|
||||
};
|
||||
|
||||
export type HttpMethods = 'POST' | 'GET' | 'PUT' | 'DELETE';
|
||||
export type Cors = {
|
||||
@@ -28,6 +34,7 @@ export type SaveData = {
|
||||
MESSAGE_UPDATE: boolean;
|
||||
CONTACTS: boolean;
|
||||
CHATS: boolean;
|
||||
LABELS: boolean;
|
||||
};
|
||||
|
||||
export type StoreConf = {
|
||||
@@ -35,6 +42,7 @@ export type StoreConf = {
|
||||
MESSAGE_UP: boolean;
|
||||
CONTACTS: boolean;
|
||||
CHATS: boolean;
|
||||
LABELS: boolean;
|
||||
};
|
||||
|
||||
export type CleanStoreConf = {
|
||||
@@ -63,6 +71,8 @@ export type Redis = {
|
||||
|
||||
export type Rabbitmq = {
|
||||
ENABLED: boolean;
|
||||
MODE: string; // global, single, isolated
|
||||
EXCHANGE_NAME: string; // available for global and single, isolated mode will use instance name as exchange
|
||||
URI: string;
|
||||
};
|
||||
|
||||
@@ -78,8 +88,17 @@ export type Websocket = {
|
||||
ENABLED: boolean;
|
||||
};
|
||||
|
||||
export type WaBusiness = {
|
||||
TOKEN_WEBHOOK: string;
|
||||
URL: string;
|
||||
VERSION: string;
|
||||
LANGUAGE: string;
|
||||
};
|
||||
|
||||
export type EventsWebhook = {
|
||||
APPLICATION_STARTUP: boolean;
|
||||
INSTANCE_CREATE: boolean;
|
||||
INSTANCE_DELETE: boolean;
|
||||
QRCODE_UPDATED: boolean;
|
||||
MESSAGES_SET: boolean;
|
||||
MESSAGES_UPSERT: boolean;
|
||||
@@ -95,6 +114,8 @@ export type EventsWebhook = {
|
||||
CHATS_DELETE: boolean;
|
||||
CHATS_UPSERT: boolean;
|
||||
CONNECTION_UPDATE: boolean;
|
||||
LABELS_EDIT: boolean;
|
||||
LABELS_ASSOCIATION: boolean;
|
||||
GROUPS_UPSERT: boolean;
|
||||
GROUP_UPDATE: boolean;
|
||||
GROUP_PARTICIPANTS_UPDATE: boolean;
|
||||
@@ -119,16 +140,42 @@ export type Auth = {
|
||||
|
||||
export type DelInstance = number | boolean;
|
||||
|
||||
export type Language = string | 'en';
|
||||
|
||||
export type GlobalWebhook = {
|
||||
URL: string;
|
||||
ENABLED: boolean;
|
||||
WEBHOOK_BY_EVENTS: boolean;
|
||||
};
|
||||
export type CacheConfRedis = {
|
||||
ENABLED: boolean;
|
||||
URI: string;
|
||||
PREFIX_KEY: string;
|
||||
TTL: number;
|
||||
};
|
||||
export type CacheConfLocal = {
|
||||
ENABLED: boolean;
|
||||
TTL: number;
|
||||
};
|
||||
export type SslConf = { PRIVKEY: string; FULLCHAIN: string };
|
||||
export type Webhook = { GLOBAL?: GlobalWebhook; EVENTS: EventsWebhook };
|
||||
export type ConfigSessionPhone = { CLIENT: string; NAME: string };
|
||||
export type QrCode = { LIMIT: number; COLOR: string };
|
||||
export type Typebot = { API_VERSION: string };
|
||||
export type Typebot = { API_VERSION: string; KEEP_OPEN: boolean };
|
||||
export type Chatwoot = {
|
||||
MESSAGE_DELETE: boolean;
|
||||
MESSAGE_READ: boolean;
|
||||
IMPORT: {
|
||||
DATABASE: {
|
||||
CONNECTION: {
|
||||
URI: string;
|
||||
};
|
||||
};
|
||||
PLACEHOLDER_MEDIA_MESSAGE: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export type CacheConf = { REDIS: CacheConfRedis; LOCAL: CacheConfLocal };
|
||||
export type Production = boolean;
|
||||
|
||||
export interface Env {
|
||||
@@ -142,12 +189,17 @@ export interface Env {
|
||||
RABBITMQ: Rabbitmq;
|
||||
SQS: Sqs;
|
||||
WEBSOCKET: Websocket;
|
||||
WA_BUSINESS: WaBusiness;
|
||||
LOG: Log;
|
||||
DEL_INSTANCE: DelInstance;
|
||||
DEL_TEMP_INSTANCES: boolean;
|
||||
LANGUAGE: Language;
|
||||
WEBHOOK: Webhook;
|
||||
CONFIG_SESSION_PHONE: ConfigSessionPhone;
|
||||
QRCODE: QrCode;
|
||||
TYPEBOT: Typebot;
|
||||
CHATWOOT: Chatwoot;
|
||||
CACHE: CacheConf;
|
||||
AUTHENTICATION: Auth;
|
||||
PRODUCTION?: Production;
|
||||
}
|
||||
@@ -169,8 +221,8 @@ export class ConfigService {
|
||||
this.env = !(process.env?.DOCKER_ENV === 'true') ? this.envYaml() : this.envProcess();
|
||||
this.env.PRODUCTION = process.env?.NODE_ENV === 'PROD';
|
||||
if (process.env?.DOCKER_ENV === 'true') {
|
||||
this.env.SERVER.TYPE = 'http';
|
||||
this.env.SERVER.PORT = 8080;
|
||||
this.env.SERVER.TYPE = process.env.SERVER_TYPE as 'http' | 'http';
|
||||
this.env.SERVER.PORT = Number.parseInt(process.env.SERVER_PORT) || 8080;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,9 +233,11 @@ export class ConfigService {
|
||||
private envProcess(): Env {
|
||||
return {
|
||||
SERVER: {
|
||||
TYPE: process.env.SERVER_TYPE as 'http' | 'https',
|
||||
TYPE: (process.env.SERVER_TYPE as 'http' | 'https') || 'http',
|
||||
PORT: Number.parseInt(process.env.SERVER_PORT) || 8080,
|
||||
URL: process.env.SERVER_URL,
|
||||
DISABLE_DOCS: process.env?.SERVER_DISABLE_DOCS === 'true',
|
||||
DISABLE_MANAGER: process.env?.SERVER_DISABLE_MANAGER === 'true',
|
||||
},
|
||||
CORS: {
|
||||
ORIGIN: process.env.CORS_ORIGIN.split(',') || ['*'],
|
||||
@@ -199,6 +253,7 @@ export class ConfigService {
|
||||
MESSAGE_UP: process.env?.STORE_MESSAGE_UP === 'true',
|
||||
CONTACTS: process.env?.STORE_CONTACTS === 'true',
|
||||
CHATS: process.env?.STORE_CHATS === 'true',
|
||||
LABELS: process.env?.STORE_LABELS === 'true',
|
||||
},
|
||||
CLEAN_STORE: {
|
||||
CLEANING_INTERVAL: Number.isInteger(process.env?.CLEAN_STORE_CLEANING_TERMINAL)
|
||||
@@ -221,6 +276,7 @@ export class ConfigService {
|
||||
MESSAGE_UPDATE: process.env?.DATABASE_SAVE_MESSAGE_UPDATE === 'true',
|
||||
CONTACTS: process.env?.DATABASE_SAVE_DATA_CONTACTS === 'true',
|
||||
CHATS: process.env?.DATABASE_SAVE_DATA_CHATS === 'true',
|
||||
LABELS: process.env?.DATABASE_SAVE_DATA_LABELS === 'true',
|
||||
},
|
||||
},
|
||||
REDIS: {
|
||||
@@ -230,6 +286,8 @@ export class ConfigService {
|
||||
},
|
||||
RABBITMQ: {
|
||||
ENABLED: process.env?.RABBITMQ_ENABLED === 'true',
|
||||
MODE: process.env?.RABBITMQ_MODE || 'isolated',
|
||||
EXCHANGE_NAME: process.env?.RABBITMQ_EXCHANGE_NAME || 'evolution_exchange',
|
||||
URI: process.env.RABBITMQ_URI || '',
|
||||
},
|
||||
SQS: {
|
||||
@@ -242,6 +300,12 @@ export class ConfigService {
|
||||
WEBSOCKET: {
|
||||
ENABLED: process.env?.WEBSOCKET_ENABLED === 'true',
|
||||
},
|
||||
WA_BUSINESS: {
|
||||
TOKEN_WEBHOOK: process.env.WA_BUSINESS_TOKEN_WEBHOOK || '',
|
||||
URL: process.env.WA_BUSINESS_URL || '',
|
||||
VERSION: process.env.WA_BUSINESS_VERSION || '',
|
||||
LANGUAGE: process.env.WA_BUSINESS_LANGUAGE || 'en',
|
||||
},
|
||||
LOG: {
|
||||
LEVEL: (process.env?.LOG_LEVEL.split(',') as LogLevel[]) || [
|
||||
'ERROR',
|
||||
@@ -259,6 +323,10 @@ export class ConfigService {
|
||||
DEL_INSTANCE: isBooleanString(process.env?.DEL_INSTANCE)
|
||||
? process.env.DEL_INSTANCE === 'true'
|
||||
: Number.parseInt(process.env.DEL_INSTANCE) || false,
|
||||
DEL_TEMP_INSTANCES: isBooleanString(process.env?.DEL_TEMP_INSTANCES)
|
||||
? process.env.DEL_TEMP_INSTANCES === 'true'
|
||||
: true,
|
||||
LANGUAGE: process.env?.LANGUAGE || 'en',
|
||||
WEBHOOK: {
|
||||
GLOBAL: {
|
||||
URL: process.env?.WEBHOOK_GLOBAL_URL || '',
|
||||
@@ -267,6 +335,8 @@ export class ConfigService {
|
||||
},
|
||||
EVENTS: {
|
||||
APPLICATION_STARTUP: process.env?.WEBHOOK_EVENTS_APPLICATION_STARTUP === 'true',
|
||||
INSTANCE_CREATE: process.env?.WEBHOOK_EVENTS_INSTANCE_CREATE === 'true',
|
||||
INSTANCE_DELETE: process.env?.WEBHOOK_EVENTS_INSTANCE_DELETE === 'true',
|
||||
QRCODE_UPDATED: process.env?.WEBHOOK_EVENTS_QRCODE_UPDATED === 'true',
|
||||
MESSAGES_SET: process.env?.WEBHOOK_EVENTS_MESSAGES_SET === 'true',
|
||||
MESSAGES_UPSERT: process.env?.WEBHOOK_EVENTS_MESSAGES_UPSERT === 'true',
|
||||
@@ -282,6 +352,8 @@ export class ConfigService {
|
||||
CHATS_UPSERT: process.env?.WEBHOOK_EVENTS_CHATS_UPSERT === 'true',
|
||||
CHATS_DELETE: process.env?.WEBHOOK_EVENTS_CHATS_DELETE === 'true',
|
||||
CONNECTION_UPDATE: process.env?.WEBHOOK_EVENTS_CONNECTION_UPDATE === 'true',
|
||||
LABELS_EDIT: process.env?.WEBHOOK_EVENTS_LABELS_EDIT === 'true',
|
||||
LABELS_ASSOCIATION: process.env?.WEBHOOK_EVENTS_LABELS_ASSOCIATION === 'true',
|
||||
GROUPS_UPSERT: process.env?.WEBHOOK_EVENTS_GROUPS_UPSERT === 'true',
|
||||
GROUP_UPDATE: process.env?.WEBHOOK_EVENTS_GROUPS_UPDATE === 'true',
|
||||
GROUP_PARTICIPANTS_UPDATE: process.env?.WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE === 'true',
|
||||
@@ -304,6 +376,31 @@ export class ConfigService {
|
||||
},
|
||||
TYPEBOT: {
|
||||
API_VERSION: process.env?.TYPEBOT_API_VERSION || 'old',
|
||||
KEEP_OPEN: process.env.TYPEBOT_KEEP_OPEN === 'true',
|
||||
},
|
||||
CHATWOOT: {
|
||||
MESSAGE_DELETE: process.env.CHATWOOT_MESSAGE_DELETE === 'false',
|
||||
MESSAGE_READ: process.env.CHATWOOT_MESSAGE_READ === 'false',
|
||||
IMPORT: {
|
||||
DATABASE: {
|
||||
CONNECTION: {
|
||||
URI: process.env.CHATWOOT_IMPORT_DATABASE_CONNECTION_URI || '',
|
||||
},
|
||||
},
|
||||
PLACEHOLDER_MEDIA_MESSAGE: process.env?.CHATWOOT_IMPORT_PLACEHOLDER_MEDIA_MESSAGE === 'true',
|
||||
},
|
||||
},
|
||||
CACHE: {
|
||||
REDIS: {
|
||||
ENABLED: process.env?.CACHE_REDIS_ENABLED === 'true',
|
||||
URI: process.env?.CACHE_REDIS_URI || '',
|
||||
PREFIX_KEY: process.env?.CACHE_REDIS_PREFIX_KEY || 'evolution-cache',
|
||||
TTL: Number.parseInt(process.env?.CACHE_REDIS_TTL) || 604800,
|
||||
},
|
||||
LOCAL: {
|
||||
ENABLED: process.env?.CACHE_LOCAL_ENABLED === 'true',
|
||||
TTL: Number.parseInt(process.env?.CACHE_REDIS_TTL) || 86400,
|
||||
},
|
||||
},
|
||||
AUTHENTICATION: {
|
||||
TYPE: process.env.AUTHENTICATION_TYPE as 'apikey',
|
||||
|
||||
@@ -9,6 +9,8 @@ SERVER:
|
||||
TYPE: http # https
|
||||
PORT: 8080 # 443
|
||||
URL: localhost
|
||||
DISABLE_MANAGER: false
|
||||
DISABLE_DOCS: false
|
||||
|
||||
CORS:
|
||||
ORIGIN:
|
||||
@@ -45,6 +47,7 @@ LOG:
|
||||
# Default time: 5 minutes
|
||||
# If you don't even want an expiration, enter the value false
|
||||
DEL_INSTANCE: false # or false
|
||||
DEL_TEMP_INSTANCES: true # Delete instances with status closed on start
|
||||
|
||||
# Temporary data storage
|
||||
STORE:
|
||||
@@ -81,6 +84,8 @@ REDIS:
|
||||
|
||||
RABBITMQ:
|
||||
ENABLED: false
|
||||
MODE: "global"
|
||||
EXCHANGE_NAME: "evolution_exchange"
|
||||
URI: "amqp://guest:guest@localhost:5672"
|
||||
|
||||
SQS:
|
||||
@@ -93,6 +98,12 @@ SQS:
|
||||
WEBSOCKET:
|
||||
ENABLED: false
|
||||
|
||||
WA_BUSINESS:
|
||||
TOKEN_WEBHOOK: evolution
|
||||
URL: https://graph.facebook.com
|
||||
VERSION: v18.0
|
||||
LANGUAGE: pt_BR
|
||||
|
||||
# Global Webhook Settings
|
||||
# Each instance's Webhook URL and events will be requested at the time it is created
|
||||
WEBHOOK:
|
||||
@@ -124,6 +135,8 @@ WEBHOOK:
|
||||
GROUP_UPDATE: true
|
||||
GROUP_PARTICIPANTS_UPDATE: true
|
||||
CONNECTION_UPDATE: true
|
||||
LABELS_EDIT: true
|
||||
LABELS_ASSOCIATION: true
|
||||
CALL: true
|
||||
# This event fires every time a new token is requested via the refresh route
|
||||
NEW_JWT_TOKEN: false
|
||||
@@ -147,7 +160,31 @@ QRCODE:
|
||||
COLOR: "#198754"
|
||||
|
||||
TYPEBOT:
|
||||
API_VERSION: 'old' # old | latest
|
||||
API_VERSION: "old" # old | latest
|
||||
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.
|
||||
MESSAGE_DELETE: true # 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.
|
||||
MESSAGE_READ: false # false | true
|
||||
IMPORT:
|
||||
# This db connection is used to import messages from whatsapp to chatwoot database
|
||||
DATABASE:
|
||||
CONNECTION:
|
||||
URI: "postgres://user:password@hostname:port/dbname"
|
||||
PLACEHOLDER_MEDIA_MESSAGE: true
|
||||
|
||||
# Cache to optimize application performance
|
||||
CACHE:
|
||||
REDIS:
|
||||
ENABLED: false
|
||||
URI: "redis://localhost:6379"
|
||||
PREFIX_KEY: "evolution-cache"
|
||||
TTL: 604800
|
||||
LOCAL:
|
||||
ENABLED: false
|
||||
TTL: 86400
|
||||
|
||||
# Defines an authentication type for the api
|
||||
# We recommend using the apikey because it will allow you to use a custom token,
|
||||
@@ -164,3 +201,5 @@ AUTHENTICATION:
|
||||
JWT:
|
||||
EXPIRIN_IN: 0 # seconds - 3600s === 1h | zero (0) - never expires
|
||||
SECRET: L=0YWt]b2w[WF>#>:&E`
|
||||
|
||||
LANGUAGE: "pt-BR" # pt-BR, en
|
||||
|
||||
@@ -25,7 +25,7 @@ info:
|
||||
</font>
|
||||
|
||||
[](https://god.gw.postman.com/run-collection/26869335-5546d063-156b-4529-915f-909dd628c090?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D26869335-5546d063-156b-4529-915f-909dd628c090%26entityType%3Dcollection%26workspaceId%3D339a4ee7-378b-45c9-b5b8-fd2c0a9c2442)
|
||||
version: 1.5.5
|
||||
version: 1.7.0
|
||||
contact:
|
||||
name: DavidsonGomes
|
||||
email: contato@agenciadgcode.com
|
||||
@@ -51,6 +51,7 @@ tags:
|
||||
- name: Send Message Controller
|
||||
- name: Chat Controller
|
||||
- name: Group Controller
|
||||
- name: Label Controller
|
||||
- name: Profile Settings
|
||||
- name: JWT
|
||||
- name: Settings
|
||||
@@ -940,7 +941,72 @@ paths:
|
||||
description: Successful response
|
||||
content:
|
||||
application/json: {}
|
||||
|
||||
/message/sendList/{instanceName}:
|
||||
post:
|
||||
tags:
|
||||
- Send Message Controller
|
||||
summary: Send a list to a specified instance.
|
||||
description: This endpoint allows users to send a list to a chat.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
number:
|
||||
type: string
|
||||
options:
|
||||
type: object
|
||||
properties:
|
||||
delay:
|
||||
type: integer
|
||||
presence:
|
||||
type: string
|
||||
listMessage:
|
||||
type: object
|
||||
properties:
|
||||
title:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
footerText:
|
||||
type: string
|
||||
nullable: true
|
||||
buttonText:
|
||||
type: string
|
||||
sections:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
title:
|
||||
type: string
|
||||
rows:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
title:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
rowId:
|
||||
type: string
|
||||
parameters:
|
||||
- name: instanceName
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The name of the instance to which the poll should be sent.
|
||||
example: "evolution"
|
||||
responses:
|
||||
"200":
|
||||
description: Successful response
|
||||
content:
|
||||
application/json: {}
|
||||
|
||||
/chat/whatsappNumbers/{instanceName}:
|
||||
post:
|
||||
tags:
|
||||
@@ -1791,6 +1857,8 @@ paths:
|
||||
"GROUP_UPDATE",
|
||||
"GROUP_PARTICIPANTS_UPDATE",
|
||||
"CONNECTION_UPDATE",
|
||||
"LABELS_EDIT",
|
||||
"LABELS_ASSOCIATION",
|
||||
"CALL",
|
||||
"NEW_JWT_TOKEN",
|
||||
]
|
||||
@@ -1867,6 +1935,8 @@ paths:
|
||||
"GROUP_UPDATE",
|
||||
"GROUP_PARTICIPANTS_UPDATE",
|
||||
"CONNECTION_UPDATE",
|
||||
"LABELS_EDIT",
|
||||
"LABELS_ASSOCIATION",
|
||||
"CALL",
|
||||
"NEW_JWT_TOKEN",
|
||||
]
|
||||
@@ -1943,6 +2013,8 @@ paths:
|
||||
"GROUP_UPDATE",
|
||||
"GROUP_PARTICIPANTS_UPDATE",
|
||||
"CONNECTION_UPDATE",
|
||||
"LABELS_EDIT",
|
||||
"LABELS_ASSOCIATION",
|
||||
"CALL",
|
||||
"NEW_JWT_TOKEN",
|
||||
]
|
||||
@@ -1981,6 +2053,97 @@ paths:
|
||||
content:
|
||||
application/json: {}
|
||||
|
||||
/label/findLabels/{instanceName}:
|
||||
get:
|
||||
tags:
|
||||
- Label Controller
|
||||
summary: List all labels for an instance.
|
||||
parameters:
|
||||
- name: instanceName
|
||||
in: path
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: "- required"
|
||||
example: "evolution"
|
||||
responses:
|
||||
"200":
|
||||
description: Successful response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
color:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
predefinedId:
|
||||
type: string
|
||||
required:
|
||||
- color
|
||||
- name
|
||||
- id
|
||||
/label/handleLabel/{instanceName}:
|
||||
put:
|
||||
tags:
|
||||
- Label Controller
|
||||
summary: Change the label (add or remove) for an specific chat.
|
||||
parameters:
|
||||
- name: instanceName
|
||||
in: path
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: "- required"
|
||||
example: "evolution"
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
number:
|
||||
type: string
|
||||
labelId:
|
||||
type: string
|
||||
action:
|
||||
type: string
|
||||
enum:
|
||||
- add
|
||||
- remove
|
||||
required:
|
||||
- number
|
||||
- labelId
|
||||
- action
|
||||
example:
|
||||
number: '553499999999'
|
||||
labelId: '1'
|
||||
action: add
|
||||
responses:
|
||||
"200":
|
||||
description: Successful response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
numberJid:
|
||||
type: string
|
||||
labelId:
|
||||
type: string
|
||||
remove:
|
||||
type: boolean
|
||||
add:
|
||||
type: boolean
|
||||
required:
|
||||
- numberJid
|
||||
- labelId
|
||||
|
||||
/settings/set/{instanceName}:
|
||||
post:
|
||||
tags:
|
||||
@@ -2011,6 +2174,9 @@ paths:
|
||||
read_status:
|
||||
type: boolean
|
||||
description: "Indicates whether to mark status messages as read."
|
||||
sync_full_history:
|
||||
type: boolean
|
||||
description: "Indicates whether to request a full history messages sync on connect."
|
||||
parameters:
|
||||
- name: instanceName
|
||||
in: path
|
||||
@@ -2076,6 +2242,15 @@ paths:
|
||||
conversation_pending:
|
||||
type: boolean
|
||||
description: "Indicates whether to mark conversations as pending."
|
||||
import_contacts:
|
||||
type: boolean
|
||||
description: "Indicates whether to import contacts from phone to Chatwoot when connecting."
|
||||
import_messages:
|
||||
type: boolean
|
||||
description: "Indicates whether to import messages from phone to Chatwoot when connecting."
|
||||
days_limit_import_messages:
|
||||
type: number
|
||||
description: "Indicates number of days to limit messages imported to Chatwoot."
|
||||
parameters:
|
||||
- name: instanceName
|
||||
in: path
|
||||
|
||||
22
src/libs/cacheengine.ts
Normal file
22
src/libs/cacheengine.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { CacheConf, ConfigService } from '../config/env.config';
|
||||
import { ICache } from '../whatsapp/abstract/abstract.cache';
|
||||
import { LocalCache } from './localcache';
|
||||
import { RedisCache } from './rediscache';
|
||||
|
||||
export class CacheEngine {
|
||||
private engine: ICache;
|
||||
|
||||
constructor(private readonly configService: ConfigService, module: string) {
|
||||
const cacheConf = configService.get<CacheConf>('CACHE');
|
||||
|
||||
if (cacheConf?.REDIS?.ENABLED && cacheConf?.REDIS?.URI !== '') {
|
||||
this.engine = new RedisCache(configService, module);
|
||||
} else if (cacheConf?.LOCAL?.ENABLED) {
|
||||
this.engine = new LocalCache(configService, module);
|
||||
}
|
||||
}
|
||||
|
||||
public getEngine() {
|
||||
return this.engine;
|
||||
}
|
||||
}
|
||||
48
src/libs/localcache.ts
Normal file
48
src/libs/localcache.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import NodeCache from 'node-cache';
|
||||
|
||||
import { CacheConf, CacheConfLocal, ConfigService } from '../config/env.config';
|
||||
import { ICache } from '../whatsapp/abstract/abstract.cache';
|
||||
|
||||
export class LocalCache implements ICache {
|
||||
private conf: CacheConfLocal;
|
||||
static localCache = new NodeCache();
|
||||
|
||||
constructor(private readonly configService: ConfigService, private readonly module: string) {
|
||||
this.conf = this.configService.get<CacheConf>('CACHE')?.LOCAL;
|
||||
}
|
||||
|
||||
async get(key: string): Promise<any> {
|
||||
return LocalCache.localCache.get(this.buildKey(key));
|
||||
}
|
||||
|
||||
async set(key: string, value: any, ttl?: number) {
|
||||
return LocalCache.localCache.set(this.buildKey(key), value, ttl || this.conf.TTL);
|
||||
}
|
||||
|
||||
async has(key: string) {
|
||||
return LocalCache.localCache.has(this.buildKey(key));
|
||||
}
|
||||
|
||||
async delete(key: string) {
|
||||
return LocalCache.localCache.del(this.buildKey(key));
|
||||
}
|
||||
|
||||
async deleteAll(appendCriteria?: string) {
|
||||
const keys = await this.keys(appendCriteria);
|
||||
if (!keys?.length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return LocalCache.localCache.del(keys);
|
||||
}
|
||||
|
||||
async keys(appendCriteria?: string) {
|
||||
const filter = `${this.buildKey('')}${appendCriteria ? `${appendCriteria}:` : ''}`;
|
||||
|
||||
return LocalCache.localCache.keys().filter((key) => key.substring(0, filter.length) === filter);
|
||||
}
|
||||
|
||||
buildKey(key: string) {
|
||||
return `${this.module}:${key}`;
|
||||
}
|
||||
}
|
||||
49
src/libs/postgres.client.ts
Normal file
49
src/libs/postgres.client.ts
Normal 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();
|
||||
59
src/libs/rediscache.client.ts
Normal file
59
src/libs/rediscache.client.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { createClient, RedisClientType } from 'redis';
|
||||
|
||||
import { CacheConf, CacheConfRedis, configService } from '../config/env.config';
|
||||
import { Logger } from '../config/logger.config';
|
||||
|
||||
class Redis {
|
||||
private logger = new Logger(Redis.name);
|
||||
private client: RedisClientType = null;
|
||||
private conf: CacheConfRedis;
|
||||
private connected = false;
|
||||
|
||||
constructor() {
|
||||
this.conf = configService.get<CacheConf>('CACHE')?.REDIS;
|
||||
}
|
||||
|
||||
getConnection(): RedisClientType {
|
||||
if (this.connected) {
|
||||
return this.client;
|
||||
} else {
|
||||
this.client = createClient({
|
||||
url: this.conf.URI,
|
||||
});
|
||||
|
||||
this.client.on('connect', () => {
|
||||
this.logger.verbose('redis connecting');
|
||||
});
|
||||
|
||||
this.client.on('ready', () => {
|
||||
this.logger.verbose('redis ready');
|
||||
this.connected = true;
|
||||
});
|
||||
|
||||
this.client.on('error', () => {
|
||||
this.logger.error('redis disconnected');
|
||||
this.connected = false;
|
||||
});
|
||||
|
||||
this.client.on('end', () => {
|
||||
this.logger.verbose('redis connection ended');
|
||||
this.connected = false;
|
||||
});
|
||||
|
||||
try {
|
||||
this.logger.verbose('connecting new redis client');
|
||||
this.client.connect();
|
||||
this.connected = true;
|
||||
this.logger.verbose('connected to new redis client');
|
||||
} catch (e) {
|
||||
this.connected = false;
|
||||
this.logger.error('redis connect exception caught: ' + e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.client;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const redisClient = new Redis();
|
||||
83
src/libs/rediscache.ts
Normal file
83
src/libs/rediscache.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { RedisClientType } from 'redis';
|
||||
|
||||
import { CacheConf, CacheConfRedis, ConfigService } from '../config/env.config';
|
||||
import { Logger } from '../config/logger.config';
|
||||
import { ICache } from '../whatsapp/abstract/abstract.cache';
|
||||
import { redisClient } from './rediscache.client';
|
||||
|
||||
export class RedisCache implements ICache {
|
||||
private readonly logger = new Logger(RedisCache.name);
|
||||
private client: RedisClientType;
|
||||
private conf: CacheConfRedis;
|
||||
|
||||
constructor(private readonly configService: ConfigService, private readonly module: string) {
|
||||
this.conf = this.configService.get<CacheConf>('CACHE')?.REDIS;
|
||||
this.client = redisClient.getConnection();
|
||||
}
|
||||
|
||||
async get(key: string): Promise<any> {
|
||||
try {
|
||||
return JSON.parse(await this.client.get(this.buildKey(key)));
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async set(key: string, value: any, ttl?: number) {
|
||||
try {
|
||||
await this.client.setEx(this.buildKey(key), ttl || this.conf?.TTL, JSON.stringify(value));
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async has(key: string) {
|
||||
try {
|
||||
return (await this.client.exists(this.buildKey(key))) > 0;
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async delete(key: string) {
|
||||
try {
|
||||
return await this.client.del(this.buildKey(key));
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteAll(appendCriteria?: string) {
|
||||
try {
|
||||
const keys = await this.keys(appendCriteria);
|
||||
if (!keys?.length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return await this.client.del(keys);
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async keys(appendCriteria?: string) {
|
||||
try {
|
||||
const match = `${this.buildKey('')}${appendCriteria ? `${appendCriteria}:` : ''}*`;
|
||||
const keys = [];
|
||||
for await (const key of this.client.scanIterator({
|
||||
MATCH: match,
|
||||
COUNT: 100,
|
||||
})) {
|
||||
keys.push(key);
|
||||
}
|
||||
|
||||
return [...new Set(keys)];
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
buildKey(key: string) {
|
||||
return `${this.conf?.PREFIX_KEY}:${this.module}:${key}`;
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,8 @@ function bootstrap() {
|
||||
app.use('/store', express.static(join(ROOT_DIR, 'store')));
|
||||
|
||||
app.use('/', router);
|
||||
app.use(swaggerRouter);
|
||||
|
||||
if (!configService.get('SERVER').DISABLE_DOCS) app.use(swaggerRouter);
|
||||
|
||||
app.use(
|
||||
(err: Error, req: Request, res: Response, next: NextFunction) => {
|
||||
|
||||
472
src/utils/chatwoot-import-helper.ts
Normal file
472
src/utils/chatwoot-import-helper.ts
Normal file
@@ -0,0 +1,472 @@
|
||||
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 { 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';
|
||||
|
||||
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();
|
||||
32
src/utils/i18n.ts
Normal file
32
src/utils/i18n.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import fs from 'fs';
|
||||
import i18next from 'i18next';
|
||||
import path from 'path';
|
||||
|
||||
import { ConfigService, Language } from '../config/env.config';
|
||||
|
||||
const languages = ['en', 'pt-BR'];
|
||||
const translationsPath = path.join(__dirname, 'translations');
|
||||
const configService: ConfigService = new ConfigService();
|
||||
|
||||
const resources: any = {};
|
||||
|
||||
languages.forEach((language) => {
|
||||
const languagePath = path.join(translationsPath, `${language}.json`);
|
||||
if (fs.existsSync(languagePath)) {
|
||||
resources[language] = {
|
||||
translation: require(languagePath),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
i18next.init({
|
||||
resources,
|
||||
fallbackLng: 'en',
|
||||
lng: configService.get<Language>('LANGUAGE'),
|
||||
debug: false,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
});
|
||||
export default i18next;
|
||||
17
src/utils/makeProxyAgent.ts
Normal file
17
src/utils/makeProxyAgent.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||
|
||||
import { wa } from '../whatsapp/types/wa.types';
|
||||
|
||||
export function makeProxyAgent(proxy: wa.Proxy | string) {
|
||||
if (typeof proxy === 'string') {
|
||||
return new HttpsProxyAgent(proxy);
|
||||
}
|
||||
|
||||
const { host, password, port, protocol, username } = proxy;
|
||||
let proxyUrl = `${protocol}://${host}:${port}`;
|
||||
|
||||
if (username && password) {
|
||||
proxyUrl = `${protocol}://${username}:${password}@${host}:${port}`;
|
||||
}
|
||||
return new HttpsProxyAgent(proxyUrl);
|
||||
}
|
||||
26
src/utils/translations/en.json
Normal file
26
src/utils/translations/en.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"qrgeneratedsuccesfully": "QRCode successfully generated!",
|
||||
"scanqr": "Scan this QR code within the next 40 seconds.",
|
||||
"qrlimitreached": "QRCode generation limit reached, to generate a new QRCode, send the 'init' message again.",
|
||||
"numbernotinwhatsapp": "The message was not sent as the contact is not a valid Whatsapp number.",
|
||||
"cw.inbox.connected": "🚀 Connection successfully established!",
|
||||
"cw.inbox.disconnect": "🚨 Disconnecting WhatsApp from inbox *{{inboxName}}*.",
|
||||
"cw.inbox.alreadyConnected": "🚨 {{inboxName}} instance is connected.",
|
||||
"cw.inbox.clearCache": "✅ {{inboxName}} instance cache cleared.",
|
||||
"cw.inbox.notFound": "⚠️ {{inboxName}} instance not found.",
|
||||
"cw.inbox.status": "⚠️ {{inboxName}} instance status: *{{state}}*.",
|
||||
"cw.import.startImport": "💬 Starting to import messages. Please wait...",
|
||||
"cw.import.importingMessages": "💬 Importing messages. More one moment...",
|
||||
"cw.import.messagesImported": "💬 {{totalMessagesImported}} messages imported. Refresh page to see the new messages.",
|
||||
"cw.import.messagesException": "💬 Something went wrong in importing messages.",
|
||||
"cw.locationMessage.location": "Location",
|
||||
"cw.locationMessage.latitude": "Latitude",
|
||||
"cw.locationMessage.longitude": "Longitude",
|
||||
"cw.locationMessage.locationName": "Name",
|
||||
"cw.locationMessage.locationAddress": "Address",
|
||||
"cw.locationMessage.locationUrl": "URL",
|
||||
"cw.contactMessage.contact": "Contact",
|
||||
"cw.contactMessage.name": "Name",
|
||||
"cw.contactMessage.number": "Number",
|
||||
"cw.message.edited": "Edited Message"
|
||||
}
|
||||
26
src/utils/translations/pt-BR.json
Normal file
26
src/utils/translations/pt-BR.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"qrgeneratedsuccesfully": "QRCode gerado com sucesso!",
|
||||
"scanqr": "Escaneie o QRCode com o WhatsApp nos próximos 40 segundos.",
|
||||
"qrlimitreached": "Limite de geração de QRCode atingido! Para gerar um novo QRCode, envie o texto 'init' nesta conversa.",
|
||||
"numbernotinwhatsapp": "A mensagem não foi enviada, pois o contato não é um número válido do WhatsApp.",
|
||||
"cw.inbox.connected": "🚀 Conectado com sucesso!",
|
||||
"cw.inbox.disconnect": "🚨 Instância *{{inboxName}}* desconectada do WhatsApp.",
|
||||
"cw.inbox.alreadyConnected": "🚨 Instância *{{inboxName}}* já está conectada.",
|
||||
"cw.inbox.clearCache": "✅ Instância *{{inboxName}}* cache removido.",
|
||||
"cw.inbox.notFound": "⚠️ Instância *{{inboxName}}* não encontrada.",
|
||||
"cw.inbox.status": "⚠️ Status da instância {{inboxName}}: *{{state}}*.",
|
||||
"cw.import.startImport": "💬 Iniciando importação de mensagens. Por favor, aguarde...",
|
||||
"cw.import.importingMessages": "💬 Importando mensagens. Mais um momento...",
|
||||
"cw.import.messagesImported": "💬 {{totalMessagesImported}} mensagens importadas. Atualize a página para ver as novas mensagens.",
|
||||
"cw.import.messagesException": "💬 Não foi possível importar as mensagens.",
|
||||
"cw.locationMessage.location": "Localização",
|
||||
"cw.locationMessage.latitude": "Latitude",
|
||||
"cw.locationMessage.longitude": "Longitude",
|
||||
"cw.locationMessage.locationName": "Nome",
|
||||
"cw.locationMessage.locationAddress": "Endereço",
|
||||
"cw.locationMessage.locationUrl": "URL",
|
||||
"cw.contactMessage.contact": "Contato",
|
||||
"cw.contactMessage.name": "Nome",
|
||||
"cw.contactMessage.number": "Número",
|
||||
"cw.message.edited": "Mensagem editada"
|
||||
}
|
||||
@@ -53,6 +53,8 @@ export const instanceNameSchema: JSONSchema7 = {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@@ -159,6 +161,18 @@ export const presenceSchema: JSONSchema7 = {
|
||||
required: ['options', 'number'],
|
||||
};
|
||||
|
||||
export const presenceOnlySchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
presence: {
|
||||
type: 'string',
|
||||
enum: ['unavailable', 'available', 'composing', 'recording', 'paused'],
|
||||
},
|
||||
},
|
||||
required: ['presence'],
|
||||
};
|
||||
|
||||
export const pollMessageSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
@@ -275,6 +289,26 @@ export const audioMessageSchema: JSONSchema7 = {
|
||||
required: ['audioMessage', 'number'],
|
||||
};
|
||||
|
||||
export const templateMessageSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
number: { ...numberDefinition },
|
||||
options: { ...optionsSchema },
|
||||
templateMessage: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
language: { type: 'string' },
|
||||
components: { type: 'array' },
|
||||
},
|
||||
required: ['name', 'language'],
|
||||
...isNotEmpty('name', 'language'),
|
||||
},
|
||||
},
|
||||
required: ['templateMessage', 'number'],
|
||||
};
|
||||
|
||||
export const buttonMessageSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
@@ -517,6 +551,17 @@ export const privacySettingsSchema: JSONSchema7 = {
|
||||
required: ['privacySettings'],
|
||||
};
|
||||
|
||||
export const blockUserSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
number: { type: 'string' },
|
||||
status: { type: 'string', enum: ['block', 'unblock'] },
|
||||
},
|
||||
required: ['number', 'status'],
|
||||
...isNotEmpty('number', 'status'),
|
||||
};
|
||||
|
||||
export const archiveChatSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
@@ -592,6 +637,26 @@ export const profileStatusSchema: JSONSchema7 = {
|
||||
...isNotEmpty('status'),
|
||||
};
|
||||
|
||||
export const updateMessageSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
number: { type: 'string' },
|
||||
text: { type: 'string' },
|
||||
key: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
remoteJid: { type: 'string' },
|
||||
fromMe: { type: 'boolean', enum: [true, false] },
|
||||
},
|
||||
required: ['id', 'fromMe', 'remoteJid'],
|
||||
...isNotEmpty('id', 'remoteJid'),
|
||||
},
|
||||
},
|
||||
...isNotEmpty('number', 'text', 'key'),
|
||||
};
|
||||
|
||||
export const profilePictureSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
@@ -751,6 +816,16 @@ export const groupInviteSchema: JSONSchema7 = {
|
||||
...isNotEmpty('inviteCode'),
|
||||
};
|
||||
|
||||
export const AcceptGroupInviteSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
inviteCode: { type: 'string', pattern: '^[a-zA-Z0-9]{22}$' },
|
||||
},
|
||||
required: ['inviteCode'],
|
||||
...isNotEmpty('inviteCode'),
|
||||
};
|
||||
|
||||
export const updateParticipantsSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
@@ -867,6 +942,8 @@ export const webhookSchema: JSONSchema7 = {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@@ -889,9 +966,13 @@ export const chatwootSchema: JSONSchema7 = {
|
||||
token: { type: 'string' },
|
||||
url: { type: 'string' },
|
||||
sign_msg: { type: 'boolean', enum: [true, false] },
|
||||
sign_delimiter: { type: ['string', 'null'] },
|
||||
reopen_conversation: { type: 'boolean', enum: [true, false] },
|
||||
conversation_pending: { type: 'boolean', enum: [true, false] },
|
||||
auto_create: { type: 'boolean', enum: [true, false] },
|
||||
import_contacts: { type: 'boolean', enum: [true, false] },
|
||||
import_messages: { type: 'boolean', enum: [true, false] },
|
||||
days_limit_import_messages: { type: 'number' },
|
||||
},
|
||||
required: ['enabled', 'account_id', 'token', 'url', 'sign_msg', 'reopen_conversation', 'conversation_pending'],
|
||||
...isNotEmpty('account_id', 'token', 'url', 'sign_msg', 'reopen_conversation', 'conversation_pending'),
|
||||
@@ -907,9 +988,10 @@ export const settingsSchema: JSONSchema7 = {
|
||||
always_online: { type: 'boolean', enum: [true, false] },
|
||||
read_messages: { type: 'boolean', enum: [true, false] },
|
||||
read_status: { type: 'boolean', enum: [true, false] },
|
||||
sync_full_history: { type: 'boolean', enum: [true, false] },
|
||||
},
|
||||
required: ['reject_call', 'groups_ignore', 'always_online', 'read_messages', 'read_status'],
|
||||
...isNotEmpty('reject_call', 'groups_ignore', 'always_online', 'read_messages', 'read_status'),
|
||||
required: ['reject_call', 'groups_ignore', 'always_online', 'read_messages', 'read_status', 'sync_full_history'],
|
||||
...isNotEmpty('reject_call', 'groups_ignore', 'always_online', 'read_messages', 'read_status', 'sync_full_history'),
|
||||
};
|
||||
|
||||
export const websocketSchema: JSONSchema7 = {
|
||||
@@ -942,6 +1024,8 @@ export const websocketSchema: JSONSchema7 = {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@@ -985,6 +1069,8 @@ export const rabbitmqSchema: JSONSchema7 = {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@@ -1028,6 +1114,8 @@ export const sqsSchema: JSONSchema7 = {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@@ -1085,7 +1173,18 @@ export const proxySchema: JSONSchema7 = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
enabled: { type: 'boolean', enum: [true, false] },
|
||||
proxy: { type: 'string' },
|
||||
proxy: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
host: { type: 'string' },
|
||||
port: { type: 'string' },
|
||||
protocol: { type: 'string' },
|
||||
username: { type: 'string' },
|
||||
password: { type: 'string' },
|
||||
},
|
||||
required: ['host', 'port', 'protocol'],
|
||||
...isNotEmpty('host', 'port', 'protocol'),
|
||||
},
|
||||
},
|
||||
required: ['enabled', 'proxy'],
|
||||
...isNotEmpty('enabled', 'proxy'),
|
||||
@@ -1104,3 +1203,14 @@ export const chamaaiSchema: JSONSchema7 = {
|
||||
required: ['enabled', 'url', 'token', 'waNumber', 'answerByAudio'],
|
||||
...isNotEmpty('enabled', 'url', 'token', 'waNumber', 'answerByAudio'),
|
||||
};
|
||||
|
||||
export const handleLabelSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
number: { ...numberDefinition },
|
||||
labelId: { type: 'string' },
|
||||
action: { type: 'string', enum: ['add', 'remove'] },
|
||||
},
|
||||
required: ['number', 'labelId', 'action'],
|
||||
};
|
||||
|
||||
13
src/whatsapp/abstract/abstract.cache.ts
Normal file
13
src/whatsapp/abstract/abstract.cache.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export interface ICache {
|
||||
get(key: string): Promise<any>;
|
||||
|
||||
set(key: string, value: any, ttl?: number): void;
|
||||
|
||||
has(key: string): Promise<boolean>;
|
||||
|
||||
keys(appendCriteria?: string): Promise<string[]>;
|
||||
|
||||
delete(key: string | string[]): Promise<number>;
|
||||
|
||||
deleteAll(appendCriteria?: string): Promise<number>;
|
||||
}
|
||||
@@ -21,7 +21,6 @@ const logger = new Logger('Validate');
|
||||
export abstract class RouterBroker {
|
||||
constructor() {}
|
||||
public routerPath(path: string, param = true) {
|
||||
// const route = param ? '/:instanceName/' + path : '/' + path;
|
||||
let route = '/' + path;
|
||||
param ? (route += '/:instanceName') : null;
|
||||
|
||||
@@ -56,10 +55,6 @@ export abstract class RouterBroker {
|
||||
message = stack.replace('instance.', '');
|
||||
}
|
||||
return message;
|
||||
// return {
|
||||
// property: property.replace('instance.', ''),
|
||||
// message,
|
||||
// };
|
||||
});
|
||||
logger.error(message);
|
||||
throw new BadRequestException(message);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import {
|
||||
ArchiveChatDto,
|
||||
BlockUserDto,
|
||||
DeleteMessage,
|
||||
getBase64FromMediaMessageDto,
|
||||
NumberDto,
|
||||
@@ -10,6 +11,7 @@ import {
|
||||
ProfileStatusDto,
|
||||
ReadMessageDto,
|
||||
SendPresenceDto,
|
||||
UpdateMessageDto,
|
||||
WhatsAppNumberDto,
|
||||
} from '../dto/chat.dto';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
@@ -117,4 +119,14 @@ export class ChatController {
|
||||
logger.verbose('requested removeProfilePicture from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].removeProfilePicture();
|
||||
}
|
||||
|
||||
public async updateMessage({ instanceName }: InstanceDto, data: UpdateMessageDto) {
|
||||
logger.verbose('requested updateMessage from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].updateMessage(data);
|
||||
}
|
||||
|
||||
public async blockUser({ instanceName }: InstanceDto, data: BlockUserDto) {
|
||||
logger.verbose('requested blockUser from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].blockUser(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@ 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 { 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';
|
||||
|
||||
@@ -37,6 +39,7 @@ export class ChatwootController {
|
||||
if (data.sign_msg !== true && data.sign_msg !== false) {
|
||||
throw new BadRequestException('sign_msg is required');
|
||||
}
|
||||
if (data.sign_msg === false) data.sign_delimiter = null;
|
||||
}
|
||||
|
||||
if (!data.enabled) {
|
||||
@@ -45,8 +48,12 @@ export class ChatwootController {
|
||||
data.token = '';
|
||||
data.url = '';
|
||||
data.sign_msg = false;
|
||||
data.sign_delimiter = null;
|
||||
data.reopen_conversation = false;
|
||||
data.conversation_pending = false;
|
||||
data.import_contacts = false;
|
||||
data.import_messages = false;
|
||||
data.days_limit_import_messages = 0;
|
||||
data.auto_create = false;
|
||||
}
|
||||
|
||||
@@ -92,7 +99,9 @@ export class ChatwootController {
|
||||
|
||||
public async receiveWebhook(instance: InstanceDto, data: any) {
|
||||
logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance');
|
||||
const chatwootService = new ChatwootService(waMonitor, this.configService, 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);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import {
|
||||
AcceptGroupInvite,
|
||||
CreateGroupDto,
|
||||
GetParticipant,
|
||||
GroupDescriptionDto,
|
||||
@@ -65,6 +66,11 @@ export class GroupController {
|
||||
return await this.waMonitor.waInstances[instance.instanceName].sendInvite(data);
|
||||
}
|
||||
|
||||
public async acceptInviteCode(instance: InstanceDto, inviteCode: AcceptGroupInvite) {
|
||||
logger.verbose('requested acceptInviteCode from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].acceptInviteCode(inviteCode);
|
||||
}
|
||||
|
||||
public async revokeInviteCode(instance: InstanceDto, groupJid: GroupJid) {
|
||||
logger.verbose('requested revokeInviteCode from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].revokeInviteCode(groupJid);
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
import { delay } from '@whiskeysockets/baileys';
|
||||
import { isURL } from 'class-validator';
|
||||
import EventEmitter2 from 'eventemitter2';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { ConfigService, HttpServer } from '../../config/env.config';
|
||||
import { 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 } from '../dto/instance.dto';
|
||||
import { InstanceDto, SetPresenceDto } from '../dto/instance.dto';
|
||||
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 { IntegrationService } from '../services/integration.service';
|
||||
import { WAMonitoringService } from '../services/monitor.service';
|
||||
import { ProxyService } from '../services/proxy.service';
|
||||
import { RabbitmqService } from '../services/rabbitmq.service';
|
||||
import { SettingsService } from '../services/settings.service';
|
||||
import { SqsService } from '../services/sqs.service';
|
||||
import { TypebotService } from '../services/typebot.service';
|
||||
import { WebhookService } from '../services/webhook.service';
|
||||
import { WebsocketService } from '../services/websocket.service';
|
||||
import { WAStartupService } from '../services/whatsapp.service';
|
||||
import { wa } from '../types/wa.types';
|
||||
import { BaileysStartupService } from '../services/whatsapp.baileys.service';
|
||||
import { BusinessStartupService } from '../services/whatsapp.business.service';
|
||||
import { Events, Integration, wa } from '../types/wa.types';
|
||||
|
||||
export class InstanceController {
|
||||
constructor(
|
||||
@@ -33,10 +36,11 @@ export class InstanceController {
|
||||
private readonly settingsService: SettingsService,
|
||||
private readonly websocketService: WebsocketService,
|
||||
private readonly rabbitmqService: RabbitmqService,
|
||||
private readonly proxyService: ProxyService,
|
||||
private readonly sqsService: SqsService,
|
||||
private readonly typebotService: TypebotService,
|
||||
private readonly integrationService: IntegrationService,
|
||||
private readonly cache: RedisCache,
|
||||
private readonly chatwootCache: CacheService,
|
||||
) {}
|
||||
|
||||
private readonly logger = new Logger(InstanceController.name);
|
||||
@@ -49,6 +53,7 @@ export class InstanceController {
|
||||
events,
|
||||
qrcode,
|
||||
number,
|
||||
integration,
|
||||
token,
|
||||
chatwoot_account_id,
|
||||
chatwoot_token,
|
||||
@@ -56,12 +61,16 @@ export class InstanceController {
|
||||
chatwoot_sign_msg,
|
||||
chatwoot_reopen_conversation,
|
||||
chatwoot_conversation_pending,
|
||||
chatwoot_import_contacts,
|
||||
chatwoot_import_messages,
|
||||
chatwoot_days_limit_import_messages,
|
||||
reject_call,
|
||||
msg_call,
|
||||
groups_ignore,
|
||||
always_online,
|
||||
read_messages,
|
||||
read_status,
|
||||
sync_full_history,
|
||||
websocket_enabled,
|
||||
websocket_events,
|
||||
rabbitmq_enabled,
|
||||
@@ -75,7 +84,6 @@ export class InstanceController {
|
||||
typebot_delay_message,
|
||||
typebot_unknown_message,
|
||||
typebot_listening_from_me,
|
||||
proxy,
|
||||
}: InstanceDto) {
|
||||
try {
|
||||
this.logger.verbose('requested createInstance from ' + instanceName + ' instance');
|
||||
@@ -83,10 +91,41 @@ export class InstanceController {
|
||||
this.logger.verbose('checking duplicate token');
|
||||
await this.authService.checkDuplicateToken(token);
|
||||
|
||||
if (!token && integration === Integration.WHATSAPP_BUSINESS) {
|
||||
throw new BadRequestException('token is required');
|
||||
}
|
||||
|
||||
this.logger.verbose('creating instance');
|
||||
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,
|
||||
);
|
||||
} else {
|
||||
instance = new BaileysStartupService(
|
||||
this.configService,
|
||||
this.eventEmitter,
|
||||
this.repository,
|
||||
this.cache,
|
||||
this.chatwootCache,
|
||||
);
|
||||
}
|
||||
|
||||
await this.waMonitor.saveInstance({ integration, instanceName, token, number });
|
||||
|
||||
instance.instanceName = instanceName;
|
||||
|
||||
const instanceId = v4();
|
||||
|
||||
instance.sendDataWebhook(Events.INSTANCE_CREATE, {
|
||||
instanceName,
|
||||
instanceId: instanceId,
|
||||
});
|
||||
|
||||
this.logger.verbose('instance: ' + instance.instanceName + ' created');
|
||||
|
||||
this.waMonitor.waInstances[instance.instanceName] = instance;
|
||||
@@ -96,6 +135,7 @@ export class InstanceController {
|
||||
const hash = await this.authService.generateHash(
|
||||
{
|
||||
instanceName: instance.instanceName,
|
||||
instanceId: instanceId,
|
||||
},
|
||||
token,
|
||||
);
|
||||
@@ -133,6 +173,8 @@ export class InstanceController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@@ -183,6 +225,8 @@ export class InstanceController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@@ -230,6 +274,8 @@ export class InstanceController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@@ -250,22 +296,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[];
|
||||
|
||||
if (sqs_enabled) {
|
||||
@@ -293,6 +323,8 @@ export class InstanceController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@@ -344,12 +376,30 @@ export class InstanceController {
|
||||
always_online: always_online || false,
|
||||
read_messages: read_messages || false,
|
||||
read_status: read_status || false,
|
||||
sync_full_history: sync_full_history ?? false,
|
||||
};
|
||||
|
||||
this.logger.verbose('settings: ' + JSON.stringify(settings));
|
||||
|
||||
this.settingsService.create(instance, settings);
|
||||
|
||||
let webhook_wa_business = null,
|
||||
access_token_wa_business = '';
|
||||
|
||||
if (integration === Integration.WHATSAPP_BUSINESS) {
|
||||
if (!number) {
|
||||
throw new BadRequestException('number is required');
|
||||
}
|
||||
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
|
||||
webhook_wa_business = `${urlServer}/webhook/whatsapp/${encodeURIComponent(instance.instanceName)}`;
|
||||
access_token_wa_business = this.configService.get<WaBusiness>('WA_BUSINESS').TOKEN_WEBHOOK;
|
||||
}
|
||||
|
||||
this.integrationService.create(instance, {
|
||||
integration,
|
||||
number,
|
||||
token,
|
||||
});
|
||||
if (!chatwoot_account_id || !chatwoot_token || !chatwoot_url) {
|
||||
let getQrcode: wa.QrCode;
|
||||
|
||||
@@ -363,6 +413,10 @@ export class InstanceController {
|
||||
const result = {
|
||||
instance: {
|
||||
instanceName: instance.instanceName,
|
||||
instanceId: instanceId,
|
||||
integration: integration,
|
||||
webhook_wa_business,
|
||||
access_token_wa_business,
|
||||
status: 'created',
|
||||
},
|
||||
hash,
|
||||
@@ -396,7 +450,6 @@ export class InstanceController {
|
||||
},
|
||||
settings,
|
||||
qrcode: getQrcode,
|
||||
proxy,
|
||||
};
|
||||
|
||||
this.logger.verbose('instance created');
|
||||
@@ -446,6 +499,9 @@ export class InstanceController {
|
||||
number,
|
||||
reopen_conversation: chatwoot_reopen_conversation || false,
|
||||
conversation_pending: chatwoot_conversation_pending || false,
|
||||
import_contacts: chatwoot_import_contacts ?? true,
|
||||
import_messages: chatwoot_import_messages ?? true,
|
||||
days_limit_import_messages: chatwoot_days_limit_import_messages ?? 60,
|
||||
auto_create: true,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -455,6 +511,10 @@ export class InstanceController {
|
||||
return {
|
||||
instance: {
|
||||
instanceName: instance.instanceName,
|
||||
instanceId: instanceId,
|
||||
integration: integration,
|
||||
webhook_wa_business,
|
||||
access_token_wa_business,
|
||||
status: 'created',
|
||||
},
|
||||
hash,
|
||||
@@ -495,11 +555,13 @@ export class InstanceController {
|
||||
sign_msg: chatwoot_sign_msg || false,
|
||||
reopen_conversation: chatwoot_reopen_conversation || false,
|
||||
conversation_pending: chatwoot_conversation_pending || false,
|
||||
import_contacts: chatwoot_import_contacts ?? true,
|
||||
import_messages: chatwoot_import_messages ?? true,
|
||||
days_limit_import_messages: chatwoot_days_limit_import_messages || 60,
|
||||
number,
|
||||
name_inbox: instance.instanceName,
|
||||
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
|
||||
},
|
||||
proxy,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error(error.message[0]);
|
||||
@@ -558,6 +620,7 @@ export class InstanceController {
|
||||
switch (state) {
|
||||
case 'open':
|
||||
this.logger.verbose('logging out instance: ' + instanceName);
|
||||
instance.clearCacheChatwoot();
|
||||
await instance.reloadConnection();
|
||||
await delay(2000);
|
||||
|
||||
@@ -580,17 +643,24 @@ export class InstanceController {
|
||||
};
|
||||
}
|
||||
|
||||
public async fetchInstances({ instanceName }: InstanceDto) {
|
||||
public async fetchInstances({ instanceName, instanceId, number }: InstanceDto) {
|
||||
if (instanceName) {
|
||||
this.logger.verbose('requested fetchInstances from ' + instanceName + ' instance');
|
||||
this.logger.verbose('instanceName: ' + instanceName);
|
||||
return this.waMonitor.instanceInfo(instanceName);
|
||||
} else if (instanceId || number) {
|
||||
return this.waMonitor.instanceInfoById(instanceId, number);
|
||||
}
|
||||
|
||||
this.logger.verbose('requested fetchInstances (all instances)');
|
||||
return this.waMonitor.instanceInfo();
|
||||
}
|
||||
|
||||
public async setPresence({ instanceName }: InstanceDto, data: SetPresenceDto) {
|
||||
this.logger.verbose('requested sendPresence from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].setPresence(data);
|
||||
}
|
||||
|
||||
public async logout({ instanceName }: InstanceDto) {
|
||||
this.logger.verbose('requested logout from ' + instanceName + ' instance');
|
||||
const { instance } = await this.connectionState({ instanceName });
|
||||
@@ -600,11 +670,7 @@ export class InstanceController {
|
||||
}
|
||||
|
||||
try {
|
||||
this.logger.verbose('logging out instance: ' + instanceName);
|
||||
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();
|
||||
this.waMonitor.waInstances[instanceName]?.logoutInstance();
|
||||
|
||||
return { status: 'SUCCESS', error: false, response: { message: 'Instance logged out' } };
|
||||
} catch (error) {
|
||||
@@ -621,6 +687,7 @@ export class InstanceController {
|
||||
}
|
||||
try {
|
||||
this.waMonitor.waInstances[instanceName]?.removeRabbitmqQueues();
|
||||
this.waMonitor.waInstances[instanceName]?.clearCacheChatwoot();
|
||||
|
||||
if (instance.state === 'connecting') {
|
||||
this.logger.verbose('logging out instance: ' + instanceName);
|
||||
@@ -630,6 +697,15 @@ export class InstanceController {
|
||||
|
||||
this.logger.verbose('deleting instance: ' + instanceName);
|
||||
|
||||
try {
|
||||
this.waMonitor.waInstances[instanceName].sendDataWebhook(Events.INSTANCE_DELETE, {
|
||||
instanceName,
|
||||
instanceId: (await this.repository.auth.find(instanceName))?.instanceId,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
|
||||
delete this.waMonitor.waInstances[instanceName];
|
||||
this.eventEmitter.emit('remove.instance', instanceName, 'inner');
|
||||
return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } };
|
||||
|
||||
20
src/whatsapp/controllers/label.controller.ts
Normal file
20
src/whatsapp/controllers/label.controller.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,36 @@
|
||||
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) {}
|
||||
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 = '';
|
||||
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);
|
||||
@@ -21,6 +38,35 @@ export class ProxyController {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ export class RabbitmqController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
SendReactionDto,
|
||||
SendStatusDto,
|
||||
SendStickerDto,
|
||||
SendTemplateDto,
|
||||
SendTextDto,
|
||||
} from '../dto/sendMessage.dto';
|
||||
import { WAMonitoringService } from '../services/monitor.service';
|
||||
@@ -28,6 +29,11 @@ export class SendMessageController {
|
||||
return await this.waMonitor.waInstances[instanceName].textMessage(data);
|
||||
}
|
||||
|
||||
public async sendTemplate({ instanceName }: InstanceDto, data: SendTemplateDto) {
|
||||
logger.verbose('requested sendList from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].templateMessage(data);
|
||||
}
|
||||
|
||||
public async sendMedia({ instanceName }: InstanceDto, data: SendMediaDto) {
|
||||
logger.verbose('requested sendMedia from ' + instanceName + ' instance');
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
// import { isURL } from 'class-validator';
|
||||
|
||||
import { Logger } from '../../config/logger.config';
|
||||
// import { BadRequestException } from '../../exceptions';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { SettingsDto } from '../dto/settings.dto';
|
||||
import { SettingsService } from '../services/settings.service';
|
||||
|
||||
@@ -38,6 +38,8 @@ export class SqsController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
|
||||
@@ -4,12 +4,13 @@ import { Logger } from '../../config/logger.config';
|
||||
import { BadRequestException } from '../../exceptions';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { WebhookDto } from '../dto/webhook.dto';
|
||||
import { WAMonitoringService } from '../services/monitor.service';
|
||||
import { WebhookService } from '../services/webhook.service';
|
||||
|
||||
const logger = new Logger('WebhookController');
|
||||
|
||||
export class WebhookController {
|
||||
constructor(private readonly webhookService: WebhookService) {}
|
||||
constructor(private readonly webhookService: WebhookService, private readonly waMonitor: WAMonitoringService) {}
|
||||
|
||||
public async createWebhook(instance: InstanceDto, data: WebhookDto) {
|
||||
logger.verbose('requested createWebhook from ' + instance.instanceName + ' instance');
|
||||
@@ -46,6 +47,8 @@ export class WebhookController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@@ -61,4 +64,9 @@ export class WebhookController {
|
||||
logger.verbose('requested findWebhook from ' + instance.instanceName + ' instance');
|
||||
return this.webhookService.find(instance);
|
||||
}
|
||||
|
||||
public async receiveWebhook(instance: InstanceDto, data: any) {
|
||||
logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].connectToWhatsapp(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ export class WebsocketController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { proto, WAPresence, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '@whiskeysockets/baileys';
|
||||
|
||||
export class OnWhatsAppDto {
|
||||
constructor(public readonly jid: string, public readonly exists: boolean, public readonly name?: string) {}
|
||||
constructor(
|
||||
public readonly jid: string,
|
||||
public readonly exists: boolean,
|
||||
public readonly number: string,
|
||||
public readonly name?: string,
|
||||
) {}
|
||||
}
|
||||
|
||||
export class getBase64FromMediaMessageDto {
|
||||
@@ -26,8 +31,12 @@ export class NumberBusiness {
|
||||
message?: string;
|
||||
description?: string;
|
||||
email?: string;
|
||||
websites?: string[];
|
||||
website?: string[];
|
||||
address?: string;
|
||||
about?: string;
|
||||
vertical?: string;
|
||||
profilehandle?: string;
|
||||
}
|
||||
|
||||
export class ProfileNameDto {
|
||||
@@ -100,3 +109,14 @@ export class SendPresenceDto extends Metadata {
|
||||
delay: number;
|
||||
};
|
||||
}
|
||||
|
||||
export class UpdateMessageDto extends Metadata {
|
||||
number: string;
|
||||
key: proto.IMessageKey;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export class BlockUserDto {
|
||||
number: string;
|
||||
status: 'block' | 'unblock';
|
||||
}
|
||||
|
||||
@@ -5,8 +5,12 @@ export class ChatwootDto {
|
||||
url?: string;
|
||||
name_inbox?: string;
|
||||
sign_msg?: boolean;
|
||||
sign_delimiter?: string;
|
||||
number?: string;
|
||||
reopen_conversation?: boolean;
|
||||
conversation_pending?: boolean;
|
||||
import_contacts?: boolean;
|
||||
import_messages?: boolean;
|
||||
days_limit_import_messages?: number;
|
||||
auto_create?: boolean;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,10 @@ export class GroupInvite {
|
||||
inviteCode: string;
|
||||
}
|
||||
|
||||
export class AcceptGroupInvite {
|
||||
inviteCode: string;
|
||||
}
|
||||
|
||||
export class GroupSendInvite {
|
||||
groupJid: string;
|
||||
description: string;
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { WAPresence } from "@whiskeysockets/baileys";
|
||||
|
||||
export class InstanceDto {
|
||||
instanceName: string;
|
||||
instanceId?: string;
|
||||
qrcode?: boolean;
|
||||
number?: string;
|
||||
integration?: string;
|
||||
token?: string;
|
||||
webhook?: string;
|
||||
webhook_by_events?: boolean;
|
||||
@@ -13,12 +17,16 @@ export class InstanceDto {
|
||||
always_online?: boolean;
|
||||
read_messages?: boolean;
|
||||
read_status?: boolean;
|
||||
sync_full_history?: boolean;
|
||||
chatwoot_account_id?: string;
|
||||
chatwoot_token?: string;
|
||||
chatwoot_url?: string;
|
||||
chatwoot_sign_msg?: boolean;
|
||||
chatwoot_reopen_conversation?: boolean;
|
||||
chatwoot_conversation_pending?: boolean;
|
||||
chatwoot_import_contacts?: boolean;
|
||||
chatwoot_import_messages?: boolean;
|
||||
chatwoot_days_limit_import_messages?: number;
|
||||
websocket_enabled?: boolean;
|
||||
websocket_events?: string[];
|
||||
rabbitmq_enabled?: boolean;
|
||||
@@ -34,3 +42,7 @@ export class InstanceDto {
|
||||
typebot_listening_from_me?: boolean;
|
||||
proxy?: string;
|
||||
}
|
||||
|
||||
export class SetPresenceDto {
|
||||
presence: WAPresence;
|
||||
}
|
||||
|
||||
5
src/whatsapp/dto/integration.dto.ts
Normal file
5
src/whatsapp/dto/integration.dto.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export class IntegrationDto {
|
||||
integration: string;
|
||||
number: string;
|
||||
token: string;
|
||||
}
|
||||
12
src/whatsapp/dto/label.dto.ts
Normal file
12
src/whatsapp/dto/label.dto.ts
Normal 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';
|
||||
}
|
||||
@@ -1,4 +1,12 @@
|
||||
class Proxy {
|
||||
host: string;
|
||||
port: string;
|
||||
protocol: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export class ProxyDto {
|
||||
enabled: boolean;
|
||||
proxy: string;
|
||||
proxy: Proxy;
|
||||
}
|
||||
|
||||
@@ -142,6 +142,16 @@ export class ContactMessage {
|
||||
email?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export class TemplateMessage {
|
||||
name: string;
|
||||
language: string;
|
||||
components: any;
|
||||
}
|
||||
|
||||
export class SendTemplateDto extends Metadata {
|
||||
templateMessage: TemplateMessage;
|
||||
}
|
||||
export class SendContactDto extends Metadata {
|
||||
contactMessage: ContactMessage[];
|
||||
}
|
||||
|
||||
@@ -5,4 +5,5 @@ export class SettingsDto {
|
||||
always_online?: boolean;
|
||||
read_messages?: boolean;
|
||||
read_status?: boolean;
|
||||
sync_full_history?: boolean;
|
||||
}
|
||||
|
||||
@@ -6,12 +6,14 @@ export class AuthRaw {
|
||||
_id?: string;
|
||||
jwt?: string;
|
||||
apikey?: string;
|
||||
instanceId?: string;
|
||||
}
|
||||
|
||||
const authSchema = new Schema<AuthRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
jwt: { type: String, minlength: 1 },
|
||||
apikey: { type: String, minlength: 1 },
|
||||
instanceId: { type: String, minlength: 1 },
|
||||
});
|
||||
|
||||
export const AuthModel = dbserver?.model(AuthRaw.name, authSchema, 'authentication');
|
||||
|
||||
@@ -7,12 +7,19 @@ export class ChatRaw {
|
||||
id?: string;
|
||||
owner: string;
|
||||
lastMsgTimestamp?: number;
|
||||
labels?: string[];
|
||||
}
|
||||
|
||||
type ChatRawBoolean<T> = {
|
||||
[P in keyof T]?: 0 | 1;
|
||||
};
|
||||
export type ChatRawSelect = ChatRawBoolean<ChatRaw>;
|
||||
|
||||
const chatSchema = new Schema<ChatRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
id: { 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');
|
||||
|
||||
@@ -10,9 +10,13 @@ export class ChatwootRaw {
|
||||
url?: string;
|
||||
name_inbox?: string;
|
||||
sign_msg?: boolean;
|
||||
sign_delimiter?: string;
|
||||
number?: string;
|
||||
reopen_conversation?: boolean;
|
||||
conversation_pending?: boolean;
|
||||
import_contacts?: boolean;
|
||||
import_messages?: boolean;
|
||||
days_limit_import_messages?: number;
|
||||
}
|
||||
|
||||
const chatwootSchema = new Schema<ChatwootRaw>({
|
||||
@@ -23,9 +27,13 @@ const chatwootSchema = new Schema<ChatwootRaw>({
|
||||
url: { type: String, required: true },
|
||||
name_inbox: { type: String, required: true },
|
||||
sign_msg: { type: Boolean, required: true },
|
||||
sign_delimiter: { type: String, required: false },
|
||||
number: { type: String, required: true },
|
||||
reopen_conversation: { type: Boolean, required: true },
|
||||
conversation_pending: { type: Boolean, required: true },
|
||||
import_contacts: { type: Boolean, required: true },
|
||||
import_messages: { type: Boolean, required: true },
|
||||
days_limit_import_messages: { type: Number, required: true },
|
||||
});
|
||||
|
||||
export const ChatwootModel = dbserver?.model(ChatwootRaw.name, chatwootSchema, 'chatwoot');
|
||||
|
||||
@@ -10,6 +10,11 @@ export class ContactRaw {
|
||||
owner: string;
|
||||
}
|
||||
|
||||
type ContactRawBoolean<T> = {
|
||||
[P in keyof T]?: 0 | 1;
|
||||
};
|
||||
export type ContactRawSelect = ContactRawBoolean<ContactRaw>;
|
||||
|
||||
const contactSchema = new Schema<ContactRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
pushName: { type: String, minlength: 1 },
|
||||
|
||||
@@ -3,6 +3,8 @@ export * from './chamaai.model';
|
||||
export * from './chat.model';
|
||||
export * from './chatwoot.model';
|
||||
export * from './contact.model';
|
||||
export * from './integration.model';
|
||||
export * from './label.model';
|
||||
export * from './message.model';
|
||||
export * from './proxy.model';
|
||||
export * from './rabbitmq.model';
|
||||
|
||||
20
src/whatsapp/models/integration.model.ts
Normal file
20
src/whatsapp/models/integration.model.ts
Normal 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 sqsSchema = 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 type IntegrationModel = typeof IntegrationModel;
|
||||
29
src/whatsapp/models/label.model.ts
Normal file
29
src/whatsapp/models/label.model.ts
Normal 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;
|
||||
@@ -10,6 +10,14 @@ class Key {
|
||||
participant?: string;
|
||||
}
|
||||
|
||||
class ChatwootMessage {
|
||||
messageId?: number;
|
||||
inboxId?: number;
|
||||
conversationId?: number;
|
||||
contactInbox?: { sourceId: string };
|
||||
isRead?: boolean;
|
||||
}
|
||||
|
||||
export class MessageRaw {
|
||||
_id?: string;
|
||||
key?: Key;
|
||||
@@ -19,12 +27,21 @@ export class MessageRaw {
|
||||
messageType?: string;
|
||||
messageTimestamp?: number | Long.Long;
|
||||
owner: string;
|
||||
source?: 'android' | 'web' | 'ios';
|
||||
source?: 'android' | 'web' | 'ios' | 'unknown' | 'desktop';
|
||||
source_id?: string;
|
||||
source_reply_id?: string;
|
||||
chatwootMessageId?: string;
|
||||
chatwoot?: ChatwootMessage;
|
||||
contextInfo?: 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>({
|
||||
_id: { type: String, _id: true },
|
||||
key: {
|
||||
@@ -37,13 +54,19 @@ const messageSchema = new Schema<MessageRaw>({
|
||||
participant: { type: String },
|
||||
messageType: { type: String },
|
||||
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 },
|
||||
owner: { type: String, required: true, minlength: 1 },
|
||||
chatwootMessageId: { type: String, required: false },
|
||||
chatwoot: {
|
||||
messageId: { type: Number },
|
||||
inboxId: { type: Number },
|
||||
conversationId: { type: Number },
|
||||
contactInbox: { type: Object },
|
||||
isRead: { type: Boolean },
|
||||
},
|
||||
});
|
||||
|
||||
messageSchema.index({ chatwootMessageId: 1, owner: 1 });
|
||||
messageSchema.index({ 'chatwoot.messageId': 1, owner: 1 });
|
||||
messageSchema.index({ 'key.id': 1 });
|
||||
messageSchema.index({ 'key.id': 1, owner: 1 });
|
||||
messageSchema.index({ owner: 1 });
|
||||
|
||||
@@ -2,16 +2,30 @@ import { Schema } from 'mongoose';
|
||||
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
|
||||
class Proxy {
|
||||
host?: string;
|
||||
port?: string;
|
||||
protocol?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export class ProxyRaw {
|
||||
_id?: string;
|
||||
enabled?: boolean;
|
||||
proxy?: string;
|
||||
proxy?: Proxy;
|
||||
}
|
||||
|
||||
const proxySchema = new Schema<ProxyRaw>({
|
||||
_id: { type: String, _id: 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');
|
||||
|
||||
@@ -10,6 +10,7 @@ export class SettingsRaw {
|
||||
always_online?: boolean;
|
||||
read_messages?: boolean;
|
||||
read_status?: boolean;
|
||||
sync_full_history?: boolean;
|
||||
}
|
||||
|
||||
const settingsSchema = new Schema<SettingsRaw>({
|
||||
@@ -20,6 +21,7 @@ const settingsSchema = new Schema<SettingsRaw>({
|
||||
always_online: { type: Boolean, required: true },
|
||||
read_messages: { 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');
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { opendirSync, readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { Auth, ConfigService } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { AUTH_DIR } from '../../config/path.config';
|
||||
import { IInsert, Repository } from '../abstract/abstract.repository';
|
||||
import { AuthRaw, IAuthModel } from '../models';
|
||||
import { AuthRaw, IAuthModel, IntegrationModel } from '../models';
|
||||
|
||||
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);
|
||||
this.auth = configService.get<Auth>('AUTHENTICATION');
|
||||
}
|
||||
@@ -19,6 +23,7 @@ export class AuthRepository extends Repository {
|
||||
public async create(data: AuthRaw, instance: string): Promise<IInsert> {
|
||||
try {
|
||||
this.logger.verbose('creating auth');
|
||||
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('saving auth to db');
|
||||
const insert = await this.authModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
|
||||
@@ -62,4 +67,69 @@ export class AuthRepository extends Repository {
|
||||
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> {
|
||||
try {
|
||||
this.logger.verbose('finding auth by instanceId');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding auth in db');
|
||||
const response = await this.authModel.findOne({ instanceId });
|
||||
|
||||
return response._id;
|
||||
}
|
||||
|
||||
this.logger.verbose('finding auth in store is not supported');
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ 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 { ChatRaw, IChatModel } from '../models';
|
||||
import { ChatRaw, ChatRawSelect, IChatModel } from '../models';
|
||||
|
||||
export class ChatQuery {
|
||||
select?: ChatRawSelect;
|
||||
where: ChatRaw;
|
||||
}
|
||||
|
||||
@@ -69,7 +70,7 @@ export class ChatRepository extends Repository {
|
||||
this.logger.verbose('finding chats');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
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');
|
||||
@@ -114,4 +115,63 @@ export class ChatRepository extends Repository {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,18 @@ 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 { ContactRaw, IContactModel } from '../models';
|
||||
import { ContactRaw, ContactRawSelect, IContactModel } from '../models';
|
||||
|
||||
export class ContactQuery {
|
||||
select?: ContactRawSelect;
|
||||
where: ContactRaw;
|
||||
}
|
||||
|
||||
export class ContactQueryMany {
|
||||
owner: ContactRaw['owner'];
|
||||
ids: ContactRaw['id'][];
|
||||
}
|
||||
|
||||
export class ContactRepository extends Repository {
|
||||
constructor(private readonly contactModel: IContactModel, private readonly configService: ConfigService) {
|
||||
super(configService);
|
||||
@@ -129,7 +135,7 @@ export class ContactRepository extends Repository {
|
||||
this.logger.verbose('finding contacts');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
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');
|
||||
@@ -168,4 +174,54 @@ export class ContactRepository extends Repository {
|
||||
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 [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
64
src/whatsapp/repository/integration.repository.ts
Normal file
64
src/whatsapp/repository/integration.repository.ts
Normal 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 {};
|
||||
}
|
||||
}
|
||||
}
|
||||
111
src/whatsapp/repository/label.repository.ts
Normal file
111
src/whatsapp/repository/label.repository.ts
Normal 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() };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
import { opendirSync, readFileSync } from 'fs';
|
||||
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 { IMessageModel, MessageRaw } from '../models';
|
||||
import { IMessageModel, MessageRaw, MessageRawSelect } from '../models';
|
||||
|
||||
export class MessageQuery {
|
||||
select?: MessageRawSelect;
|
||||
where: MessageRaw;
|
||||
limit?: number;
|
||||
}
|
||||
@@ -18,6 +19,28 @@ export class MessageRepository extends Repository {
|
||||
|
||||
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> {
|
||||
this.logger.verbose('inserting messages');
|
||||
|
||||
@@ -91,15 +114,11 @@ export class MessageRepository extends Repository {
|
||||
this.logger.verbose('finding messages');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding messages in db');
|
||||
if (query?.where?.key) {
|
||||
for (const [k, v] of Object.entries(query.where.key)) {
|
||||
query.where['key.' + k] = v;
|
||||
}
|
||||
delete query?.where?.key;
|
||||
}
|
||||
query = this.buildQuery(query);
|
||||
|
||||
return await this.messageModel
|
||||
.find({ ...query.where })
|
||||
.select(query.select || {})
|
||||
.sort({ messageTimestamp: -1 })
|
||||
.limit(query?.limit ?? 0);
|
||||
}
|
||||
@@ -141,6 +160,7 @@ export class MessageRepository extends Repository {
|
||||
})
|
||||
.splice(0, query?.limit ?? messages.length);
|
||||
} catch (error) {
|
||||
this.logger.error(`error on message find: ${error.toString()}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -195,4 +215,26 @@ export class MessageRepository extends Repository {
|
||||
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() };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ 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';
|
||||
@@ -33,7 +35,9 @@ export class RepositoryBroker {
|
||||
public readonly typebot: TypebotRepository,
|
||||
public readonly proxy: ProxyRepository,
|
||||
public readonly chamaai: ChamaaiRepository,
|
||||
public readonly integration: IntegrationRepository,
|
||||
public readonly auth: AuthRepository,
|
||||
public readonly labels: LabelRepository,
|
||||
private configService: ConfigService,
|
||||
dbServer?: MongoClient,
|
||||
) {
|
||||
@@ -69,6 +73,7 @@ export class RepositoryBroker {
|
||||
const typebotDir = join(storePath, 'typebot');
|
||||
const proxyDir = join(storePath, 'proxy');
|
||||
const chamaaiDir = join(storePath, 'chamaai');
|
||||
const integrationDir = join(storePath, 'integration');
|
||||
const tempDir = join(storePath, 'temp');
|
||||
|
||||
if (!fs.existsSync(authDir)) {
|
||||
@@ -127,6 +132,10 @@ export class RepositoryBroker {
|
||||
this.logger.verbose('creating chamaai dir: ' + chamaaiDir);
|
||||
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)) {
|
||||
this.logger.verbose('creating temp dir: ' + tempDir);
|
||||
fs.mkdirSync(tempDir, { recursive: true });
|
||||
|
||||
@@ -3,22 +3,26 @@ import { RequestHandler, Router } from 'express';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import {
|
||||
archiveChatSchema,
|
||||
blockUserSchema,
|
||||
contactValidateSchema,
|
||||
deleteMessageSchema,
|
||||
messageUpSchema,
|
||||
messageValidateSchema,
|
||||
presenceSchema,
|
||||
presenceOnlySchema,
|
||||
privacySettingsSchema,
|
||||
profileNameSchema,
|
||||
profilePictureSchema,
|
||||
profileSchema,
|
||||
profileStatusSchema,
|
||||
readMessageSchema,
|
||||
updateMessageSchema,
|
||||
whatsappNumberSchema,
|
||||
} from '../../validate/validate.schema';
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
import {
|
||||
ArchiveChatDto,
|
||||
BlockUserDto,
|
||||
DeleteMessage,
|
||||
getBase64FromMediaMessageDto,
|
||||
NumberDto,
|
||||
@@ -28,6 +32,7 @@ import {
|
||||
ProfileStatusDto,
|
||||
ReadMessageDto,
|
||||
SendPresenceDto,
|
||||
UpdateMessageDto,
|
||||
WhatsAppNumberDto,
|
||||
} from '../dto/chat.dto';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
@@ -58,7 +63,7 @@ export class ChatRouter extends RouterBroker {
|
||||
execute: (instance, data) => chatController.whatsappNumber(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.put(this.routerPath('markMessageAsRead'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in markMessageAsRead');
|
||||
@@ -365,6 +370,40 @@ export class ChatRouter extends RouterBroker {
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.put(this.routerPath('updateMessage'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in updateMessage');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<UpdateMessageDto>({
|
||||
request: req,
|
||||
schema: updateMessageSchema,
|
||||
ClassRef: UpdateMessageDto,
|
||||
execute: (instance, data) => chatController.updateMessage(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.put(this.routerPath('updateBlockStatus'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in updateBlockStatus');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<BlockUserDto>({
|
||||
request: req,
|
||||
schema: blockUserSchema,
|
||||
ClassRef: BlockUserDto,
|
||||
execute: (instance, data) => chatController.blockUser(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import { chatwootSchema, instanceNameSchema } from '../../validate/validate.sche
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
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';
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { RequestHandler, Router } from 'express';
|
||||
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import {
|
||||
AcceptGroupInviteSchema,
|
||||
createGroupSchema,
|
||||
getParticipantsSchema,
|
||||
groupInviteSchema,
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
} from '../../validate/validate.schema';
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
import {
|
||||
AcceptGroupInvite,
|
||||
CreateGroupDto,
|
||||
GetParticipant,
|
||||
GroupDescriptionDto,
|
||||
@@ -182,6 +184,22 @@ export class GroupRouter extends RouterBroker {
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.get(this.routerPath('acceptInviteCode'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in acceptInviteCode');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.inviteCodeValidate<AcceptGroupInvite>({
|
||||
request: req,
|
||||
schema: AcceptGroupInviteSchema,
|
||||
ClassRef: AcceptGroupInvite,
|
||||
execute: (instance, data) => groupController.acceptInviteCode(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.post(this.routerPath('sendInvite'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in sendInvite');
|
||||
logger.verbose('request body: ');
|
||||
|
||||
@@ -9,6 +9,7 @@ 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';
|
||||
@@ -31,26 +32,29 @@ enum HttpStatus {
|
||||
|
||||
const router = Router();
|
||||
const authType = configService.get<Auth>('AUTHENTICATION').TYPE;
|
||||
const serverConfig = configService.get('SERVER');
|
||||
const guards = [instanceExistsGuard, instanceLoggedGuard, authGuard[authType]];
|
||||
|
||||
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
||||
|
||||
if (!serverConfig.DISABLE_MANAGER) router.use('/manager', new ViewsRouter().router);
|
||||
|
||||
router
|
||||
.get('/', (req, res) => {
|
||||
res.status(HttpStatus.OK).json({
|
||||
status: HttpStatus.OK,
|
||||
message: 'Welcome to the Evolution API, it is working!',
|
||||
version: packageJson.version,
|
||||
documentation: `${req.protocol}://${req.get('host')}/docs`,
|
||||
manager: `${req.protocol}://${req.get('host')}/manager`,
|
||||
swagger: !serverConfig.DISABLE_DOCS ? `${req.protocol}://${req.get('host')}/docs` : undefined,
|
||||
manager: !serverConfig.DISABLE_MANAGER ? `${req.protocol}://${req.get('host')}/manager` : undefined,
|
||||
documentation: `https://doc.evolution-api.com`,
|
||||
});
|
||||
})
|
||||
.use('/instance', new InstanceRouter(configService, ...guards).router)
|
||||
.use('/manager', new ViewsRouter().router)
|
||||
.use('/message', new MessageRouter(...guards).router)
|
||||
.use('/chat', new ChatRouter(...guards).router)
|
||||
.use('/group', new GroupRouter(...guards).router)
|
||||
.use('/webhook', new WebhookRouter(...guards).router)
|
||||
.use('/webhook', new WebhookRouter(configService, ...guards).router)
|
||||
.use('/chatwoot', new ChatwootRouter(...guards).router)
|
||||
.use('/settings', new SettingsRouter(...guards).router)
|
||||
.use('/websocket', new WebsocketRouter(...guards).router)
|
||||
@@ -58,6 +62,7 @@ router
|
||||
.use('/sqs', new SqsRouter(...guards).router)
|
||||
.use('/typebot', new TypebotRouter(...guards).router)
|
||||
.use('/proxy', new ProxyRouter(...guards).router)
|
||||
.use('/chamaai', new ChamaaiRouter(...guards).router);
|
||||
.use('/chamaai', new ChamaaiRouter(...guards).router)
|
||||
.use('/label', new LabelRouter(...guards).router);
|
||||
|
||||
export { HttpStatus, router };
|
||||
|
||||
@@ -3,9 +3,9 @@ import { RequestHandler, Router } from 'express';
|
||||
import { Auth, ConfigService, Database } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
import { instanceNameSchema, oldTokenSchema } from '../../validate/validate.schema';
|
||||
import {instanceNameSchema, oldTokenSchema, presenceOnlySchema} from '../../validate/validate.schema';
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { InstanceDto, SetPresenceDto } from '../dto/instance.dto';
|
||||
import { OldToken } from '../services/auth.service';
|
||||
import { instanceController } from '../whatsapp.module';
|
||||
import { HttpStatus } from './index.router';
|
||||
@@ -98,6 +98,22 @@ export class InstanceRouter extends RouterBroker {
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.post(this.routerPath('setPresence'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in setPresence');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<null>({
|
||||
request: req,
|
||||
schema: presenceOnlySchema,
|
||||
ClassRef: SetPresenceDto,
|
||||
execute: (instance, data) => instanceController.setPresence(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.delete(this.routerPath('logout'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in logoutInstances');
|
||||
logger.verbose('request body: ');
|
||||
|
||||
53
src/whatsapp/routers/label.router.ts
Normal file
53
src/whatsapp/routers/label.router.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { RequestHandler, Router } from 'express';
|
||||
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { handleLabelSchema } from '../../validate/validate.schema';
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
import { HandleLabelDto, LabelDto } from '../dto/label.dto';
|
||||
import { labelController } from '../whatsapp.module';
|
||||
import { HttpStatus } from './index.router';
|
||||
|
||||
const logger = new Logger('LabelRouter');
|
||||
|
||||
export class LabelRouter extends RouterBroker {
|
||||
constructor(...guards: RequestHandler[]) {
|
||||
super();
|
||||
this.router
|
||||
.get(this.routerPath('findLabels'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in findLabels');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<LabelDto>({
|
||||
request: req,
|
||||
schema: null,
|
||||
ClassRef: LabelDto,
|
||||
execute: (instance) => labelController.fetchLabels(instance),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.put(this.routerPath('handleLabel'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in handleLabel');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<HandleLabelDto>({
|
||||
request: req,
|
||||
schema: handleLabelSchema,
|
||||
ClassRef: HandleLabelDto,
|
||||
execute: (instance, data) => labelController.handleLabel(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
});
|
||||
}
|
||||
|
||||
public readonly router = Router();
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
reactionMessageSchema,
|
||||
statusMessageSchema,
|
||||
stickerMessageSchema,
|
||||
templateMessageSchema,
|
||||
textMessageSchema,
|
||||
} from '../../validate/validate.schema';
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
@@ -26,6 +27,7 @@ import {
|
||||
SendReactionDto,
|
||||
SendStatusDto,
|
||||
SendStickerDto,
|
||||
SendTemplateDto,
|
||||
SendTextDto,
|
||||
} from '../dto/sendMessage.dto';
|
||||
import { sendMessageController } from '../whatsapp.module';
|
||||
@@ -85,6 +87,22 @@ export class MessageRouter extends RouterBroker {
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('sendTemplate'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in sendTemplate');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<SendTemplateDto>({
|
||||
request: req,
|
||||
schema: templateMessageSchema,
|
||||
ClassRef: SendTemplateDto,
|
||||
execute: (instance, data) => sendMessageController.sendTemplate(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('sendButtons'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in sendButtons');
|
||||
logger.verbose('request body: ');
|
||||
|
||||
@@ -5,7 +5,6 @@ import { instanceNameSchema, settingsSchema } from '../../validate/validate.sche
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { SettingsDto } from '../dto/settings.dto';
|
||||
// import { SettingsService } from '../services/settings.service';
|
||||
import { settingsController } from '../whatsapp.module';
|
||||
import { HttpStatus } from './index.router';
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { RequestHandler, Router } from 'express';
|
||||
|
||||
import { ConfigService, WaBusiness } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { instanceNameSchema, webhookSchema } from '../../validate/validate.schema';
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
@@ -11,7 +12,7 @@ import { HttpStatus } from './index.router';
|
||||
const logger = new Logger('WebhookRouter');
|
||||
|
||||
export class WebhookRouter extends RouterBroker {
|
||||
constructor(...guards: RequestHandler[]) {
|
||||
constructor(readonly configService: ConfigService, ...guards: RequestHandler[]) {
|
||||
super();
|
||||
this.router
|
||||
.post(this.routerPath('set'), ...guards, async (req, res) => {
|
||||
@@ -45,6 +46,31 @@ export class WebhookRouter extends RouterBroker {
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.post(this.routerPath('whatsapp'), async (req, res) => {
|
||||
logger.verbose('request received in webhook');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceNameSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance, data) => webhookController.receiveWebhook(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.get(this.routerPath('whatsapp'), async (req, res) => {
|
||||
logger.verbose('request received in webhook');
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
if (req.query['hub.verify_token'] === this.configService.get<WaBusiness>('WA_BUSINESS').TOKEN_WEBHOOK)
|
||||
res.send(req.query['hub.challenge']);
|
||||
else res.send('Error, wrong validation token');
|
||||
logger.verbose('Error, wrong validation token');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,10 @@ export class AuthService {
|
||||
|
||||
this.logger.verbose('JWT token created: ' + token);
|
||||
|
||||
const auth = await this.repository.auth.create({ jwt: token }, instance.instanceName);
|
||||
const auth = await this.repository.auth.create(
|
||||
{ jwt: token, instanceId: instance.instanceId },
|
||||
instance.instanceName,
|
||||
);
|
||||
|
||||
this.logger.verbose('JWT token saved in database');
|
||||
|
||||
@@ -66,7 +69,7 @@ export class AuthService {
|
||||
|
||||
this.logger.verbose(token ? 'APIKEY defined: ' + apikey : 'APIKEY created: ' + apikey);
|
||||
|
||||
const auth = await this.repository.auth.create({ apikey }, instance.instanceName);
|
||||
const auth = await this.repository.auth.create({ apikey, instanceId: instance.instanceId }, instance.instanceName);
|
||||
|
||||
this.logger.verbose('APIKEY saved in database');
|
||||
|
||||
|
||||
62
src/whatsapp/services/cache.service.ts
Normal file
62
src/whatsapp/services/cache.service.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { ICache } from '../abstract/abstract.cache';
|
||||
|
||||
export class CacheService {
|
||||
private readonly logger = new Logger(CacheService.name);
|
||||
|
||||
constructor(private readonly cache: ICache) {
|
||||
if (cache) {
|
||||
this.logger.verbose(`cacheservice created using cache engine: ${cache.constructor?.name}`);
|
||||
} else {
|
||||
this.logger.verbose(`cacheservice disabled`);
|
||||
}
|
||||
}
|
||||
|
||||
async get(key: string): Promise<any> {
|
||||
if (!this.cache) {
|
||||
return;
|
||||
}
|
||||
this.logger.verbose(`cacheservice getting key: ${key}`);
|
||||
return this.cache.get(key);
|
||||
}
|
||||
|
||||
async set(key: string, value: any) {
|
||||
if (!this.cache) {
|
||||
return;
|
||||
}
|
||||
this.logger.verbose(`cacheservice setting key: ${key}`);
|
||||
this.cache.set(key, value);
|
||||
}
|
||||
|
||||
async has(key: string) {
|
||||
if (!this.cache) {
|
||||
return;
|
||||
}
|
||||
this.logger.verbose(`cacheservice has key: ${key}`);
|
||||
return this.cache.has(key);
|
||||
}
|
||||
|
||||
async delete(key: string) {
|
||||
if (!this.cache) {
|
||||
return;
|
||||
}
|
||||
this.logger.verbose(`cacheservice deleting key: ${key}`);
|
||||
return this.cache.delete(key);
|
||||
}
|
||||
|
||||
async deleteAll(appendCriteria?: string) {
|
||||
if (!this.cache) {
|
||||
return;
|
||||
}
|
||||
this.logger.verbose(`cacheservice deleting all keys`);
|
||||
return this.cache.deleteAll(appendCriteria);
|
||||
}
|
||||
|
||||
async keys(appendCriteria?: string) {
|
||||
if (!this.cache) {
|
||||
return;
|
||||
}
|
||||
this.logger.verbose(`cacheservice getting all keys`);
|
||||
return this.cache.keys(appendCriteria);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
33
src/whatsapp/services/integration.service.ts
Normal file
33
src/whatsapp/services/integration.service.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { IntegrationDto } from '../dto/integration.dto';
|
||||
import { IntegrationRaw } from '../models';
|
||||
import { WAMonitoringService } from './monitor.service';
|
||||
|
||||
export class IntegrationService {
|
||||
constructor(private readonly waMonitor: WAMonitoringService) {}
|
||||
|
||||
private readonly logger = new Logger(IntegrationService.name);
|
||||
|
||||
public create(instance: InstanceDto, data: IntegrationDto) {
|
||||
this.logger.verbose('create integration: ' + instance.instanceName);
|
||||
this.waMonitor.waInstances[instance.instanceName].setIntegration(data);
|
||||
|
||||
return { integration: { ...instance, integration: data } };
|
||||
}
|
||||
|
||||
public async find(instance: InstanceDto): Promise<IntegrationRaw> {
|
||||
try {
|
||||
this.logger.verbose('find integration: ' + instance.instanceName);
|
||||
const result = await this.waMonitor.waInstances[instance.instanceName].findIntegration();
|
||||
|
||||
if (Object.keys(result).length === 0) {
|
||||
throw new Error('Integration not found');
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
return { integration: '', number: '', token: '' };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,21 @@
|
||||
import { execSync } from 'child_process';
|
||||
import EventEmitter2 from 'eventemitter2';
|
||||
import { opendirSync, readdirSync, rmSync } from 'fs';
|
||||
import { existsSync, mkdirSync, opendirSync, readdirSync, rmSync, writeFileSync } from 'fs';
|
||||
import { Db } from 'mongodb';
|
||||
import { Collection } from 'mongoose';
|
||||
import { join } from 'path';
|
||||
|
||||
import { Auth, ConfigService, Database, DelInstance, HttpServer, Redis } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { INSTANCE_DIR, STORE_DIR } from '../../config/path.config';
|
||||
import { NotFoundException } from '../../exceptions';
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
import { RedisCache } from '../../libs/redis.client';
|
||||
import {
|
||||
AuthModel,
|
||||
ChamaaiModel,
|
||||
// ChatModel,
|
||||
ChatwootModel,
|
||||
// ContactModel,
|
||||
// MessageModel,
|
||||
// MessageUpModel,
|
||||
ContactModel,
|
||||
LabelModel,
|
||||
ProxyModel,
|
||||
RabbitmqModel,
|
||||
SettingsModel,
|
||||
@@ -26,7 +24,10 @@ import {
|
||||
WebsocketModel,
|
||||
} from '../models';
|
||||
import { RepositoryBroker } from '../repository/repository.manager';
|
||||
import { WAStartupService } from './whatsapp.service';
|
||||
import { Integration } from '../types/wa.types';
|
||||
import { CacheService } from './cache.service';
|
||||
import { BaileysStartupService } from './whatsapp.baileys.service';
|
||||
import { BusinessStartupService } from './whatsapp.business.service';
|
||||
|
||||
export class WAMonitoringService {
|
||||
constructor(
|
||||
@@ -34,12 +35,12 @@ export class WAMonitoringService {
|
||||
private readonly configService: ConfigService,
|
||||
private readonly repository: RepositoryBroker,
|
||||
private readonly cache: RedisCache,
|
||||
private readonly chatwootCache: CacheService,
|
||||
) {
|
||||
this.logger.verbose('instance created');
|
||||
|
||||
this.removeInstance();
|
||||
this.noConnection();
|
||||
// this.delInstanceFiles();
|
||||
|
||||
Object.assign(this.db, configService.get<Database>('DATABASE'));
|
||||
Object.assign(this.redis, configService.get<Redis>('REDIS'));
|
||||
@@ -54,10 +55,8 @@ export class WAMonitoringService {
|
||||
|
||||
private dbInstance: Db;
|
||||
|
||||
private dbStore = dbserver;
|
||||
|
||||
private readonly logger = new Logger(WAMonitoringService.name);
|
||||
public readonly waInstances: Record<string, WAStartupService> = {};
|
||||
public readonly waInstances: Record<string, BaileysStartupService | BusinessStartupService> = {};
|
||||
|
||||
public delInstanceTime(instance: string) {
|
||||
const time = this.configService.get<DelInstance>('DEL_INSTANCE');
|
||||
@@ -67,9 +66,11 @@ export class WAMonitoringService {
|
||||
setTimeout(async () => {
|
||||
if (this.waInstances[instance]?.connectionStatus?.state !== 'open') {
|
||||
if (this.waInstances[instance]?.connectionStatus?.state === 'connecting') {
|
||||
await this.waInstances[instance]?.client?.logout('Log out instance: ' + instance);
|
||||
this.waInstances[instance]?.client?.ws?.close();
|
||||
this.waInstances[instance]?.client?.end(undefined);
|
||||
if ((await this.waInstances[instance].findIntegration()).integration === Integration.WHATSAPP_BAILEYS) {
|
||||
await this.waInstances[instance]?.client?.logout('Log out instance: ' + instance);
|
||||
this.waInstances[instance]?.client?.ws?.close();
|
||||
this.waInstances[instance]?.client?.end(undefined);
|
||||
}
|
||||
this.waInstances[instance]?.removeRabbitmqQueues();
|
||||
delete this.waInstances[instance];
|
||||
} else {
|
||||
@@ -106,12 +107,23 @@ export class WAMonitoringService {
|
||||
};
|
||||
}
|
||||
|
||||
const findIntegration = await this.waInstances[key].findIntegration();
|
||||
|
||||
let integration: any;
|
||||
if (findIntegration) {
|
||||
integration = {
|
||||
...findIntegration,
|
||||
webhook_wa_business: `${urlServer}/webhook/whatsapp/${encodeURIComponent(key)}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (value.connectionStatus.state === 'open') {
|
||||
this.logger.verbose('instance: ' + key + ' - connectionStatus: open');
|
||||
|
||||
const instanceData = {
|
||||
instance: {
|
||||
instanceName: key,
|
||||
instanceId: (await this.repository.auth.find(key))?.instanceId,
|
||||
owner: value.wuid,
|
||||
profileName: (await value.getProfileName()) || 'not loaded',
|
||||
profilePictureUrl: value.profilePictureUrl,
|
||||
@@ -126,6 +138,8 @@ export class WAMonitoringService {
|
||||
instanceData.instance['apikey'] = (await this.repository.auth.find(key))?.apikey;
|
||||
|
||||
instanceData.instance['chatwoot'] = chatwoot;
|
||||
|
||||
instanceData.instance['integration'] = integration;
|
||||
}
|
||||
|
||||
instances.push(instanceData);
|
||||
@@ -135,6 +149,7 @@ export class WAMonitoringService {
|
||||
const instanceData = {
|
||||
instance: {
|
||||
instanceName: key,
|
||||
instanceId: (await this.repository.auth.find(key))?.instanceId,
|
||||
status: value.connectionStatus.state,
|
||||
},
|
||||
};
|
||||
@@ -145,6 +160,8 @@ export class WAMonitoringService {
|
||||
instanceData.instance['apikey'] = (await this.repository.auth.find(key))?.apikey;
|
||||
|
||||
instanceData.instance['chatwoot'] = chatwoot;
|
||||
|
||||
instanceData.instance['integration'] = integration;
|
||||
}
|
||||
|
||||
instances.push(instanceData);
|
||||
@@ -157,6 +174,32 @@ export class WAMonitoringService {
|
||||
return instances.find((i) => i.instance.instanceName === instanceName) ?? instances;
|
||||
}
|
||||
|
||||
public async instanceInfoById(instanceId?: string, number?: string) {
|
||||
this.logger.verbose('get instance info');
|
||||
let instanceName: string;
|
||||
if (instanceId) {
|
||||
instanceName = await this.repository.auth.findInstanceNameById(instanceId);
|
||||
if (!instanceName) {
|
||||
throw new NotFoundException(`Instance "${instanceId}" not found`);
|
||||
}
|
||||
} else if (number) {
|
||||
instanceName = await this.repository.auth.findInstanceNameByNumber(number);
|
||||
if (!instanceName) {
|
||||
throw new NotFoundException(`Instance "${number}" not found`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!instanceName) {
|
||||
throw new NotFoundException(`Instance "${instanceId}" not found`);
|
||||
}
|
||||
|
||||
if (instanceName && !this.waInstances[instanceName]) {
|
||||
throw new NotFoundException(`Instance "${instanceName}" not found`);
|
||||
}
|
||||
|
||||
return this.instanceInfo(instanceName);
|
||||
}
|
||||
|
||||
private delInstanceFiles() {
|
||||
this.logger.verbose('cron to delete instance files started');
|
||||
setInterval(async () => {
|
||||
@@ -193,12 +236,6 @@ export class WAMonitoringService {
|
||||
|
||||
public async cleaningUp(instanceName: string) {
|
||||
this.logger.verbose('cleaning up instance: ' + instanceName);
|
||||
if (this.redis.ENABLED) {
|
||||
this.logger.verbose('cleaning up instance in redis: ' + instanceName);
|
||||
this.cache.reference = instanceName;
|
||||
await this.cache.delAll();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
|
||||
this.logger.verbose('cleaning up instance in database: ' + instanceName);
|
||||
@@ -210,6 +247,13 @@ export class WAMonitoringService {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.redis.ENABLED) {
|
||||
this.logger.verbose('cleaning up instance in redis: ' + instanceName);
|
||||
this.cache.reference = instanceName;
|
||||
await this.cache.delAll();
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.verbose('cleaning up instance in files: ' + instanceName);
|
||||
rmSync(join(INSTANCE_DIR, instanceName), { recursive: true, force: true });
|
||||
}
|
||||
@@ -233,17 +277,13 @@ export class WAMonitoringService {
|
||||
execSync(`rm -rf ${join(STORE_DIR, 'typebot', instanceName + '*')}`);
|
||||
execSync(`rm -rf ${join(STORE_DIR, 'websocket', instanceName + '*')}`);
|
||||
execSync(`rm -rf ${join(STORE_DIR, 'settings', instanceName + '*')}`);
|
||||
execSync(`rm -rf ${join(STORE_DIR, 'labels', instanceName + '*')}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.verbose('cleaning store database instance: ' + instanceName);
|
||||
|
||||
// await ChatModel.deleteMany({ owner: instanceName });
|
||||
// await ContactModel.deleteMany({ owner: instanceName });
|
||||
// await MessageUpModel.deleteMany({ owner: instanceName });
|
||||
// await MessageModel.deleteMany({ owner: instanceName });
|
||||
|
||||
await AuthModel.deleteMany({ _id: instanceName });
|
||||
await WebhookModel.deleteMany({ _id: instanceName });
|
||||
await ChatwootModel.deleteMany({ _id: instanceName });
|
||||
@@ -253,6 +293,8 @@ export class WAMonitoringService {
|
||||
await TypebotModel.deleteMany({ _id: instanceName });
|
||||
await WebsocketModel.deleteMany({ _id: instanceName });
|
||||
await SettingsModel.deleteMany({ _id: instanceName });
|
||||
await LabelModel.deleteMany({ owner: instanceName });
|
||||
await ContactModel.deleteMany({ owner: instanceName });
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -273,9 +315,56 @@ export class WAMonitoringService {
|
||||
}
|
||||
}
|
||||
|
||||
public async saveInstance(data: any) {
|
||||
this.logger.verbose('Save instance');
|
||||
|
||||
try {
|
||||
const msgParsed = JSON.parse(JSON.stringify(data));
|
||||
if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
|
||||
await this.repository.dbServer.connect();
|
||||
await this.dbInstance.collection(data.instanceName).replaceOne({ _id: 'integration' }, msgParsed, {
|
||||
upsert: true,
|
||||
});
|
||||
} else {
|
||||
const path = join(INSTANCE_DIR, data.instanceName);
|
||||
if (!existsSync(path)) mkdirSync(path, { recursive: true });
|
||||
writeFileSync(path + '/integration.json', JSON.stringify(msgParsed));
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
private async setInstance(name: string) {
|
||||
const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache);
|
||||
instance.instanceName = name;
|
||||
const integration = await this.repository.integration.find(name);
|
||||
|
||||
let instance: BaileysStartupService | BusinessStartupService;
|
||||
if (integration && integration.integration === Integration.WHATSAPP_BUSINESS) {
|
||||
instance = new BusinessStartupService(
|
||||
this.configService,
|
||||
this.eventEmitter,
|
||||
this.repository,
|
||||
this.cache,
|
||||
this.chatwootCache,
|
||||
);
|
||||
|
||||
instance.instanceName = name;
|
||||
} else {
|
||||
instance = new BaileysStartupService(
|
||||
this.configService,
|
||||
this.eventEmitter,
|
||||
this.repository,
|
||||
this.cache,
|
||||
this.chatwootCache,
|
||||
);
|
||||
|
||||
instance.instanceName = name;
|
||||
|
||||
if (!integration) {
|
||||
await instance.setIntegration({ integration: Integration.WHATSAPP_BAILEYS });
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.verbose('Instance loaded: ' + name);
|
||||
await instance.connectToWhatsapp();
|
||||
this.logger.verbose('connectToWhatsapp: ' + name);
|
||||
@@ -300,7 +389,7 @@ export class WAMonitoringService {
|
||||
this.logger.verbose('Database enabled');
|
||||
await this.repository.dbServer.connect();
|
||||
const collections: any[] = await this.dbInstance.collections();
|
||||
|
||||
await this.deleteTempInstances(collections);
|
||||
if (collections.length > 0) {
|
||||
this.logger.verbose('Reading collections and setting instances');
|
||||
await Promise.all(collections.map((coll) => this.setInstance(coll.namespace.replace(/^[\w-]+\./, ''))));
|
||||
@@ -357,8 +446,9 @@ export class WAMonitoringService {
|
||||
this.eventEmitter.on('logout.instance', async (instanceName: string) => {
|
||||
this.logger.verbose('logout instance: ' + instanceName);
|
||||
try {
|
||||
// this.logger.verbose('request cleaning up instance: ' + instanceName);
|
||||
// this.cleaningUp(instanceName);
|
||||
this.waInstances[instanceName]?.clearCacheChatwoot();
|
||||
this.logger.verbose('request cleaning up instance: ' + instanceName);
|
||||
this.cleaningUp(instanceName);
|
||||
} finally {
|
||||
this.logger.warn(`Instance "${instanceName}" - LOGOUT`);
|
||||
}
|
||||
@@ -388,4 +478,27 @@ export class WAMonitoringService {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async deleteTempInstances(collections: Collection<Document>[]) {
|
||||
const shouldDelete = this.configService.get<boolean>('DEL_TEMP_INSTANCES');
|
||||
if (!shouldDelete) {
|
||||
this.logger.verbose('Temp instances deletion is disabled');
|
||||
return;
|
||||
}
|
||||
this.logger.verbose('Cleaning up temp instances');
|
||||
const auths = await this.repository.auth.list();
|
||||
if (auths.length === 0) {
|
||||
this.logger.verbose('No temp instances found');
|
||||
return;
|
||||
}
|
||||
let tempInstances = 0;
|
||||
auths.forEach((auth) => {
|
||||
if (collections.find((coll) => coll.namespace.replace(/^[\w-]+\./, '') === auth._id)) {
|
||||
return;
|
||||
}
|
||||
tempInstances++;
|
||||
this.eventEmitter.emit('remove.instance', auth._id, 'inner');
|
||||
});
|
||||
this.logger.verbose('Temp instances removed: ' + tempInstances);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ export class ProxyService {
|
||||
|
||||
private readonly logger = new Logger(ProxyService.name);
|
||||
|
||||
public create(instance: InstanceDto, data: ProxyDto, reload = true) {
|
||||
public create(instance: InstanceDto, data: ProxyDto) {
|
||||
this.logger.verbose('create proxy: ' + instance.instanceName);
|
||||
this.waMonitor.waInstances[instance.instanceName].setProxy(data, reload);
|
||||
this.waMonitor.waInstances[instance.instanceName].setProxy(data);
|
||||
|
||||
return { proxy: { ...instance, proxy: data } };
|
||||
}
|
||||
@@ -27,7 +27,7 @@ export class ProxyService {
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
return { enabled: false, proxy: '' };
|
||||
return { enabled: false, proxy: null };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import EventEmitter2 from 'eventemitter2';
|
||||
|
||||
import { ConfigService, Typebot } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
@@ -9,7 +10,18 @@ import { Events } from '../types/wa.types';
|
||||
import { WAMonitoringService } from './monitor.service';
|
||||
|
||||
export class TypebotService {
|
||||
constructor(private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService) {}
|
||||
constructor(
|
||||
private readonly waMonitor: WAMonitoringService,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
) {
|
||||
this.eventEmitter.on('typebot:end', async (data) => {
|
||||
const keep_open = this.configService.get<Typebot>('TYPEBOT').KEEP_OPEN;
|
||||
if (keep_open) return;
|
||||
|
||||
await this.clearSessions(data.instance, data.remoteJid);
|
||||
});
|
||||
}
|
||||
|
||||
private readonly logger = new Logger(TypebotService.name);
|
||||
|
||||
@@ -110,6 +122,37 @@ export class TypebotService {
|
||||
return { typebot: { ...instance, typebot: typebotData } };
|
||||
}
|
||||
|
||||
public async clearSessions(instance: InstanceDto, remoteJid: string) {
|
||||
const findTypebot = await this.find(instance);
|
||||
const sessions = (findTypebot.sessions as Session[]) ?? [];
|
||||
|
||||
const sessionWithRemoteJid = sessions.filter((session) => session.remoteJid === remoteJid);
|
||||
|
||||
if (sessionWithRemoteJid.length > 0) {
|
||||
sessionWithRemoteJid.forEach((session) => {
|
||||
sessions.splice(sessions.indexOf(session), 1);
|
||||
});
|
||||
|
||||
const typebotData = {
|
||||
enabled: findTypebot.enabled,
|
||||
url: findTypebot.url,
|
||||
typebot: findTypebot.typebot,
|
||||
expire: findTypebot.expire,
|
||||
keyword_finish: findTypebot.keyword_finish,
|
||||
delay_message: findTypebot.delay_message,
|
||||
unknown_message: findTypebot.unknown_message,
|
||||
listening_from_me: findTypebot.listening_from_me,
|
||||
sessions,
|
||||
};
|
||||
|
||||
this.create(instance, typebotData);
|
||||
|
||||
return sessions;
|
||||
}
|
||||
|
||||
return sessions;
|
||||
}
|
||||
|
||||
public async startTypebot(instance: InstanceDto, data: any) {
|
||||
if (data.remoteJid === 'status@broadcast') return;
|
||||
|
||||
@@ -169,20 +212,25 @@ export class TypebotService {
|
||||
} else {
|
||||
const id = Math.floor(Math.random() * 10000000000).toString();
|
||||
|
||||
const reqData = {
|
||||
startParams: {
|
||||
publicId: data.typebot,
|
||||
prefilledVariables: prefilledVariables,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const version = this.configService.get<Typebot>('TYPEBOT').API_VERSION;
|
||||
let url: string;
|
||||
let reqData: {};
|
||||
if (version === 'latest') {
|
||||
url = `${data.url}/api/v1/typebots/${data.typebot}/startChat`;
|
||||
|
||||
reqData = {
|
||||
prefilledVariables: prefilledVariables,
|
||||
};
|
||||
} else {
|
||||
url = `${data.url}/api/v1/sendMessage`;
|
||||
|
||||
reqData = {
|
||||
startParams: {
|
||||
publicId: data.typebot,
|
||||
prefilledVariables: prefilledVariables,
|
||||
},
|
||||
};
|
||||
}
|
||||
const request = await axios.post(url, reqData);
|
||||
|
||||
@@ -226,6 +274,7 @@ export class TypebotService {
|
||||
const types = {
|
||||
conversation: msg.conversation,
|
||||
extendedTextMessage: msg.extendedTextMessage?.text,
|
||||
responseRowId: msg.listResponseMessage?.singleSelectReply?.selectedRowId,
|
||||
};
|
||||
|
||||
this.logger.verbose('type message: ' + types);
|
||||
@@ -260,25 +309,35 @@ export class TypebotService {
|
||||
if (data.remoteJid === 'status@broadcast') return;
|
||||
const id = Math.floor(Math.random() * 10000000000).toString();
|
||||
|
||||
const reqData = {
|
||||
startParams: {
|
||||
publicId: data.typebot,
|
||||
prefilledVariables: {
|
||||
...data.prefilledVariables,
|
||||
remoteJid: data.remoteJid,
|
||||
pushName: data.pushName || data.prefilledVariables?.pushName || '',
|
||||
instanceName: instance.instanceName,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const version = this.configService.get<Typebot>('TYPEBOT').API_VERSION;
|
||||
let url: string;
|
||||
let reqData: {};
|
||||
if (version === 'latest') {
|
||||
url = `${data.url}/api/v1/typebots/${data.typebot}/startChat`;
|
||||
|
||||
reqData = {
|
||||
prefilledVariables: {
|
||||
...data.prefilledVariables,
|
||||
remoteJid: data.remoteJid,
|
||||
pushName: data.pushName || data.prefilledVariables?.pushName || '',
|
||||
instanceName: instance.instanceName,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
url = `${data.url}/api/v1/sendMessage`;
|
||||
|
||||
reqData = {
|
||||
startParams: {
|
||||
publicId: data.typebot,
|
||||
prefilledVariables: {
|
||||
...data.prefilledVariables,
|
||||
remoteJid: data.remoteJid,
|
||||
pushName: data.pushName || data.prefilledVariables?.pushName || '',
|
||||
instanceName: instance.instanceName,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
const request = await axios.post(url, reqData);
|
||||
|
||||
@@ -318,37 +377,6 @@ export class TypebotService {
|
||||
}
|
||||
}
|
||||
|
||||
public async clearSessions(instance: InstanceDto, remoteJid: string) {
|
||||
const findTypebot = await this.find(instance);
|
||||
const sessions = (findTypebot.sessions as Session[]) ?? [];
|
||||
|
||||
const sessionWithRemoteJid = sessions.filter((session) => session.remoteJid === remoteJid);
|
||||
|
||||
if (sessionWithRemoteJid.length > 0) {
|
||||
sessionWithRemoteJid.forEach((session) => {
|
||||
sessions.splice(sessions.indexOf(session), 1);
|
||||
});
|
||||
|
||||
const typebotData = {
|
||||
enabled: findTypebot.enabled,
|
||||
url: findTypebot.url,
|
||||
typebot: findTypebot.typebot,
|
||||
expire: findTypebot.expire,
|
||||
keyword_finish: findTypebot.keyword_finish,
|
||||
delay_message: findTypebot.delay_message,
|
||||
unknown_message: findTypebot.unknown_message,
|
||||
listening_from_me: findTypebot.listening_from_me,
|
||||
sessions,
|
||||
};
|
||||
|
||||
this.create(instance, typebotData);
|
||||
|
||||
return sessions;
|
||||
}
|
||||
|
||||
return sessions;
|
||||
}
|
||||
|
||||
public async sendWAMessage(
|
||||
instance: InstanceDto,
|
||||
remoteJid: string,
|
||||
@@ -356,11 +384,16 @@ export class TypebotService {
|
||||
input: any[],
|
||||
clientSideActions: any[],
|
||||
) {
|
||||
processMessages(this.waMonitor.waInstances[instance.instanceName], messages, input, clientSideActions).catch(
|
||||
(err) => {
|
||||
console.error('Erro ao processar mensagens:', err);
|
||||
},
|
||||
);
|
||||
processMessages(
|
||||
this.waMonitor.waInstances[instance.instanceName],
|
||||
messages,
|
||||
input,
|
||||
clientSideActions,
|
||||
this.eventEmitter,
|
||||
applyFormatting,
|
||||
).catch((err) => {
|
||||
console.error('Erro ao processar mensagens:', err);
|
||||
});
|
||||
|
||||
function findItemAndGetSecondsToWait(array, targetId) {
|
||||
if (!array) return null;
|
||||
@@ -373,53 +406,67 @@ export class TypebotService {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function processMessages(instance, messages, input, clientSideActions) {
|
||||
for (const message of messages) {
|
||||
const wait = findItemAndGetSecondsToWait(clientSideActions, message.id);
|
||||
function applyFormatting(element) {
|
||||
let text = '';
|
||||
|
||||
if (element.text) {
|
||||
text += element.text;
|
||||
}
|
||||
|
||||
if (
|
||||
element.children &&
|
||||
(element.type === 'p' ||
|
||||
element.type === 'a' ||
|
||||
element.type === 'inline-variable' ||
|
||||
element.type === 'variable')
|
||||
) {
|
||||
for (const child of element.children) {
|
||||
text += applyFormatting(child);
|
||||
}
|
||||
}
|
||||
|
||||
let formats = '';
|
||||
|
||||
if (element.bold) {
|
||||
formats += '*';
|
||||
}
|
||||
|
||||
if (element.italic) {
|
||||
formats += '_';
|
||||
}
|
||||
|
||||
if (element.underline) {
|
||||
formats += '~';
|
||||
}
|
||||
|
||||
let formattedText = `${formats}${text}${formats.split('').reverse().join('')}`;
|
||||
|
||||
if (element.url) {
|
||||
formattedText = element.children[0]?.text ? `[${formattedText}]\n(${element.url})` : `${element.url}`;
|
||||
}
|
||||
|
||||
return formattedText;
|
||||
}
|
||||
|
||||
async function processMessages(instance, messages, input, clientSideActions, eventEmitter, applyFormatting) {
|
||||
for (const message of messages) {
|
||||
if (message.type === 'text') {
|
||||
let formattedText = '';
|
||||
|
||||
let linkPreview = false;
|
||||
|
||||
for (const richText of message.content.richText) {
|
||||
for (const element of richText.children) {
|
||||
let text = '';
|
||||
if (element.text) {
|
||||
text = element.text;
|
||||
}
|
||||
|
||||
if (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 += applyFormatting(element);
|
||||
}
|
||||
formattedText += '\n';
|
||||
}
|
||||
|
||||
formattedText = formattedText.replace(/\n$/, '');
|
||||
formattedText = formattedText.replace(/\*\*/g, '').replace(/__/, '').replace(/~~/, '').replace(/\n$/, '');
|
||||
|
||||
await instance.textMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000,
|
||||
delay: instance.localTypebot.delay_message || 1000,
|
||||
presence: 'composing',
|
||||
linkPreview: linkPreview,
|
||||
},
|
||||
textMessage: {
|
||||
text: formattedText,
|
||||
@@ -431,7 +478,7 @@ export class TypebotService {
|
||||
await instance.mediaMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000,
|
||||
delay: instance.localTypebot.delay_message || 1000,
|
||||
presence: 'composing',
|
||||
},
|
||||
mediaMessage: {
|
||||
@@ -445,7 +492,7 @@ export class TypebotService {
|
||||
await instance.mediaMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000,
|
||||
delay: instance.localTypebot.delay_message || 1000,
|
||||
presence: 'composing',
|
||||
},
|
||||
mediaMessage: {
|
||||
@@ -459,7 +506,7 @@ export class TypebotService {
|
||||
await instance.audioWhatsapp({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000,
|
||||
delay: instance.localTypebot.delay_message || 1000,
|
||||
presence: 'recording',
|
||||
encoding: true,
|
||||
},
|
||||
@@ -468,6 +515,12 @@ export class TypebotService {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const wait = findItemAndGetSecondsToWait(clientSideActions, message.id);
|
||||
|
||||
if (wait) {
|
||||
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
|
||||
}
|
||||
}
|
||||
|
||||
if (input) {
|
||||
@@ -485,15 +538,19 @@ export class TypebotService {
|
||||
await instance.textMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: 1200,
|
||||
delay: instance.localTypebot.delay_message || 1000,
|
||||
presence: 'composing',
|
||||
linkPreview: false,
|
||||
},
|
||||
textMessage: {
|
||||
text: formattedText,
|
||||
},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
eventEmitter.emit('typebot:end', {
|
||||
instance: instance,
|
||||
remoteJid: remoteJid,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -582,12 +639,12 @@ export class TypebotService {
|
||||
let urlTypebot: string;
|
||||
let reqData: {};
|
||||
if (version === 'latest') {
|
||||
urlTypebot = `${data.url}/api/v1/sessions/${data.sessionId}/continueChat`;
|
||||
urlTypebot = `${url}/api/v1/sessions/${data.sessionId}/continueChat`;
|
||||
reqData = {
|
||||
message: content,
|
||||
};
|
||||
} else {
|
||||
urlTypebot = `${data.url}/api/v1/sendMessage`;
|
||||
urlTypebot = `${url}/api/v1/sendMessage`;
|
||||
reqData = {
|
||||
message: content,
|
||||
sessionId: data.sessionId,
|
||||
@@ -654,7 +711,7 @@ export class TypebotService {
|
||||
}
|
||||
|
||||
if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) {
|
||||
sessions.splice(sessions.indexOf(session), 1);
|
||||
const newSessions = await this.clearSessions(instance, remoteJid);
|
||||
|
||||
const typebotData = {
|
||||
enabled: findTypebot.enabled,
|
||||
@@ -665,7 +722,7 @@ export class TypebotService {
|
||||
delay_message: delay_message,
|
||||
unknown_message: unknown_message,
|
||||
listening_from_me: listening_from_me,
|
||||
sessions,
|
||||
sessions: newSessions,
|
||||
};
|
||||
|
||||
this.create(instance, typebotData);
|
||||
@@ -679,12 +736,12 @@ export class TypebotService {
|
||||
let urlTypebot: string;
|
||||
let reqData: {};
|
||||
if (version === 'latest') {
|
||||
urlTypebot = `${data.url}/api/v1/sessions/${data.sessionId}/continueChat`;
|
||||
urlTypebot = `${url}/api/v1/sessions/${data.sessionId}/continueChat`;
|
||||
reqData = {
|
||||
message: content,
|
||||
};
|
||||
} else {
|
||||
urlTypebot = `${data.url}/api/v1/sendMessage`;
|
||||
urlTypebot = `${url}/api/v1/sendMessage`;
|
||||
reqData = {
|
||||
message: content,
|
||||
sessionId: data.sessionId,
|
||||
@@ -746,7 +803,7 @@ export class TypebotService {
|
||||
}
|
||||
|
||||
if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) {
|
||||
sessions.splice(sessions.indexOf(session), 1);
|
||||
const newSessions = await this.clearSessions(instance, remoteJid);
|
||||
|
||||
const typebotData = {
|
||||
enabled: findTypebot.enabled,
|
||||
@@ -757,7 +814,7 @@ export class TypebotService {
|
||||
delay_message: delay_message,
|
||||
unknown_message: unknown_message,
|
||||
listening_from_me: listening_from_me,
|
||||
sessions,
|
||||
sessions: newSessions,
|
||||
};
|
||||
|
||||
this.create(instance, typebotData);
|
||||
|
||||
3245
src/whatsapp/services/whatsapp.baileys.service.ts
Normal file
3245
src/whatsapp/services/whatsapp.baileys.service.ts
Normal file
File diff suppressed because it is too large
Load Diff
1272
src/whatsapp/services/whatsapp.business.service.ts
Normal file
1272
src/whatsapp/services/whatsapp.business.service.ts
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,8 @@ import { AuthenticationState, WAConnectionState } from '@whiskeysockets/baileys'
|
||||
|
||||
export enum Events {
|
||||
APPLICATION_STARTUP = 'application.startup',
|
||||
INSTANCE_CREATE = 'instance.create',
|
||||
INSTANCE_DELETE = 'instance.delete',
|
||||
QRCODE_UPDATED = 'qrcode.updated',
|
||||
CONNECTION_UPDATE = 'connection.update',
|
||||
STATUS_INSTANCE = 'status.instance',
|
||||
@@ -26,6 +28,10 @@ export enum Events {
|
||||
TYPEBOT_START = 'typebot.start',
|
||||
TYPEBOT_CHANGE_STATUS = 'typebot.change-status',
|
||||
CHAMA_AI_ACTION = 'chama-ai.action',
|
||||
LABELS_EDIT = 'labels.edit',
|
||||
LABELS_ASSOCIATION = 'labels.association',
|
||||
CREDS_UPDATE = 'creds.update',
|
||||
MESSAGING_HISTORY_SET = 'messaging-history.set',
|
||||
}
|
||||
|
||||
export declare namespace wa {
|
||||
@@ -63,6 +69,9 @@ export declare namespace wa {
|
||||
number?: string;
|
||||
reopen_conversation?: boolean;
|
||||
conversation_pending?: boolean;
|
||||
import_contacts?: boolean;
|
||||
import_messages?: boolean;
|
||||
days_limit_import_messages?: number;
|
||||
};
|
||||
|
||||
export type LocalSettings = {
|
||||
@@ -72,6 +81,7 @@ export declare namespace wa {
|
||||
always_online?: boolean;
|
||||
read_messages?: boolean;
|
||||
read_status?: boolean;
|
||||
sync_full_history?: boolean;
|
||||
};
|
||||
|
||||
export type LocalWebsocket = {
|
||||
@@ -107,9 +117,17 @@ export declare namespace wa {
|
||||
sessions?: Session[];
|
||||
};
|
||||
|
||||
type Proxy = {
|
||||
host?: string;
|
||||
port?: string;
|
||||
protocol?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
};
|
||||
|
||||
export type LocalProxy = {
|
||||
enabled?: boolean;
|
||||
proxy?: string;
|
||||
proxy?: Proxy;
|
||||
};
|
||||
|
||||
export type LocalChamaai = {
|
||||
@@ -120,6 +138,12 @@ export declare namespace wa {
|
||||
answerByAudio?: boolean;
|
||||
};
|
||||
|
||||
export type LocalIntegration = {
|
||||
integration?: string;
|
||||
number?: string;
|
||||
token?: string;
|
||||
};
|
||||
|
||||
export type StateConnection = {
|
||||
instance?: string;
|
||||
state?: WAConnectionState | 'refused';
|
||||
@@ -137,3 +161,8 @@ export const MessageSubtype = [
|
||||
'viewOnceMessage',
|
||||
'viewOnceMessageV2',
|
||||
];
|
||||
|
||||
export const Integration = {
|
||||
WHATSAPP_BUSINESS: 'WHATSAPP-BUSINESS',
|
||||
WHATSAPP_BAILEYS: 'WHATSAPP-BAILEYS',
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { configService } from '../config/env.config';
|
||||
import { eventEmitter } from '../config/event.config';
|
||||
import { Logger } from '../config/logger.config';
|
||||
import { CacheEngine } from '../libs/cacheengine';
|
||||
import { dbserver } from '../libs/db.connect';
|
||||
import { RedisCache } from '../libs/redis.client';
|
||||
import { ChamaaiController } from './controllers/chamaai.controller';
|
||||
@@ -8,6 +9,7 @@ import { ChatController } from './controllers/chat.controller';
|
||||
import { ChatwootController } from './controllers/chatwoot.controller';
|
||||
import { GroupController } from './controllers/group.controller';
|
||||
import { InstanceController } from './controllers/instance.controller';
|
||||
import { LabelController } from './controllers/label.controller';
|
||||
import { ProxyController } from './controllers/proxy.controller';
|
||||
import { RabbitmqController } from './controllers/rabbitmq.controller';
|
||||
import { SendMessageController } from './controllers/sendMessage.controller';
|
||||
@@ -22,6 +24,7 @@ import {
|
||||
ChatModel,
|
||||
ChatwootModel,
|
||||
ContactModel,
|
||||
IntegrationModel,
|
||||
MessageModel,
|
||||
MessageUpModel,
|
||||
ProxyModel,
|
||||
@@ -32,11 +35,14 @@ import {
|
||||
WebhookModel,
|
||||
WebsocketModel,
|
||||
} from './models';
|
||||
import { LabelModel } from './models/label.model';
|
||||
import { AuthRepository } from './repository/auth.repository';
|
||||
import { ChamaaiRepository } from './repository/chamaai.repository';
|
||||
import { ChatRepository } from './repository/chat.repository';
|
||||
import { ChatwootRepository } from './repository/chatwoot.repository';
|
||||
import { ContactRepository } from './repository/contact.repository';
|
||||
import { IntegrationRepository } from './repository/integration.repository';
|
||||
import { LabelRepository } from './repository/label.repository';
|
||||
import { MessageRepository } from './repository/message.repository';
|
||||
import { MessageUpRepository } from './repository/messageUp.repository';
|
||||
import { ProxyRepository } from './repository/proxy.repository';
|
||||
@@ -48,8 +54,10 @@ import { TypebotRepository } from './repository/typebot.repository';
|
||||
import { WebhookRepository } from './repository/webhook.repository';
|
||||
import { WebsocketRepository } from './repository/websocket.repository';
|
||||
import { AuthService } from './services/auth.service';
|
||||
import { CacheService } from './services/cache.service';
|
||||
import { ChamaaiService } from './services/chamaai.service';
|
||||
import { ChatwootService } from './services/chatwoot.service';
|
||||
import { IntegrationService } from './services/integration.service';
|
||||
import { WAMonitoringService } from './services/monitor.service';
|
||||
import { ProxyService } from './services/proxy.service';
|
||||
import { RabbitmqService } from './services/rabbitmq.service';
|
||||
@@ -72,9 +80,11 @@ const proxyRepository = new ProxyRepository(ProxyModel, configService);
|
||||
const chamaaiRepository = new ChamaaiRepository(ChamaaiModel, configService);
|
||||
const rabbitmqRepository = new RabbitmqRepository(RabbitmqModel, configService);
|
||||
const sqsRepository = new SqsRepository(SqsModel, configService);
|
||||
const integrationRepository = new IntegrationRepository(IntegrationModel, configService);
|
||||
const chatwootRepository = new ChatwootRepository(ChatwootModel, configService);
|
||||
const settingsRepository = new SettingsRepository(SettingsModel, configService);
|
||||
const authRepository = new AuthRepository(AuthModel, configService);
|
||||
const authRepository = new AuthRepository(AuthModel, IntegrationModel, configService);
|
||||
const labelRepository = new LabelRepository(LabelModel, configService);
|
||||
|
||||
export const repository = new RepositoryBroker(
|
||||
messageRepository,
|
||||
@@ -90,24 +100,28 @@ export const repository = new RepositoryBroker(
|
||||
typebotRepository,
|
||||
proxyRepository,
|
||||
chamaaiRepository,
|
||||
integrationRepository,
|
||||
authRepository,
|
||||
labelRepository,
|
||||
configService,
|
||||
dbserver?.getClient(),
|
||||
);
|
||||
|
||||
export const cache = new RedisCache();
|
||||
|
||||
export const waMonitor = new WAMonitoringService(eventEmitter, configService, repository, cache);
|
||||
const chatwootCache = new CacheService(new CacheEngine(configService, ChatwootService.name).getEngine());
|
||||
|
||||
export const waMonitor = new WAMonitoringService(eventEmitter, configService, repository, cache, chatwootCache);
|
||||
|
||||
const authService = new AuthService(configService, waMonitor, repository);
|
||||
|
||||
const typebotService = new TypebotService(waMonitor, configService);
|
||||
const typebotService = new TypebotService(waMonitor, configService, eventEmitter);
|
||||
|
||||
export const typebotController = new TypebotController(typebotService);
|
||||
|
||||
const webhookService = new WebhookService(waMonitor);
|
||||
|
||||
export const webhookController = new WebhookController(webhookService);
|
||||
export const webhookController = new WebhookController(webhookService, waMonitor);
|
||||
|
||||
const websocketService = new WebsocketService(waMonitor);
|
||||
|
||||
@@ -115,7 +129,7 @@ export const websocketController = new WebsocketController(websocketService);
|
||||
|
||||
const proxyService = new ProxyService(waMonitor);
|
||||
|
||||
export const proxyController = new ProxyController(proxyService);
|
||||
export const proxyController = new ProxyController(proxyService, waMonitor);
|
||||
|
||||
const chamaaiService = new ChamaaiService(waMonitor, configService);
|
||||
|
||||
@@ -129,7 +143,9 @@ const sqsService = new SqsService(waMonitor);
|
||||
|
||||
export const sqsController = new SqsController(sqsService);
|
||||
|
||||
const chatwootService = new ChatwootService(waMonitor, configService, repository);
|
||||
const integrationService = new IntegrationService(waMonitor);
|
||||
|
||||
const chatwootService = new ChatwootService(waMonitor, configService, repository, chatwootCache);
|
||||
|
||||
export const chatwootController = new ChatwootController(chatwootService, configService, repository);
|
||||
|
||||
@@ -148,13 +164,15 @@ export const instanceController = new InstanceController(
|
||||
settingsService,
|
||||
websocketService,
|
||||
rabbitmqService,
|
||||
proxyService,
|
||||
sqsService,
|
||||
typebotService,
|
||||
integrationService,
|
||||
cache,
|
||||
chatwootCache,
|
||||
);
|
||||
export const sendMessageController = new SendMessageController(waMonitor);
|
||||
export const chatController = new ChatController(waMonitor);
|
||||
export const groupController = new GroupController(waMonitor);
|
||||
export const labelController = new LabelController(waMonitor);
|
||||
|
||||
logger.info('Module - ON');
|
||||
|
||||
@@ -18,5 +18,9 @@
|
||||
"incremental": true,
|
||||
"noImplicitAny": false
|
||||
},
|
||||
"exclude": ["node_modules", "./test", "./dist", "./prisma"]
|
||||
"exclude": ["node_modules", "./test", "./dist", "./prisma"],
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"src/**/*.json"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user