Compare commits

..

50 Commits
1.8.0 ... 1.8.2

Author SHA1 Message Date
Davidson Gomes
b8fe5603fd Merge branch 'release/1.8.2' 2024-07-03 16:10:38 -03:00
Davidson Gomes
762453c0e3 chore: Add workflow to publish Docker image
A new untracked file '.github/workflows/publish\_docker\_image\_latest.yml' was added. This workflow will handle the process of publishing the latest Docker image. This change allows for easier and more automated deployment of the Node.js project.
2024-07-03 16:10:23 -03:00
Davidson Gomes
af1b5caa29 Merge tag '1.8.2' into develop
* Corretion in globall rabbitmq queue name
* Improvement in the use of mongodb database for credentials
* Fixed base64 in webhook for documentWithCaption
* Fixed Generate pairing code
2024-07-03 16:09:25 -03:00
Davidson Gomes
50b3379d88 Merge branch 'release/1.8.2' 2024-07-03 16:09:17 -03:00
Davidson Gomes
c9ac5984ec Merge tag '1.8.2' into develop
* Corretion in globall rabbitmq queue name
* Improvement in the use of mongodb database for credentials
* Fixed base64 in webhook for documentWithCaption
* Fixed Generate pairing code
2024-07-03 16:06:24 -03:00
Davidson Gomes
29ba63d621 Merge branch 'release/1.8.2' 2024-07-03 16:06:16 -03:00
Davidson Gomes
2ce64af502 Merge tag '1.8.2' into develop
* Corretion in globall rabbitmq queue name
* Improvement in the use of mongodb database for credentials
* Fixed base64 in webhook for documentWithCaption
* Fixed Generate pairing code
2024-07-03 13:51:36 -03:00
Davidson Gomes
28c517a3d5 Merge branch 'release/1.8.2' 2024-07-03 13:51:28 -03:00
Davidson Gomes
18ebe27bc3 "chore: Updated package and documentation versions to v1.8.2"
Explanation:
This commit updates the package and documentation versions to v1.8.2. The package.json and swagger.yaml files were modified accordingly. These changes are mainly for maintenance purposes and do not affect the functionality of the application.
2024-07-03 13:51:16 -03:00
Davidson Gomes
0f7a39a08f chore(changelog): Update changelog to v1.8.2
This commit updates the changelog to reflect the release of version 1.8.2. The date of the release has been updated in the header of the changelog. No functional changes were made in this release.

Modified: CHANGELOG.md
2024-07-03 13:50:30 -03:00
Davidson Gomes
a8121d7fe6 fix: Correction in global RabbitMQ queue name
Fix global RabbitMQ queue name in `channel.service.ts` and update CHANGELOG.md.
The queue name has been changed from `transformedWe` to `event`.
This fix prevents queue errors and ensures correct functionality of inter-service communication.
2024-07-03 13:47:25 -03:00
Davidson Gomes
b63b7b0b7b fix: Generate pairing code and update Baileys repository
Update package.json to use the new Baileys repository and modify the Whatsapp Baileys service to generate a pairing code. This change fixes the issue with the previous Baileys repository and improves the pairing process for Whatsapp.

Changes:
- Update package.json to use the new Baileys repository
- Modify Whatsapp Baileys service to generate a pairing code
- Fix issue with previous Baileys repository
- Improve pairing process for Whatsapp
2024-07-03 13:45:05 -03:00
Davidson Gomes
14ea5d959f Merge pull request #662 from drauber/cwId_missing
Add -cwId- when findByName
2024-06-26 10:32:09 -03:00
Douglas Rauber at Nitro
86b2999fcf Add -cwId- when findByName 2024-06-24 10:37:54 -03:00
Davidson Gomes
f5bd11fc19 feat: add support for documentWithCaptionMessage in WhatsApp Baileys service
Modified whatsapp.baileys.service.ts to include handling for documentWithCaptionMessage. This change ensures that messages with documents having captions are properly processed, enhancing the service's message handling capabilities. No impact on existing functionalities.
2024-06-18 11:34:47 -03:00
Davidson Gomes
c060d330de fix: normalize event names in channel.service.ts
Normalized event names by replacing underscores with dots and converting to lowercase. This ensures consistent naming conventions and prevents potential issues with queue bindings.
2024-06-18 11:32:50 -03:00
Davidson Gomes
1e320f7904 fix: update use-multi-file-auth-state-db.ts to handle edge cases
Refactored the use-multi-file-auth-state-db.ts to better handle edge cases in multi-file authentication state management. This change improves reliability and ensures more robust error handling, reducing potential issues during authentication.
2024-06-17 11:19:22 -03:00
Davidson Gomes
975b3ee528 fix: reorder imports to resolve linting issues
Reordered imports in multiple files to resolve linting issues and improve code readability. This change does not impact the functionality but ensures the code adheres to the project's coding standards.
2024-06-16 17:19:45 -03:00
Davidson Gomes
5b47bc9ef0 feat: update dependencies and improve caching logic
Updated package.json to include latest dependencies. Enhanced caching logic in cache.service.ts and rediscache.ts for better performance. Improved DTOs in chat.dto.ts, instance.dto.ts, and sendMessage.dto.ts for more robust data handling. Refined instance.controller.ts and chatwoot.service.ts to streamline API integrations. Adjusted authentication state management in use-multi-file-auth-state-db.ts, use-multi-file-auth-state-provider-files.ts, and use-multi-file-auth-state-redis-db.ts. These changes aim to optimize the system's performance and reliability.
2024-06-16 17:17:55 -03:00
Davidson Gomes
053a7981d1 fix: update default values for WA_BUSINESS environment variables
Updated default values for WA_BUSINESS_TOKEN_WEBHOOK, WA_BUSINESS_URL, and WA_BUSINESS_VERSION in env.config.ts to 'evolution', 'https://graph.facebook.com', and 'v19.0' respectively. This change ensures that the application uses more appropriate defaults if environment variables are not set, improving reliability and consistency.
2024-06-13 18:51:18 -03:00
Davidson Gomes
5f1f025d65 Merge tag '1.8.1' into develop
v
2024-06-12 19:49:40 -03:00
Davidson Gomes
dfb003fd72 Merge branch 'release/1.8.1' 2024-06-12 19:49:37 -03:00
Davidson Gomes
5239e431c2 feat: update server and monitor services
Updated server.module.ts and monitor.service.ts to improve service initialization and monitoring logic. Modified main.ts to integrate changes. This enhances the application's performance and reliability.
2024-06-12 19:48:56 -03:00
Davidson Gomes
53cc6132f5 Merge tag '1.8.1' into develop
v
2024-06-12 19:03:36 -03:00
Davidson Gomes
0bc1b78db9 Merge branch 'release/1.8.1' 2024-06-12 19:03:30 -03:00
Davidson Gomes
2a9412c81a chore: adjust socket configuration and update baileys version
Updated package.json to include the latest version of baileys for improved functionality. Modified whatsapp.baileys.service.ts to adjust socket configuration, enhancing the stability and performance of the service.
2024-06-12 19:02:36 -03:00
Davidson Gomes
f707cf4109 Merge tag '1.8.1' into develop
v
2024-06-09 14:22:20 -03:00
Davidson Gomes
410cfc8bcb Merge branch 'release/1.8.1' 2024-06-09 14:22:17 -03:00
Davidson Gomes
f1a3fd872f git actions v2 2024-06-09 14:20:48 -03:00
Davidson Gomes
fff0ca5c79 Merge tag '1.8.1' into develop
v
2024-06-08 21:33:12 -03:00
Davidson Gomes
bd069200ea Merge branch 'release/1.8.1' 2024-06-08 21:33:02 -03:00
Davidson Gomes
c898f1e62a v1.8.1 2024-06-08 21:32:50 -03:00
Davidson Gomes
2002e1c38d changelog 2024-06-08 21:31:02 -03:00
Davidson Gomes
1f817df5f6 fix: Correction of variables breaking lines in typebot 2024-06-08 21:27:58 -03:00
Davidson Gomes
8eced6c575 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2024-06-05 21:48:53 -03:00
Davidson Gomes
afcf6eab71 cacheGroupMetadata 2024-06-05 21:48:37 -03:00
Davidson Gomes
9633412ef6 cacheGroupMetadata 2024-06-05 21:39:33 -03:00
Davidson Gomes
8034e7f587 cacheGroupMetadata 2024-06-05 21:25:55 -03:00
Davidson Gomes
5be01e1c18 Merge pull request #629 from cark7/develop
fix: added validation when sending gif image
2024-06-04 16:59:36 -03:00
Carlos Chinchilla
6371773416 fix: added validation when sending gif image 2024-06-04 15:14:02 -03:00
Davidson Gomes
52230edc5c session worker compatibility 2024-06-03 19:06:00 -03:00
Davidson Gomes
dd123c6a99 Adjust to repository from session worker 2024-06-01 19:47:43 -03:00
Davidson Gomes
5b2a0fdcb1 Adjust to repository from session worker 2024-06-01 19:29:37 -03:00
Davidson Gomes
9354af3bc7 Adjust to repository from session worker 2024-06-01 19:20:59 -03:00
Davidson Gomes
f48f331d43 New method of saving sessions to a file using worker 2024-06-01 13:28:29 -03:00
Davidson Gomes
696261d749 New method of saving sessions to a file using worker 2024-06-01 09:54:56 -03:00
Davidson Gomes
0e9d036fe0 Merge pull request #622 from jrCleber/main
Adiciona templates para Relatório de Bug e Solicitação de Recursos
2024-05-29 10:34:23 -03:00
Cleber Wilson
8e19fe2fb5 labels para filtros adicionada 2024-05-29 10:33:23 -03:00
Cleber Wilson
861b690217 Adiciona templates para Relatório de Bug e Solicitação de Recursos
- Cria um template para Relatório de Bug com os seguintes campos:
  - Termos de aceite
  - Descrição detalhada do problema
  - Expectativa versus observação
  - Capturas de tela/vídeos
  - Versão da API
  - Ambiente
  - Especificações adicionais do ambiente
  - Logs (se aplicável)
  - Notas adicionais

- Cria um template para Solicitação de Recursos com os seguintes campos:
  - Termos de aceite
  - Tipo de recurso
  - Motivação para a solicitação
  - Exemplos de uso
  - Ideias de desenvolvimento
  - Notas adicionais
2024-05-29 10:08:42 -03:00
Davidson Gomes
f7c9541f5e Merge tag '1.8.0' into develop
v
2024-05-27 16:36:08 -03:00
36 changed files with 1278 additions and 466 deletions

View File

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

View File

@@ -0,0 +1,107 @@
name: Bug Report
description: Create a report to help us improve.
labels:
- bug
- en
# - help wanted
# Automatically assign the issue to:
# assignees: DavidsonGomes
body:
- type: checkboxes
id: terms
attributes:
label: Welcome!
description: |
The issue tracker is only for reporting bugs and feature requests.
For user-related support questions, please visit:
- [Discord](https://evolution-api.com/discord)
- [WhatsApp Group](https://evolution-api.com/whatsapp)
<br/>
**DO NOT OPEN AN ISSUE FOR GENERAL SUPPORT QUESTIONS.**
options:
- label: Yes, I have searched for similar issues on [GitHub](https://github.com/EvolutionAPI/evolution-api/issues) and found none.
required: true
- type: textarea
attributes:
label: What did you do?
description: |
How to write a good bug report?
- Respect the issue template as much as possible.
- The title should be short and descriptive.
- Explain the conditions that led you to report this issue: the context.
- The context should lead to something, an idea or problem you are facing.
- Be clear and concise.
- Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown)
placeholder: Describe the problem you encountered in detail.
validations:
required: true
- type: textarea
attributes:
label: What did you expect?
placeholder: Describe what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: What did you observe instead of what you expected?
placeholder: Explain what actually happens when you follow the steps above.
validations:
required: true
- type: textarea
attributes:
label: Screenshots/Videos
placeholder: |
If possible, add screenshots or videos that illustrate the problem. This can be extremely helpful in understanding the issue better.
- type: textarea
attributes:
label: Which version of the API are you using?
description: |
Enter the version number found in the `version` property of the **package.json**.
placeholder: Paste the version here.
validations:
required: true
- type: dropdown
id: select
attributes:
label: What is your environment?
options:
- Windows
- Linux
- Mac
- Docker
- Other
validations:
required: true
- type: textarea
attributes:
label: Other environment specifications
placeholder: 'Hardware/Software: [e.g., CPU, GPU]'
validations:
required: false
- type: textarea
attributes:
label: If applicable, paste the log output
description: |
Please attach any logs that might be related to the issue.
If the logs contain sensitive information, consider sending them privately to one of the project maintainers.
placeholder: Paste the application log here.
validations:
required: false
- type: textarea
attributes:
label: Additional Notes
description: Include any other information you think might be useful to understand or resolve the bug.
validations:
required: false

View File

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

View File

@@ -0,0 +1,86 @@
name: Feature Request
description: Suggest ideas for the project.
labels:
- enhancement
- en
# Automatically assign the issue to:
# assignees: DavidsonGomes
body:
- type: checkboxes
id: terms
attributes:
label: Welcome!
description: '**DO NOT OPEN FOR GENERAL SUPPORT QUESTIONS.**'
options:
- label: Yes, I have searched for similar requests on [GitHub](https://github.com/code-chat-br/whatsapp-api/issues) and found none.
required: true
- type: dropdown
attributes:
label: What type of feature?
description: |
How to write a good feature request?
- Respect the issue template as much as possible.
- The title should be short and descriptive.
- Explain the conditions that led you to suggest this feature: the context.
- The context should lead to something, an idea or problem you are facing.
- Be clear and concise.
- Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown)
options:
- Integration
- Functionality
- Endpoint
- Database adjustment
- Other
validations:
required: true
- type: textarea
attributes:
label: What is the motivation for the request?
description: |
What problem does the feature seek to solve?
Clearly and in detail describe the functionality you want to be implemented.
Explain how it fits into the context of the project.
placeholder: Detailed description
validations:
required: true
- type: textarea
attributes:
label: Usage Examples
description: |
Provide specific examples of how this functionality could be used.
This can include scenarios or use cases where the feature would be particularly beneficial.
placeholder: text - image - video - flowcharts
validations:
required: false
- type: textarea
attributes:
label: How should the feature be developed?
description: |
Should it be inserted directly into the code?
Should it be built as a different application that acts as an extension of the API?
For example: a `worker`?
Discuss how this new functionality could impact other parts of the project, if applicable.
---
If you have ideas on how this functionality could be implemented, please share them here.
This is not mandatory, but it can be helpful for the development team.
placeholder: Insert feature ideas here
validations:
required: false
- type: textarea
attributes:
label: Additional Notes
description: Any other information you believe is relevant to your request.
placeholder: Insert your observations here.
validations:
required: false

View File

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

View File

@@ -0,0 +1,108 @@
name: Relatório de bug
description: Crie um relatório para nos ajudar a melhorar.
labels:
- bug
- pt-br
# - help wanted
# Atrubuir automaticamente a issue a:
# assignees: DavidsonGomes
body:
- type: checkboxes
id: termos
attributes:
label: Bem-vido!
description: |
O rastreador de problemas é apenas para relatar bugs e solicitações de recursos.
Para perguntas de suporte relacionadas ao usuário final, acesse:
- [Discord](https://evolution-api.com/discord)
- [Grupo do WhatsApp](https://evolution-api.com/whatsapp)
<br/>
**NÃO ABRA UM PROBLEMA PARA PERGUNTAS GERAIS DE SUPORTE.**
options:
- label: Sim, pesquisei problemas semelhantes no [GitHub](https://github.com/EvolutionAPI/evolution-api/issues) e não encontrei nenhum.
required: true
- type: textarea
attributes:
label: O que você fez?
description: |
Como escrever um bom relatório de bug?
- Respeite o modelo de problema tanto quanto possível.
- O título deve ser curto e descritivo.
- Explique as condições que o levaram a reportar este problema: o contexto.
- O contexto deve levar a algo, ideia ou problema que você está enfrentando.
- Seja claro e conciso.
- Formate suas mensagens para ajudar o leitor a se concentrar no que importa e entender a estrutura da sua mensagem, use [sintaxe Markdown](https://help.github.com/articles/github-flavored-markdown)
placeholder: Descreva detalhadamente o problema que você encontrou.
validations:
required: true
- type: textarea
attributes:
label: O que você esperava?
placeholder: Descreva o que você esperava que acontecesse.
validations:
required: true
- type: textarea
attributes:
label: O que vc observou ao invés do que esperava?
placeholder: Explique o que realmente acontece quando você segue os passos acima.
validations:
required: true
- type: textarea
attributes:
label: Capturas de Tela/Vídeos
placeholder: |
Se possível, adicione capturas de tela ou vídeos que ilustrem o problema. Isso pode ser extremamente útil para entender melhor o problema.
- type: textarea
attributes:
label: Qual versão da API você está usando?
description: |
Insira o número da sua versão que está na propriedade `version` do **package.json**.
placeholder: Cole aqui a versão.
validations:
required: true
- type: dropdown
id: select
attributes:
label: Qual é o seu ambiente?
options:
- Windows
- Linux
- Mac
- Docker
- Outro
validations:
required: true
- type: textarea
attributes:
label: Outras expecificações do ambiente
placeholder: 'Hardware/Software: [ex: CPU, GPU]'
validations:
required: false
- type: textarea
attributes:
label: Se aplicável, cole a saída do log
description: |
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.
placeholder: Cole aqui o log da aplicação
validations:
required: false
- type: textarea
attributes:
label: Notas Adicionais
description: Inclua aqui qualquer outra informação que você ache que possa ser útil para entender ou resolver o bug.
validations:
required: false

View File

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

View File

@@ -0,0 +1,86 @@
name: Solicitação de recursos
description: Sugira ideias para o projeto.
labels:
- enhancement
- pt-br
# Automatically assign the issue to:
# assignees: DavidsonGomes
body:
- type: checkboxes
id: termos
attributes:
label: Bem-vindo!
description: '**NÃO ABRA PARA PERGUNTAS GERAIS DE SUPORTE.**'
options:
- label: Sim, pesquisei solicitações semelhantes no [GitHub](https://github.com/code-chat-br/whatsapp-api/issues) e não encontrei nenhum.
required: true
- type: dropdown
attributes:
label: Qual tipo de recurso?
description: |
Como escrever uma boa solicitação de bug?
- Respeite o modelo de problema tanto quanto possível.
- O título deve ser curto e descritivo.
- Explique as condições que o levaram a reportar este problema: o contexto.
- O contexto deve levar a algo, ideia ou problema que você está enfrentando.
- Seja claro e conciso.
- Formate suas mensagens para ajudar o leitor a se concentrar no que importa e entender a estrutura da sua mensagem, use [sintaxe Markdown](https://help.github.com/articles/github-flavored-markdown)
options:
- Integração
- Funcionalidade
- Endpoint
- Ajuste de banco de dados
- Outro
validations:
required: true
- type: textarea
attributes:
label: Qual a motivação para a solicitação?
description: |
Qual problema o recurso busca resolver?
Descreva claramente e em detalhes a funcionalidade que você deseja que seja implementada.
Explique como isso se encaixa no contexto do projeto.
placeholder: Descrição detalhada
validations:
required: true
- type: textarea
attributes:
label: Exemplos de Uso
description: |
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.
placeholder: texto - imagem - video - fluxogramas
validations:
required: false
- type: textarea
attributes:
label: Como o recurso deve ser desenvolvido?
description: |
Deve ser inserido diretamente no código?
Deve ser construído uma aplicação diferente que atuará como uma extenção da api?
Por exemplo: um `worker`?
Discuta como essa nova funcionalidade poderia impactar outras partes do projeto, se aplicável.
---
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.
placeholder: Insira ideias para o recurso
validations:
required: false
- type: textarea
attributes:
label: Notas Adicionais
description: Qualquer outra informação que você acredita ser relevante para a sua solicitação.
placeholder: Insira aqui as sua observções.
validations:
required: false

View File

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

View File

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

View File

@@ -1,3 +1,22 @@
# 1.8.2 (2024-07-03 13:50)
### Fixed
* Corretion in globall rabbitmq queue name
* Improvement in the use of mongodb database for credentials
* Fixed base64 in webhook for documentWithCaption
* Fixed Generate pairing code
# 1.8.1 (2024-06-08 21:32)
### Feature
* New method of saving sessions to a file using worker, made in partnership with [codechat](https://github.com/code-chat-br/whatsapp-api)
### Fixed
* Correction of variables breaking lines in typebot
# 1.8.0 (2024-05-27 16:10)
### Feature
@@ -7,12 +26,14 @@
* Build in docker for linux/amd64, linux/arm64 platforms
### Fixed
* Correction in message formatting when generated by AI as markdown in typebot
* Security fix in fetch instance with client key when not connected to mongodb
# 1.7.5 (2024-05-21 08:50)
### Fixed
* Add merge_brazil_contacts function to solve nine digit in brazilian numbers
* Optimize ChatwootService method for updating contact
* Fix swagger auth
@@ -24,6 +45,7 @@
# 1.7.4 (2024-04-28 09:46)
### Fixed
* Adjusts in proxy on fetchAgent
* Recovering messages lost with redis cache
* Log when init redis cache service
@@ -34,6 +56,7 @@
# 1.7.3 (2024-04-18 12:07)
### Fixed
* Revert fix audio encoding
* Recovering messages lost with redis cache
* Adjusts in redis for save instances

View File

@@ -33,10 +33,7 @@ CLEAN_STORE_CHATS=true
# Permanent data storage
DATABASE_ENABLED=false
DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin &
readPreference=primary &
ssl=false &
directConnection=true
DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true
DATABASE_CONNECTION_DB_PREFIX_NAME=evdocker
# Choose the data you want to save in the application's database or store
@@ -137,7 +134,7 @@ CONFIG_SESSION_PHONE_NAME=Chrome
# Set qrcode display limit
QRCODE_LIMIT=30
QRCODE_COLOR=#198754
QRCODE_COLOR='#198754'
# old | latest
TYPEBOT_API_VERSION=latest

View File

@@ -1,6 +1,6 @@
{
"name": "evolution-api",
"version": "1.8.0",
"version": "1.8.2",
"description": "Rest api for communication with WhatsApp",
"main": "./dist/src/main.js",
"scripts": {
@@ -49,7 +49,7 @@
"amqplib": "^0.10.3",
"@aws-sdk/client-sqs": "^3.569.0",
"axios": "^1.6.5",
"@whiskeysockets/baileys": "^6.7.2",
"baileys": "github:EvolutionAPI/Baileys",
"class-validator": "^0.14.1",
"compression": "^1.7.4",
"cors": "^2.8.5",

View File

@@ -1,4 +1,4 @@
import { delay } from '@whiskeysockets/baileys';
import { delay } from 'baileys';
import { isURL } from 'class-validator';
import EventEmitter2 from 'eventemitter2';
import { v4 } from 'uuid';
@@ -12,6 +12,7 @@ import { RabbitmqService } from '../integrations/rabbitmq/services/rabbitmq.serv
import { SqsService } from '../integrations/sqs/services/sqs.service';
import { TypebotService } from '../integrations/typebot/services/typebot.service';
import { WebsocketService } from '../integrations/websocket/services/websocket.service';
import { ProviderFiles } from '../provider/sessions';
import { RepositoryBroker } from '../repository/repository.manager';
import { AuthService, OldToken } from '../services/auth.service';
import { CacheService } from '../services/cache.service';
@@ -42,7 +43,8 @@ export class InstanceController {
private readonly proxyService: ProxyController,
private readonly cache: CacheService,
private readonly chatwootCache: CacheService,
private readonly messagesLostCache: CacheService,
private readonly baileysCache: CacheService,
private readonly providerFiles: ProviderFiles,
) {}
private readonly logger = new Logger(InstanceController.name);
@@ -110,7 +112,8 @@ export class InstanceController {
this.repository,
this.cache,
this.chatwootCache,
this.messagesLostCache,
this.baileysCache,
this.providerFiles,
);
} else {
instance = new BaileysStartupService(
@@ -119,7 +122,8 @@ export class InstanceController {
this.repository,
this.cache,
this.chatwootCache,
this.messagesLostCache,
this.baileysCache,
this.providerFiles,
);
}
@@ -749,7 +753,7 @@ export class InstanceController {
this.logger.verbose('deleting instance: ' + instanceName);
try {
this.waMonitor.waInstances[instanceName].sendDataWebhook(Events.INSTANCE_DELETE, {
this.waMonitor.waInstances[instanceName]?.sendDataWebhook(Events.INSTANCE_DELETE, {
instanceName,
instanceId: (await this.repository.auth.find(instanceName))?.instanceId,
});

View File

@@ -1,4 +1,4 @@
import { proto, WAPresence, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '@whiskeysockets/baileys';
import { proto, WAPresence, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from 'baileys';
export class OnWhatsAppDto {
constructor(

View File

@@ -1,4 +1,4 @@
import { WAPresence } from '@whiskeysockets/baileys';
import { WAPresence } from 'baileys';
import { ProxyDto } from './proxy.dto';

View File

@@ -1,4 +1,4 @@
import { proto, WAPresence } from '@whiskeysockets/baileys';
import { proto, WAPresence } from 'baileys';
export class Quoted {
key: proto.IMessageKey;

View File

@@ -8,8 +8,8 @@ import ChatwootClient, {
inbox,
} from '@figuro/chatwoot-sdk';
import { request as chatwootRequest } from '@figuro/chatwoot-sdk/dist/core/request';
import { proto } from '@whiskeysockets/baileys';
import axios from 'axios';
import { proto } from 'baileys';
import FormData from 'form-data';
import { createReadStream, unlinkSync, writeFileSync } from 'fs';
import Jimp from 'jimp';
@@ -444,8 +444,7 @@ export class ChatwootService {
const searchableFields = this.getSearchableFields();
// eslint-disable-next-line prettier/prettier
if(contacts.length === 2 && this.getClientCwConfig().merge_brazil_contacts && query.startsWith('+55')){
if (contacts.length === 2 && this.getClientCwConfig().merge_brazil_contacts && query.startsWith('+55')) {
const contact = this.mergeBrazilianContacts(contacts);
if (contact) {
return contact;
@@ -736,7 +735,12 @@ export class ChatwootService {
}
this.logger.verbose('find inbox by name');
const findByName = inbox.payload.find((inbox) => inbox.name === this.getClientCwConfig().name_inbox);
let findByName = inbox.payload.find((inbox) => inbox.name === this.getClientCwConfig().name_inbox);
if (!findByName) {
findByName = inbox.payload.find((inbox) => inbox.name === this.getClientCwConfig().name_inbox.split('-cwId-')[0]);
}
if (!findByName) {
this.logger.warn('inbox not found');
@@ -1092,6 +1096,10 @@ export class ChatwootService {
return messageSent;
}
if (type === 'image' && parsedMedia && parsedMedia?.ext === '.gif') {
type = 'document';
}
this.logger.verbose('send media to instance: ' + waInstance.instanceName);
const data: SendMediaDto = {
number: number,

View File

@@ -1,5 +1,5 @@
import { inbox } from '@figuro/chatwoot-sdk';
import { proto } from '@whiskeysockets/baileys';
import { proto } from 'baileys';
import { InstanceDto } from '../../../../api/dto/instance.dto';
import { ChatwootRaw, ContactRaw, MessageRaw } from '../../../../api/models';

View File

@@ -525,10 +525,14 @@ export class TypebotService {
}
}
if (element.type === 'p') {
if (element.type === 'p' && element.type !== 'inline-variable') {
text = text.trim() + '\n';
}
if (element.type === 'inline-variable') {
text = text.trim();
}
if (element.type === 'ol') {
text =
'\n' +
@@ -582,6 +586,8 @@ export class TypebotService {
formattedText = formattedText.replace(/\*\*/g, '').replace(/__/, '').replace(/~~/, '').replace(/\n$/, '');
formattedText = formattedText.replace(/\n$/, '');
await instance.textMessage({
number: remoteJid.split('@')[0],
options: {

View File

@@ -0,0 +1,150 @@
import axios from 'axios';
import { execSync } from 'child_process';
import { Auth, ConfigService, ProviderSession } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
type ResponseSuccess = { status: number; data?: any };
type ResponseProvider = Promise<[ResponseSuccess?, Error?]>;
export class ProviderFiles {
constructor(private readonly configService: ConfigService) {
this.baseUrl = `http://${this.config.HOST}:${this.config.PORT}/session/${this.config.PREFIX}`;
this.globalApiToken = this.configService.get<Auth>('AUTHENTICATION').API_KEY.KEY;
}
private readonly logger = new Logger(ProviderFiles.name);
private baseUrl: string;
private globalApiToken: string;
private readonly config = Object.freeze(this.configService.get<ProviderSession>('PROVIDER'));
get isEnabled() {
return !!this.config?.ENABLED;
}
public async onModuleInit() {
if (this.config.ENABLED) {
const url = `http://${this.config.HOST}:${this.config.PORT}`;
try {
const response = await axios.options(url + '/ping');
if (response?.data != 'pong') {
throw new Error('Offline file provider.');
}
await axios.post(`${url}/session`, { group: this.config.PREFIX }, { headers: { apikey: this.globalApiToken } });
} catch (error) {
this.logger.error(['Failed to connect to the file server', error?.message, error?.stack]);
const pid = process.pid;
execSync(`kill -9 ${pid}`);
}
}
}
public async onModuleDestroy() {
//
}
public async create(instance: string): ResponseProvider {
try {
const response = await axios.post(
`${this.baseUrl}`,
{
instance,
},
{ headers: { apikey: this.globalApiToken } },
);
return [{ status: response.status, data: response?.data }];
} catch (error) {
return [
{
status: error?.response?.status,
data: error?.response?.data,
},
error,
];
}
}
public async write(instance: string, key: string, data: any): ResponseProvider {
try {
const response = await axios.post(`${this.baseUrl}/${instance}/${key}`, data, {
headers: { apikey: this.globalApiToken },
});
return [{ status: response.status, data: response?.data }];
} catch (error) {
return [
{
status: error?.response?.status,
data: error?.response?.data,
},
error,
];
}
}
public async read(instance: string, key: string): ResponseProvider {
try {
const response = await axios.get(`${this.baseUrl}/${instance}/${key}`, {
headers: { apikey: this.globalApiToken },
});
return [{ status: response.status, data: response?.data }];
} catch (error) {
return [
{
status: error?.response?.status,
data: error?.response?.data,
},
error,
];
}
}
public async delete(instance: string, key: string): ResponseProvider {
try {
const response = await axios.delete(`${this.baseUrl}/${instance}/${key}`, {
headers: { apikey: this.globalApiToken },
});
return [{ status: response.status, data: response?.data }];
} catch (error) {
return [
{
status: error?.response?.status,
data: error?.response?.data,
},
error,
];
}
}
public async allInstances(): ResponseProvider {
try {
const response = await axios.get(`${this.baseUrl}/list-instances`, { headers: { apikey: this.globalApiToken } });
return [{ status: response.status, data: response?.data as string[] }];
} catch (error) {
return [
{
status: error?.response?.status,
data: error?.response?.data,
},
error,
];
}
}
public async removeSession(instance: string): ResponseProvider {
try {
const response = await axios.delete(`${this.baseUrl}/${instance}`, { headers: { apikey: this.globalApiToken } });
return [{ status: response.status, data: response?.data }];
} catch (error) {
return [
{
status: error?.response?.status,
data: error?.response?.data,
},
error,
];
}
}
}

View File

@@ -1,5 +1,5 @@
import { CacheEngine } from '../cache/cacheengine';
import { configService } from '../config/env.config';
import { configService, ProviderSession } from '../config/env.config';
import { eventEmitter } from '../config/event.config';
import { Logger } from '../config/logger.config';
import { dbserver } from '../libs/db.connect';
@@ -47,6 +47,7 @@ import {
WebsocketModel,
} from './models';
import { LabelModel } from './models/label.model';
import { ProviderFiles } from './provider/sessions';
import { AuthRepository } from './repository/auth.repository';
import { ChatRepository } from './repository/chat.repository';
import { ContactRepository } from './repository/contact.repository';
@@ -108,7 +109,13 @@ export const repository = new RepositoryBroker(
export const cache = new CacheService(new CacheEngine(configService, 'instance').getEngine());
const chatwootCache = new CacheService(new CacheEngine(configService, ChatwootService.name).getEngine());
const messagesLostCache = new CacheService(new CacheEngine(configService, 'baileys').getEngine());
const baileysCache = new CacheService(new CacheEngine(configService, 'baileys').getEngine());
let providerFiles: ProviderFiles = null;
if (configService.get<ProviderSession>('PROVIDER')?.ENABLED) {
providerFiles = new ProviderFiles(configService);
}
export const waMonitor = new WAMonitoringService(
eventEmitter,
@@ -116,7 +123,8 @@ export const waMonitor = new WAMonitoringService(
repository,
cache,
chatwootCache,
messagesLostCache,
baileysCache,
providerFiles,
);
const authService = new AuthService(configService, waMonitor, repository);
@@ -167,7 +175,8 @@ export const instanceController = new InstanceController(
proxyController,
cache,
chatwootCache,
messagesLostCache,
baileysCache,
providerFiles,
);
export const sendMessageController = new SendMessageController(waMonitor);
export const chatController = new ChatController(waMonitor);

View File

@@ -1,4 +1,4 @@
import { BufferJSON } from '@whiskeysockets/baileys';
import { BufferJSON } from 'baileys';
import { Logger } from '../../config/logger.config';
import { ICache } from '../abstract/abstract.cache';

View File

@@ -1,5 +1,5 @@
import { WASocket } from '@whiskeysockets/baileys';
import axios from 'axios';
import { WASocket } from 'baileys';
import { execSync } from 'child_process';
import { isURL } from 'class-validator';
import EventEmitter2 from 'eventemitter2';
@@ -721,7 +721,9 @@ export class ChannelStartupService {
autoDelete: false,
});
const queueName = `${this.instanceName}.${event}`;
const eventName = event.replace(/_/g, '.').toLowerCase();
const queueName = `${this.instanceName}.${eventName}`;
await amqp.assertQueue(queueName, {
durable: true,
@@ -731,7 +733,7 @@ export class ChannelStartupService {
},
});
await amqp.bindQueue(queueName, exchangeName, event);
await amqp.bindQueue(queueName, exchangeName, eventName);
const message = {
event,
@@ -786,7 +788,7 @@ export class ChannelStartupService {
autoDelete: false,
});
const queueName = transformedWe;
const queueName = event;
await amqp.assertQueue(queueName, {
durable: true,

View File

@@ -1,5 +1,6 @@
import ffmpegPath from '@ffmpeg-installer/ffmpeg';
import { Boom } from '@hapi/boom';
import axios from 'axios';
import makeWASocket, {
AnyMessageContent,
BufferedEventData,
@@ -19,6 +20,7 @@ import makeWASocket, {
GroupMetadata,
isJidBroadcast,
isJidGroup,
isJidNewsletter,
isJidUser,
makeCacheableSignalKeyStore,
MessageUpsertType,
@@ -35,10 +37,9 @@ import makeWASocket, {
WAMessageUpdate,
WAPresence,
WASocket,
} from '@whiskeysockets/baileys';
import { Label } from '@whiskeysockets/baileys/lib/Types/Label';
import { LabelAssociation } from '@whiskeysockets/baileys/lib/Types/LabelAssociation';
import axios from 'axios';
} from 'baileys';
import { Label } from 'baileys/lib/Types/Label';
import { LabelAssociation } from 'baileys/lib/Types/LabelAssociation';
import { exec } from 'child_process';
import { isBase64, isURL } from 'class-validator';
import EventEmitter2 from 'eventemitter2';
@@ -55,12 +56,23 @@ import qrcode, { QRCodeToDataURLOptions } from 'qrcode';
import qrcodeTerminal from 'qrcode-terminal';
import sharp from 'sharp';
import { CacheConf, ConfigService, ConfigSessionPhone, Database, Log, QrCode } from '../../../config/env.config';
import { CacheEngine } from '../../../cache/cacheengine';
import {
CacheConf,
ConfigService,
configService,
ConfigSessionPhone,
Database,
Log,
ProviderSession,
QrCode,
} from '../../../config/env.config';
import { INSTANCE_DIR } from '../../../config/path.config';
import { BadRequestException, InternalServerErrorException, NotFoundException } from '../../../exceptions';
import { dbserver } from '../../../libs/db.connect';
import { makeProxyAgent } from '../../../utils/makeProxyAgent';
import { useMultiFileAuthStateDb } from '../../../utils/use-multi-file-auth-state-db';
import { AuthStateProvider } from '../../../utils/use-multi-file-auth-state-provider-files';
import { useMultiFileAuthStateRedisDb } from '../../../utils/use-multi-file-auth-state-redis-db';
import {
ArchiveChatDto,
@@ -114,12 +126,15 @@ import { SettingsRaw } from '../../models';
import { ChatRaw } from '../../models/chat.model';
import { ContactRaw } from '../../models/contact.model';
import { MessageRaw, MessageUpdateRaw } from '../../models/message.model';
import { ProviderFiles } from '../../provider/sessions';
import { RepositoryBroker } from '../../repository/repository.manager';
import { waMonitor } from '../../server.module';
import { Events, MessageSubtype, TypeMediaMessage, wa } from '../../types/wa.types';
import { CacheService } from './../cache.service';
import { ChannelStartupService } from './../channel.service';
const groupMetadataCache = new CacheService(new CacheEngine(configService, 'groups').getEngine());
export class BaileysStartupService extends ChannelStartupService {
constructor(
public readonly configService: ConfigService,
@@ -127,7 +142,8 @@ export class BaileysStartupService extends ChannelStartupService {
public readonly repository: RepositoryBroker,
public readonly cache: CacheService,
public readonly chatwootCache: CacheService,
public readonly messagesLostCache: CacheService,
public readonly baileysCache: CacheService,
private readonly providerFiles: ProviderFiles,
) {
super(configService, eventEmitter, repository, chatwootCache);
this.logger.verbose('BaileysStartupService initialized');
@@ -135,8 +151,12 @@ export class BaileysStartupService extends ChannelStartupService {
this.instance.qrcode = { count: 0 };
this.mobile = false;
this.recoveringMessages();
this.forceUpdateGroupMetadataCache();
this.authStateProvider = new AuthStateProvider(this.providerFiles);
}
private authStateProvider: AuthStateProvider;
private readonly msgRetryCounterCache: CacheStore = new NodeCache();
private readonly userDevicesCache: CacheStore = new NodeCache();
private endSession = false;
@@ -153,9 +173,9 @@ export class BaileysStartupService extends ChannelStartupService {
if ((cacheConf?.REDIS?.ENABLED && cacheConf?.REDIS?.URI !== '') || cacheConf?.LOCAL?.ENABLED) {
setInterval(async () => {
this.messagesLostCache.keys().then((keys) => {
this.baileysCache.keys().then((keys) => {
keys.forEach(async (key) => {
const message = await this.messagesLostCache.get(key.split(':')[2]);
const message = await this.baileysCache.get(key.split(':')[2]);
if (message.messageStubParameters && message.messageStubParameters[0] === 'Message absent from node') {
this.logger.info('Message absent from node, retrying to send, key: ' + key.split(':')[2]);
@@ -167,6 +187,23 @@ export class BaileysStartupService extends ChannelStartupService {
}
}
private async forceUpdateGroupMetadataCache() {
if (
!this.configService.get<CacheConf>('CACHE').REDIS.ENABLED &&
!this.configService.get<CacheConf>('CACHE').LOCAL.ENABLED
)
return;
setInterval(async () => {
this.logger.verbose('Forcing update group metadata cache');
const groups = await this.fetchAllGroups({ getParticipants: 'false' });
for (const group of groups) {
await this.updateGroupMetadataCache(group.id);
}
}, 3600000);
}
public get connectionStatus() {
this.logger.verbose('Getting connection status');
return this.stateConnection;
@@ -217,8 +254,8 @@ export class BaileysStartupService extends ChannelStartupService {
this.logger.verbose('Getting profile status');
const status = await this.client.fetchStatus(this.instance.wuid);
this.logger.verbose(`Profile status: ${status.status}`);
return status.status;
this.logger.verbose(`Profile status: ${status[0]?.status}`);
return status[0]?.status;
}
public get profilePictureUrl() {
@@ -461,6 +498,12 @@ export class BaileysStartupService extends ChannelStartupService {
const db = this.configService.get<Database>('DATABASE');
const cache = this.configService.get<CacheConf>('CACHE');
const provider = this.configService.get<ProviderSession>('PROVIDER');
if (provider?.ENABLED) {
return await this.authStateProvider.authStateProvider(this.instance.name);
}
if (cache?.REDIS.ENABLED && cache?.REDIS.SAVE_INSTANCES) {
this.logger.info('Redis enabled');
return await useMultiFileAuthStateRedisDb(this.instance.name, this.cache);
@@ -475,19 +518,7 @@ export class BaileysStartupService extends ChannelStartupService {
return await useMultiFileAuthState(join(INSTANCE_DIR, this.instance.name));
}
public async connectToWhatsapp(number?: string, mobile?: boolean): Promise<WASocket> {
this.logger.verbose('Connecting to whatsapp');
try {
this.loadWebhook();
this.loadChatwoot();
this.loadSettings();
this.loadWebsocket();
this.loadRabbitmq();
this.loadSqs();
this.loadTypebot();
this.loadProxy();
this.loadChamaai();
private async createClient(number?: string, mobile?: boolean): Promise<WASocket> {
this.instance.authState = await this.defineAuthState();
if (!mobile) {
@@ -497,8 +528,19 @@ export class BaileysStartupService extends ChannelStartupService {
}
const session = this.configService.get<ConfigSessionPhone>('CONFIG_SESSION_PHONE');
let browserOptions = {};
if (number || this.phoneNumber) {
this.phoneNumber = number;
this.logger.info(`Phone number: ${number}`);
} else {
const browser: WABrowserDescription = [session.CLIENT, session.NAME, release()];
this.logger.verbose('Browser: ' + JSON.stringify(browser));
browserOptions = { browser };
this.logger.info(`Browser: ${browser}`);
}
let version;
let log;
@@ -550,19 +592,23 @@ export class BaileysStartupService extends ChannelStartupService {
logger: P({ level: this.logBaileys }),
printQRInTerminal: false,
mobile,
browser: number ? ['Chrome (Linux)', session.NAME, release()] : browser,
...browserOptions,
version,
markOnlineOnConnect: this.localSettings.always_online,
retryRequestDelayMs: 10,
connectTimeoutMs: 60_000,
qrTimeout: 40_000,
retryRequestDelayMs: 350,
maxMsgRetryCount: 4,
fireInitQueries: true,
connectTimeoutMs: 20_000,
keepAliveIntervalMs: 30_000,
qrTimeout: 45_000,
defaultQueryTimeoutMs: undefined,
emitOwnEvents: false,
shouldIgnoreJid: (jid) => {
const isGroupJid = this.localSettings.groups_ignore && isJidGroup(jid);
const isBroadcast = !this.localSettings.read_status && isJidBroadcast(jid);
const isNewsletter = isJidNewsletter(jid);
return isGroupJid || isBroadcast;
return isGroupJid || isBroadcast || isNewsletter;
},
msgRetryCounterCache: this.msgRetryCounterCache,
getMessage: async (key) => (await this.getMessage(key)) as Promise<proto.IMessage>,
@@ -572,11 +618,10 @@ export class BaileysStartupService extends ChannelStartupService {
return this.historySyncNotification(msg);
},
userDevicesCache: this.userDevicesCache,
transactionOpts: { maxCommitRetries: 10, delayBetweenTriesMs: 10 },
transactionOpts: { maxCommitRetries: 5, delayBetweenTriesMs: 2500 },
patchMessageBeforeSending(message) {
if (
message.deviceSentMessage?.message?.listMessage?.listType ===
proto.Message.ListMessage.ListType.PRODUCT_LIST
message.deviceSentMessage?.message?.listMessage?.listType === proto.Message.ListMessage.ListType.PRODUCT_LIST
) {
message = JSON.parse(JSON.stringify(message));
@@ -608,6 +653,22 @@ export class BaileysStartupService extends ChannelStartupService {
this.phoneNumber = number;
return this.client;
}
public async connectToWhatsapp(number?: string, mobile?: boolean): Promise<WASocket> {
this.logger.verbose('Connecting to whatsapp');
try {
this.loadWebhook();
this.loadChatwoot();
this.loadSettings();
this.loadWebsocket();
this.loadRabbitmq();
this.loadSqs();
this.loadTypebot();
this.loadProxy();
this.loadChamaai();
return await this.createClient(number, mobile);
} catch (error) {
this.logger.error(error);
throw new InternalServerErrorException(error?.toString());
@@ -628,12 +689,8 @@ export class BaileysStartupService extends ChannelStartupService {
return;
}
console.log('phoneNumber', phoneNumber);
const parsedPhoneNumber = parsePhoneNumber(phoneNumber);
console.log('parsedPhoneNumber', parsedPhoneNumber);
if (!parsedPhoneNumber?.isValid()) {
this.logger.error('Phone number invalid');
return;
@@ -655,7 +712,6 @@ export class BaileysStartupService extends ChannelStartupService {
try {
const response = await this.client.requestRegistrationCode(registration);
console.log('response', response);
if (['ok', 'sent'].includes(response?.status)) {
this.logger.verbose('Registration code sent successfully');
@@ -669,9 +725,8 @@ export class BaileysStartupService extends ChannelStartupService {
public async receiveMobileCode(code: string) {
await this.client
.register(code.replace(/["']/g, '').trim().toLowerCase())
.then(async (response) => {
.then(async () => {
this.logger.verbose('Registration code received successfully');
console.log(response);
})
.catch((error) => {
this.logger.error(error);
@@ -680,106 +735,7 @@ export class BaileysStartupService extends ChannelStartupService {
public async reloadConnection(): Promise<WASocket> {
try {
this.instance.authState = await this.defineAuthState();
const session = this.configService.get<ConfigSessionPhone>('CONFIG_SESSION_PHONE');
const browser: WABrowserDescription = [session.CLIENT, session.NAME, release()];
let version;
let log;
if (session.VERSION) {
version = session.VERSION.split(',');
log = `Baileys version env: ${version}`;
} else {
const baileysVersion = await fetchLatestBaileysVersion();
version = baileysVersion.version;
log = `Baileys version: ${version}`;
}
this.logger.info(log);
let options;
if (this.localProxy.enabled) {
this.logger.info('Proxy enabled: ' + this.localProxy.proxy?.host);
if (this.localProxy?.proxy?.host?.includes('proxyscrape')) {
try {
const response = await axios.get(this.localProxy.proxy?.host);
const text = response.data;
const proxyUrls = text.split('\r\n');
const rand = Math.floor(Math.random() * Math.floor(proxyUrls.length));
const proxyUrl = 'http://' + proxyUrls[rand];
options = {
agent: makeProxyAgent(proxyUrl),
fetchAgent: makeProxyAgent(proxyUrl),
};
} catch (error) {
this.localProxy.enabled = false;
}
} else {
options = {
agent: makeProxyAgent(this.localProxy.proxy),
fetchAgent: makeProxyAgent(this.localProxy.proxy),
};
}
}
const socketConfig: UserFacingSocketConfig = {
...options,
auth: {
creds: this.instance.authState.state.creds,
keys: makeCacheableSignalKeyStore(this.instance.authState.state.keys, P({ level: 'error' }) as any),
},
logger: P({ level: this.logBaileys }),
printQRInTerminal: false,
browser: this.phoneNumber ? ['Chrome (Linux)', session.NAME, release()] : browser,
version,
markOnlineOnConnect: this.localSettings.always_online,
retryRequestDelayMs: 10,
connectTimeoutMs: 60_000,
qrTimeout: 40_000,
defaultQueryTimeoutMs: undefined,
emitOwnEvents: false,
shouldIgnoreJid: (jid) => {
const isGroupJid = this.localSettings.groups_ignore && isJidGroup(jid);
const isBroadcast = !this.localSettings.read_status && isJidBroadcast(jid);
return isGroupJid || isBroadcast;
},
msgRetryCounterCache: this.msgRetryCounterCache,
getMessage: async (key) => (await this.getMessage(key)) as Promise<proto.IMessage>,
generateHighQualityLinkPreview: true,
syncFullHistory: this.localSettings.sync_full_history,
shouldSyncHistoryMessage: (msg: proto.Message.IHistorySyncNotification) => {
return this.historySyncNotification(msg);
},
userDevicesCache: this.userDevicesCache,
transactionOpts: { maxCommitRetries: 10, delayBetweenTriesMs: 10 },
patchMessageBeforeSending(message) {
if (
message.deviceSentMessage?.message?.listMessage?.listType ===
proto.Message.ListMessage.ListType.PRODUCT_LIST
) {
message = JSON.parse(JSON.stringify(message));
message.deviceSentMessage.message.listMessage.listType = proto.Message.ListMessage.ListType.SINGLE_SELECT;
}
if (message.listMessage?.listType == proto.Message.ListMessage.ListType.PRODUCT_LIST) {
message = JSON.parse(JSON.stringify(message));
message.listMessage.listType = proto.Message.ListMessage.ListType.SINGLE_SELECT;
}
return message;
},
};
this.client = makeWASocket(socketConfig);
return this.client;
return await this.createClient(this.phoneNumber, this.mobile);
} catch (error) {
this.logger.error(error);
throw new InternalServerErrorException(error?.toString());
@@ -1017,7 +973,7 @@ export class BaileysStartupService extends ChannelStartupService {
m.messageTimestamp = m.messageTimestamp?.toNumber();
}
if (m.messageTimestamp <= timestampLimitToImport) {
if ((m.messageTimestamp as number) <= timestampLimitToImport) {
continue;
}
@@ -1105,15 +1061,15 @@ export class BaileysStartupService extends ChannelStartupService {
if (received.messageStubParameters && received.messageStubParameters[0] === 'Message absent from node') {
this.logger.info('Recovering message lost');
await this.messagesLostCache.set(received.key.id, received);
await this.baileysCache.set(received.key.id, received);
continue;
}
const retryCache = (await this.messagesLostCache.get(received.key.id)) || null;
const retryCache = (await this.baileysCache.get(received.key.id)) || null;
if (retryCache) {
this.logger.info('Recovered message lost');
await this.messagesLostCache.delete(received.key.id);
await this.baileysCache.delete(received.key.id);
}
if (
@@ -1142,6 +1098,7 @@ export class BaileysStartupService extends ChannelStartupService {
received?.message?.videoMessage ||
received?.message?.stickerMessage ||
received?.message?.documentMessage ||
received?.message?.documentWithCaptionMessage ||
received?.message?.audioMessage;
const contentMsg = received?.message[getContentType(received.message)] as any;
@@ -1402,6 +1359,12 @@ export class BaileysStartupService extends ChannelStartupService {
this.logger.verbose('Sending data to webhook in event GROUPS_UPDATE');
this.sendDataWebhook(Events.GROUPS_UPDATE, groupMetadataUpdate);
groupMetadataUpdate.forEach((group) => {
if (isJidGroup(group.id)) {
this.updateGroupMetadataCache(group.id);
}
});
},
'group-participants.update': (participantsUpdate: {
@@ -1459,7 +1422,7 @@ export class BaileysStartupService extends ChannelStartupService {
this.logger.verbose('Sending data to webhook in event LABELS_ASSOCIATION');
// Atualiza labels nos chats
if (database.SAVE_DATA.CHATS) {
if (database.ENABLED && database.SAVE_DATA.CHATS) {
const chats = await this.repository.chat.find({
where: {
owner: this.instance.name,
@@ -1687,7 +1650,7 @@ export class BaileysStartupService extends ChannelStartupService {
this.logger.verbose('Getting status');
return {
wuid: jid,
status: (await this.client.fetchStatus(jid))?.status,
status: (await this.client.fetchStatus(jid))[0]?.status,
};
} catch (error) {
this.logger.verbose('Status not found');
@@ -1838,7 +1801,11 @@ export class BaileysStartupService extends ChannelStartupService {
let mentions: string[];
if (isJidGroup(sender)) {
try {
const group = await this.findGroup({ groupJid: sender }, 'inner');
let group;
const cache = this.configService.get<CacheConf>('CACHE');
if (!cache.REDIS.ENABLED && !cache.LOCAL.ENABLED) group = await this.findGroup({ groupJid: sender }, 'inner');
else group = await this.getGroupMetadataCache(sender);
if (!group) {
throw new NotFoundException('Group not found');
@@ -1891,7 +1858,12 @@ export class BaileysStartupService extends ChannelStartupService {
key: message['reactionMessage']['key'],
},
} as unknown as AnyMessageContent,
option as unknown as MiscMessageGenerationOptions,
{
...option,
useCachedGroupMetadata:
!!this.configService.get<CacheConf>('CACHE').REDIS.ENABLED &&
!!this.configService.get<CacheConf>('CACHE').LOCAL.ENABLED,
} as unknown as MiscMessageGenerationOptions,
);
}
}
@@ -1904,7 +1876,12 @@ export class BaileysStartupService extends ChannelStartupService {
mentions,
linkPreview: linkPreview,
} as unknown as AnyMessageContent,
option as unknown as MiscMessageGenerationOptions,
{
...option,
useCachedGroupMetadata:
!!this.configService.get<CacheConf>('CACHE').REDIS.ENABLED &&
!!this.configService.get<CacheConf>('CACHE').LOCAL.ENABLED,
} as unknown as MiscMessageGenerationOptions,
);
}
@@ -1919,7 +1896,12 @@ export class BaileysStartupService extends ChannelStartupService {
},
mentions,
},
option as unknown as MiscMessageGenerationOptions,
{
...option,
useCachedGroupMetadata:
!!this.configService.get<CacheConf>('CACHE').REDIS.ENABLED &&
!!this.configService.get<CacheConf>('CACHE').LOCAL.ENABLED,
} as unknown as MiscMessageGenerationOptions,
);
}
@@ -1940,7 +1922,12 @@ export class BaileysStartupService extends ChannelStartupService {
return await this.client.sendMessage(
sender,
message as unknown as AnyMessageContent,
option as unknown as MiscMessageGenerationOptions,
{
...option,
useCachedGroupMetadata:
!!this.configService.get<CacheConf>('CACHE').REDIS.ENABLED &&
!!this.configService.get<CacheConf>('CACHE').LOCAL.ENABLED,
} as unknown as MiscMessageGenerationOptions,
);
})();
@@ -3145,6 +3132,38 @@ export class BaileysStartupService extends ChannelStartupService {
}
// Group
private async updateGroupMetadataCache(groupJid: string) {
try {
const meta = await this.client.groupMetadata(groupJid);
await groupMetadataCache.set(groupJid, {
timestamp: Date.now(),
data: meta,
});
return meta;
} catch (error) {
this.logger.error(error);
return null;
}
}
private async getGroupMetadataCache(groupJid: string) {
if (!isJidGroup(groupJid)) return null;
if (await groupMetadataCache.has(groupJid)) {
console.log('Has cache for group: ' + groupJid);
const meta = await groupMetadataCache.get(groupJid);
if (Date.now() - meta.timestamp > 3600000) {
await this.updateGroupMetadataCache(groupJid);
}
return meta.data;
}
return await this.updateGroupMetadataCache(groupJid);
}
public async createGroup(create: CreateGroupDto) {
this.logger.verbose('Creating group: ' + create.subject);
try {
@@ -3241,6 +3260,10 @@ export class BaileysStartupService extends ChannelStartupService {
}
public async findGroup(id: GroupJid, reply: 'inner' | 'out' = 'out') {
if (this.localSettings.groups_ignore === true) {
return;
}
this.logger.verbose('Fetching group');
try {
const group = await this.client.groupMetadata(id.groupJid);
@@ -3271,6 +3294,10 @@ export class BaileysStartupService extends ChannelStartupService {
}
public async fetchAllGroups(getParticipants: GetParticipant) {
if (this.localSettings.groups_ignore === true) {
return;
}
this.logger.verbose('Fetching all groups');
try {
const fetch = Object.values(await this.client.groupFetchAllParticipating());

View File

@@ -23,6 +23,7 @@ import {
SendTextDto,
} from '../../dto/sendMessage.dto';
import { ContactRaw, MessageRaw, MessageUpdateRaw, SettingsRaw } from '../../models';
import { ProviderFiles } from '../../provider/sessions';
import { RepositoryBroker } from '../../repository/repository.manager';
import { Events, wa } from '../../types/wa.types';
import { CacheService } from './../cache.service';
@@ -35,7 +36,8 @@ export class BusinessStartupService extends ChannelStartupService {
public readonly repository: RepositoryBroker,
public readonly cache: CacheService,
public readonly chatwootCache: CacheService,
public readonly messagesLostCache: CacheService,
public readonly baileysCache: CacheService,
private readonly providerFiles: ProviderFiles,
) {
super(configService, eventEmitter, repository, chatwootCache);
this.logger.verbose('BusinessStartupService initialized');

View File

@@ -5,7 +5,15 @@ import { Db } from 'mongodb';
import { Collection } from 'mongoose';
import { join } from 'path';
import { Auth, CacheConf, ConfigService, Database, DelInstance, HttpServer } from '../../config/env.config';
import {
Auth,
CacheConf,
ConfigService,
Database,
DelInstance,
HttpServer,
ProviderSession,
} from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { INSTANCE_DIR, STORE_DIR } from '../../config/path.config';
import { NotFoundException } from '../../exceptions';
@@ -22,6 +30,7 @@ import {
WebhookModel,
WebsocketModel,
} from '../models';
import { ProviderFiles } from '../provider/sessions';
import { RepositoryBroker } from '../repository/repository.manager';
import { Integration } from '../types/wa.types';
import { CacheService } from './cache.service';
@@ -35,7 +44,8 @@ export class WAMonitoringService {
private readonly repository: RepositoryBroker,
private readonly cache: CacheService,
private readonly chatwootCache: CacheService,
private readonly messagesLostCache: CacheService,
private readonly baileysCache: CacheService,
private readonly providerFiles: ProviderFiles,
) {
this.logger.verbose('instance created');
@@ -58,6 +68,8 @@ export class WAMonitoringService {
private readonly logger = new Logger(WAMonitoringService.name);
public readonly waInstances: Record<string, BaileysStartupService | BusinessStartupService> = {};
private readonly providerSession = Object.freeze(this.configService.get<ProviderSession>('PROVIDER'));
public delInstanceTime(instance: string) {
const time = this.configService.get<DelInstance>('DEL_INSTANCE');
if (typeof time === 'number' && time > 0) {
@@ -257,12 +269,18 @@ export class WAMonitoringService {
}
this.logger.verbose('cleaning up instance in files: ' + instanceName);
if (this.providerSession?.ENABLED) {
await this.providerFiles.removeSession(instanceName);
}
rmSync(join(INSTANCE_DIR, instanceName), { recursive: true, force: true });
}
public async cleaningStoreFiles(instanceName: string) {
if (!this.db.ENABLED) {
this.logger.verbose('cleaning store files instance: ' + instanceName);
if (this.providerSession?.ENABLED) {
await this.providerFiles.removeSession(instanceName);
}
rmSync(join(INSTANCE_DIR, instanceName), { recursive: true, force: true });
execSync(`rm -rf ${join(STORE_DIR, 'chats', instanceName)}`);
@@ -305,7 +323,9 @@ export class WAMonitoringService {
this.logger.verbose('Loading instances');
try {
if (this.redis.REDIS.ENABLED && this.redis.REDIS.SAVE_INSTANCES) {
if (this.providerSession?.ENABLED) {
await this.loadInstancesFromProvider();
} else if (this.redis.REDIS.ENABLED && this.redis.REDIS.SAVE_INSTANCES) {
await this.loadInstancesFromRedis();
} else if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
await this.loadInstancesFromDatabase();
@@ -348,7 +368,8 @@ export class WAMonitoringService {
this.repository,
this.cache,
this.chatwootCache,
this.messagesLostCache,
this.baileysCache,
this.providerFiles,
);
instance.instanceName = name;
@@ -359,7 +380,8 @@ export class WAMonitoringService {
this.repository,
this.cache,
this.chatwootCache,
this.messagesLostCache,
this.baileysCache,
this.providerFiles,
);
instance.instanceName = name;
@@ -401,6 +423,18 @@ export class WAMonitoringService {
}
}
private async loadInstancesFromProvider() {
this.logger.verbose('Provider in files enabled');
const [instances] = await this.providerFiles.allInstances();
if (!instances?.data) {
this.logger.verbose('No instances found');
return;
}
await Promise.all(instances?.data?.map(async (instanceName: string) => this.setInstance(instanceName)));
}
private async loadInstancesFromFiles() {
this.logger.verbose('Store in files enabled');
const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' });

View File

@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-namespace */
import { AuthenticationState, WAConnectionState } from '@whiskeysockets/baileys';
import { AuthenticationState, WAConnectionState } from 'baileys';
export enum Events {
APPLICATION_STARTUP = 'application.startup',

View File

@@ -1,4 +1,4 @@
import { BufferJSON } from '@whiskeysockets/baileys';
import { BufferJSON } from 'baileys';
import { RedisClientType } from 'redis';
import { ICache } from '../api/abstract/abstract.cache';

View File

@@ -28,6 +28,13 @@ export type Log = {
BAILEYS: LogBaileys;
};
export type ProviderSession = {
ENABLED: boolean;
HOST: string;
PORT: string;
PREFIX: string;
};
export type SaveData = {
INSTANCE: boolean;
NEW_MESSAGE: boolean;
@@ -209,6 +216,7 @@ export interface Env {
SERVER: HttpServer;
CORS: Cors;
SSL_CONF: SslConf;
PROVIDER: ProviderSession;
STORE: StoreConf;
CLEAN_STORE: CleanStoreConf;
DATABASE: Database;
@@ -274,6 +282,12 @@ export class ConfigService {
PRIVKEY: process.env?.SSL_CONF_PRIVKEY || '',
FULLCHAIN: process.env?.SSL_CONF_FULLCHAIN || '',
},
PROVIDER: {
ENABLED: process.env?.PROVIDER_ENABLED === 'true',
HOST: process.env.PROVIDER_HOST,
PORT: process.env?.PROVIDER_PORT || '5656',
PREFIX: process.env?.PROVIDER_PREFIX || 'evolution',
},
STORE: {
MESSAGES: process.env?.STORE_MESSAGES === 'true',
MESSAGE_UP: process.env?.STORE_MESSAGE_UP === 'true',
@@ -352,9 +366,9 @@ export class ConfigService {
GLOBAL_EVENTS: process.env?.WEBSOCKET_GLOBAL_EVENTS === 'true',
},
WA_BUSINESS: {
TOKEN_WEBHOOK: process.env.WA_BUSINESS_TOKEN_WEBHOOK || '',
URL: process.env.WA_BUSINESS_URL || '',
VERSION: process.env.WA_BUSINESS_VERSION || '',
TOKEN_WEBHOOK: process.env.WA_BUSINESS_TOKEN_WEBHOOK || 'evolution',
URL: process.env.WA_BUSINESS_URL || 'https://graph.facebook.com',
VERSION: process.env.WA_BUSINESS_VERSION || 'v19.0',
LANGUAGE: process.env.WA_BUSINESS_LANGUAGE || 'en',
},
LOG: {

View File

@@ -49,6 +49,14 @@ LOG:
DEL_INSTANCE: false # or false
DEL_TEMP_INSTANCES: true # Delete instances with status closed on start
# Seesion Files Providers
# Provider responsible for managing credentials files and WhatsApp sessions.
PROVIDER:
ENABLED: true
HOST: 127.0.0.1
PORT: 5656
PREFIX: evolution
# Temporary data storage
STORE:
MESSAGES: true

View File

@@ -25,7 +25,7 @@ info:
</font>
[![Run in Postman](https://run.pstmn.io/button.svg)](https://god.gw.postman.com/run-collection/26869335-5546d063-156b-4529-915f-909dd628c090?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D26869335-5546d063-156b-4529-915f-909dd628c090%26entityType%3Dcollection%26workspaceId%3D339a4ee7-378b-45c9-b5b8-fd2c0a9c2442)
version: 1.8.0
version: 1.8.2
contact:
name: DavidsonGomes
email: contato@agenciadgcode.com

View File

@@ -9,9 +9,10 @@ import { join } from 'path';
import { initAMQP, initGlobalQueues } from './api/integrations/rabbitmq/libs/amqp.server';
import { initSQS } from './api/integrations/sqs/libs/sqs.server';
import { initIO } from './api/integrations/websocket/libs/socket.server';
import { ProviderFiles } from './api/provider/sessions';
import { HttpStatus, router } from './api/routes/index.router';
import { waMonitor } from './api/server.module';
import { Auth, configService, Cors, HttpServer, Rabbitmq, Sqs, Webhook } from './config/env.config';
import { Auth, configService, Cors, HttpServer, ProviderSession, Rabbitmq, Sqs, Webhook } from './config/env.config';
import { onUnexpectedError } from './config/error.config';
import { Logger } from './config/logger.config';
import { ROOT_DIR } from './config/path.config';
@@ -22,10 +23,18 @@ function initWA() {
waMonitor.loadInstance();
}
function bootstrap() {
async function bootstrap() {
const logger = new Logger('SERVER');
const app = express();
let providerFiles: ProviderFiles = null;
if (configService.get<ProviderSession>('PROVIDER')?.ENABLED) {
providerFiles = new ProviderFiles(configService);
await providerFiles.onModuleInit();
logger.info('Provider:Files - ON');
}
app.use(
cors({
origin(requestOrigin, callback) {

View File

@@ -1,29 +1,55 @@
import {
AuthenticationCreds,
AuthenticationState,
BufferJSON,
initAuthCreds,
proto,
SignalDataTypeMap,
} from '@whiskeysockets/baileys';
import { AuthenticationState, BufferJSON, initAuthCreds, WAProto as proto } from 'baileys';
import fs from 'fs/promises';
import path from 'path';
import { configService, Database } from '../config/env.config';
import { Logger } from '../config/logger.config';
import { INSTANCE_DIR } from '../config/path.config';
import { dbserver } from '../libs/db.connect';
const fixFileName = (file) => {
if (!file) {
return undefined;
}
const replacedSlash = file.replace(/\//g, '__');
const replacedColon = replacedSlash.replace(/:/g, '-');
return replacedColon;
};
async function fileExists(file) {
try {
const stat = await fs.stat(file);
if (stat.isFile()) return true;
} catch (error) {
return;
}
}
export async function useMultiFileAuthStateDb(
coll: string,
): Promise<{ state: AuthenticationState; saveCreds: () => Promise<void> }> {
const logger = new Logger(useMultiFileAuthStateDb.name);
const client = dbserver.getClient();
const logger = new Logger(useMultiFileAuthStateDb.name);
const collection = client
.db(configService.get<Database>('DATABASE').CONNECTION.DB_PREFIX_NAME + '-instances')
.collection(coll);
const writeData = async (data: any, key: string): Promise<any> => {
const sessionId = coll;
const localFolder = path.join(INSTANCE_DIR, sessionId);
const localFile = (key: string) => path.join(localFolder, fixFileName(key) + '.json');
await fs.mkdir(localFolder, { recursive: true });
async function writeData(data: any, key: string): Promise<any> {
try {
const dataString = JSON.stringify(data, BufferJSON.replacer);
if (key != 'creds') {
await fs.writeFile(localFile(key), dataString);
return;
}
await client.connect();
let msgParsed = JSON.parse(JSON.stringify(data, BufferJSON.replacer));
if (Array.isArray(msgParsed)) {
@@ -37,11 +63,19 @@ export async function useMultiFileAuthStateDb(
});
} catch (error) {
logger.error(error);
return;
}
}
};
const readData = async (key: string): Promise<any> => {
async function readData(key: string): Promise<any> {
try {
if (key != 'creds') {
if (!(await fileExists(localFile(key)))) return null;
const rawData = await fs.readFile(localFile(key), { encoding: 'utf-8' });
const parsedData = JSON.parse(rawData, BufferJSON.reviver);
return parsedData;
} else {
await client.connect();
let data = (await collection.findOne({ _id: key })) as any;
if (data?.content_array) {
@@ -49,30 +83,39 @@ export async function useMultiFileAuthStateDb(
}
const creds = JSON.stringify(data);
return JSON.parse(creds, BufferJSON.reviver);
}
} catch (error) {
logger.error(error);
return null;
}
}
};
const removeData = async (key: string) => {
async function removeData(key: string): Promise<any> {
try {
if (key != 'creds') {
await fs.unlink(localFile(key));
} else {
await client.connect();
return await collection.deleteOne({ _id: key });
}
} catch (error) {
logger.error(error);
return;
}
}
};
const creds: AuthenticationCreds = (await readData('creds')) || initAuthCreds();
let creds = await readData('creds');
if (!creds) {
creds = initAuthCreds();
await writeData(creds, 'creds');
}
return {
state: {
creds,
keys: {
get: async (type, ids: string[]) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const data: { [_: string]: SignalDataTypeMap[type] } = {};
get: async (type, ids) => {
const data = {};
await Promise.all(
ids.map(async (id) => {
let value = await readData(`${type}-${id}`);
@@ -83,25 +126,24 @@ export async function useMultiFileAuthStateDb(
data[id] = value;
}),
);
return data;
},
set: async (data: any) => {
const tasks: Promise<void>[] = [];
set: async (data) => {
const tasks = [];
for (const category in data) {
for (const id in data[category]) {
const value = data[category][id];
const key = `${category}-${id}`;
tasks.push(value ? writeData(value, key) : removeData(key));
}
}
await Promise.all(tasks);
},
},
},
saveCreds: async () => {
return await writeData(creds, 'creds');
saveCreds: () => {
return writeData(creds, 'creds');
},
};
}

View File

@@ -0,0 +1,132 @@
/**
* ┌──────────────────────────────────────────────────────────────────────────────┐
* │ @author jrCleber │
* │ @filename use-multi-file-auth-state-provider-files.ts │
* │ Developed by: Cleber Wilson │
* │ Creation date: May 31, 2024 │
* │ Contact: contato@codechat.dev │
* ├──────────────────────────────────────────────────────────────────────────────┤
* │ @copyright © Cleber Wilson 2023. All rights reserved. │
* │ Licensed under the Apache License, Version 2.0 │
* │ │
* │ @license "https://github.com/code-chat-br/whatsapp-api/blob/main/LICENSE" │
* │ │
* │ You may not use this file except in compliance with the License. │
* │ You may obtain a copy of the License at │
* │ │
* │ http://www.apache.org/licenses/LICENSE-2.0 │
* │ │
* │ Unless required by applicable law or agreed to in writing, software │
* │ distributed under the License is distributed on an "AS IS" BASIS, │
* │ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
* │ │
* │ See the License for the specific language governing permissions and │
* │ limitations under the License. │
* │ │
* │ @type {AuthState} │
* │ @function useMultiFileAuthStateRedisDb │
* │ @returns {Promise<AuthState>} │
* ├──────────────────────────────────────────────────────────────────────────────┤
* │ @important │
* │ For any future changes to the code in this file, it is recommended to │
* │ contain, together with the modification, the information of the developer │
* │ who changed it and the date of modification. │
* └──────────────────────────────────────────────────────────────────────────────┘
*/
import { AuthenticationCreds, AuthenticationState, BufferJSON, initAuthCreds, proto, SignalDataTypeMap } from 'baileys';
import { isNotEmpty } from 'class-validator';
import { ProviderFiles } from '../api/provider/sessions';
import { Logger } from '../config/logger.config';
export type AuthState = { state: AuthenticationState; saveCreds: () => Promise<void> };
export class AuthStateProvider {
constructor(private readonly providerFiles: ProviderFiles) {}
private readonly logger = new Logger(AuthStateProvider.name);
public async authStateProvider(instance: string): Promise<AuthState> {
const [, error] = await this.providerFiles.create(instance);
if (error) {
this.logger.error(['Failed to create folder on file server', error?.message, error?.stack]);
return;
}
const writeData = async (data: any, key: string): Promise<any> => {
const json = JSON.stringify(data, BufferJSON.replacer);
const [response, error] = await this.providerFiles.write(instance, key, {
data: json,
});
if (error) {
// this.logger.error(['writeData', error?.message, error?.stack]);
return;
}
return response;
};
const readData = async (key: string): Promise<any> => {
const [response, error] = await this.providerFiles.read(instance, key);
if (error) {
// this.logger.error(['readData', error?.message, error?.stack]);
return;
}
if (isNotEmpty(response?.data)) {
return JSON.parse(JSON.stringify(response.data), BufferJSON.reviver);
}
};
const removeData = async (key: string) => {
const [response, error] = await this.providerFiles.delete(instance, key);
if (error) {
// this.logger.error(['removeData', error?.message, error?.stack]);
return;
}
return response;
};
const creds: AuthenticationCreds = (await readData('creds')) || initAuthCreds();
return {
state: {
creds,
keys: {
get: async (type, ids: string[]) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const data: { [_: string]: SignalDataTypeMap[type] } = {};
await Promise.all(
ids.map(async (id) => {
let value = await readData(`${type}-${id}`);
if (type === 'app-state-sync-key' && value) {
value = proto.Message.AppStateSyncKeyData.fromObject(value);
}
data[id] = value;
}),
);
return data;
},
set: async (data: any) => {
const tasks: Promise<void>[] = [];
for (const category in data) {
for (const id in data[category]) {
const value = data[category][id];
const key = `${category}-${id}`;
tasks.push(value ? await writeData(value, key) : await removeData(key));
}
}
await Promise.all(tasks);
},
},
},
saveCreds: async () => {
return await writeData(creds, 'creds');
},
};
}
}

View File

@@ -1,10 +1,4 @@
import {
AuthenticationCreds,
AuthenticationState,
initAuthCreds,
proto,
SignalDataTypeMap,
} from '@whiskeysockets/baileys';
import { AuthenticationCreds, AuthenticationState, initAuthCreds, proto, SignalDataTypeMap } from 'baileys';
import { CacheService } from '../api/services/cache.service';
import { Logger } from '../config/logger.config';