Compare commits

..

647 Commits
2.2.0 ... 2.3.5

Author SHA1 Message Date
Davidson Gomes
d48fbc3a4e Merge branch 'release/2.3.5'
Some checks failed
Check Code Quality / check-lint-and-build (push) Has been cancelled
Build Docker image / Build and Deploy (push) Has been cancelled
Security Scan / CodeQL Analysis (javascript) (push) Has been cancelled
Security Scan / Dependency Review (push) Has been cancelled
2025-10-15 09:58:37 -03:00
Davidson Gomes
cdf06666a1 chore(workflows): update checkout step to include submodules
Some checks failed
Check Code Quality / check-lint-and-build (push) Has been cancelled
Build Docker image / Build and Deploy (push) Has been cancelled
Security Scan / CodeQL Analysis (javascript) (push) Has been cancelled
Security Scan / Dependency Review (push) Has been cancelled
- Added 'submodules: recursive' option to the checkout step in multiple workflow files to ensure submodules are properly initialized during CI/CD processes.
2025-10-15 09:58:27 -03:00
Davidson Gomes
5254928887 Merge branch 'release/2.3.5' 2025-10-15 09:48:00 -03:00
Davidson Gomes
8468690d37 chore(manager): update asset files and install process
- Updated subproject reference in evolution-manager-v2 to the latest commit.
- Enhanced the manager_install.sh script to include npm install and build steps for the evolution-manager-v2.
- Replaced old JavaScript asset file with a new version for improved performance.
- Added a new CSS file for consistent styling across the application.
2025-10-15 09:47:32 -03:00
Willian Coqueiro
bdd9257c47 Merge branch 'develop' of https://github.com/KokeroO/evolution-api into develop 2025-10-15 09:47:32 -03:00
Willian Coqueiro
d6834c8741 fix(chatwoot): correct chatId extraction for non-group JIDs 2025-10-15 09:47:31 -03:00
Davidson Gomes
164beddb39 chore(manager): update asset files and install process
- Updated subproject reference in evolution-manager-v2 to the latest commit.
- Enhanced the manager_install.sh script to include npm install and build steps for the evolution-manager-v2.
- Replaced old JavaScript asset file with a new version for improved performance.
- Added a new CSS file for consistent styling across the application.
2025-10-15 09:44:15 -03:00
Davidson Gomes
4991f1dc37 feat(telemetry): add message type telemetry logging in channel services
- Integrated telemetry logging for received messages in Evolution, WhatsApp Business, and Baileys services.
- Enhanced message tracking by sending the message type to the telemetry system for better observability.
2025-10-15 09:42:45 -03:00
Davidson Gomes
1b1e3b3e9d chore(changelog): update CHANGELOG for version 2.3.5 release date
- Updated the release date for version 2.3.5 to 2025-10-15.
- Adjusted subproject reference in evolution-manager-v2 to the latest commit.
2025-10-15 09:42:44 -03:00
Davidson Gomes
563ca2dd22 chore(changelog): update CHANGELOG for version 2.3.5
- Added features for Chatwoot enhancements, participants data handling, and LID to phone number conversion.
- Updated Docker configurations to include Kafka and frontend services.
- Fixed PostgreSQL migration errors and improved message handling in Baileys and Chatwoot services.
- Refactored TypeScript build process and implemented exponential backoff patterns.
2025-10-15 09:42:44 -03:00
Davidson Gomes
4e44bfb222 chore(manager): update asset files and dependencies
- Updated subproject reference in evolution-manager-v2.
- Replaced old JavaScript and CSS asset files with new versions for improved performance and styling.
- Added new CSS file for consistent font styling across the application.
- Updated the evolution logo image to the latest version.
2025-10-15 09:42:44 -03:00
Davidson Gomes
9edd600513 Merge pull request #2083 from davidmnzs/main
fix: correct the error of hardcoded prisma/kafka schema
2025-10-15 09:40:15 -03:00
Davidson Gomes
501b06d133 Merge branch 'release/2.3.5' 2025-10-15 09:38:34 -03:00
Davidson Gomes
dc530285d5 feat(telemetry): add message type telemetry logging in channel services
- Integrated telemetry logging for received messages in Evolution, WhatsApp Business, and Baileys services.
- Enhanced message tracking by sending the message type to the telemetry system for better observability.
2025-10-15 09:38:06 -03:00
Davidson Gomes
8775cdf036 chore(changelog): update CHANGELOG for version 2.3.5 release date
- Updated the release date for version 2.3.5 to 2025-10-15.
- Adjusted subproject reference in evolution-manager-v2 to the latest commit.
2025-10-15 09:32:09 -03:00
Davidson Gomes
6ad33df879 chore(changelog): update CHANGELOG for version 2.3.5
- Added features for Chatwoot enhancements, participants data handling, and LID to phone number conversion.
- Updated Docker configurations to include Kafka and frontend services.
- Fixed PostgreSQL migration errors and improved message handling in Baileys and Chatwoot services.
- Refactored TypeScript build process and implemented exponential backoff patterns.
2025-10-15 09:31:45 -03:00
Davidson Gomes
633d0b4c45 Merge pull request #2085 from KokeroO/develop
Convert LIDs to PN by sending a call rejection message
2025-10-15 09:25:37 -03:00
Davidson Gomes
82c0eadf7c chore(manager): update asset files and dependencies
- Updated subproject reference in evolution-manager-v2.
- Replaced old JavaScript and CSS asset files with new versions for improved performance and styling.
- Added new CSS file for consistent font styling across the application.
- Updated the evolution logo image to the latest version.
2025-10-15 09:25:21 -03:00
Willian Coqueiro
1756abf1e6 Merge branch 'develop' of https://github.com/KokeroO/evolution-api into develop 2025-10-14 05:33:54 +00:00
Willian Coqueiro
a2f48030dc Merge branch 'develop' of https://github.com/KokeroO/evolution-api into develop 2025-10-14 05:33:33 +00:00
Willian Coqueiro
3214a9fb5b fix(chatwoot): correct chatId extraction for non-group JIDs 2025-10-14 05:25:36 +00:00
Willian Coqueiro
4b89e3f987 fix(chatwoot): correct chatId extraction for non-group JIDs 2025-10-14 02:16:22 +00:00
Willian Coqueiro
72622dca31 Merge upstream/develop into develop 2025-10-14 02:12:15 +00:00
davidmnzs
d73b72b67e fix: correct the error of hardcoded prisma/kafka schema 2025-10-13 20:28:17 -03:00
Davidson Gomes
20eef33df3 Merge pull request #2076 from KokeroO/fix/chatwoot
Some checks failed
Check Code Quality / check-lint-and-build (push) Has been cancelled
Build Docker image / Build and Deploy (push) Has been cancelled
Security Scan / CodeQL Analysis (javascript) (push) Has been cancelled
Security Scan / Dependency Review (push) Has been cancelled
Implementations and corrections of previous commits in the chatwoot and baileys services
2025-10-13 12:19:58 -03:00
Davidson Gomes
37571c03b4 Merge pull request #2072 from nolramaf/fix/media-content-validation
fix/media content validation
2025-10-13 12:19:05 -03:00
Willian Coqueiro
017949458b refactor(baileys): simplify linkPreview handling in BaileysStartupService 2025-10-12 15:38:05 +00:00
Willian Coqueiro
2feaf1c74e fix(baileys): handle undefined status in update by defaulting to 'DELETED' 2025-10-12 15:29:48 +00:00
Willian Coqueiro
4b043cb4b8 refactor: update TypeScript build process and dependencies
- Changed the build command in package.json to use TypeScript compiler (tsc) with noEmit option.
- Added @swc/core and @swc/helpers as development dependencies for improved performance.

refactor: clean up WhatsApp Baileys service

- Removed unused properties and interfaces related to message keys.
- Simplified message handling logic by removing redundant checks and conditions.
- Updated message timestamp handling for consistency.
- Improved readability and maintainability by restructuring code and removing commented-out sections.

refactor: optimize Chatwoot service

- Streamlined database queries by reusing PostgreSQL client connection.
- Enhanced conversation creation logic with better cache handling.
- Removed unnecessary methods and improved existing ones for clarity.
- Updated message sending logic to handle file streams instead of buffers.

fix: improve translation loading mechanism

- Simplified translation file loading by removing environment variable checks.
- Ensured translations are loaded from a consistent path within the project structure.
2025-10-12 15:03:48 +00:00
Marlon Alves
b0d261b305 fix/media content validation 2025-10-11 04:13:12 -03:00
Willian Coqueiro
c041986e26 Merge upstream/develop into develop 2025-10-10 02:11:44 +00:00
Davidson Gomes
0976109d27 Merge pull request #2025 from guispiller/main
Some checks failed
Check Code Quality / check-lint-and-build (push) Has been cancelled
Build Docker image / Build and Deploy (push) Has been cancelled
Security Scan / CodeQL Analysis (javascript) (push) Has been cancelled
Security Scan / Dependency Review (push) Has been cancelled
feat: convert LID to phoneNumber on GROUP_PARTICIPANTS_UPDATE webhook
2025-10-09 15:05:41 -03:00
Davidson Gomes
b808dda33b Merge pull request #2048 from dersonbsb2022/main
feat(chatwoot): comprehensive improvements to message handling, editing, deletion and i18n (translate messages)
2025-10-09 14:59:53 -03:00
Anderson Silva
98b7f15a43 fix(baileys): update to 7.0.0-rc.5 and fix assertSessions signature
Problem:
- GitHub Actions failing: Expected 1 arguments, but got 2
- Local had outdated Baileys 7.0.0-rc.3 in node_modules
- assertSessions signature changed between versions

Solution:
- Fresh npm install with Baileys 7.0.0-rc.5
- Updated assertSessions to pass only jids (no force param)
- Regenerated Prisma Client after reinstall
- Updated package-lock.json for version consistency

Changes:
- assertSessions now receives 1 argument (jids only)
- Kept force param in method signature for API compatibility
- Removed @ts-expect-error directives (no longer needed)

Tested:
-  Server starts successfully
-  Build passes without errors
-  Lint passes
2025-10-06 19:30:13 -03:00
Anderson Silva
94ddc0dfbe fix(baileys): use type assertion for assertSessions compatibility
Problem:
- GitHub Actions shows: Expected 1 arguments, but got 2
- Local environment shows: Expected 2 arguments, but got 1
- Different Baileys versions/definitions between environments

Solution:
- Use 'as any' type assertion for force parameter
- Maintains compatibility with both signature variations
- Allows code to work in all environments

Technical notes:
- Local: baileys@7.0.0-rc.5 expects 2 arguments (jids, force)
- GitHub Actions: May have different version/cache expecting 1 argument
- Type assertion bypasses strict type checking for cross-version compatibility
2025-10-06 19:12:32 -03:00
Anderson Silva
d4b0cfd2ba fix(chatwoot): resolve webhook timeout on deletion with 5+ images
Problem:
- Chatwoot shows red error when deleting messages with 5+ images
- Cause: Chatwoot webhook timeout of 5 seconds
- Processing 5 images takes ~9 seconds
- Duplicate webhooks arrive during processing

Solution:
- Implemented async processing with setImmediate()
- Webhook responds immediately (< 100ms)
- Deletion processes in background without blocking
- Maintains idempotency with cache (1 hour TTL)
- Maintains lock mechanism (60 seconds TTL)

Benefits:
- Scales infinitely (10, 20, 100+ images)
- No timeout regardless of quantity
- No error messages in Chatwoot
- Reliable background processing

Tested:
- 5 images: 9s background processing
- Webhook response: < 100ms
- No red error in Chatwoot
- Deletion completes successfully

BREAKING CHANGE: Fixed assertSessions signature to accept force parameter
2025-10-06 16:14:26 -03:00
dersonbsb2022
a5a46dc72a Merge branch 'develop' into main 2025-10-06 15:21:10 -03:00
Anderson Silva
e13434804c refactor: implement exponential backoff patterns and extract magic numbers to constants
- Extract HTTP timeout constant (60s for large file downloads)
- Extract S3/MinIO retry configuration (3 retries, 1s-8s exponential backoff)
- Extract database polling retry configuration (5 retries, 100ms-2s exponential backoff)
- Extract webhook and lock polling delays to named constants
- Extract cache TTL values (5min for messages, 30min for updates) in Baileys service
- Implement exponential backoff for S3/MinIO downloads following webhook controller pattern
- Implement exponential backoff for database polling removing fixed delays
- Add deletion event lock to prevent race conditions with duplicate webhooks
- Process deletion events immediately (no delay) to fix Chatwoot local storage red error
- Make i18n translations path configurable via TRANSLATIONS_BASE_DIR env variable
- Add detailed logging for deletion events debugging

Addresses code review suggestions from Sourcery AI and Copilot AI:
- Magic numbers extracted to well-documented constants
- Retry configurations consolidated and clearly separated by use case
- S3/MinIO retry uses longer delays (external storage)
- Database polling uses shorter delays (internal operations)
- Fixes Chatwoot local storage deletion error (red message issue)
- Maintains full compatibility with S3/MinIO storage (tested)

Breaking changes: None - all changes are internal improvements
2025-10-06 15:10:38 -03:00
Davidson Gomes
53cd7d5d13 chore(deps): update baileys package to version 7.0.0-rc.5
Some checks failed
Check Code Quality / check-lint-and-build (push) Has been cancelled
Build Docker image / Build and Deploy (push) Has been cancelled
Security Scan / CodeQL Analysis (javascript) (push) Has been cancelled
Security Scan / Dependency Review (push) Has been cancelled
- Bumped baileys dependency version in package.json and package-lock.json to 7.0.0-rc.5 for improved functionality and bug fixes.
- Added p-queue and p-timeout packages for enhanced performance and timeout management.
2025-10-06 14:29:22 -03:00
Spiller
bedfb019aa fix lint 2025-10-06 11:53:50 -03:00
Anderson Silva
6e1d027750 feat(chatwoot): comprehensive improvements to message handling, editing, deletion and i18n
- Fix bidirectional message deletion between Chatwoot and WhatsApp
- Support deletion of multiple attachments sent together
- Implement proper message editing with 'Edited Message:' prefix format
- Enable deletion of edited messages by updating chatwootMessageId
- Skip cache for deleted messages (messageStubType === 1) to prevent duplicates
- Fix i18n translation path detection for production environment
- Add automatic dev/prod path resolution for translation files
- Improve error handling and logging for message operations

Technical improvements:
- Changed Chatwoot deletion query from findFirst to findMany for multiple attachments
- Fixed instanceId override issue in message deletion payload
- Added retry logic with Prisma MessageUpdate validation
- Implemented cache bypass for revoked messages to ensure proper processing
- Enhanced i18n to detect dist/ folder in production vs src/ in development

Resolves issues with:
- Message deletion not working from Chatwoot to WhatsApp
- Multiple attachments causing incomplete deletion
- Edited messages showing raw i18n keys instead of translated text
- Cache collision preventing deletion of edited messages
- Production environment not loading translation files correctly

Note: Tested and validated with Chatwoot v4.1 in production environment
2025-10-03 14:47:24 -03:00
Spiller
fb1fa4d91a feat: add participantsData field maintaining backward
compatibility

  - Keep original participants array (string[]) for backward
  compatibility
  - Add new participantsData field with resolved phone numbers and
  metadata
  - Consumers can migrate gradually from participants to
  participantsData
  - No breaking changes to existing webhook integrations

  Payload structure:
  - participants: string[] (original JID strings)
  - participantsData: object[] (enhanced with phoneNumber, name,
  imgUrl)
2025-09-30 10:12:14 -03:00
Spiller
57ea6707bc feat: convert LID to phoneNumber on
GROUP_PARTICIPANTS_UPDATE
2025-09-29 20:50:39 -03:00
Davidson Gomes
ad8df44236 Merge pull request #2023 from Vitordotpy/fix/chatwoot-conversation-handling
Some checks failed
Check Code Quality / check-lint-and-build (push) Has been cancelled
Build Docker image / Build and Deploy (push) Has been cancelled
Security Scan / CodeQL Analysis (javascript) (push) Has been cancelled
Security Scan / Dependency Review (push) Has been cancelled
fix(chatwoot): Corrige Reabertura de Conversas e Loop de Mensagem de Conexão
2025-09-29 16:08:52 -03:00
Vitordotpy
c132379b3a fix(chatwoot): ajustar lógica de verificação de conversas e cache
Este commit modifica a lógica de verificação de conversas no serviço Chatwoot, garantindo que a busca por conversas ativas seja priorizada em relação ao uso de cache. A verificação de cache foi removida em pontos críticos para evitar que conversas desatualizadas sejam utilizadas, melhorando a precisão na recuperação de dados. Além disso, a lógica de reabertura de conversas foi refinada para garantir que as interações sejam tratadas corretamente, mantendo a experiência do usuário mais fluida.
2025-09-29 15:26:24 -03:00
Vitordotpy
f7862637b1 fix(chatwoot): otimizar lógica de reabertura de conversas e notificação de conexão
Este commit introduz melhorias na integração com o Chatwoot, focando na reabertura de conversas e na notificação de conexão. A lógica foi refatorada para centralizar a busca por conversas abertas e a reabertura de conversas resolvidas, garantindo que interações não sejam perdidas. Além disso, foi implementado um intervalo mínimo para notificações de conexão, evitando mensagens excessivas e melhorando a experiência do usuário.
2025-09-28 22:38:45 -03:00
Vitordotpy
0d8e8bc0fb fix(chatwoot): corrige reabertura de conversas e loop de conexão
Este commit aborda duas questões críticas na integração com o Chatwoot para melhorar a estabilidade e a experiência do agente.

Primeiro, as conversas que já estavam marcadas como "resolvidas" no Chatwoot não eram reabertas automaticamente quando o cliente enviava uma nova mensagem. Isso foi corrigido para que o sistema verifique o status da conversa e a reabra, garantindo que nenhuma nova interação seja perdida.

Segundo, um bug no tratamento do evento de conexão fazia com que a mensagem de status "Conexão estabelecida com sucesso" fosse enviada repetidamente, poluindo o histórico da conversa. A lógica foi ajustada para garantir que esta notificação seja enviada apenas uma vez por evento de conexão.
2025-09-28 22:19:36 -03:00
Davidson Gomes
b62917e80f Merge pull request #2021 from Vitordotpy/fix/message-update-and-i18n-errors
Some checks failed
Security Scan / CodeQL Analysis (javascript) (push) Has been cancelled
Security Scan / Dependency Review (push) Has been cancelled
Check Code Quality / check-lint-and-build (push) Has been cancelled
Build Docker image / Build and Deploy (push) Has been cancelled
fix(baileys): message update and i18n errors
2025-09-26 16:37:24 -03:00
Vitordotpy
eeb324227b fix(baileys): adicionar log de aviso para mensagens não encontradas
- Implementada uma mensagem de aviso no serviço Baileys quando a mensagem original não é encontrada durante a atualização, melhorando a rastreabilidade de erros.
- Ajustada a lógica de verificação do caminho de traduções para garantir que o diretório correto seja utilizado, com tratamento de erro caso não seja encontrado.
2025-09-26 16:12:40 -03:00
Vitordotpy
c31b62fb3d fix(baileys): corrigir verificação de mensagem no serviço Baileys
- Ajustada a lógica de verificação para garantir que o ID da mensagem seja definido apenas quando disponível, evitando possíveis erros de referência.
- Atualizada a definição do caminho de traduções para suportar a estrutura de diretórios em produção.
2025-09-26 16:00:39 -03:00
Davidson Gomes
22465c0a56 fix: corrigido incompatibilidade no use voise call da wavoip com versao nova da baileys 2025-09-26 13:00:52 -03:00
Davidson Gomes
da6f1bd540 chore(changelog): update CHANGELOG for Baileys v7.0.0-rc.4 and PostgreSQL connection improvements
- Added entry for Baileys version update to v7.0.0-rc.4.
- Refactored PostgreSQL connection handling and enhanced message processing capabilities.
2025-09-26 12:58:36 -03:00
Davidson Gomes
069786b9fe chore(deps): update baileys package to version 7.0.0-rc.4
- Bumped baileys dependency version in package.json and package-lock.json to 7.0.0-rc.4 for improved functionality and bug fixes.
2025-09-26 12:56:40 -03:00
Davidson Gomes
bd0c43feac Merge pull request #2017 from Vitordotpy/fix/enhanced-chatwoot-database-connection
Some checks are pending
Check Code Quality / check-lint-and-build (push) Waiting to run
Build Docker image / Build and Deploy (push) Waiting to run
Security Scan / CodeQL Analysis (javascript) (push) Waiting to run
Security Scan / Dependency Review (push) Waiting to run
Fix Chatwoot DB Connection Instability and Implement Stale Conversation Cache Handling
2025-09-26 07:35:26 -03:00
Vitordotpy
5dc1d02d0a refactor(chatbot): melhorar tratamento de erros em mensagens no Chatwoot
- Implementada a função `handleStaleConversationError` para centralizar a lógica de tratamento de erros relacionados a conversas não encontradas.
- A lógica de retry foi aprimorada para as funções `createMessage` e `sendData`, garantindo que as operações sejam reprocessadas corretamente em caso de falhas.
- Removido código duplicado e melhorada a legibilidade do serviço Chatwoot.
2025-09-25 17:38:10 -03:00
Vitor Manoel Santos Moura
8697329f71 Update src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts
aplicação de desestruturação de objetos que é uma boa prática do ts

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
2025-09-25 17:30:43 -03:00
Vitor Manoel Santos Moura
58b5561f72 Update src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts
aplicação de desestruturação de objetos que é uma boa prática do ts

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
2025-09-25 17:30:30 -03:00
Vitordotpy
093515555d refactor(chatbot): refatorar conexão com PostgreSQL e melhorar tratamento de mensagens
- Alterado método de obtenção da conexão PostgreSQL para ser assíncrono, melhorando a gestão de conexões.
- Implementada lógica de retry para criação de mensagens e conversas, garantindo maior robustez em caso de falhas.
- Ajustadas chamadas de consulta ao banco de dados para utilizar a nova abordagem de conexão.
- Adicionada nova propriedade `messageBodyForRetry` para facilitar o reenvio de mensagens em caso de erro.
2025-09-25 17:08:40 -03:00
Davidson Gomes
d8268b0eb1 fix(migration): resolve PostgreSQL migration error for Kafka integration
Some checks failed
Check Code Quality / check-lint-and-build (push) Has been cancelled
Build Docker image / Build and Deploy (push) Has been cancelled
Security Scan / CodeQL Analysis (javascript) (push) Has been cancelled
Security Scan / Dependency Review (push) Has been cancelled
- Corrected table reference in migration SQL to align with naming conventions.
- Fixed foreign key constraint issue that caused migration failure.
- Ensured successful setup of Kafka integration by addressing database migration errors.
2025-09-24 13:59:23 -03:00
Davidson Gomes
4585850741 chore(release): bump version to 2.3.5 and update bug report template
Some checks are pending
Check Code Quality / check-lint-and-build (push) Waiting to run
Build Docker image / Build and Deploy (push) Waiting to run
Security Scan / CodeQL Analysis (javascript) (push) Waiting to run
Security Scan / Dependency Review (push) Waiting to run
- Updated package and lock files to version 2.3.5.
- Modified bug report template to reflect the new version number.
- Removed outdated Kafka Docker README file.
2025-09-23 18:42:07 -03:00
Davidson Gomes
6c150eed6d chore(docker): add Kafka and frontend services to Docker configurations
- Introduced Kafka and Zookeeper services in a new docker-compose file for better message handling.
- Added frontend service to both development and production docker-compose files for improved UI management.
- Updated evolution-manager-v2 submodule to the latest commit.
- Updated CHANGELOG for version 2.3.5 release.
2025-09-23 18:40:19 -03:00
Davidson Gomes
78c7b96f0f Merge branch 'release/2.3.4'
Some checks failed
Check Code Quality / check-lint-and-build (push) Has been cancelled
Build Docker image / Build and Deploy (push) Has been cancelled
Security Scan / CodeQL Analysis (javascript) (push) Has been cancelled
Security Scan / Dependency Review (push) Has been cancelled
2025-09-23 11:42:25 -03:00
Davidson Gomes
dfea584aa7 chore(changelog): update CHANGELOG for version 2.3.4 release
Some checks are pending
Check Code Quality / check-lint-and-build (push) Waiting to run
Build Docker image / Build and Deploy (push) Waiting to run
Security Scan / CodeQL Analysis (javascript) (push) Waiting to run
Security Scan / Dependency Review (push) Waiting to run
- Enhanced EvolutionBot functionality with splitMessages and linkPreview support
- Centralized message splitting logic across chatbot services for consistency
- Improved message formatting and delivery capabilities
2025-09-23 11:41:51 -03:00
Davidson Gomes
6c5b056615 chore(changelog): remove empty line in CHANGELOG for consistency 2025-09-23 11:37:48 -03:00
Davidson Gomes
d8b4378163 Merge pull request #1986 from dersonbsb2022/main
Some checks failed
Check Code Quality / check-lint-and-build (push) Has been cancelled
Build Docker image / Build and Deploy (push) Has been cancelled
Security Scan / CodeQL Analysis (javascript) (push) Has been cancelled
Security Scan / Dependency Review (push) Has been cancelled
fix(evolutionbot):  Fixing the correct message sending method so that messages are split.
2025-09-22 08:02:11 -03:00
Davidson Gomes
838cc14531 Merge pull request #1989 from JamsMendez/update-docker-compose
Some checks are pending
Check Code Quality / check-lint-and-build (push) Waiting to run
Build Docker image / Build and Deploy (push) Waiting to run
Security Scan / CodeQL Analysis (javascript) (push) Waiting to run
Security Scan / Dependency Review (push) Waiting to run
fix(docker): change private image to public image in docker-compose
2025-09-21 15:18:13 -03:00
Jose A. Mendez Santiago
878da12fa4 fix(docker): change private image to public image in docker-compose 2025-09-20 22:02:25 -06:00
Anderson Silva
10a2b60595 refactor(chatbot): centralize split logic and ensure linkPreview consistency
- Centralize double-line-break message splitting logic into dedicated helper methods
- Add targeted debug logs for better observability without clutter
- Ensure linkPreview parameter is consistently passed across all chatbot services
- Extract splitMessageByDoubleLineBreaks() and sendSingleMessage() helpers
- Update all chatbot services to explicitly pass linkPreview: true
- Improve code testability and maintainability

Services updated:
- BaseChatbotService: Refactored split logic and added debug logs
- TypebotService: Added linkPreview parameter to all sendMessageWhatsApp calls
- OpenAIService: Added linkPreview parameter to all sendMessageWhatsApp calls
- N8nService: Added linkPreview parameter to sendMessageWhatsApp call
- FlowiseService: Added linkPreview parameter to sendMessageWhatsApp call
- EvoaiService: Added linkPreview parameter to sendMessageWhatsApp call
- DifyService: Added linkPreview parameter to all sendMessageWhatsApp calls
2025-09-20 11:35:15 -03:00
Anderson Silva
b0ca79cd11 fix(evolutionbot): implement splitMessages and linkPreview functionality
- Replace instance.textMessage() with sendMessageWhatsApp() method
- Enable message splitting by double line breaks (\n\n)
- Add proper delay and typing indicators between split messages
- Fix linkPreview parameter passing to base class methods
- Support linkPreview: false/true from webhook response
- Remove unnecessary debug logs for cleaner output

Fixes: EvolutionBot was not respecting splitMessages and linkPreview configurations
2025-09-20 10:57:03 -03:00
Davidson Gomes
71eb189a6d chore(changelog): update CHANGELOG for recent enhancements and fixes
Some checks failed
Security Scan / Dependency Review (push) Has been cancelled
Check Code Quality / check-lint-and-build (push) Has been cancelled
Build Docker image / Build and Deploy (push) Has been cancelled
Security Scan / CodeQL Analysis (javascript) (push) Has been cancelled
- Fixed `instanceName` field error in message creation, resolving Prisma validation issues.
- Enhanced media message processing across chatbot services, improving base64 conversion and media URL handling.
- Resolved ESLint configuration conflicts in Evolution Manager v2, updating rules and fixing code formatting issues.
- Streamlined media message handling and improved data consistency in database operations.
2025-09-18 17:47:54 -03:00
Davidson Gomes
407d254cf7 refactor(chatbot): streamline media message handling across chatbot services
- Removed redundant instance name references in EvolutionStartupService to enhance data consistency.
- Updated media message processing in various chatbot services to utilize base64 and mediaUrl more effectively, ensuring better handling of image messages.
- Improved overall code readability and maintainability by simplifying media handling logic.
2025-09-18 17:46:47 -03:00
Davidson Gomes
5f44da61fb feat(evolution-manager): add evolution-manager-v2 as a submodule and update changelog
- Introduced evolution-manager-v2 as a git submodule for easier access and integration.
- Updated CHANGELOG to reflect the addition of evolution-manager-v2 with details on its features and open-source setup.
- Adjusted media message handling in EvolutionStartupService to use null instead of undefined for base64 and mediaUrl properties, ensuring better data consistency.
2025-09-18 17:00:15 -03:00
Davidson Gomes
41a36bbb19 feat(changelog): update CHANGELOG for version 2.3.4
- Added Apache Kafka integration for real-time event streaming, including a new controller, router, and schema.
- Fixed MySQL schema issues related to default values and added missing relation fields in the Instance model.
- Introduced new environment variables for comprehensive Kafka configuration.
2025-09-18 15:48:27 -03:00
Davidson Gomes
8ab41fcfc9 feat(kafka): add Kafka integration for event streaming
- Introduced Kafka support in the Evolution API, allowing for real-time event streaming and processing.
- Updated environment configuration to include Kafka-related variables.
- Added KafkaController and KafkaRouter for managing Kafka events.
- Enhanced event management to support Kafka alongside existing integrations.
- Updated database schemas and migrations for Kafka integration in both MySQL and PostgreSQL.
- Documented Kafka integration in the README file.
2025-09-18 15:44:56 -03:00
Davidson Gomes
5e08628d89 refactor(eslint): change unused vars rule to error and update error handling in services
- Update ESLint configuration to set `@typescript-eslint/no-unused-vars` from 'warn' to 'error' for stricter linting.
- Refactor error handling in various services to omit error variable in catch blocks for cleaner code.
2025-09-18 14:59:33 -03:00
Davidson Gomes
4726c4727d Merge branch 'release/2.3.3'
Some checks failed
Check Code Quality / check-lint-and-build (push) Has been cancelled
Build Docker image / Build and Deploy (push) Has been cancelled
Security Scan / CodeQL Analysis (javascript) (push) Has been cancelled
Security Scan / Dependency Review (push) Has been cancelled
2025-09-18 14:55:45 -03:00
Davidson Gomes
0787a10f39 chore(dependabot): remove dependabot configuration file
Some checks are pending
Check Code Quality / check-lint-and-build (push) Waiting to run
Build Docker image / Build and Deploy (push) Waiting to run
Security Scan / CodeQL Analysis (javascript) (push) Waiting to run
Security Scan / Dependency Review (push) Waiting to run
2025-09-18 14:55:11 -03:00
Davidson Gomes
5bdd6ad9d8 chore(dependencies): upgrade eslint-plugin-simple-import-sort to version 12.1.1 2025-09-18 14:54:12 -03:00
Davidson Gomes
f602ba343f Merge branch 'hotfix/2.3.3' into develop 2025-09-18 14:49:20 -03:00
Davidson Gomes
9d42ad3495 refactor(eslint): update TypeScript object type usage and adjust linting rules
- Change usage of `Object` to `object` in various files for better type safety.
- Update ESLint configuration to change `@typescript-eslint/no-unused-vars` from 'error' to 'warn' and disable certain rules related to object types.
2025-09-18 14:48:56 -03:00
Davidson Gomes
e4da6a1763 Merge branch 'release/2.3.3' 2025-09-18 14:45:42 -03:00
Davidson Gomes
55125856fe chore(docker): upgrade Node.js version in Dockerfile and update dependencies
- Change base image from node:20-alpine to node:24-alpine in Dockerfile for both builder and final stages.
- Update package dependencies in package.json and package-lock.json, including @aws-sdk/client-sqs, @prisma/client, @sentry/node, multer, pino, and others to their latest versions.
- Adjust GitHub Actions workflows to use updated action versions for better performance and security.
2025-09-18 14:45:02 -03:00
Davidson Gomes
a1281927a8 Merge branch 'release/2.3.3' 2025-09-18 14:17:50 -03:00
Davidson Gomes
250cb61748 chore(changelog): update version date for release 2.3.3 2025-09-18 14:17:39 -03:00
Davidson Gomes
ff9ff60e3b Merge pull request #1955 from ricaelchiquetti/fix/upsert_remote_jid_alt
Fix/Adjust remote message key handling in WhatsApp service to include alternative JID
2025-09-18 09:30:28 -03:00
ricael
c6a7e2368b fix: ajustar a manipulação da chave remota da mensagem no serviço WhatsApp para incluir JID alternativo 2025-09-18 09:22:40 -03:00
Davidson Gomes
deb4494fc0 chore(commitlint): increase body and footer line length limits to 150 characters
Some checks are pending
Check Code Quality / check-lint-and-build (push) Waiting to run
Build Docker image / Build and Deploy (push) Waiting to run
Security Scan / CodeQL Analysis (javascript) (push) Waiting to run
Security Scan / Dependency Review (push) Waiting to run
- Update body-max-line-length and footer-max-line-length in commitlint configuration to allow for longer lines, enhancing readability and accommodating more detailed commit messages.
2025-09-17 16:52:03 -03:00
Davidson Gomes
0a357089b3 chore(commitlint): update line length rules for body and footer to 120 characters
- Adjust body-max-line-length and footer-max-line-length in commitlint configuration to allow for longer lines, improving readability and accommodating more detailed commit messages.
2025-09-17 16:51:46 -03:00
Davidson Gomes
3ddbd6a7fb feat(config): add telemetry and metrics configuration options
- Introduce new environment variables for telemetry and Prometheus metrics in .env.example
- Create example configuration files for Prometheus and Grafana dashboards
- Update main application to utilize new configuration settings for Sentry, audio converter, and proxy
- Enhance channel services to support audio conversion API integration
- Implement middleware for metrics IP whitelisting and basic authentication in routes
2025-09-17 16:50:36 -03:00
Davidson Gomes
81a991a62e docs(agents.md and claude.md): update and expand AI Agent guidelines in AGENTS.md and CLAUDE.md
Revise AGENTS.md to provide comprehensive guidelines for AI agents working with the Evolution API.
Enhance CLAUDE.md with detailed project overview, common development commands, and integration
points. Include architecture patterns, coding standards, and testing strategies for better clarity
and consistency.
2025-09-17 15:54:39 -03:00
Davidson Gomes
a721beda3c chore(rules): update input validation standards to use JSONSchema7 and add commit standards 2025-09-17 15:47:26 -03:00
Davidson Gomes
7088ad05d2 feat: add project guidelines and configuration files for development standards
- Introduce AGENTS.md for repository guidelines and project structure
- Add core development principles in .cursor/rules/core-development.mdc
- Establish project-specific context in .cursor/rules/project-context.mdc
- Implement Cursor IDE configuration in .cursor/rules/cursor.json
- Create specialized rules for controllers, services, DTOs, guards, routes, and integrations
- Update .gitignore to exclude unnecessary files
- Enhance CLAUDE.md with project overview and common development commands
2025-09-17 15:43:32 -03:00
Davidson Gomes
805f40c841 feat: add code quality tools and security policy
- Configure Husky with pre-commit and pre-push hooks
- Add commitlint for conventional commit validation
- Create comprehensive security policy (SECURITY.md)
- Add GitHub Actions for security scanning and dependency review
- Create PR and issue templates for better collaboration
- Add Canny.io references for community feedback
- Fix path traversal vulnerability in /assets endpoint
- Create MySQL schema sync analysis tools
2025-09-17 15:05:17 -03:00
Davidson Gomes
09ee2e6296 feat: integrate Husky and lint-staged for automated code quality checks; update changelog and README for new features 2025-09-17 14:49:26 -03:00
Davidson Gomes
dd931eee36 docs: changelog 2.3.3 2025-09-17 14:38:48 -03:00
Davidson Gomes
55822f9443 style: improve code formatting for better readability in WhatsApp service files 2025-09-17 14:30:27 -03:00
Davidson Gomes
00780157db style(sqs): format messageGroupId assignment for improved readability 2025-09-17 14:27:19 -03:00
Davidson Gomes
b514fab30e fix: address Path Traversal vulnerability in /assets endpoint by implementing security checks 2025-09-17 14:27:19 -03:00
Davidson Gomes
7ba878742e Merge pull request #1897 from nolramaf/feat/validate-video-type-before-uploading-to-s3
Some checks are pending
Build Docker image / Build and Deploy (push) Waiting to run
feat/validate video type before uploading to S3
2025-09-17 14:27:03 -03:00
Davidson Gomes
d998baa839 Merge pull request #1896 from nolramaf/feat/add-global-sqs-mode
feat/add global SQS mode with single-queue-per-event and payload size control
2025-09-17 14:26:15 -03:00
Davidson Gomes
e75dae38ee Merge pull request #1947 from elizandropacheco/feat/prometheus-metrics
Feat/prometheus metrics
2025-09-17 14:25:22 -03:00
Davidson Gomes
1fabb1f3bd Merge pull request #1951 from Nocelli/develop
feat: add extra fields to object sent to Flowise bot
2025-09-17 14:23:52 -03:00
Elizandro Pacheco
edfcb0c082 style(metrics): linted index.router.ts after eslint --fix 2025-09-17 12:05:30 -03:00
Davidson Gomes
827604644b Merge pull request #1918 from ricaelchiquetti/main
feat: implement standardized error handling for WhatsApp API responses
2025-09-17 11:40:23 -03:00
Rafael Nocelli Soares
481e179cc5 feat: add extra fields to object sent to Flowise bot 2025-09-17 10:49:13 -03:00
Davidson Gomes
5a39f1ae49 Merge pull request #1948 from furious/fix/download-media-bad-decrypt
fix: convert mediaKey from media messages to avoid bad decrypt errors
2025-09-17 08:52:50 -03:00
ricael
4378c33f42 Merge branch 'develop' into main_ 2025-09-17 08:51:16 -03:00
ricael
ab4bec3b54 Merge branch 'develop' into main 2025-09-17 08:50:39 -03:00
furious
3eeffe4586 fix: convert mediaKey from media messages to avoid bad decrypt errors 2025-09-16 23:02:36 -03:00
Elizandro Pacheco
0e737d48c1 chore(metrics): use 'unknown' as fallback for clientName label 2025-09-16 19:40:21 -03:00
Elizandro Pacheco
a3223ec890 chore: local compose/image tweaks for testing metrics (not part of PR) 2025-09-16 19:35:23 -03:00
Elizandro Pacheco
875b874a7b feat: add Prometheus-compatible /metrics endpoint (gated by PROMETHEUS_METRICS) 2025-09-16 19:35:23 -03:00
Davidson Gomes
486645fb08 chore: update Baileys dependency to version 7.0.0-rc.3 and improve message key handling in WhatsApp service
Some checks failed
Build Docker image / Build and Deploy (push) Has been cancelled
2025-09-15 16:21:33 -03:00
Davidson Gomes
bfe7030fab docs: add CLAUDE.md for development guidance and project architecture overview 2025-09-10 13:07:41 -03:00
Davidson Gomes
1320ec8d4f chore: update Baileys dependency to version 7.0.0-rc.2 in package-lock.json
Some checks failed
Build Docker image / Build and Deploy (push) Has been cancelled
2025-09-10 10:56:15 -03:00
Davidson Gomes
d31d6fa554 refactor: replace JSON path queries with raw SQL in Baileys and Chatwoot services to improve message retrieval and update logic
Some checks are pending
Build Docker image / Build and Deploy (push) Waiting to run
2025-09-09 16:18:15 -03:00
Davidson Gomes
edbf36019e Merge pull request #1929 from moothz/main
Customizable Websockets Security
2025-09-09 16:06:31 -03:00
moothz
d67eb3202b Fix lint 2025-09-09 15:46:26 -03:00
moothz
0aa6c96765 Customizable Websockets Security
Enables the option to specify safe remote addresses using WEBSOCKET_ALLOWED_HOSTS enviroment variables. Defaults to the secure only localhost.
2025-09-09 14:56:11 -03:00
Davidson Gomes
8619e320bc Merge pull request #1928 from josiasmaceda/hotfix/chatbot-not-return-after-human
fix: allowing the chatbot return after the time expires and after human interaction (stopBotFromMe)
2025-09-09 14:33:20 -03:00
Davidson Gomes
5015cfbc9d Merge pull request #1927 from josiasmaceda/hotfix/change-typebot-status-webhook
fix: integrate Typebot status change events for webhook in chatbot controller e service
2025-09-09 14:31:57 -03:00
Josias Maceda
cf548eedbe fix: lint with npm run lint 2025-09-09 14:19:54 -03:00
Josias Maceda
b9ae40145d fix: the lint with npm run lint 2025-09-09 14:15:16 -03:00
Josias Maceda
bc9724a929 fix: remove abort process when status is paused, allowing the chatbot return after the time expires and after being paused due to human interaction (stopBotFromMe) 2025-09-09 13:54:53 -03:00
Davidson Gomes
21502b996d fix: enhance message content sanitization in Baileys service and improve message retrieval logic in Chatwoot service 2025-09-09 12:50:51 -03:00
Davidson Gomes
d9c04fc866 Merge pull request #1906 from andres99x/enhancmenet/check-chatwoot-cron-id
fix: Prevent Duplicate Cron Jobs for Chatwoot Message Sync
2025-09-09 12:20:27 -03:00
Davidson Gomes
d6a76a096d Merge pull request #1908 from dersonbsb2022/main
feat: implement disable/enable linkPreview support for Evolution Bot
2025-09-09 12:19:57 -03:00
Josias Maceda
0116bc4c9f fix: integrate Typebot status change events for webhook in chatbot controller and service 2025-09-09 11:49:13 -03:00
Davidson Gomes
6da79f0313 chore: update CHANGELOG for Baileys v7.0.0-rc.2 and implement message content sanitization in Baileys service
Some checks are pending
Build Docker image / Build and Deploy (push) Waiting to run
2025-09-08 19:53:52 -03:00
Davidson Gomes
8830f476e8 chore: bump version to 2.3.3 in package-lock.json and update remoteJid handling in Baileys service
Some checks are pending
Build Docker image / Build and Deploy (push) Waiting to run
2025-09-08 15:48:38 -03:00
Davidson Gomes
e864b18561 chore: update .gitignore to include 'Baileys' directory 2025-09-08 15:33:54 -03:00
Davidson Gomes
9cb703a427 Merge pull request #1919 from EvolutionAPI/revert-1732-testmsg
Revert "Ignore events that are not messages (like EPHEMERAL_SYNC_RESPONSE)"
2025-09-08 14:49:20 -03:00
ricael
05fcdd9ffc refactor: melhora na formatação do log de erro da API do WhatsApp 2025-09-08 14:45:33 -03:00
Davidson Gomes
3ecf288daf Revert "Ignore events that are not messages (like EPHEMERAL_SYNC_RESPONSE)" 2025-09-08 14:45:29 -03:00
Davidson Gomes
3989ff928b chore: update CHANGELOG for version 2.3.3 and update baileys dependency to latest commit 2025-09-08 14:44:28 -03:00
ricael
16c0a8033f add stringify 2025-09-08 14:36:09 -03:00
ricael
79438c9445 refactor: lint fix 2025-09-08 09:11:45 -03:00
ricael
10a2c001ab feat: implement standardized error handling for WhatsApp API responses 2025-09-08 08:48:49 -03:00
Marlon Alves
7486d22ed1 feat/change variable messageGroupId 2025-09-05 07:18:07 -03:00
Anderson Silva
4f13a8ac28 style: clean up code formatting for linkPreview implementation
- Remove unnecessary trailing whitespace
- Use shorthand property syntax for linkPreview parameter
- Apply ESLint formatting standards
- Maintain code consistency and readability
2025-09-04 14:35:56 -03:00
Anderson Silva
1be58c8487 refactor: improve linkPreview implementation based on PR feedback
- Default linkPreview to true when not specified for backward compatibility
- Validate linkPreview is boolean before passing to textMessage
- Consolidate debug logs and remove sensitive data from logging
- Sanitize API keys in debug output ([REDACTED])
- Reduce log verbosity while maintaining debugging capability
- Ensure robust fallback behavior for malformed responses

Addresses PR feedback regarding:
- Backward compatibility preservation
- Security considerations in logging
- Input validation and error handling
2025-09-04 12:19:51 -03:00
Andres Pache
613d486fc2 stop tasks with missmatch cronId 2025-09-04 09:03:52 -03:00
Marlon Alves
025b183ebf feat/change variable GROUP_UPDATE to GROUPS_UPDATE 2025-09-04 03:18:35 -03:00
Anderson Silva
ceddace915 chore: remove documentation .md files
- Remove evolution-bot-documentation.md
- Remove evolution-bot-linkpreview-example.md
- Remove send-text-api-documentation.md
- Keep only the core linkPreview implementation
2025-09-03 19:59:24 -03:00
Anderson Silva
02b81beb7a feat: implement linkPreview support for Evolution Bot
- Add linkPreview extraction from webhook/n8n response
- Implement linkPreview parameter in textMessage calls
- Add debug logging for linkPreview functionality
- Support for disabling link previews when linkPreview: false
- Add comprehensive documentation for linkPreview feature

Usage:
- Return { "message": "text", "linkPreview": false } from webhook to disable preview
- Return { "message": "text", "linkPreview": true } from webhook to enable preview
- Omit linkPreview for default WhatsApp behavior
2025-09-03 16:08:15 -03:00
Andres Pache
43cc6d3608 check cronId before executing syncChatwootLostMessages 2025-09-03 12:16:00 -03:00
Marlon Alves
edb4fa3b3e feat/force MessageGroupId 2025-09-03 06:21:24 -03:00
Marlon Alves
17120e91a4 feat/force MessageGroupId 2025-09-03 05:57:18 -03:00
Marlon Alves
23cd6d2fd8 feat/force to save all evolution events in a single SQS queue 2025-09-02 17:44:44 -03:00
Davidson Gomes
c2085b59ea Merge branch 'release/2.3.2'
Some checks failed
Build Docker image / Build and Deploy (push) Has been cancelled
2025-09-02 13:33:39 -03:00
Davidson Gomes
2513f96178 chore: bump version to 2.3.2 in package.json and package-lock.json
Some checks failed
Build Docker image / Build and Deploy (push) Has been cancelled
2025-09-02 13:33:34 -03:00
Davidson Gomes
70a2c18146 Merge branch 'release/2.3.2' 2025-09-02 10:53:10 -03:00
Davidson Gomes
b8953f1431 chore: update CHANGELOG for version 2.3.2
- Added support for socks proxy.
- Included key id in webhook payload for n8n service.
- Enhanced RabbitMQ controller with improved connection management and shutdown procedures.
- Converted outgoing images to JPEG format before sending with Chatwoot.
- Updated baileys dependency to version 6.7.19.
2025-09-02 10:53:00 -03:00
nolramaf
9ab6f9ad76 Update src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
2025-09-01 19:50:08 -03:00
nolramaf
9beb38d807 Update src/config/env.config.ts
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
2025-09-01 19:50:01 -03:00
nolramaf
e48e878b4f Update src/api/integrations/channel/meta/whatsapp.business.service.ts
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
2025-09-01 19:49:55 -03:00
Marlon Alves
293f655274 feat/validate video type before uploading to S3 2025-09-01 19:41:56 -03:00
Marlon Alves
cca929e7fc feat/add global SQS mode with single-queue-per-event and payload size control 2025-09-01 19:18:55 -03:00
Davidson Gomes
a91a4ad19c Merge pull request #1889 from codingbox2022/main
Some checks failed
Build Docker image / Build and Deploy (push) Has been cancelled
update version
2025-09-01 11:00:37 -03:00
Davidson Gomes
00ba227e15 chore: update baileys dependency to version 6.7.19 2025-09-01 10:43:12 -03:00
codingbox2022
c02d37028b Merge branch 'main' into main 2025-08-29 22:08:52 -05:00
Davidson Gomes
14771ab84e Merge pull request #1851 from caiquezanetoni/patch-1
Some checks failed
Build Docker image / Build and Deploy (push) Has been cancelled
Update 20250116001415_add_wavoip_token_to_settings_table
2025-08-29 07:17:02 -03:00
Davidson Gomes
20352e77ef Merge pull request #1878 from LuisSantosJS/reconnect-rabbitmq
Reconnect rabbitmq
2025-08-29 07:16:13 -03:00
Davidson Gomes
78f7618d04 Merge pull request #1884 from nestordavalos/feat/whatsapp/convert-to-jpeg
Feat/whatsapp/convert-to-jpeg
2025-08-29 07:14:56 -03:00
nestordavalos
57b19d85d5 feat(whatsapp): Convert outgoing images to JPEG before sending
All images sent via the Baileys integration are now pre-processed and converted to JPEG format using the `sharp` library. This ensures better compatibility and prevents potential issues with unsupported formats.

- Images from URLs are now downloaded via axios before processing, which allows for the use of a proxy.
- The default filename and mimetype are updated to `image.jpg` and `image/jpeg` to reflect the conversion.
2025-08-28 00:17:15 -03:00
luissantosjs
b325500310 improve rabbit controller 2025-08-26 19:15:59 +01:00
luissantosjs
4681576cfc up 2025-08-22 22:49:40 +01:00
luissantosjs
7a99fba556 feat: enhance RabbitMQ controller with improved connection management and shutdown procedures 2025-08-20 12:29:45 +01:00
Caíque Zanetoni Fim
6e652d6ea2 Update migration.sql 2025-08-18 08:43:21 -03:00
Davidson Gomes
33a922995b Merge pull request #1838 from bilaliqbalr/patch-2
Some checks failed
Build Docker image / Build and Deploy (push) Has been cancelled
Added key id into webhook payload in n8n service
2025-08-15 12:49:10 -03:00
Bilal Iqbal
74cb65c4ea Added key id into webhook payload in n8n service 2025-08-14 00:51:41 +05:00
Davidson Gomes
f9c4255500 Merge pull request #1809 from neto-developer/patch-1
Some checks failed
Build Docker image / Build and Deploy (push) Has been cancelled
docs(readme): corrigidos badge Docker image no README
2025-08-07 08:22:26 -03:00
Davidson Gomes
af423cef28 Merge pull request #1793 from henrybarreto/feat/add-support-socks-proxy
feat: add support to socks proxy
2025-08-07 08:22:00 -03:00
Davidson Gomes
40ce6b56ca Merge pull request #1790 from henrybarreto/feat/improve-test-proxy-error
feat: enhance logging for proxy testing errors
2025-08-07 08:21:48 -03:00
Neto, Aristides da Silva
4945345519 docs(readme): corrigidos badge Docker image no README
Corrigida formatação do badge Docker image no README
2025-08-05 22:07:20 -03:00
Henry Barreto
ab9e0edad6 feat: enhance logging for proxy testing errors
This commit improves the logging in the testProxy method of the
ProxyController class. Now, when an Axios error occurs, the specific
error message will be logged if available. For unexpected errors, the
error object is included for better insight.

For reference, see the "message" field in the Axios documentation:
[Axios Error Handling](https://axios-http.com/docs/handling_errors).
2025-08-05 07:08:49 -03:00
Henry Barreto
3390958314 feat: add support to socks proxy 2025-08-05 07:08:00 -03:00
Davidson Gomes
a8343a8739 Merge pull request #1802 from frieck/main
Some checks failed
Build Docker image / Build and Deploy (push) Has been cancelled
Securing websockets
2025-08-04 19:18:30 -03:00
Davidson Gomes
dab66fc8c8 Merge pull request #1786 from bilaliqbalr/patch-1
Fixed boolean and integer type attributes for MySQL
2025-08-04 19:17:49 -03:00
Davidson Gomes
03a44cf9b2 Merge pull request #1798 from apresentame/fix/webhook_event
Permitir correta utilização da evolution quando mensagens não são persistidas no DB
2025-08-04 18:52:58 -03:00
Felipe Augusto Rieck
fb11f3f99c Code quality 2025-08-04 18:19:14 -03:00
Felipe Augusto Rieck
d4eb61f64d Improving localhost check 2025-08-04 18:14:33 -03:00
Felipe Augusto Rieck
4f043f9576 Securing websockets 2025-08-04 16:34:20 -03:00
William Dumes
79f4a22217 refactor: lint check 2025-08-04 13:56:16 -03:00
William Dumes
20dd1b1660 Merge branch 'develop' of github.com:apresentame/evolution-api into fix/webhook_event 2025-08-04 13:52:07 -03:00
William Dumes
fe8280ab7b Merge branch 'main' of github.com:apresentame/evolution-api into fix/webhook_event 2025-08-01 09:41:28 -03:00
William Dumes
bc11d0f751 fix: corrigido delete de mensagem quando nao salvo no banco de dados 2025-07-31 17:27:06 -03:00
Bilal Iqbal
095e435561 Fixed boolean and integer type attributes 2025-07-31 14:26:05 +05:00
Davidson Gomes
96f4b80d46 Merge branch 'release/2.3.1' into develop
Some checks failed
Build Docker image / Build and Deploy (push) Has been cancelled
2025-07-29 09:20:29 -03:00
Davidson Gomes
9cdb897a0f Merge branch 'release/1.3.1'
Some checks failed
Build Docker image / Build and Deploy (push) Has been cancelled
2025-07-29 09:20:16 -03:00
Davidson Gomes
b5c67774dc chore: remove CONFIG_SESSION_PHONE_VERSION and update related code
- Removed the CONFIG_SESSION_PHONE_VERSION environment variable from the configuration and Docker files.
- Updated the BaileysStartupService to directly fetch the latest WhatsApp Web version without relying on the removed environment variable.
- Adjusted the index router to reflect the changes in the WhatsApp Web version retrieval.
2025-07-29 09:20:08 -03:00
Davidson Gomes
7f8293f4c6 changelog: v2.3.1 2025-07-29 09:16:19 -03:00
William Dumes
a62f9ebe46 chore: voltar porta do dockerfile 2025-07-28 11:19:07 -03:00
William Dumes
69726f0dc2 fix(evo): melhorado controle de recebimento do ack das msgs 2025-07-28 09:24:22 -03:00
William Dumes
6101c8d651 fix: corrigido disparo de eventos quando nao usa a opcao da ENV de salvar mensagens no banco 2025-07-25 17:08:14 -03:00
Davidson Gomes
c66485ef98 Merge pull request #1748 from coreh/fix-url-corruption-querystring
Some checks failed
Build Docker image / Build and Deploy (push) Has been cancelled
Avoid corrupting media URLs with query strings
2025-07-25 16:56:06 -03:00
Davidson Gomes
4a25cd1ff7 Merge pull request #1757 from KokeroO/develop
fix: atualizar o handle de erros de eventos
2025-07-25 16:54:28 -03:00
Willian Coqueiro
23f54d1d96 lint 2025-07-25 12:13:56 +00:00
Willian Coqueiro
5191438acf fix: update error handling messages and correct group parameter in createContact method 2025-07-25 12:02:17 +00:00
Marco Buono
96d3ec2017 fix: avoid corrupting URLs with query strings 2025-07-23 16:06:14 -03:00
Davidson Gomes
cf95c027eb chore: update package dependencies and scripts
Some checks failed
Build Docker image / Build and Deploy (push) Has been cancelled
- Changed the `baileys` dependency source from `EvolutionAPI` to `WhiskeySockets`.
- Updated the start and development server scripts to use `tsx` instead of `tsnd`.
- Added `tsx` as a new dependency and removed `ts-node-dev`.
- Updated `music-metadata` to version 11.7.1 and adjusted its dependencies.
- Cleaned up the package-lock by removing unused modules and adding new ones like `fflate` and `uint8array-extras`.
2025-07-22 18:21:46 -03:00
Davidson Gomes
9b7ca4bfb7 Merge pull request #1743 from foqc/foqc/develop
Some checks are pending
Build Docker image / Build and Deploy (push) Waiting to run
Add  endpoint to retrieve chat data by remoteJid
2025-07-21 16:13:59 -03:00
foqc
d490f8f576 feature: run lint to clean up codebase 2025-07-21 12:26:08 -05:00
foqc
68e847d10e feature: add endpoint to retrieve chat data by phone number 2025-07-21 12:21:45 -05:00
Davidson Gomes
b98cd11fb1 Merge pull request #1736 from juniortopanotti/main
Some checks are pending
Build Docker image / Build and Deploy (push) Waiting to run
remove lógica de paginação duplicada no fetchChats que causa resultados vazios quando skip > 0
2025-07-21 13:16:22 -03:00
Davidson Gomes
5386d7171b Merge pull request #1732 from rafwell/testmsg
Ignore events that are not messages (like EPHEMERAL_SYNC_RESPONSE)
2025-07-21 13:15:38 -03:00
Davidson Gomes
f59cae7ee2 Merge pull request #1729 from ricaelchiquetti/main
improv: Ajustado isEmoji para aceitar todos os emojis.
2025-07-21 13:15:17 -03:00
Davidson Gomes
0e358cf9c1 Merge pull request #1696 from foqc/foqc/develop
Preserve alias casing in chat fetch query
2025-07-21 13:14:33 -03:00
Gilberto Topanotti Junior
6954472070 remove lógica de paginação duplicada no fetchChats que causa resultados vazios quando skip > 0
Problema:
O método fetchChats estava aplicando a lógica de paginação duas vezes, causando resultados vazios ao usar o parâmetro skip com valores maiores que 0.
Causa Raiz:
A query SQL já aplica LIMIT e OFFSET corretamente
O código JavaScript então aplica .slice(skip, skip + take) nos resultados já paginados
Este "offset duplo" faz com que o slice tente acessar posições do array que não existem
Exemplo do bug:
Requisição: {"take": 10, "skip": 10}
SQL: LIMIT 10 OFFSET 10 → retorna chats 11-20 (10 itens)
JS: .slice(10, 20) → tenta pegar posições 10-20 de um array com apenas 10 itens
Resultado: [] (array vazio)
Solução:
Removida a lógica de paginação JavaScript redundante (linhas 796-800) já que a query SQL já manipula a paginação corretamente com LIMIT e OFFSET.
Arquivos Alterados:
src/api/services/channel.service.ts
Testes:
 {"take": 10, "skip": 0} - Retorna os primeiros 10 chats
 {"take": 10, "skip": 10} - Retorna chats 11-20 (anteriormente retornava [])
 {"take": 5, "skip": 15} - Retorna chats 16-20 (anteriormente retornava [])
Impacto:
Corrige a funcionalidade de paginação para todos os valores de skip > 0
Mantém compatibilidade com versões anteriores
Sem mudanças que quebrem implementações existentes
2025-07-18 14:20:22 -03:00
Rafael Souza
afd0e01ddb fix lint 2025-07-17 15:59:33 -03:00
Rafael Souza
b3dae7a68e Ignore events that are not messages 2025-07-17 15:40:31 -03:00
Rafael Souza
44d4781f6f Ignore events that are not messages 2025-07-17 15:37:37 -03:00
Rafael Souza
e304b1dcdf Fix erro key 2025-07-17 15:32:25 -03:00
Rafael Souza
f8f2153cb4 Fix erro key 2025-07-17 15:29:04 -03:00
codingbox2022
192c34caa0 Update docker-compose.yaml 2025-07-17 12:47:36 -05:00
Davidson Gomes
cdef7dc9f9 Merge pull request #1728 from KokeroO/develop
Some checks failed
Build Docker image / Build and Deploy (push) Has been cancelled
Fix: [Chatwoot] Corrige mensagens editas
2025-07-17 11:55:55 -03:00
ricael
85798b209c improv: remover o emojiRefex() da função isEmoji. 2025-07-17 09:43:52 -03:00
ricael
71ebecbed3 improv: ajustado a validação aceitar todos os emojis. 2025-07-17 09:17:06 -03:00
Willian Coqueiro
419300b31f refactor: simplify edited message check in BaileysStartupService 2025-07-17 02:24:21 +00:00
Davidson Gomes
9fd40a411a Merge pull request #1726 from Santosl2/feat/add-async-lock
Some checks are pending
Build Docker image / Build and Deploy (push) Waiting to run
feat: add BaileysMessageProcessor for improved message handling
2025-07-16 19:24:23 -03:00
Davidson Gomes
d98fa5259e Merge branch 'develop' into feat/add-async-lock 2025-07-16 19:22:44 -03:00
Santosl2
05886ec684 feat: enhance message processing with retry logic for error handling 2025-07-15 21:41:25 -03:00
Santosl2
89d4d341f6 feat: add BaileysMessageProcessor for improved message handling and integrate rxjs for asynchronous processing 2025-07-15 21:35:25 -03:00
foqc
1ca829c00b feature: run lint to clean up codebase 2025-07-15 16:40:17 -05:00
Davidson Gomes
e321609b93 refactor: Simplify conditional check for contact updates in ChatwootService
Some checks failed
Build Docker image / Build and Deploy (push) Has been cancelled
2025-07-14 14:52:02 -03:00
Davidson Gomes
196617507e Merge pull request #1700 from guilherme-aguilar/guilheme_aguilar/develop
feat(database): add pgbouncer support and optimize postgres config
2025-07-14 14:51:49 -03:00
Davidson Gomes
6da71f5161 Merge pull request #1683 from pauloboc/develop
added missing migrations in mysql prisma
2025-07-14 14:51:21 -03:00
Davidson Gomes
5d0278a589 Merge branch 'develop' into foqc/develop 2025-07-14 14:46:36 -03:00
Davidson Gomes
2fb3eac383 Merge pull request #1715 from AlexisJusviack/fix/getBase64-template-support
Fix: Support media extraction from templateMessage in getBase64FromMediaMessage
2025-07-14 14:44:43 -03:00
Davidson Gomes
4a5696eda9 Merge pull request #1704 from rafwell/develop
Throw exception if download media fail
2025-07-14 14:44:29 -03:00
Davidson Gomes
11520481ba Merge pull request #1705 from KokeroO/develop
fix: Tratar conversas @lid no inicio do recebimento dos eventos e novo erro da libsignal
2025-07-14 14:43:57 -03:00
Davidson Gomes
95a53d33ef Merge pull request #1701 from leonardocintra/patch-1
Update README.md - 2025
2025-07-14 14:43:20 -03:00
Davidson Gomes
d458c978f3 Merge branch 'develop' into develop 2025-07-14 14:42:18 -03:00
AlexisJusviack
bd35d7977c Fix: Support media extraction from templateMessage in getBase64FromMediaMessage
### Fix: Add support for templateMessage media in getBase64FromMediaMessage

#### What this does
Adds support to download media from `templateMessage` structures in `getBase64FromMediaMessage`, by checking for `hydratedTemplate` and `hydratedFourRowTemplate`.

#### Why it's needed
Currently, media inside templates (e.g. `imageMessage`, `videoMessage`, `documentMessage`) is not processed by the method, which leads to errors or media being skipped.

#### How it works
If a `templateMessage` is detected, the code looks into the inner hydrated template and assigns the correct `mediaMessage` and `mediaType`. Then it proceeds as usual with the download logic.

#### Example message
```json
{
  "message": {
    "templateMessage": {
      "hydratedTemplate": {
        "imageMessage": {
          "mimetype": "image/jpeg",
          "fileLength": 123456,
          "url": "https://..."
        }
      }
    }
  }
}
2025-07-11 23:50:58 -03:00
guilherme
e92961e7b0 feat(database): add psql_bouncer support and simplify postgresql config
- Add new psql_bouncer database provider option
- Update database scripts to handle psql_bouncer provider
- Comment out pgbouncer service in docker-compose
- Simplify postgresql schema by removing directUrl
- Add new psql_bouncer-schema.prisma file
- Update .env.example with psql_bouncer configuration
- Modify runWithProvider.js to handle psql_bouncer migrations
2025-07-10 01:08:08 -03:00
Rafael Souza
f11d490f7a Remove package-lock.json do .gititnore 2025-07-09 18:56:32 -03:00
Willian Coqueiro
37319966db Remove redudantent code 2025-07-09 18:42:35 +00:00
Willian Coqueiro
630f5c5624 fix:
- [Baileys] Trocar @lids em remoteJid por senderPn em todos os serviços;
 - [Baileys] Adicionar valor @lid recebido em remoteJid para previousRemoteJid (Posteriormente utilizasse em ChatwootService);
 - Minors fixes;
2025-07-09 18:35:57 +00:00
Leonardo Nascimento Cintra
3e690fe9e2 Update README.md - 2025 2025-07-08 20:00:42 -03:00
guilherme
09429e68fe docs: update database name in .env.example from evolution to evolution_db 2025-07-08 18:09:59 -03:00
Rafael Souza
9acccf723d Throw exception if download media fail 2025-07-08 17:43:21 -03:00
guilherme
3b920f93c5 feat(database): add pgbouncer support and optimize postgres config
- Add pgbouncer service to handle connection pooling
- Update database connection URIs to support direct and pooled connections
- Optimize postgres configuration with better memory settings
- Update prisma schema to support directUrl connection
2025-07-08 17:14:37 -03:00
foqc
85936dcaed feature: Correctly map SQL query results by enforcing quoted column aliases (update missing mappings) 2025-07-07 17:40:30 -05:00
foqc
333ef3eeb8 feature: Correctly map SQL query results by enforcing quoted column aliases 2025-07-07 17:31:49 -05:00
Paulo Ferreira
8dd51b0302 added missing migrations in mysql prisma 2025-07-03 15:20:03 -03:00
Davidson Gomes
e6ec706a38 Merge pull request #1665 from pauloboc/fix-prisma-type-mysql
Fix prisma type mysql
2025-07-02 11:22:48 -03:00
Davidson Gomes
53101d4571 Merge pull request #1664 from pauloboc/fix-setup-mysql
(mysql): remove out-of-order wavoipToken migration
2025-07-01 08:15:26 -03:00
Davidson Gomes
c7b5abce6e Merge pull request #1670 from Santosl2/fix/typebot-variables
fix: correçao do typebot não conseguir ouvir mensagens de input
2025-07-01 08:14:28 -03:00
Santosl2
5b1b5ff9d2 fix: bind applyFormatting method in processMessages to maintain context 2025-06-29 20:23:31 -03:00
codingbox2022
505490d237 Update docker-compose.yaml 2025-06-28 22:36:21 -05:00
codingbox2022
675745ae3c Update docker-compose.yaml 2025-06-28 22:28:42 -05:00
codingbox2022
6dfbfe2d83 Update docker-compose.yaml 2025-06-28 22:27:42 -05:00
codingbox2022
1c247498d8 Update docker-compose.yaml 2025-06-28 22:25:44 -05:00
codingbox2022
419324837c Update docker-compose.yaml 2025-06-28 22:05:12 -05:00
codingbox2022
17f97fb051 Update docker-compose.yaml 2025-06-28 22:03:45 -05:00
codingbox2022
f7e7a6c901 Update docker-compose.yaml 2025-06-28 22:02:34 -05:00
codingbox2022
b35b33ca50 Update docker-compose.yaml 2025-06-28 22:00:52 -05:00
Paulo Ferreira
3efe69ada3 fix(prisma) Mysql: update data types for N8n, N8nSetting, Evoai, and EvoaiSetting models 2025-06-28 09:34:52 -03:00
Paulo Ferreira
287c679ce4 (mysql): remove out-of-order wavoipToken migration
Delete prisma/mysql-migrations/1707735894523_add_wavoip_token_to_settings_table, which executes before the initial Setting table is created and breaks fresh MySQL installs.

The later migration 20250214181954_add_wavoip_token_column, line 145, already adds the column correctly, so keeping only that directory guarantees a clean deploy.
2025-06-28 09:27:13 -03:00
Davidson Gomes
918697866f fix(package-lock): update baileys dependency to latest commit hash 2025-06-27 09:59:40 -03:00
Davidson Gomes
3c917af602 Merge pull request #1660 from KokeroO/develop
fix(whatsapp-baileys): Verifica eventos com falhas e fallback para erro ao baixar mídias
2025-06-27 09:55:22 -03:00
KokeroO
52798fd761 fix(whatsapp-baileys): adjust indentation and access modifier for getBase64FromMediaMessage method 2025-06-27 09:29:12 -03:00
KokeroO
34446d188e fix(whatsapp-baileys):
- Implement broken event checking before duplicate message checking. (Do not process failed events).
- Implement error handling when downloading media with a fallback mechanism.
2025-06-27 09:03:32 -03:00
Davidson Gomes
ef31b6de1f Merge pull request #1655 from KokeroO/develop
fix/references-instanceName-typebot
2025-06-26 17:10:58 -03:00
KokeroO
d7afe5d7ab fix(typebot): update instance query to use instance name instead of instance ID 2025-06-26 14:53:54 -03:00
Davidson Gomes
72fb2f408b feat(rabbitmq): implement robust connection handling with reconnection logic and error logging 2025-06-26 14:25:37 -03:00
KokeroO
e4f7856ca9 fix(typebot): update instance query to use instanceName instead of instanceId 2025-06-26 14:05:28 -03:00
Davidson Gomes
e86b6463fd Merge pull request #1652 from KokeroO/patch-1
Corrige ref. instance typebot.controller.ts
2025-06-26 11:49:41 -03:00
Willian Coqueiro
0302944654 Corrige ref. instance typebot.controller.ts 2025-06-26 11:42:27 -03:00
Davidson Gomes
1b0d81b022 Merge pull request #1641 from caduzin02/patch-1
Create railway.json
2025-06-25 16:32:30 -03:00
Davidson Gomes
854d357518 chore(package-lock): update Baileys dependency to version 6.7.18 2025-06-25 16:25:16 -03:00
caduzin02
6e8da4a8dc Create railway.json 2025-06-24 11:02:00 -03:00
Davidson Gomes
e92e98dd22 chore: optimize Dockerfile by using npm ci for dependency installation and streamlining file copy operations 2025-06-23 16:55:04 -03:00
Davidson Gomes
cdd2e59755 chore: update CHANGELOG for version 2.3.1 to include fix for S3 media upload 2025-06-23 16:51:54 -03:00
Davidson Gomes
1a1d9fc957 refactor: add validation for media content in Evolution and Business services to enhance error handling 2025-06-23 16:42:29 -03:00
Davidson Gomes
8ea4d65bc2 refactor: enhance media handling in Baileys service with validation for valid media content 2025-06-23 16:42:24 -03:00
Davidson Gomes
af713dee55 chore: update Dockerfile to use npm run build instead of npm run build:docker 2025-06-23 16:00:01 -03:00
Davidson Gomes
ee9ccb55ca chore: update version to 2.3.1 in Dockerfile, package.json, and package-lock.json 2025-06-23 15:41:49 -03:00
Davidson Gomes
977f686233 chore: update CHANGELOG for version 2.3.1 and fix package.json formatting 2025-06-23 15:38:54 -03:00
Davidson Gomes
a38caeaf5f chore(package-lock): update Baileys dependency to version 6.7.18 2025-06-23 15:36:54 -03:00
Davidson Gomes
873fdcb22a Merge pull request #1633 from VCalazans/FIX/ISSUE-28
🐛 fix: Phone number as message ID for Evo AI #ISSUE 28
2025-06-23 15:35:56 -03:00
Davidson Gomes
2e077a77ef Merge pull request #1626 from fernandeshenrique15/main
add unreadMessages in the response
2025-06-23 15:35:44 -03:00
Davidson Gomes
4be818a436 Merge pull request #1605 from ToniShelby/patch-2
Update Dockerfile
2025-06-23 15:35:33 -03:00
Davidson Gomes
a7badb9af5 Merge pull request #1624 from matheusfterra/main
Correção do envio de variáveis pelo typeboy
2025-06-23 15:35:20 -03:00
Davidson Gomes
ba5fb567eb Merge branch 'develop' into patch-2 2025-06-23 15:34:54 -03:00
Davidson Gomes
1070caf131 Merge pull request #1609 from KokeroO/feat/merge-contacts-lid-in-message-upsert
feat: Adiciona mesclagem de contatos @lid no Chatwoot
2025-06-23 15:32:46 -03:00
Davidson Gomes
9766e10ce9 Merge pull request #1623 from skarious/main
Update Dockerhub Repository and Delete Config Session Variable
2025-06-23 15:32:00 -03:00
Davidson Gomes
cc5612822f Merge pull request #1635 from autonomaia/patch-8
fix: corrige versão inválida do swagger-ui-express
2025-06-23 15:30:58 -03:00
autonomaia
83c1040114 fix: corrige versão inválida do swagger-ui-express 2025-06-22 22:10:51 -03:00
Victor Calazans
b4d1148d6a 🐛 fix: Phone number as message ID for Evo AI 2025-06-22 09:40:14 -03:00
Henrique Fernandes Neto
bd94423e5b add unreadMessages in the response 2025-06-19 16:30:57 -03:00
Matheus Terra
94670cbca3 Merge pull request #1 from matheusfterra/codex/corrigir-erro-ao-processar-mensagens-com-variáveis
Fix Typebot formatting function
2025-06-19 12:45:33 -03:00
Matheus Terra
bda3f5f146 Fix applyFormatting context in Typebot service 2025-06-19 12:45:09 -03:00
Ron Schneider
7724fe3a4f Merge branch 'main' into main 2025-06-19 02:11:50 -03:00
Ron Schneider
8b5f49592a Update evolution_api_v2.yaml
Update EvolutionApi DockerHub Repository and Delete Config Session Variable
2025-06-19 02:02:59 -03:00
Willian Coqueiro
6c0082cd7a feat: adicionar funcionalidade de mesclagem de contatos no Chatwoot 2025-06-17 22:33:51 +00:00
ToniShelby
3391b2ce4b Update Dockerfile 2025-06-17 16:33:50 +01:00
Davidson Gomes
39606240da Merge branch 'release/2.3.0' 2025-06-17 09:25:38 -03:00
Davidson Gomes
f4fbc4afc6 chore(changelog): remove fixed issue with @lid in chatwoot from CHANGELOG 2025-06-17 09:25:19 -03:00
Davidson Gomes
70905e7338 chore(changelog): update version date for 2.3.0 release 2025-06-17 09:20:12 -03:00
Davidson Gomes
07cccb7c7f Merge pull request #1599 from splusoficial/develop
fix: ajuste na validacao dos bots e pausar sessao
2025-06-17 08:52:47 -03:00
Davidson Gomes
5f0862a759 test: disable webhook https test 2025-06-16 15:27:16 -03:00
Joao Vitor
029d68e2cd fix: ajuste na validacao dos bots e pausar sessao 2025-06-15 18:24:34 -03:00
Davidson Gomes
a1fae85531 Merge pull request #1594 from KokeroO/feat/improves-and-adjusts-treatment-lid-numbers
refactor(chatwoot): Melhora e ajusta tratamento de números @lid
2025-06-13 17:54:09 -03:00
Willian Coqueiro
1afa8df556 refactor(chatwoot): simplify findContact method and update contact creation logic 2025-06-13 19:36:13 +00:00
Davidson Gomes
bfb044b234 feat(whatsapp): enhance contact handling and configuration logic
- Added support for `lid` in WhatsApp numbers router to improve contact identification.
- Updated the contact retrieval logic to accept an object with `phone_number` and `identifier` for better clarity and consistency.
- Enhanced error handling and logging in the Chatwoot service for improved traceability during contact creation and updates.
- Implemented automatic search for the latest WhatsApp version if the CONFIG_SESSION_PHONE_VERSION variable is not set, ensuring users have the most up-to-date integration.
2025-06-13 14:58:01 -03:00
Davidson Gomes
ae99ec7a0e feat(whatsapp): implement fetchLatestWaWebVersion utility and update version fetching logic
- Added a new utility function `fetchLatestWaWebVersion` to retrieve the latest WhatsApp Web version.
- Updated the Baileys service and router to utilize the new function instead of the deprecated `fetchLatestBaileysVersion`, ensuring accurate version information is fetched for WhatsApp integration.
- This change enhances the reliability of version management in the application.
2025-06-13 13:14:35 -03:00
Davidson Gomes
22c379aa36 chore(env): comment out WhatsApp Web version in example configuration
- Updated the .env.example file to comment out the CONFIG_SESSION_PHONE_VERSION variable, indicating it is not currently set. This change helps clarify the configuration options available for users.
2025-06-13 12:34:45 -03:00
Davidson Gomes
62c00c3db2 feat(router): update whatsappWebVersion to fetch latest Baileys version
- Modified the root route to fetch the latest WhatsApp web version from Baileys if not set in the environment configuration.
- This enhancement ensures that clients receive the most up-to-date version information in the API response.
2025-06-13 12:03:24 -03:00
Davidson Gomes
afc2927837 feat(IsOnWhatsapp): add optional lid field and update related logic
- Introduced a new optional `lid` field in the IsOnWhatsapp model to enhance data tracking.
- Updated migration script to add the `lid` column to the database.
- Modified OnWhatsAppDto to include the `lid` property for better integration with WhatsApp user data.
- Enhanced the WhatsApp Baileys service to handle `lid` numbers separately and improve user verification logic.
- Updated cache handling functions to support the new `lid` field for consistent data management.
2025-06-13 11:52:32 -03:00
Davidson Gomes
c17b48bca0 fix(chatwoot.service): improve number retrieval logic for message senders
- Enhanced the logic for retrieving sender numbers by introducing a new method that prioritizes senderPn, followed by participant and remoteJid.
- This change ensures more reliable identification of message senders across different scenarios.
2025-06-13 09:10:29 -03:00
Davidson Gomes
534c54a171 fix(chatwoot.service): enhance contact retrieval logic and normalize identifiers
- Updated contact retrieval to handle cases where the identifier is not found, specifically for @lid scenarios.
- Introduced a new method to normalize contact identifiers, prioritizing senderLid and participantLid.
- Improved logging for better traceability during contact lookups and conversation handling.
2025-06-13 08:28:09 -03:00
Davidson Gomes
8603e6def0 feat(router): add whatsappWebVersion to response object
- Included `whatsappWebVersion` in the response object to provide the version of the WhatsApp web session from the environment configuration.
- This addition enhances the API's response with relevant session information for clients.
2025-06-12 18:08:18 -03:00
Davidson Gomes
bcf2febf48 refactor(whatsapp.business.service): replace hardcoded token with class property
- Updated the token retrieval in the BusinessStartupService to use a class property instead of a hardcoded environment variable.
- This change enhances flexibility and maintainability of the service configuration.
2025-06-12 17:32:00 -03:00
Davidson Gomes
a02ecc88f5 refactor(whatsapp.business.service): enhance media handling and audio processing
- Updated media message preparation to conditionally include filename and caption based on media type.
- Improved error handling in media ID retrieval and audio processing methods.
- Refactored audio processing to support file uploads and URL handling more effectively.
- Enhanced logging for better error tracking during media operations.
2025-06-12 17:28:02 -03:00
Davidson Gomes
bc451e8493 feat(Typebot): add splitMessages and timePerChar fields to Typebot models
- Introduced `splitMessages` and `timePerChar` fields in the Typebot and TypebotSetting models with default values.
- Created a migration script to update the database schema accordingly.
- Updated audio message handling to prepend `[audio]` to transcriptions for better clarity in message context.
2025-06-12 13:24:25 -03:00
Davidson Gomes
1eb2c848f7 fix(chatwoot.service): add isLid parameter to service methods for enhanced functionality
- Introduced a new boolean parameter `isLid` in relevant methods to improve the handling of conversation data.
- Updated conditionals to incorporate `isLid` for more precise data management.
- Ensured backward compatibility by maintaining existing functionality while enhancing the service's capabilities.
2025-06-12 12:02:38 -03:00
Davidson Gomes
7cfc359be9 chore: update package-lock.json and refactor WhatsApp Baileys service
- Added new dependency `audio-decode` version 2.2.3.
- Introduced `@eshaz/web-worker` version 1.2.2 with its metadata.
- Refactored `userDevicesCache` initialization and various object destructuring for improved readability in `whatsapp.baileys.service.ts`.
- Streamlined multiple object property assignments for better code clarity.
- Removed deprecated `@typescript-eslint/project-service` and related packages from package-lock.json.
2025-06-12 12:01:01 -03:00
Davidson Gomes
421e762c2d fix(chatwoot.service): Ensure conversation checks are robust before toggling status
- Added null checks for conversation object in reopenConversation mode and when finding unresolved conversations.
- Improved logging for better debugging of conversation retrieval.
2025-06-12 11:18:59 -03:00
Davidson Gomes
9e1f9cbb83 Merge pull request #1574 from edisonmartinsmkt/develop
feat(audio): support LPCM and fix waveform distortion
2025-06-08 20:59:46 -03:00
edisoncm-ti
44e0ff2250 feat(audio): support LPCM and fix waveform distortion 2025-06-08 20:41:59 -03:00
Davidson Gomes
614ad7cbdf Merge pull request #1556 from splusoficial/develop
fix: adjustment in audio transcription with official api
2025-06-06 23:10:57 -03:00
Joao Vitor
77b3b331f8 fix: adjustment in audio transcription with official api 2025-06-05 20:31:11 -03:00
Davidson Gomes
e469dc132f Merge pull request #1536 from Daquisu/fix-fetchStatusMessage-params
Update `fetchStatusMessage` to handle empty offset / page
2025-06-03 12:40:14 -03:00
Daquisu
fa14abac5a Update fetchStatusMessage to handle empty offset / page 2025-06-01 15:22:01 -06:00
Ron Schneider
9db56560e4 Merge pull request #1 from skarious/skarious-patch-1
CONFIG_SESSION_PHONE_VERSION Updated
2025-05-29 14:06:12 -03:00
Ron Schneider
786e57603f CONFIG_SESSION_PHONE_VERSION Updated
CONFIG_SESSION_PHONE_VERSION updated
2025-05-29 14:05:42 -03:00
Davidson Gomes
a53e0a8694 Merge pull request #1513 from samuelterra22/patch-1
fix: change service image version and change to latest config session phone version
2025-05-28 13:53:17 -03:00
Davidson Gomes
16e4bba108 chore: update dependencies in package-lock.json and package.json
This commit updates several dependencies in the package-lock.json and package.json files to their latest versions. Key changes include:
- Upgraded various @typescript-eslint packages to version 8.33.0, ensuring compatibility and access to the latest features and fixes.
- Updated the baileys package from version 6.7.17 to 6.7.18, incorporating the latest improvements.
- Bumped the pino package version from 9.6.0 to 9.7.0 for enhanced performance and stability.
- Updated the process-warning package from version 4.0.1 to 5.0.0, which may include important updates and fixes.
- Adjusted the protobufjs package version from 7.5.2 to 7.4.0, reflecting the latest changes.

These updates contribute to improved performance, security, and maintainability of the codebase.
2025-05-28 11:44:17 -03:00
Davidson Gomes
bd19fff264 Merge branch 'develop' into patch-1 2025-05-28 11:01:17 -03:00
Samuel Terra
94285ecb90 fix: change service image version and change to latest config session phone version 2025-05-28 09:22:38 -03:00
Davidson Gomes
3500fbe27f Merge pull request #1508 from gomessguii/develop
refactor: improve chatbot integrations
2025-05-27 18:03:44 -03:00
Guilherme Gomes
cb76381466 refactor: reorder parameters and simplify EvolutionBot DTO
This commit refines the EvolutionBot integration by reordering constructor parameters for consistency and removing unused properties from the EvolutionBotDto and EvolutionBotSettingDto classes. Key changes include:
- Adjusted the parameter order in the EvolutionBotService constructor for improved clarity.
- Streamlined the EvolutionBotDto and EvolutionBotSettingDto by eliminating unnecessary fields.

These updates enhance the maintainability and readability of the EvolutionBot integration.
2025-05-27 17:52:45 -03:00
Guilherme Gomes
98502f6555 fix: resolve build errors and audio transcription issues across chatbot services
- Add YAML file loader to tsup.config.ts to fix build compilation errors
- Fix OpenAI speechToText method signature across all chatbot services
- Correct DifyService constructor parameter order in server.module.ts and channel.service.ts
- Add missing OpenAI service dependency injection to EvoaiService
- Standardize audio transcription logic in FlowiseService to match N8N implementation
- Fix speechToText calls in WhatsApp Baileys and Evolution channel services
- Ensure consistent error handling and parameter passing for audio processing

This resolves the "Cannot read properties of undefined" errors and ensures
all chatbot integrations (OpenAI, N8N, Flowise, EvoAI, Dify, EvolutionBot)
properly handle audio message transcription using OpenAI Whisper.
2025-05-27 17:46:29 -03:00
Guilherme Gomes
3fc77e4c76 refactor: update asset references in index.html and replace CSS/JS files
This commit updates the asset references in the index.html file to point to new CSS and JS files. Key changes include:
- Replaced the old JavaScript file `index-mxi8bQ4k.js` with `index-D-oOjDYe.js`.
- Updated the CSS file reference from `index-DNOCacL_.css` to the new `index-CXH2BdD4.css`.
- Removed the old CSS and JS files to clean up the codebase.

These updates ensure that the application uses the latest styles and scripts, contributing to improved performance and maintainability.
2025-05-27 17:37:01 -03:00
Guilherme Gomes
19e291178c refactor: streamline integration checks and parameter handling in chatbot controllers
This commit refines the Flowise and Typebot integrations by simplifying the integration enablement checks in their respective controllers. Key changes include:
- Consolidated the integration checks in the createBot method of FlowiseController and startBot method of TypebotController for improved readability.
- Removed unnecessary line breaks to enhance code clarity.

These updates contribute to a cleaner and more maintainable codebase for chatbot integrations.
2025-05-27 17:26:00 -03:00
Guilherme Gomes
7682a679d1 refactor: enhance Dify integration with improved validation and message processing
This commit refines the Dify integration by updating the controller and service logic for better functionality and maintainability. Key changes include:
- Added Dify-specific validation in the createBot method to prevent duplicate entries.
- Simplified comments for clarity and removed unused methods in DifyController.
- Enhanced message processing in DifyService to handle audio messages more effectively and improve error handling.
- Updated DifyDto and DifySettingDto to streamline properties and improve clarity.

These updates contribute to a more robust and maintainable Dify integration.
2025-05-27 17:04:35 -03:00
Guilherme Gomes
97ca23a7b0 refactor: enhance Evoai integration with improved validation and message handling
This commit refines the Evoai integration by updating the service and controller logic for better functionality and maintainability. Key changes include:
- Added the `openaiService` as a parameter in the EvoaiService constructor for improved dependency management.
- Enhanced the createBot method in EvoaiController to include EvoAI-specific validation and duplicate checks.
- Updated EvoaiDto and EvoaiSettingDto to remove unnecessary comments and add a fallback property.
- Refined the message processing logic in EvoaiService to handle audio messages more effectively and improve logging clarity.
- Adjusted the schema for Evoai settings to rename `evoaiIdFallback` to `botIdFallback` for better clarity.

These updates contribute to a more robust and maintainable Evoai integration.
2025-05-27 16:00:32 -03:00
Guilherme Gomes
95bd85b6e3 refactor: update Flowise integration for improved configuration and validation
This commit refines the Flowise integration by enhancing configuration management and validation logic. Key changes include:
- Reordered parameters in the FlowiseService constructor for consistency.
- Updated FlowiseController to utilize the configService for integration enablement checks.
- Simplified FlowiseDto and FlowiseSettingDto by removing unused properties.
- Enhanced validation logic in flowise.schema.ts to include new fields.
- Improved error handling in the createBot method to prevent duplicate entries.

These updates contribute to a more robust and maintainable Flowise integration.
2025-05-27 15:49:15 -03:00
Guilherme Gomes
64fc7a05ac refactor: improve session handling and validation in N8n integration
This commit enhances the N8n integration by refining session management and validation logic. Key changes include:
- Added error handling for session creation failures in the BaseChatbotService.
- Removed unused methods and properties in N8nService and N8nDto to streamline the codebase.
- Updated N8n schema to enforce required fields and improve validation checks.
- Simplified message processing logic to utilize base class methods, enhancing maintainability.

These improvements contribute to a more robust and efficient N8n integration.
2025-05-27 15:10:47 -03:00
Guilherme Gomes
39aaf29d54 refactor: enhance OpenAI controller and service for better credential management
This commit refactors the OpenAIController and OpenAIService to improve credential handling and error management. Key changes include:
- Added checks to prevent duplicate API keys and names during bot creation.
- Updated the getModels method to accept an optional credential ID for more flexible credential usage.
- Enhanced error handling with specific BadRequestException messages for better clarity.
- Removed unused methods and streamlined the speech-to-text functionality to utilize instance-specific settings.

These improvements enhance the maintainability and usability of the OpenAI integration.
2025-05-27 14:32:10 -03:00
Guilherme Gomes
22e99f7934 refactor: simplify TypebotController and TypebotService methods
This commit refactors the TypebotController and TypebotService to streamline the processTypebot method, aligning it with the base class pattern. It reduces complexity by consolidating parameters and improving readability. Additionally, it updates the TypebotDto and TypebotSettingDto to remove unused properties, enhancing code clarity and maintainability.
2025-05-27 13:07:15 -03:00
Davidson Gomes
dd0dfd447c Merge pull request #1504 from KokeroO/develop
fix: Melhora o método createConversation (evita conversas criadas duplicadas Chatwoot)
2025-05-27 08:11:34 -03:00
Willian Coqueiro
fb18267ac5 fix: eslint 2025-05-27 02:10:15 +00:00
Willian Coqueiro
fc5965938e fix: improve createConversation method with caching and locking mechanisms 2025-05-27 01:31:06 +00:00
Davidson Gomes
623efd86a2 Merge pull request #1498 from thrsouza/main
Inclusão do parâmetro frame_max para compatibilidade com RabbitMQ 4.1+
2025-05-25 11:10:40 -03:00
Thiago Souza
3297364c10 fix: update RabbitMQ frame_max parameter for 4.1+ compatibility
Updates the minimum frame_max value from 4096 to 8192 to meet the requirements
of RabbitMQ 4.1+ servers. This resolves connection failures with newer RabbitMQ
versions while maintaining backwards compatibility with older versions.
2025-05-25 03:08:12 -03:00
Davidson Gomes
373a531e88 fix: update logging and message handling in EvoaiService
This commit modifies the logging messages in the `EvoaiService` to use the correct service name "EvoAI" instead of "Dify" for better clarity. Additionally, it refines the message handling logic by changing the way message IDs are generated and updating the payload structure sent to the API. The extraction of the message from the response artifacts has also been improved to ensure that the correct message is retrieved from the response data.

Changes include:
- Updated logging statements to reflect the correct service name.
- Changed the message ID generation to use a shorter UUID substring.
- Adjusted the payload structure to include `contextId` and `messageId`.
- Enhanced message extraction logic from the response artifacts.

These changes enhance the clarity of logs and improve the robustness of message handling in the service.
2025-05-23 20:37:01 -03:00
Davidson Gomes
e081533f02 fix: remove unused Auth import from Dify and N8n services
This commit removes the unused `Auth` import from both `dify.service.ts` and `n8n.service.ts`. This change helps to clean up the code and improve readability by eliminating unnecessary dependencies.
2025-05-23 20:09:36 -03:00
Davidson Gomes
17fd407d8d Merge pull request #1494 from KokeroO/develop
fix: melhora a formatação e tratamento de erros na função getExistingSourceIds
2025-05-23 10:43:27 -03:00
Willian Coqueiro
0cdc67effe fix: eslint 2025-05-23 02:40:05 +00:00
Willian Coqueiro
ddaf32be76 fix: melhora a formatação e tratamento de erros na função getExistingSourceIds 2025-05-23 02:18:13 +00:00
Willian Coqueiro
0b2d8a752f fix: melhora o tratamento de erros e otimiza a consulta de IDs de origem no Chatwoot 2025-05-23 02:13:46 +00:00
Davidson Gomes
9cda6a2f99 Merge pull request #1493 from oriondesign2015/develop
corrige estrutura de if/else e bloco try/catch em chatwoot-import-helper.ts
2025-05-22 21:57:33 -03:00
OrionDesign
d2263af3e8 fix: corrige estrutura de if/else e bloco try/catch em chatwoot-import-helper.ts
Esta PR corrige problemas de sintaxe no arquivo chatwoot-import-helper.ts, especificamente:
- Ajusta a estrutura do bloco if/else no método getExistingSourceIds, evitando erro de compilação do TypeScript.
- Garante que o bloco catch esteja corretamente posicionado dentro do método, retornando null em caso de erro.
- Mantém a lógica original do método, apenas corrigindo a sintaxe para permitir a compilação e execução correta do projeto.
Essas correções eliminam o erro de build relacionado ao TypeScript e melhoram a robustez do código ao tratar exceções de forma adequada.
2025-05-22 18:51:28 -03:00
Davidson Gomes
0239638232 Merge pull request #1490 from KokeroO/develop
fix: Corrige o PR1481
2025-05-22 17:08:43 -03:00
Willian Coqueiro
3459d61eff fix: Corrige o PR1481 2025-05-22 20:03:32 +00:00
Davidson Gomes
5330121c49 Merge pull request #1486 from oriondesign2015/develop
fix: Corrige envio da apiKey da instância nos payloads do Flowise e do Dify
2025-05-22 11:24:20 -03:00
OrionDesign
53c1c218c4 fix: Corrige envio da apiKey da instância no payload do Dify
Corrige o envio da apiKey no payload do Dify para usar a apiKey específica da instância ao invés da apiKey global do sistema.
2025-05-22 11:04:13 -03:00
OrionDesign
06378e5d6b fix: Corrige envio da apiKey da instância no payload do Flowise
Corrige o envio da apiKey no payload do Flowise para usar a apiKey específica da instância ao invés da apiKey global do sistema.
2025-05-22 11:03:22 -03:00
Davidson Gomes
6a83e89394 Merge pull request #1485 from oriondesign2015/develop
fix: Corrige envio da apiKey da instância nos payloads do Evolution Bot e N8N
2025-05-22 11:02:21 -03:00
OrionDesign
d24540d6dd fix: Corrige envio da apiKey da instância no payload do N8N
Corrige o envio da apiKey no payload do N8N para usar a apiKey específica da instância ao invés da apiKey global do sistema.
2025-05-22 10:57:41 -03:00
OrionDesign
2af7b24013 fix: Corrige envio da apiKey da instância no payload do Evolution Bot
Corrige o envio da apiKey no payload do Evolution Bot para usar a apiKey específica da instância ao invés da apiKey global do sistema.
2025-05-22 10:56:54 -03:00
Davidson Gomes
6f47a54fae Merge pull request #1483 from KokeroO/develop
chore: possibilita o envio de medias do tipo [svg, tiff] vindas do Chatwoot
2025-05-22 07:03:41 -03:00
Davidson Gomes
edde059fa1 Merge pull request #1484 from oriondesign2015/develop
fix: melhora consistência e formatação dos chatbots (N8N e Evolution Bot)
2025-05-22 06:58:21 -03:00
OrionDesign
dcb09b87fe fix: corrige comportamento de sessão pausada no N8N
Corrige o problema onde o N8N reativava automaticamente a sessão após receber uma mensagem quando estava pausado. Agora, quando uma sessão está pausada, o bot ignora completamente as mensagens recebidas até que a sessão seja explicitamente reativada através do endpoint de mudança de status.
2025-05-22 01:08:48 -03:00
OrionDesign
bbf142cf39 fix: corrige comportamento de sessão pausada no Evolution Bot
Corrige o problema onde o Evolution Bot reativava automaticamente por qualquer mensagem do usuario quando a sessão estava pausada. Agora, quando uma sessão está pausada, o bot ignora completamente as mensagens recebidas até que a sessão seja explicitamente reativada.
2025-05-22 01:01:51 -03:00
OrionDesign
da51b6bd76 fix: corrige processamento de mensagens subsequentes no Evolution Bot
Corrige o problema onde o Evolution Bot não processava mensagens subsequentes após a primeira resposta. A correção permite que o bot continue respondendo a todas as mensagens enquanto a sessão estiver ativa, melhorando a continuidade da conversa.
2025-05-22 00:54:37 -03:00
Willian Coqueiro
8b15c11817 chore: adiciona suporte para extensão de arquivo .tif no envio de documentos 2025-05-22 03:38:57 +00:00
Willian Coqueiro
272a4de236 chore: possibilita o envio de medias do tipo svg e tiff vindas do Chatwoot 2025-05-22 03:29:16 +00:00
OrionDesign
65111481b9 fix: remove quebras de linha extras nas mensagens do N8n
Corrige o problema de formatação nas mensagens do N8n onde quebras de linha extras estavam sendo adicionadas antes e depois das mídias (imagens, vídeos, etc). Agora o texto é enviado mantendo apenas as quebras de linha intencionais.
2025-05-22 00:17:06 -03:00
Willian Coqueiro
0ca109e9d6 chore: possibilita o envio de medias do tipo .svg 2025-05-22 03:02:04 +00:00
Davidson Gomes
5b817028a9 refactor(chatbot): enhance EvoaiService with OpenAI integration
- Integrated OpenAI service into the EvoaiService to streamline audio message transcription.
- Updated the constructor to initialize OpenaiService, allowing for direct transcription of audio messages.
- Refactored audio message handling to utilize the OpenAI service for improved reliability and maintainability.
- Adjusted the processing logic to handle transcription results more effectively, ensuring fallback content is provided when transcription fails.

This commit focuses on enhancing the EvoaiService's capabilities by leveraging OpenAI for audio transcription, improving overall service functionality and code structure.
2025-05-21 22:27:59 -03:00
Davidson Gomes
6a0fc19702 refactor(chatbot): integrate OpenAI service into chatbot implementations
- Updated various chatbot services (Typebot, Dify, EvolutionBot, Flowise, N8n) to include the OpenAI service for audio transcription capabilities.
- Modified constructors to accept OpenaiService as a dependency, enhancing the ability to transcribe audio messages directly within each service.
- Refactored the handling of `keywordFinish` in multiple controllers and services, changing its type from an array to a string for consistency and simplifying logic.
- Removed redundant audio transcription logic from the base service, centralizing it within the OpenAI service to improve maintainability and reduce code duplication.

This commit focuses on enhancing the chatbot services by integrating OpenAI's transcription capabilities, improving code structure, and ensuring consistent handling of session keywords.
2025-05-21 22:17:10 -03:00
Davidson Gomes
9cedf31eed feat(env): enhance webhook configuration and SSL support
- Added new environment variables for SSL configuration, including `SSL_CONF_PRIVKEY` and `SSL_CONF_FULLCHAIN`, to support secure connections.
- Introduced additional webhook configuration options in the `.env.example` file, such as `WEBHOOK_REQUEST_TIMEOUT_MS`, `WEBHOOK_RETRY_MAX_ATTEMPTS`, and related retry settings to improve webhook resilience and error handling.
- Updated the `bootstrap` function in `main.ts` to handle SSL certificate loading failures gracefully, falling back to HTTP if necessary.
- Enhanced error handling and logging in the `BusinessStartupService` to ensure better traceability and robustness when processing messages.

This commit focuses on improving the security and reliability of webhook interactions while ensuring that the application can handle SSL configurations effectively.
2025-05-21 17:55:00 -03:00
Davidson Gomes
f9567fbeaa refactor(chatbot): unify keywordFinish type and enhance session handling
- Changed the type of `keywordFinish` from an array to a string in multiple DTOs and controller interfaces to simplify data handling.
- Updated the `BaseChatbotService` to include logic for updating session status to 'opened' and managing user responses more effectively.
- Refactored the media message handling in the `BaseChatbotService` to streamline the process and improve readability.
- Enhanced error logging across various services to ensure better traceability during operations.

This commit focuses on improving the structure and consistency of chatbot integrations while ensuring that session management is robust and user-friendly.
2025-05-21 17:02:24 -03:00
Davidson Gomes
d673c83a93 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2025-05-21 15:14:50 -03:00
Davidson Gomes
e16bb0e580 style(whatsapp): format code for consistency
- Adjusted spacing in destructuring assignments for `remoteJid` in multiple locations to enhance code readability and maintain consistency with coding standards.
- Removed unnecessary blank lines to streamline the code structure.

This commit focuses on improving the overall style of the `whatsapp.baileys.service.ts` file without altering any functionality.
2025-05-21 15:14:41 -03:00
Davidson Gomes
09120aa026 Merge pull request #1482 from gomessguii/fix/message-query
Refatoração da funcionalidade de chatbots (em andamento)
2025-05-21 15:14:25 -03:00
Davidson Gomes
fbfa364df9 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2025-05-21 15:01:22 -03:00
Davidson Gomes
624b37e2aa fix(media): improve media download handling for base64 conversion
- Enhanced the logic for converting media messages to base64 by adding a retry mechanism for downloading media if the initial buffer is not available.
- This change ensures that media messages are reliably converted to base64 format, improving the robustness of media handling in the WhatsApp integration service.
- Updated error logging to capture issues during the media conversion process.
2025-05-21 15:01:17 -03:00
Davidson Gomes
fcde1f9acc Merge pull request #1481 from AlexSzefezuk/fix-chatwoot-import
Fix-chatwoot-import
2025-05-21 13:54:51 -03:00
Davidson Gomes
cc01787501 Merge pull request #1473 from KokeroO/develop
fix: Deadlock errors and undefined arguments
2025-05-21 13:52:57 -03:00
Guilherme Gomes
c30bae4c3a refactor(openai): improve service initialization and streamline audio transcription handling
- Updated OpenaiService and related classes to enhance the initialization process by ensuring the correct order of parameters.
- Simplified audio message handling by consolidating transcription logic and improving error handling.
- Refactored the OpenaiController to utilize the new structure, ensuring better integration with the base chatbot framework.
- Enhanced logging for better traceability during audio processing and API interactions.
2025-05-21 12:16:12 -03:00
Alex Szefezuk
2545013040 fix: enable sourceId exists in a conversation 2025-05-21 10:36:26 -03:00
Willian Coqueiro
c53a96e757 Fix suggestions 2025-05-20 13:07:28 +00:00
Willian Coqueiro
a2d8642e1c fix: Corrige processamento de documentos sem filename.
## Erros:
- Cannot read properties of null (reading 'fileName')
2025-05-19 23:21:01 +00:00
Willian Coqueiro
9c530c69cf fix: Evita tentar processar media messageContextInfo e disparar um erro generico. 2025-05-19 23:18:23 +00:00
Willian Coqueiro
348a4ff251 fix: Corrige problemas com deadlocks
## Erros:
- code: "40P01", message: "deadlock detected".
- Argument status is missing.
2025-05-19 23:14:57 +00:00
Willian Coqueiro
fc00916345 fix: Corrige o erro Unable to fit integer value '1747658857155' into an INT4 (32-bit signed integer.
## Solução
Após analise dos valores recebidos e seguindo a logica em outras partes também, se confirma que o valor de entrada é de timestamp em ms.
2025-05-19 23:05:11 +00:00
Guilherme Gomes
69b4f1aa02 feat(chatbot): implement base chatbot structure and enhance integration capabilities
- Introduced a base structure for chatbot integrations, including BaseChatbotController and BaseChatbotService.
- Added common DTOs for chatbot settings and data to streamline integration processes.
- Updated existing chatbot controllers (Dify, Evoai, N8n) to extend from the new base classes, improving code reusability and maintainability.
- Enhanced media message handling across integrations, including audio transcription capabilities using OpenAI's Whisper API.
- Refactored service methods to accommodate new message structures and improve error handling.
2025-05-17 16:22:13 -03:00
Davidson Gomes
7cccda10bb Merge pull request #1456 from thiagoomatheus/main
Fixes issue #879
2025-05-16 13:38:30 -03:00
Davidson Gomes
33c808b195 Merge pull request #1457 from gomessguii/fix/message-query
feat(channel): enhance pushName logic for messages
2025-05-16 12:02:31 -03:00
Guilherme Gomes
d3ee370bdc feat(channel): enhance pushName logic for messages
- Updated the pushName selection to differentiate between group and individual messages.
- Added conditional logic to display the chat name for group messages and the sender's name for individual messages.
2025-05-16 11:33:52 -03:00
thiagoomatheus
7e5740b462 fix(media): allow multiples files with same name 2025-05-15 22:37:55 -03:00
Davidson Gomes
0b33a76394 changelog v2.3.0 2025-05-15 18:49:35 -03:00
Davidson Gomes
6a0b024b13 Merge pull request #1453 from gomessguii/fix/evoai-migration
feat(evoai): add Evoai and EvoaiSetting tables with foreign key const…
2025-05-15 18:20:09 -03:00
Guilherme Gomes
fda6b0d50e feat(evoai): add Evoai and EvoaiSetting tables with foreign key constraints
- Created the Evoai and EvoaiSetting tables in the PostgreSQL migration.
- Defined primary keys and added foreign key constraints to link with the Instance table.
- Included unique index on instanceId for EvoaiSetting to ensure data integrity.
2025-05-15 18:19:23 -03:00
Davidson Gomes
9ec6847683 Merge pull request #1452 from gomessguii/feature/new-manager-version
feat: updated manager to the last version with suport to n8n and EvoA…
2025-05-15 18:12:38 -03:00
Guilherme Gomes
c745fbad64 feat: updated manager to the last version with suport to n8n and EvoAI chatbot integrations 2025-05-15 18:09:23 -03:00
Davidson Gomes
40ea8bf356 Merge pull request #1451 from gomessguii/feature/evoai-chatbot
feat(evoai): add EvoAI integration with models, services, and routes
2025-05-15 15:50:12 -03:00
Guilherme Gomes
0699ad4bb0 fix(evoai): update EvoAI service initialization to include configService
- Modified the EvoaiService instantiation in the server module to include configService for enhanced configuration management.
2025-05-15 15:46:13 -03:00
Guilherme Gomes
70a4fe8f6e feat(evoai): enhance media message handling and transcription capabilities
- Added support for audio message detection and transcription using OpenAI's Whisper API.
- Integrated media downloading for both audio and image messages, with appropriate error handling.
- Updated logging to redact sensitive information from payloads.
- Modified existing methods to accommodate the new message structure, ensuring seamless integration with EvoAI services.
2025-05-15 15:42:17 -03:00
Guilherme Gomes
71124755b0 feat(evoai): add EvoAI integration with models, services, and routes
- Introduced Evoai and EvoaiSetting models in both MySQL and PostgreSQL schemas.
- Implemented EvoaiController and EvoaiService for managing EvoAI bots.
- Created EvoaiRouter for handling API requests related to EvoAI.
- Added DTOs and validation schemas for EvoAI integration.
- Updated server module and chatbot controller to include EvoAI functionality.
- Configured environment settings for EvoAI integration.
2025-05-15 11:54:11 -03:00
Davidson Gomes
a1cc504777 Merge pull request #1449 from gomessguii/feature/enhance-message-fetching
feat(whatsapp): enhance message fetching and processing logic
2025-05-14 21:44:39 -03:00
Davidson Gomes
0fd2e04286 Merge pull request #1448 from gomessguii/feature/n8n-chatbot
Add N8n integration with models, services, and routes
2025-05-14 21:44:25 -03:00
Guilherme Gomes
4f2b0c42f3 Merge pull request #2 from EvolutionAPI/develop
Develop
2025-05-14 21:35:36 -03:00
Guilherme Gomes
401b0359cd Merge pull request #1 from EvolutionAPI/develop
Develop
2025-05-14 21:32:41 -03:00
Davidson Gomes
b1f3c5cc5f Merge pull request #1450 from gomessguii/fix/npm-install-action
fix: remove postinstall script from package.json to fix GitHub Action
2025-05-14 21:29:57 -03:00
Guilherme Gomes
362736ea71 refactor(whatsapp): format userDevicesCache initialization for improved readability 2025-05-14 21:27:23 -03:00
Guilherme Gomes
2544c10592 fix: remove postinstall script from package.json to fix GitHub Action 2025-05-14 21:22:52 -03:00
Guilherme Gomes
383805aa95 feat(whatsapp): enhance message fetching and processing logic
- Added a new method `fetchMessages` to retrieve messages based on various query parameters.
- Improved handling of `pushName` for messages, ensuring proper assignment based on participant information.
- Refactored user devices cache initialization for better readability.
- Cleaned up commented-out code related to message recovery.
2025-05-14 21:12:45 -03:00
Guilherme Gomes
71101807bb Refactor N8n integration: update schema exports, improve import order, and enhance service logic
- Added export for N8n schema in chatbot.schema.ts.
- Improved import order in n8n.dto.ts and n8n.router.ts for better readability.
- Refactored variable declarations in n8n.service.ts for consistency and clarity.
2025-05-14 20:50:48 -03:00
Guilherme Gomes
38f089f04c Add N8n integration with models, services, and routes
- Introduced N8n and N8nSetting models in both MySQL and PostgreSQL schemas.
- Implemented N8nController and N8nService for managing N8n bots.
- Created N8nRouter for handling API requests related to N8n.
- Added DTOs and validation schemas for N8n integration.
- Updated server module and chatbot controller to include N8n functionality.
- Configured environment settings for N8n integration.
2025-05-14 20:47:23 -03:00
Davidson Gomes
6d63f2fb6e Merge pull request #1420 from luiis716/main
Findchat group name treatment
2025-05-13 06:28:21 -03:00
Davidson Gomes
bb0b9b94ff Merge branch 'develop' into main 2025-05-13 06:28:07 -03:00
luiis716
a449fdf0ef Findchat group name treatment 2025-05-12 16:14:59 -03:00
Davidson Gomes
ec9de49647 Merge pull request #1435 from edisonmartinsmkt/fix/audio-caption-fallback
fix(chatwoot): only fallback audio caption when audioMessage exists
2025-05-10 19:14:41 -03:00
edisoncm-ti
a7a9de2903 fix: only fallback caption when audioMessage exists 2025-05-10 16:20:05 -03:00
Davidson Gomes
99b0c86278 Merge pull request #1422 from icaro-andrade/patch-2
Update package.json
2025-05-10 13:07:39 -03:00
Davidson Gomes
e6a72bd829 Merge pull request #1406 from Faelst/hotfix/issues-1385
fix: when you set jpegThumbnail, image not appear on app mobile
2025-05-10 13:07:06 -03:00
Davidson Gomes
341a0d884f Merge pull request #1332 from theeusmartins/main
Defininando TTL no userDivicesCache igual usado no Baileys
2025-05-10 11:26:23 -03:00
Davidson Gomes
383bac090a Merge pull request #1434 from edisonmartinsmkt/fix/group-audio-caption
fix(chatwoot): avoid "undefined" caption on group audio messages
2025-05-10 11:25:14 -03:00
edisoncm-ti
60a58ca037 fix: not show undefined in caption for received audio messages in group chats 2025-05-10 11:12:44 -03:00
Davidson Gomes
e5989f3d47 Merge pull request #1433 from paulocoutinhox/fix-mysql-wavoip-token
fix mysql wavoip token
2025-05-10 10:32:12 -03:00
Davidson Gomes
bff3bf564b Merge pull request #1415 from victoreduardo/victoreduardos/fix-conversation-not-found
fix: Erro na criação de conversation quando já existe uma conversation de outro inbox para o mesmo usuário
2025-05-10 10:31:06 -03:00
Davidson Gomes
c74eee8e52 Merge pull request #1414 from onerrogus/fix_link_preview
Corrigir utilização do linkPreview no WhatsApp Business API
2025-05-10 10:30:54 -03:00
Davidson Gomes
d1a28ea4f7 Merge pull request #1318 from victoreduardo/victoreduardos/jwt-webhook
Tornando Webhook mais seguro com JWT token
2025-05-10 10:28:07 -03:00
Davidson Gomes
d2f1985913 Merge pull request #1425 from edisonmartinsmkt/develop
fix(audio): ensure full WhatsApp compatibility for audio conversion
2025-05-10 08:33:28 -03:00
Paulo Coutinho
90e27cc7d8 fix mysql wavoip token 2025-05-10 03:53:35 -03:00
edisoncm-ti
c4ddfe6804 style: fix linting issues with Prettier 2025-05-07 18:18:47 -03:00
edisoncm-ti
aaa103a842 fix(audio): ensure full WhatsApp compatibility for audio conversion (libopus, 48kHz, mono) 2025-05-07 13:41:09 -03:00
icaro-andrade
b2809b6f3e Update package.json 2025-05-06 19:33:46 -03:00
luiis716
d52256718d Findchat group name treatment
ajusta para endpoint puxa o nome do grupo correntemente quando for grupo usando nomenclatura @g.us
2025-05-03 14:55:07 -03:00
Victor Eduardo
8f0ede4207 fix: preventing use conversation from other inbox for the same user 2025-05-03 09:56:28 -03:00
Victor Eduardo
95827a2d70 lint 2025-05-03 09:42:18 -03:00
Victor Eduardo
b064e512e2 Merge branch 'develop' into victoreduardos/jwt-webhook 2025-05-03 09:40:37 -03:00
OnerRogus
db5f0d0891 fix: corrigido erro que não exibia o preview das urls quando utilizado o whatsapp business api 2025-05-02 15:32:47 -03:00
Davidson Gomes
ccbd866e42 Merge pull request #1384 from leandrosroc/develop
fix(api): modifica fetchChats para trazer mensagens de contatos não salvos
2025-04-30 06:15:42 -03:00
Rafael Silverio
dc67039b39 fix: when you set jpegThumbnail, image not appear on app mobile 2025-04-29 14:54:42 -03:00
Leandro Santos Rocha
eeedfb0e2a fetchContacts - nestordavalos 2025-04-22 16:12:47 -03:00
Leandro Santos Rocha
3ab75faff7 fix lint 2025-04-21 00:42:33 -03:00
Leandro Santos Rocha
095754d173 perf(api): otimiza paginação em fetchChats usando LIMIT/OFFSET no SQL 2025-04-21 00:38:25 -03:00
Leandro Rocha
b94b452597 fix(api): modifica fetchChats para trazer mensagens de contatos não salvos
- Muda tabela base da consulta de Contact para Message
- Altera INNER JOIN para LEFT JOIN entre Message e Contact
- Usa COALESCE para campos que podem estar vazios
- Adiciona flag isSaved para identificar contatos salvos/não salvos
- Preserva toda funcionalidade de filtros existente

Resolve issue #1376
2025-04-21 00:30:48 -03:00
Davidson Gomes
2ded19752f Merge pull request #1366 from adaptwebtech/hotfix_chatname
Corrigindo um bug ao atualizar o push name no evento MESSAGES_UPSERT e MESSAGES_UPDATE
2025-04-14 20:06:23 -03:00
pedro-php
4c8c7ee19b lint fixes 2025-04-09 15:15:48 -03:00
pedro-php
8c6f95fbef Fixing chatname on the events message.upsert and message.update in order to return always the chatname from the user correctly 2025-04-09 14:55:22 -03:00
Davidson Gomes
402b37d7b4 Merge pull request #1351 from ricocorreia1/main
Correção para quando enviar uma localização.
2025-04-08 19:10:38 -03:00
Davidson Gomes
09f79c94be Merge pull request #1362 from Deyvi-dev/develop
feat(s3): add S3_SKIP_POLICY env variable to disable setBucketPolicy
2025-04-08 10:51:52 -03:00
Davidson Gomes
7c7dca9da9 Merge pull request #1354 from jeffersonfelixdev/hotfix/issue-1348
Hotfix - shell injection vulnerability
2025-04-08 10:51:35 -03:00
deyvi-dev
1d2e029b54 feat(s3): add S3_SKIP_POLICY env variable to disable setBucketPolicy for incompatible providers 2025-04-07 20:23:33 -03:00
Jefferson Felix
3f8d89e970 fix: remove wildcard 2025-04-02 12:04:07 -03:00
Jefferson Felix
abda9e2113 docs: update CHANGELOG.md 2025-04-02 11:55:53 -03:00
Jefferson Felix
2a020928e8 fix: change execSync to execFileSync 2025-04-02 11:55:23 -03:00
ricocorreia1
b436e5b0b0 quando localização, conversão de degreesLatitude para string, o parametro content é do tipo string nas chamadas de findBotByTrigger() em chatbot.controller. 2025-04-01 23:26:46 -03:00
Davidson Gomes
9889035ddc Merge pull request #1344 from alvestassio/fix/multiple-files-sent-via-whatsapp
Adicionando um timestamp ao filename para possibilitar enviar o mesmo arquivo mais de uma vez na mesma conversa.
2025-03-30 12:09:01 -03:00
Davidson Gomes
07ce09d8e2 Merge pull request #1322 from marceloapd/fix/animated-stickers
Fix/animated stickers
2025-03-30 12:08:15 -03:00
Davidson Gomes
0d2a7ad50b Merge pull request #1343 from adaptwebtech/new_feature_add_send_update_message_webhook
Adicionando um novo webhook no endpoint de updateMessage
2025-03-30 12:08:03 -03:00
pedro-php
7e8044a777 lint changes 2025-03-28 11:10:48 -03:00
pedro-php
645f305cd6 fixing build error on prisma 2025-03-28 11:08:38 -03:00
pedro-php
829032dc08 lint changes 2025-03-28 10:58:32 -03:00
pedro-php
3d40b0850b lint changes 2025-03-28 10:57:59 -03:00
pedro-php
17bd108251 treating errors gracefully 2025-03-28 10:56:19 -03:00
pedro-php
f695e8bdc9 merging with develop 2025-03-28 10:36:34 -03:00
Tassio Alves
bf59ff1287 [FIX] Run lint 2025-03-27 14:36:39 -04:00
Tassio Alves
ce1680f515 [FIX] Adding a timestamp to the filename to make it possible to send the same file more than once in the same conversation. 2025-03-27 10:46:13 -04:00
pedro-php
119ceba1ca Adding a new webhook that triggers when a message is updated by the user 2025-03-27 11:13:22 -03:00
Marcelo Assis
9710fbdac4 remove animated to webp 2025-03-26 13:09:08 -03:00
Marcelo Assis
658dae0b59 lint fix 2025-03-26 10:45:09 -03:00
Davidson Gomes
b89f1144b4 Merge pull request #1334 from adaptwebtech/fix_and_add_name_to_find_chats_and_paginate_get_contacts_and_get_chats
Corrigindo um bug no endpoint de findChats e permitindo paginação nos endpoints de findChats e findContacts
2025-03-26 10:34:56 -03:00
Pedro Afonso
d196590862 Update src/api/services/channel.service.ts
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
2025-03-25 11:17:46 -03:00
pedro-php
c5c354ffe7 fix_and_add_name_to_find_chats_and_paginate_get_contacts_and_get_chats 2025-03-25 10:52:19 -03:00
Matheus Martins Piedade
36757dddc2 Defininando TTL no userDivicesCache igual usado no Baileys 2025-03-24 15:15:45 -03:00
Marcelo Assis
027401b839 fix: normalize file extension checks for case insensitivity in sticker conversion 2025-03-20 19:13:19 -03:00
Marcelo Assis
6e7dd51679 fix: preserve animation in GIF and WebP stickers 2025-03-20 19:00:02 -03:00
Marcelo Assis
f8b1c6e0fa fix: preserve animation in GIF and WebP stickers 2025-03-20 18:37:58 -03:00
Victor Eduardo
cee2bc4d71 sending JWT token when sending webhook if jwt_key exists in webhook header record 2025-03-19 18:04:42 -03:00
Davidson Gomes
043df62a8f Merge pull request #1304 from rafwell/main
Add eventos referente a instancia que estavam faltando
2025-03-14 14:33:41 -03:00
Rafael Souza
53ae521863 Add eventos referente a instancia que estavam faltando 2025-03-10 18:11:03 -03:00
Davidson Gomes
13bdbc268c Merge pull request #1290 from jrCleber/main
Corrige validação de URL para permitir localhost e endereços IP
2025-03-06 16:59:48 -03:00
Davidson Gomes
ea9c3fbbe0 Merge pull request #1287 from pedroepif/main
fix: adjusting cloud api send audio and video
2025-03-06 16:53:05 -03:00
Cleber Wilson
22a958616d basic regex for url 2025-03-06 16:17:42 -03:00
Davidson Gomes
32cde710b8 feat: Enhance WebSocket authentication and connection handling
- Add robust authentication mechanism for WebSocket connections
- Implement API key validation for both instance-specific and global tokens
- Improve connection request handling with detailed logging
- Refactor WebSocket controller to support more secure connection validation
2025-03-06 09:17:43 -03:00
Pedro Epifanio
d9aa111800 fix: lint 2025-03-06 08:59:45 -03:00
Pedro Epifanio
fb41ab14e8 fix: adding media verification 2025-03-05 20:01:14 -03:00
Pedro Epifanio
5998fcf940 fix: adjustin cloud api send audio and video 2025-03-05 19:54:34 -03:00
Davidson Gomes
2198a86ae4 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2025-02-25 15:44:42 -03:00
Davidson Gomes
8a54efe11c feat: Add NATS integration and update Baileys service
- Create Nats table in PostgreSQL migration
- Disable message recovery logic in Baileys service
- Remove console log in instance creation route
2025-02-25 15:42:40 -03:00
Davidson Gomes
4fadf64bae Merge pull request #1264 from ygorsantana/fix/expiration-useless
Fix: Expiration being useless on awaitUser false
2025-02-22 21:03:05 -03:00
Ygor Santana
2f1df734e0 style: run lint 2025-02-22 14:17:37 -03:00
Ygor Santana
247c39fe39 fix: expiration time being useless when not awaitUser 2025-02-22 14:16:08 -03:00
Davidson Gomes
ada63b58af Merge pull request #1251 from fernandoralha/hotfix/sqs-event
fix: Refactor SQS controller to correct bug in sqs events by instance
2025-02-21 19:31:29 -03:00
Davidson Gomes
5192f49a4f Merge pull request #1259 from julianoaj/develop
 Feat: Remover a reação de uma mensagem.
2025-02-21 19:24:39 -03:00
julianoaj
48b5fd41e0 🐛 Fix: Linting requirements 2025-02-20 17:25:39 -03:00
julianoaj
5720bdc0ef Remove reaction from a message 2025-02-20 16:54:29 -03:00
Alison Juliano
ab2364b9a3 Merge pull request #1 from julianoaj/fix-1234-migration-wavoipToken-create
🐛 Corrige problema na API relacionado à migration. Fixes #1234
2025-02-18 13:36:21 -03:00
“fernandoralha”
278add6b11 fix: Refactor SQS controller to correct bug in sqs events by instance
- Implement dynamic queue creation based on enabled events - Add method to list existing queues for an instance - Improve error handling and logging for SQS operations - Remove unused queue removal methods - Update set method to handle queue creation/deletion on event changes - Add comments for future feature of forced queue deletion
2025-02-17 21:22:59 -03:00
Davidson Gomes
8f632a6f5c Merge pull request #1244 from AndersonSilvaCavalcante/hotfix/missing-wavoipToken-mysql
hotfix(migration): add missing wavoipToken column in MySQL schema
2025-02-16 13:04:29 -03:00
Anderson Cavalcante
a4ac798b43 hotfix(migration): add missing wavoipToken column in MySQL schema 2025-02-14 15:26:07 -03:00
Davidson Gomes
fc513f1d1d Merge pull request #1240 from ygorsantana/fix/list-response-message
fix: change mediaId optional chaining and list response message text obtain
2025-02-14 13:05:34 -03:00
Ygor Santana
6f1667abb5 style: run lint 2025-02-14 12:58:46 -03:00
Davidson Gomes
98b8419b8d Merge pull request #1237 from GrimBit1/main
Refactor Editing Message events and update message handler
2025-02-14 12:09:27 -03:00
Ygor Santana
59479f9a21 fix: obtain mediaUrl not defined 2025-02-13 22:35:09 -03:00
Ygor Santana
ff77bc018a fix: change mediaId optional chaining and list response message text obtain 2025-02-13 21:54:09 -03:00
Aditya Nandwana
33f7f2932d Implement message update handling in BaileysStartupService 2025-02-13 11:07:18 +05:30
Aditya Nandwana
c939ed2337 Enhance message editing validation in BaileysStartupService 2025-02-13 10:39:29 +05:30
Davidson Gomes
a49c97996c Merge pull request #1235 from julianoaj/fix-1234-migration-wavoipToken-create
🐛 Fix: Add migration for missing wavoipToken column in MySQL build env (#1234)
2025-02-12 18:23:35 -03:00
julianoaj
da72edfb03 🐛 Corrige problema na API relacionado à migration. Fixes #1234
Corrige problema na API relacionado à migration. Fixes #1234
2025-02-12 16:14:21 -03:00
Davidson Gomes
b51526aff3 Merge pull request #1226 from Desarrollo-TMS/fix-csat-chatwoot
fix: chatwoot csat creating new conversation in another language
2025-02-11 12:14:50 -03:00
Alexis Hernandez
b93ee2e023 fix: chatwoot csat creating new conversation in another language 2025-02-11 08:20:36 -04:00
Davidson Gomes
b58d9e957f Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2025-02-10 12:53:06 -03:00
Davidson Gomes
e5137e1aac Merge pull request #1221 from mbap-dev/fix-audio
Fix audio send duplicate from chatwoot.
2025-02-10 12:52:48 -03:00
mbap-dev
6b120e5da2 Fix audio send duplicate from chatwoot. 2025-02-09 20:12:40 -03:00
Davidson Gomes
342dacc398 Merge pull request #1217 from Desarrollo-TMS/feat-message-location
feat: add message location support whatsapp meta
2025-02-07 16:57:45 -03:00
Alexis Hernandez
d75c37e233 feat: add message location support meta 2025-02-07 13:04:36 -04:00
Davidson Gomes
36df38d78b Merge pull request #1215 from rafwell/main
Fix instance creation on v2.2.3
2025-02-07 12:34:32 -03:00
Davidson Gomes
68c6ad4f91 Merge pull request #1211 from joaosouz4dev/main
feat: notconvertsticket for animated stickers
2025-02-07 11:38:00 -03:00
Davidson Gomes
8685f2fdc4 Merge pull request #1214 from wayre/develop
Adicionado suporte para obter Catálogos e Coleções no WhatsApp Business
2025-02-07 11:19:11 -03:00
Rafael Souza
5a50381a8e Fix table name 2025-02-07 10:07:38 -03:00
Rafael Souza
736ca5e4b6 Fix case in table name 2025-02-07 09:52:08 -03:00
Wayre Avelar
6c1355b64b feat: Criado um novo grupo de rotas (business) para tratar dos catalogos de produtos e Coleções evitando alterações desnecessárias em arquivos do repositório 2025-02-07 00:56:49 -03:00
João Victor Souza
95401cf9b0 fix: rollback deploy_database.sh 2025-02-06 18:05:31 -03:00
João Victor Souza
0c5d28bb6c Merge branch 'main' of https://github.com/EvolutionAPI/evolution-api 2025-02-06 18:02:36 -03:00
João Victor Souza
0d1e7c08c9 feat: sendsticket notconvertsticket 2025-02-06 17:56:21 -03:00
Davidson Gomes
d665474404 feat: Add NATS integration support to the event system
- Added NATS package to dependencies
- Created Prisma schema models for NATS configuration
- Implemented NATS controller, router, and event management
- Updated instance controller and event manager to support NATS
- Added NATS configuration options in environment configuration
- Included NATS events in instance validation schema
2025-02-05 17:05:29 -03:00
Davidson Gomes
9a72b90ab2 refactor: Make RabbitMQ prefix key optional in configuration 2025-02-04 17:57:21 -03:00
Davidson Gomes
b143363c5d Merge pull request #1201 from wayre/catalogProducts
Feat: Adicionei suporte para obter o Catálogos de Produtos e as Coleções de Produtos para a versão 2.2.3
2025-02-04 09:59:59 -03:00
Wayre Avelar
56a8e09ba8 chore: eslint applied 2025-02-04 09:04:41 -03:00
Davidson Gomes
023e030802 Merge pull request #1195 from GrimBit1/main
Refactor edit and delete message functionality in BaileyStartupService
2025-02-04 06:22:49 -03:00
Wayre Avelar
e27db0612f feat: Add support to get Catalogs and Collections with new routes: '{{baseUrl}}/chat/fetchCatalogs' and '{{baseUrl}}/chat/fetchCollections' 2025-02-04 03:51:47 -03:00
Davidson Gomes
e51b6e9270 fix: improve message deduplication and edited message handling in Baileys service
- Refactor edited message detection logic
- Prevent duplicate message processing for edited messages
- Optimize message key caching mechanism
2025-02-03 17:19:07 -03:00
Davidson Gomes
867e8493aa fix: added cache to identify duplicated messages on events
- Update Docker image repository to evoapicloud/evolution-api
- Modify contact email to contato@evolution-api.com
- Update Docker Compose, Dockerfile, and workflow files
- Add Docker image badge to README
- Include additional content creator in README
- Implement message deduplication cache in Baileys service
2025-02-03 15:37:11 -03:00
Davidson Gomes
8135994340 Merge tag '2.2.3' into develop
v
2025-02-03 11:52:51 -03:00
Davidson Gomes
427c994993 Merge branch 'release/2.2.3' 2025-02-03 11:52:49 -03:00
Davidson Gomes
da74611769 version: 2.2.3 2025-02-03 11:52:37 -03:00
Aditya Nandwana
91e7a32209 Refactor createJid method in BaileysStartupService 2025-02-03 14:24:27 +05:30
Aditya Nandwana
4137984b5d Refactor message deletion in BaileysStartupService 2025-02-03 11:44:41 +05:30
Aditya Nandwana
96821f5d9a Refactor BaileysStartupService updateMessage method 2025-02-03 11:43:53 +05:30
Davidson Gomes
3c2ea5c67c feat: Re-enable group metadata caching in Baileys service
- Restore group metadata caching mechanisms
- Uncomment cache-related methods for group updates and participants
- Implement conditional group metadata retrieval based on cache configuration
2025-02-02 16:42:17 -03:00
Davidson Gomes
4a5d7a91e2 chore: Update Baileys package to latest commit hash 2025-02-02 11:39:45 -03:00
Davidson Gomes
9109b140a9 Merge pull request #1192 from tonimoreiraa/fix-dify-truncated-messages
fix(dify-service): Truncated messages (agent bot)
2025-02-01 16:11:07 -03:00
Davidson Gomes
ff5a8adc71 Merge pull request #1190 from GrimBit1/main
Fix Message deletion in Whatsapp Bailey Service
2025-02-01 16:08:56 -03:00
Davidson Gomes
b09638600e chore: Upgrade Baileys to version 6.7.12
- Update Baileys package to latest version
- Bump package version to 2.2.3
2025-02-01 15:39:14 -03:00
Toni Moreira
fc84e0f327 fix: dify truncated messages 2025-02-01 11:47:50 -03:00
Aditya Nandwana
c1494ca035 Refactor logical message deletion in BaileysStartupService 2025-02-01 16:09:08 +05:30
Davidson Gomes
7ea46a05ca version: 2.2.3 2025-01-31 17:45:09 -03:00
Davidson Gomes
f8f1cbf4a2 fix: Disable group metadata caching in Baileys service
- Remove group metadata caching mechanisms
- Modify group-related cache update methods
- Simplify group metadata retrieval process
2025-01-31 17:44:44 -03:00
Davidson Gomes
79b1c6bb1c feat: Add configurable file/cache storage for authentication state
- Update Baileys package to version 6.7.10
- Implement conditional storage mechanism for authentication state
- Add support for file-based or Redis cache storage based on environment configuration
- Re-enable previously commented out file handling utility functions
2025-01-31 17:38:42 -03:00
Davidson Gomes
169d2f797b Merge tag '2.2.2' into develop
v
2025-01-31 07:14:30 -03:00
Davidson Gomes
db9cdbfc38 Merge branch 'release/2.2.2' 2025-01-31 07:14:29 -03:00
Davidson Gomes
6afcc958c5 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2025-01-31 07:13:54 -03:00
Davidson Gomes
2166aad1d3 Merge tag '2.2.2' into develop
v
2025-01-31 07:13:30 -03:00
Davidson Gomes
14fea2f5e0 Merge branch 'release/2.2.2' 2025-01-31 07:13:25 -03:00
Davidson Gomes
9122dae262 version: 2.2.2 2025-01-31 07:13:10 -03:00
Davidson Gomes
96549664c9 Merge pull request #1179 from MarceloSoaresJr/develop
bugfix: SQL query column quoting in ChannelStartupService
2025-01-28 18:41:14 -03:00
Davidson Gomes
fa19c7fa89 feat(rabbitmq): Add prefix key configuration for queue names 2025-01-28 18:01:28 -03:00
Marcelo Soares
503728e1e7 fix: SQL query column quoting in ChannelStartupService 2025-01-27 17:42:04 -03:00
Davidson Gomes
f11e3247f0 Merge tag '2.2.1' into develop
* Retry system for send webhooks
* Message filtering to support timestamp range queries
* Chats filtering to support timestamp range queries

* Correction of webhook global
* Fixed send audio with whatsapp cloud api
* Refactor on fetch chats
* Refactor on Evolution Channel
2025-01-22 14:39:40 -03:00
Davidson Gomes
7f10a0eecd Merge branch 'release/2.2.1' 2025-01-22 14:39:27 -03:00
Davidson Gomes
a1085b4a4d version: 2.2.1 2025-01-22 14:38:50 -03:00
Davidson Gomes
b1b2d18e5d Remove redundant migration cleanup step for wavoip_token in PostgreSQL migration file. This change simplifies the migration process by eliminating unnecessary deletion of previous migration records, ensuring a cleaner migration history. 2025-01-22 12:01:17 -03:00
Davidson Gomes
37a9e316a8 Refactor database migrations and schema for Chat and Setting tables
- Removed the unique constraint on `remoteJid` and `instanceId` in the `Chat` model to prevent potential migration failures due to existing duplicates.
- Deleted the old migration file for adding `wavoipToken` to the `Setting` table and created a new migration that includes a check for the column's existence before adding it, ensuring smoother migration processes.
- Updated migration logic to clear previous migrations related to `wavoip_token` to maintain a clean migration history.
2025-01-22 11:58:34 -03:00
Davidson Gomes
f340f7716f Remove unused dependencies from ChannelController to streamline integration logic 2025-01-22 11:14:05 -03:00
Davidson Gomes
dab843f118 changelog 2025-01-22 11:09:29 -03:00
Davidson Gomes
f7db442a94 Refactor Instance Management with Additional Profile Information
- Added new fields `ownerJid`, `profileName`, and `profilePicUrl` to the Instance DTO for improved user identification and personalization.
- Updated InstanceController to include the new profile information in instance data handling.
- Enhanced WAMonitoringService to utilize the additional profile fields, improving the context of instance data during monitoring operations.
2025-01-22 11:09:05 -03:00
Davidson Gomes
3a04f7587e Refactor Evolution Channel with new instance management and media handling features
- Added `setInstance` method to manage instance details and trigger events for instance creation.
- Refactored `connectToWhatsapp` to improve error handling and streamline connection logic.
- Enhanced `sendMessageWithTyping` to support various message types including buttons and lists, and integrated file handling for media messages.
- Updated `processAudio` to handle audio conversion using an external API, improving audio processing capabilities.
- Introduced new methods for sending button messages and handling audio messages, enhancing user interaction features.
- Improved contact update logic to ensure accurate data synchronization with the database and external services.
2025-01-22 11:06:10 -03:00
Davidson Gomes
666c0b514d Chats filtering to support timestamp range queries
- Introduced a new `timestampFilter` to allow filtering messages based on `messageTimestamp` within specified `gte` and `lte` ranges.
- Updated SQL query logic in `ChannelStartupService` to incorporate the timestamp filtering, improving message retrieval accuracy.
2025-01-22 10:22:09 -03:00
Davidson Gomes
ab5eb80edd Refactor JID creation and fetch chats
- Introduced a new utility function `createJid` to standardize JID creation across the application, replacing the previous method in `ChannelStartupService`.
- Updated multiple services to utilize the new `createJid` function for improved consistency and maintainability.
- Added a `cleanMessageData` method in `ChannelStartupService` to sanitize message objects before processing.
- Updated CHANGELOG to reflect the refactor on chat fetching logic and the introduction of the new JID utility.
2025-01-22 10:16:48 -03:00
Davidson Gomes
b0219e5e5a Update version to 2.2.1, enhance database migration, and update dependencies
- Updated version labels in Dockerfile and package-lock.json to 2.2.1.
- Added a new column `wavoipToken` to the `Setting` table in the PostgreSQL migration.
- Implemented a cleanup step to remove duplicate records in the `Chat` table before creating a unique index.
- Upgraded various package dependencies to their latest versions for improved functionality and security.
2025-01-21 17:39:03 -03:00
Davidson Gomes
d7b4965d60 Update CHANGELOG for version 2.2.1
- Added features: retry system for sending webhooks and enhanced message filtering to support timestamp range queries.
- Fixed issues: corrected global webhook handling and resolved audio sending problems with WhatsApp Cloud API.
2025-01-21 17:32:24 -03:00
Davidson Gomes
cfe6bd9ae0 Refactor instance deletion logic and enhance WhatsApp connection updates
- Updated the `deleteInstance` method to allow logout for instances in 'connecting' or 'open' states, improving instance management.
- Enhanced the `BaileysStartupService` to include additional profile information (wuid, profileName, profilePictureUrl) in connection update webhooks.
- Removed redundant webhook data sending logic, streamlining connection state updates for better performance.
- Adjusted settings schema to ensure required fields are properly validated.
2025-01-17 17:54:18 -03:00
Davidson Gomes
ac58f58bbc Update package version to 2.2.1 in package.json 2025-01-17 16:07:03 -03:00
Davidson Gomes
cb08f6b152 Refactor WebhookController to implement retry logic for webhook requests
- Introduced a new `retryWebhookRequest` method to handle retries for failed webhook requests, allowing up to 10 attempts with a delay of 30 seconds between each.
- Updated error logging to provide detailed information on each retry attempt, including the attempt number and error details.
- Enhanced existing webhook request handling to utilize the new retry logic, improving reliability in sending webhook data.
- Modified error messages to be more informative, indicating when all retry attempts have failed.
2025-01-17 16:06:23 -03:00
Davidson Gomes
a817d62067 Enhance message filtering in ChannelStartupService to support timestamp range queries
- Added support for filtering messages based on a timestamp range in the `ChannelStartupService`.
- Introduced a `timestampFilter` object to handle `gte` and `lte` conditions for `messageTimestamp`.
- Updated message count queries to include the new timestamp filtering logic.
2025-01-16 17:40:19 -03:00
Davidson Gomes
540467293c Enhance settings and integrate Baileys controller for WhatsApp functionality
- Added `wavoipToken` field to `Setting` model in both MySQL and PostgreSQL schemas.
- Updated `package.json` and `package-lock.json` to include `mime-types` and `socket.io-client` dependencies.
- Introduced `BaileysController` and `BaileysRouter` for handling WhatsApp interactions.
- Refactored media type handling to use `mime-types` instead of `mime` across various services.
- Updated DTOs and validation schemas to accommodate the new `wavoipToken` field.
- Implemented voice call functionalities using the Wavoip service in the Baileys integration.
- Enhanced event handling in the WebSocket controller to support new features.
2025-01-16 11:58:33 -03:00
Davidson Gomes
616ae0a7eb Update ESLint configuration, Dockerfile, and package dependencies; add GitHub Actions workflow for code quality checks
- Changed ESLint parser options to use 'module' as source type.
- Updated Dockerfile to remove force flag from npm install command.
- Upgraded 'mime' package from version 3.0.0 to 4.0.0 in package.json.
- Added '@types/mime' as a development dependency.
- Updated TypeScript configuration to use 'CommonJS' module format.
- Introduced a new GitHub Actions workflow for checking code quality, including linting and build checks.
2025-01-09 17:04:33 -03:00
Davidson Gomes
d598c4ed0b Update ESLint configuration, Dockerfile, and package dependencies; refactor bot trigger logic
- Updated ESLint configuration to use TypeScript project references and adjusted parser options.
- Modified Dockerfile to include OpenSSL in both builder and final stages.
- Changed `mime` package version from `^4.0.6` to `^3.0.0` in `package.json` and updated TypeScript ESLint packages to `^6.21.0`.
- Refactored `findBotByTrigger` function to remove unnecessary settings repository parameter.
- Adjusted bot trigger logic in multiple controller files to streamline function calls.
2025-01-09 12:57:21 -03:00
Davidson Gomes
ca451bfacc Merge pull request #1126 from jesus-chacon/update_chats
Fix: cuid security deprecation, update libs, lint and improve chat DB update
2025-01-07 13:06:23 -03:00
Davidson Gomes
f971f388d5 Merge pull request #1134 from Allyson-Santana/bugfix/import-prisma-type
bugfix: import prisma types
2025-01-07 13:05:22 -03:00
Jesus
236b0f9b26 Fix import prisma types 2025-01-07 08:59:51 +01:00
Jesus
0f2498bbaa Fix prettier errors 2025-01-07 08:50:34 +01:00
Jesus
2816a16387 Added lock and update some libs 2025-01-07 08:50:24 +01:00
Jesus
a82669b6fa Improve chat update and fix unread messages 2025-01-07 08:25:37 +01:00
Jesus
72a33ae59f fix: lint and prettify 2025-01-07 08:25:37 +01:00
Jesus
425d340956 Fix: cuid security deprecacion 2025-01-07 08:25:37 +01:00
Jesus
f2872cf59a Update several libs 2025-01-07 08:25:37 +01:00
Allyson Santana
1773f2738e bugfix: import prisma types 2025-01-06 18:31:40 -03:00
Davidson Gomes
18626c9846 Merge pull request #1131 from FaelN1/develop
fix: include filename in media message payload for WhatsApp Business
2025-01-06 12:20:19 -03:00
Davidson Gomes
6212ee3eb0 Merge pull request #1125 from fmorett/patch-1
fix cannot read null of mentioned
2025-01-06 12:18:56 -03:00
Davidson Gomes
8c877029e5 Merge pull request #1124 from ScrashOff/fix/instance-creation-webhook-and-chatwoot-settings
fix: instance create w/webhook and chatwoot settings
2025-01-06 12:18:25 -03:00
Davidson Gomes
0000c1c05c Merge pull request #1121 from EdmilsonSantana/fix/upgrade_prima_to_6_1_0
fix: upgrade Prisma packages to 6.1.0 to resolve an issue in Alpine images
2025-01-06 12:17:29 -03:00
Davidson Gomes
16daf9be8f Merge pull request #1119 from MarksonSolutions/develop
send audio using sendWhatsAppAudio route, wabussines
2025-01-06 12:17:12 -03:00
Rafael Nicolas Barbosa Moreira
e0c960cc54 chore: remove redundant comment about filename condition 2025-01-03 16:35:14 -03:00
Rafael Nicolas Barbosa Moreira
08ceb803c8 fix: conditionally include filename for non-image media 2025-01-03 16:33:34 -03:00
Rafael Nicolas Barbosa Moreira
6b72550286 fix: include filename in media message payload for WhatsApp Business 2025-01-03 15:56:39 -03:00
Fabio Moretti
df0990df0f fix cannot read null of mentioned 2024-12-28 02:15:26 +01:00
Fellipe "ScrashOff
60c9e231be fix: instance create w/webhook and chatwoot settings 2024-12-27 16:58:15 -03:00
João Victor Souza
2fb6318011 feat: updates api evolution for docker 2024-12-27 10:40:04 -03:00
EdmilsonSantana
070159568e fix: upgrading prisma packages to 6.1.0 to fix a issue in alpine images 2024-12-26 07:59:54 -03:00
MarksonSolutions
bfba702fde send audio using sendWhatsAppAudio route, wabussines 2024-12-20 16:00:15 -04:00
Davidson Gomes
d5c2cfb4f9 Merge pull request #1103 from roberto0arruda/fix/sentry-auto-instrument-express
chore: improve instrumentSentry.ts import before another import
2024-12-11 15:38:24 -03:00
Roberto Arruda
cdab5e2ae8 chore: improve instrumentSentry.ts import before another import
Signed-off-by: Roberto Arruda <roberto0arruda@hotmail.com>
2024-12-10 12:13:42 -04:00
Davidson Gomes
682eaa995f Merge pull request #1088 from joaosantanadev/develop
[FIX]Correction of webhook global
2024-11-27 12:10:52 -03:00
Davidson Gomes
62ea22a06a Merge pull request #1076 from verbeux-ai/main
[FIX] Fixando label handler e chats
2024-11-27 12:10:31 -03:00
joaosantanadev
370025b8f9 Correction of webhook global 2024-11-25 12:59:48 -03:00
Pedro Ivo
5d13f7055b Merge remote-tracking branch 'refs/remotes/evo/develop'
# Conflicts:
#	prisma/mysql-schema.prisma
#	src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
2024-11-22 10:11:50 -03:00
Davidson Gomes
c4e87c160e Merge pull request #1078 from Richards0nd/feature/windows-commands-support
Adaptação de scripts e comandos para compatibilidade com Windows
2024-11-21 14:51:14 -03:00
Davidson Gomes
ee8e937fad Merge pull request #1073 from lucastononro/fix-prisma-p1001-dockercompose
Update docker-compose and .env.example
2024-11-21 14:49:10 -03:00
Davidson Gomes
d5a7e03ec2 Merge pull request #1012 from yousseefspires/fix/chats-messages
Add unique in Chat by instance/remotejid
2024-11-21 14:48:40 -03:00
Davidson Gomes
52b6b61ac9 Merge pull request #1071 from AlanCezarAraujo/fix/openai-audio
Correção de envio de áudio para OpenAI via Cloud API
2024-11-21 14:46:51 -03:00
Pedro Ivo
6e6711a5af fix: get instance id in right place on handle label 2024-11-19 10:26:01 -03:00
Pedro Ivo
013fa9dc08 fix: join is considering instance id 2024-11-19 10:17:49 -03:00
Pedro Ivo
a42bc988ec fix: add/remove saving on db and improve add query for startup case 2024-11-19 10:15:08 -03:00
Richards0n
3582cd38fb Add Windows support for database migration commands in package.json 2024-11-18 20:07:08 -03:00
Richards0n
bfd8c08987 Add Windows support for database deployment and improve error handling in runWithProvider.js 2024-11-18 20:01:10 -03:00
Pedro Ivo
ecbbc5b090 fix: avoid concurrency cases in label handler 2024-11-17 20:04:27 -03:00
Lucas Tonon
b7e15be418 Update docker-compose and .env.example
Error:

Database URL: 
Environment variables loaded from .env
Prisma schema loaded from prisma/postgresql-schema.prisma
Datasource "db": PostgreSQL database "evolution", schema "public" at "localhost:5432"
Error: P1001: Can't reach database server at localhost:5432

Fix:

Update `docker-compose.yaml` and `.env.example` files to configure PostgreSQL service and connection URI.

* **docker-compose.yaml**
  - Add `listen_addresses=*` command to the `postgres` service.
  - Add environment variables: `POSTGRES_USER=user`, `POSTGRES_PASSWORD=pass`, `POSTGRES_DB=evolution`, `POSTGRES_HOST_AUTH_METHOD=trust`.

* **.env.example**
  - Update `DATABASE_CONNECTION_URI` to `postgresql://user:pass@postgres:5432/evolution?schema=public`.

---

For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/EvolutionAPI/evolution-api?shareId=XXXX-XXXX-XXXX-XXXX).
2024-11-17 11:36:12 -03:00
Alan Cezar
1d5508736e corrigido o envio de áudio para OpenAI 2024-11-15 17:57:38 -03:00
Pedro Ivo
cefe3ef6c3 fix: chats must be unique 2024-11-15 17:38:26 -03:00
Davidson Gomes
09a33a423e Merge pull request #1070 from help-corretora/main
Quando a conversa está aberta e o cliente manda mensagem, o status da conversa muda para pendente.
2024-11-15 10:59:10 -03:00
Help Corretora
e092a80aeb Merge pull request #1 from vitor-help/main
erro, colocando mensagens em pendente
2024-11-14 20:50:58 -03:00
Vitor
99a533afc1 o chat está colocando a conversa como pendente quando o cliente manda mensagem, esse código visa corrigir isso. 2024-11-14 20:48:47 -03:00
Davidson Gomes
b603021f56 Merge pull request #1067 from Alexandre-Prado/fix-chatbots-send-message
fix: esvazia textBuffer dos chatbots no sendMessageWhatsapp quando splitMessages é true
2024-11-14 18:37:07 -03:00
Davidson Gomes
7bf0fd1c36 Merge pull request #1058 from Richards0nd/newPhoneNumberContent
Correção: Número de telefone apresentado no nome do contato (CW Service)
2024-11-14 18:36:43 -03:00
Davidson Gomes
52cf4fa8b8 Merge pull request #1069 from AlanCezarAraujo/fix/whatsapp-images
Correção de Imagens no Cloud API
2024-11-14 18:36:24 -03:00
Alan Cezar
ef7574273c message recorded for no media messages 2024-11-14 09:11:21 -03:00
Alan Cezar
d797d8177c added isMediaMessage method 2024-11-14 09:10:11 -03:00
Alan Cezar
e7ed1446ee saved base64 file in memory 2024-11-14 09:09:27 -03:00
Alan Cezar
5eaabfb1eb fixed messageId 2024-11-14 09:08:52 -03:00
Alan Cezar
59383d5944 fixed remoteJid 2024-11-14 09:08:23 -03:00
Alan Cezar
d87d7c0775 fixed image mime type 2024-11-14 09:07:57 -03:00
Alexandre Prado
753f4ba141 fix: chatbots send message text buffer 2024-11-14 02:25:15 -03:00
Richards0n
7ef8afa9b3 Refactor phone number formatting in ChatwootService to improve parsing logic 2024-11-11 16:57:04 -03:00
Richardson Douglas
c5fd81ddbf Merge pull request #1 from Richards0nd/develop
Develop
2024-11-11 16:49:17 -03:00
Davidson Gomes
8f855b4bfd Merge pull request #1046 from rafwell/fixMessageStatus
Fix Message.Status as String
2024-11-11 15:44:25 -03:00
Davidson Gomes
084be1cee2 Merge pull request #1044 from Richards0nd/newPhoneNumberContent
Adiciona o número de telefone do remetente nas mensagens recebidas do WhatsApp antes do nome do contato (GRUPOS)
2024-11-11 15:44:05 -03:00
Davidson Gomes
997aceeebf Merge pull request #1038 from fmedeiros95/develop
Ajuste nos tipos de mensagem respondidas
2024-11-11 15:43:49 -03:00
Rafael Souza
c8410bd146 Fix Message.Status as String 2024-11-08 07:15:23 -03:00
Richards0n
3d51b45e2b feat: format participant phone number in messages 2024-11-08 00:25:00 -03:00
Felipe Medeiros
50d84d1a08 Merge branch 'EvolutionAPI:develop' into develop 2024-11-05 17:17:15 -03:00
Felipe Medeiros
fd1f08a41e feat: handle quoted messages in WhatsApp integration 2024-11-05 17:16:41 -03:00
Davidson Gomes
8dc27919b1 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2024-11-02 08:13:04 -03:00
Davidson Gomes
6591a67ab6 feat: sh local install 2024-11-02 08:12:30 -03:00
Davidson Gomes
08192a03ca Merge pull request #1023 from adriel12319/main
Fix Ios Response Button
2024-11-01 16:10:07 -03:00
Adriel Santos Araujo
8f86c9d758 Update getConversationMessage.ts 2024-10-31 23:19:17 -03:00
Adriel Santos Araujo
0e5f9e3b77 Update getConversationMessage.ts 2024-10-31 22:52:45 -03:00
yousseefs
f6ccd58dee some fixs 2024-10-29 18:43:31 +00:00
197 changed files with 37875 additions and 9348 deletions

107
.cursor/rules/README.md Normal file
View File

@@ -0,0 +1,107 @@
# Evolution API Cursor Rules
Este diretório contém as regras e configurações do Cursor IDE para o projeto Evolution API.
## Estrutura dos Arquivos
### Arquivos Principais (alwaysApply: true)
- **`core-development.mdc`** - Princípios fundamentais de desenvolvimento
- **`project-context.mdc`** - Contexto específico do projeto Evolution API
- **`cursor.json`** - Configurações do Cursor IDE
### Regras Especializadas (alwaysApply: false)
Estas regras são ativadas automaticamente quando você trabalha nos arquivos correspondentes:
#### Camadas da Aplicação
- **`specialized-rules/service-rules.mdc`** - Padrões para services (`src/api/services/`)
- **`specialized-rules/controller-rules.mdc`** - Padrões para controllers (`src/api/controllers/`)
- **`specialized-rules/dto-rules.mdc`** - Padrões para DTOs (`src/api/dto/`)
- **`specialized-rules/guard-rules.mdc`** - Padrões para guards (`src/api/guards/`)
- **`specialized-rules/route-rules.mdc`** - Padrões para routers (`src/api/routes/`)
#### Tipos e Validação
- **`specialized-rules/type-rules.mdc`** - Definições TypeScript (`src/api/types/`)
- **`specialized-rules/validate-rules.mdc`** - Schemas de validação (`src/validate/`)
#### Utilitários
- **`specialized-rules/util-rules.mdc`** - Funções utilitárias (`src/utils/`)
#### Integrações
- **`specialized-rules/integration-channel-rules.mdc`** - Integrações de canal (`src/api/integrations/channel/`)
- **`specialized-rules/integration-chatbot-rules.mdc`** - Integrações de chatbot (`src/api/integrations/chatbot/`)
- **`specialized-rules/integration-storage-rules.mdc`** - Integrações de storage (`src/api/integrations/storage/`)
- **`specialized-rules/integration-event-rules.mdc`** - Integrações de eventos (`src/api/integrations/event/`)
## Como Usar
### Referências Cruzadas
Os arquivos principais fazem referência aos especializados usando a sintaxe `@specialized-rules/nome-do-arquivo.mdc`. Quando você trabalha em um arquivo específico, o Cursor automaticamente carrega as regras relevantes.
### Exemplo de Uso
Quando você edita um arquivo em `src/api/services/`, o Cursor automaticamente:
1. Carrega `core-development.mdc` (sempre ativo)
2. Carrega `project-context.mdc` (sempre ativo)
3. Carrega `specialized-rules/service-rules.mdc` (ativado pelo glob pattern)
### Padrões de Código
Cada arquivo de regras contém:
- **Estruturas padrão** - Como organizar o código
- **Padrões de nomenclatura** - Convenções de nomes
- **Exemplos práticos** - Código de exemplo
- **Anti-padrões** - O que evitar
- **Testes** - Como testar o código
- **Padrões de Commit** - Conventional Commits com commitlint
## Configuração do Cursor
O arquivo `cursor.json` contém:
- Configurações de formatação
- Padrões de código específicos do Evolution API
- Diretórios principais do projeto
- Integrações e tecnologias utilizadas
## Manutenção
Para manter as regras atualizadas:
1. Analise novos padrões no código
2. Atualize as regras especializadas correspondentes
3. Mantenha os exemplos sincronizados com o código real
4. Documente mudanças significativas
## Tecnologias Cobertas
- **Backend**: Node.js 20+ + TypeScript 5+ + Express.js
- **Database**: Prisma ORM (PostgreSQL/MySQL)
- **Cache**: Redis + Node-cache
- **Queue**: RabbitMQ + Amazon SQS
- **Real-time**: Socket.io
- **Storage**: AWS S3 + Minio
- **Validation**: JSONSchema7
- **Logging**: Pino
- **WhatsApp**: Baileys + Meta Business API
- **Integrations**: Chatwoot, Typebot, OpenAI, Dify
## Estrutura do Projeto
```
src/
├── api/
│ ├── controllers/ # Controllers (HTTP handlers)
│ ├── services/ # Business logic
│ ├── dto/ # Data Transfer Objects
│ ├── guards/ # Authentication/Authorization
│ ├── routes/ # Express routers
│ ├── types/ # TypeScript definitions
│ └── integrations/ # External integrations
│ ├── channel/ # WhatsApp channels (Baileys, Business API)
│ ├── chatbot/ # Chatbot integrations
│ ├── event/ # Event integrations
│ └── storage/ # Storage integrations
├── cache/ # Cache implementations
├── config/ # Configuration files
├── utils/ # Utility functions
├── validate/ # Validation schemas
└── exceptions/ # Custom exceptions
```
Este sistema de regras garante consistência no código e facilita o desenvolvimento seguindo os padrões estabelecidos do Evolution API.

View File

@@ -0,0 +1,167 @@
---
description: Core development principles and standards for Evolution API development
globs:
alwaysApply: true
---
# Evolution API Development Standards
## Cross-References
- **Project Context**: @project-context.mdc for Evolution API-specific patterns
- **Specialized Rules**:
- @specialized-rules/service-rules.mdc for service layer patterns
- @specialized-rules/controller-rules.mdc for controller patterns
- @specialized-rules/dto-rules.mdc for DTO validation patterns
- @specialized-rules/guard-rules.mdc for authentication/authorization
- @specialized-rules/route-rules.mdc for router patterns
- @specialized-rules/type-rules.mdc for TypeScript definitions
- @specialized-rules/util-rules.mdc for utility functions
- @specialized-rules/validate-rules.mdc for validation schemas
- @specialized-rules/integration-channel-rules.mdc for channel integrations
- @specialized-rules/integration-chatbot-rules.mdc for chatbot integrations
- @specialized-rules/integration-storage-rules.mdc for storage integrations
- @specialized-rules/integration-event-rules.mdc for event integrations
- **TypeScript/Node.js**: Node.js 20+ + TypeScript 5+ best practices
- **Express/Prisma**: Express.js + Prisma ORM patterns
- **WhatsApp Integrations**: Baileys + Meta Business API patterns
## Senior Engineer Context - Evolution API Platform
- You are a senior software engineer working on a WhatsApp API platform
- Focus on Node.js + TypeScript + Express.js full-stack development
- Specialized in real-time messaging, WhatsApp integrations, and event-driven architecture
- Apply scalable patterns for multi-tenant API platform
- Consider WhatsApp integration workflow implications and performance at scale
## Fundamental Principles
### Code Quality Standards
- **Simplicity First**: Always prefer simple solutions over complex ones
- **DRY Principle**: Avoid code duplication - check for existing similar functionality before implementing
- **Single Responsibility**: Each function/class should have one clear purpose
- **Readable Code**: Write code that tells a story - clear naming and structure
### Problem Resolution Approach
- **Follow Existing Patterns**: Use established Service patterns, DTOs, and Integration patterns
- **Event-Driven First**: Leverage EventEmitter2 for event publishing when adding new features
- **Integration Pattern**: Follow existing WhatsApp integration patterns for new channels
- **Conservative Changes**: Prefer extending existing services over creating new architecture
- **Clean Migration**: Remove deprecated patterns when introducing new ones
- **Incremental Changes**: Break large changes into smaller, testable increments with proper migrations
### File and Function Organization - Node.js/TypeScript Structure
- **Services**: Keep services focused and under 200 lines
- **Controllers**: Keep controllers thin - only routing and validation
- **DTOs**: Use JSONSchema7 for all input validation
- **Integrations**: Follow `src/api/integrations/` structure for new integrations
- **Utils**: Extract common functionality into well-named utilities
- **Types**: Define clear TypeScript interfaces and types
### Code Analysis and Reflection
- After writing code, deeply reflect on scalability and maintainability
- Provide 1-2 paragraph analysis of code changes
- Suggest improvements or next steps based on reflection
- Consider performance, security, and maintenance implications
## Development Standards
### TypeScript Standards
- **Strict Mode**: Always use TypeScript strict mode
- **No Any**: Avoid `any` type - use proper typing
- **Interfaces**: Define clear contracts with interfaces
- **Enums**: Use enums for constants and status values
- **Generics**: Use generics for reusable components
### Error Handling Standards
- **HTTP Exceptions**: Use appropriate HTTP status codes
- **Logging**: Structured logging with context
- **Retry Logic**: Implement retry for external services
- **Graceful Degradation**: Handle service failures gracefully
### Security Standards
- **Input Validation**: Validate all inputs with JSONSchema7
- **Authentication**: Use API keys and JWT tokens
- **Rate Limiting**: Implement rate limiting for APIs
- **Data Sanitization**: Sanitize sensitive data in logs
### Performance Standards
- **Caching**: Use Redis for frequently accessed data
- **Database**: Optimize Prisma queries with proper indexing
- **Memory**: Monitor memory usage and implement cleanup
- **Async**: Use async/await properly with error handling
## Communication Standards
### Language Requirements
- **User Communication**: Always respond in Portuguese (PT-BR)
- **Code Comments**: English for technical documentation
- **API Documentation**: English for consistency
- **Error Messages**: Portuguese for user-facing errors
### Documentation Standards
- **Code Comments**: Document complex business logic
- **API Documentation**: Document all public endpoints
- **README**: Keep project documentation updated
- **Changelog**: Document breaking changes
## Quality Assurance
### Testing Standards
- **Unit Tests**: Test business logic in services
- **Integration Tests**: Test API endpoints
- **Mocks**: Mock external dependencies
- **Coverage**: Aim for 70%+ test coverage
### Code Review Standards
- **Peer Review**: All code must be reviewed
- **Automated Checks**: ESLint, Prettier, TypeScript
- **Security Review**: Check for security vulnerabilities
- **Performance Review**: Check for performance issues
### Commit Standards (Conventional Commits)
- **Format**: `type(scope): subject` (max 100 characters)
- **Types**:
- `feat` - New feature
- `fix` - Bug fix
- `docs` - Documentation changes
- `style` - Code style changes (formatting, etc)
- `refactor` - Code refactoring
- `perf` - Performance improvements
- `test` - Adding or updating tests
- `chore` - Maintenance tasks
- `ci` - CI/CD changes
- `build` - Build system changes
- `revert` - Reverting changes
- `security` - Security fixes
- **Examples**:
- `feat(api): add WhatsApp message status endpoint`
- `fix(baileys): resolve connection timeout issue`
- `docs(readme): update installation instructions`
- `refactor(service): extract common message validation logic`
- **Tools**: Use `npm run commit` (Commitizen) for guided commits
- **Validation**: Enforced by commitlint on commit-msg hook
## Evolution API Specific Patterns
### WhatsApp Integration Patterns
- **Connection Management**: One connection per instance
- **Event Handling**: Proper event listeners for Baileys
- **Message Processing**: Queue-based message processing
- **Error Recovery**: Automatic reconnection logic
### Multi-Database Support
- **Schema Compatibility**: Support PostgreSQL and MySQL
- **Migration Sync**: Keep migrations synchronized
- **Type Safety**: Use Prisma generated types
- **Connection Pooling**: Proper database connection management
### Cache Strategy
- **Redis Primary**: Use Redis for distributed caching
- **Local Fallback**: Node-cache for local fallback
- **TTL Strategy**: Appropriate TTL for different data types
- **Cache Invalidation**: Proper cache invalidation patterns
### Event System
- **EventEmitter2**: Use for internal events
- **WebSocket**: Socket.io for real-time updates
- **Queue Systems**: RabbitMQ/SQS for async processing
- **Webhook Processing**: Proper webhook validation and processing

179
.cursor/rules/cursor.json Normal file
View File

@@ -0,0 +1,179 @@
{
"version": "1.0",
"description": "Cursor IDE configuration for Evolution API project",
"rules": {
"general": {
"max_line_length": 120,
"indent_size": 2,
"end_of_line": "lf",
"charset": "utf-8",
"trim_trailing_whitespace": true,
"insert_final_newline": true
},
"typescript": {
"quotes": "single",
"semi": true,
"trailing_comma": "es5",
"bracket_spacing": true,
"arrow_parens": "avoid",
"print_width": 120,
"tab_width": 2,
"use_tabs": false,
"single_quote": true,
"end_of_line": "lf",
"strict": true,
"no_implicit_any": true,
"strict_null_checks": true
},
"javascript": {
"quotes": "single",
"semi": true,
"trailing_comma": "es5",
"bracket_spacing": true,
"arrow_parens": "avoid",
"print_width": 120,
"tab_width": 2,
"use_tabs": false,
"single_quote": true,
"end_of_line": "lf",
"style_guide": "eslint-airbnb"
},
"json": {
"tab_width": 2,
"use_tabs": false,
"parser": "json"
},
"ignore": {
"files": [
"node_modules/**",
"dist/**",
"build/**",
".git/**",
"*.min.js",
"*.min.css",
".env",
".env.*",
".env.example",
"coverage/**",
"*.log",
"*.lock",
"pnpm-lock.yaml",
"package-lock.json",
"yarn.lock",
"log/**",
"tmp/**",
"instances/**",
"public/uploads/**",
"*.dump",
"*.rdb",
"*.mmdb",
".DS_Store",
"*.swp",
"*.swo",
"*.un~",
".jest-cache",
".idea/**",
".vscode/**",
".yalc/**",
"yalc.lock",
"*.local",
"prisma/migrations/**",
"prisma/mysql-migrations/**",
"prisma/postgresql-migrations/**"
]
},
"search": {
"exclude_patterns": [
"**/node_modules/**",
"**/dist/**",
"**/build/**",
"**/.git/**",
"**/coverage/**",
"**/log/**",
"**/tmp/**",
"**/instances/**",
"**/public/uploads/**",
"**/*.min.js",
"**/*.min.css",
"**/*.log",
"**/*.lock",
"**/pnpm-lock.yaml",
"**/package-lock.json",
"**/yarn.lock",
"**/*.dump",
"**/*.rdb",
"**/*.mmdb",
"**/.DS_Store",
"**/*.swp",
"**/*.swo",
"**/*.un~",
"**/.jest-cache",
"**/.idea/**",
"**/.vscode/**",
"**/.yalc/**",
"**/yalc.lock",
"**/*.local",
"**/prisma/migrations/**",
"**/prisma/mysql-migrations/**",
"**/prisma/postgresql-migrations/**"
]
},
"evolution_api": {
"project_type": "nodejs_typescript_api",
"backend_framework": "express_prisma",
"database": ["postgresql", "mysql"],
"cache": ["redis", "node_cache"],
"queue": ["rabbitmq", "sqs"],
"real_time": "socket_io",
"file_storage": ["aws_s3", "minio"],
"validation": "class_validator",
"logging": "pino",
"main_directories": {
"source": "src/",
"api": "src/api/",
"controllers": "src/api/controllers/",
"services": "src/api/services/",
"integrations": "src/api/integrations/",
"dto": "src/api/dto/",
"types": "src/api/types/",
"guards": "src/api/guards/",
"routes": "src/api/routes/",
"cache": "src/cache/",
"config": "src/config/",
"utils": "src/utils/",
"exceptions": "src/exceptions/",
"validate": "src/validate/",
"prisma": "prisma/",
"tests": "test/",
"docs": "docs/"
},
"key_patterns": [
"whatsapp_integration",
"multi_database_support",
"instance_management",
"event_driven_architecture",
"service_layer_pattern",
"dto_validation",
"webhook_processing",
"message_queuing",
"real_time_communication",
"file_storage_integration"
],
"whatsapp_integrations": [
"baileys",
"meta_business_api",
"whatsapp_cloud_api"
],
"external_integrations": [
"chatwoot",
"typebot",
"openai",
"dify",
"rabbitmq",
"sqs",
"s3",
"minio"
]
}
}
}

View File

@@ -0,0 +1,174 @@
---
description: Evolution API project-specific context and constraints
globs:
alwaysApply: true
---
# Evolution API Project Context
## Cross-References
- **Core Development**: @core-development.mdc for fundamental development principles
- **Specialized Rules**: Reference specific specialized rules when working on:
- Services: @specialized-rules/service-rules.mdc
- Controllers: @specialized-rules/controller-rules.mdc
- DTOs: @specialized-rules/dto-rules.mdc
- Guards: @specialized-rules/guard-rules.mdc
- Routes: @specialized-rules/route-rules.mdc
- Types: @specialized-rules/type-rules.mdc
- Utils: @specialized-rules/util-rules.mdc
- Validation: @specialized-rules/validate-rules.mdc
- Channel Integrations: @specialized-rules/integration-channel-rules.mdc
- Chatbot Integrations: @specialized-rules/integration-chatbot-rules.mdc
- Storage Integrations: @specialized-rules/integration-storage-rules.mdc
- Event Integrations: @specialized-rules/integration-event-rules.mdc
- **TypeScript/Node.js**: Node.js 20+ + TypeScript 5+ backend standards
- **Express/Prisma**: Express.js + Prisma ORM patterns
- **WhatsApp Integrations**: Baileys, Meta Business API, and other messaging platforms
## Technology Stack
- **Backend**: Node.js 20+ + TypeScript 5+ + Express.js
- **Database**: Prisma ORM (PostgreSQL/MySQL support)
- **Cache**: Redis + Node-cache for local fallback
- **Queue**: RabbitMQ + Amazon SQS for message processing
- **Real-time**: Socket.io for WebSocket connections
- **Storage**: AWS S3 + Minio for file storage
- **Validation**: JSONSchema7 for input validation
- **Logging**: Pino for structured logging
- **Architecture**: Multi-tenant API with WhatsApp integrations
## Project-Specific Patterns
### WhatsApp Integration Architecture
- **MANDATORY**: All WhatsApp integrations must follow established patterns
- **BAILEYS**: Use `whatsapp.baileys.service.ts` patterns for WhatsApp Web
- **META BUSINESS**: Use `whatsapp.business.service.ts` for official API
- **CONNECTION MANAGEMENT**: One connection per instance with proper lifecycle
- **EVENT HANDLING**: Proper event listeners and error handling
### Multi-Database Architecture
- **CRITICAL**: Support both PostgreSQL and MySQL
- **SCHEMAS**: Use appropriate schema files (postgresql-schema.prisma / mysql-schema.prisma)
- **MIGRATIONS**: Keep migrations synchronized between databases
- **TYPES**: Use database-specific types (@db.JsonB vs @db.Json)
- **COMPATIBILITY**: Ensure feature parity between databases
### API Integration Workflow
- **CORE FEATURE**: REST API for WhatsApp communication
- **COMPLEXITY**: High - involves webhook processing, message routing, and instance management
- **COMPONENTS**: Instance management, message handling, media processing
- **INTEGRATIONS**: Baileys, Meta Business API, Chatwoot, Typebot, OpenAI, Dify
### Multi-Tenant Instance Architecture
- **CRITICAL**: All operations must be scoped by instance
- **ISOLATION**: Complete data isolation between instances
- **SECURITY**: Validate instance ownership before operations
- **SCALING**: Support thousands of concurrent instances
- **AUTHENTICATION**: API key-based authentication per instance
## Documentation Requirements
### Implementation Documentation
- **MANDATORY**: Document complex integration patterns
- **LOCATION**: Use inline comments for business logic
- **API DOCS**: Document all public endpoints
- **WEBHOOK DOCS**: Document webhook payloads and signatures
### Change Documentation
- **CHANGELOG**: Document breaking changes
- **MIGRATION GUIDES**: Document database migrations
- **INTEGRATION GUIDES**: Document new integration patterns
## Environment and Security
### Environment Variables
- **CRITICAL**: Never hardcode sensitive values
- **VALIDATION**: Validate required environment variables on startup
- **SECURITY**: Use secure defaults and proper encryption
- **DOCUMENTATION**: Document all environment variables
### File Organization - Node.js/TypeScript Structure
- **CONTROLLERS**: Organized by feature (`api/controllers/`)
- **SERVICES**: Business logic in service classes (`api/services/`)
- **INTEGRATIONS**: External integrations (`api/integrations/`)
- **DTOS**: Data transfer objects (`api/dto/`)
- **TYPES**: TypeScript types (`api/types/`)
- **UTILS**: Utility functions (`utils/`)
## Integration Points
### WhatsApp Providers
- **BAILEYS**: WhatsApp Web integration with QR code
- **META BUSINESS**: Official WhatsApp Business API
- **CLOUD API**: WhatsApp Cloud API integration
- **WEBHOOK PROCESSING**: Proper webhook validation and processing
### External Integrations
- **CHATWOOT**: Customer support platform integration
- **TYPEBOT**: Chatbot flow integration
- **OPENAI**: AI-powered chat integration
- **DIFY**: AI workflow integration
- **STORAGE**: S3/Minio for media file storage
### Event-Driven Communication
- **EVENTEMITTER2**: Internal event system
- **SOCKET.IO**: Real-time WebSocket communication
- **RABBITMQ**: Message queue for async processing
- **SQS**: Amazon SQS for cloud-based queuing
- **WEBHOOKS**: Outbound webhook system
## Development Constraints
### Language Requirements
- **USER COMMUNICATION**: Always respond in Portuguese (PT-BR)
- **CODE/COMMENTS**: English for code and technical documentation
- **API RESPONSES**: English for consistency
- **ERROR MESSAGES**: Portuguese for user-facing errors
### Performance Constraints
- **MEMORY**: Efficient memory usage for multiple instances
- **DATABASE**: Optimized queries with proper indexing
- **CACHE**: Strategic caching for frequently accessed data
- **CONNECTIONS**: Proper connection pooling and management
### Security Constraints
- **AUTHENTICATION**: API key validation for all endpoints
- **AUTHORIZATION**: Instance-based access control
- **INPUT VALIDATION**: Validate all inputs with JSONSchema7
- **RATE LIMITING**: Prevent abuse with rate limiting
- **WEBHOOK SECURITY**: Validate webhook signatures
## Quality Standards
- **TYPE SAFETY**: Full TypeScript coverage with strict mode
- **ERROR HANDLING**: Comprehensive error scenarios with proper logging
- **TESTING**: Unit and integration tests for critical paths
- **MONITORING**: Proper logging and error tracking
- **DOCUMENTATION**: Clear API documentation and code comments
- **PERFORMANCE**: Optimized for high-throughput message processing
- **SECURITY**: Secure by default with proper validation
- **SCALABILITY**: Design for horizontal scaling
## Evolution API Specific Development Patterns
### Instance Management
- **LIFECYCLE**: Proper instance creation, connection, and cleanup
- **STATE MANAGEMENT**: Track connection status and health
- **RECOVERY**: Automatic reconnection and error recovery
- **MONITORING**: Health checks and status reporting
### Message Processing
- **QUEUE-BASED**: Use queues for message processing
- **RETRY LOGIC**: Implement exponential backoff for failures
- **MEDIA HANDLING**: Proper media upload and processing
- **WEBHOOK DELIVERY**: Reliable webhook delivery with retries
### Integration Patterns
- **SERVICE LAYER**: Business logic in service classes
- **DTO VALIDATION**: Input validation with JSONSchema7
- **ERROR HANDLING**: Consistent error responses
- **LOGGING**: Structured logging with correlation IDs
### Database Patterns
- **PRISMA**: Use Prisma ORM for all database operations
- **TRANSACTIONS**: Use transactions for multi-step operations
- **MIGRATIONS**: Proper migration management
- **INDEXING**: Optimize queries with appropriate indexes

View File

@@ -0,0 +1,342 @@
---
description: Controller patterns for Evolution API
globs:
- "src/api/controllers/**/*.ts"
- "src/api/integrations/**/controllers/*.ts"
alwaysApply: false
---
# Evolution API Controller Rules
## Controller Structure Pattern
### Standard Controller Class
```typescript
export class ExampleController {
constructor(private readonly exampleService: ExampleService) {}
public async createExample(instance: InstanceDto, data: ExampleDto) {
return this.exampleService.create(instance, data);
}
public async findExample(instance: InstanceDto) {
return this.exampleService.find(instance);
}
}
```
## Dependency Injection Pattern
### Service Injection
```typescript
// CORRECT - Evolution API pattern
export class ChatController {
constructor(private readonly waMonitor: WAMonitoringService) {}
public async whatsappNumber({ instanceName }: InstanceDto, data: WhatsAppNumberDto) {
return await this.waMonitor.waInstances[instanceName].getWhatsAppNumbers(data);
}
}
// INCORRECT - Don't inject multiple services when waMonitor is sufficient
export class ChatController {
constructor(
private readonly waMonitor: WAMonitoringService,
private readonly prismaRepository: PrismaRepository, // ❌ Unnecessary if waMonitor has access
private readonly configService: ConfigService, // ❌ Unnecessary if waMonitor has access
) {}
}
```
## Method Signature Pattern
### Instance Parameter Pattern
```typescript
// CORRECT - Evolution API pattern (destructuring instanceName)
public async fetchCatalog({ instanceName }: InstanceDto, data: getCatalogDto) {
return await this.waMonitor.waInstances[instanceName].fetchCatalog(instanceName, data);
}
// CORRECT - Alternative pattern for full instance (when using services)
public async createTemplate(instance: InstanceDto, data: TemplateDto) {
return this.templateService.create(instance, data);
}
// INCORRECT - Don't use generic method names
public async methodName(instance: InstanceDto, data: DataDto) { // ❌ Use specific names
return this.service.performAction(instance, data);
}
```
## WAMonitor Access Pattern
### Direct WAMonitor Usage
```typescript
// CORRECT - Standard pattern in controllers
export class CallController {
constructor(private readonly waMonitor: WAMonitoringService) {}
public async offerCall({ instanceName }: InstanceDto, data: OfferCallDto) {
return await this.waMonitor.waInstances[instanceName].offerCall(data);
}
}
```
## Controller Registration Pattern
### Server Module Registration
```typescript
// In server.module.ts
export const templateController = new TemplateController(templateService);
export const businessController = new BusinessController(waMonitor);
export const chatController = new ChatController(waMonitor);
export const callController = new CallController(waMonitor);
```
## Error Handling in Controllers
### Let Services Handle Errors
```typescript
// CORRECT - Let service handle errors
public async fetchCatalog(instance: InstanceDto, data: getCatalogDto) {
return await this.waMonitor.waInstances[instance.instanceName].fetchCatalog(instance.instanceName, data);
}
// INCORRECT - Don't add try-catch in controllers unless specific handling needed
public async fetchCatalog(instance: InstanceDto, data: getCatalogDto) {
try {
return await this.waMonitor.waInstances[instance.instanceName].fetchCatalog(instance.instanceName, data);
} catch (error) {
throw error; // ❌ Unnecessary try-catch
}
}
```
## Complex Controller Pattern
### Instance Controller Pattern
```typescript
export class InstanceController {
constructor(
private readonly waMonitor: WAMonitoringService,
private readonly configService: ConfigService,
private readonly prismaRepository: PrismaRepository,
private readonly eventEmitter: EventEmitter2,
private readonly chatwootService: ChatwootService,
private readonly settingsService: SettingsService,
private readonly proxyService: ProxyController,
private readonly cache: CacheService,
private readonly chatwootCache: CacheService,
private readonly baileysCache: CacheService,
private readonly providerFiles: ProviderFiles,
) {}
private readonly logger = new Logger('InstanceController');
// Multiple methods handling different aspects
public async createInstance(data: InstanceDto) {
// Complex instance creation logic
}
public async deleteInstance({ instanceName }: InstanceDto) {
// Complex instance deletion logic
}
}
```
## Channel Controller Pattern
### Base Channel Controller
```typescript
export class ChannelController {
public prismaRepository: PrismaRepository;
public waMonitor: WAMonitoringService;
constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {
this.prisma = prismaRepository;
this.monitor = waMonitor;
}
// Getters and setters for dependency access
public set prisma(prisma: PrismaRepository) {
this.prismaRepository = prisma;
}
public get prisma() {
return this.prismaRepository;
}
public set monitor(waMonitor: WAMonitoringService) {
this.waMonitor = waMonitor;
}
public get monitor() {
return this.waMonitor;
}
}
```
### Extended Channel Controller
```typescript
export class EvolutionController extends ChannelController implements ChannelControllerInterface {
private readonly logger = new Logger('EvolutionController');
constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {
super(prismaRepository, waMonitor);
}
integrationEnabled: boolean;
public async receiveWebhook(data: any) {
const numberId = data.numberId;
if (!numberId) {
this.logger.error('WebhookService -> receiveWebhookEvolution -> numberId not found');
return;
}
const instance = await this.prismaRepository.instance.findFirst({
where: { number: numberId },
});
if (!instance) {
this.logger.error('WebhookService -> receiveWebhook -> instance not found');
return;
}
await this.waMonitor.waInstances[instance.name].connectToWhatsapp(data);
return {
status: 'success',
};
}
}
```
## Chatbot Controller Pattern
### Base Chatbot Controller
```typescript
export abstract class BaseChatbotController<BotType = any, BotData extends BaseChatbotDto = BaseChatbotDto>
extends ChatbotController
implements ChatbotControllerInterface
{
public readonly logger: Logger;
integrationEnabled: boolean;
// Abstract methods to be implemented
protected abstract readonly integrationName: string;
protected abstract processBot(/* parameters */): Promise<void>;
protected abstract getFallbackBotId(settings: any): string | undefined;
constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {
super(prismaRepository, waMonitor);
}
// Base implementation methods
public async createBot(instance: InstanceDto, data: BotData) {
// Common bot creation logic
}
}
```
## Method Naming Conventions
### Standard Method Names
- `create*()` - Create operations
- `find*()` - Find operations
- `fetch*()` - Fetch from external APIs
- `send*()` - Send operations
- `receive*()` - Receive webhook/data
- `handle*()` - Handle specific actions
- `offer*()` - Offer services (like calls)
## Return Patterns
### Direct Return Pattern
```typescript
// CORRECT - Direct return from service
public async createTemplate(instance: InstanceDto, data: TemplateDto) {
return this.templateService.create(instance, data);
}
// CORRECT - Direct return from waMonitor
public async offerCall({ instanceName }: InstanceDto, data: OfferCallDto) {
return await this.waMonitor.waInstances[instanceName].offerCall(data);
}
```
## Controller Testing Pattern
### Unit Test Structure
```typescript
describe('ExampleController', () => {
let controller: ExampleController;
let service: jest.Mocked<ExampleService>;
beforeEach(() => {
const mockService = {
create: jest.fn(),
find: jest.fn(),
};
controller = new ExampleController(mockService as any);
service = mockService as any;
});
describe('createExample', () => {
it('should call service create method', async () => {
const instance = { instanceName: 'test' };
const data = { test: 'data' };
const expectedResult = { success: true };
service.create.mockResolvedValue(expectedResult);
const result = await controller.createExample(instance, data);
expect(service.create).toHaveBeenCalledWith(instance, data);
expect(result).toEqual(expectedResult);
});
});
});
```
## Interface Implementation
### Controller Interface Pattern
```typescript
export interface ChannelControllerInterface {
integrationEnabled: boolean;
}
export interface ChatbotControllerInterface {
integrationEnabled: boolean;
createBot(instance: InstanceDto, data: any): Promise<any>;
findBot(instance: InstanceDto): Promise<any>;
// ... other methods
}
```
## Controller Organization
### File Naming Convention
- `*.controller.ts` - Main controllers
- `*/*.controller.ts` - Integration-specific controllers
### Method Organization
1. Constructor
2. Public methods (alphabetical order)
3. Private methods (if any)
### Import Organization
```typescript
// DTOs first
import { InstanceDto } from '@api/dto/instance.dto';
import { ExampleDto } from '@api/dto/example.dto';
// Services
import { ExampleService } from '@api/services/example.service';
// Types
import { WAMonitoringService } from '@api/services/monitor.service';
```

View File

@@ -0,0 +1,433 @@
---
description: DTO patterns and validation for Evolution API
globs:
- "src/api/dto/**/*.ts"
- "src/api/integrations/**/dto/*.ts"
alwaysApply: false
---
# Evolution API DTO Rules
## DTO Structure Pattern
### Basic DTO Class
```typescript
export class ExampleDto {
name: string;
category: string;
allowCategoryChange: boolean;
language: string;
components: any;
webhookUrl?: string;
}
```
## Inheritance Pattern
### DTO Inheritance
```typescript
// CORRECT - Evolution API pattern
export class InstanceDto extends IntegrationDto {
instanceName: string;
instanceId?: string;
qrcode?: boolean;
businessId?: string;
number?: string;
integration?: string;
token?: string;
status?: string;
ownerJid?: string;
profileName?: string;
profilePicUrl?: string;
// Settings
rejectCall?: boolean;
msgCall?: string;
groupsIgnore?: boolean;
alwaysOnline?: boolean;
readMessages?: boolean;
readStatus?: boolean;
syncFullHistory?: boolean;
wavoipToken?: string;
// Proxy settings
proxyHost?: string;
proxyPort?: string;
proxyProtocol?: string;
proxyUsername?: string;
proxyPassword?: string;
// Webhook configuration
webhook?: {
enabled?: boolean;
events?: string[];
headers?: JsonValue;
url?: string;
byEvents?: boolean;
base64?: boolean;
};
// Chatwoot integration
chatwootAccountId?: string;
chatwootConversationPending?: boolean;
chatwootAutoCreate?: boolean;
chatwootDaysLimitImportMessages?: number;
chatwootImportContacts?: boolean;
chatwootImportMessages?: boolean;
chatwootLogo?: string;
chatwootMergeBrazilContacts?: boolean;
chatwootNameInbox?: string;
chatwootOrganization?: string;
chatwootReopenConversation?: boolean;
chatwootSignMsg?: boolean;
chatwootToken?: string;
chatwootUrl?: string;
}
```
## Base DTO Pattern
### Base Chatbot DTO
```typescript
/**
* Base DTO for all chatbot integrations
* Contains common properties shared by all chatbot types
*/
export class BaseChatbotDto {
enabled?: boolean;
description: string;
expire?: number;
keywordFinish?: string;
delayMessage?: number;
unknownMessage?: string;
listeningFromMe?: boolean;
stopBotFromMe?: boolean;
keepOpen?: boolean;
debounceTime?: number;
triggerType: TriggerType;
triggerOperator?: TriggerOperator;
triggerValue?: string;
ignoreJids?: string[];
splitMessages?: boolean;
timePerChar?: number;
}
/**
* Base settings DTO for all chatbot integrations
*/
export class BaseChatbotSettingDto {
expire?: number;
keywordFinish?: string;
delayMessage?: number;
unknownMessage?: string;
listeningFromMe?: boolean;
stopBotFromMe?: boolean;
keepOpen?: boolean;
debounceTime?: number;
ignoreJids?: string[];
splitMessages?: boolean;
timePerChar?: number;
}
```
## Message DTO Patterns
### Send Message DTOs
```typescript
export class Metadata {
number: string;
delay?: number;
}
export class SendTextDto extends Metadata {
text: string;
linkPreview?: boolean;
mentionsEveryOne?: boolean;
mentioned?: string[];
}
export class SendListDto extends Metadata {
title: string;
description: string;
buttonText: string;
footerText?: string;
sections: Section[];
}
export class ContactMessage {
fullName: string;
wuid: string;
phoneNumber: string;
organization?: string;
email?: string;
url?: string;
}
export class SendTemplateDto extends Metadata {
name: string;
language: string;
components: any;
}
```
## Simple DTO Patterns
### Basic DTOs
```typescript
export class NumberDto {
number: string;
}
export class LabelDto {
id?: string;
name: string;
color: string;
predefinedId?: string;
}
export class HandleLabelDto {
number: string;
labelId: string;
}
export class ProfileNameDto {
name: string;
}
export class WhatsAppNumberDto {
numbers: string[];
}
```
## Complex DTO Patterns
### Business DTOs
```typescript
export class getCatalogDto {
number?: string;
limit?: number;
cursor?: string;
}
export class getCollectionsDto {
number?: string;
limit?: number;
cursor?: string;
}
export class NumberBusiness {
number: string;
name?: string;
description?: string;
email?: string;
websites?: string[];
latitude?: number;
longitude?: number;
address?: string;
profilehandle?: string;
}
```
## Settings DTO Pattern
### Settings Configuration
```typescript
export class SettingsDto {
rejectCall?: boolean;
msgCall?: string;
groupsIgnore?: boolean;
alwaysOnline?: boolean;
readMessages?: boolean;
readStatus?: boolean;
syncFullHistory?: boolean;
wavoipToken?: string;
}
export class ProxyDto {
host?: string;
port?: string;
protocol?: string;
username?: string;
password?: string;
}
```
## Presence DTO Pattern
### WhatsApp Presence
```typescript
export class SetPresenceDto {
presence: WAPresence;
}
export class SendPresenceDto {
number: string;
presence: WAPresence;
}
```
## DTO Structure (No Decorators)
### Simple DTO Classes (Evolution API Pattern)
```typescript
// CORRECT - Evolution API pattern (no decorators)
export class ExampleDto {
name: string;
description?: string;
enabled: boolean;
items?: string[];
timeout?: number;
}
// INCORRECT - Don't use class-validator decorators
export class ValidatedDto {
@IsString() // ❌ Evolution API doesn't use decorators
name: string;
}
```
## Type Safety Patterns
### Prisma Type Integration
```typescript
import { JsonValue } from '@prisma/client/runtime/library';
import { WAPresence } from 'baileys';
import { TriggerOperator, TriggerType } from '@prisma/client';
export class TypeSafeDto {
presence: WAPresence;
triggerType: TriggerType;
triggerOperator?: TriggerOperator;
metadata?: JsonValue;
}
```
## DTO Documentation
### JSDoc Comments
```typescript
/**
* DTO for creating WhatsApp templates
* Used by Meta Business API integration
*/
export class TemplateDto {
/** Template name - must be unique */
name: string;
/** Template category (MARKETING, UTILITY, AUTHENTICATION) */
category: string;
/** Whether category can be changed after creation */
allowCategoryChange: boolean;
/** Language code (e.g., 'pt_BR', 'en_US') */
language: string;
/** Template components (header, body, footer, buttons) */
components: any;
/** Optional webhook URL for template status updates */
webhookUrl?: string;
}
```
## DTO Naming Conventions
### Standard Naming Patterns
- `*Dto` - Data transfer objects
- `Create*Dto` - Creation DTOs
- `Update*Dto` - Update DTOs
- `Send*Dto` - Message sending DTOs
- `Get*Dto` - Query DTOs
- `Handle*Dto` - Action DTOs
## File Organization
### DTO File Structure
```
src/api/dto/
├── instance.dto.ts # Main instance DTO
├── template.dto.ts # Template management
├── sendMessage.dto.ts # Message sending DTOs
├── chat.dto.ts # Chat operations
├── business.dto.ts # Business API DTOs
├── group.dto.ts # Group management
├── label.dto.ts # Label management
├── proxy.dto.ts # Proxy configuration
├── settings.dto.ts # Instance settings
└── call.dto.ts # Call operations
```
## Integration DTO Patterns
### Chatbot Integration DTOs
```typescript
// Base for all chatbot DTOs
export class BaseChatbotDto {
enabled?: boolean;
description: string;
// ... common properties
}
// Specific chatbot DTOs extend base
export class TypebotDto extends BaseChatbotDto {
url: string;
typebot: string;
// ... typebot-specific properties
}
export class OpenaiDto extends BaseChatbotDto {
apiKey: string;
model: string;
// ... openai-specific properties
}
```
## DTO Testing Pattern
### DTO Validation Tests
```typescript
describe('ExampleDto', () => {
it('should validate required fields', () => {
const dto = new ExampleDto();
dto.name = 'test';
dto.category = 'MARKETING';
dto.allowCategoryChange = true;
dto.language = 'pt_BR';
dto.components = {};
expect(dto.name).toBe('test');
expect(dto.category).toBe('MARKETING');
});
it('should handle optional fields', () => {
const dto = new ExampleDto();
dto.name = 'test';
dto.category = 'MARKETING';
dto.allowCategoryChange = true;
dto.language = 'pt_BR';
dto.components = {};
dto.webhookUrl = 'https://example.com/webhook';
expect(dto.webhookUrl).toBe('https://example.com/webhook');
});
});
```
## DTO Transformation
### Request to DTO Mapping (Evolution API Pattern)
```typescript
// CORRECT - Evolution API uses RouterBroker dataValidate
const response = await this.dataValidate<ExampleDto>({
request: req,
schema: exampleSchema, // JSONSchema7
ClassRef: ExampleDto,
execute: (instance, data) => controller.method(instance, data),
});
// INCORRECT - Don't use class-validator
const dto = plainToClass(ExampleDto, req.body); // ❌ Not used in Evolution API
const errors = await validate(dto); // ❌ Not used in Evolution API
```

View File

@@ -0,0 +1,416 @@
---
description: Guard patterns for authentication and authorization in Evolution API
globs:
- "src/api/guards/**/*.ts"
alwaysApply: false
---
# Evolution API Guard Rules
## Guard Structure Pattern
### Standard Guard Function
```typescript
import { NextFunction, Request, Response } from 'express';
import { Logger } from '@config/logger.config';
import { UnauthorizedException, ForbiddenException } from '@exceptions';
const logger = new Logger('GUARD');
async function guardFunction(req: Request, _: Response, next: NextFunction) {
// Guard logic here
if (validationFails) {
throw new UnauthorizedException();
}
return next();
}
export const guardName = { guardFunction };
```
## Authentication Guard Pattern
### API Key Authentication
```typescript
async function apikey(req: Request, _: Response, next: NextFunction) {
const env = configService.get<Auth>('AUTHENTICATION').API_KEY;
const key = req.get('apikey');
const db = configService.get<Database>('DATABASE');
if (!key) {
throw new UnauthorizedException();
}
// Global API key check
if (env.KEY === key) {
return next();
}
// Special routes handling
if ((req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) && !key) {
throw new ForbiddenException('Missing global api key', 'The global api key must be set');
}
const param = req.params as unknown as InstanceDto;
try {
if (param?.instanceName) {
const instance = await prismaRepository.instance.findUnique({
where: { name: param.instanceName },
});
if (instance.token === key) {
return next();
}
} else {
if (req.originalUrl.includes('/instance/fetchInstances') && db.SAVE_DATA.INSTANCE) {
const instanceByKey = await prismaRepository.instance.findFirst({
where: { token: key },
});
if (instanceByKey) {
return next();
}
}
}
} catch (error) {
logger.error(error);
}
throw new UnauthorizedException();
}
export const authGuard = { apikey };
```
## Instance Validation Guards
### Instance Exists Guard
```typescript
async function getInstance(instanceName: string) {
try {
const cacheConf = configService.get<CacheConf>('CACHE');
const exists = !!waMonitor.waInstances[instanceName];
if (cacheConf.REDIS.ENABLED && cacheConf.REDIS.SAVE_INSTANCES) {
const keyExists = await cache.has(instanceName);
return exists || keyExists;
}
return exists || (await prismaRepository.instance.findMany({ where: { name: instanceName } })).length > 0;
} catch (error) {
throw new InternalServerErrorException(error?.toString());
}
}
export async function instanceExistsGuard(req: Request, _: Response, next: NextFunction) {
if (req.originalUrl.includes('/instance/create')) {
return next();
}
const param = req.params as unknown as InstanceDto;
if (!param?.instanceName) {
throw new BadRequestException('"instanceName" not provided.');
}
if (!(await getInstance(param.instanceName))) {
throw new NotFoundException(`The "${param.instanceName}" instance does not exist`);
}
next();
}
```
### Instance Logged Guard
```typescript
export async function instanceLoggedGuard(req: Request, _: Response, next: NextFunction) {
if (req.originalUrl.includes('/instance/create')) {
const instance = req.body as InstanceDto;
if (await getInstance(instance.instanceName)) {
throw new ForbiddenException(`This name "${instance.instanceName}" is already in use.`);
}
if (waMonitor.waInstances[instance.instanceName]) {
delete waMonitor.waInstances[instance.instanceName];
}
}
next();
}
```
## Telemetry Guard Pattern
### Telemetry Collection
```typescript
class Telemetry {
public collectTelemetry(req: Request, res: Response, next: NextFunction): void {
// Collect telemetry data
const telemetryData = {
route: req.originalUrl,
method: req.method,
timestamp: new Date(),
userAgent: req.get('User-Agent'),
};
// Send telemetry asynchronously (don't block request)
setImmediate(() => {
this.sendTelemetry(telemetryData);
});
next();
}
private async sendTelemetry(data: any): Promise<void> {
try {
// Send telemetry data
} catch (error) {
// Silently fail - don't affect main request
}
}
}
export default Telemetry;
```
## Guard Composition Pattern
### Multiple Guards Usage
```typescript
// In router setup
const guards = [instanceExistsGuard, instanceLoggedGuard, authGuard['apikey']];
router
.use('/instance', new InstanceRouter(configService, ...guards).router)
.use('/message', new MessageRouter(...guards).router)
.use('/chat', new ChatRouter(...guards).router);
```
## Error Handling in Guards
### Proper Exception Throwing
```typescript
// CORRECT - Use proper HTTP exceptions
if (!apiKey) {
throw new UnauthorizedException('API key required');
}
if (instanceExists) {
throw new ForbiddenException('Instance already exists');
}
if (!instanceFound) {
throw new NotFoundException('Instance not found');
}
if (validationFails) {
throw new BadRequestException('Invalid request parameters');
}
// INCORRECT - Don't use generic Error
if (!apiKey) {
throw new Error('API key required'); // ❌ Use specific exceptions
}
```
## Configuration Access in Guards
### Config Service Usage
```typescript
async function configAwareGuard(req: Request, _: Response, next: NextFunction) {
const authConfig = configService.get<Auth>('AUTHENTICATION');
const cacheConfig = configService.get<CacheConf>('CACHE');
const dbConfig = configService.get<Database>('DATABASE');
// Use configuration for guard logic
if (authConfig.API_KEY.KEY === providedKey) {
return next();
}
throw new UnauthorizedException();
}
```
## Database Access in Guards
### Prisma Repository Usage
```typescript
async function databaseGuard(req: Request, _: Response, next: NextFunction) {
try {
const param = req.params as unknown as InstanceDto;
const instance = await prismaRepository.instance.findUnique({
where: { name: param.instanceName },
});
if (!instance) {
throw new NotFoundException('Instance not found');
}
// Additional validation logic
if (instance.status !== 'active') {
throw new ForbiddenException('Instance not active');
}
return next();
} catch (error) {
logger.error('Database guard error:', error);
throw new InternalServerErrorException('Database access failed');
}
}
```
## Cache Integration in Guards
### Cache Service Usage
```typescript
async function cacheAwareGuard(req: Request, _: Response, next: NextFunction) {
const cacheConf = configService.get<CacheConf>('CACHE');
if (cacheConf.REDIS.ENABLED) {
const cached = await cache.get(`guard:${req.params.instanceName}`);
if (cached) {
// Use cached validation result
return next();
}
}
// Perform validation and cache result
const isValid = await performValidation(req.params.instanceName);
if (cacheConf.REDIS.ENABLED) {
await cache.set(`guard:${req.params.instanceName}`, isValid, 300); // 5 min TTL
}
if (isValid) {
return next();
}
throw new UnauthorizedException();
}
```
## Logging in Guards
### Structured Logging
```typescript
const logger = new Logger('GUARD');
async function loggedGuard(req: Request, _: Response, next: NextFunction) {
logger.log(`Guard validation started for ${req.originalUrl}`);
try {
// Guard logic
const isValid = await validateRequest(req);
if (isValid) {
logger.log(`Guard validation successful for ${req.params.instanceName}`);
return next();
}
logger.warn(`Guard validation failed for ${req.params.instanceName}`);
throw new UnauthorizedException();
} catch (error) {
logger.error(`Guard validation error: ${error.message}`, error.stack);
throw error;
}
}
```
## Guard Testing Pattern
### Unit Test Structure
```typescript
describe('authGuard', () => {
let req: Partial<Request>;
let res: Partial<Response>;
let next: NextFunction;
beforeEach(() => {
req = {
get: jest.fn(),
params: {},
originalUrl: '/test',
};
res = {};
next = jest.fn();
});
describe('apikey', () => {
it('should pass with valid global API key', async () => {
(req.get as jest.Mock).mockReturnValue('valid-global-key');
await authGuard.apikey(req as Request, res as Response, next);
expect(next).toHaveBeenCalled();
});
it('should throw UnauthorizedException with no API key', async () => {
(req.get as jest.Mock).mockReturnValue(undefined);
await expect(
authGuard.apikey(req as Request, res as Response, next)
).rejects.toThrow(UnauthorizedException);
});
it('should pass with valid instance token', async () => {
(req.get as jest.Mock).mockReturnValue('instance-token');
req.params = { instanceName: 'test-instance' };
// Mock prisma repository
jest.spyOn(prismaRepository.instance, 'findUnique').mockResolvedValue({
token: 'instance-token',
} as any);
await authGuard.apikey(req as Request, res as Response, next);
expect(next).toHaveBeenCalled();
});
});
});
```
## Guard Performance Considerations
### Efficient Validation
```typescript
// CORRECT - Efficient guard with early returns
async function efficientGuard(req: Request, _: Response, next: NextFunction) {
// Quick checks first
if (req.originalUrl.includes('/public')) {
return next(); // Skip validation for public routes
}
const apiKey = req.get('apikey');
if (!apiKey) {
throw new UnauthorizedException(); // Fail fast
}
// More expensive checks only if needed
if (apiKey === globalKey) {
return next(); // Skip database check
}
// Database check only as last resort
const isValid = await validateInDatabase(apiKey);
if (isValid) {
return next();
}
throw new UnauthorizedException();
}
// INCORRECT - Inefficient guard
async function inefficientGuard(req: Request, _: Response, next: NextFunction) {
// Always do expensive database check first
const dbResult = await expensiveDatabaseQuery(); // ❌ Expensive operation first
const apiKey = req.get('apikey');
if (!apiKey && dbResult) { // ❌ Complex logic
throw new UnauthorizedException();
}
next();
}
```

View File

@@ -0,0 +1,552 @@
---
description: Channel integration patterns for Evolution API
globs:
- "src/api/integrations/channel/**/*.ts"
alwaysApply: false
---
# Evolution API Channel Integration Rules
## Channel Controller Pattern
### Base Channel Controller
```typescript
export interface ChannelControllerInterface {
integrationEnabled: boolean;
}
export class ChannelController {
public prismaRepository: PrismaRepository;
public waMonitor: WAMonitoringService;
constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {
this.prisma = prismaRepository;
this.monitor = waMonitor;
}
public set prisma(prisma: PrismaRepository) {
this.prismaRepository = prisma;
}
public get prisma() {
return this.prismaRepository;
}
public set monitor(waMonitor: WAMonitoringService) {
this.waMonitor = waMonitor;
}
public get monitor() {
return this.waMonitor;
}
public init(instanceData: InstanceDto, data: ChannelDataType) {
if (!instanceData.token && instanceData.integration === Integration.WHATSAPP_BUSINESS) {
throw new BadRequestException('token is required');
}
if (instanceData.integration === Integration.WHATSAPP_BUSINESS) {
return new BusinessStartupService(/* dependencies */);
}
if (instanceData.integration === Integration.EVOLUTION) {
return new EvolutionStartupService(/* dependencies */);
}
if (instanceData.integration === Integration.WHATSAPP_BAILEYS) {
return new BaileysStartupService(/* dependencies */);
}
return null;
}
}
```
### Extended Channel Controller
```typescript
export class EvolutionController extends ChannelController implements ChannelControllerInterface {
private readonly logger = new Logger('EvolutionController');
constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {
super(prismaRepository, waMonitor);
}
integrationEnabled: boolean;
public async receiveWebhook(data: any) {
const numberId = data.numberId;
if (!numberId) {
this.logger.error('WebhookService -> receiveWebhookEvolution -> numberId not found');
return;
}
const instance = await this.prismaRepository.instance.findFirst({
where: { number: numberId },
});
if (!instance) {
this.logger.error('WebhookService -> receiveWebhook -> instance not found');
return;
}
await this.waMonitor.waInstances[instance.name].connectToWhatsapp(data);
return {
status: 'success',
};
}
}
```
## Channel Service Pattern
### Base Channel Service
```typescript
export class ChannelStartupService {
constructor(
private readonly configService: ConfigService,
private readonly eventEmitter: EventEmitter2,
private readonly prismaRepository: PrismaRepository,
public readonly cache: CacheService,
public readonly chatwootCache: CacheService,
) {}
public readonly logger = new Logger('ChannelStartupService');
public client: WASocket;
public readonly instance: wa.Instance = {};
public readonly localChatwoot: wa.LocalChatwoot = {};
public readonly localProxy: wa.LocalProxy = {};
public readonly localSettings: wa.LocalSettings = {};
public readonly localWebhook: wa.LocalWebHook = {};
public setInstance(instance: InstanceDto) {
this.logger.setInstance(instance.instanceName);
this.instance.name = instance.instanceName;
this.instance.id = instance.instanceId;
this.instance.integration = instance.integration;
this.instance.number = instance.number;
this.instance.token = instance.token;
this.instance.businessId = instance.businessId;
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {
this.chatwootService.eventWhatsapp(
Events.STATUS_INSTANCE,
{ instanceName: this.instance.name },
{
instance: this.instance.name,
status: 'created',
},
);
}
}
public set instanceName(name: string) {
this.logger.setInstance(name);
this.instance.name = name;
}
public get instanceName() {
return this.instance.name;
}
}
```
### Extended Channel Service
```typescript
export class EvolutionStartupService extends ChannelStartupService {
constructor(
configService: ConfigService,
eventEmitter: EventEmitter2,
prismaRepository: PrismaRepository,
cache: CacheService,
chatwootCache: CacheService,
) {
super(configService, eventEmitter, prismaRepository, cache, chatwootCache);
}
public async sendMessage(data: SendTextDto): Promise<any> {
// Evolution-specific message sending logic
const response = await this.evolutionApiCall('/send-message', data);
return response;
}
public async connectToWhatsapp(data: any): Promise<void> {
// Evolution-specific connection logic
this.logger.log('Connecting to Evolution API');
// Set up webhook listeners
this.setupWebhookHandlers();
// Initialize connection
await this.initializeConnection(data);
}
private async evolutionApiCall(endpoint: string, data: any): Promise<any> {
const config = this.configService.get<Evolution>('EVOLUTION');
try {
const response = await axios.post(`${config.API_URL}${endpoint}`, data, {
headers: {
'Authorization': `Bearer ${this.instance.token}`,
'Content-Type': 'application/json',
},
});
return response.data;
} catch (error) {
this.logger.error(`Evolution API call failed: ${error.message}`);
throw new InternalServerErrorException('Evolution API call failed');
}
}
private setupWebhookHandlers(): void {
// Set up webhook event handlers
}
private async initializeConnection(data: any): Promise<void> {
// Initialize connection with Evolution API
}
}
```
## Business API Service Pattern
### Meta Business Service
```typescript
export class BusinessStartupService extends ChannelStartupService {
constructor(
configService: ConfigService,
eventEmitter: EventEmitter2,
prismaRepository: PrismaRepository,
cache: CacheService,
chatwootCache: CacheService,
baileysCache: CacheService,
providerFiles: ProviderFiles,
) {
super(configService, eventEmitter, prismaRepository, cache, chatwootCache);
}
public async sendMessage(data: SendTextDto): Promise<any> {
const businessConfig = this.configService.get<WaBusiness>('WA_BUSINESS');
const payload = {
messaging_product: 'whatsapp',
to: data.number,
type: 'text',
text: {
body: data.text,
},
};
try {
const response = await axios.post(
`${businessConfig.URL}/${businessConfig.VERSION}/${this.instance.businessId}/messages`,
payload,
{
headers: {
'Authorization': `Bearer ${this.instance.token}`,
'Content-Type': 'application/json',
},
}
);
return response.data;
} catch (error) {
this.logger.error(`Business API call failed: ${error.message}`);
throw new BadRequestException('Failed to send message via Business API');
}
}
public async receiveWebhook(data: any): Promise<void> {
// Process incoming webhook from Meta Business API
const { entry } = data;
for (const entryItem of entry) {
const { changes } = entryItem;
for (const change of changes) {
if (change.field === 'messages') {
await this.processMessage(change.value);
}
}
}
}
private async processMessage(messageData: any): Promise<void> {
// Process incoming message from Business API
const { messages, contacts } = messageData;
if (messages) {
for (const message of messages) {
await this.handleIncomingMessage(message, contacts);
}
}
}
private async handleIncomingMessage(message: any, contacts: any[]): Promise<void> {
// Handle individual message
const contact = contacts?.find(c => c.wa_id === message.from);
// Emit event for message processing
this.eventEmitter.emit(Events.MESSAGES_UPSERT, {
instanceName: this.instance.name,
message,
contact,
});
}
}
```
## Baileys Service Pattern
### Baileys Integration Service
```typescript
export class BaileysStartupService extends ChannelStartupService {
constructor(
configService: ConfigService,
eventEmitter: EventEmitter2,
prismaRepository: PrismaRepository,
cache: CacheService,
chatwootCache: CacheService,
baileysCache: CacheService,
providerFiles: ProviderFiles,
) {
super(configService, eventEmitter, prismaRepository, cache, chatwootCache);
}
public async connectToWhatsapp(): Promise<void> {
const authPath = path.join(INSTANCE_DIR, this.instance.name);
const { state, saveCreds } = await useMultiFileAuthState(authPath);
this.client = makeWASocket({
auth: state,
logger: P({ level: 'error' }),
printQRInTerminal: false,
browser: ['Evolution API', 'Chrome', '4.0.0'],
defaultQueryTimeoutMs: 60000,
});
this.setupEventHandlers();
this.client.ev.on('creds.update', saveCreds);
}
private setupEventHandlers(): void {
this.client.ev.on('connection.update', (update) => {
this.handleConnectionUpdate(update);
});
this.client.ev.on('messages.upsert', ({ messages, type }) => {
this.handleIncomingMessages(messages, type);
});
this.client.ev.on('messages.update', (updates) => {
this.handleMessageUpdates(updates);
});
this.client.ev.on('contacts.upsert', (contacts) => {
this.handleContactsUpdate(contacts);
});
this.client.ev.on('chats.upsert', (chats) => {
this.handleChatsUpdate(chats);
});
}
private async handleConnectionUpdate(update: ConnectionUpdate): Promise<void> {
const { connection, lastDisconnect, qr } = update;
if (qr) {
this.instance.qrcode = {
count: this.instance.qrcode?.count ? this.instance.qrcode.count + 1 : 1,
base64: qr,
};
this.eventEmitter.emit(Events.QRCODE_UPDATED, {
instanceName: this.instance.name,
qrcode: this.instance.qrcode,
});
}
if (connection === 'close') {
const shouldReconnect = (lastDisconnect?.error as Boom)?.output?.statusCode !== DisconnectReason.loggedOut;
if (shouldReconnect) {
this.logger.log('Connection closed, reconnecting...');
await this.connectToWhatsapp();
} else {
this.logger.log('Connection closed, logged out');
this.eventEmitter.emit(Events.LOGOUT_INSTANCE, {
instanceName: this.instance.name,
});
}
}
if (connection === 'open') {
this.logger.log('Connection opened successfully');
this.instance.wuid = this.client.user?.id;
this.eventEmitter.emit(Events.CONNECTION_UPDATE, {
instanceName: this.instance.name,
state: 'open',
});
}
}
public async sendMessage(data: SendTextDto): Promise<any> {
const jid = createJid(data.number);
const message = {
text: data.text,
};
if (data.linkPreview !== undefined) {
message.linkPreview = data.linkPreview;
}
if (data.mentionsEveryOne) {
// Handle mentions
}
try {
const response = await this.client.sendMessage(jid, message);
return response;
} catch (error) {
this.logger.error(`Failed to send message: ${error.message}`);
throw new BadRequestException('Failed to send message');
}
}
}
```
## Channel Router Pattern
### Channel Router Structure
```typescript
export class ChannelRouter {
public readonly router: Router;
constructor(configService: any, ...guards: any[]) {
this.router = Router();
this.router.use('/', new EvolutionRouter(configService).router);
this.router.use('/', new MetaRouter(configService).router);
this.router.use('/baileys', new BaileysRouter(...guards).router);
}
}
```
### Specific Channel Router
```typescript
export class EvolutionRouter extends RouterBroker {
constructor(private readonly configService: ConfigService) {
super();
this.router
.post(this.routerPath('webhook'), async (req, res) => {
const response = await evolutionController.receiveWebhook(req.body);
return res.status(HttpStatus.OK).json(response);
});
}
public readonly router: Router = Router();
}
```
## Integration Types
### Channel Data Types
```typescript
type ChannelDataType = {
configService: ConfigService;
eventEmitter: EventEmitter2;
prismaRepository: PrismaRepository;
cache: CacheService;
chatwootCache: CacheService;
baileysCache: CacheService;
providerFiles: ProviderFiles;
};
export enum Integration {
WHATSAPP_BUSINESS = 'WHATSAPP-BUSINESS',
WHATSAPP_BAILEYS = 'WHATSAPP-BAILEYS',
EVOLUTION = 'EVOLUTION',
}
```
## Error Handling in Channels
### Channel-Specific Error Handling
```typescript
// CORRECT - Channel-specific error handling
public async sendMessage(data: SendTextDto): Promise<any> {
try {
const response = await this.channelSpecificSend(data);
return response;
} catch (error) {
this.logger.error(`${this.constructor.name} send failed: ${error.message}`);
if (error.response?.status === 401) {
throw new UnauthorizedException('Invalid token for channel');
}
if (error.response?.status === 429) {
throw new BadRequestException('Rate limit exceeded');
}
throw new InternalServerErrorException('Channel communication failed');
}
}
```
## Channel Testing Pattern
### Channel Service Testing
```typescript
describe('EvolutionStartupService', () => {
let service: EvolutionStartupService;
let configService: jest.Mocked<ConfigService>;
let eventEmitter: jest.Mocked<EventEmitter2>;
beforeEach(() => {
const mockConfig = {
get: jest.fn().mockReturnValue({
API_URL: 'https://api.evolution.com',
}),
};
service = new EvolutionStartupService(
mockConfig as any,
eventEmitter,
prismaRepository,
cache,
chatwootCache,
);
});
describe('sendMessage', () => {
it('should send message successfully', async () => {
const data = { number: '5511999999999', text: 'Test message' };
// Mock axios response
jest.spyOn(axios, 'post').mockResolvedValue({
data: { success: true, messageId: '123' },
});
const result = await service.sendMessage(data);
expect(result.success).toBe(true);
expect(axios.post).toHaveBeenCalledWith(
expect.stringContaining('/send-message'),
data,
expect.objectContaining({
headers: expect.objectContaining({
'Authorization': expect.stringContaining('Bearer'),
}),
})
);
});
});
});
```

View File

@@ -0,0 +1,597 @@
---
description: Chatbot integration patterns for Evolution API
globs:
- "src/api/integrations/chatbot/**/*.ts"
alwaysApply: false
---
# Evolution API Chatbot Integration Rules
## Base Chatbot Pattern
### Base Chatbot DTO
```typescript
/**
* Base DTO for all chatbot integrations
* Contains common properties shared by all chatbot types
*/
export class BaseChatbotDto {
enabled?: boolean;
description: string;
expire?: number;
keywordFinish?: string;
delayMessage?: number;
unknownMessage?: string;
listeningFromMe?: boolean;
stopBotFromMe?: boolean;
keepOpen?: boolean;
debounceTime?: number;
triggerType: TriggerType;
triggerOperator?: TriggerOperator;
triggerValue?: string;
ignoreJids?: string[];
splitMessages?: boolean;
timePerChar?: number;
}
```
### Base Chatbot Controller
```typescript
export abstract class BaseChatbotController<BotType = any, BotData extends BaseChatbotDto = BaseChatbotDto>
extends ChatbotController
implements ChatbotControllerInterface
{
public readonly logger: Logger;
integrationEnabled: boolean;
botRepository: any;
settingsRepository: any;
sessionRepository: any;
userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {};
// Abstract methods to be implemented by specific chatbots
protected abstract readonly integrationName: string;
protected abstract processBot(
waInstance: any,
remoteJid: string,
bot: BotType,
session: any,
settings: ChatbotSettings,
content: string,
pushName?: string,
msg?: any,
): Promise<void>;
protected abstract getFallbackBotId(settings: any): string | undefined;
constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {
super(prismaRepository, waMonitor);
this.sessionRepository = this.prismaRepository.integrationSession;
}
// Base implementation methods
public async createBot(instance: InstanceDto, data: BotData) {
if (!data.enabled) {
throw new BadRequestException(`${this.integrationName} is disabled`);
}
// Common bot creation logic
const bot = await this.botRepository.create({
data: {
...data,
instanceId: instance.instanceId,
},
});
return bot;
}
}
```
### Base Chatbot Service
```typescript
/**
* Base class for all chatbot service implementations
* Contains common methods shared across different chatbot integrations
*/
export abstract class BaseChatbotService<BotType = any, SettingsType = any> {
protected readonly logger: Logger;
protected readonly waMonitor: WAMonitoringService;
protected readonly prismaRepository: PrismaRepository;
protected readonly configService?: ConfigService;
constructor(
waMonitor: WAMonitoringService,
prismaRepository: PrismaRepository,
loggerName: string,
configService?: ConfigService,
) {
this.waMonitor = waMonitor;
this.prismaRepository = prismaRepository;
this.logger = new Logger(loggerName);
this.configService = configService;
}
/**
* Check if a message contains an image
*/
protected isImageMessage(content: string): boolean {
return content.includes('imageMessage');
}
/**
* Extract text content from message
*/
protected getMessageContent(msg: any): string {
return getConversationMessage(msg);
}
/**
* Send typing indicator
*/
protected async sendTyping(instanceName: string, remoteJid: string): Promise<void> {
await this.waMonitor.waInstances[instanceName].sendPresence(remoteJid, 'composing');
}
}
```
## Typebot Integration Pattern
### Typebot Service
```typescript
export class TypebotService extends BaseChatbotService<TypebotModel, any> {
constructor(
waMonitor: WAMonitoringService,
configService: ConfigService,
prismaRepository: PrismaRepository,
private readonly openaiService: OpenaiService,
) {
super(waMonitor, prismaRepository, 'TypebotService', configService);
}
public async sendTypebotMessage(
instanceName: string,
remoteJid: string,
typebot: TypebotModel,
content: string,
): Promise<void> {
try {
const response = await axios.post(
`${typebot.url}/api/v1/typebots/${typebot.typebot}/startChat`,
{
message: content,
sessionId: `${instanceName}-${remoteJid}`,
},
{
headers: {
'Content-Type': 'application/json',
},
}
);
const { messages } = response.data;
for (const message of messages) {
await this.processTypebotMessage(instanceName, remoteJid, message);
}
} catch (error) {
this.logger.error(`Typebot API error: ${error.message}`);
throw new InternalServerErrorException('Typebot communication failed');
}
}
private async processTypebotMessage(
instanceName: string,
remoteJid: string,
message: any,
): Promise<void> {
const waInstance = this.waMonitor.waInstances[instanceName];
if (message.type === 'text') {
await waInstance.sendMessage({
number: remoteJid.split('@')[0],
text: message.content.richText[0].children[0].text,
});
}
if (message.type === 'image') {
await waInstance.sendMessage({
number: remoteJid.split('@')[0],
mediaMessage: {
mediatype: 'image',
media: message.content.url,
},
});
}
}
}
```
## OpenAI Integration Pattern
### OpenAI Service
```typescript
export class OpenaiService extends BaseChatbotService<OpenaiModel, any> {
constructor(
waMonitor: WAMonitoringService,
prismaRepository: PrismaRepository,
configService: ConfigService,
) {
super(waMonitor, prismaRepository, 'OpenaiService', configService);
}
public async sendOpenaiMessage(
instanceName: string,
remoteJid: string,
openai: OpenaiModel,
content: string,
pushName?: string,
): Promise<void> {
try {
const openaiConfig = this.configService.get<Openai>('OPENAI');
const response = await axios.post(
'https://api.openai.com/v1/chat/completions',
{
model: openai.model || 'gpt-3.5-turbo',
messages: [
{
role: 'system',
content: openai.systemMessage || 'You are a helpful assistant.',
},
{
role: 'user',
content: content,
},
],
max_tokens: openai.maxTokens || 1000,
temperature: openai.temperature || 0.7,
},
{
headers: {
'Authorization': `Bearer ${openai.apiKey || openaiConfig.API_KEY}`,
'Content-Type': 'application/json',
},
}
);
const aiResponse = response.data.choices[0].message.content;
await this.waMonitor.waInstances[instanceName].sendMessage({
number: remoteJid.split('@')[0],
text: aiResponse,
});
} catch (error) {
this.logger.error(`OpenAI API error: ${error.message}`);
// Send fallback message
await this.waMonitor.waInstances[instanceName].sendMessage({
number: remoteJid.split('@')[0],
text: openai.unknownMessage || 'Desculpe, não consegui processar sua mensagem.',
});
}
}
}
```
## Chatwoot Integration Pattern
### Chatwoot Service
```typescript
export class ChatwootService extends BaseChatbotService<any, any> {
constructor(
waMonitor: WAMonitoringService,
configService: ConfigService,
prismaRepository: PrismaRepository,
private readonly chatwootCache: CacheService,
) {
super(waMonitor, prismaRepository, 'ChatwootService', configService);
}
public async eventWhatsapp(
event: Events,
instanceName: { instanceName: string },
data: any,
): Promise<void> {
const chatwootConfig = this.configService.get<Chatwoot>('CHATWOOT');
if (!chatwootConfig.ENABLED) {
return;
}
try {
const instance = await this.prismaRepository.instance.findUnique({
where: { name: instanceName.instanceName },
});
if (!instance?.chatwootAccountId) {
return;
}
const webhook = {
event,
instance: instanceName.instanceName,
data,
timestamp: new Date().toISOString(),
};
await axios.post(
`${instance.chatwootUrl}/api/v1/accounts/${instance.chatwootAccountId}/webhooks`,
webhook,
{
headers: {
'Authorization': `Bearer ${instance.chatwootToken}`,
'Content-Type': 'application/json',
},
}
);
} catch (error) {
this.logger.error(`Chatwoot webhook error: ${error.message}`);
}
}
public async createConversation(
instanceName: string,
contact: any,
message: any,
): Promise<void> {
// Create conversation in Chatwoot
const instance = await this.prismaRepository.instance.findUnique({
where: { name: instanceName },
});
if (!instance?.chatwootAccountId) {
return;
}
try {
const conversation = await axios.post(
`${instance.chatwootUrl}/api/v1/accounts/${instance.chatwootAccountId}/conversations`,
{
source_id: contact.id,
inbox_id: instance.chatwootInboxId,
contact_id: contact.chatwootContactId,
},
{
headers: {
'Authorization': `Bearer ${instance.chatwootToken}`,
'Content-Type': 'application/json',
},
}
);
// Cache conversation
await this.chatwootCache.set(
`conversation:${instanceName}:${contact.id}`,
conversation.data,
3600
);
} catch (error) {
this.logger.error(`Chatwoot conversation creation error: ${error.message}`);
}
}
}
```
## Dify Integration Pattern
### Dify Service
```typescript
export class DifyService extends BaseChatbotService<DifyModel, any> {
constructor(
waMonitor: WAMonitoringService,
prismaRepository: PrismaRepository,
configService: ConfigService,
private readonly openaiService: OpenaiService,
) {
super(waMonitor, prismaRepository, 'DifyService', configService);
}
public async sendDifyMessage(
instanceName: string,
remoteJid: string,
dify: DifyModel,
content: string,
): Promise<void> {
try {
const response = await axios.post(
`${dify.apiUrl}/v1/chat-messages`,
{
inputs: {},
query: content,
user: remoteJid,
conversation_id: `${instanceName}-${remoteJid}`,
response_mode: 'blocking',
},
{
headers: {
'Authorization': `Bearer ${dify.apiKey}`,
'Content-Type': 'application/json',
},
}
);
const aiResponse = response.data.answer;
await this.waMonitor.waInstances[instanceName].sendMessage({
number: remoteJid.split('@')[0],
text: aiResponse,
});
} catch (error) {
this.logger.error(`Dify API error: ${error.message}`);
// Fallback to OpenAI if configured
if (dify.fallbackOpenai && this.openaiService) {
await this.openaiService.sendOpenaiMessage(instanceName, remoteJid, dify.openaiBot, content);
}
}
}
}
```
## Chatbot Router Pattern
### Chatbot Router Structure (Evolution API Real Pattern)
```typescript
export class ChatbotRouter {
public readonly router: Router;
constructor(...guards: any[]) {
this.router = Router();
// Real Evolution API chatbot integrations
this.router.use('/evolutionBot', new EvolutionBotRouter(...guards).router);
this.router.use('/chatwoot', new ChatwootRouter(...guards).router);
this.router.use('/typebot', new TypebotRouter(...guards).router);
this.router.use('/openai', new OpenaiRouter(...guards).router);
this.router.use('/dify', new DifyRouter(...guards).router);
this.router.use('/flowise', new FlowiseRouter(...guards).router);
this.router.use('/n8n', new N8nRouter(...guards).router);
this.router.use('/evoai', new EvoaiRouter(...guards).router);
}
}
```
## Chatbot Validation Patterns
### Chatbot Schema Validation (Evolution API Pattern)
```typescript
import { JSONSchema7 } from 'json-schema';
import { v4 } from 'uuid';
const isNotEmpty = (...fields: string[]) => {
const properties = {};
fields.forEach((field) => {
properties[field] = {
if: { properties: { [field]: { type: 'string' } } },
then: { properties: { [field]: { minLength: 1 } } },
};
});
return {
allOf: Object.values(properties),
};
};
export const evolutionBotSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
enabled: { type: 'boolean' },
description: { type: 'string' },
apiUrl: { type: 'string' },
apiKey: { type: 'string' },
triggerType: { type: 'string', enum: ['all', 'keyword', 'none', 'advanced'] },
triggerOperator: { type: 'string', enum: ['equals', 'contains', 'startsWith', 'endsWith', 'regex'] },
triggerValue: { type: 'string' },
expire: { type: 'integer' },
keywordFinish: { type: 'string' },
delayMessage: { type: 'integer' },
unknownMessage: { type: 'string' },
listeningFromMe: { type: 'boolean' },
stopBotFromMe: { type: 'boolean' },
keepOpen: { type: 'boolean' },
debounceTime: { type: 'integer' },
ignoreJids: { type: 'array', items: { type: 'string' } },
splitMessages: { type: 'boolean' },
timePerChar: { type: 'integer' },
},
required: ['enabled', 'apiUrl', 'triggerType'],
...isNotEmpty('enabled', 'apiUrl', 'triggerType'),
};
function validateKeywordTrigger(
content: string,
operator: TriggerOperator,
value: string,
): boolean {
const normalizedContent = content.toLowerCase().trim();
const normalizedValue = value.toLowerCase().trim();
switch (operator) {
case TriggerOperator.EQUALS:
return normalizedContent === normalizedValue;
case TriggerOperator.CONTAINS:
return normalizedContent.includes(normalizedValue);
case TriggerOperator.STARTS_WITH:
return normalizedContent.startsWith(normalizedValue);
case TriggerOperator.ENDS_WITH:
return normalizedContent.endsWith(normalizedValue);
default:
return false;
}
}
```
## Session Management Pattern
### Chatbot Session Handling
```typescript
export class ChatbotSessionManager {
constructor(
private readonly prismaRepository: PrismaRepository,
private readonly cache: CacheService,
) {}
public async getSession(
instanceName: string,
remoteJid: string,
botId: string,
): Promise<IntegrationSession | null> {
const cacheKey = `session:${instanceName}:${remoteJid}:${botId}`;
// Try cache first
let session = await this.cache.get(cacheKey);
if (session) {
return session;
}
// Query database
session = await this.prismaRepository.integrationSession.findFirst({
where: {
instanceId: instanceName,
remoteJid,
botId,
status: 'opened',
},
});
// Cache result
if (session) {
await this.cache.set(cacheKey, session, 300); // 5 min TTL
}
return session;
}
public async createSession(
instanceName: string,
remoteJid: string,
botId: string,
): Promise<IntegrationSession> {
const session = await this.prismaRepository.integrationSession.create({
data: {
instanceId: instanceName,
remoteJid,
botId,
status: 'opened',
createdAt: new Date(),
},
});
// Cache new session
const cacheKey = `session:${instanceName}:${remoteJid}:${botId}`;
await this.cache.set(cacheKey, session, 300);
return session;
}
public async closeSession(sessionId: string): Promise<void> {
await this.prismaRepository.integrationSession.update({
where: { id: sessionId },
data: { status: 'closed', updatedAt: new Date() },
});
// Invalidate cache
// Note: In a real implementation, you'd need to track cache keys by session ID
}
}
```

View File

@@ -0,0 +1,851 @@
---
description: Event integration patterns for Evolution API
globs:
- "src/api/integrations/event/**/*.ts"
alwaysApply: false
---
# Evolution API Event Integration Rules
## Event Manager Pattern
### Event Manager Structure
```typescript
import { PrismaRepository } from '@api/repository/repository.service';
import { ConfigService } from '@config/env.config';
import { Logger } from '@config/logger.config';
import { Server } from 'http';
export class EventManager {
private prismaRepository: PrismaRepository;
private configService: ConfigService;
private logger = new Logger('EventManager');
// Event integrations
private webhook: WebhookController;
private websocket: WebsocketController;
private rabbitmq: RabbitmqController;
private nats: NatsController;
private sqs: SqsController;
private pusher: PusherController;
constructor(
prismaRepository: PrismaRepository,
configService: ConfigService,
server?: Server,
) {
this.prismaRepository = prismaRepository;
this.configService = configService;
// Initialize event controllers
this.webhook = new WebhookController(prismaRepository, configService);
this.websocket = new WebsocketController(prismaRepository, configService, server);
this.rabbitmq = new RabbitmqController(prismaRepository, configService);
this.nats = new NatsController(prismaRepository, configService);
this.sqs = new SqsController(prismaRepository, configService);
this.pusher = new PusherController(prismaRepository, configService);
}
public async emit(eventData: {
instanceName: string;
origin: string;
event: string;
data: Object;
serverUrl: string;
dateTime: string;
sender: string;
apiKey?: string;
local?: boolean;
integration?: string[];
}): Promise<void> {
this.logger.log(`Emitting event ${eventData.event} for instance ${eventData.instanceName}`);
// Emit to all configured integrations
await Promise.allSettled([
this.webhook.emit(eventData),
this.websocket.emit(eventData),
this.rabbitmq.emit(eventData),
this.nats.emit(eventData),
this.sqs.emit(eventData),
this.pusher.emit(eventData),
]);
}
public async setInstance(instanceName: string, data: any): Promise<any> {
const promises = [];
if (data.websocket) {
promises.push(
this.websocket.set(instanceName, {
websocket: {
enabled: true,
events: data.websocket?.events,
},
})
);
}
if (data.rabbitmq) {
promises.push(
this.rabbitmq.set(instanceName, {
rabbitmq: {
enabled: true,
events: data.rabbitmq?.events,
},
})
);
}
if (data.webhook) {
promises.push(
this.webhook.set(instanceName, {
webhook: {
enabled: true,
events: data.webhook?.events,
url: data.webhook?.url,
headers: data.webhook?.headers,
base64: data.webhook?.base64,
byEvents: data.webhook?.byEvents,
},
})
);
}
// Set other integrations...
await Promise.allSettled(promises);
}
}
```
## Base Event Controller Pattern
### Abstract Event Controller
```typescript
import { PrismaRepository } from '@api/repository/repository.service';
import { ConfigService } from '@config/env.config';
import { Logger } from '@config/logger.config';
export type EmitData = {
instanceName: string;
origin: string;
event: string;
data: Object;
serverUrl: string;
dateTime: string;
sender: string;
apiKey?: string;
local?: boolean;
integration?: string[];
};
export interface EventControllerInterface {
integrationEnabled: boolean;
emit(data: EmitData): Promise<void>;
set(instanceName: string, data: any): Promise<any>;
}
export abstract class EventController implements EventControllerInterface {
protected readonly logger: Logger;
protected readonly prismaRepository: PrismaRepository;
protected readonly configService: ConfigService;
public integrationEnabled: boolean = false;
// Available events for all integrations
public static readonly events = [
'APPLICATION_STARTUP',
'INSTANCE_CREATE',
'INSTANCE_DELETE',
'QRCODE_UPDATED',
'CONNECTION_UPDATE',
'STATUS_INSTANCE',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_EDITED',
'MESSAGES_UPDATE',
'MESSAGES_DELETE',
'SEND_MESSAGE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'PRESENCE_UPDATE',
'CHATS_SET',
'CHATS_UPDATE',
'CHATS_UPSERT',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUPS_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CALL',
'TYPEBOT_START',
'TYPEBOT_CHANGE_STATUS',
'LABELS_EDIT',
'LABELS_ASSOCIATION',
'CREDS_UPDATE',
'MESSAGING_HISTORY_SET',
'REMOVE_INSTANCE',
'LOGOUT_INSTANCE',
];
constructor(
prismaRepository: PrismaRepository,
configService: ConfigService,
loggerName: string,
) {
this.prismaRepository = prismaRepository;
this.configService = configService;
this.logger = new Logger(loggerName);
}
// Abstract methods to be implemented by specific integrations
public abstract emit(data: EmitData): Promise<void>;
public abstract set(instanceName: string, data: any): Promise<any>;
// Helper method to check if event should be processed
protected shouldProcessEvent(eventName: string, configuredEvents?: string[]): boolean {
if (!configuredEvents || configuredEvents.length === 0) {
return true; // Process all events if none specified
}
return configuredEvents.includes(eventName);
}
// Helper method to get instance configuration
protected async getInstanceConfig(instanceName: string): Promise<any> {
try {
const instance = await this.prismaRepository.instance.findUnique({
where: { name: instanceName },
});
return instance;
} catch (error) {
this.logger.error(`Failed to get instance config for ${instanceName}:`, error);
return null;
}
}
}
```
## Webhook Integration Pattern
### Webhook Controller Implementation
```typescript
export class WebhookController extends EventController {
constructor(
prismaRepository: PrismaRepository,
configService: ConfigService,
) {
super(prismaRepository, configService, 'WebhookController');
}
public async emit(data: EmitData): Promise<void> {
try {
const instance = await this.getInstanceConfig(data.instanceName);
if (!instance?.webhook?.enabled) {
return;
}
const webhookConfig = instance.webhook;
if (!this.shouldProcessEvent(data.event, webhookConfig.events)) {
return;
}
const payload = {
event: data.event,
instance: data.instanceName,
data: data.data,
timestamp: data.dateTime,
sender: data.sender,
server: {
version: process.env.npm_package_version,
url: data.serverUrl,
},
};
// Encode data as base64 if configured
if (webhookConfig.base64) {
payload.data = Buffer.from(JSON.stringify(payload.data)).toString('base64');
}
const headers = {
'Content-Type': 'application/json',
'User-Agent': 'Evolution-API-Webhook',
...webhookConfig.headers,
};
if (webhookConfig.byEvents) {
// Send to event-specific endpoint
const eventUrl = `${webhookConfig.url}/${data.event.toLowerCase()}`;
await this.sendWebhook(eventUrl, payload, headers);
} else {
// Send to main webhook URL
await this.sendWebhook(webhookConfig.url, payload, headers);
}
this.logger.log(`Webhook sent for event ${data.event} to instance ${data.instanceName}`);
} catch (error) {
this.logger.error(`Webhook emission failed for ${data.instanceName}:`, error);
}
}
public async set(instanceName: string, data: any): Promise<any> {
try {
const webhookData = data.webhook;
await this.prismaRepository.instance.update({
where: { name: instanceName },
data: {
webhook: webhookData,
},
});
this.logger.log(`Webhook configuration set for instance ${instanceName}`);
return { webhook: webhookData };
} catch (error) {
this.logger.error(`Failed to set webhook config for ${instanceName}:`, error);
throw error;
}
}
private async sendWebhook(url: string, payload: any, headers: any): Promise<void> {
try {
const response = await axios.post(url, payload, {
headers,
timeout: 30000,
maxRedirects: 3,
});
if (response.status >= 200 && response.status < 300) {
this.logger.log(`Webhook delivered successfully to ${url}`);
} else {
this.logger.warn(`Webhook returned status ${response.status} for ${url}`);
}
} catch (error) {
this.logger.error(`Webhook delivery failed to ${url}:`, error.message);
// Implement retry logic here if needed
if (error.response?.status >= 500) {
// Server error - might be worth retrying
this.logger.log(`Server error detected, webhook might be retried later`);
}
}
}
}
```
## WebSocket Integration Pattern
### WebSocket Controller Implementation
```typescript
import { Server as SocketIOServer } from 'socket.io';
import { Server } from 'http';
export class WebsocketController extends EventController {
private io: SocketIOServer;
constructor(
prismaRepository: PrismaRepository,
configService: ConfigService,
server?: Server,
) {
super(prismaRepository, configService, 'WebsocketController');
if (server) {
this.io = new SocketIOServer(server, {
cors: {
origin: "*",
methods: ["GET", "POST"],
},
});
this.setupSocketHandlers();
}
}
private setupSocketHandlers(): void {
this.io.on('connection', (socket) => {
this.logger.log(`WebSocket client connected: ${socket.id}`);
socket.on('join-instance', (instanceName: string) => {
socket.join(`instance:${instanceName}`);
this.logger.log(`Client ${socket.id} joined instance ${instanceName}`);
});
socket.on('leave-instance', (instanceName: string) => {
socket.leave(`instance:${instanceName}`);
this.logger.log(`Client ${socket.id} left instance ${instanceName}`);
});
socket.on('disconnect', () => {
this.logger.log(`WebSocket client disconnected: ${socket.id}`);
});
});
}
public async emit(data: EmitData): Promise<void> {
if (!this.io) {
return;
}
try {
const instance = await this.getInstanceConfig(data.instanceName);
if (!instance?.websocket?.enabled) {
return;
}
const websocketConfig = instance.websocket;
if (!this.shouldProcessEvent(data.event, websocketConfig.events)) {
return;
}
const payload = {
event: data.event,
instance: data.instanceName,
data: data.data,
timestamp: data.dateTime,
sender: data.sender,
};
// Emit to specific instance room
this.io.to(`instance:${data.instanceName}`).emit('evolution-event', payload);
// Also emit to global room for monitoring
this.io.emit('global-event', payload);
this.logger.log(`WebSocket event ${data.event} emitted for instance ${data.instanceName}`);
} catch (error) {
this.logger.error(`WebSocket emission failed for ${data.instanceName}:`, error);
}
}
public async set(instanceName: string, data: any): Promise<any> {
try {
const websocketData = data.websocket;
await this.prismaRepository.instance.update({
where: { name: instanceName },
data: {
websocket: websocketData,
},
});
this.logger.log(`WebSocket configuration set for instance ${instanceName}`);
return { websocket: websocketData };
} catch (error) {
this.logger.error(`Failed to set WebSocket config for ${instanceName}:`, error);
throw error;
}
}
}
```
## Queue Integration Patterns
### RabbitMQ Controller Implementation
```typescript
import amqp from 'amqplib';
export class RabbitmqController extends EventController {
private connection: amqp.Connection | null = null;
private channel: amqp.Channel | null = null;
constructor(
prismaRepository: PrismaRepository,
configService: ConfigService,
) {
super(prismaRepository, configService, 'RabbitmqController');
this.initializeConnection();
}
private async initializeConnection(): Promise<void> {
try {
const rabbitmqConfig = this.configService.get('RABBITMQ');
if (!rabbitmqConfig?.ENABLED) {
return;
}
this.connection = await amqp.connect(rabbitmqConfig.URI);
this.channel = await this.connection.createChannel();
// Declare exchange for Evolution API events
await this.channel.assertExchange('evolution-events', 'topic', { durable: true });
this.logger.log('RabbitMQ connection established');
} catch (error) {
this.logger.error('Failed to initialize RabbitMQ connection:', error);
}
}
public async emit(data: EmitData): Promise<void> {
if (!this.channel) {
return;
}
try {
const instance = await this.getInstanceConfig(data.instanceName);
if (!instance?.rabbitmq?.enabled) {
return;
}
const rabbitmqConfig = instance.rabbitmq;
if (!this.shouldProcessEvent(data.event, rabbitmqConfig.events)) {
return;
}
const payload = {
event: data.event,
instance: data.instanceName,
data: data.data,
timestamp: data.dateTime,
sender: data.sender,
};
const routingKey = `evolution.${data.instanceName}.${data.event.toLowerCase()}`;
await this.channel.publish(
'evolution-events',
routingKey,
Buffer.from(JSON.stringify(payload)),
{
persistent: true,
timestamp: Date.now(),
messageId: `${data.instanceName}-${Date.now()}`,
}
);
this.logger.log(`RabbitMQ message published for event ${data.event} to instance ${data.instanceName}`);
} catch (error) {
this.logger.error(`RabbitMQ emission failed for ${data.instanceName}:`, error);
}
}
public async set(instanceName: string, data: any): Promise<any> {
try {
const rabbitmqData = data.rabbitmq;
await this.prismaRepository.instance.update({
where: { name: instanceName },
data: {
rabbitmq: rabbitmqData,
},
});
this.logger.log(`RabbitMQ configuration set for instance ${instanceName}`);
return { rabbitmq: rabbitmqData };
} catch (error) {
this.logger.error(`Failed to set RabbitMQ config for ${instanceName}:`, error);
throw error;
}
}
}
```
### SQS Controller Implementation
```typescript
import { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs';
export class SqsController extends EventController {
private sqsClient: SQSClient | null = null;
constructor(
prismaRepository: PrismaRepository,
configService: ConfigService,
) {
super(prismaRepository, configService, 'SqsController');
this.initializeSQSClient();
}
private initializeSQSClient(): void {
try {
const sqsConfig = this.configService.get('SQS');
if (!sqsConfig?.ENABLED) {
return;
}
this.sqsClient = new SQSClient({
region: sqsConfig.REGION,
credentials: {
accessKeyId: sqsConfig.ACCESS_KEY_ID,
secretAccessKey: sqsConfig.SECRET_ACCESS_KEY,
},
});
this.logger.log('SQS client initialized');
} catch (error) {
this.logger.error('Failed to initialize SQS client:', error);
}
}
public async emit(data: EmitData): Promise<void> {
if (!this.sqsClient) {
return;
}
try {
const instance = await this.getInstanceConfig(data.instanceName);
if (!instance?.sqs?.enabled) {
return;
}
const sqsConfig = instance.sqs;
if (!this.shouldProcessEvent(data.event, sqsConfig.events)) {
return;
}
const payload = {
event: data.event,
instance: data.instanceName,
data: data.data,
timestamp: data.dateTime,
sender: data.sender,
};
const command = new SendMessageCommand({
QueueUrl: sqsConfig.queueUrl,
MessageBody: JSON.stringify(payload),
MessageAttributes: {
event: {
DataType: 'String',
StringValue: data.event,
},
instance: {
DataType: 'String',
StringValue: data.instanceName,
},
},
MessageGroupId: data.instanceName, // For FIFO queues
MessageDeduplicationId: `${data.instanceName}-${Date.now()}`, // For FIFO queues
});
await this.sqsClient.send(command);
this.logger.log(`SQS message sent for event ${data.event} to instance ${data.instanceName}`);
} catch (error) {
this.logger.error(`SQS emission failed for ${data.instanceName}:`, error);
}
}
public async set(instanceName: string, data: any): Promise<any> {
try {
const sqsData = data.sqs;
await this.prismaRepository.instance.update({
where: { name: instanceName },
data: {
sqs: sqsData,
},
});
this.logger.log(`SQS configuration set for instance ${instanceName}`);
return { sqs: sqsData };
} catch (error) {
this.logger.error(`Failed to set SQS config for ${instanceName}:`, error);
throw error;
}
}
}
```
## Event DTO Pattern
### Event Configuration DTO
```typescript
import { JsonValue } from '@prisma/client/runtime/library';
export class EventDto {
webhook?: {
enabled?: boolean;
events?: string[];
url?: string;
headers?: JsonValue;
byEvents?: boolean;
base64?: boolean;
};
websocket?: {
enabled?: boolean;
events?: string[];
};
sqs?: {
enabled?: boolean;
events?: string[];
queueUrl?: string;
};
rabbitmq?: {
enabled?: boolean;
events?: string[];
exchange?: string;
};
nats?: {
enabled?: boolean;
events?: string[];
subject?: string;
};
pusher?: {
enabled?: boolean;
appId?: string;
key?: string;
secret?: string;
cluster?: string;
useTLS?: boolean;
events?: string[];
};
}
```
## Event Router Pattern
### Event Router Structure
```typescript
import { NatsRouter } from '@api/integrations/event/nats/nats.router';
import { PusherRouter } from '@api/integrations/event/pusher/pusher.router';
import { RabbitmqRouter } from '@api/integrations/event/rabbitmq/rabbitmq.router';
import { SqsRouter } from '@api/integrations/event/sqs/sqs.router';
import { WebhookRouter } from '@api/integrations/event/webhook/webhook.router';
import { WebsocketRouter } from '@api/integrations/event/websocket/websocket.router';
import { Router } from 'express';
export class EventRouter {
public readonly router: Router;
constructor(configService: any, ...guards: any[]) {
this.router = Router();
this.router.use('/webhook', new WebhookRouter(configService, ...guards).router);
this.router.use('/websocket', new WebsocketRouter(...guards).router);
this.router.use('/rabbitmq', new RabbitmqRouter(...guards).router);
this.router.use('/nats', new NatsRouter(...guards).router);
this.router.use('/pusher', new PusherRouter(...guards).router);
this.router.use('/sqs', new SqsRouter(...guards).router);
}
}
```
## Event Validation Schema
### Event Configuration Validation
```typescript
import Joi from 'joi';
import { EventController } from '@api/integrations/event/event.controller';
const eventListSchema = Joi.array().items(
Joi.string().valid(...EventController.events)
).optional();
export const webhookSchema = Joi.object({
enabled: Joi.boolean().required(),
url: Joi.string().when('enabled', {
is: true,
then: Joi.required().uri({ scheme: ['http', 'https'] }),
otherwise: Joi.optional(),
}),
events: eventListSchema,
headers: Joi.object().pattern(Joi.string(), Joi.string()).optional(),
byEvents: Joi.boolean().optional().default(false),
base64: Joi.boolean().optional().default(false),
}).required();
export const websocketSchema = Joi.object({
enabled: Joi.boolean().required(),
events: eventListSchema,
}).required();
export const rabbitmqSchema = Joi.object({
enabled: Joi.boolean().required(),
events: eventListSchema,
exchange: Joi.string().optional().default('evolution-events'),
}).required();
export const sqsSchema = Joi.object({
enabled: Joi.boolean().required(),
events: eventListSchema,
queueUrl: Joi.string().when('enabled', {
is: true,
then: Joi.required().uri(),
otherwise: Joi.optional(),
}),
}).required();
export const eventSchema = Joi.object({
webhook: webhookSchema.optional(),
websocket: websocketSchema.optional(),
rabbitmq: rabbitmqSchema.optional(),
sqs: sqsSchema.optional(),
nats: Joi.object({
enabled: Joi.boolean().required(),
events: eventListSchema,
subject: Joi.string().optional().default('evolution.events'),
}).optional(),
pusher: Joi.object({
enabled: Joi.boolean().required(),
appId: Joi.string().when('enabled', { is: true, then: Joi.required() }),
key: Joi.string().when('enabled', { is: true, then: Joi.required() }),
secret: Joi.string().when('enabled', { is: true, then: Joi.required() }),
cluster: Joi.string().when('enabled', { is: true, then: Joi.required() }),
useTLS: Joi.boolean().optional().default(true),
events: eventListSchema,
}).optional(),
}).min(1).required();
```
## Event Testing Pattern
### Event Controller Testing
```typescript
describe('WebhookController', () => {
let controller: WebhookController;
let prismaRepository: jest.Mocked<PrismaRepository>;
let configService: jest.Mocked<ConfigService>;
beforeEach(() => {
controller = new WebhookController(prismaRepository, configService);
});
describe('emit', () => {
it('should send webhook when enabled', async () => {
const mockInstance = {
webhook: {
enabled: true,
url: 'https://example.com/webhook',
events: ['MESSAGES_UPSERT'],
},
};
prismaRepository.instance.findUnique.mockResolvedValue(mockInstance);
jest.spyOn(axios, 'post').mockResolvedValue({ status: 200 });
const eventData = {
instanceName: 'test-instance',
event: 'MESSAGES_UPSERT',
data: { message: 'test' },
origin: 'test',
serverUrl: 'http://localhost',
dateTime: new Date().toISOString(),
sender: 'test',
};
await controller.emit(eventData);
expect(axios.post).toHaveBeenCalledWith(
'https://example.com/webhook',
expect.objectContaining({
event: 'MESSAGES_UPSERT',
instance: 'test-instance',
}),
expect.objectContaining({
headers: expect.objectContaining({
'Content-Type': 'application/json',
}),
})
);
});
});
});
```

View File

@@ -0,0 +1,608 @@
---
description: Storage integration patterns for Evolution API
globs:
- "src/api/integrations/storage/**/*.ts"
alwaysApply: false
---
# Evolution API Storage Integration Rules
## Storage Service Pattern
### Base Storage Service Structure
```typescript
import { InstanceDto } from '@api/dto/instance.dto';
import { PrismaRepository } from '@api/repository/repository.service';
import { Logger } from '@config/logger.config';
import { BadRequestException } from '@exceptions';
export class StorageService {
constructor(private readonly prismaRepository: PrismaRepository) {}
private readonly logger = new Logger('StorageService');
public async getMedia(instance: InstanceDto, query?: MediaDto) {
try {
const where: any = {
instanceId: instance.instanceId,
...query,
};
const media = await this.prismaRepository.media.findMany({
where,
select: {
id: true,
fileName: true,
type: true,
mimetype: true,
createdAt: true,
Message: true,
},
});
if (!media || media.length === 0) {
throw 'Media not found';
}
return media;
} catch (error) {
throw new BadRequestException(error);
}
}
public async getMediaUrl(instance: InstanceDto, data: MediaDto) {
const media = (await this.getMedia(instance, { id: data.id }))[0];
const mediaUrl = await this.generateUrl(media.fileName, data.expiry);
return {
mediaUrl,
...media,
};
}
protected abstract generateUrl(fileName: string, expiry?: number): Promise<string>;
}
```
## S3/MinIO Integration Pattern
### MinIO Client Setup
```typescript
import { ConfigService, S3 } from '@config/env.config';
import { Logger } from '@config/logger.config';
import { BadRequestException } from '@exceptions';
import * as MinIo from 'minio';
import { join } from 'path';
import { Readable, Transform } from 'stream';
const logger = new Logger('S3 Service');
const BUCKET = new ConfigService().get<S3>('S3');
interface Metadata extends MinIo.ItemBucketMetadata {
instanceId: string;
messageId?: string;
}
const minioClient = (() => {
if (BUCKET?.ENABLE) {
return new MinIo.Client({
endPoint: BUCKET.ENDPOINT,
port: BUCKET.PORT,
useSSL: BUCKET.USE_SSL,
accessKey: BUCKET.ACCESS_KEY,
secretKey: BUCKET.SECRET_KEY,
region: BUCKET.REGION,
});
}
})();
const bucketName = process.env.S3_BUCKET;
```
### Bucket Management Functions
```typescript
const bucketExists = async (): Promise<boolean> => {
if (minioClient) {
try {
const list = await minioClient.listBuckets();
return !!list.find((bucket) => bucket.name === bucketName);
} catch (error) {
logger.error('Error checking bucket existence:', error);
return false;
}
}
return false;
};
const setBucketPolicy = async (): Promise<void> => {
if (minioClient && bucketName) {
try {
const policy = {
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Principal: { AWS: ['*'] },
Action: ['s3:GetObject'],
Resource: [`arn:aws:s3:::${bucketName}/*`],
},
],
};
await minioClient.setBucketPolicy(bucketName, JSON.stringify(policy));
logger.log('Bucket policy set successfully');
} catch (error) {
logger.error('Error setting bucket policy:', error);
}
}
};
const createBucket = async (): Promise<void> => {
if (minioClient && bucketName) {
try {
const exists = await bucketExists();
if (!exists) {
await minioClient.makeBucket(bucketName, BUCKET.REGION || 'us-east-1');
await setBucketPolicy();
logger.log(`Bucket ${bucketName} created successfully`);
}
} catch (error) {
logger.error('Error creating bucket:', error);
}
}
};
```
### File Upload Functions
```typescript
export const uploadFile = async (
fileName: string,
buffer: Buffer,
mimetype: string,
metadata?: Metadata,
): Promise<string> => {
if (!minioClient || !bucketName) {
throw new BadRequestException('S3 storage not configured');
}
try {
await createBucket();
const uploadMetadata = {
'Content-Type': mimetype,
...metadata,
};
await minioClient.putObject(bucketName, fileName, buffer, buffer.length, uploadMetadata);
logger.log(`File ${fileName} uploaded successfully`);
return fileName;
} catch (error) {
logger.error(`Error uploading file ${fileName}:`, error);
throw new BadRequestException(`Failed to upload file: ${error.message}`);
}
};
export const uploadStream = async (
fileName: string,
stream: Readable,
size: number,
mimetype: string,
metadata?: Metadata,
): Promise<string> => {
if (!minioClient || !bucketName) {
throw new BadRequestException('S3 storage not configured');
}
try {
await createBucket();
const uploadMetadata = {
'Content-Type': mimetype,
...metadata,
};
await minioClient.putObject(bucketName, fileName, stream, size, uploadMetadata);
logger.log(`Stream ${fileName} uploaded successfully`);
return fileName;
} catch (error) {
logger.error(`Error uploading stream ${fileName}:`, error);
throw new BadRequestException(`Failed to upload stream: ${error.message}`);
}
};
```
### File Download Functions
```typescript
export const getObject = async (fileName: string): Promise<Buffer> => {
if (!minioClient || !bucketName) {
throw new BadRequestException('S3 storage not configured');
}
try {
const stream = await minioClient.getObject(bucketName, fileName);
const chunks: Buffer[] = [];
return new Promise((resolve, reject) => {
stream.on('data', (chunk) => chunks.push(chunk));
stream.on('end', () => resolve(Buffer.concat(chunks)));
stream.on('error', reject);
});
} catch (error) {
logger.error(`Error getting object ${fileName}:`, error);
throw new BadRequestException(`Failed to get object: ${error.message}`);
}
};
export const getObjectUrl = async (fileName: string, expiry: number = 3600): Promise<string> => {
if (!minioClient || !bucketName) {
throw new BadRequestException('S3 storage not configured');
}
try {
const url = await minioClient.presignedGetObject(bucketName, fileName, expiry);
logger.log(`Generated URL for ${fileName} with expiry ${expiry}s`);
return url;
} catch (error) {
logger.error(`Error generating URL for ${fileName}:`, error);
throw new BadRequestException(`Failed to generate URL: ${error.message}`);
}
};
export const getObjectStream = async (fileName: string): Promise<Readable> => {
if (!minioClient || !bucketName) {
throw new BadRequestException('S3 storage not configured');
}
try {
const stream = await minioClient.getObject(bucketName, fileName);
return stream;
} catch (error) {
logger.error(`Error getting object stream ${fileName}:`, error);
throw new BadRequestException(`Failed to get object stream: ${error.message}`);
}
};
```
### File Management Functions
```typescript
export const deleteObject = async (fileName: string): Promise<void> => {
if (!minioClient || !bucketName) {
throw new BadRequestException('S3 storage not configured');
}
try {
await minioClient.removeObject(bucketName, fileName);
logger.log(`File ${fileName} deleted successfully`);
} catch (error) {
logger.error(`Error deleting file ${fileName}:`, error);
throw new BadRequestException(`Failed to delete file: ${error.message}`);
}
};
export const listObjects = async (prefix?: string): Promise<MinIo.BucketItem[]> => {
if (!minioClient || !bucketName) {
throw new BadRequestException('S3 storage not configured');
}
try {
const objects: MinIo.BucketItem[] = [];
const stream = minioClient.listObjects(bucketName, prefix, true);
return new Promise((resolve, reject) => {
stream.on('data', (obj) => objects.push(obj));
stream.on('end', () => resolve(objects));
stream.on('error', reject);
});
} catch (error) {
logger.error('Error listing objects:', error);
throw new BadRequestException(`Failed to list objects: ${error.message}`);
}
};
export const objectExists = async (fileName: string): Promise<boolean> => {
if (!minioClient || !bucketName) {
return false;
}
try {
await minioClient.statObject(bucketName, fileName);
return true;
} catch (error) {
return false;
}
};
```
## Storage Controller Pattern
### S3 Controller Implementation
```typescript
import { InstanceDto } from '@api/dto/instance.dto';
import { MediaDto } from '@api/integrations/storage/s3/dto/media.dto';
import { S3Service } from '@api/integrations/storage/s3/services/s3.service';
export class S3Controller {
constructor(private readonly s3Service: S3Service) {}
public async getMedia(instance: InstanceDto, data: MediaDto) {
return this.s3Service.getMedia(instance, data);
}
public async getMediaUrl(instance: InstanceDto, data: MediaDto) {
return this.s3Service.getMediaUrl(instance, data);
}
public async uploadMedia(instance: InstanceDto, data: UploadMediaDto) {
return this.s3Service.uploadMedia(instance, data);
}
public async deleteMedia(instance: InstanceDto, data: MediaDto) {
return this.s3Service.deleteMedia(instance, data);
}
}
```
## Storage Router Pattern
### Storage Router Structure
```typescript
import { S3Router } from '@api/integrations/storage/s3/routes/s3.router';
import { Router } from 'express';
export class StorageRouter {
public readonly router: Router;
constructor(...guards: any[]) {
this.router = Router();
this.router.use('/s3', new S3Router(...guards).router);
// Add other storage providers here
// this.router.use('/gcs', new GCSRouter(...guards).router);
// this.router.use('/azure', new AzureRouter(...guards).router);
}
}
```
### S3 Specific Router
```typescript
import { RouterBroker } from '@api/abstract/abstract.router';
import { MediaDto } from '@api/integrations/storage/s3/dto/media.dto';
import { s3Schema, s3UrlSchema } from '@api/integrations/storage/s3/validate/s3.schema';
import { HttpStatus } from '@api/routes/index.router';
import { s3Controller } from '@api/server.module';
import { RequestHandler, Router } from 'express';
export class S3Router extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.post(this.routerPath('getMedia'), ...guards, async (req, res) => {
const response = await this.dataValidate<MediaDto>({
request: req,
schema: s3Schema,
ClassRef: MediaDto,
execute: (instance, data) => s3Controller.getMedia(instance, data),
});
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('getMediaUrl'), ...guards, async (req, res) => {
const response = await this.dataValidate<MediaDto>({
request: req,
schema: s3UrlSchema,
ClassRef: MediaDto,
execute: (instance, data) => s3Controller.getMediaUrl(instance, data),
});
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('uploadMedia'), ...guards, async (req, res) => {
const response = await this.dataValidate<UploadMediaDto>({
request: req,
schema: uploadSchema,
ClassRef: UploadMediaDto,
execute: (instance, data) => s3Controller.uploadMedia(instance, data),
});
res.status(HttpStatus.CREATED).json(response);
})
.delete(this.routerPath('deleteMedia'), ...guards, async (req, res) => {
const response = await this.dataValidate<MediaDto>({
request: req,
schema: s3Schema,
ClassRef: MediaDto,
execute: (instance, data) => s3Controller.deleteMedia(instance, data),
});
res.status(HttpStatus.OK).json(response);
});
}
public readonly router: Router = Router();
}
```
## Storage DTO Pattern
### Media DTO
```typescript
export class MediaDto {
id?: string;
fileName?: string;
type?: string;
mimetype?: string;
expiry?: number;
}
export class UploadMediaDto {
fileName: string;
mimetype: string;
buffer?: Buffer;
base64?: string;
url?: string;
metadata?: {
instanceId: string;
messageId?: string;
contactId?: string;
[key: string]: any;
};
}
```
## Storage Validation Schema
### S3 Validation Schemas
```typescript
import Joi from 'joi';
export const s3Schema = Joi.object({
id: Joi.string().optional(),
fileName: Joi.string().optional(),
type: Joi.string().optional().valid('image', 'video', 'audio', 'document'),
mimetype: Joi.string().optional(),
expiry: Joi.number().optional().min(60).max(604800).default(3600), // 1 min to 7 days
}).min(1).required();
export const s3UrlSchema = Joi.object({
id: Joi.string().required(),
expiry: Joi.number().optional().min(60).max(604800).default(3600),
}).required();
export const uploadSchema = Joi.object({
fileName: Joi.string().required().max(255),
mimetype: Joi.string().required(),
buffer: Joi.binary().optional(),
base64: Joi.string().base64().optional(),
url: Joi.string().uri().optional(),
metadata: Joi.object({
instanceId: Joi.string().required(),
messageId: Joi.string().optional(),
contactId: Joi.string().optional(),
}).optional(),
}).xor('buffer', 'base64', 'url').required(); // Exactly one of these must be present
```
## Error Handling in Storage
### Storage-Specific Error Handling
```typescript
// CORRECT - Storage-specific error handling
public async uploadFile(fileName: string, buffer: Buffer): Promise<string> {
try {
const result = await this.storageClient.upload(fileName, buffer);
return result;
} catch (error) {
this.logger.error(`Storage upload failed: ${error.message}`);
if (error.code === 'NoSuchBucket') {
throw new BadRequestException('Storage bucket not found');
}
if (error.code === 'AccessDenied') {
throw new UnauthorizedException('Storage access denied');
}
if (error.code === 'EntityTooLarge') {
throw new BadRequestException('File too large');
}
throw new InternalServerErrorException('Storage operation failed');
}
}
```
## Storage Configuration Pattern
### Environment Configuration
```typescript
export interface S3Config {
ENABLE: boolean;
ENDPOINT: string;
PORT: number;
USE_SSL: boolean;
ACCESS_KEY: string;
SECRET_KEY: string;
REGION: string;
BUCKET: string;
}
// Usage in service
const s3Config = this.configService.get<S3Config>('S3');
if (!s3Config.ENABLE) {
throw new BadRequestException('S3 storage is disabled');
}
```
## Storage Testing Pattern
### Storage Service Testing
```typescript
describe('S3Service', () => {
let service: S3Service;
let prismaRepository: jest.Mocked<PrismaRepository>;
beforeEach(() => {
service = new S3Service(prismaRepository);
});
describe('getMedia', () => {
it('should return media list', async () => {
const instance = { instanceId: 'test-instance' };
const mockMedia = [
{ id: '1', fileName: 'test.jpg', type: 'image', mimetype: 'image/jpeg' },
];
prismaRepository.media.findMany.mockResolvedValue(mockMedia);
const result = await service.getMedia(instance);
expect(result).toEqual(mockMedia);
expect(prismaRepository.media.findMany).toHaveBeenCalledWith({
where: { instanceId: 'test-instance' },
select: expect.objectContaining({
id: true,
fileName: true,
type: true,
mimetype: true,
}),
});
});
it('should throw error when no media found', async () => {
const instance = { instanceId: 'test-instance' };
prismaRepository.media.findMany.mockResolvedValue([]);
await expect(service.getMedia(instance)).rejects.toThrow(BadRequestException);
});
});
});
```
## Storage Performance Considerations
### Efficient File Handling
```typescript
// CORRECT - Stream-based upload for large files
public async uploadLargeFile(fileName: string, stream: Readable, size: number): Promise<string> {
const uploadStream = new Transform({
transform(chunk, encoding, callback) {
// Optional: Add compression, encryption, etc.
callback(null, chunk);
},
});
return new Promise((resolve, reject) => {
stream
.pipe(uploadStream)
.on('error', reject)
.on('finish', () => resolve(fileName));
});
}
// INCORRECT - Loading entire file into memory
public async uploadLargeFile(fileName: string, filePath: string): Promise<string> {
const buffer = fs.readFileSync(filePath); // ❌ Memory intensive for large files
return await this.uploadFile(fileName, buffer);
}
```

View File

@@ -0,0 +1,416 @@
---
description: Router patterns for Evolution API
globs:
- "src/api/routes/**/*.ts"
alwaysApply: false
---
# Evolution API Route Rules
## Router Base Pattern
### RouterBroker Extension
```typescript
import { RouterBroker } from '@api/abstract/abstract.router';
import { RequestHandler, Router } from 'express';
import { HttpStatus } from './index.router';
export class ExampleRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.get(this.routerPath('findExample'), ...guards, async (req, res) => {
const response = await this.dataValidate<ExampleDto>({
request: req,
schema: null,
ClassRef: ExampleDto,
execute: (instance) => exampleController.find(instance),
});
return res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('createExample'), ...guards, async (req, res) => {
const response = await this.dataValidate<ExampleDto>({
request: req,
schema: exampleSchema,
ClassRef: ExampleDto,
execute: (instance, data) => exampleController.create(instance, data),
});
return res.status(HttpStatus.CREATED).json(response);
});
}
public readonly router: Router = Router();
}
```
## Main Router Pattern
### Index Router Structure
```typescript
import { Router } from 'express';
import { authGuard } from '@api/guards/auth.guard';
import { instanceExistsGuard, instanceLoggedGuard } from '@api/guards/instance.guard';
import Telemetry from '@api/guards/telemetry.guard';
enum HttpStatus {
OK = 200,
CREATED = 201,
NOT_FOUND = 404,
FORBIDDEN = 403,
BAD_REQUEST = 400,
UNAUTHORIZED = 401,
INTERNAL_SERVER_ERROR = 500,
}
const router: Router = Router();
const guards = [instanceExistsGuard, instanceLoggedGuard, authGuard['apikey']];
const telemetry = new Telemetry();
router
.use((req, res, next) => telemetry.collectTelemetry(req, res, next))
.get('/', async (req, res) => {
res.status(HttpStatus.OK).json({
status: HttpStatus.OK,
message: 'Welcome to the Evolution API, it is working!',
version: packageJson.version,
clientName: process.env.DATABASE_CONNECTION_CLIENT_NAME,
manager: !serverConfig.DISABLE_MANAGER ? `${req.protocol}://${req.get('host')}/manager` : undefined,
documentation: `https://doc.evolution-api.com`,
whatsappWebVersion: (await fetchLatestWaWebVersion({})).version.join('.'),
});
})
.use('/instance', new InstanceRouter(configService, ...guards).router)
.use('/message', new MessageRouter(...guards).router)
.use('/chat', new ChatRouter(...guards).router)
.use('/business', new BusinessRouter(...guards).router);
export { HttpStatus, router };
```
## Data Validation Pattern
### RouterBroker dataValidate Usage
```typescript
// CORRECT - Standard validation pattern
.post(this.routerPath('createTemplate'), ...guards, async (req, res) => {
const response = await this.dataValidate<TemplateDto>({
request: req,
schema: templateSchema,
ClassRef: TemplateDto,
execute: (instance, data) => templateController.create(instance, data),
});
return res.status(HttpStatus.CREATED).json(response);
})
// CORRECT - No schema validation (for simple DTOs)
.get(this.routerPath('findTemplate'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: null,
ClassRef: InstanceDto,
execute: (instance) => templateController.find(instance),
});
return res.status(HttpStatus.OK).json(response);
})
```
## Error Handling in Routes
### Try-Catch Pattern
```typescript
// CORRECT - Error handling with utility function
.post(this.routerPath('getCatalog'), ...guards, async (req, res) => {
try {
const response = await this.dataValidate<NumberDto>({
request: req,
schema: catalogSchema,
ClassRef: NumberDto,
execute: (instance, data) => businessController.fetchCatalog(instance, data),
});
return res.status(HttpStatus.OK).json(response);
} catch (error) {
// Log error for debugging
console.error('Business catalog error:', error);
// Use utility function to create standardized error response
const errorResponse = createMetaErrorResponse(error, 'business_catalog');
return res.status(errorResponse.status).json(errorResponse);
}
})
// INCORRECT - Let RouterBroker handle errors (when possible)
.post(this.routerPath('simpleOperation'), ...guards, async (req, res) => {
try {
const response = await this.dataValidate<SimpleDto>({
request: req,
schema: simpleSchema,
ClassRef: SimpleDto,
execute: (instance, data) => controller.simpleOperation(instance, data),
});
return res.status(HttpStatus.OK).json(response);
} catch (error) {
throw error; // ❌ Unnecessary - RouterBroker handles this
}
})
```
## Route Path Pattern
### routerPath Usage
```typescript
// CORRECT - Use routerPath for consistent naming
.get(this.routerPath('findLabels'), ...guards, async (req, res) => {
// Implementation
})
.post(this.routerPath('handleLabel'), ...guards, async (req, res) => {
// Implementation
})
// INCORRECT - Hardcoded paths
.get('/labels', ...guards, async (req, res) => { // ❌ Use routerPath
// Implementation
})
```
## Guard Application Pattern
### Guards Usage
```typescript
// CORRECT - Apply guards to protected routes
export class ProtectedRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.get(this.routerPath('protectedAction'), ...guards, async (req, res) => {
// Protected action
})
.post(this.routerPath('anotherAction'), ...guards, async (req, res) => {
// Another protected action
});
}
}
// CORRECT - No guards for public routes
export class PublicRouter extends RouterBroker {
constructor() {
super();
this.router
.get('/health', async (req, res) => {
res.status(HttpStatus.OK).json({ status: 'healthy' });
})
.get('/version', async (req, res) => {
res.status(HttpStatus.OK).json({ version: packageJson.version });
});
}
}
```
## Static File Serving Pattern
### Static Assets Route
```typescript
// CORRECT - Secure static file serving
router.get('/assets/*', (req, res) => {
const fileName = req.params[0];
// Security: Reject paths containing traversal patterns
if (!fileName || fileName.includes('..') || fileName.includes('\\') || path.isAbsolute(fileName)) {
return res.status(403).send('Forbidden');
}
const basePath = path.join(process.cwd(), 'manager', 'dist');
const assetsPath = path.join(basePath, 'assets');
const filePath = path.join(assetsPath, fileName);
// Security: Ensure the resolved path is within the assets directory
const resolvedPath = path.resolve(filePath);
const resolvedAssetsPath = path.resolve(assetsPath);
if (!resolvedPath.startsWith(resolvedAssetsPath + path.sep) && resolvedPath !== resolvedAssetsPath) {
return res.status(403).send('Forbidden');
}
if (fs.existsSync(resolvedPath)) {
res.set('Content-Type', mimeTypes.lookup(resolvedPath) || 'text/css');
res.send(fs.readFileSync(resolvedPath));
} else {
res.status(404).send('File not found');
}
});
```
## Special Route Patterns
### Manager Route Pattern
```typescript
export class ViewsRouter extends RouterBroker {
public readonly router: Router;
constructor() {
super();
this.router = Router();
const basePath = path.join(process.cwd(), 'manager', 'dist');
const indexPath = path.join(basePath, 'index.html');
this.router.use(express.static(basePath));
this.router.get('*', (req, res) => {
res.sendFile(indexPath);
});
}
}
```
### Webhook Route Pattern
```typescript
// CORRECT - Webhook without guards
.post('/webhook/evolution', async (req, res) => {
const response = await evolutionController.receiveWebhook(req.body);
return res.status(HttpStatus.OK).json(response);
})
// CORRECT - Webhook with signature validation
.post('/webhook/meta', validateWebhookSignature, async (req, res) => {
const response = await metaController.receiveWebhook(req.body);
return res.status(HttpStatus.OK).json(response);
})
```
## Response Pattern
### Standard Response Format
```typescript
// CORRECT - Standard success response
return res.status(HttpStatus.OK).json(response);
// CORRECT - Created response
return res.status(HttpStatus.CREATED).json(response);
// CORRECT - Custom response with additional data
return res.status(HttpStatus.OK).json({
...response,
timestamp: new Date().toISOString(),
instanceName: req.params.instanceName,
});
```
## Route Organization
### File Structure
```
src/api/routes/
├── index.router.ts # Main router with all route registrations
├── instance.router.ts # Instance management routes
├── sendMessage.router.ts # Message sending routes
├── chat.router.ts # Chat operations routes
├── business.router.ts # Business API routes
├── group.router.ts # Group management routes
├── label.router.ts # Label management routes
├── proxy.router.ts # Proxy configuration routes
├── settings.router.ts # Instance settings routes
├── template.router.ts # Template management routes
├── call.router.ts # Call operations routes
└── view.router.ts # Frontend views routes
```
## Route Testing Pattern
### Router Testing
```typescript
describe('ExampleRouter', () => {
let app: express.Application;
let router: ExampleRouter;
beforeEach(() => {
app = express();
router = new ExampleRouter();
app.use('/api', router.router);
app.use(express.json());
});
describe('GET /findExample', () => {
it('should return example data', async () => {
const response = await request(app)
.get('/api/findExample/test-instance')
.set('apikey', 'test-key')
.expect(200);
expect(response.body).toBeDefined();
expect(response.body.instanceName).toBe('test-instance');
});
it('should return 401 without API key', async () => {
await request(app)
.get('/api/findExample/test-instance')
.expect(401);
});
});
describe('POST /createExample', () => {
it('should create example successfully', async () => {
const data = {
name: 'Test Example',
description: 'Test Description',
};
const response = await request(app)
.post('/api/createExample/test-instance')
.set('apikey', 'test-key')
.send(data)
.expect(201);
expect(response.body.name).toBe(data.name);
});
it('should validate required fields', async () => {
const data = {
description: 'Test Description',
// Missing required 'name' field
};
await request(app)
.post('/api/createExample/test-instance')
.set('apikey', 'test-key')
.send(data)
.expect(400);
});
});
});
```
## Route Documentation
### JSDoc for Routes
```typescript
/**
* @route GET /api/template/findTemplate/:instanceName
* @description Find template for instance
* @param {string} instanceName - Instance name
* @returns {TemplateDto} Template data
* @throws {404} Template not found
* @throws {401} Unauthorized
*/
.get(this.routerPath('findTemplate'), ...guards, async (req, res) => {
// Implementation
})
/**
* @route POST /api/template/createTemplate/:instanceName
* @description Create new template
* @param {string} instanceName - Instance name
* @body {TemplateDto} Template data
* @returns {TemplateDto} Created template
* @throws {400} Validation error
* @throws {401} Unauthorized
*/
.post(this.routerPath('createTemplate'), ...guards, async (req, res) => {
// Implementation
})
```

View File

@@ -0,0 +1,294 @@
---
description: Service layer patterns for Evolution API
globs:
- "src/api/services/**/*.ts"
- "src/api/integrations/**/services/*.ts"
alwaysApply: false
---
# Evolution API Service Rules
## Service Structure Pattern
### Standard Service Class
```typescript
export class ExampleService {
constructor(private readonly waMonitor: WAMonitoringService) {}
private readonly logger = new Logger('ExampleService');
public async create(instance: InstanceDto, data: ExampleDto) {
await this.waMonitor.waInstances[instance.instanceName].setData(data);
return { example: { ...instance, data } };
}
public async find(instance: InstanceDto): Promise<ExampleDto> {
try {
const result = await this.waMonitor.waInstances[instance.instanceName].findData();
if (Object.keys(result).length === 0) {
throw new Error('Data not found');
}
return result;
} catch (error) {
return null; // Evolution pattern - return null on error
}
}
}
```
## Dependency Injection Pattern
### Constructor Pattern
```typescript
// CORRECT - Evolution API pattern
constructor(
private readonly waMonitor: WAMonitoringService,
private readonly prismaRepository: PrismaRepository,
private readonly configService: ConfigService,
) {}
// INCORRECT - Don't use
constructor(waMonitor, prismaRepository, configService) {} // ❌ No types
```
## Logger Pattern
### Standard Logger Usage
```typescript
// CORRECT - Evolution API pattern
private readonly logger = new Logger('ServiceName');
// Usage
this.logger.log('Operation started');
this.logger.error('Operation failed', error);
// INCORRECT
console.log('Operation started'); // ❌ Use Logger
```
## WAMonitor Integration Pattern
### Instance Access Pattern
```typescript
// CORRECT - Standard pattern
public async operation(instance: InstanceDto, data: DataDto) {
await this.waMonitor.waInstances[instance.instanceName].performAction(data);
return { result: { ...instance, data } };
}
// Instance validation
const waInstance = this.waMonitor.waInstances[instance.instanceName];
if (!waInstance) {
throw new NotFoundException('Instance not found');
}
```
## Error Handling Pattern
### Try-Catch Pattern
```typescript
// CORRECT - Evolution API pattern
public async find(instance: InstanceDto): Promise<DataDto> {
try {
const result = await this.waMonitor.waInstances[instance.instanceName].findData();
if (Object.keys(result).length === 0) {
throw new Error('Data not found');
}
return result;
} catch (error) {
this.logger.error('Find operation failed', error);
return null; // Return null on error (Evolution pattern)
}
}
```
## Cache Integration Pattern
### Cache Service Usage
```typescript
export class CacheAwareService {
constructor(
private readonly cache: CacheService,
private readonly chatwootCache: CacheService,
private readonly baileysCache: CacheService,
) {}
public async getCachedData(key: string): Promise<any> {
const cached = await this.cache.get(key);
if (cached) return cached;
const data = await this.fetchFromSource(key);
await this.cache.set(key, data, 300); // 5 min TTL
return data;
}
}
```
## Integration Service Patterns
### Chatbot Service Base Pattern
```typescript
export class ChatbotService extends BaseChatbotService<BotType, SettingsType> {
constructor(
waMonitor: WAMonitoringService,
prismaRepository: PrismaRepository,
configService: ConfigService,
) {
super(waMonitor, prismaRepository, 'ChatbotService', configService);
}
protected async processBot(
waInstance: any,
remoteJid: string,
bot: BotType,
session: any,
settings: any,
content: string,
): Promise<void> {
// Implementation
}
}
```
### Channel Service Pattern
```typescript
export class ChannelService extends ChannelStartupService {
constructor(
configService: ConfigService,
eventEmitter: EventEmitter2,
prismaRepository: PrismaRepository,
cache: CacheService,
chatwootCache: CacheService,
baileysCache: CacheService,
) {
super(configService, eventEmitter, prismaRepository, cache, chatwootCache, baileysCache);
}
public readonly logger = new Logger('ChannelService');
public client: WASocket;
public readonly instance: wa.Instance = {};
}
```
## Service Initialization Pattern
### Service Registration
```typescript
// In server.module.ts pattern
export const templateService = new TemplateService(
waMonitor,
prismaRepository,
configService,
);
export const settingsService = new SettingsService(waMonitor);
```
## Async Operation Patterns
### Promise Handling
```typescript
// CORRECT - Evolution API pattern
public async sendMessage(instance: InstanceDto, data: MessageDto) {
const waInstance = this.waMonitor.waInstances[instance.instanceName];
return await waInstance.sendMessage(data);
}
// INCORRECT - Don't use .then()
public sendMessage(instance: InstanceDto, data: MessageDto) {
return this.waMonitor.waInstances[instance.instanceName]
.sendMessage(data)
.then(result => result); // ❌ Use async/await
}
```
## Configuration Access Pattern
### Config Service Usage
```typescript
// CORRECT - Evolution API pattern
const serverConfig = this.configService.get<HttpServer>('SERVER');
const authConfig = this.configService.get<Auth>('AUTHENTICATION');
const dbConfig = this.configService.get<Database>('DATABASE');
// Type-safe configuration access
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED) {
// Chatwoot logic
}
```
## Event Emission Pattern
### EventEmitter2 Usage
```typescript
// CORRECT - Evolution API pattern
this.eventEmitter.emit(Events.INSTANCE_CREATE, {
instanceName: instance.name,
status: 'created',
});
// Chatwoot event pattern
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {
this.chatwootService.eventWhatsapp(
Events.STATUS_INSTANCE,
{ instanceName: this.instance.name },
{
instance: this.instance.name,
status: 'created',
},
);
}
```
## Service Method Naming
### Standard Method Names
- `create()` - Create new resource
- `find()` - Find single resource
- `findAll()` - Find multiple resources
- `update()` - Update resource
- `delete()` - Delete resource
- `fetch*()` - Fetch from external API
- `send*()` - Send data/messages
- `process*()` - Process data
## Service Testing Pattern
### Unit Test Structure
```typescript
describe('ExampleService', () => {
let service: ExampleService;
let waMonitor: jest.Mocked<WAMonitoringService>;
let prismaRepository: jest.Mocked<PrismaRepository>;
beforeEach(() => {
const mockWaMonitor = {
waInstances: {
'test-instance': {
performAction: jest.fn(),
},
},
};
service = new ExampleService(
mockWaMonitor as any,
prismaRepository,
configService,
);
});
it('should perform action successfully', async () => {
const instance = { instanceName: 'test-instance' };
const data = { test: 'data' };
const result = await service.create(instance, data);
expect(result).toBeDefined();
expect(waMonitor.waInstances['test-instance'].performAction).toHaveBeenCalledWith(data);
});
});
```

View File

@@ -0,0 +1,490 @@
---
description: Type definitions and interfaces for Evolution API
globs:
- "src/api/types/**/*.ts"
- "src/@types/**/*.ts"
alwaysApply: false
---
# Evolution API Type Rules
## Namespace Pattern
### WhatsApp Types Namespace
```typescript
/* eslint-disable @typescript-eslint/no-namespace */
import { JsonValue } from '@prisma/client/runtime/library';
import { AuthenticationState, WAConnectionState } from 'baileys';
export declare namespace wa {
export type QrCode = {
count?: number;
pairingCode?: string;
base64?: string;
code?: string;
};
export type Instance = {
id?: string;
qrcode?: QrCode;
pairingCode?: string;
authState?: { state: AuthenticationState; saveCreds: () => void };
name?: string;
wuid?: string;
profileName?: string;
profilePictureUrl?: string;
token?: string;
number?: string;
integration?: string;
businessId?: string;
};
export type LocalChatwoot = {
enabled?: boolean;
accountId?: string;
token?: string;
url?: string;
nameInbox?: string;
mergeBrazilContacts?: boolean;
importContacts?: boolean;
importMessages?: boolean;
daysLimitImportMessages?: number;
organization?: string;
logo?: string;
};
export type LocalProxy = {
enabled?: boolean;
host?: string;
port?: string;
protocol?: string;
username?: string;
password?: string;
};
export type LocalSettings = {
rejectCall?: boolean;
msgCall?: string;
groupsIgnore?: boolean;
alwaysOnline?: boolean;
readMessages?: boolean;
readStatus?: boolean;
syncFullHistory?: boolean;
};
export type LocalWebHook = {
enabled?: boolean;
url?: string;
events?: string[];
headers?: JsonValue;
byEvents?: boolean;
base64?: boolean;
};
export type StatusMessage = 'ERROR' | 'PENDING' | 'SERVER_ACK' | 'DELIVERY_ACK' | 'READ' | 'DELETED' | 'PLAYED';
}
```
## Enum Definitions
### Events Enum
```typescript
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',
MESSAGES_SET = 'messages.set',
MESSAGES_UPSERT = 'messages.upsert',
MESSAGES_EDITED = 'messages.edited',
MESSAGES_UPDATE = 'messages.update',
MESSAGES_DELETE = 'messages.delete',
SEND_MESSAGE = 'send.message',
SEND_MESSAGE_UPDATE = 'send.message.update',
CONTACTS_SET = 'contacts.set',
CONTACTS_UPSERT = 'contacts.upsert',
CONTACTS_UPDATE = 'contacts.update',
PRESENCE_UPDATE = 'presence.update',
CHATS_SET = 'chats.set',
CHATS_UPDATE = 'chats.update',
CHATS_UPSERT = 'chats.upsert',
CHATS_DELETE = 'chats.delete',
GROUPS_UPSERT = 'groups.upsert',
GROUPS_UPDATE = 'groups.update',
GROUP_PARTICIPANTS_UPDATE = 'group-participants.update',
CALL = 'call',
TYPEBOT_START = 'typebot.start',
TYPEBOT_CHANGE_STATUS = 'typebot.change-status',
LABELS_EDIT = 'labels.edit',
LABELS_ASSOCIATION = 'labels.association',
CREDS_UPDATE = 'creds.update',
MESSAGING_HISTORY_SET = 'messaging-history.set',
REMOVE_INSTANCE = 'remove.instance',
LOGOUT_INSTANCE = 'logout.instance',
}
```
### Integration Types
```typescript
export const Integration = {
WHATSAPP_BUSINESS: 'WHATSAPP-BUSINESS',
WHATSAPP_BAILEYS: 'WHATSAPP-BAILEYS',
EVOLUTION: 'EVOLUTION',
} as const;
export type IntegrationType = typeof Integration[keyof typeof Integration];
```
## Constant Arrays
### Message Type Constants
```typescript
export const TypeMediaMessage = [
'imageMessage',
'documentMessage',
'audioMessage',
'videoMessage',
'stickerMessage',
'ptvMessage', // Evolution API includes this
];
export const MessageSubtype = [
'ephemeralMessage',
'documentWithCaptionMessage',
'viewOnceMessage',
'viewOnceMessageV2',
];
export type MediaMessageType = typeof TypeMediaMessage[number];
export type MessageSubtypeType = typeof MessageSubtype[number];
```
## Interface Definitions
### Service Interfaces
```typescript
export interface ServiceInterface {
create(instance: InstanceDto, data: any): Promise<any>;
find(instance: InstanceDto): Promise<any>;
update?(instance: InstanceDto, data: any): Promise<any>;
delete?(instance: InstanceDto): Promise<any>;
}
export interface ChannelServiceInterface extends ServiceInterface {
sendMessage(data: SendMessageDto): Promise<any>;
connectToWhatsapp(data?: any): Promise<void>;
receiveWebhook?(data: any): Promise<void>;
}
export interface ChatbotServiceInterface extends ServiceInterface {
processMessage(
instanceName: string,
remoteJid: string,
message: any,
pushName?: string,
): Promise<void>;
}
```
## Configuration Types
### Environment Configuration Types
```typescript
export interface DatabaseConfig {
CONNECTION: {
URI: string;
DB_PREFIX_NAME: string;
CLIENT_NAME?: string;
};
ENABLED: boolean;
SAVE_DATA: {
INSTANCE: boolean;
NEW_MESSAGE: boolean;
MESSAGE_UPDATE: boolean;
CONTACTS: boolean;
CHATS: boolean;
};
}
export interface AuthConfig {
TYPE: 'apikey' | 'jwt';
API_KEY: {
KEY: string;
};
JWT?: {
EXPIRIN_IN: number;
SECRET: string;
};
}
export interface CacheConfig {
REDIS: {
ENABLED: boolean;
URI: string;
PREFIX_KEY: string;
SAVE_INSTANCES: boolean;
};
LOCAL: {
ENABLED: boolean;
TTL: number;
};
}
```
## Message Types
### Message Structure Types
```typescript
export interface MessageContent {
text?: string;
caption?: string;
media?: Buffer | string;
mediatype?: 'image' | 'video' | 'audio' | 'document' | 'sticker';
fileName?: string;
mimetype?: string;
}
export interface MessageOptions {
delay?: number;
presence?: 'unavailable' | 'available' | 'composing' | 'recording' | 'paused';
linkPreview?: boolean;
mentionsEveryOne?: boolean;
mentioned?: string[];
quoted?: {
key: {
remoteJid: string;
fromMe: boolean;
id: string;
};
message: any;
};
}
export interface SendMessageRequest {
number: string;
content: MessageContent;
options?: MessageOptions;
}
```
## Webhook Types
### Webhook Payload Types
```typescript
export interface WebhookPayload {
event: Events;
instance: string;
data: any;
timestamp: string;
server?: {
version: string;
host: string;
};
}
export interface WebhookConfig {
enabled: boolean;
url: string;
events: Events[];
headers?: Record<string, string>;
byEvents?: boolean;
base64?: boolean;
}
```
## Error Types
### Custom Error Types
```typescript
export interface ApiError {
status: number;
message: string;
error?: string;
details?: any;
timestamp: string;
path: string;
}
export interface ValidationError extends ApiError {
status: 400;
validationErrors: Array<{
field: string;
message: string;
value?: any;
}>;
}
export interface AuthenticationError extends ApiError {
status: 401;
message: 'Unauthorized' | 'Invalid API Key' | 'Token Expired';
}
```
## Utility Types
### Generic Utility Types
```typescript
export type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
export type RequiredFields<T, K extends keyof T> = T & Required<Pick<T, K>>;
export type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
export type NonEmptyArray<T> = [T, ...T[]];
export type StringKeys<T> = {
[K in keyof T]: T[K] extends string ? K : never;
}[keyof T];
```
## Response Types
### API Response Types
```typescript
export interface ApiResponse<T = any> {
success: boolean;
data?: T;
message?: string;
error?: string;
timestamp: string;
}
export interface PaginatedResponse<T> extends ApiResponse<T[]> {
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
hasNext: boolean;
hasPrev: boolean;
};
}
export interface InstanceResponse extends ApiResponse {
instance: {
instanceName: string;
status: 'connecting' | 'open' | 'close' | 'qr';
qrcode?: string;
profileName?: string;
profilePicUrl?: string;
};
}
```
## Integration-Specific Types
### Baileys Types Extension
```typescript
import { WASocket, ConnectionState, DisconnectReason } from 'baileys';
export interface BaileysInstance {
client: WASocket;
state: ConnectionState;
qrRetry: number;
authPath: string;
}
export interface BaileysConfig {
qrTimeout: number;
maxQrRetries: number;
authTimeout: number;
reconnectInterval: number;
}
```
### Business API Types
```typescript
export interface BusinessApiConfig {
version: string;
baseUrl: string;
timeout: number;
retries: number;
}
export interface BusinessApiMessage {
messaging_product: 'whatsapp';
to: string;
type: 'text' | 'image' | 'document' | 'audio' | 'video' | 'template';
text?: {
body: string;
preview_url?: boolean;
};
image?: {
link?: string;
id?: string;
caption?: string;
};
template?: {
name: string;
language: {
code: string;
};
components?: any[];
};
}
```
## Type Guards
### Type Guard Functions
```typescript
export function isMediaMessage(message: any): message is MediaMessage {
return message && TypeMediaMessage.some(type => message[type]);
}
export function isTextMessage(message: any): message is TextMessage {
return message && message.conversation;
}
export function isValidIntegration(integration: string): integration is IntegrationType {
return Object.values(Integration).includes(integration as IntegrationType);
}
export function isValidEvent(event: string): event is Events {
return Object.values(Events).includes(event as Events);
}
```
## Module Augmentation
### Express Request Extension
```typescript
declare global {
namespace Express {
interface Request {
instanceName?: string;
instanceData?: InstanceDto;
user?: {
id: string;
apiKey: string;
};
}
}
}
```
## Type Documentation
### JSDoc Type Documentation
```typescript
/**
* WhatsApp instance configuration
* @interface InstanceConfig
* @property {string} name - Unique instance name
* @property {IntegrationType} integration - Integration type
* @property {string} [token] - API token for business integrations
* @property {WebhookConfig} [webhook] - Webhook configuration
* @property {ProxyConfig} [proxy] - Proxy configuration
*/
export interface InstanceConfig {
name: string;
integration: IntegrationType;
token?: string;
webhook?: WebhookConfig;
proxy?: ProxyConfig;
}
```

View File

@@ -0,0 +1,653 @@
---
description: Utility functions and helpers for Evolution API
globs:
- "src/utils/**/*.ts"
alwaysApply: false
---
# Evolution API Utility Rules
## Utility Function Structure
### Standard Utility Pattern
```typescript
import { Logger } from '@config/logger.config';
const logger = new Logger('UtilityName');
export function utilityFunction(param: ParamType): ReturnType {
try {
// Utility logic
return result;
} catch (error) {
logger.error(`Utility function failed: ${error.message}`);
throw error;
}
}
export default utilityFunction;
```
## Authentication Utilities
### Multi-File Auth State Pattern
```typescript
import { AuthenticationState } from 'baileys';
import { CacheService } from '@api/services/cache.service';
import fs from 'fs/promises';
import path from 'path';
export default async function useMultiFileAuthStatePrisma(
sessionId: string,
cache: CacheService,
): Promise<{
state: AuthenticationState;
saveCreds: () => Promise<void>;
}> {
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> {
const dataString = JSON.stringify(data, BufferJSON.replacer);
if (key !== 'creds') {
if (process.env.CACHE_REDIS_ENABLED === 'true') {
return await cache.hSet(sessionId, key, data);
} else {
await fs.writeFile(localFile(key), dataString);
return;
}
}
await saveKey(sessionId, dataString);
return;
}
async function readData(key: string): Promise<any> {
try {
let rawData;
if (key !== 'creds') {
if (process.env.CACHE_REDIS_ENABLED === 'true') {
return await cache.hGet(sessionId, key);
} else {
if (!(await fileExists(localFile(key)))) return null;
rawData = await fs.readFile(localFile(key), { encoding: 'utf-8' });
return JSON.parse(rawData, BufferJSON.reviver);
}
} else {
rawData = await getAuthKey(sessionId);
}
const parsedData = JSON.parse(rawData, BufferJSON.reviver);
return parsedData;
} catch (error) {
return null;
}
}
async function removeData(key: string): Promise<any> {
try {
if (key !== 'creds') {
if (process.env.CACHE_REDIS_ENABLED === 'true') {
return await cache.hDelete(sessionId, key);
} else {
await fs.unlink(localFile(key));
}
} else {
await deleteAuthKey(sessionId);
}
} catch (error) {
return;
}
}
let creds = await readData('creds');
if (!creds) {
creds = initAuthCreds();
await writeData(creds, 'creds');
}
return {
state: {
creds,
keys: {
get: async (type, ids) => {
const data = {};
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) => {
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: () => writeData(creds, 'creds'),
};
}
```
## Message Processing Utilities
### Message Content Extraction
```typescript
export const getConversationMessage = (msg: any): string => {
const types = getTypeMessage(msg);
const messageContent = getMessageContent(types);
return messageContent;
};
const getTypeMessage = (msg: any): any => {
return Object.keys(msg?.message || msg || {})[0];
};
const getMessageContent = (type: string, msg?: any): string => {
const typeKey = type?.replace('Message', '');
const types = {
conversation: msg?.message?.conversation,
extendedTextMessage: msg?.message?.extendedTextMessage?.text,
imageMessage: msg?.message?.imageMessage?.caption || 'Image',
videoMessage: msg?.message?.videoMessage?.caption || 'Video',
audioMessage: 'Audio',
documentMessage: msg?.message?.documentMessage?.caption || 'Document',
stickerMessage: 'Sticker',
contactMessage: 'Contact',
locationMessage: 'Location',
liveLocationMessage: 'Live Location',
viewOnceMessage: 'View Once',
reactionMessage: 'Reaction',
pollCreationMessage: 'Poll',
pollUpdateMessage: 'Poll Update',
};
let result = types[typeKey] || types[type] || 'Unknown';
if (!result || result === 'Unknown') {
result = JSON.stringify(msg);
}
return result;
};
```
### JID Creation Utility
```typescript
export const createJid = (number: string): string => {
if (number.includes('@')) {
return number;
}
// Remove any non-numeric characters except +
let cleanNumber = number.replace(/[^\d+]/g, '');
// Remove + if present
if (cleanNumber.startsWith('+')) {
cleanNumber = cleanNumber.substring(1);
}
// Add country code if missing (assuming Brazil as default)
if (cleanNumber.length === 11 && cleanNumber.startsWith('11')) {
cleanNumber = '55' + cleanNumber;
} else if (cleanNumber.length === 10) {
cleanNumber = '5511' + cleanNumber;
}
// Determine if it's a group or individual
const isGroup = cleanNumber.includes('-');
const domain = isGroup ? 'g.us' : 's.whatsapp.net';
return `${cleanNumber}@${domain}`;
};
```
## Cache Utilities
### WhatsApp Number Cache
```typescript
interface ISaveOnWhatsappCacheParams {
remoteJid: string;
lid?: string;
}
function getAvailableNumbers(remoteJid: string): string[] {
const numbersAvailable: string[] = [];
if (remoteJid.startsWith('+')) {
remoteJid = remoteJid.slice(1);
}
const [number, domain] = remoteJid.split('@');
// Brazilian numbers
if (remoteJid.startsWith('55')) {
const numberWithDigit =
number.slice(4, 5) === '9' && number.length === 13 ? number : `${number.slice(0, 4)}9${number.slice(4)}`;
const numberWithoutDigit = number.length === 12 ? number : number.slice(0, 4) + number.slice(5);
numbersAvailable.push(numberWithDigit);
numbersAvailable.push(numberWithoutDigit);
}
// Mexican/Argentina numbers
else if (number.startsWith('52') || number.startsWith('54')) {
let prefix = '';
if (number.startsWith('52')) {
prefix = '1';
}
if (number.startsWith('54')) {
prefix = '9';
}
const numberWithDigit =
number.slice(2, 3) === prefix && number.length === 13
? number
: `${number.slice(0, 2)}${prefix}${number.slice(2)}`;
const numberWithoutDigit = number.length === 12 ? number : number.slice(0, 2) + number.slice(3);
numbersAvailable.push(numberWithDigit);
numbersAvailable.push(numberWithoutDigit);
}
// Other countries
else {
numbersAvailable.push(remoteJid);
}
return numbersAvailable.map((number) => `${number}@${domain}`);
}
export async function saveOnWhatsappCache(params: ISaveOnWhatsappCacheParams): Promise<void> {
const { remoteJid, lid } = params;
const db = configService.get<Database>('DATABASE');
if (!db.SAVE_DATA.CONTACTS) {
return;
}
try {
const numbersAvailable = getAvailableNumbers(remoteJid);
const existingContact = await prismaRepository.contact.findFirst({
where: {
OR: numbersAvailable.map(number => ({ id: number })),
},
});
if (!existingContact) {
await prismaRepository.contact.create({
data: {
id: remoteJid,
pushName: '',
profilePicUrl: '',
isOnWhatsapp: true,
lid: lid || null,
createdAt: new Date(),
updatedAt: new Date(),
},
});
} else {
await prismaRepository.contact.update({
where: { id: existingContact.id },
data: {
isOnWhatsapp: true,
lid: lid || existingContact.lid,
updatedAt: new Date(),
},
});
}
} catch (error) {
console.error('Error saving WhatsApp cache:', error);
}
}
```
## Search Utilities
### Advanced Search Operators
```typescript
function normalizeString(str: string): string {
return str
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '');
}
export function advancedOperatorsSearch(data: string, query: string): boolean {
const normalizedData = normalizeString(data);
const normalizedQuery = normalizeString(query);
// Exact phrase search with quotes
if (normalizedQuery.startsWith('"') && normalizedQuery.endsWith('"')) {
const phrase = normalizedQuery.slice(1, -1);
return normalizedData.includes(phrase);
}
// OR operator
if (normalizedQuery.includes(' OR ')) {
const terms = normalizedQuery.split(' OR ');
return terms.some(term => normalizedData.includes(term.trim()));
}
// AND operator (default behavior)
if (normalizedQuery.includes(' AND ')) {
const terms = normalizedQuery.split(' AND ');
return terms.every(term => normalizedData.includes(term.trim()));
}
// NOT operator
if (normalizedQuery.startsWith('NOT ')) {
const term = normalizedQuery.slice(4);
return !normalizedData.includes(term);
}
// Wildcard search
if (normalizedQuery.includes('*')) {
const regex = new RegExp(normalizedQuery.replace(/\*/g, '.*'), 'i');
return regex.test(normalizedData);
}
// Default: simple contains search
return normalizedData.includes(normalizedQuery);
}
```
## Proxy Utilities
### Proxy Agent Creation
```typescript
import { HttpsProxyAgent } from 'https-proxy-agent';
import { SocksProxyAgent } from 'socks-proxy-agent';
type Proxy = {
host: string;
port: string;
protocol: 'http' | 'https' | 'socks4' | 'socks5';
username?: string;
password?: string;
};
function selectProxyAgent(proxyUrl: string): HttpsProxyAgent<string> | SocksProxyAgent {
const url = new URL(proxyUrl);
if (url.protocol === 'socks4:' || url.protocol === 'socks5:') {
return new SocksProxyAgent(proxyUrl);
} else {
return new HttpsProxyAgent(proxyUrl);
}
}
export function makeProxyAgent(proxy: Proxy): HttpsProxyAgent<string> | SocksProxyAgent | null {
if (!proxy.host || !proxy.port) {
return null;
}
let proxyUrl = `${proxy.protocol}://`;
if (proxy.username && proxy.password) {
proxyUrl += `${proxy.username}:${proxy.password}@`;
}
proxyUrl += `${proxy.host}:${proxy.port}`;
try {
return selectProxyAgent(proxyUrl);
} catch (error) {
console.error('Failed to create proxy agent:', error);
return null;
}
}
```
## Telemetry Utilities
### Telemetry Data Collection
```typescript
export interface TelemetryData {
route: string;
apiVersion: string;
timestamp: Date;
method?: string;
statusCode?: number;
responseTime?: number;
userAgent?: string;
instanceName?: string;
}
export const sendTelemetry = async (route: string): Promise<void> => {
try {
const telemetryData: TelemetryData = {
route,
apiVersion: packageJson.version,
timestamp: new Date(),
};
// Only send telemetry if enabled
if (process.env.DISABLE_TELEMETRY === 'true') {
return;
}
// Send to telemetry service (implement as needed)
await axios.post('https://telemetry.evolution-api.com/collect', telemetryData, {
timeout: 5000,
});
} catch (error) {
// Silently fail - don't affect main application
console.debug('Telemetry failed:', error.message);
}
};
```
## Internationalization Utilities
### i18n Setup
```typescript
import { ConfigService, Language } from '@config/env.config';
import fs from 'fs';
import i18next from 'i18next';
import path from 'path';
const __dirname = path.resolve(process.cwd(), 'src', 'utils');
const languages = ['en', 'pt-BR', 'es'];
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)) {
const translationContent = fs.readFileSync(languagePath, 'utf8');
resources[language] = {
translation: JSON.parse(translationContent),
};
}
});
i18next.init({
resources,
fallbackLng: 'en',
lng: configService.get<Language>('LANGUAGE') || 'pt-BR',
interpolation: {
escapeValue: false,
},
});
export const t = i18next.t.bind(i18next);
export default i18next;
```
## Bot Trigger Utilities
### Bot Trigger Matching
```typescript
import { TriggerOperator, TriggerType } from '@prisma/client';
export function findBotByTrigger(
bots: any[],
content: string,
remoteJid: string,
): any | null {
for (const bot of bots) {
if (!bot.enabled) continue;
// Check ignore list
if (bot.ignoreJids && bot.ignoreJids.includes(remoteJid)) {
continue;
}
// Check trigger
if (matchesTrigger(content, bot.triggerType, bot.triggerOperator, bot.triggerValue)) {
return bot;
}
}
return null;
}
function matchesTrigger(
content: string,
triggerType: TriggerType,
triggerOperator: TriggerOperator,
triggerValue: string,
): boolean {
const normalizedContent = content.toLowerCase().trim();
const normalizedValue = triggerValue.toLowerCase().trim();
switch (triggerType) {
case TriggerType.ALL:
return true;
case TriggerType.KEYWORD:
return matchesKeyword(normalizedContent, triggerOperator, normalizedValue);
case TriggerType.REGEX:
try {
const regex = new RegExp(triggerValue, 'i');
return regex.test(content);
} catch {
return false;
}
default:
return false;
}
}
function matchesKeyword(
content: string,
operator: TriggerOperator,
value: string,
): boolean {
switch (operator) {
case TriggerOperator.EQUALS:
return content === value;
case TriggerOperator.CONTAINS:
return content.includes(value);
case TriggerOperator.STARTS_WITH:
return content.startsWith(value);
case TriggerOperator.ENDS_WITH:
return content.endsWith(value);
default:
return false;
}
}
```
## Server Utilities
### Server Status Check
```typescript
export class ServerUP {
private static instance: ServerUP;
private isServerUp: boolean = false;
private constructor() {}
public static getInstance(): ServerUP {
if (!ServerUP.instance) {
ServerUP.instance = new ServerUP();
}
return ServerUP.instance;
}
public setServerStatus(status: boolean): void {
this.isServerUp = status;
}
public getServerStatus(): boolean {
return this.isServerUp;
}
public async waitForServer(timeout: number = 30000): Promise<boolean> {
const startTime = Date.now();
while (!this.isServerUp && (Date.now() - startTime) < timeout) {
await new Promise(resolve => setTimeout(resolve, 100));
}
return this.isServerUp;
}
}
```
## Error Response Utilities
### Standardized Error Responses
```typescript
export function createMetaErrorResponse(error: any, context: string) {
const timestamp = new Date().toISOString();
if (error.response?.data) {
return {
status: error.response.status || 500,
error: {
message: error.response.data.error?.message || 'External API error',
type: error.response.data.error?.type || 'api_error',
code: error.response.data.error?.code || 'unknown_error',
context,
timestamp,
},
};
}
return {
status: 500,
error: {
message: error.message || 'Internal server error',
type: 'internal_error',
code: 'server_error',
context,
timestamp,
},
};
}
export function createValidationErrorResponse(errors: any[], context: string) {
return {
status: 400,
error: {
message: 'Validation failed',
type: 'validation_error',
code: 'invalid_input',
context,
details: errors,
timestamp: new Date().toISOString(),
},
};
}
```

View File

@@ -0,0 +1,498 @@
---
description: Validation schemas and patterns for Evolution API
globs:
- "src/validate/**/*.ts"
alwaysApply: false
---
# Evolution API Validation Rules
## Validation Schema Structure
### JSONSchema7 Pattern (Evolution API Standard)
```typescript
import { JSONSchema7 } from 'json-schema';
import { v4 } from 'uuid';
const isNotEmpty = (...fields: string[]) => {
const properties = {};
fields.forEach((field) => {
properties[field] = {
if: { properties: { [field]: { type: 'string' } } },
then: { properties: { [field]: { minLength: 1 } } },
};
});
return {
allOf: Object.values(properties),
};
};
export const exampleSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
name: { type: 'string' },
description: { type: 'string' },
enabled: { type: 'boolean' },
settings: {
type: 'object',
properties: {
timeout: { type: 'number', minimum: 1000, maximum: 60000 },
retries: { type: 'number', minimum: 0, maximum: 5 },
},
},
tags: {
type: 'array',
items: { type: 'string' },
},
},
required: ['name', 'enabled'],
...isNotEmpty('name'),
};
```
## Message Validation Schemas
### Send Message Validation
```typescript
const numberDefinition = {
type: 'string',
pattern: '^\\d+[\\.@\\w-]+',
description: 'Invalid number',
};
export const sendTextSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
number: numberDefinition,
text: { type: 'string', minLength: 1, maxLength: 4096 },
delay: { type: 'number', minimum: 0, maximum: 60000 },
linkPreview: { type: 'boolean' },
mentionsEveryOne: { type: 'boolean' },
mentioned: {
type: 'array',
items: { type: 'string' },
},
},
required: ['number', 'text'],
...isNotEmpty('number', 'text'),
};
export const sendMediaSchema = Joi.object({
number: Joi.string().required().pattern(/^\d+$/),
mediatype: Joi.string().required().valid('image', 'video', 'audio', 'document'),
media: Joi.alternatives().try(
Joi.string().uri(),
Joi.string().base64(),
).required(),
caption: Joi.string().optional().max(1024),
fileName: Joi.string().optional().max(255),
delay: Joi.number().optional().min(0).max(60000),
}).required();
export const sendButtonsSchema = Joi.object({
number: Joi.string().required().pattern(/^\d+$/),
title: Joi.string().required().max(1024),
description: Joi.string().optional().max(1024),
footer: Joi.string().optional().max(60),
buttons: Joi.array().items(
Joi.object({
type: Joi.string().required().valid('replyButton', 'urlButton', 'callButton'),
displayText: Joi.string().required().max(20),
id: Joi.string().when('type', {
is: 'replyButton',
then: Joi.required().max(256),
otherwise: Joi.optional(),
}),
url: Joi.string().when('type', {
is: 'urlButton',
then: Joi.required().uri(),
otherwise: Joi.optional(),
}),
phoneNumber: Joi.string().when('type', {
is: 'callButton',
then: Joi.required().pattern(/^\+?\d+$/),
otherwise: Joi.optional(),
}),
})
).min(1).max(3).required(),
}).required();
```
## Instance Validation Schemas
### Instance Creation Validation
```typescript
export const instanceSchema = Joi.object({
instanceName: Joi.string().required().min(1).max(100).pattern(/^[a-zA-Z0-9_-]+$/),
integration: Joi.string().required().valid('WHATSAPP-BAILEYS', 'WHATSAPP-BUSINESS', 'EVOLUTION'),
token: Joi.string().when('integration', {
is: Joi.valid('WHATSAPP-BUSINESS', 'EVOLUTION'),
then: Joi.required().min(10),
otherwise: Joi.optional(),
}),
qrcode: Joi.boolean().optional().default(false),
number: Joi.string().optional().pattern(/^\d+$/),
businessId: Joi.string().when('integration', {
is: 'WHATSAPP-BUSINESS',
then: Joi.required(),
otherwise: Joi.optional(),
}),
}).required();
export const settingsSchema = Joi.object({
rejectCall: Joi.boolean().optional(),
msgCall: Joi.string().optional().max(500),
groupsIgnore: Joi.boolean().optional(),
alwaysOnline: Joi.boolean().optional(),
readMessages: Joi.boolean().optional(),
readStatus: Joi.boolean().optional(),
syncFullHistory: Joi.boolean().optional(),
wavoipToken: Joi.string().optional(),
}).optional();
export const proxySchema = Joi.object({
host: Joi.string().required().hostname(),
port: Joi.string().required().pattern(/^\d+$/).custom((value) => {
const port = parseInt(value);
if (port < 1 || port > 65535) {
throw new Error('Port must be between 1 and 65535');
}
return value;
}),
protocol: Joi.string().required().valid('http', 'https', 'socks4', 'socks5'),
username: Joi.string().optional(),
password: Joi.string().optional(),
}).optional();
```
## Webhook Validation Schemas
### Webhook Configuration Validation
```typescript
export const webhookSchema = Joi.object({
enabled: Joi.boolean().required(),
url: Joi.string().when('enabled', {
is: true,
then: Joi.required().uri({ scheme: ['http', 'https'] }),
otherwise: Joi.optional(),
}),
events: Joi.array().items(
Joi.string().valid(
'APPLICATION_STARTUP',
'INSTANCE_CREATE',
'INSTANCE_DELETE',
'QRCODE_UPDATED',
'CONNECTION_UPDATE',
'STATUS_INSTANCE',
'MESSAGES_SET',
'MESSAGES_UPSERT',
'MESSAGES_UPDATE',
'MESSAGES_DELETE',
'CONTACTS_SET',
'CONTACTS_UPSERT',
'CONTACTS_UPDATE',
'CHATS_SET',
'CHATS_UPDATE',
'CHATS_UPSERT',
'CHATS_DELETE',
'GROUPS_UPSERT',
'GROUPS_UPDATE',
'GROUP_PARTICIPANTS_UPDATE',
'CALL'
)
).min(1).when('enabled', {
is: true,
then: Joi.required(),
otherwise: Joi.optional(),
}),
headers: Joi.object().pattern(
Joi.string(),
Joi.string()
).optional(),
byEvents: Joi.boolean().optional().default(false),
base64: Joi.boolean().optional().default(false),
}).required();
```
## Chatbot Validation Schemas
### Base Chatbot Validation
```typescript
export const baseChatbotSchema = Joi.object({
enabled: Joi.boolean().required(),
description: Joi.string().required().min(1).max(500),
expire: Joi.number().optional().min(0).max(86400), // 24 hours in seconds
keywordFinish: Joi.string().optional().max(100),
delayMessage: Joi.number().optional().min(0).max(10000),
unknownMessage: Joi.string().optional().max(1000),
listeningFromMe: Joi.boolean().optional().default(false),
stopBotFromMe: Joi.boolean().optional().default(false),
keepOpen: Joi.boolean().optional().default(false),
debounceTime: Joi.number().optional().min(0).max(60000),
triggerType: Joi.string().required().valid('ALL', 'KEYWORD', 'REGEX'),
triggerOperator: Joi.string().when('triggerType', {
is: 'KEYWORD',
then: Joi.required().valid('EQUALS', 'CONTAINS', 'STARTS_WITH', 'ENDS_WITH'),
otherwise: Joi.optional(),
}),
triggerValue: Joi.string().when('triggerType', {
is: Joi.valid('KEYWORD', 'REGEX'),
then: Joi.required().min(1).max(500),
otherwise: Joi.optional(),
}),
ignoreJids: Joi.array().items(Joi.string()).optional(),
splitMessages: Joi.boolean().optional().default(false),
timePerChar: Joi.number().optional().min(10).max(1000).default(100),
}).required();
export const typebotSchema = baseChatbotSchema.keys({
url: Joi.string().required().uri({ scheme: ['http', 'https'] }),
typebot: Joi.string().required().min(1).max(100),
apiVersion: Joi.string().optional().valid('v1', 'v2').default('v1'),
}).required();
export const openaiSchema = baseChatbotSchema.keys({
apiKey: Joi.string().required().min(10),
model: Joi.string().optional().valid(
'gpt-3.5-turbo',
'gpt-3.5-turbo-16k',
'gpt-4',
'gpt-4-32k',
'gpt-4-turbo-preview'
).default('gpt-3.5-turbo'),
systemMessage: Joi.string().optional().max(2000),
maxTokens: Joi.number().optional().min(1).max(4000).default(1000),
temperature: Joi.number().optional().min(0).max(2).default(0.7),
}).required();
```
## Business API Validation Schemas
### Template Validation
```typescript
export const templateSchema = Joi.object({
name: Joi.string().required().min(1).max(512).pattern(/^[a-z0-9_]+$/),
category: Joi.string().required().valid('MARKETING', 'UTILITY', 'AUTHENTICATION'),
allowCategoryChange: Joi.boolean().required(),
language: Joi.string().required().pattern(/^[a-z]{2}_[A-Z]{2}$/), // e.g., pt_BR, en_US
components: Joi.array().items(
Joi.object({
type: Joi.string().required().valid('HEADER', 'BODY', 'FOOTER', 'BUTTONS'),
format: Joi.string().when('type', {
is: 'HEADER',
then: Joi.valid('TEXT', 'IMAGE', 'VIDEO', 'DOCUMENT'),
otherwise: Joi.optional(),
}),
text: Joi.string().when('type', {
is: Joi.valid('HEADER', 'BODY', 'FOOTER'),
then: Joi.required().min(1).max(1024),
otherwise: Joi.optional(),
}),
buttons: Joi.array().when('type', {
is: 'BUTTONS',
then: Joi.items(
Joi.object({
type: Joi.string().required().valid('QUICK_REPLY', 'URL', 'PHONE_NUMBER'),
text: Joi.string().required().min(1).max(25),
url: Joi.string().when('type', {
is: 'URL',
then: Joi.required().uri(),
otherwise: Joi.optional(),
}),
phone_number: Joi.string().when('type', {
is: 'PHONE_NUMBER',
then: Joi.required().pattern(/^\+?\d+$/),
otherwise: Joi.optional(),
}),
})
).min(1).max(10),
otherwise: Joi.optional(),
}),
})
).min(1).required(),
webhookUrl: Joi.string().optional().uri({ scheme: ['http', 'https'] }),
}).required();
export const catalogSchema = Joi.object({
number: Joi.string().optional().pattern(/^\d+$/),
limit: Joi.number().optional().min(1).max(1000).default(10),
cursor: Joi.string().optional(),
}).optional();
```
## Group Validation Schemas
### Group Management Validation
```typescript
export const createGroupSchema = Joi.object({
subject: Joi.string().required().min(1).max(100),
description: Joi.string().optional().max(500),
participants: Joi.array().items(
Joi.string().pattern(/^\d+$/)
).min(1).max(256).required(),
promoteParticipants: Joi.boolean().optional().default(false),
}).required();
export const updateGroupSchema = Joi.object({
subject: Joi.string().optional().min(1).max(100),
description: Joi.string().optional().max(500),
}).min(1).required();
export const groupParticipantsSchema = Joi.object({
participants: Joi.array().items(
Joi.string().pattern(/^\d+$/)
).min(1).max(50).required(),
action: Joi.string().required().valid('add', 'remove', 'promote', 'demote'),
}).required();
```
## Label Validation Schemas
### Label Management Validation
```typescript
export const labelSchema = Joi.object({
name: Joi.string().required().min(1).max(100),
color: Joi.string().required().pattern(/^#[0-9A-Fa-f]{6}$/), // Hex color
predefinedId: Joi.string().optional(),
}).required();
export const handleLabelSchema = Joi.object({
number: Joi.string().required().pattern(/^\d+$/),
labelId: Joi.string().required(),
action: Joi.string().required().valid('add', 'remove'),
}).required();
```
## Custom Validation Functions
### Phone Number Validation
```typescript
export function validatePhoneNumber(number: string): boolean {
// Remove any non-digit characters
const cleaned = number.replace(/\D/g, '');
// Check minimum and maximum length
if (cleaned.length < 10 || cleaned.length > 15) {
return false;
}
// Check for valid country codes (basic validation)
const validCountryCodes = ['1', '7', '20', '27', '30', '31', '32', '33', '34', '36', '39', '40', '41', '43', '44', '45', '46', '47', '48', '49', '51', '52', '53', '54', '55', '56', '57', '58', '60', '61', '62', '63', '64', '65', '66', '81', '82', '84', '86', '90', '91', '92', '93', '94', '95', '98'];
// Check if starts with valid country code
const startsWithValidCode = validCountryCodes.some(code => cleaned.startsWith(code));
return startsWithValidCode;
}
export const phoneNumberValidator = Joi.string().custom((value, helpers) => {
if (!validatePhoneNumber(value)) {
return helpers.error('any.invalid');
}
return value;
}, 'Phone number validation');
```
### Base64 Validation
```typescript
export function validateBase64(base64: string): boolean {
try {
// Check if it's a valid base64 string
const decoded = Buffer.from(base64, 'base64').toString('base64');
return decoded === base64;
} catch {
return false;
}
}
export const base64Validator = Joi.string().custom((value, helpers) => {
if (!validateBase64(value)) {
return helpers.error('any.invalid');
}
return value;
}, 'Base64 validation');
```
### URL Validation with Protocol Check
```typescript
export function validateWebhookUrl(url: string): boolean {
try {
const parsed = new URL(url);
return ['http:', 'https:'].includes(parsed.protocol);
} catch {
return false;
}
}
export const webhookUrlValidator = Joi.string().custom((value, helpers) => {
if (!validateWebhookUrl(value)) {
return helpers.error('any.invalid');
}
return value;
}, 'Webhook URL validation');
```
## Validation Error Handling
### Error Message Customization
```typescript
export const validationMessages = {
'any.required': 'O campo {#label} é obrigatório',
'string.empty': 'O campo {#label} não pode estar vazio',
'string.min': 'O campo {#label} deve ter pelo menos {#limit} caracteres',
'string.max': 'O campo {#label} deve ter no máximo {#limit} caracteres',
'string.pattern.base': 'O campo {#label} possui formato inválido',
'number.min': 'O campo {#label} deve ser maior ou igual a {#limit}',
'number.max': 'O campo {#label} deve ser menor ou igual a {#limit}',
'array.min': 'O campo {#label} deve ter pelo menos {#limit} itens',
'array.max': 'O campo {#label} deve ter no máximo {#limit} itens',
'any.only': 'O campo {#label} deve ser um dos valores: {#valids}',
};
export function formatValidationError(error: Joi.ValidationError): any {
return {
message: 'Dados de entrada inválidos',
details: error.details.map(detail => ({
field: detail.path.join('.'),
message: detail.message,
value: detail.context?.value,
})),
};
}
```
## Schema Composition
### Reusable Schema Components
```typescript
export const commonFields = {
instanceName: Joi.string().required().min(1).max(100).pattern(/^[a-zA-Z0-9_-]+$/),
number: phoneNumberValidator.required(),
delay: Joi.number().optional().min(0).max(60000),
enabled: Joi.boolean().optional().default(true),
};
export const mediaFields = {
mediatype: Joi.string().required().valid('image', 'video', 'audio', 'document'),
media: Joi.alternatives().try(
Joi.string().uri(),
base64Validator,
).required(),
caption: Joi.string().optional().max(1024),
fileName: Joi.string().optional().max(255),
};
// Compose schemas using common fields
export const quickMessageSchema = Joi.object({
...commonFields,
text: Joi.string().required().min(1).max(4096),
}).required();
export const quickMediaSchema = Joi.object({
...commonFields,
...mediaFields,
}).required();
```

View File

@@ -1,5 +1,6 @@
.git
*Dockerfile*
*docker-compose*
.env
node_modules
dist

View File

@@ -1,10 +1,36 @@
SERVER_NAME=evolution
SERVER_TYPE=http
SERVER_PORT=8080
# Server URL - Set your application url
SERVER_URL=http://localhost:8080
SSL_CONF_PRIVKEY=/path/to/cert.key
SSL_CONF_FULLCHAIN=/path/to/cert.crt
SENTRY_DSN=
# Telemetry - Set to false to disable telemetry
TELEMETRY_ENABLED=true
TELEMETRY_URL=
# Prometheus metrics - Set to true to enable Prometheus metrics
PROMETHEUS_METRICS=false
METRICS_AUTH_REQUIRED=true
METRICS_USER=prometheus
METRICS_PASSWORD=secure_random_password_here
METRICS_ALLOWED_IPS=127.0.0.1,10.0.0.100,192.168.1.50
# Proxy configuration (optional)
PROXY_HOST=
PROXY_PORT=
PROXY_PROTOCOL=
PROXY_USERNAME=
PROXY_PASSWORD=
# Audio converter API (optional)
API_AUDIO_CONVERTER=
API_AUDIO_CONVERTER_KEY=
# Cors - * for all or set separate by commas - ex.: 'yourdomain1.com, yourdomain2.com'
CORS_ORIGIN=*
CORS_METHODS=GET,POST,PUT,DELETE
@@ -24,13 +50,17 @@ EVENT_EMITTER_MAX_LISTENERS=50
# If you don't even want an expiration, enter the value false
DEL_INSTANCE=false
# Provider: postgresql | mysql
# Provider: postgresql | mysql | psql_bouncer
DATABASE_PROVIDER=postgresql
DATABASE_CONNECTION_URI='postgresql://user:pass@localhost:5432/evolution?schema=public'
DATABASE_CONNECTION_URI='postgresql://user:pass@postgres:5432/evolution_db?schema=evolution_api'
# Client name for the database connection
# It is used to separate an API installation from another that uses the same database.
DATABASE_CONNECTION_CLIENT_NAME=evolution_exchange
# Bouncer connection: used only when the database provider is set to 'psql_bouncer'.
# Defines the PostgreSQL URL with pgbouncer enabled (pgbouncer=true).
# DATABASE_BOUNCER_CONNECTION_URI=postgresql://user:pass@pgbouncer:5432/evolution_db?pgbouncer=true&schema=evolution_api
# Choose the data you want to save in the application's database
DATABASE_SAVE_DATA_INSTANCE=true
DATABASE_SAVE_DATA_NEW_MESSAGE=true
@@ -47,8 +77,11 @@ DATABASE_DELETE_MESSAGE=true
RABBITMQ_ENABLED=false
RABBITMQ_URI=amqp://localhost
RABBITMQ_EXCHANGE_NAME=evolution
RABBITMQ_FRAME_MAX=8192
# Global events - By enabling this variable, events from all instances are sent in the same event queue.
RABBITMQ_GLOBAL_ENABLED=false
# Prefix key to queue name
RABBITMQ_PREFIX_KEY=evolution
# Choose the events you want to send to RabbitMQ
RABBITMQ_EVENTS_APPLICATION_STARTUP=false
RABBITMQ_EVENTS_INSTANCE_CREATE=false
@@ -60,6 +93,7 @@ RABBITMQ_EVENTS_MESSAGES_EDITED=false
RABBITMQ_EVENTS_MESSAGES_UPDATE=false
RABBITMQ_EVENTS_MESSAGES_DELETE=false
RABBITMQ_EVENTS_SEND_MESSAGE=false
RABBITMQ_EVENTS_SEND_MESSAGE_UPDATE=false
RABBITMQ_EVENTS_CONTACTS_SET=false
RABBITMQ_EVENTS_CONTACTS_UPSERT=false
RABBITMQ_EVENTS_CONTACTS_UPDATE=false
@@ -85,9 +119,40 @@ SQS_SECRET_ACCESS_KEY=
SQS_ACCOUNT_ID=
SQS_REGION=
SQS_GLOBAL_ENABLED=false
SQS_GLOBAL_FORCE_SINGLE_QUEUE=false
SQS_GLOBAL_APPLICATION_STARTUP=false
SQS_GLOBAL_CALL=false
SQS_GLOBAL_CHATS_DELETE=false
SQS_GLOBAL_CHATS_SET=false
SQS_GLOBAL_CHATS_UPDATE=false
SQS_GLOBAL_CHATS_UPSERT=false
SQS_GLOBAL_CONNECTION_UPDATE=false
SQS_GLOBAL_CONTACTS_SET=false
SQS_GLOBAL_CONTACTS_UPDATE=false
SQS_GLOBAL_CONTACTS_UPSERT=false
SQS_GLOBAL_GROUP_PARTICIPANTS_UPDATE=false
SQS_GLOBAL_GROUPS_UPDATE=false
SQS_GLOBAL_GROUPS_UPSERT=false
SQS_GLOBAL_LABELS_ASSOCIATION=false
SQS_GLOBAL_LABELS_EDIT=false
SQS_GLOBAL_LOGOUT_INSTANCE=false
SQS_GLOBAL_MESSAGES_DELETE=false
SQS_GLOBAL_MESSAGES_EDITED=false
SQS_GLOBAL_MESSAGES_SET=false
SQS_GLOBAL_MESSAGES_UPDATE=false
SQS_GLOBAL_MESSAGES_UPSERT=false
SQS_GLOBAL_PRESENCE_UPDATE=false
SQS_GLOBAL_QRCODE_UPDATED=false
SQS_GLOBAL_REMOVE_INSTANCE=false
SQS_GLOBAL_SEND_MESSAGE=false
SQS_GLOBAL_TYPEBOT_CHANGE_STATUS=false
SQS_GLOBAL_TYPEBOT_START=false
# Websocket - Environment variables
WEBSOCKET_ENABLED=false
WEBSOCKET_GLOBAL_EVENTS=false
WEBSOCKET_ALLOWED_HOSTS=127.0.0.1,::1,::ffff:127.0.0.1
# Pusher - Environment variables
PUSHER_ENABLED=false
@@ -106,6 +171,7 @@ PUSHER_EVENTS_MESSAGES_EDITED=true
PUSHER_EVENTS_MESSAGES_UPDATE=true
PUSHER_EVENTS_MESSAGES_DELETE=true
PUSHER_EVENTS_SEND_MESSAGE=true
PUSHER_EVENTS_SEND_MESSAGE_UPDATE=true
PUSHER_EVENTS_CONTACTS_SET=true
PUSHER_EVENTS_CONTACTS_UPSERT=true
PUSHER_EVENTS_CONTACTS_UPDATE=true
@@ -124,6 +190,60 @@ PUSHER_EVENTS_CALL=true
PUSHER_EVENTS_TYPEBOT_START=false
PUSHER_EVENTS_TYPEBOT_CHANGE_STATUS=false
# Kafka - Environment variables
KAFKA_ENABLED=false
KAFKA_CLIENT_ID=evolution-api
KAFKA_BROKERS=localhost:9092
KAFKA_CONNECTION_TIMEOUT=3000
KAFKA_REQUEST_TIMEOUT=30000
# Global events - By enabling this variable, events from all instances are sent to global Kafka topics.
KAFKA_GLOBAL_ENABLED=false
KAFKA_CONSUMER_GROUP_ID=evolution-api-consumers
KAFKA_TOPIC_PREFIX=evolution
KAFKA_NUM_PARTITIONS=1
KAFKA_REPLICATION_FACTOR=1
KAFKA_AUTO_CREATE_TOPICS=false
# Choose the events you want to send to Kafka
KAFKA_EVENTS_APPLICATION_STARTUP=false
KAFKA_EVENTS_INSTANCE_CREATE=false
KAFKA_EVENTS_INSTANCE_DELETE=false
KAFKA_EVENTS_QRCODE_UPDATED=false
KAFKA_EVENTS_MESSAGES_SET=false
KAFKA_EVENTS_MESSAGES_UPSERT=false
KAFKA_EVENTS_MESSAGES_EDITED=false
KAFKA_EVENTS_MESSAGES_UPDATE=false
KAFKA_EVENTS_MESSAGES_DELETE=false
KAFKA_EVENTS_SEND_MESSAGE=false
KAFKA_EVENTS_SEND_MESSAGE_UPDATE=false
KAFKA_EVENTS_CONTACTS_SET=false
KAFKA_EVENTS_CONTACTS_UPSERT=false
KAFKA_EVENTS_CONTACTS_UPDATE=false
KAFKA_EVENTS_PRESENCE_UPDATE=false
KAFKA_EVENTS_CHATS_SET=false
KAFKA_EVENTS_CHATS_UPSERT=false
KAFKA_EVENTS_CHATS_UPDATE=false
KAFKA_EVENTS_CHATS_DELETE=false
KAFKA_EVENTS_GROUPS_UPSERT=false
KAFKA_EVENTS_GROUPS_UPDATE=false
KAFKA_EVENTS_GROUP_PARTICIPANTS_UPDATE=false
KAFKA_EVENTS_CONNECTION_UPDATE=false
KAFKA_EVENTS_LABELS_EDIT=false
KAFKA_EVENTS_LABELS_ASSOCIATION=false
KAFKA_EVENTS_CALL=false
KAFKA_EVENTS_TYPEBOT_START=false
KAFKA_EVENTS_TYPEBOT_CHANGE_STATUS=false
# SASL Authentication (optional)
KAFKA_SASL_ENABLED=false
KAFKA_SASL_MECHANISM=plain
KAFKA_SASL_USERNAME=
KAFKA_SASL_PASSWORD=
# SSL Configuration (optional)
KAFKA_SSL_ENABLED=false
KAFKA_SSL_REJECT_UNAUTHORIZED=true
KAFKA_SSL_CA=
KAFKA_SSL_KEY=
KAFKA_SSL_CERT=
# WhatsApp Business API - Environment variables
# Token used to validate the webhook on the Facebook APP
WA_BUSINESS_TOKEN_WEBHOOK=evolution
@@ -147,6 +267,7 @@ WEBHOOK_EVENTS_MESSAGES_EDITED=true
WEBHOOK_EVENTS_MESSAGES_UPDATE=true
WEBHOOK_EVENTS_MESSAGES_DELETE=true
WEBHOOK_EVENTS_SEND_MESSAGE=true
WEBHOOK_EVENTS_SEND_MESSAGE_UPDATE=true
WEBHOOK_EVENTS_CONTACTS_SET=true
WEBHOOK_EVENTS_CONTACTS_UPSERT=true
WEBHOOK_EVENTS_CONTACTS_UPDATE=true
@@ -171,6 +292,15 @@ WEBHOOK_EVENTS_TYPEBOT_CHANGE_STATUS=false
WEBHOOK_EVENTS_ERRORS=false
WEBHOOK_EVENTS_ERRORS_WEBHOOK=
WEBHOOK_REQUEST_TIMEOUT_MS=60000
WEBHOOK_RETRY_MAX_ATTEMPTS=10
WEBHOOK_RETRY_INITIAL_DELAY_SECONDS=5
WEBHOOK_RETRY_USE_EXPONENTIAL_BACKOFF=true
WEBHOOK_RETRY_MAX_DELAY_SECONDS=300
WEBHOOK_RETRY_JITTER_FACTOR=0.2
# Comma separated list of HTTP status codes that should not trigger retries
WEBHOOK_RETRY_NON_RETRYABLE_STATUS_CODES=400,401,403,404,422
# Name that will be displayed on smartphone connection
CONFIG_SESSION_PHONE_CLIENT=Evolution API
# Browser Name = Chrome | Firefox | Edge | Opera | Safari
@@ -178,7 +308,7 @@ CONFIG_SESSION_PHONE_NAME=Chrome
# Whatsapp Web version for baileys channel
# https://web.whatsapp.com/check-update?version=0&platform=web
CONFIG_SESSION_PHONE_VERSION=2.3000.1015901307
# Set qrcode display limit
QRCODE_LIMIT=30
@@ -208,6 +338,12 @@ OPENAI_ENABLED=false
# Dify - Environment variables
DIFY_ENABLED=false
# n8n - Environment variables
N8N_ENABLED=false
# EvoAI - Environment variables
EVOAI_ENABLED=false
# Cache - Environment variables
# Redis Cache enabled
CACHE_REDIS_ENABLED=true
@@ -264,4 +400,4 @@ LANGUAGE=en
# PROXY_PORT=80
# PROXY_PROTOCOL=http
# PROXY_USERNAME=
# PROXY_PASSWORD=
# PROXY_PASSWORD=

View File

@@ -1,7 +1,11 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'CommonJS',
project: 'tsconfig.json',
tsconfigRootDir: __dirname,
sourceType: 'module',
warnOnUnsupportedTypeScriptVersion: false,
EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true,
},
plugins: ['@typescript-eslint', 'simple-import-sort', 'import'],
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'],
@@ -27,16 +31,9 @@ module.exports = {
'import/no-duplicates': 'error',
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
'@typescript-eslint/ban-types': [
'error',
{
extendDefaults: true,
types: {
'{}': false,
Object: false,
},
},
],
'@typescript-eslint/no-empty-object-type': 'off',
'@typescript-eslint/no-wrapper-object-types': 'off',
'@typescript-eslint/no-unused-expressions': 'off',
'prettier/prettier': ['error', { endOfLine: 'auto' }],
},
};

81
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,81 @@
name: 🐛 Bug Report
description: Report a bug or unexpected behavior
title: "[BUG] "
labels: ["bug", "needs-triage"]
assignees: []
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
Please search existing issues before creating a new one.
- type: textarea
id: description
attributes:
label: 📋 Bug Description
description: A clear and concise description of what the bug is.
placeholder: Describe the bug...
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: 🔄 Steps to Reproduce
description: Steps to reproduce the behavior
placeholder: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: true
- type: textarea
id: expected
attributes:
label: ✅ Expected Behavior
description: A clear and concise description of what you expected to happen.
placeholder: What should happen?
validations:
required: true
- type: textarea
id: actual
attributes:
label: ❌ Actual Behavior
description: A clear and concise description of what actually happened.
placeholder: What actually happened?
validations:
required: true
- type: textarea
id: environment
attributes:
label: 🌍 Environment
description: Please provide information about your environment
value: |
- OS: [e.g. Ubuntu 20.04, Windows 10, macOS 12.0]
- Node.js version: [e.g. 18.17.0]
- Evolution API version: [e.g. 2.3.5]
- Database: [e.g. PostgreSQL 14, MySQL 8.0]
- Connection type: [e.g. Baileys, WhatsApp Business API]
validations:
required: true
- type: textarea
id: logs
attributes:
label: 📋 Logs
description: If applicable, add logs to help explain your problem.
placeholder: Paste relevant logs here...
render: shell
- type: textarea
id: additional
attributes:
label: 📝 Additional Context
description: Add any other context about the problem here.
placeholder: Any additional information...

View File

@@ -0,0 +1,85 @@
name: ✨ Feature Request
description: Suggest a new feature or enhancement
title: "[FEATURE] "
labels: ["enhancement", "needs-triage"]
assignees: []
body:
- type: markdown
attributes:
value: |
Thanks for suggesting a new feature!
Please check our [Feature Requests on Canny](https://evolutionapi.canny.io/feature-requests) first.
- type: textarea
id: problem
attributes:
label: 🎯 Problem Statement
description: Is your feature request related to a problem? Please describe.
placeholder: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
validations:
required: true
- type: textarea
id: solution
attributes:
label: 💡 Proposed Solution
description: Describe the solution you'd like
placeholder: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: 🔄 Alternatives Considered
description: Describe alternatives you've considered
placeholder: A clear and concise description of any alternative solutions or features you've considered.
- type: dropdown
id: priority
attributes:
label: 📊 Priority
description: How important is this feature to you?
options:
- Low - Nice to have
- Medium - Would be helpful
- High - Important for my use case
- Critical - Blocking my work
validations:
required: true
- type: dropdown
id: component
attributes:
label: 🧩 Component
description: Which component does this feature relate to?
options:
- WhatsApp Integration (Baileys)
- WhatsApp Business API
- Chatwoot Integration
- Typebot Integration
- OpenAI Integration
- Dify Integration
- API Endpoints
- Database
- Authentication
- Webhooks
- File Storage
- Other
- type: textarea
id: use_case
attributes:
label: 🎯 Use Case
description: Describe your specific use case for this feature
placeholder: How would you use this feature? What problem does it solve for you?
validations:
required: true
- type: textarea
id: additional
attributes:
label: 📝 Additional Context
description: Add any other context, screenshots, or examples about the feature request here.
placeholder: Any additional information, mockups, or examples...

41
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,41 @@
## 📋 Description
<!-- Describe your changes in detail -->
## 🔗 Related Issue
<!-- Link to the issue this PR addresses -->
Closes #(issue_number)
## 🧪 Type of Change
<!-- Mark with an `x` all the checkboxes that apply -->
- [ ] 🐛 Bug fix (non-breaking change which fixes an issue)
- [ ] ✨ New feature (non-breaking change which adds functionality)
- [ ] 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] 📚 Documentation update
- [ ] 🔧 Refactoring (no functional changes)
- [ ] ⚡ Performance improvement
- [ ] 🧹 Code cleanup
- [ ] 🔒 Security fix
## 🧪 Testing
<!-- Describe the testing you performed to verify your changes -->
- [ ] Manual testing completed
- [ ] Functionality verified in development environment
- [ ] No breaking changes introduced
- [ ] Tested with different connection types (if applicable)
## 📸 Screenshots (if applicable)
<!-- Add screenshots to help explain your changes -->
## ✅ Checklist
<!-- Mark with an `x` all the checkboxes that apply -->
- [ ] My code follows the project's style guidelines
- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have manually tested my changes thoroughly
- [ ] I have verified the changes work with different scenarios
- [ ] Any dependent changes have been merged and published
## 📝 Additional Notes
<!-- Any additional information, concerns, or questions -->

View File

@@ -0,0 +1,42 @@
name: Check Code Quality
on:
pull_request:
branches: [ main, develop ]
push:
branches: [ main, develop ]
jobs:
check-lint-and-build:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v5
with:
submodules: recursive
- name: Install Node
uses: actions/setup-node@v5
with:
node-version: 20.x
- name: Cache node modules
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install packages
run: npm ci
- name: Check linting
run: npm run lint:check
- name: Generate Prisma client
run: npm run db:generate
- name: Check build
run: npm run build

View File

@@ -14,13 +14,15 @@ jobs:
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
submodules: recursive
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: atendai/evolution-api
images: evoapicloud/evolution-api
tags: type=semver,pattern=v{{version}}
- name: Set up QEMU
@@ -37,7 +39,7 @@ jobs:
- name: Build and push
id: docker_build
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/arm64
push: true

View File

@@ -14,13 +14,15 @@ jobs:
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
submodules: recursive
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: atendai/evolution-api
images: evoapicloud/evolution-api
tags: homolog
- name: Set up QEMU
@@ -37,7 +39,7 @@ jobs:
- name: Build and push
id: docker_build
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/arm64
push: true

View File

@@ -14,13 +14,15 @@ jobs:
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
submodules: recursive
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: atendai/evolution-api
images: evoapicloud/evolution-api
tags: latest
- name: Set up QEMU
@@ -37,7 +39,7 @@ jobs:
- name: Build and push
id: docker_build
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/arm64
push: true

55
.github/workflows/security.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: Security Scan
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
schedule:
- cron: '0 0 * * 1' # Weekly on Mondays
jobs:
codeql:
name: CodeQL Analysis
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
submodules: recursive
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"
dependency-review:
name: Dependency Review
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Checkout Repository
uses: actions/checkout@v5
with:
submodules: recursive
- name: Dependency Review
uses: actions/dependency-review-action@v4

5
.gitignore vendored
View File

@@ -1,11 +1,11 @@
# Repo
Baileys
# compiled output
/dist
/node_modules
/Docker/.env
.vscode
# Logs
logs/**.json
*.log
@@ -21,7 +21,6 @@ lerna-debug.log*
# Package
/yarn.lock
/pnpm-lock.yaml
/package-lock.json
# IDEs
.vscode/*

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "evolution-manager-v2"]
path = evolution-manager-v2
url = https://github.com/EvolutionAPI/evolution-manager-v2.git

51
.husky/README.md Normal file
View File

@@ -0,0 +1,51 @@
# Git Hooks Configuration
Este projeto usa [Husky](https://typicode.github.io/husky/) para automatizar verificações de qualidade de código.
## Hooks Configurados
### Pre-commit
- **Arquivo**: `.husky/pre-commit`
- **Executa**: `npx lint-staged`
- **Função**: Executa lint e correções automáticas apenas nos arquivos modificados
### Pre-push
- **Arquivo**: `.husky/pre-push`
- **Executa**: `npm run build` + `npm run lint:check`
- **Função**: Verifica se o projeto compila e não tem erros de lint antes do push
## Lint-staged Configuration
Configurado no `package.json`:
```json
"lint-staged": {
"src/**/*.{ts,js}": [
"eslint --fix",
"git add"
],
"src/**/*.ts": [
"npm run build"
]
}
```
## Como funciona
1. **Ao fazer commit**: Executa lint apenas nos arquivos modificados
2. **Ao fazer push**: Executa build completo e verificação de lint
3. **Se houver erros**: O commit/push é bloqueado até correção
## Comandos úteis
```bash
# Pular hooks (não recomendado)
git commit --no-verify
git push --no-verify
# Executar lint manualmente
npm run lint
# Executar build manualmente
npm run build
```

1
.husky/commit-msg Executable file
View File

@@ -0,0 +1 @@
npx --no -- commitlint --edit $1

1
.husky/pre-commit Normal file
View File

@@ -0,0 +1 @@
npx lint-staged

2
.husky/pre-push Executable file
View File

@@ -0,0 +1,2 @@
npm run build
npm run lint:check

355
AGENTS.md Normal file
View File

@@ -0,0 +1,355 @@
# Evolution API - AI Agent Guidelines
This document provides comprehensive guidelines for AI agents (Claude, GPT, Cursor, etc.) working with the Evolution API codebase.
## Project Overview
**Evolution API** is a production-ready, multi-tenant WhatsApp API platform built with Node.js, TypeScript, and Express.js. It supports multiple WhatsApp providers and extensive integrations with chatbots, CRM systems, and messaging platforms.
## Project Structure & Module Organization
### Core Directories
- **`src/`** TypeScript source code with modular architecture
- `api/controllers/` HTTP route handlers (thin layer)
- `api/services/` Business logic (core functionality)
- `api/routes/` Express route definitions (RouterBroker pattern)
- `api/integrations/` External service integrations
- `channel/` WhatsApp providers (Baileys, Business API, Evolution)
- `chatbot/` AI/Bot integrations (OpenAI, Dify, Typebot, Chatwoot)
- `event/` Event systems (WebSocket, RabbitMQ, SQS, NATS, Pusher)
- `storage/` File storage (S3, MinIO)
- `dto/` Data Transfer Objects (simple classes, no decorators)
- `guards/` Authentication/authorization middleware
- `types/` TypeScript type definitions
- `repository/` Data access layer (Prisma)
- **`prisma/`** Database schemas and migrations
- `postgresql-schema.prisma` / `mysql-schema.prisma` Provider-specific schemas
- `postgresql-migrations/` / `mysql-migrations/` Provider-specific migrations
- **`config/`** Environment and application configuration
- **`utils/`** Shared utilities and helper functions
- **`validate/`** JSONSchema7 validation schemas
- **`exceptions/`** Custom HTTP exception classes
- **`cache/`** Redis and local cache implementations
### Build & Deployment
- **`dist/`** Build output (do not edit directly)
- **`public/`** Static assets and media files
- **`Docker*`**, **`docker-compose*.yaml`** Containerization and local development stack
## Build, Test, and Development Commands
### Development Workflow
```bash
# Development server with hot reload
npm run dev:server
# Direct execution for testing
npm start
# Production build and run
npm run build
npm run start:prod
```
### Code Quality
```bash
# Linting and formatting
npm run lint # ESLint with auto-fix
npm run lint:check # ESLint check only
# Commit with conventional commits
npm run commit # Interactive commit with Commitizen
```
### Database Management
```bash
# Set database provider first (CRITICAL)
export DATABASE_PROVIDER=postgresql # or mysql
# Generate Prisma client
npm run db:generate
# Development migrations (with provider sync)
npm run db:migrate:dev # Unix/Mac
npm run db:migrate:dev:win # Windows
# Production deployment
npm run db:deploy # Unix/Mac
npm run db:deploy:win # Windows
# Database tools
npm run db:studio # Open Prisma Studio
```
### Docker Development
```bash
# Start local services (Redis, PostgreSQL, etc.)
docker-compose up -d
# Full development stack
docker-compose -f docker-compose.dev.yaml up -d
```
## Coding Standards & Architecture Patterns
### Code Style (Enforced by ESLint + Prettier)
- **TypeScript strict mode** with full type coverage
- **2-space indentation**, single quotes, trailing commas
- **120-character line limit**
- **Import order** via `simple-import-sort`
- **File naming**: `feature.kind.ts` (e.g., `whatsapp.baileys.service.ts`)
- **Naming conventions**:
- Classes: `PascalCase`
- Functions/variables: `camelCase`
- Constants: `UPPER_SNAKE_CASE`
- Files: `kebab-case.type.ts`
### Architecture Patterns
#### Service Layer Pattern
```typescript
export class ExampleService {
constructor(private readonly waMonitor: WAMonitoringService) {}
private readonly logger = new Logger('ExampleService');
public async create(instance: InstanceDto, data: ExampleDto) {
// Business logic here
return { example: { ...instance, data } };
}
public async find(instance: InstanceDto): Promise<ExampleDto | null> {
try {
const result = await this.waMonitor.waInstances[instance.instanceName].findData();
return result || null; // Return null on not found (Evolution pattern)
} catch (error) {
this.logger.error('Error finding data:', error);
return null; // Return null on error (Evolution pattern)
}
}
}
```
#### Controller Pattern (Thin Layer)
```typescript
export class ExampleController {
constructor(private readonly exampleService: ExampleService) {}
public async createExample(instance: InstanceDto, data: ExampleDto) {
return this.exampleService.create(instance, data);
}
}
```
#### RouterBroker Pattern
```typescript
export class ExampleRouter extends RouterBroker {
constructor(...guards: any[]) {
super();
this.router.post(this.routerPath('create'), ...guards, async (req, res) => {
const response = await this.dataValidate<ExampleDto>({
request: req,
schema: exampleSchema, // JSONSchema7
ClassRef: ExampleDto,
execute: (instance, data) => controller.createExample(instance, data),
});
res.status(201).json(response);
});
}
}
```
#### DTO Pattern (Simple Classes)
```typescript
// CORRECT - Evolution API pattern (no decorators)
export class ExampleDto {
name: string;
description?: string;
enabled: boolean;
}
// INCORRECT - Don't use class-validator decorators
export class BadExampleDto {
@IsString() // ❌ Evolution API doesn't use decorators
name: string;
}
```
#### Validation Pattern (JSONSchema7)
```typescript
import { JSONSchema7 } from 'json-schema';
import { v4 } from 'uuid';
export const exampleSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
name: { type: 'string' },
description: { type: 'string' },
enabled: { type: 'boolean' },
},
required: ['name', 'enabled'],
};
```
## Multi-Tenant Architecture
### Instance Isolation
- **CRITICAL**: All operations must be scoped by `instanceName` or `instanceId`
- **Database queries**: Always include `where: { instanceId: ... }`
- **Authentication**: Validate instance ownership before operations
- **Data isolation**: Complete separation between tenant instances
### WhatsApp Instance Management
```typescript
// Access instance via WAMonitoringService
const waInstance = this.waMonitor.waInstances[instance.instanceName];
if (!waInstance) {
throw new NotFoundException(`Instance ${instance.instanceName} not found`);
}
```
## Database Patterns
### Multi-Provider Support
- **PostgreSQL**: Uses `@db.Integer`, `@db.JsonB`, `@default(now())`
- **MySQL**: Uses `@db.Int`, `@db.Json`, `@default(now())`
- **Environment**: Set `DATABASE_PROVIDER=postgresql` or `mysql`
- **Migrations**: Provider-specific folders auto-selected
### Prisma Repository Pattern
```typescript
// Always use PrismaRepository for database operations
const result = await this.prismaRepository.instance.findUnique({
where: { name: instanceName },
});
```
## Integration Patterns
### Channel Integration (WhatsApp Providers)
- **Baileys**: WhatsApp Web with QR code authentication
- **Business API**: Official Meta WhatsApp Business API
- **Evolution API**: Custom WhatsApp integration
- **Pattern**: Extend base channel service classes
### Chatbot Integration
- **Base classes**: Extend `BaseChatbotService` and `BaseChatbotController`
- **Trigger system**: Support keyword, regex, and advanced triggers
- **Session management**: Handle conversation state per user
- **Available integrations**: EvolutionBot, OpenAI, Dify, Typebot, Chatwoot, Flowise, N8N, EvoAI
### Event Integration
- **Internal events**: EventEmitter2 for application events
- **External events**: WebSocket, RabbitMQ, SQS, NATS, Pusher
- **Webhook delivery**: Reliable delivery with retry logic
## Testing Guidelines
### Current State
- **No formal test suite** currently implemented
- **Manual testing** is the primary approach
- **Integration testing** in development environment
### Testing Strategy
```typescript
// Place tests in test/ directory as *.test.ts
// Run: npm test (watches test/all.test.ts)
describe('ExampleService', () => {
it('should create example', async () => {
// Mock external dependencies
// Test business logic
// Assert expected behavior
});
});
```
### Recommended Approach
- Focus on **critical business logic** in services
- **Mock external dependencies** (WhatsApp APIs, databases)
- **Integration tests** for API endpoints
- **Manual testing** for WhatsApp connection flows
## Commit & Pull Request Guidelines
### Conventional Commits (Enforced by commitlint)
```bash
# Use interactive commit tool
npm run commit
# Commit format: type(scope): subject (max 100 chars)
# Types: feat, fix, docs, style, refactor, perf, test, chore, ci, build, revert, security
```
### Examples
- `feat(api): add WhatsApp message status endpoint`
- `fix(baileys): resolve connection timeout issue`
- `docs(readme): update installation instructions`
- `refactor(service): extract common message validation logic`
### Pull Request Requirements
- **Clear description** of changes and motivation
- **Linked issues** if applicable
- **Migration impact** (specify database provider)
- **Local testing steps** with screenshots/logs
- **Breaking changes** clearly documented
## Security & Configuration
### Environment Setup
```bash
# Copy example environment file
cp .env.example .env
# NEVER commit secrets to version control
# Set DATABASE_PROVIDER before database commands
export DATABASE_PROVIDER=postgresql # or mysql
```
### Security Best Practices
- **API key authentication** via `apikey` header
- **Input validation** with JSONSchema7
- **Rate limiting** on all endpoints
- **Webhook signature validation**
- **Instance-based access control**
- **Secure defaults** for all configurations
### Vulnerability Reporting
- See `SECURITY.md` for security vulnerability reporting process
- Contact: `contato@evolution-api.com`
## Communication Standards
### Language Requirements
- **User communication**: Always respond in Portuguese (PT-BR)
- **Code/comments**: English for technical documentation
- **API responses**: English for consistency
- **Error messages**: Portuguese for user-facing errors
### Documentation Standards
- **Inline comments**: Document complex business logic
- **API documentation**: Document all public endpoints
- **Integration guides**: Document new integration patterns
- **Migration guides**: Document database schema changes
## Performance & Scalability
### Caching Strategy
- **Redis primary**: Distributed caching for production
- **Node-cache fallback**: Local caching when Redis unavailable
- **TTL strategy**: Appropriate cache expiration per data type
- **Cache invalidation**: Proper invalidation on data changes
### Connection Management
- **Database**: Prisma connection pooling
- **WhatsApp**: One connection per instance with lifecycle management
- **Redis**: Connection pooling and retry logic
- **External APIs**: Rate limiting and retry with exponential backoff
### Monitoring & Observability
- **Structured logging**: Pino logger with correlation IDs
- **Error tracking**: Comprehensive error scenarios
- **Health checks**: Instance status and connection monitoring
- **Telemetry**: Usage analytics (non-sensitive data only)

View File

@@ -1,4 +1,245 @@
# 2.2.0 (develop)
# 2.3.5 (2025-10-15)
### Features
* **Chatwoot Enhancements**: Comprehensive improvements to message handling, editing, deletion and i18n
* **Participants Data**: Add participantsData field maintaining backward compatibility for group participants
* **LID to Phone Number**: Convert LID to phoneNumber on group participants
* **Docker Configurations**: Add Kafka and frontend services to Docker configurations
### Fixed
* **Kafka Migration**: Fixed PostgreSQL migration error for Kafka integration
- Corrected table reference from `"public"."Instance"` to `"Instance"` in foreign key constraint
- Fixed `ERROR: relation "public.Instance" does not exist` issue in migration `20250918182355_add_kafka_integration`
- Aligned table naming convention with other Evolution API migrations for consistency
- Resolved database migration failure that prevented Kafka integration setup
* **Update Baileys Version**: v7.0.0-rc.5 with compatibility fixes
- Fixed assertSessions signature compatibility using type assertion
- Fixed incompatibility in voice call (wavoip) with new Baileys version
- Handle undefined status in update by defaulting to 'DELETED'
* **Chatwoot Improvements**: Multiple fixes for enhanced reliability
- Correct chatId extraction for non-group JIDs
- Resolve webhook timeout on deletion with 5+ images
- Improve error handling in Chatwoot messages
- Adjust conversation verification logic and cache
- Optimize conversation reopening logic and connection notification
- Fix conversation reopening and connection loop
* **Baileys Message Handling**: Enhanced message processing
- Add warning log for messages not found
- Fix message verification in Baileys service
- Simplify linkPreview handling in BaileysStartupService
* **Media Validation**: Fix media content validation
* **PostgreSQL Connection**: Refactor connection with PostgreSQL and improve message handling
### Code Quality & Refactoring
* **Exponential Backoff**: Implement exponential backoff patterns and extract magic numbers to constants
* **TypeScript Build**: Update TypeScript build process and dependencies
###
# 2.3.4 (2025-09-23)
### Features
* **Kafka Integration**: Added Apache Kafka event integration for real-time event streaming
- New Kafka controller, router, and schema for event publishing
- Support for instance-specific and global event topics
- Configurable SASL/SSL authentication and connection settings
- Auto-creation of topics with configurable partitions and replication
- Consumer group management for reliable event processing
- Integration with existing event manager for seamless event distribution
* **Evolution Manager v2 Open Source**: Evolution Manager v2 is now available as open source
- Added as git submodule with HTTPS URL for easy access
- Complete open source setup with Apache 2.0 license + Evolution API custom conditions
- GitHub templates for issues, pull requests, and workflows
- Comprehensive documentation and contribution guidelines
- Docker support for development and production environments
- CI/CD workflows for code quality, security audits, and automated builds
- Multi-language support (English, Portuguese, Spanish, French)
- Modern React + TypeScript + Vite frontend with Tailwind CSS
* **EvolutionBot Enhancements**: Improved EvolutionBot functionality and message handling
- Implemented splitMessages functionality for better message segmentation
- Added linkPreview support for enhanced message presentation
- Centralized split logic across chatbot services for consistency
- Enhanced message formatting and delivery capabilities
### Fixed
* **MySQL Schema**: Fixed invalid default value errors for `createdAt` fields in `Evoai` and `EvoaiSetting` models
- Changed `@default(now())` to `@default(dbgenerated("CURRENT_TIMESTAMP"))` for MySQL compatibility
- Added missing relation fields (`N8n`, `N8nSetting`, `Evoai`, `EvoaiSetting`) in Instance model
- Resolved Prisma schema validation errors for MySQL provider
* **Prisma Schema Validation**: Fixed `instanceName` field error in message creation
- Removed invalid `instanceName` field from message objects before database insertion
- Resolved `Unknown argument 'instanceName'` Prisma validation error
- Streamlined message data structure to match Prisma schema requirements
* **Media Message Processing**: Enhanced media handling across chatbot services
- Fixed base64 conversion in EvoAI service for proper image processing
- Converted ArrayBuffer to base64 string using `Buffer.from().toString('base64')`
- Improved media URL handling and base64 encoding for better chatbot integration
- Enhanced image message detection and processing workflow
* **Evolution Manager v2 Linting**: Resolved ESLint configuration conflicts
- Disabled conflicting Prettier rules in ESLint configuration
- Added comprehensive rule overrides for TypeScript and React patterns
- Fixed import ordering and code formatting issues
- Updated security vulnerabilities in dependencies (Vite, esbuild)
### Code Quality & Refactoring
* **Chatbot Services**: Streamlined media message handling across all chatbot integrations
- Standardized base64 and mediaUrl processing patterns
- Improved code readability and maintainability in media handling logic
- Enhanced error handling for media download and conversion processes
- Unified image message detection across different chatbot services
* **Database Operations**: Improved data consistency and validation
- Enhanced Prisma schema compliance across all message operations
- Removed redundant instance name references for better data integrity
- Optimized message creation workflow with proper field validation
### Environment Variables
* Added comprehensive Kafka configuration options:
- `KAFKA_ENABLED`, `KAFKA_CLIENT_ID`, `KAFKA_BROKERS`
- `KAFKA_CONSUMER_GROUP_ID`, `KAFKA_TOPIC_PREFIX`
- `KAFKA_SASL_*` and `KAFKA_SSL_*` for authentication
- `KAFKA_EVENTS_*` for event type configuration
# 2.3.3 (2025-09-18)
### Features
* Add extra fields to object sent to Flowise bot
* Add Prometheus-compatible /metrics endpoint (gated by PROMETHEUS_METRICS)
* Implement linkPreview support for Evolution Bot
### Fixed
* Address Path Traversal vulnerability in /assets endpoint by implementing security checks
* Configure Husky and lint-staged for automated code quality checks on commits and pushes
* Convert mediaKey from media messages to avoid bad decrypt errors
* Improve code formatting for better readability in WhatsApp service files
* Format messageGroupId assignment for improved readability
* Improve linkPreview implementation based on PR feedback
* Clean up code formatting for linkPreview implementation
* Use 'unknown' as fallback for clientName label
* Remove abort process when status is paused, allowing the chatbot return after the time expires and after being paused due to human interaction (stopBotFromMe)
* Enhance message content sanitization in Baileys service and improve message retrieval logic in Chatwoot service
* Integrate Typebot status change events for webhook in chatbot controller and service
* Mimetype of videos video
### Security
* **CRITICAL**: Fixed Path Traversal vulnerability in /assets endpoint that allowed unauthenticated local file read
* Customizable Websockets Security
### Testing
* Baileys Updates: v7.0.0-rc.3 ([Link](https://github.com/WhiskeySockets/Baileys/releases/tag/v7.0.0-rc.3))
# 2.3.2 (2025-09-02)
### Features
* Add support to socks proxy
### Fixed
* Added key id into webhook payload in n8n service
* Enhance RabbitMQ controller with improved connection management and shutdown procedures
* Convert outgoing images to JPEG before sending with Chatwoot
* Update baileys dependency to version 6.7.19
# 2.3.1 (2025-07-29)
### Feature
* Add BaileysMessageProcessor for improved message handling and integrate rxjs for asynchronous processing
* Enhance message processing with retry logic for error handling
### Fixed
* Update Baileys Version
* Update Dockerhub Repository and Delete Config Session Variable
* Fixed sending variables in typebot
* Add unreadMessages in the response
* Phone number as message ID for Evo AI
* Fix upload to s3 when media message
* Simplify edited message check in BaileysStartupService
* Avoid corrupting URLs with query strings
* Removed CONFIG_SESSION_PHONE_VERSION environment variable
# 2.3.0 (2025-06-17 09:19)
### Feature
* Add support to get Catalogs and Collections with new routes: '{{baseUrl}}/chat/fetchCatalogs' and '{{baseUrl}}/chat/fetchCollections'
* Add NATS integration support to the event system
* Add message location support meta
* Add S3_SKIP_POLICY env variable to disable setBucketPolicy for incompatible providers
* Add EvoAI integration with models, services, and routes
* Add N8n integration with models, services, and routes
### Fixed
* Shell injection vulnerability
* Update Baileys Version v6.7.18
* Audio send duplicate from chatwoot
* Chatwoot csat creating new conversation in another language
* Refactor SQS controller to correct bug in sqs events by instance
* Adjustin cloud api send audio and video
* Preserve animation in GIF and WebP stickers
* Preventing use conversation from other inbox for the same user
* Ensure full WhatsApp compatibility for audio conversion (libopus, 48kHz, mono)
* Enhance message fetching and processing logic
* Added lid on whatsapp numbers router
* Now if the CONFIG_SESSION_PHONE_VERSION variable is not filled in it automatically searches for the most updated version
### Security
* Change execSync to execFileSync
* Enhance WebSocket authentication and connection handling
# 2.2.3 (2025-02-03 11:52)
### Fixed
* Fix cache in local file system
* Update Baileys Version
# 2.2.2 (2025-01-31 06:55)
### Features
* Added prefix key to queue name in RabbitMQ
### Fixed
* Update Baileys Version
# 2.2.1 (2025-01-22 14:37)
### Features
* Retry system for send webhooks
* Message filtering to support timestamp range queries
* Chats filtering to support timestamp range queries
### Fixed
* Correction of webhook global
* Fixed send audio with whatsapp cloud api
* Refactor on fetch chats
* Refactor on Evolution Channel
# 2.2.0 (2024-10-18 10:00)
### Features

223
CLAUDE.md Normal file
View File

@@ -0,0 +1,223 @@
# CLAUDE.md
This file provides comprehensive guidance to Claude AI when working with the Evolution API codebase.
## Project Overview
**Evolution API** is a powerful, production-ready REST API for WhatsApp communication that supports multiple WhatsApp providers:
- **Baileys** (WhatsApp Web) - Open-source WhatsApp Web client
- **Meta Business API** - Official WhatsApp Business API
- **Evolution API** - Custom WhatsApp integration
Built with **Node.js 20+**, **TypeScript 5+**, and **Express.js**, it provides extensive integrations with chatbots, CRM systems, and messaging platforms in a **multi-tenant architecture**.
## Common Development Commands
### Build and Run
```bash
# Development
npm run dev:server # Run in development with hot reload (tsx watch)
# Production
npm run build # TypeScript check + tsup build
npm run start:prod # Run production build
# Direct execution
npm start # Run with tsx
```
### Code Quality
```bash
npm run lint # ESLint with auto-fix
npm run lint:check # ESLint check only
npm run commit # Interactive commit with commitizen
```
### Database Management
```bash
# Set database provider first
export DATABASE_PROVIDER=postgresql # or mysql
# Generate Prisma client (automatically uses DATABASE_PROVIDER env)
npm run db:generate
# Deploy migrations (production)
npm run db:deploy # Unix/Mac
npm run db:deploy:win # Windows
# Development migrations (with sync to provider folder)
npm run db:migrate:dev # Unix/Mac
npm run db:migrate:dev:win # Windows
# Open Prisma Studio
npm run db:studio
# Development migrations
npm run db:migrate:dev # Unix/Mac
npm run db:migrate:dev:win # Windows
```
### Testing
```bash
npm test # Run tests with watch mode
```
## Architecture Overview
### Core Structure
- **Multi-tenant SaaS**: Complete instance isolation with per-tenant authentication
- **Multi-provider database**: PostgreSQL and MySQL via Prisma ORM with provider-specific schemas and migrations
- **WhatsApp integrations**: Baileys, Meta Business API, and Evolution API with unified interface
- **Event-driven architecture**: EventEmitter2 for internal events + WebSocket, RabbitMQ, SQS, NATS, Pusher for external events
- **Microservices pattern**: Modular integrations for chatbots, storage, and external services
### Directory Layout
```
src/
├── api/
│ ├── controllers/ # HTTP route handlers (thin layer)
│ ├── services/ # Business logic (core functionality)
│ ├── repository/ # Data access layer (Prisma)
│ ├── dto/ # Data Transfer Objects (simple classes)
│ ├── guards/ # Authentication/authorization middleware
│ ├── integrations/ # External service integrations
│ │ ├── channel/ # WhatsApp providers (Baileys, Business API, Evolution)
│ │ ├── chatbot/ # AI/Bot integrations (OpenAI, Dify, Typebot, Chatwoot)
│ │ ├── event/ # Event systems (WebSocket, RabbitMQ, SQS, NATS, Pusher)
│ │ └── storage/ # File storage (S3, MinIO)
│ ├── routes/ # Express route definitions (RouterBroker pattern)
│ └── types/ # TypeScript type definitions
├── config/ # Environment and app configuration
├── cache/ # Redis and local cache implementations
├── exceptions/ # Custom HTTP exception classes
├── utils/ # Shared utilities and helpers
└── validate/ # JSONSchema7 validation schemas
```
### Key Integration Points
**Channel Integrations** (`src/api/integrations/channel/`):
- **Baileys**: WhatsApp Web client with QR code authentication
- **Business API**: Official Meta WhatsApp Business API
- **Evolution API**: Custom WhatsApp integration
- Connection lifecycle management per instance with automatic reconnection
**Chatbot Integrations** (`src/api/integrations/chatbot/`):
- **EvolutionBot**: Native chatbot with trigger system
- **Chatwoot**: Customer service platform integration
- **Typebot**: Visual chatbot flow builder
- **OpenAI**: AI capabilities including GPT and Whisper (audio transcription)
- **Dify**: AI agent workflow platform
- **Flowise**: LangChain visual builder
- **N8N**: Workflow automation platform
- **EvoAI**: Custom AI integration
**Event Integrations** (`src/api/integrations/event/`):
- **WebSocket**: Real-time Socket.io connections
- **RabbitMQ**: Message queue for async processing
- **Amazon SQS**: Cloud-based message queuing
- **NATS**: High-performance messaging system
- **Pusher**: Real-time push notifications
**Storage Integrations** (`src/api/integrations/storage/`):
- **AWS S3**: Cloud object storage
- **MinIO**: Self-hosted S3-compatible storage
- Media file management and URL generation
### Database Schema Management
- Separate schema files: `postgresql-schema.prisma` and `mysql-schema.prisma`
- Environment variable `DATABASE_PROVIDER` determines active database
- Migration folders are provider-specific and auto-selected during deployment
### Authentication & Security
- **API key-based authentication** via `apikey` header (global or per-instance)
- **Instance-specific tokens** for WhatsApp connection authentication
- **Guards system** for route protection and authorization
- **Input validation** using JSONSchema7 with RouterBroker `dataValidate`
- **Rate limiting** and security middleware
- **Webhook signature validation** for external integrations
## Important Implementation Details
### WhatsApp Instance Management
- Each WhatsApp connection is an "instance" with unique name
- Instance data stored in database with connection state
- Session persistence in database or file system (configurable)
- Automatic reconnection handling with exponential backoff
### Message Queue Architecture
- Supports RabbitMQ, Amazon SQS, and WebSocket for events
- Event types: message.received, message.sent, connection.update, etc.
- Configurable per instance which events to send
### Media Handling
- Local storage or S3/Minio for media files
- Automatic media download from WhatsApp
- Media URL generation for external access
- Support for audio transcription via OpenAI
### Multi-tenancy Support
- Instance isolation at database level
- Separate webhook configurations per instance
- Independent integration settings per instance
## Environment Configuration
Key environment variables are defined in `.env.example`. The system uses a strongly-typed configuration system via `src/config/env.config.ts`.
Critical configurations:
- `DATABASE_PROVIDER`: postgresql or mysql
- `DATABASE_CONNECTION_URI`: Database connection string
- `AUTHENTICATION_API_KEY`: Global API authentication
- `REDIS_ENABLED`: Enable Redis cache
- `RABBITMQ_ENABLED`/`SQS_ENABLED`: Message queue options
## Development Guidelines
The project follows comprehensive development standards defined in `.cursor/rules/`:
### Core Principles
- **Always respond in Portuguese (PT-BR)** for user communication
- **Follow established architecture patterns** (Service Layer, RouterBroker, etc.)
- **Robust error handling** with retry logic and graceful degradation
- **Multi-database compatibility** (PostgreSQL and MySQL)
- **Security-first approach** with input validation and rate limiting
- **Performance optimizations** with Redis caching and connection pooling
### Code Standards
- **TypeScript strict mode** with full type coverage
- **JSONSchema7** for input validation (not class-validator)
- **Conventional Commits** enforced by commitlint
- **ESLint + Prettier** for code formatting
- **Service Object pattern** for business logic
- **RouterBroker pattern** for route handling with `dataValidate`
### Architecture Patterns
- **Multi-tenant isolation** at database and instance level
- **Event-driven communication** with EventEmitter2
- **Microservices integration** pattern for external services
- **Connection pooling** and lifecycle management
- **Caching strategy** with Redis primary and Node-cache fallback
## Testing Approach
Currently, the project has minimal formal testing infrastructure:
- **Manual testing** is the primary approach
- **Integration testing** in development environment
- **No unit test suite** currently implemented
- Test files can be placed in `test/` directory as `*.test.ts`
- Run `npm test` for watch mode development testing
### Recommended Testing Strategy
- Focus on **critical business logic** in services
- **Mock external dependencies** (WhatsApp APIs, databases)
- **Integration tests** for API endpoints
- **Manual testing** for WhatsApp connection flows
## Deployment Considerations
- Docker support with `Dockerfile` and `docker-compose.yaml`
- Graceful shutdown handling for connections
- Health check endpoints for monitoring
- Sentry integration for error tracking
- Telemetry for usage analytics (non-sensitive data only)

View File

@@ -0,0 +1,51 @@
version: '3.3'
services:
zookeeper:
container_name: zookeeper
image: confluentinc/cp-zookeeper:7.5.0
environment:
- ZOOKEEPER_CLIENT_PORT=2181
- ZOOKEEPER_TICK_TIME=2000
- ZOOKEEPER_SYNC_LIMIT=2
volumes:
- zookeeper_data:/var/lib/zookeeper/
ports:
- 2181:2181
kafka:
container_name: kafka
image: confluentinc/cp-kafka:7.5.0
depends_on:
- zookeeper
environment:
- KAFKA_BROKER_ID=1
- KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181
- KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT,OUTSIDE:PLAINTEXT
- KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092,OUTSIDE://host.docker.internal:9094
- KAFKA_INTER_BROKER_LISTENER_NAME=PLAINTEXT
- KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1
- KAFKA_TRANSACTION_STATE_LOG_MIN_ISR=1
- KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=1
- KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS=0
- KAFKA_AUTO_CREATE_TOPICS_ENABLE=true
- KAFKA_LOG_RETENTION_HOURS=168
- KAFKA_LOG_SEGMENT_BYTES=1073741824
- KAFKA_LOG_RETENTION_CHECK_INTERVAL_MS=300000
- KAFKA_COMPRESSION_TYPE=gzip
ports:
- 29092:29092
- 9092:9092
- 9094:9094
volumes:
- kafka_data:/var/lib/kafka/data
volumes:
zookeeper_data:
kafka_data:
networks:
evolution-net:
name: evolution-net
driver: bridge

View File

@@ -6,7 +6,7 @@ if [ "$DOCKER_ENV" != "true" ]; then
export_env_vars
fi
if [[ "$DATABASE_PROVIDER" == "postgresql" || "$DATABASE_PROVIDER" == "mysql" ]]; then
if [[ "$DATABASE_PROVIDER" == "postgresql" || "$DATABASE_PROVIDER" == "mysql" || "$DATABASE_PROVIDER" == "psql_bouncer" ]]; then
export DATABASE_URL
echo "Deploying migrations for $DATABASE_PROVIDER"
echo "Database URL: $DATABASE_URL"

View File

@@ -6,7 +6,7 @@ if [ "$DOCKER_ENV" != "true" ]; then
export_env_vars
fi
if [[ "$DATABASE_PROVIDER" == "postgresql" || "$DATABASE_PROVIDER" == "mysql" ]]; then
if [[ "$DATABASE_PROVIDER" == "postgresql" || "$DATABASE_PROVIDER" == "mysql" || "$DATABASE_PROVIDER" == "psql_bouncer" ]]; then
export DATABASE_URL
echo "Generating database for $DATABASE_PROVIDER"
echo "Database URL: $DATABASE_URL"
@@ -20,4 +20,4 @@ if [[ "$DATABASE_PROVIDER" == "postgresql" || "$DATABASE_PROVIDER" == "mysql" ]]
else
echo "Error: Database provider $DATABASE_PROVIDER invalid."
exit 1
fi
fi

View File

@@ -2,7 +2,7 @@ version: "3.7"
services:
evolution_v2:
image: atendai/evolution-api:v2.1.2
image: evoapicloud/evolution-api:v2.3.5
volumes:
- evolution_instances:/evolution/instances
networks:
@@ -34,6 +34,7 @@ services:
- RABBITMQ_EVENTS_MESSAGES_UPDATE=false
- RABBITMQ_EVENTS_MESSAGES_DELETE=false
- RABBITMQ_EVENTS_SEND_MESSAGE=false
- RABBITMQ_EVENTS_SEND_MESSAGE_UPDATE=false
- RABBITMQ_EVENTS_CONTACTS_SET=false
- RABBITMQ_EVENTS_CONTACTS_UPSERT=false
- RABBITMQ_EVENTS_CONTACTS_UPDATE=false
@@ -71,6 +72,7 @@ services:
- WEBHOOK_EVENTS_MESSAGES_UPDATE=true
- WEBHOOK_EVENTS_MESSAGES_DELETE=true
- WEBHOOK_EVENTS_SEND_MESSAGE=true
- WEBHOOK_EVENTS_SEND_MESSAGE_UPDATE=true
- WEBHOOK_EVENTS_CONTACTS_SET=true
- WEBHOOK_EVENTS_CONTACTS_UPSERT=true
- WEBHOOK_EVENTS_CONTACTS_UPDATE=true
@@ -92,7 +94,6 @@ services:
- WEBHOOK_EVENTS_ERRORS_WEBHOOK=
- CONFIG_SESSION_PHONE_CLIENT=Evolution API V2
- CONFIG_SESSION_PHONE_NAME=Chrome
- CONFIG_SESSION_PHONE_VERSION=2.3000.1015901307
- QRCODE_LIMIT=30
- OPENAI_ENABLED=true
- DIFY_ENABLED=true

View File

@@ -1,17 +1,19 @@
FROM node:20-alpine AS builder
FROM node:24-alpine AS builder
RUN apk update && \
apk add git ffmpeg wget curl bash
apk add --no-cache git ffmpeg wget curl bash openssl
LABEL version="2.2.0" description="Api to control whatsapp features through http requests."
LABEL version="2.3.1" description="Api to control whatsapp features through http requests."
LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
LABEL contact="contato@atendai.com"
LABEL contact="contato@evolution-api.com"
WORKDIR /evolution
COPY ./package.json ./tsconfig.json ./
COPY ./package*.json ./
COPY ./tsconfig.json ./
COPY ./tsup.config.ts ./
RUN npm install -f
RUN npm ci --silent
COPY ./src ./src
COPY ./public ./public
@@ -19,7 +21,6 @@ COPY ./prisma ./prisma
COPY ./manager ./manager
COPY ./.env.example ./.env
COPY ./runWithProvider.js ./
COPY ./tsup.config.ts ./
COPY ./Docker ./Docker
@@ -29,12 +30,13 @@ RUN ./Docker/scripts/generate_database.sh
RUN npm run build
FROM node:20-alpine AS final
FROM node:24-alpine AS final
RUN apk update && \
apk add tzdata ffmpeg bash
apk add tzdata ffmpeg bash openssl
ENV TZ=America/Sao_Paulo
ENV DOCKER_ENV=true
WORKDIR /evolution
@@ -55,4 +57,4 @@ ENV DOCKER_ENV=true
EXPOSE 8080
ENTRYPOINT ["/bin/bash", "-c", ". ./Docker/scripts/deploy_database.sh && npm run start:prod" ]
ENTRYPOINT ["/bin/bash", "-c", ". ./Docker/scripts/deploy_database.sh && npm run start:prod" ]

19
Dockerfile.metrics Normal file
View File

@@ -0,0 +1,19 @@
FROM evoapicloud/evolution-api:latest AS base
WORKDIR /evolution
# Copiamos apenas o necessário para recompilar o dist com as mudanças locais
COPY tsconfig.json tsup.config.ts package.json ./
COPY src ./src
# Recompila usando os node_modules já presentes na imagem base
RUN npm run build
# Runtime final: reaproveita a imagem oficial e apenas sobrepõe o dist
FROM evoapicloud/evolution-api:latest AS final
WORKDIR /evolution
COPY --from=base /evolution/dist ./dist
ENV PROMETHEUS_METRICS=true
# Entrada original da imagem oficial já sobe o app em /evolution

View File

@@ -8,7 +8,7 @@ a. LOGO and copyright information: In the process of using Evolution API's front
b. Usage Notification Requirement: If Evolution API is used as part of any project, including closed-source systems (e.g., proprietary software), the user is required to display a clear notification within the system that Evolution API is being utilized. This notification should be visible to system administrators and accessible from the system's documentation or settings page. Failure to comply with this requirement may result in the necessity for a commercial license, as determined by the producer.
Please contact contato@atendai.com to inquire about licensing matters.
Please contact contato@evolution-api.com to inquire about licensing matters.
2. As a contributor, you should agree that:
@@ -17,5 +17,5 @@ b. Your contributed code may be used for commercial purposes, including but not
Apart from the specific conditions mentioned above, all other rights and restrictions follow the Apache License 2.0. Detailed information about the Apache License 2.0 can be found at http://www.apache.org/licenses/LICENSE-2.0.
© 2024 Evolution API
© 2025 Evolution API

View File

@@ -2,10 +2,14 @@
<div align="center">
[![Docker Image](https://img.shields.io/badge/Docker-image-blue)](https://hub.docker.com/r/evoapicloud/evolution-api)
[![Whatsapp Group](https://img.shields.io/badge/Group-WhatsApp-%2322BC18)](https://evolution-api.com/whatsapp)
[![Discord Community](https://img.shields.io/badge/Discord-Community-blue)](https://evolution-api.com/discord)
[![Postman Collection](https://img.shields.io/badge/Postman-Collection-orange)](https://evolution-api.com/postman)
[![Documentation](https://img.shields.io/badge/Documentation-Official-green)](https://doc.evolution-api.com)
[![Feature Requests](https://img.shields.io/badge/Feature-Requests-purple)](https://evolutionapi.canny.io/feature-requests)
[![Roadmap](https://img.shields.io/badge/Roadmap-Community-blue)](https://evolutionapi.canny.io/feature-requests)
[![Changelog](https://img.shields.io/badge/Changelog-Updates-green)](https://evolutionapi.canny.io/changelog)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue)](./LICENSE)
[![Support](https://img.shields.io/badge/Donation-picpay-green)](https://app.picpay.com/user/davidsongomes1998)
[![Sponsors](https://img.shields.io/badge/Github-sponsor-orange)](https://github.com/sponsors/EvolutionAPI)
@@ -51,6 +55,9 @@ Evolution API supports various integrations to enhance its functionality. Below
- [RabbitMQ](https://www.rabbitmq.com/):
- Receive events from the Evolution API via RabbitMQ.
- [Apache Kafka](https://kafka.apache.org/):
- Receive events from the Evolution API via Apache Kafka for real-time event streaming and processing.
- [Amazon SQS](https://aws.amazon.com/pt/sqs/):
- Receive events from the Evolution API via Amazon SQS.
@@ -66,6 +73,24 @@ Evolution API supports various integrations to enhance its functionality. Below
- Amazon S3 / Minio:
- Store media files received in [Amazon S3](https://aws.amazon.com/pt/s3/) or [Minio](https://min.io/).
## Community & Feedback
We value community input and feedback to continuously improve Evolution API:
### 🚀 Feature Requests & Roadmap
- **[Feature Requests](https://evolutionapi.canny.io/feature-requests)**: Submit new feature ideas and vote on community proposals
- **[Roadmap](https://evolutionapi.canny.io/feature-requests)**: View planned features and development progress
- **[Changelog](https://evolutionapi.canny.io/changelog)**: Stay updated with the latest releases and improvements
### 💬 Community Support
- **[WhatsApp Group](https://evolution-api.com/whatsapp)**: Join our community for support and discussions
- **[Discord Community](https://evolution-api.com/discord)**: Real-time chat with developers and users
- **[GitHub Issues](https://github.com/EvolutionAPI/evolution-api/issues)**: Report bugs and technical issues
### 🔒 Security
- **[Security Policy](./SECURITY.md)**: Guidelines for reporting security vulnerabilities
- **Security Contact**: contato@evolution-api.com
## Telemetry Notice
To continuously improve our services, we have implemented telemetry that collects data on the routes used, the most accessed routes, and the version of the API in use. We would like to assure you that no sensitive or personal data is collected during this process. The telemetry helps us identify improvements and provide a better experience for users.
@@ -87,6 +112,7 @@ https://github.com/sponsors/EvolutionAPI
We are proud to collaborate with the following content creators who have contributed valuable insights and tutorials about Evolution API:
- [Promovaweb](https://www.youtube.com/@promovaweb)
- [Sandeco](https://www.youtube.com/@canalsandeco)
- [Comunidade ZDG](https://www.youtube.com/@ComunidadeZDG)
- [Francis MNO](https://www.youtube.com/@FrancisMNO)
- [Pablo Cabral](https://youtube.com/@pablocabral)
@@ -111,8 +137,8 @@ Evolution API is licensed under the Apache License 2.0, with the following addit
2. **Usage Notification Requirement**: If Evolution API is used as part of any project, including closed-source systems (e.g., proprietary software), the user is required to display a clear notification within the system that Evolution API is being utilized. This notification should be visible to system administrators and accessible from the system's documentation or settings page. Failure to comply with this requirement may result in the necessity for a commercial license, as determined by the producer.
Please contact contato@atendai.com to inquire about licensing matters.
Please contact contato@evolution-api.com to inquire about licensing matters.
Apart from the specific conditions mentioned above, all other rights and restrictions follow the Apache License 2.0. Detailed information about the Apache License 2.0 can be found at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0).
© 2024 Evolution API
© 2025 Evolution API

99
SECURITY.md Normal file
View File

@@ -0,0 +1,99 @@
# Security Policy
## Supported Versions
We actively support the following versions of Evolution API with security updates:
| Version | Supported |
| ------- | ------------------ |
| 2.3.x | ✅ Yes |
| 2.2.x | ✅ Yes |
| 2.1.x | ⚠️ Critical fixes only |
| < 2.1 | ❌ No |
## Reporting a Vulnerability
We take security vulnerabilities seriously. If you discover a security vulnerability in Evolution API, please help us by reporting it responsibly.
### 🔒 Private Disclosure Process
**Please DO NOT create a public GitHub issue for security vulnerabilities.**
Instead, please report security vulnerabilities via email to:
**📧 contato@evolution-api.com**
### 📋 What to Include
When reporting a vulnerability, please include:
- **Description**: A clear description of the vulnerability
- **Impact**: What an attacker could achieve by exploiting this vulnerability
- **Steps to Reproduce**: Detailed steps to reproduce the issue
- **Proof of Concept**: If possible, include a minimal proof of concept
- **Environment**: Version of Evolution API, OS, Node.js version, etc.
- **Suggested Fix**: If you have ideas for how to fix the issue
### 🕐 Response Timeline
We will acknowledge receipt of your vulnerability report within **48 hours** and will send you regular updates about our progress.
- **Initial Response**: Within 48 hours
- **Status Update**: Within 7 days
- **Resolution Timeline**: Varies based on complexity, typically 30-90 days
### 🎯 Scope
This security policy applies to:
- Evolution API core application
- Official Docker images
- Documentation that could lead to security issues
### 🚫 Out of Scope
The following are generally considered out of scope:
- Third-party integrations (Chatwoot, Typebot, etc.) - please report to respective projects
- Issues in dependencies - please report to the dependency maintainers
- Social engineering attacks
- Physical attacks
- Denial of Service attacks
### 🏆 Recognition
We believe in recognizing security researchers who help us keep Evolution API secure:
- We will acknowledge your contribution in our security advisories (unless you prefer to remain anonymous)
- For significant vulnerabilities, we may feature you in our Hall of Fame
- We will work with you on coordinated disclosure timing
### 📚 Security Best Practices
For users deploying Evolution API:
- Always use the latest supported version
- Keep your dependencies up to date
- Use strong authentication methods
- Implement proper network security
- Monitor your logs for suspicious activity
- Follow the principle of least privilege
### 🔄 Security Updates
Security updates will be:
- Released as patch versions (e.g., 2.3.1 → 2.3.2)
- Documented in our [CHANGELOG.md](./CHANGELOG.md)
- Announced in our community channels
- Tagged with security labels in GitHub releases
## Contact
For any questions about this security policy, please contact:
- **Email**: contato@evolution-api.com
---
Thank you for helping keep Evolution API and our community safe! 🛡️

34
commitlint.config.js Normal file
View File

@@ -0,0 +1,34 @@
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
[
'feat', // New feature
'fix', // Bug fix
'docs', // Documentation changes
'style', // Code style changes (formatting, etc)
'refactor', // Code refactoring
'perf', // Performance improvements
'test', // Adding or updating tests
'chore', // Maintenance tasks
'ci', // CI/CD changes
'build', // Build system changes
'revert', // Reverting changes
'security', // Security fixes
],
],
'type-case': [2, 'always', 'lower-case'],
'type-empty': [2, 'never'],
'scope-case': [2, 'always', 'lower-case'],
'subject-case': [2, 'never', ['sentence-case', 'start-case', 'pascal-case', 'upper-case']],
'subject-empty': [2, 'never'],
'subject-full-stop': [2, 'never', '.'],
'header-max-length': [2, 'always', 100],
'body-leading-blank': [1, 'always'],
'body-max-line-length': [0, 'always', 150],
'footer-leading-blank': [1, 'always'],
'footer-max-line-length': [0, 'always', 150],
},
};

View File

@@ -15,6 +15,16 @@ services:
expose:
- 8080
frontend:
container_name: evolution_frontend
image: evolution/manager:local
build: ./evolution-manager-v2
restart: always
ports:
- "3000:80"
networks:
- evolution-net
volumes:
evolution_instances:

View File

@@ -1,57 +1,84 @@
version: "3.8"
services:
api:
container_name: evolution_api
image: atendai/evolution-api:homolog
image: evoapicloud/evolution-api:latest
restart: always
depends_on:
- redis
- postgres
- evolution-postgres
ports:
- 8080:8080
- "127.0.0.1:8080:8080"
volumes:
- evolution_instances:/evolution/instances
networks:
- evolution-net
- dokploy-network
env_file:
- .env
expose:
- 8080
- "8080"
redis:
image: redis:latest
frontend:
container_name: evolution_frontend
image: evoapicloud/evolution-manager:latest
restart: always
ports:
- "3000:80"
networks:
- evolution-net
container_name: redis
redis:
container_name: evolution_redis
image: redis:latest
restart: always
command: >
redis-server --port 6379 --appendonly yes
volumes:
- evolution_redis:/data
ports:
- 6379:6379
postgres:
container_name: postgres
image: postgres:15
networks:
- evolution-net
command: ["postgres", "-c", "max_connections=1000"]
evolution-net:
aliases:
- evolution-redis
dokploy-network:
aliases:
- evolution-redis
expose:
- "6379"
evolution-postgres:
container_name: evolution_postgres
image: postgres:15
restart: always
ports:
- 5432:5432
env_file:
- .env
command:
- postgres
- -c
- max_connections=1000
- -c
- listen_addresses=*
environment:
- POSTGRES_PASSWORD=PASSWORD
- POSTGRES_DB=${POSTGRES_DATABASE}
- POSTGRES_USER=${POSTGRES_USERNAME}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- evolution-net
- dokploy-network
expose:
- 5432
- "5432"
volumes:
evolution_instances:
evolution_redis:
postgres_data:
networks:
evolution-net:
name: evolution-net
driver: bridge
dokploy-network:
external: true

302
env.example Normal file
View File

@@ -0,0 +1,302 @@
# ===========================================
# EVOLUTION API - CONFIGURAÇÃO DE AMBIENTE
# ===========================================
# ===========================================
# SERVIDOR
# ===========================================
SERVER_NAME=evolution
SERVER_TYPE=http
SERVER_PORT=8080
SERVER_URL=http://localhost:8080
SERVER_DISABLE_DOCS=false
SERVER_DISABLE_MANAGER=false
# ===========================================
# CORS
# ===========================================
CORS_ORIGIN=*
CORS_METHODS=POST,GET,PUT,DELETE
CORS_CREDENTIALS=true
# ===========================================
# SSL (opcional)
# ===========================================
SSL_CONF_PRIVKEY=
SSL_CONF_FULLCHAIN=
# ===========================================
# BANCO DE DADOS
# ===========================================
DATABASE_PROVIDER=postgresql
DATABASE_CONNECTION_URI=postgresql://username:password@localhost:5432/evolution_api
DATABASE_CONNECTION_CLIENT_NAME=evolution
# Configurações de salvamento de dados
DATABASE_SAVE_DATA_INSTANCE=true
DATABASE_SAVE_DATA_NEW_MESSAGE=true
DATABASE_SAVE_MESSAGE_UPDATE=true
DATABASE_SAVE_DATA_CONTACTS=true
DATABASE_SAVE_DATA_CHATS=true
DATABASE_SAVE_DATA_HISTORIC=true
DATABASE_SAVE_DATA_LABELS=true
DATABASE_SAVE_IS_ON_WHATSAPP=true
DATABASE_SAVE_IS_ON_WHATSAPP_DAYS=7
DATABASE_DELETE_MESSAGE=false
# ===========================================
# REDIS
# ===========================================
CACHE_REDIS_ENABLED=true
CACHE_REDIS_URI=redis://localhost:6379
CACHE_REDIS_PREFIX_KEY=evolution-cache
CACHE_REDIS_TTL=604800
CACHE_REDIS_SAVE_INSTANCES=true
# Cache local (fallback)
CACHE_LOCAL_ENABLED=true
CACHE_LOCAL_TTL=86400
# ===========================================
# AUTENTICAÇÃO
# ===========================================
AUTHENTICATION_API_KEY=BQYHJGJHJ
AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES=false
# ===========================================
# LOGS
# ===========================================
LOG_LEVEL=ERROR,WARN,DEBUG,INFO,LOG,VERBOSE,DARK,WEBHOOKS,WEBSOCKET
LOG_COLOR=true
LOG_BAILEYS=error
# ===========================================
# INSTÂNCIAS
# ===========================================
DEL_INSTANCE=false
DEL_TEMP_INSTANCES=true
# ===========================================
# IDIOMA
# ===========================================
LANGUAGE=pt-BR
# ===========================================
# WEBHOOK
# ===========================================
WEBHOOK_GLOBAL_URL=
WEBHOOK_GLOBAL_ENABLED=false
WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=false
# Eventos de webhook
WEBHOOK_EVENTS_APPLICATION_STARTUP=false
WEBHOOK_EVENTS_INSTANCE_CREATE=false
WEBHOOK_EVENTS_INSTANCE_DELETE=false
WEBHOOK_EVENTS_QRCODE_UPDATED=false
WEBHOOK_EVENTS_MESSAGES_SET=false
WEBHOOK_EVENTS_MESSAGES_UPSERT=false
WEBHOOK_EVENTS_MESSAGES_EDITED=false
WEBHOOK_EVENTS_MESSAGES_UPDATE=false
WEBHOOK_EVENTS_MESSAGES_DELETE=false
WEBHOOK_EVENTS_SEND_MESSAGE=false
WEBHOOK_EVENTS_SEND_MESSAGE_UPDATE=false
WEBHOOK_EVENTS_CONTACTS_SET=false
WEBHOOK_EVENTS_CONTACTS_UPDATE=false
WEBHOOK_EVENTS_CONTACTS_UPSERT=false
WEBHOOK_EVENTS_PRESENCE_UPDATE=false
WEBHOOK_EVENTS_CHATS_SET=false
WEBHOOK_EVENTS_CHATS_UPDATE=false
WEBHOOK_EVENTS_CHATS_UPSERT=false
WEBHOOK_EVENTS_CHATS_DELETE=false
WEBHOOK_EVENTS_CONNECTION_UPDATE=false
WEBHOOK_EVENTS_LABELS_EDIT=false
WEBHOOK_EVENTS_LABELS_ASSOCIATION=false
WEBHOOK_EVENTS_GROUPS_UPSERT=false
WEBHOOK_EVENTS_GROUPS_UPDATE=false
WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=false
WEBHOOK_EVENTS_CALL=false
WEBHOOK_EVENTS_TYPEBOT_START=false
WEBHOOK_EVENTS_TYPEBOT_CHANGE_STATUS=false
WEBHOOK_EVENTS_ERRORS=false
WEBHOOK_EVENTS_ERRORS_WEBHOOK=
# Configurações de webhook
WEBHOOK_REQUEST_TIMEOUT_MS=30000
WEBHOOK_RETRY_MAX_ATTEMPTS=10
WEBHOOK_RETRY_INITIAL_DELAY_SECONDS=5
WEBHOOK_RETRY_USE_EXPONENTIAL_BACKOFF=true
WEBHOOK_RETRY_MAX_DELAY_SECONDS=300
WEBHOOK_RETRY_JITTER_FACTOR=0.2
WEBHOOK_RETRY_NON_RETRYABLE_STATUS_CODES=400,401,403,404,422
# ===========================================
# WEBSOCKET
# ===========================================
WEBSOCKET_ENABLED=true
WEBSOCKET_GLOBAL_EVENTS=true
WEBSOCKET_ALLOWED_HOSTS=
# ===========================================
# RABBITMQ
# ===========================================
RABBITMQ_ENABLED=false
RABBITMQ_GLOBAL_ENABLED=false
RABBITMQ_PREFIX_KEY=
RABBITMQ_EXCHANGE_NAME=evolution_exchange
RABBITMQ_URI=
RABBITMQ_FRAME_MAX=8192
# ===========================================
# NATS
# ===========================================
NATS_ENABLED=false
NATS_GLOBAL_ENABLED=false
NATS_PREFIX_KEY=
NATS_EXCHANGE_NAME=evolution_exchange
NATS_URI=
# ===========================================
# SQS
# ===========================================
SQS_ENABLED=false
SQS_GLOBAL_ENABLED=false
SQS_GLOBAL_FORCE_SINGLE_QUEUE=false
SQS_GLOBAL_PREFIX_NAME=global
SQS_ACCESS_KEY_ID=
SQS_SECRET_ACCESS_KEY=
SQS_ACCOUNT_ID=
SQS_REGION=
SQS_MAX_PAYLOAD_SIZE=1048576
# ===========================================
# PUSHER
# ===========================================
PUSHER_ENABLED=false
PUSHER_GLOBAL_ENABLED=false
PUSHER_GLOBAL_APP_ID=
PUSHER_GLOBAL_KEY=
PUSHER_GLOBAL_SECRET=
PUSHER_GLOBAL_CLUSTER=
PUSHER_GLOBAL_USE_TLS=false
# ===========================================
# WHATSAPP BUSINESS
# ===========================================
WA_BUSINESS_TOKEN_WEBHOOK=evolution
WA_BUSINESS_URL=https://graph.facebook.com
WA_BUSINESS_VERSION=v18.0
WA_BUSINESS_LANGUAGE=en
# ===========================================
# CONFIGURAÇÕES DE SESSÃO
# ===========================================
CONFIG_SESSION_PHONE_CLIENT=Evolution API
CONFIG_SESSION_PHONE_NAME=Chrome
# ===========================================
# QR CODE
# ===========================================
QRCODE_LIMIT=30
QRCODE_COLOR=#198754
# ===========================================
# INTEGRAÇÕES
# ===========================================
# Typebot
TYPEBOT_ENABLED=false
TYPEBOT_API_VERSION=old
TYPEBOT_SEND_MEDIA_BASE64=false
# Chatwoot
CHATWOOT_ENABLED=false
CHATWOOT_MESSAGE_DELETE=false
CHATWOOT_MESSAGE_READ=false
CHATWOOT_BOT_CONTACT=true
CHATWOOT_IMPORT_DATABASE_CONNECTION_URI=
CHATWOOT_IMPORT_PLACEHOLDER_MEDIA_MESSAGE=false
# OpenAI
OPENAI_ENABLED=false
OPENAI_API_KEY_GLOBAL=
# Dify
DIFY_ENABLED=false
# N8N
N8N_ENABLED=false
# EvoAI
EVOAI_ENABLED=false
# Flowise
FLOWISE_ENABLED=false
# ===========================================
# S3 / MINIO
# ===========================================
S3_ENABLED=false
S3_ACCESS_KEY=
S3_SECRET_KEY=
S3_ENDPOINT=
S3_BUCKET=
S3_PORT=9000
S3_USE_SSL=false
S3_REGION=
S3_SKIP_POLICY=false
S3_SAVE_VIDEO=false
# ===========================================
# MÉTRICAS
# ===========================================
PROMETHEUS_METRICS=false
METRICS_AUTH_REQUIRED=false
METRICS_USER=
METRICS_PASSWORD=
METRICS_ALLOWED_IPS=
# ===========================================
# TELEMETRIA
# ===========================================
TELEMETRY_ENABLED=true
TELEMETRY_URL=
# ===========================================
# PROXY
# ===========================================
PROXY_HOST=
PROXY_PORT=
PROXY_PROTOCOL=
PROXY_USERNAME=
PROXY_PASSWORD=
# ===========================================
# CONVERSOR DE ÁUDIO
# ===========================================
API_AUDIO_CONVERTER=
API_AUDIO_CONVERTER_KEY=
# ===========================================
# FACEBOOK
# ===========================================
FACEBOOK_APP_ID=
FACEBOOK_CONFIG_ID=
FACEBOOK_USER_TOKEN=
# ===========================================
# SENTRY
# ===========================================
SENTRY_DSN=
# ===========================================
# EVENT EMITTER
# ===========================================
EVENT_EMITTER_MAX_LISTENERS=50
# ===========================================
# PROVIDER
# ===========================================
PROVIDER_ENABLED=false
PROVIDER_HOST=
PROVIDER_PORT=5656
PROVIDER_PREFIX=evolution

1
evolution-manager-v2 Submodule

Submodule evolution-manager-v2 added at f054b9bc28

View File

@@ -0,0 +1,238 @@
{
"dashboard": {
"id": null,
"title": "Evolution API Monitoring",
"tags": ["evolution-api", "whatsapp", "monitoring"],
"style": "dark",
"timezone": "browser",
"panels": [
{
"id": 1,
"title": "API Status",
"type": "stat",
"targets": [
{
"expr": "up{job=\"evolution-api\"}",
"legendFormat": "API Status"
}
],
"fieldConfig": {
"defaults": {
"mappings": [
{
"options": {
"0": {
"text": "DOWN",
"color": "red"
},
"1": {
"text": "UP",
"color": "green"
}
},
"type": "value"
}
]
}
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 0
}
},
{
"id": 2,
"title": "Total Instances",
"type": "stat",
"targets": [
{
"expr": "evolution_instances_total",
"legendFormat": "Total Instances"
}
],
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 0
}
},
{
"id": 3,
"title": "Instance Status Overview",
"type": "piechart",
"targets": [
{
"expr": "sum by (state) (evolution_instance_state)",
"legendFormat": "{{ state }}"
}
],
"gridPos": {
"h": 9,
"w": 12,
"x": 0,
"y": 8
}
},
{
"id": 4,
"title": "Instances by Integration Type",
"type": "piechart",
"targets": [
{
"expr": "sum by (integration) (evolution_instance_up)",
"legendFormat": "{{ integration }}"
}
],
"gridPos": {
"h": 9,
"w": 12,
"x": 12,
"y": 8
}
},
{
"id": 5,
"title": "Instance Uptime",
"type": "table",
"targets": [
{
"expr": "evolution_instance_up",
"format": "table",
"instant": true
}
],
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"Time": true,
"__name__": true
},
"renameByName": {
"instance": "Instance Name",
"integration": "Integration Type",
"Value": "Status"
}
}
}
],
"fieldConfig": {
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Status"
},
"properties": [
{
"id": "mappings",
"value": [
{
"options": {
"0": {
"text": "DOWN",
"color": "red"
},
"1": {
"text": "UP",
"color": "green"
}
},
"type": "value"
}
]
}
]
}
]
},
"gridPos": {
"h": 9,
"w": 24,
"x": 0,
"y": 17
}
},
{
"id": 6,
"title": "Instance Status Timeline",
"type": "timeseries",
"targets": [
{
"expr": "evolution_instance_up",
"legendFormat": "{{ instance }} ({{ integration }})"
}
],
"fieldConfig": {
"defaults": {
"custom": {
"drawStyle": "line",
"lineInterpolation": "stepAfter",
"lineWidth": 2,
"fillOpacity": 10,
"gradientMode": "none",
"spanNulls": false,
"insertNulls": false,
"showPoints": "never",
"pointSize": 5,
"stacking": {
"mode": "none",
"group": "A"
},
"axisPlacement": "auto",
"axisLabel": "",
"scaleDistribution": {
"type": "linear"
},
"hideFrom": {
"legend": false,
"tooltip": false,
"vis": false
},
"thresholdsStyle": {
"mode": "off"
}
},
"min": 0,
"max": 1
}
},
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 26
}
}
],
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {},
"templating": {
"list": []
},
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"refresh": "30s",
"schemaVersion": 27,
"version": 0,
"links": []
}
}

150
local_install.sh Executable file
View File

@@ -0,0 +1,150 @@
#!/bin/bash
# Definir cores para melhor legibilidade
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Função para log
log() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
# Verificar se está rodando como root
if [ "$(id -u)" = "0" ]; then
log_error "Este script não deve ser executado como root"
exit 1
fi
# Verificar sistema operacional
OS="$(uname -s)"
case "${OS}" in
Linux*)
if [ ! -x "$(command -v curl)" ]; then
log_warning "Curl não está instalado. Tentando instalar..."
if [ -x "$(command -v apt-get)" ]; then
sudo apt-get update && sudo apt-get install -y curl
elif [ -x "$(command -v yum)" ]; then
sudo yum install -y curl
else
log_error "Não foi possível instalar curl automaticamente. Por favor, instale manualmente."
exit 1
fi
fi
;;
Darwin*)
if [ ! -x "$(command -v curl)" ]; then
log_error "Curl não está instalado. Por favor, instale o Xcode Command Line Tools."
exit 1
fi
;;
*)
log_error "Sistema operacional não suportado: ${OS}"
exit 1
;;
esac
# Verificar conexão com a internet antes de prosseguir
if ! ping -c 1 8.8.8.8 &> /dev/null; then
log_error "Sem conexão com a internet. Por favor, verifique sua conexão."
exit 1
fi
# Adicionar verificação de espaço em disco
REQUIRED_SPACE=1000000 # 1GB em KB
AVAILABLE_SPACE=$(df -k . | awk 'NR==2 {print $4}')
if [ $AVAILABLE_SPACE -lt $REQUIRED_SPACE ]; then
log_error "Espaço em disco insuficiente. Necessário pelo menos 1GB livre."
exit 1
fi
# Adicionar tratamento de erro para comandos npm
npm_install_with_retry() {
local max_attempts=3
local attempt=1
while [ $attempt -le $max_attempts ]; do
log "Tentativa $attempt de $max_attempts para npm install"
if npm install; then
return 0
fi
attempt=$((attempt + 1))
[ $attempt -le $max_attempts ] && log_warning "Falha na instalação. Tentando novamente em 5 segundos..." && sleep 5
done
log_error "Falha ao executar npm install após $max_attempts tentativas"
return 1
}
# Adicionar timeout para comandos
execute_with_timeout() {
timeout 300 $@ || log_error "Comando excedeu o tempo limite de 5 minutos: $@"
}
# Verificar se o NVM já está instalado
if [ -d "$HOME/.nvm" ]; then
log "NVM já está instalado."
else
log "Instalando NVM..."
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
fi
# Carregar o NVM no ambiente atual
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
# Verificar se a versão do Node.js já está instalada
if command -v node >/dev/null 2>&1 && [ "$(node -v)" = "v20.10.0" ]; then
log "Node.js v20.10.0 já está instalado."
else
log "Instalando Node.js v20.10.0..."
nvm install v20.10.0
fi
nvm use v20.10.0
# Verificar as versões instaladas
log "Verificando as versões instaladas:"
log "Node.js: $(node -v)"
log "npm: $(npm -v)"
# Instala dependências do projeto
log "Instalando dependências do projeto..."
rm -rf node_modules
npm install
# Deploy do banco de dados
log "Deploy do banco de dados..."
npm run db:generate
npm run db:deploy
# Iniciar o projeto
log "Iniciando o projeto..."
if [ "$1" = "-dev" ]; then
npm run dev:server
else
npm run build
npm run start:prod
fi
log "Instalação concluída com sucesso!"
# Criar arquivo de log
LOGFILE="./installation_log_$(date +%Y%m%d_%H%M%S).log"
exec 1> >(tee -a "$LOGFILE")
exec 2>&1
# Adicionar trap para limpeza em caso de interrupção
cleanup() {
log "Limpando recursos temporários..."
# Adicione comandos de limpeza aqui
}
trap cleanup EXIT

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 25 KiB

File diff suppressed because one or more lines are too long

485
manager/dist/assets/index-CO3NSIFj.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -2,11 +2,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/assets/images/evolution-logo.png" />
<link rel="icon" type="image/png" href="https://evolution-api.com/files/evo/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Evolution Manager</title>
<script type="module" crossorigin src="/assets/index-CFAZX6IV.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DNOCacL_.css">
<script type="module" crossorigin src="/assets/index-CO3NSIFj.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DsIrum0U.css">
</head>
<body>
<div id="root"></div>

8
manager_install.sh Executable file
View File

@@ -0,0 +1,8 @@
#! /bin/bash
cd evolution-manager-v2
npm install
npm run build
cd ..
rm -rf manager/dist
cp -r evolution-manager-v2/dist manager/dist

15045
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,26 @@
{
"name": "evolution-api",
"version": "2.2.0",
"version": "2.3.5",
"description": "Rest api for communication with WhatsApp",
"main": "./dist/main.js",
"type": "commonjs",
"scripts": {
"build": "tsc --noEmit && tsup",
"start": "tsnd -r tsconfig-paths/register --files --transpile-only ./src/main.ts",
"start": "tsx ./src/main.ts",
"start:prod": "node dist/main",
"dev:server": "tsnd -r tsconfig-paths/register --files --transpile-only --respawn --ignore-watch node_modules ./src/main.ts",
"test": "tsnd -r tsconfig-paths/register --files --transpile-only --respawn --ignore-watch node_modules ./test/all.test.ts",
"dev:server": "tsx watch ./src/main.ts",
"test": "tsx watch ./test/all.test.ts",
"lint": "eslint --fix --ext .ts src",
"lint:check": "eslint --ext .ts src",
"commit": "cz",
"commitlint": "commitlint --edit",
"db:generate": "node runWithProvider.js \"npx prisma generate --schema ./prisma/DATABASE_PROVIDER-schema.prisma\"",
"db:deploy": "node runWithProvider.js \"rm -rf ./prisma/migrations && cp -r ./prisma/DATABASE_PROVIDER-migrations ./prisma/migrations && npx prisma migrate deploy --schema ./prisma/DATABASE_PROVIDER-schema.prisma\"",
"db:deploy:win": "node runWithProvider.js \"xcopy /E /I prisma\\DATABASE_PROVIDER-migrations prisma\\migrations && npx prisma migrate deploy --schema prisma\\DATABASE_PROVIDER-schema.prisma\"",
"db:studio": "node runWithProvider.js \"npx prisma studio --schema ./prisma/DATABASE_PROVIDER-schema.prisma\"",
"db:migrate:dev": "node runWithProvider.js \"rm -rf ./prisma/migrations && cp -r ./prisma/DATABASE_PROVIDER-migrations ./prisma/migrations && npx prisma migrate dev --schema ./prisma/DATABASE_PROVIDER-schema.prisma && cp -r ./prisma/migrations/* ./prisma/DATABASE_PROVIDER-migrations\""
"db:migrate:dev": "node runWithProvider.js \"rm -rf ./prisma/migrations && cp -r ./prisma/DATABASE_PROVIDER-migrations ./prisma/migrations && npx prisma migrate dev --schema ./prisma/DATABASE_PROVIDER-schema.prisma && cp -r ./prisma/migrations/* ./prisma/DATABASE_PROVIDER-migrations\"",
"db:migrate:dev:win": "node runWithProvider.js \"xcopy /E /I prisma\\DATABASE_PROVIDER-migrations prisma\\migrations && npx prisma migrate dev --schema prisma\\DATABASE_PROVIDER-schema.prisma\"",
"prepare": "husky"
},
"repository": {
"type": "git",
@@ -38,81 +44,112 @@
],
"author": {
"name": "Davidson Gomes",
"email": "contato@atendai.com"
"email": "contato@evolution-api.com"
},
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/EvolutionAPI/evolution-api/issues"
},
"homepage": "https://github.com/EvolutionAPI/evolution-api#readme",
"lint-staged": {
"src/**/*.{ts,js}": [
"eslint --fix"
],
"src/**/*.ts": [
"sh -c 'tsc --noEmit'"
]
},
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
},
"dependencies": {
"@adiwajshing/keyed-db": "^0.2.4",
"@aws-sdk/client-sqs": "^3.569.0",
"@aws-sdk/client-sqs": "^3.891.0",
"@ffmpeg-installer/ffmpeg": "^1.1.0",
"@figuro/chatwoot-sdk": "^1.1.16",
"@hapi/boom": "^10.0.1",
"@prisma/client": "^5.15.0",
"@sentry/node": "^8.28.0",
"amqplib": "^0.10.3",
"axios": "^1.6.5",
"baileys": "github:EvolutionAPI/Baileys",
"@paralleldrive/cuid2": "^2.2.2",
"@prisma/client": "^6.16.2",
"@sentry/node": "^10.12.0",
"@types/uuid": "^10.0.0",
"amqplib": "^0.10.5",
"audio-decode": "^2.2.3",
"axios": "^1.7.9",
"baileys": "^7.0.0-rc.5",
"class-validator": "^0.14.1",
"compression": "^1.7.4",
"compression": "^1.7.5",
"cors": "^2.8.5",
"dayjs": "^1.11.7",
"dotenv": "^16.4.5",
"dayjs": "^1.11.13",
"dotenv": "^16.4.7",
"emoji-regex": "^10.4.0",
"eventemitter2": "^6.4.9",
"express": "^4.18.2",
"express": "^4.21.2",
"express-async-errors": "^3.1.1",
"fluent-ffmpeg": "^2.1.2",
"form-data": "^4.0.0",
"https-proxy-agent": "^7.0.2",
"fluent-ffmpeg": "^2.1.3",
"form-data": "^4.0.1",
"https-proxy-agent": "^7.0.6",
"i18next": "^23.7.19",
"jimp": "^0.16.13",
"jimp": "^1.6.0",
"json-schema": "^0.4.0",
"jsonschema": "^1.4.1",
"link-preview-js": "^3.0.4",
"jsonwebtoken": "^9.0.2",
"kafkajs": "^2.2.4",
"link-preview-js": "^3.0.13",
"long": "^5.2.3",
"mediainfo.js": "^0.3.2",
"mime": "^3.0.0",
"minio": "^8.0.1",
"multer": "^1.4.5-lts.1",
"mediainfo.js": "^0.3.4",
"mime": "^4.0.0",
"mime-types": "^2.1.35",
"minio": "^8.0.3",
"multer": "^2.0.2",
"nats": "^2.29.1",
"node-cache": "^5.1.2",
"node-cron": "^3.0.3",
"openai": "^4.52.7",
"pg": "^8.11.3",
"pino": "^8.11.0",
"prisma": "^5.15.0",
"openai": "^4.77.3",
"pg": "^8.13.1",
"pino": "^9.10.0",
"prisma": "^6.1.0",
"pusher": "^5.2.0",
"qrcode": "^1.5.1",
"qrcode": "^1.5.4",
"qrcode-terminal": "^0.12.0",
"redis": "^4.6.5",
"sharp": "^0.32.2",
"socket.io": "^4.7.1",
"tsup": "^8.2.4",
"uuid": "^9.0.0"
"redis": "^4.7.0",
"rxjs": "^7.8.2",
"sharp": "^0.34.2",
"socket.io": "^4.8.1",
"socket.io-client": "^4.8.1",
"socks-proxy-agent": "^8.0.5",
"swagger-ui-express": "^5.0.1",
"tsup": "^8.3.5",
"uuid": "^13.0.0"
},
"devDependencies": {
"@types/compression": "^1.7.2",
"@types/cors": "^2.8.13",
"@types/express": "^4.17.17",
"@commitlint/cli": "^19.8.1",
"@commitlint/config-conventional": "^19.8.1",
"@types/compression": "^1.7.5",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.18",
"@types/json-schema": "^7.0.15",
"@types/mime": "3.0.0",
"@types/node": "^18.15.11",
"@types/mime": "^4.0.0",
"@types/mime-types": "^2.1.4",
"@types/node": "^24.5.2",
"@types/node-cron": "^3.0.11",
"@types/qrcode": "^1.5.0",
"@types/qrcode-terminal": "^0.12.0",
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@types/qrcode": "^1.5.5",
"@types/qrcode-terminal": "^0.12.2",
"@typescript-eslint/eslint-plugin": "^8.44.0",
"@typescript-eslint/parser": "^8.44.0",
"commitizen": "^4.3.1",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^8.45.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-simple-import-sort": "^10.0.0",
"prettier": "^2.8.8",
"ts-node-dev": "^2.0.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-simple-import-sort": "^12.1.1",
"husky": "^9.1.7",
"lint-staged": "^16.1.6",
"prettier": "^3.4.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.5.4"
"tsx": "^4.20.5",
"typescript": "^5.7.2"
}
}

View File

@@ -0,0 +1,232 @@
/*
Warnings:
- You are about to alter the column `createdAt` on the `Chat` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Chat` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Chatwoot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Chatwoot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Contact` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Contact` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Dify` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Dify` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `DifySetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `DifySetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `EvolutionBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `EvolutionBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `EvolutionBotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `EvolutionBotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Flowise` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Flowise` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `FlowiseSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `FlowiseSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `disconnectionAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `IntegrationSession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `IntegrationSession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `IsOnWhatsapp` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `IsOnWhatsapp` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Label` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Label` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Media` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `OpenaiBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `OpenaiBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `OpenaiCreds` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `OpenaiCreds` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `OpenaiSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `OpenaiSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Proxy` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Proxy` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Rabbitmq` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Rabbitmq` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Session` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Setting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Setting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Sqs` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Sqs` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Template` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Template` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Typebot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Typebot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `TypebotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `TypebotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Webhook` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Webhook` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Websocket` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Websocket` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
*/
-- AlterTable
ALTER TABLE `Chat` ADD COLUMN `unreadMessages` INTEGER NOT NULL DEFAULT 0,
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NULL;
-- AlterTable
ALTER TABLE `Chatwoot` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Contact` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NULL;
-- AlterTable
ALTER TABLE `Dify` ADD COLUMN `splitMessages` BOOLEAN NULL DEFAULT false,
ADD COLUMN `timePerChar` INTEGER NULL DEFAULT 50,
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `DifySetting` ADD COLUMN `splitMessages` BOOLEAN NULL DEFAULT false,
ADD COLUMN `timePerChar` INTEGER NULL DEFAULT 50,
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `EvolutionBot` ADD COLUMN `splitMessages` BOOLEAN NULL DEFAULT false,
ADD COLUMN `timePerChar` INTEGER NULL DEFAULT 50,
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `EvolutionBotSetting` ADD COLUMN `splitMessages` BOOLEAN NULL DEFAULT false,
ADD COLUMN `timePerChar` INTEGER NULL DEFAULT 50,
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Flowise` ADD COLUMN `splitMessages` BOOLEAN NULL DEFAULT false,
ADD COLUMN `timePerChar` INTEGER NULL DEFAULT 50,
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `FlowiseSetting` ADD COLUMN `splitMessages` BOOLEAN NULL DEFAULT false,
ADD COLUMN `timePerChar` INTEGER NULL DEFAULT 50,
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Instance` MODIFY `disconnectionAt` TIMESTAMP NULL,
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NULL;
-- AlterTable
ALTER TABLE `IntegrationSession` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `IsOnWhatsapp` MODIFY `createdAt` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Label` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Media` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP;
-- AlterTable
ALTER TABLE `Message` MODIFY `status` VARCHAR(30) NULL;
-- AlterTable
ALTER TABLE `OpenaiBot` ADD COLUMN `splitMessages` BOOLEAN NULL DEFAULT false,
ADD COLUMN `timePerChar` INTEGER NULL DEFAULT 50,
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `OpenaiCreds` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `OpenaiSetting` ADD COLUMN `splitMessages` BOOLEAN NULL DEFAULT false,
ADD COLUMN `timePerChar` INTEGER NULL DEFAULT 50,
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Proxy` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Rabbitmq` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Session` MODIFY `createdAt` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
-- AlterTable
ALTER TABLE `Setting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Sqs` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Template` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Typebot` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NULL;
-- AlterTable
ALTER TABLE `TypebotSetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Webhook` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Websocket` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- CreateTable
CREATE TABLE `Pusher` (
`id` VARCHAR(191) NOT NULL,
`enabled` BOOLEAN NOT NULL DEFAULT false,
`appId` VARCHAR(100) NOT NULL,
`key` VARCHAR(100) NOT NULL,
`secret` VARCHAR(100) NOT NULL,
`cluster` VARCHAR(100) NOT NULL,
`useTLS` BOOLEAN NOT NULL DEFAULT false,
`events` JSON NOT NULL,
`createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
`updatedAt` TIMESTAMP NOT NULL,
`instanceId` VARCHAR(191) NOT NULL,
UNIQUE INDEX `Pusher_instanceId_key`(`instanceId`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateIndex
CREATE INDEX `Chat_remoteJid_idx` ON `Chat`(`remoteJid`);
-- CreateIndex
CREATE INDEX `Contact_remoteJid_idx` ON `Contact`(`remoteJid`);
-- CreateIndex
CREATE INDEX `Setting_instanceId_idx` ON `Setting`(`instanceId`);
-- CreateIndex
CREATE INDEX `Webhook_instanceId_idx` ON `Webhook`(`instanceId`);
-- AddForeignKey
ALTER TABLE `Pusher` ADD CONSTRAINT `Pusher_instanceId_fkey` FOREIGN KEY (`instanceId`) REFERENCES `Instance`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- RenameIndex
ALTER TABLE `Chat` RENAME INDEX `Chat_instanceId_fkey` TO `Chat_instanceId_idx`;
-- RenameIndex
ALTER TABLE `Contact` RENAME INDEX `Contact_instanceId_fkey` TO `Contact_instanceId_idx`;
-- RenameIndex
ALTER TABLE `Message` RENAME INDEX `Message_instanceId_fkey` TO `Message_instanceId_idx`;
-- RenameIndex
ALTER TABLE `MessageUpdate` RENAME INDEX `MessageUpdate_instanceId_fkey` TO `MessageUpdate_instanceId_idx`;
-- RenameIndex
ALTER TABLE `MessageUpdate` RENAME INDEX `MessageUpdate_messageId_fkey` TO `MessageUpdate_messageId_idx`;

View File

@@ -0,0 +1,175 @@
/*
Warnings:
- You are about to alter the column `createdAt` on the `Chat` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Chat` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Chatwoot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Chatwoot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Contact` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Contact` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Dify` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Dify` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `DifySetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `DifySetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `EvolutionBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `EvolutionBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `EvolutionBotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `EvolutionBotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Flowise` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Flowise` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `FlowiseSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `FlowiseSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `disconnectionAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `IntegrationSession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `IntegrationSession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `IsOnWhatsapp` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `IsOnWhatsapp` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Label` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Label` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Media` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `OpenaiBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `OpenaiBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `OpenaiCreds` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `OpenaiCreds` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `OpenaiSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `OpenaiSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Proxy` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Proxy` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Pusher` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Pusher` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Rabbitmq` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Rabbitmq` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Session` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Setting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Setting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Sqs` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Sqs` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Template` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Template` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Typebot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Typebot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `TypebotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `TypebotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Webhook` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Webhook` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Websocket` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Websocket` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- A unique constraint covering the columns `[instanceId,remoteJid]` on the table `Chat` will be added. If there are existing duplicate values, this will fail.
*/
-- AlterTable
ALTER TABLE `Chat` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NULL;
-- AlterTable
ALTER TABLE `Chatwoot` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Contact` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NULL;
-- AlterTable
ALTER TABLE `Dify` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `DifySetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `EvolutionBot` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `EvolutionBotSetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Flowise` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `FlowiseSetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Instance` MODIFY `disconnectionAt` TIMESTAMP NULL,
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NULL;
-- AlterTable
ALTER TABLE `IntegrationSession` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `IsOnWhatsapp` MODIFY `createdAt` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Label` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Media` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP;
-- AlterTable
ALTER TABLE `OpenaiBot` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `OpenaiCreds` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `OpenaiSetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Proxy` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Pusher` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Rabbitmq` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Session` MODIFY `createdAt` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
-- AlterTable
ALTER TABLE `Setting` ADD COLUMN `wavoipToken` VARCHAR(100) NULL,
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Sqs` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Template` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Typebot` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NULL;
-- AlterTable
ALTER TABLE `TypebotSetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Webhook` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Websocket` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- CreateIndex
CREATE UNIQUE INDEX `Chat_instanceId_remoteJid_key` ON `Chat`(`instanceId`, `remoteJid`);

View File

@@ -0,0 +1,17 @@
-- CreateTable
CREATE TABLE `Nats` (
`id` VARCHAR(191) NOT NULL,
`enabled` BOOLEAN NOT NULL DEFAULT false,
`events` JSON NOT NULL,
`createdAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updatedAt` TIMESTAMP NOT NULL,
`instanceId` VARCHAR(191) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateIndex
CREATE UNIQUE INDEX `Nats_instanceId_key` ON `Nats`(`instanceId`);
-- AddForeignKey
ALTER TABLE `Nats` ADD CONSTRAINT `Nats_instanceId_fkey` FOREIGN KEY (`instanceId`) REFERENCES `Instance`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,26 @@
/*
Warnings:
- A unique constraint covering the columns `[remoteJid,instanceId]` on the table `Chat` will be added. If there are existing duplicate values, this will fail.
*/
-- AlterTable
SET @column_exists := (
SELECT COUNT(*)
FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = 'Setting'
AND column_name = 'wavoipToken'
);
SET @sql := IF(@column_exists = 0,
'ALTER TABLE Setting ADD COLUMN wavoipToken VARCHAR(100);',
'SELECT "Column already exists";'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
ALTER TABLE Chat ADD CONSTRAINT unique_remote_instance UNIQUE (remoteJid, instanceId);

View File

@@ -0,0 +1,62 @@
-- CreateTable
CREATE TABLE `N8n` (
`id` VARCHAR(191) NOT NULL,
`enabled` BOOLEAN NOT NULL DEFAULT true,
`description` VARCHAR(255),
`webhookUrl` VARCHAR(255),
`basicAuthUser` VARCHAR(255),
`basicAuthPass` VARCHAR(255),
`expire` INTEGER DEFAULT 0,
`keywordFinish` VARCHAR(100),
`delayMessage` INTEGER,
`unknownMessage` VARCHAR(100),
`listeningFromMe` BOOLEAN DEFAULT false,
`stopBotFromMe` BOOLEAN DEFAULT false,
`keepOpen` BOOLEAN DEFAULT false,
`debounceTime` INTEGER,
`ignoreJids` JSON,
`splitMessages` BOOLEAN DEFAULT false,
`timePerChar` INTEGER DEFAULT 50,
`triggerType` ENUM('all', 'keyword', 'none') NULL,
`triggerOperator` ENUM('contains', 'equals', 'startsWith', 'endsWith', 'regex') NULL,
`triggerValue` VARCHAR(191) NULL,
`createdAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updatedAt` TIMESTAMP NOT NULL,
`instanceId` VARCHAR(191) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `N8nSetting` (
`id` VARCHAR(191) NOT NULL,
`expire` INTEGER DEFAULT 0,
`keywordFinish` VARCHAR(100),
`delayMessage` INTEGER,
`unknownMessage` VARCHAR(100),
`listeningFromMe` BOOLEAN DEFAULT false,
`stopBotFromMe` BOOLEAN DEFAULT false,
`keepOpen` BOOLEAN DEFAULT false,
`debounceTime` INTEGER,
`ignoreJids` JSON,
`splitMessages` BOOLEAN DEFAULT false,
`timePerChar` INTEGER DEFAULT 50,
`createdAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updatedAt` TIMESTAMP NOT NULL,
`n8nIdFallback` VARCHAR(100),
`instanceId` VARCHAR(191) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateIndex
CREATE UNIQUE INDEX `N8nSetting_instanceId_key` ON `N8nSetting`(`instanceId`);
-- AddForeignKey
ALTER TABLE `N8n` ADD CONSTRAINT `N8n_instanceId_fkey` FOREIGN KEY (`instanceId`) REFERENCES `Instance`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `N8nSetting` ADD CONSTRAINT `N8nSetting_n8nIdFallback_fkey` FOREIGN KEY (`n8nIdFallback`) REFERENCES `N8n`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `N8nSetting` ADD CONSTRAINT `N8nSetting_instanceId_fkey` FOREIGN KEY (`instanceId`) REFERENCES `Instance`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,61 @@
-- CreateTable
CREATE TABLE `Evoai` (
`id` VARCHAR(191) NOT NULL,
`enabled` BOOLEAN NOT NULL DEFAULT true,
`description` VARCHAR(255),
`agentUrl` VARCHAR(255),
`apiKey` VARCHAR(255),
`expire` INTEGER DEFAULT 0,
`keywordFinish` VARCHAR(100),
`delayMessage` INTEGER,
`unknownMessage` VARCHAR(100),
`listeningFromMe` BOOLEAN DEFAULT false,
`stopBotFromMe` BOOLEAN DEFAULT false,
`keepOpen` BOOLEAN DEFAULT false,
`debounceTime` INTEGER,
`ignoreJids` JSON,
`splitMessages` BOOLEAN DEFAULT false,
`timePerChar` INTEGER DEFAULT 50,
`triggerType` ENUM('all', 'keyword', 'none') NULL,
`triggerOperator` ENUM('contains', 'equals', 'startsWith', 'endsWith', 'regex') NULL,
`triggerValue` VARCHAR(191) NULL,
`createdAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updatedAt` TIMESTAMP NOT NULL,
`instanceId` VARCHAR(191) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `EvoaiSetting` (
`id` VARCHAR(191) NOT NULL,
`expire` INTEGER DEFAULT 0,
`keywordFinish` VARCHAR(100),
`delayMessage` INTEGER,
`unknownMessage` VARCHAR(100),
`listeningFromMe` BOOLEAN DEFAULT false,
`stopBotFromMe` BOOLEAN DEFAULT false,
`keepOpen` BOOLEAN DEFAULT false,
`debounceTime` INTEGER,
`ignoreJids` JSON,
`splitMessages` BOOLEAN DEFAULT false,
`timePerChar` INTEGER DEFAULT 50,
`createdAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updatedAt` TIMESTAMP NOT NULL,
`evoaiIdFallback` VARCHAR(100),
`instanceId` VARCHAR(191) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateIndex
CREATE UNIQUE INDEX `EvoaiSetting_instanceId_key` ON `EvoaiSetting`(`instanceId`);
-- AddForeignKey
ALTER TABLE `Evoai` ADD CONSTRAINT `Evoai_instanceId_fkey` FOREIGN KEY (`instanceId`) REFERENCES `Instance`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `EvoaiSetting` ADD CONSTRAINT `EvoaiSetting_evoaiIdFallback_fkey` FOREIGN KEY (`evoaiIdFallback`) REFERENCES `Evoai`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `EvoaiSetting` ADD CONSTRAINT `EvoaiSetting_instanceId_fkey` FOREIGN KEY (`instanceId`) REFERENCES `Instance`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,2 @@
-- DropIndex
ALTER TABLE `Media` DROP INDEX `Media_fileName_key`;

View File

@@ -0,0 +1,7 @@
-- AlterTable
ALTER TABLE `Typebot` ADD COLUMN `splitMessages` BOOLEAN DEFAULT false,
ADD COLUMN `timePerChar` INTEGER DEFAULT 50;
-- AlterTable
ALTER TABLE `TypebotSetting` ADD COLUMN `splitMessages` BOOLEAN DEFAULT false,
ADD COLUMN `timePerChar` INTEGER DEFAULT 50;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE `IsOnWhatsapp` ADD COLUMN `lid` VARCHAR(100);

View File

@@ -0,0 +1,231 @@
/*
Warnings:
- You are about to alter the column `createdAt` on the `Chat` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Chat` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Chatwoot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Chatwoot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Contact` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Contact` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Dify` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Dify` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `DifySetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `DifySetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Evoai` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Evoai` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `EvoaiSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `EvoaiSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `EvolutionBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `EvolutionBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `EvolutionBotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `EvolutionBotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Flowise` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Flowise` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `FlowiseSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `FlowiseSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `disconnectionAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `IntegrationSession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `IntegrationSession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to drop the column `lid` on the `IsOnWhatsapp` table. All the data in the column will be lost.
- You are about to alter the column `createdAt` on the `IsOnWhatsapp` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `IsOnWhatsapp` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Label` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Label` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Media` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `N8n` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `N8n` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `N8nSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `N8nSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Nats` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Nats` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `OpenaiBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `OpenaiBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `OpenaiCreds` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `OpenaiCreds` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `OpenaiSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `OpenaiSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Proxy` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Proxy` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Pusher` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Pusher` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Rabbitmq` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Rabbitmq` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Session` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Setting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Setting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Sqs` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Sqs` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Template` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Template` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to drop the column `splitMessages` on the `Typebot` table. All the data in the column will be lost.
- You are about to drop the column `timePerChar` on the `Typebot` table. All the data in the column will be lost.
- You are about to alter the column `createdAt` on the `Typebot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Typebot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to drop the column `splitMessages` on the `TypebotSetting` table. All the data in the column will be lost.
- You are about to drop the column `timePerChar` on the `TypebotSetting` table. All the data in the column will be lost.
- You are about to alter the column `createdAt` on the `TypebotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `TypebotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Webhook` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Webhook` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `createdAt` on the `Websocket` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
- You are about to alter the column `updatedAt` on the `Websocket` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`.
*/
-- DropIndex
DROP INDEX `unique_remote_instance` ON `Chat`;
-- AlterTable
ALTER TABLE `Chat` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NULL;
-- AlterTable
ALTER TABLE `Chatwoot` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Contact` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NULL;
-- AlterTable
ALTER TABLE `Dify` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `DifySetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Evoai` MODIFY `triggerType` ENUM('all', 'keyword', 'none', 'advanced') NULL,
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `EvoaiSetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `EvolutionBot` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `EvolutionBotSetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Flowise` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `FlowiseSetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Instance` MODIFY `disconnectionAt` TIMESTAMP NULL,
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NULL;
-- AlterTable
ALTER TABLE `IntegrationSession` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `IsOnWhatsapp` DROP COLUMN `lid`,
MODIFY `createdAt` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Label` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Media` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP;
-- AlterTable
ALTER TABLE `N8n` MODIFY `triggerType` ENUM('all', 'keyword', 'none', 'advanced') NULL,
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `N8nSetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Nats` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `OpenaiBot` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `OpenaiCreds` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `OpenaiSetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Proxy` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Pusher` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Rabbitmq` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Session` MODIFY `createdAt` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
-- AlterTable
ALTER TABLE `Setting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Sqs` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Template` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Typebot` DROP COLUMN `splitMessages`,
DROP COLUMN `timePerChar`,
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NULL;
-- AlterTable
ALTER TABLE `TypebotSetting` DROP COLUMN `splitMessages`,
DROP COLUMN `timePerChar`,
MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Webhook` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- AlterTable
ALTER TABLE `Websocket` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY `updatedAt` TIMESTAMP NOT NULL;
-- CreateTable
CREATE TABLE `Kafka` (
`id` VARCHAR(191) NOT NULL,
`enabled` BOOLEAN NOT NULL DEFAULT false,
`events` JSON NOT NULL,
`createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
`updatedAt` TIMESTAMP NOT NULL,
`instanceId` VARCHAR(191) NOT NULL,
UNIQUE INDEX `Kafka_instanceId_key`(`instanceId`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- AddForeignKey
ALTER TABLE `Kafka` ADD CONSTRAINT `Kafka_instanceId_fkey` FOREIGN KEY (`instanceId`) REFERENCES `Instance`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -1,3 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
# It should be added in your version-control system (e.g., Git)
provider = "mysql"

View File

@@ -86,7 +86,9 @@ model Instance {
Proxy Proxy?
Setting Setting?
Rabbitmq Rabbitmq?
Nats Nats?
Sqs Sqs?
Kafka Kafka?
Websocket Websocket?
Typebot Typebot[]
Session Session?
@@ -99,11 +101,15 @@ model Instance {
Template Template[]
Dify Dify[]
DifySetting DifySetting?
integrationSessions IntegrationSession[]
IntegrationSession IntegrationSession[]
EvolutionBot EvolutionBot[]
EvolutionBotSetting EvolutionBotSetting?
Flowise Flowise[]
FlowiseSetting FlowiseSetting?
N8n N8n[]
N8nSetting N8nSetting?
Evoai Evoai[]
EvoaiSetting EvoaiSetting?
Pusher Pusher?
}
@@ -116,15 +122,17 @@ model Session {
}
model Chat {
id String @id @default(cuid())
remoteJid String @db.VarChar(100)
name String? @db.VarChar(100)
labels Json? @db.Json
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
updatedAt DateTime? @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
id String @id @default(cuid())
remoteJid String @db.VarChar(100)
name String? @db.VarChar(100)
labels Json? @db.Json
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
updatedAt DateTime? @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
unreadMessages Int @default(0)
@@unique([instanceId, remoteJid])
@@index([instanceId])
@@index([remoteJid])
}
@@ -169,6 +177,7 @@ model Message {
sessionId String?
session IntegrationSession? @relation(fields: [sessionId], references: [id])
@@index([instanceId])
}
@@ -184,6 +193,7 @@ model MessageUpdate {
messageId String
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
@@index([instanceId])
@@index([messageId])
}
@@ -200,6 +210,7 @@ model Webhook {
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
@@index([instanceId])
}
@@ -263,10 +274,12 @@ model Setting {
readMessages Boolean @default(false)
readStatus Boolean @default(false)
syncFullHistory Boolean @default(false)
wavoipToken String? @db.VarChar(100)
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
@@index([instanceId])
}
@@ -280,6 +293,16 @@ model Rabbitmq {
instanceId String @unique
}
model Nats {
id String @id @default(cuid())
enabled Boolean @default(false)
events Json @db.Json
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model Sqs {
id String @id @default(cuid())
enabled Boolean @default(false)
@@ -290,6 +313,16 @@ model Sqs {
instanceId String @unique
}
model Kafka {
id String @id @default(cuid())
enabled Boolean @default(false)
events Json @db.Json
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model Websocket {
id String @id @default(cuid())
enabled Boolean @default(false)
@@ -380,7 +413,7 @@ model IntegrationSession {
model Media {
id String @id @default(cuid())
fileName String @unique @db.VarChar(500)
fileName String @db.VarChar(500)
type String @db.VarChar(100)
mimetype String @db.VarChar(100)
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
@@ -625,3 +658,100 @@ model IsOnWhatsapp {
createdAt DateTime @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
}
model N8n {
id String @id @default(cuid())
enabled Boolean @default(true) @db.TinyInt()
description String? @db.VarChar(255)
webhookUrl String? @db.VarChar(255)
basicAuthUser String? @db.VarChar(255)
basicAuthPass String? @db.VarChar(255)
expire Int? @default(0) @db.Int
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Int
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false)
stopBotFromMe Boolean? @default(false)
keepOpen Boolean? @default(false)
debounceTime Int? @db.Int
ignoreJids Json?
splitMessages Boolean? @default(false)
timePerChar Int? @default(50) @db.Int
triggerType TriggerType?
triggerOperator TriggerOperator?
triggerValue String?
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
N8nSetting N8nSetting[]
}
model N8nSetting {
id String @id @default(cuid())
expire Int? @default(0) @db.Int
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Int
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false)
stopBotFromMe Boolean? @default(false)
keepOpen Boolean? @default(false)
debounceTime Int? @db.Int
ignoreJids Json?
splitMessages Boolean? @default(false)
timePerChar Int? @default(50) @db.Int
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Fallback N8n? @relation(fields: [n8nIdFallback], references: [id])
n8nIdFallback String? @db.VarChar(100)
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model Evoai {
id String @id @default(cuid())
enabled Boolean @default(true) @db.TinyInt()
description String? @db.VarChar(255)
agentUrl String? @db.VarChar(255)
apiKey String? @db.VarChar(255)
expire Int? @default(0) @db.Int
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Int
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false)
stopBotFromMe Boolean? @default(false)
keepOpen Boolean? @default(false)
debounceTime Int? @db.Int
ignoreJids Json?
splitMessages Boolean? @default(false)
timePerChar Int? @default(50) @db.Int
triggerType TriggerType?
triggerOperator TriggerOperator?
triggerValue String?
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
EvoaiSetting EvoaiSetting[]
}
model EvoaiSetting {
id String @id @default(cuid())
expire Int? @default(0) @db.Int
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Int
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false)
stopBotFromMe Boolean? @default(false)
keepOpen Boolean? @default(false)
debounceTime Int? @db.Int
ignoreJids Json?
splitMessages Boolean? @default(false)
timePerChar Int? @default(50) @db.Int
createdAt DateTime? @default(dbgenerated("CURRENT_TIMESTAMP")) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Fallback Evoai? @relation(fields: [evoaiIdFallback], references: [id])
evoaiIdFallback String? @db.VarChar(100)
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}

View File

@@ -0,0 +1,9 @@
/*
Warnings:
- A unique constraint covering the columns `[remoteJid,instanceId]` on the table `Chat` will be added. If there are existing duplicate values, this will fail.
*/
-- AlterTable
ALTER TABLE "Setting" ADD COLUMN IF NOT EXISTS "wavoipToken" VARCHAR(100);

View File

@@ -0,0 +1,17 @@
-- CreateTable
CREATE TABLE "Nats" (
"id" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT false,
"events" JSONB NOT NULL,
"createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP NOT NULL,
"instanceId" TEXT NOT NULL,
CONSTRAINT "Nats_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Nats_instanceId_key" ON "Nats"("instanceId");
-- AddForeignKey
ALTER TABLE "Nats" ADD CONSTRAINT "Nats_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "Instance"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,62 @@
-- CreateTable
CREATE TABLE "N8n" (
"id" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"description" VARCHAR(255),
"webhookUrl" VARCHAR(255),
"basicAuthUser" VARCHAR(255),
"basicAuthPass" VARCHAR(255),
"expire" INTEGER DEFAULT 0,
"keywordFinish" VARCHAR(100),
"delayMessage" INTEGER,
"unknownMessage" VARCHAR(100),
"listeningFromMe" BOOLEAN DEFAULT false,
"stopBotFromMe" BOOLEAN DEFAULT false,
"keepOpen" BOOLEAN DEFAULT false,
"debounceTime" INTEGER,
"ignoreJids" JSONB,
"splitMessages" BOOLEAN DEFAULT false,
"timePerChar" INTEGER DEFAULT 50,
"triggerType" "TriggerType",
"triggerOperator" "TriggerOperator",
"triggerValue" TEXT,
"createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP NOT NULL,
"instanceId" TEXT NOT NULL,
CONSTRAINT "N8n_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "N8nSetting" (
"id" TEXT NOT NULL,
"expire" INTEGER DEFAULT 0,
"keywordFinish" VARCHAR(100),
"delayMessage" INTEGER,
"unknownMessage" VARCHAR(100),
"listeningFromMe" BOOLEAN DEFAULT false,
"stopBotFromMe" BOOLEAN DEFAULT false,
"keepOpen" BOOLEAN DEFAULT false,
"debounceTime" INTEGER,
"ignoreJids" JSONB,
"splitMessages" BOOLEAN DEFAULT false,
"timePerChar" INTEGER DEFAULT 50,
"createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP NOT NULL,
"n8nIdFallback" VARCHAR(100),
"instanceId" TEXT NOT NULL,
CONSTRAINT "N8nSetting_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "N8nSetting_instanceId_key" ON "N8nSetting"("instanceId");
-- AddForeignKey
ALTER TABLE "N8n" ADD CONSTRAINT "N8n_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "Instance"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "N8nSetting" ADD CONSTRAINT "N8nSetting_n8nIdFallback_fkey" FOREIGN KEY ("n8nIdFallback") REFERENCES "N8n"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "N8nSetting" ADD CONSTRAINT "N8nSetting_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "Instance"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,61 @@
-- CreateTable
CREATE TABLE "Evoai" (
"id" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"description" VARCHAR(255),
"agentUrl" VARCHAR(255),
"apiKey" VARCHAR(255),
"expire" INTEGER DEFAULT 0,
"keywordFinish" VARCHAR(100),
"delayMessage" INTEGER,
"unknownMessage" VARCHAR(100),
"listeningFromMe" BOOLEAN DEFAULT false,
"stopBotFromMe" BOOLEAN DEFAULT false,
"keepOpen" BOOLEAN DEFAULT false,
"debounceTime" INTEGER,
"ignoreJids" JSONB,
"splitMessages" BOOLEAN DEFAULT false,
"timePerChar" INTEGER DEFAULT 50,
"triggerType" "TriggerType",
"triggerOperator" "TriggerOperator",
"triggerValue" TEXT,
"createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP NOT NULL,
"instanceId" TEXT NOT NULL,
CONSTRAINT "Evoai_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "EvoaiSetting" (
"id" TEXT NOT NULL,
"expire" INTEGER DEFAULT 0,
"keywordFinish" VARCHAR(100),
"delayMessage" INTEGER,
"unknownMessage" VARCHAR(100),
"listeningFromMe" BOOLEAN DEFAULT false,
"stopBotFromMe" BOOLEAN DEFAULT false,
"keepOpen" BOOLEAN DEFAULT false,
"debounceTime" INTEGER,
"ignoreJids" JSONB,
"splitMessages" BOOLEAN DEFAULT false,
"timePerChar" INTEGER DEFAULT 50,
"createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP NOT NULL,
"evoaiIdFallback" VARCHAR(100),
"instanceId" TEXT NOT NULL,
CONSTRAINT "EvoaiSetting_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "EvoaiSetting_instanceId_key" ON "EvoaiSetting"("instanceId");
-- AddForeignKey
ALTER TABLE "Evoai" ADD CONSTRAINT "Evoai_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "Instance"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "EvoaiSetting" ADD CONSTRAINT "EvoaiSetting_evoaiIdFallback_fkey" FOREIGN KEY ("evoaiIdFallback") REFERENCES "Evoai"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "EvoaiSetting" ADD CONSTRAINT "EvoaiSetting_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "Instance"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,2 @@
-- DropIndex
DROP INDEX "Media_fileName_key";

View File

@@ -0,0 +1,7 @@
-- AlterTable
ALTER TABLE "Typebot" ADD COLUMN "splitMessages" BOOLEAN DEFAULT false,
ADD COLUMN "timePerChar" INTEGER DEFAULT 50;
-- AlterTable
ALTER TABLE "TypebotSetting" ADD COLUMN "splitMessages" BOOLEAN DEFAULT false,
ADD COLUMN "timePerChar" INTEGER DEFAULT 50;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "IsOnWhatsapp" ADD COLUMN "lid" VARCHAR(100);

View File

@@ -0,0 +1,17 @@
-- CreateTable
CREATE TABLE "Kafka" (
"id" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT false,
"events" JSONB NOT NULL,
"createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP NOT NULL,
"instanceId" TEXT NOT NULL,
CONSTRAINT "Kafka_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Kafka_instanceId_key" ON "Kafka"("instanceId");
-- AddForeignKey
ALTER TABLE "Kafka" ADD CONSTRAINT "Kafka_instanceId_fkey" FOREIGN KEY ("instanceId") REFERENCES "Instance"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -1,3 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"

View File

@@ -86,7 +86,9 @@ model Instance {
Proxy Proxy?
Setting Setting?
Rabbitmq Rabbitmq?
Nats Nats?
Sqs Sqs?
Kafka Kafka?
Websocket Websocket?
Typebot Typebot[]
Session Session?
@@ -99,12 +101,16 @@ model Instance {
Template Template[]
Dify Dify[]
DifySetting DifySetting?
integrationSessions IntegrationSession[]
IntegrationSession IntegrationSession[]
EvolutionBot EvolutionBot[]
EvolutionBotSetting EvolutionBotSetting?
Flowise Flowise[]
FlowiseSetting FlowiseSetting?
Pusher Pusher?
N8n N8n[]
N8nSetting N8nSetting[]
Evoai Evoai[]
EvoaiSetting EvoaiSetting?
}
model Session {
@@ -125,6 +131,7 @@ model Chat {
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
unreadMessages Int @default(0)
@@index([instanceId])
@@index([remoteJid])
}
@@ -168,6 +175,7 @@ model Message {
sessionId String?
session IntegrationSession? @relation(fields: [sessionId], references: [id])
@@index([instanceId])
}
@@ -183,6 +191,7 @@ model MessageUpdate {
messageId String
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
@@index([instanceId])
@@index([messageId])
}
@@ -199,6 +208,7 @@ model Webhook {
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
@@index([instanceId])
}
@@ -264,10 +274,12 @@ model Setting {
readMessages Boolean @default(false) @db.Boolean
readStatus Boolean @default(false) @db.Boolean
syncFullHistory Boolean @default(false) @db.Boolean
wavoipToken String? @db.VarChar(100)
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
@@index([instanceId])
}
@@ -281,6 +293,16 @@ model Rabbitmq {
instanceId String @unique
}
model Nats {
id String @id @default(cuid())
enabled Boolean @default(false) @db.Boolean
events Json @db.JsonB
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model Sqs {
id String @id @default(cuid())
enabled Boolean @default(false) @db.Boolean
@@ -291,6 +313,16 @@ model Sqs {
instanceId String @unique
}
model Kafka {
id String @id @default(cuid())
enabled Boolean @default(false) @db.Boolean
events Json @db.JsonB
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model Websocket {
id String @id @default(cuid())
enabled Boolean @default(false) @db.Boolean
@@ -336,6 +368,8 @@ model Typebot {
triggerType TriggerType?
triggerOperator TriggerOperator?
triggerValue String?
splitMessages Boolean? @default(false) @db.Boolean
timePerChar Int? @default(50) @db.Integer
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
TypebotSetting TypebotSetting[]
@@ -353,6 +387,8 @@ model TypebotSetting {
debounceTime Int? @db.Integer
typebotIdFallback String? @db.VarChar(100)
ignoreJids Json?
splitMessages Boolean? @default(false) @db.Boolean
timePerChar Int? @default(50) @db.Integer
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Fallback Typebot? @relation(fields: [typebotIdFallback], references: [id])
@@ -362,7 +398,7 @@ model TypebotSetting {
model Media {
id String @id @default(cuid())
fileName String @unique @db.VarChar(500)
fileName String @db.VarChar(500)
type String @db.VarChar(100)
mimetype String @db.VarChar(100)
createdAt DateTime? @default(now()) @db.Date
@@ -623,6 +659,104 @@ model IsOnWhatsapp {
id String @id @default(cuid())
remoteJid String @unique @db.VarChar(100)
jidOptions String
lid String? @db.VarChar(100)
createdAt DateTime @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
}
model N8n {
id String @id @default(cuid())
enabled Boolean @default(true) @db.Boolean
description String? @db.VarChar(255)
webhookUrl String? @db.VarChar(255)
basicAuthUser String? @db.VarChar(255)
basicAuthPass String? @db.VarChar(255)
expire Int? @default(0) @db.Integer
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Integer
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false) @db.Boolean
stopBotFromMe Boolean? @default(false) @db.Boolean
keepOpen Boolean? @default(false) @db.Boolean
debounceTime Int? @db.Integer
ignoreJids Json?
splitMessages Boolean? @default(false) @db.Boolean
timePerChar Int? @default(50) @db.Integer
triggerType TriggerType?
triggerOperator TriggerOperator?
triggerValue String?
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
N8nSetting N8nSetting[]
}
model N8nSetting {
id String @id @default(cuid())
expire Int? @default(0) @db.Integer
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Integer
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false) @db.Boolean
stopBotFromMe Boolean? @default(false) @db.Boolean
keepOpen Boolean? @default(false) @db.Boolean
debounceTime Int? @db.Integer
ignoreJids Json?
splitMessages Boolean? @default(false) @db.Boolean
timePerChar Int? @default(50) @db.Integer
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Fallback N8n? @relation(fields: [n8nIdFallback], references: [id])
n8nIdFallback String? @db.VarChar(100)
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model Evoai {
id String @id @default(cuid())
enabled Boolean @default(true) @db.Boolean
description String? @db.VarChar(255)
agentUrl String? @db.VarChar(255)
apiKey String? @db.VarChar(255)
expire Int? @default(0) @db.Integer
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Integer
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false) @db.Boolean
stopBotFromMe Boolean? @default(false) @db.Boolean
keepOpen Boolean? @default(false) @db.Boolean
debounceTime Int? @db.Integer
ignoreJids Json?
splitMessages Boolean? @default(false) @db.Boolean
timePerChar Int? @default(50) @db.Integer
triggerType TriggerType?
triggerOperator TriggerOperator?
triggerValue String?
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
EvoaiSetting EvoaiSetting[]
}
model EvoaiSetting {
id String @id @default(cuid())
expire Int? @default(0) @db.Integer
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Integer
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false) @db.Boolean
stopBotFromMe Boolean? @default(false) @db.Boolean
keepOpen Boolean? @default(false) @db.Boolean
debounceTime Int? @db.Integer
ignoreJids Json?
splitMessages Boolean? @default(false) @db.Boolean
timePerChar Int? @default(50) @db.Integer
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Fallback Evoai? @relation(fields: [evoaiIdFallback], references: [id])
evoaiIdFallback String? @db.VarChar(100)
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}

View File

@@ -0,0 +1,763 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_BOUNCER_CONNECTION_URI")
directUrl = env("DATABASE_CONNECTION_URI")
}
enum InstanceConnectionStatus {
open
close
connecting
}
enum DeviceMessage {
ios
android
web
unknown
desktop
}
enum SessionStatus {
opened
closed
paused
}
enum TriggerType {
all
keyword
none
advanced
}
enum TriggerOperator {
contains
equals
startsWith
endsWith
regex
}
enum OpenaiBotType {
assistant
chatCompletion
}
enum DifyBotType {
chatBot
textGenerator
agent
workflow
}
model Instance {
id String @id @default(cuid())
name String @unique @db.VarChar(255)
connectionStatus InstanceConnectionStatus @default(open)
ownerJid String? @db.VarChar(100)
profileName String? @db.VarChar(100)
profilePicUrl String? @db.VarChar(500)
integration String? @db.VarChar(100)
number String? @db.VarChar(100)
businessId String? @db.VarChar(100)
token String? @db.VarChar(255)
clientName String? @db.VarChar(100)
disconnectionReasonCode Int? @db.Integer
disconnectionObject Json? @db.JsonB
disconnectionAt DateTime? @db.Timestamp
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime? @updatedAt @db.Timestamp
Chat Chat[]
Contact Contact[]
Message Message[]
Webhook Webhook?
Chatwoot Chatwoot?
Label Label[]
Proxy Proxy?
Setting Setting?
Rabbitmq Rabbitmq?
Nats Nats?
Sqs Sqs?
Kafka Kafka?
Websocket Websocket?
Typebot Typebot[]
Session Session?
MessageUpdate MessageUpdate[]
TypebotSetting TypebotSetting?
Media Media[]
OpenaiCreds OpenaiCreds[]
OpenaiBot OpenaiBot[]
OpenaiSetting OpenaiSetting?
Template Template[]
Dify Dify[]
DifySetting DifySetting?
IntegrationSession IntegrationSession[]
EvolutionBot EvolutionBot[]
EvolutionBotSetting EvolutionBotSetting?
Flowise Flowise[]
FlowiseSetting FlowiseSetting?
Pusher Pusher?
N8n N8n[]
N8nSetting N8nSetting[]
Evoai Evoai[]
EvoaiSetting EvoaiSetting?
}
model Session {
id String @id @default(cuid())
sessionId String @unique
creds String? @db.Text
createdAt DateTime @default(now()) @db.Timestamp
Instance Instance @relation(fields: [sessionId], references: [id], onDelete: Cascade)
}
model Chat {
id String @id @default(cuid())
remoteJid String @db.VarChar(100)
name String? @db.VarChar(100)
labels Json? @db.JsonB
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime? @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
unreadMessages Int @default(0)
@@index([instanceId])
@@index([remoteJid])
}
model Contact {
id String @id @default(cuid())
remoteJid String @db.VarChar(100)
pushName String? @db.VarChar(100)
profilePicUrl String? @db.VarChar(500)
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime? @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
@@unique([remoteJid, instanceId])
@@index([remoteJid])
@@index([instanceId])
}
model Message {
id String @id @default(cuid())
key Json @db.JsonB
pushName String? @db.VarChar(100)
participant String? @db.VarChar(100)
messageType String @db.VarChar(100)
message Json @db.JsonB
contextInfo Json? @db.JsonB
source DeviceMessage
messageTimestamp Int @db.Integer
chatwootMessageId Int? @db.Integer
chatwootInboxId Int? @db.Integer
chatwootConversationId Int? @db.Integer
chatwootContactInboxSourceId String? @db.VarChar(100)
chatwootIsRead Boolean? @db.Boolean
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
MessageUpdate MessageUpdate[]
Media Media?
webhookUrl String? @db.VarChar(500)
status String? @db.VarChar(30)
sessionId String?
session IntegrationSession? @relation(fields: [sessionId], references: [id])
@@index([instanceId])
}
model MessageUpdate {
id String @id @default(cuid())
keyId String @db.VarChar(100)
remoteJid String @db.VarChar(100)
fromMe Boolean @db.Boolean
participant String? @db.VarChar(100)
pollUpdates Json? @db.JsonB
status String @db.VarChar(30)
Message Message @relation(fields: [messageId], references: [id], onDelete: Cascade)
messageId String
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
@@index([instanceId])
@@index([messageId])
}
model Webhook {
id String @id @default(cuid())
url String @db.VarChar(500)
headers Json? @db.JsonB
enabled Boolean? @default(true) @db.Boolean
events Json? @db.JsonB
webhookByEvents Boolean? @default(false) @db.Boolean
webhookBase64 Boolean? @default(false) @db.Boolean
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
@@index([instanceId])
}
model Chatwoot {
id String @id @default(cuid())
enabled Boolean? @default(true) @db.Boolean
accountId String? @db.VarChar(100)
token String? @db.VarChar(100)
url String? @db.VarChar(500)
nameInbox String? @db.VarChar(100)
signMsg Boolean? @default(false) @db.Boolean
signDelimiter String? @db.VarChar(100)
number String? @db.VarChar(100)
reopenConversation Boolean? @default(false) @db.Boolean
conversationPending Boolean? @default(false) @db.Boolean
mergeBrazilContacts Boolean? @default(false) @db.Boolean
importContacts Boolean? @default(false) @db.Boolean
importMessages Boolean? @default(false) @db.Boolean
daysLimitImportMessages Int? @db.Integer
organization String? @db.VarChar(100)
logo String? @db.VarChar(500)
ignoreJids Json?
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model Label {
id String @id @default(cuid())
labelId String? @db.VarChar(100)
name String @db.VarChar(100)
color String @db.VarChar(100)
predefinedId String? @db.VarChar(100)
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
@@unique([labelId, instanceId])
}
model Proxy {
id String @id @default(cuid())
enabled Boolean @default(false) @db.Boolean
host String @db.VarChar(100)
port String @db.VarChar(100)
protocol String @db.VarChar(100)
username String @db.VarChar(100)
password String @db.VarChar(100)
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model Setting {
id String @id @default(cuid())
rejectCall Boolean @default(false) @db.Boolean
msgCall String? @db.VarChar(100)
groupsIgnore Boolean @default(false) @db.Boolean
alwaysOnline Boolean @default(false) @db.Boolean
readMessages Boolean @default(false) @db.Boolean
readStatus Boolean @default(false) @db.Boolean
syncFullHistory Boolean @default(false) @db.Boolean
wavoipToken String? @db.VarChar(100)
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
@@index([instanceId])
}
model Rabbitmq {
id String @id @default(cuid())
enabled Boolean @default(false) @db.Boolean
events Json @db.JsonB
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model Nats {
id String @id @default(cuid())
enabled Boolean @default(false) @db.Boolean
events Json @db.JsonB
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model Sqs {
id String @id @default(cuid())
enabled Boolean @default(false) @db.Boolean
events Json @db.JsonB
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model Kafka {
id String @id @default(cuid())
enabled Boolean @default(false) @db.Boolean
events Json @db.JsonB
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model Websocket {
id String @id @default(cuid())
enabled Boolean @default(false) @db.Boolean
events Json @db.JsonB
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model Pusher {
id String @id @default(cuid())
enabled Boolean @default(false) @db.Boolean
appId String @db.VarChar(100)
key String @db.VarChar(100)
secret String @db.VarChar(100)
cluster String @db.VarChar(100)
useTLS Boolean @default(false) @db.Boolean
events Json @db.JsonB
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model Typebot {
id String @id @default(cuid())
enabled Boolean @default(true) @db.Boolean
description String? @db.VarChar(255)
url String @db.VarChar(500)
typebot String @db.VarChar(100)
expire Int? @default(0) @db.Integer
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Integer
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false) @db.Boolean
stopBotFromMe Boolean? @default(false) @db.Boolean
keepOpen Boolean? @default(false) @db.Boolean
debounceTime Int? @db.Integer
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime? @updatedAt @db.Timestamp
ignoreJids Json?
triggerType TriggerType?
triggerOperator TriggerOperator?
triggerValue String?
splitMessages Boolean? @default(false) @db.Boolean
timePerChar Int? @default(50) @db.Integer
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
TypebotSetting TypebotSetting[]
}
model TypebotSetting {
id String @id @default(cuid())
expire Int? @default(0) @db.Integer
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Integer
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false) @db.Boolean
stopBotFromMe Boolean? @default(false) @db.Boolean
keepOpen Boolean? @default(false) @db.Boolean
debounceTime Int? @db.Integer
typebotIdFallback String? @db.VarChar(100)
ignoreJids Json?
splitMessages Boolean? @default(false) @db.Boolean
timePerChar Int? @default(50) @db.Integer
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Fallback Typebot? @relation(fields: [typebotIdFallback], references: [id])
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model Media {
id String @id @default(cuid())
fileName String @db.VarChar(500)
type String @db.VarChar(100)
mimetype String @db.VarChar(100)
createdAt DateTime? @default(now()) @db.Date
Message Message @relation(fields: [messageId], references: [id], onDelete: Cascade)
messageId String @unique
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
}
model OpenaiCreds {
id String @id @default(cuid())
name String? @unique @db.VarChar(255)
apiKey String? @unique @db.VarChar(255)
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
OpenaiAssistant OpenaiBot[]
OpenaiSetting OpenaiSetting?
}
model OpenaiBot {
id String @id @default(cuid())
enabled Boolean @default(true) @db.Boolean
description String? @db.VarChar(255)
botType OpenaiBotType
assistantId String? @db.VarChar(255)
functionUrl String? @db.VarChar(500)
model String? @db.VarChar(100)
systemMessages Json? @db.JsonB
assistantMessages Json? @db.JsonB
userMessages Json? @db.JsonB
maxTokens Int? @db.Integer
expire Int? @default(0) @db.Integer
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Integer
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false) @db.Boolean
stopBotFromMe Boolean? @default(false) @db.Boolean
keepOpen Boolean? @default(false) @db.Boolean
debounceTime Int? @db.Integer
splitMessages Boolean? @default(false) @db.Boolean
timePerChar Int? @default(50) @db.Integer
ignoreJids Json?
triggerType TriggerType?
triggerOperator TriggerOperator?
triggerValue String?
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
OpenaiCreds OpenaiCreds @relation(fields: [openaiCredsId], references: [id], onDelete: Cascade)
openaiCredsId String
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
OpenaiSetting OpenaiSetting[]
}
model IntegrationSession {
id String @id @default(cuid())
sessionId String @db.VarChar(255)
remoteJid String @db.VarChar(100)
pushName String?
status SessionStatus
awaitUser Boolean @default(false) @db.Boolean
context Json?
type String? @db.VarChar(100)
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Message Message[]
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
parameters Json? @db.JsonB
botId String?
}
model OpenaiSetting {
id String @id @default(cuid())
expire Int? @default(0) @db.Integer
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Integer
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false) @db.Boolean
stopBotFromMe Boolean? @default(false) @db.Boolean
keepOpen Boolean? @default(false) @db.Boolean
debounceTime Int? @db.Integer
ignoreJids Json?
splitMessages Boolean? @default(false) @db.Boolean
timePerChar Int? @default(50) @db.Integer
speechToText Boolean? @default(false) @db.Boolean
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
OpenaiCreds OpenaiCreds? @relation(fields: [openaiCredsId], references: [id])
openaiCredsId String @unique
Fallback OpenaiBot? @relation(fields: [openaiIdFallback], references: [id])
openaiIdFallback String? @db.VarChar(100)
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model Template {
id String @id @default(cuid())
templateId String @unique @db.VarChar(255)
name String @unique @db.VarChar(255)
template Json @db.JsonB
webhookUrl String? @db.VarChar(500)
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
}
model Dify {
id String @id @default(cuid())
enabled Boolean @default(true) @db.Boolean
description String? @db.VarChar(255)
botType DifyBotType
apiUrl String? @db.VarChar(255)
apiKey String? @db.VarChar(255)
expire Int? @default(0) @db.Integer
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Integer
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false) @db.Boolean
stopBotFromMe Boolean? @default(false) @db.Boolean
keepOpen Boolean? @default(false) @db.Boolean
debounceTime Int? @db.Integer
ignoreJids Json?
splitMessages Boolean? @default(false) @db.Boolean
timePerChar Int? @default(50) @db.Integer
triggerType TriggerType?
triggerOperator TriggerOperator?
triggerValue String?
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
DifySetting DifySetting[]
}
model DifySetting {
id String @id @default(cuid())
expire Int? @default(0) @db.Integer
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Integer
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false) @db.Boolean
stopBotFromMe Boolean? @default(false) @db.Boolean
keepOpen Boolean? @default(false) @db.Boolean
debounceTime Int? @db.Integer
ignoreJids Json?
splitMessages Boolean? @default(false) @db.Boolean
timePerChar Int? @default(50) @db.Integer
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Fallback Dify? @relation(fields: [difyIdFallback], references: [id])
difyIdFallback String? @db.VarChar(100)
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model EvolutionBot {
id String @id @default(cuid())
enabled Boolean @default(true) @db.Boolean
description String? @db.VarChar(255)
apiUrl String? @db.VarChar(255)
apiKey String? @db.VarChar(255)
expire Int? @default(0) @db.Integer
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Integer
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false) @db.Boolean
stopBotFromMe Boolean? @default(false) @db.Boolean
keepOpen Boolean? @default(false) @db.Boolean
debounceTime Int? @db.Integer
ignoreJids Json?
splitMessages Boolean? @default(false) @db.Boolean
timePerChar Int? @default(50) @db.Integer
triggerType TriggerType?
triggerOperator TriggerOperator?
triggerValue String?
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
EvolutionBotSetting EvolutionBotSetting[]
}
model EvolutionBotSetting {
id String @id @default(cuid())
expire Int? @default(0) @db.Integer
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Integer
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false) @db.Boolean
stopBotFromMe Boolean? @default(false) @db.Boolean
keepOpen Boolean? @default(false) @db.Boolean
debounceTime Int? @db.Integer
ignoreJids Json?
splitMessages Boolean? @default(false) @db.Boolean
timePerChar Int? @default(50) @db.Integer
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Fallback EvolutionBot? @relation(fields: [botIdFallback], references: [id])
botIdFallback String? @db.VarChar(100)
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model Flowise {
id String @id @default(cuid())
enabled Boolean @default(true) @db.Boolean
description String? @db.VarChar(255)
apiUrl String? @db.VarChar(255)
apiKey String? @db.VarChar(255)
expire Int? @default(0) @db.Integer
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Integer
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false) @db.Boolean
stopBotFromMe Boolean? @default(false) @db.Boolean
keepOpen Boolean? @default(false) @db.Boolean
debounceTime Int? @db.Integer
ignoreJids Json?
splitMessages Boolean? @default(false) @db.Boolean
timePerChar Int? @default(50) @db.Integer
triggerType TriggerType?
triggerOperator TriggerOperator?
triggerValue String?
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
FlowiseSetting FlowiseSetting[]
}
model FlowiseSetting {
id String @id @default(cuid())
expire Int? @default(0) @db.Integer
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Integer
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false) @db.Boolean
stopBotFromMe Boolean? @default(false) @db.Boolean
keepOpen Boolean? @default(false) @db.Boolean
debounceTime Int? @db.Integer
ignoreJids Json?
splitMessages Boolean? @default(false) @db.Boolean
timePerChar Int? @default(50) @db.Integer
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Fallback Flowise? @relation(fields: [flowiseIdFallback], references: [id])
flowiseIdFallback String? @db.VarChar(100)
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model IsOnWhatsapp {
id String @id @default(cuid())
remoteJid String @unique @db.VarChar(100)
jidOptions String
lid String? @db.VarChar(100)
createdAt DateTime @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
}
model N8n {
id String @id @default(cuid())
enabled Boolean @default(true) @db.Boolean
description String? @db.VarChar(255)
webhookUrl String? @db.VarChar(255)
basicAuthUser String? @db.VarChar(255)
basicAuthPass String? @db.VarChar(255)
expire Int? @default(0) @db.Integer
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Integer
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false) @db.Boolean
stopBotFromMe Boolean? @default(false) @db.Boolean
keepOpen Boolean? @default(false) @db.Boolean
debounceTime Int? @db.Integer
ignoreJids Json?
splitMessages Boolean? @default(false) @db.Boolean
timePerChar Int? @default(50) @db.Integer
triggerType TriggerType?
triggerOperator TriggerOperator?
triggerValue String?
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
N8nSetting N8nSetting[]
}
model N8nSetting {
id String @id @default(cuid())
expire Int? @default(0) @db.Integer
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Integer
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false) @db.Boolean
stopBotFromMe Boolean? @default(false) @db.Boolean
keepOpen Boolean? @default(false) @db.Boolean
debounceTime Int? @db.Integer
ignoreJids Json?
splitMessages Boolean? @default(false) @db.Boolean
timePerChar Int? @default(50) @db.Integer
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Fallback N8n? @relation(fields: [n8nIdFallback], references: [id])
n8nIdFallback String? @db.VarChar(100)
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}
model Evoai {
id String @id @default(cuid())
enabled Boolean @default(true) @db.Boolean
description String? @db.VarChar(255)
agentUrl String? @db.VarChar(255)
apiKey String? @db.VarChar(255)
expire Int? @default(0) @db.Integer
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Integer
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false) @db.Boolean
stopBotFromMe Boolean? @default(false) @db.Boolean
keepOpen Boolean? @default(false) @db.Boolean
debounceTime Int? @db.Integer
ignoreJids Json?
splitMessages Boolean? @default(false) @db.Boolean
timePerChar Int? @default(50) @db.Integer
triggerType TriggerType?
triggerOperator TriggerOperator?
triggerValue String?
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String
EvoaiSetting EvoaiSetting[]
}
model EvoaiSetting {
id String @id @default(cuid())
expire Int? @default(0) @db.Integer
keywordFinish String? @db.VarChar(100)
delayMessage Int? @db.Integer
unknownMessage String? @db.VarChar(100)
listeningFromMe Boolean? @default(false) @db.Boolean
stopBotFromMe Boolean? @default(false) @db.Boolean
keepOpen Boolean? @default(false) @db.Boolean
debounceTime Int? @db.Integer
ignoreJids Json?
splitMessages Boolean? @default(false) @db.Boolean
timePerChar Int? @default(50) @db.Integer
createdAt DateTime? @default(now()) @db.Timestamp
updatedAt DateTime @updatedAt @db.Timestamp
Fallback Evoai? @relation(fields: [evoaiIdFallback], references: [id])
evoaiIdFallback String? @db.VarChar(100)
Instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
instanceId String @unique
}

76
prometheus.yml.example Normal file
View File

@@ -0,0 +1,76 @@
# Prometheus configuration example for Evolution API
# Copy this file to prometheus.yml and adjust the settings
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
# - "first_rules.yml"
# - "second_rules.yml"
scrape_configs:
# Evolution API metrics
- job_name: 'evolution-api'
static_configs:
- targets: ['localhost:8080'] # Adjust to your Evolution API URL
# Metrics endpoint path
metrics_path: '/metrics'
# Scrape interval for this job
scrape_interval: 30s
# Basic authentication (if METRICS_AUTH_REQUIRED=true)
basic_auth:
username: 'prometheus' # Should match METRICS_USER
password: 'secure_random_password_here' # Should match METRICS_PASSWORD
# Optional: Add custom labels
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: localhost:8080 # Evolution API address
# Alerting configuration (optional)
alerting:
alertmanagers:
- static_configs:
- targets:
# - alertmanager:9093
# Example alert rules for Evolution API
# Create a file called evolution_alerts.yml with these rules:
#
# groups:
# - name: evolution-api
# rules:
# - alert: EvolutionAPIDown
# expr: up{job="evolution-api"} == 0
# for: 1m
# labels:
# severity: critical
# annotations:
# summary: "Evolution API is down"
# description: "Evolution API has been down for more than 1 minute."
#
# - alert: EvolutionInstanceDown
# expr: evolution_instance_up == 0
# for: 2m
# labels:
# severity: warning
# annotations:
# summary: "Evolution instance {{ $labels.instance }} is down"
# description: "Instance {{ $labels.instance }} has been down for more than 2 minutes."
#
# - alert: HighInstanceCount
# expr: evolution_instances_total > 100
# for: 5m
# labels:
# severity: warning
# annotations:
# summary: "High number of Evolution instances"
# description: "Evolution API is managing {{ $value }} instances."

View File

@@ -1,23 +1,52 @@
const dotenv = require('dotenv');
const { execSync } = require('child_process');
const { existsSync } = require('fs');
dotenv.config();
const { DATABASE_PROVIDER } = process.env;
const databaseProviderDefault = DATABASE_PROVIDER ?? "postgresql"
const databaseProviderDefault = DATABASE_PROVIDER ?? 'postgresql';
if (!DATABASE_PROVIDER) {
console.error(`DATABASE_PROVIDER is not set in the .env file, using default: ${databaseProviderDefault}`);
// process.exit(1);
console.warn(`DATABASE_PROVIDER is not set in the .env file, using default: ${databaseProviderDefault}`);
}
const command = process.argv
// Função para determinar qual pasta de migrations usar
// Função para determinar qual pasta de migrations usar
function getMigrationsFolder(provider) {
switch (provider) {
case 'psql_bouncer':
return 'postgresql-migrations'; // psql_bouncer usa as migrations do postgresql
default:
return `${provider}-migrations`;
}
}
const migrationsFolder = getMigrationsFolder(databaseProviderDefault);
let command = process.argv
.slice(2)
.join(' ')
.replace(/\DATABASE_PROVIDER/g, databaseProviderDefault);
.replace(/DATABASE_PROVIDER/g, databaseProviderDefault);
// Substituir referências à pasta de migrations pela pasta correta
const migrationsPattern = new RegExp(`${databaseProviderDefault}-migrations`, 'g');
command = command.replace(migrationsPattern, migrationsFolder);
if (command.includes('rmdir') && existsSync('prisma\\migrations')) {
try {
execSync('rmdir /S /Q prisma\\migrations', { stdio: 'inherit' });
} catch (error) {
console.error(`Error removing directory: prisma\\migrations`);
process.exit(1);
}
} else if (command.includes('rmdir')) {
console.warn(`Directory 'prisma\\migrations' does not exist, skipping removal.`);
}
try {
execSync(command, { stdio: 'inherit' });
} catch (error) {
console.error(`Error executing command: ${command}`);
process.exit(1);
}
}

View File

@@ -0,0 +1,15 @@
import { getCatalogDto, getCollectionsDto } from '@api/dto/business.dto';
import { InstanceDto } from '@api/dto/instance.dto';
import { WAMonitoringService } from '@api/services/monitor.service';
export class BusinessController {
constructor(private readonly waMonitor: WAMonitoringService) {}
public async fetchCatalog({ instanceName }: InstanceDto, data: getCatalogDto) {
return await this.waMonitor.waInstances[instanceName].fetchCatalog(instanceName, data);
}
public async fetchCollections({ instanceName }: InstanceDto, data: getCollectionsDto) {
return await this.waMonitor.waInstances[instanceName].fetchCollections(instanceName, data);
}
}

View File

@@ -70,6 +70,10 @@ export class ChatController {
return await this.waMonitor.waInstances[instanceName].fetchChats(query);
}
public async findChatByRemoteJid({ instanceName }: InstanceDto, remoteJid: string) {
return await this.waMonitor.waInstances[instanceName].findChatByRemoteJid(remoteJid);
}
public async sendPresence({ instanceName }: InstanceDto, data: SendPresenceDto) {
return await this.waMonitor.waInstances[instanceName].sendPresence(data);
}

View File

@@ -63,6 +63,9 @@ export class InstanceController {
instanceId,
integration: instanceData.integration,
instanceName: instanceData.instanceName,
ownerJid: instanceData.ownerJid,
profileName: instanceData.profileName,
profilePicUrl: instanceData.profilePicUrl,
hash,
number: instanceData.number,
businessId: instanceData.businessId,
@@ -119,6 +122,7 @@ export class InstanceController {
readMessages: instanceData.readMessages === true,
readStatus: instanceData.readStatus === true,
syncFullHistory: instanceData.syncFullHistory === true,
wavoipToken: instanceData.wavoipToken || '',
};
await this.settingsService.create(instance, settings);
@@ -166,6 +170,9 @@ export class InstanceController {
rabbitmq: {
enabled: instanceData?.rabbitmq?.enabled,
},
nats: {
enabled: instanceData?.nats?.enabled,
},
sqs: {
enabled: instanceData?.sqs?.enabled,
},
@@ -254,6 +261,9 @@ export class InstanceController {
rabbitmq: {
enabled: instanceData?.rabbitmq?.enabled,
},
nats: {
enabled: instanceData?.nats?.enabled,
},
sqs: {
enabled: instanceData?.sqs?.enabled,
},
@@ -409,15 +419,11 @@ export class InstanceController {
public async deleteInstance({ instanceName }: InstanceDto) {
const { instance } = await this.connectionState({ instanceName });
if (instance.state === 'open') {
throw new BadRequestException('The "' + instanceName + '" instance needs to be disconnected');
}
try {
const waInstances = this.waMonitor.waInstances[instanceName];
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED) waInstances?.clearCacheChatwoot();
if (instance.state === 'connecting') {
if (instance.state === 'connecting' || instance.state === 'open') {
await this.logout({ instanceName });
}

View File

@@ -10,7 +10,10 @@ import axios from 'axios';
const logger = new Logger('ProxyController');
export class ProxyController {
constructor(private readonly proxyService: ProxyService, private readonly waMonitor: WAMonitoringService) {}
constructor(
private readonly proxyService: ProxyService,
private readonly waMonitor: WAMonitoringService,
) {}
public async createProxy(instance: InstanceDto, data: ProxyDto) {
if (!this.waMonitor.waInstances[instance.instanceName]) {
@@ -50,15 +53,21 @@ export class ProxyController {
httpsAgent: makeProxyAgent(proxy),
});
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: ');
const result = response?.data !== serverIp?.data;
if (result) {
logger.info('testProxy: proxy connection successful');
} else {
logger.error('testProxy error: ');
logger.warn("testProxy: proxy connection doesn't change the origin IP");
}
return result;
} catch (error) {
if (axios.isAxiosError(error)) {
logger.error('testProxy error: axios error: ' + error.message);
} else {
logger.error('testProxy error: unexpected error: ' + error);
}
return false;
}
}

View File

@@ -17,6 +17,16 @@ import {
import { WAMonitoringService } from '@api/services/monitor.service';
import { BadRequestException } from '@exceptions';
import { isBase64, isURL } from 'class-validator';
import emojiRegex from 'emoji-regex';
const regex = emojiRegex();
function isEmoji(str: string) {
if (str === '') return true;
const match = str.match(regex);
return match?.length === 1 && match[0] === str;
}
export class SendMessageController {
constructor(private readonly waMonitor: WAMonitoringService) {}
@@ -81,8 +91,8 @@ export class SendMessageController {
}
public async sendReaction({ instanceName }: InstanceDto, data: SendReactionDto) {
if (!data.reaction.match(/[^()\w\sà-ú"-+]+/)) {
throw new BadRequestException('"reaction" must be an emoji');
if (!isEmoji(data.reaction)) {
throw new BadRequestException('Reaction must be a single emoji or empty string');
}
return await this.waMonitor.waInstances[instanceName].reactionMessage(data);
}

View File

@@ -0,0 +1,14 @@
export class NumberDto {
number: string;
}
export class getCatalogDto {
number?: string;
limit?: number;
cursor?: string;
}
export class getCollectionsDto {
number?: string;
limit?: number;
}

View File

@@ -13,6 +13,7 @@ export class OnWhatsAppDto {
public readonly exists: boolean,
public readonly number: string,
public readonly name?: string,
public readonly lid?: string,
) {}
}

View File

@@ -1,4 +1,5 @@
import { IntegrationDto } from '@api/integrations/integration.dto';
import { JsonValue } from '@prisma/client/runtime/library';
import { WAPresence } from 'baileys';
export class InstanceDto extends IntegrationDto {
@@ -10,6 +11,9 @@ export class InstanceDto extends IntegrationDto {
integration?: string;
token?: string;
status?: string;
ownerJid?: string;
profileName?: string;
profilePicUrl?: string;
// settings
rejectCall?: boolean;
msgCall?: string;
@@ -18,12 +22,35 @@ export class InstanceDto extends IntegrationDto {
readMessages?: boolean;
readStatus?: boolean;
syncFullHistory?: boolean;
wavoipToken?: string;
// proxy
proxyHost?: string;
proxyPort?: string;
proxyProtocol?: string;
proxyUsername?: string;
proxyPassword?: string;
webhook?: {
enabled?: boolean;
events?: string[];
headers?: JsonValue;
url?: string;
byEvents?: boolean;
base64?: boolean;
};
chatwootAccountId?: string;
chatwootConversationPending?: boolean;
chatwootAutoCreate?: boolean;
chatwootDaysLimitImportMessages?: number;
chatwootImportContacts?: boolean;
chatwootImportMessages?: boolean;
chatwootLogo?: string;
chatwootMergeBrazilContacts?: boolean;
chatwootNameInbox?: string;
chatwootOrganization?: string;
chatwootReopenConversation?: boolean;
chatwootSignMsg?: boolean;
chatwootToken?: string;
chatwootUrl?: string;
}
export class SetPresenceDto {

View File

@@ -44,6 +44,7 @@ export class Metadata {
mentionsEveryOne?: boolean;
mentioned?: string[];
encoding?: boolean;
notConvertSticker?: boolean;
}
export class SendTextDto extends Metadata {

View File

@@ -6,4 +6,5 @@ export class SettingsDto {
readMessages?: boolean;
readStatus?: boolean;
syncFullHistory?: boolean;
wavoipToken?: string;
}

View File

@@ -75,8 +75,6 @@ export class ChannelController {
data.prismaRepository,
data.cache,
data.chatwootCache,
data.baileysCache,
data.providerFiles,
);
}

View File

@@ -2,14 +2,16 @@ import { Router } from 'express';
import { EvolutionRouter } from './evolution/evolution.router';
import { MetaRouter } from './meta/meta.router';
import { BaileysRouter } from './whatsapp/baileys.router';
export class ChannelRouter {
public readonly router: Router;
constructor(configService: any) {
constructor(configService: any, ...guards: any[]) {
this.router = Router();
this.router.use('/', new EvolutionRouter(configService).router);
this.router.use('/', new MetaRouter(configService).router);
this.router.use('/baileys', new BaileysRouter(...guards).router);
}
}

View File

@@ -1,16 +1,28 @@
import { MediaMessage, Options, SendAudioDto, SendMediaDto, SendTextDto } from '@api/dto/sendMessage.dto';
import { ProviderFiles } from '@api/provider/sessions';
import { InstanceDto } from '@api/dto/instance.dto';
import {
MediaMessage,
Options,
SendAudioDto,
SendButtonsDto,
SendMediaDto,
SendTextDto,
} from '@api/dto/sendMessage.dto';
import * as s3Service from '@api/integrations/storage/s3/libs/minio.server';
import { PrismaRepository } from '@api/repository/repository.service';
import { chatbotController } from '@api/server.module';
import { CacheService } from '@api/services/cache.service';
import { ChannelStartupService } from '@api/services/channel.service';
import { Events, wa } from '@api/types/wa.types';
import { Chatwoot, ConfigService, Openai } from '@config/env.config';
import { AudioConverter, Chatwoot, ConfigService, Openai, S3 } from '@config/env.config';
import { BadRequestException, InternalServerErrorException } from '@exceptions';
import { status } from '@utils/renderStatus';
import { isURL } from 'class-validator';
import { createJid } from '@utils/createJid';
import { sendTelemetry } from '@utils/sendTelemetry';
import axios from 'axios';
import { isBase64, isURL } from 'class-validator';
import EventEmitter2 from 'eventemitter2';
import mime from 'mime';
import FormData from 'form-data';
import mimeTypes from 'mime-types';
import { join } from 'path';
import { v4 } from 'uuid';
export class EvolutionStartupService extends ChannelStartupService {
@@ -20,8 +32,6 @@ export class EvolutionStartupService extends ChannelStartupService {
public readonly prismaRepository: PrismaRepository,
public readonly cache: CacheService,
public readonly chatwootCache: CacheService,
public readonly baileysCache: CacheService,
private readonly providerFiles: ProviderFiles,
) {
super(configService, eventEmitter, prismaRepository, chatwootCache);
@@ -56,8 +66,34 @@ export class EvolutionStartupService extends ChannelStartupService {
await this.closeClient();
}
public setInstance(instance: InstanceDto) {
this.logger.setInstance(instance.instanceId);
this.instance.name = instance.instanceName;
this.instance.id = instance.instanceId;
this.instance.integration = instance.integration;
this.instance.number = instance.number;
this.instance.token = instance.token;
this.instance.businessId = instance.businessId;
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {
this.chatwootService.eventWhatsapp(
Events.STATUS_INSTANCE,
{
instanceName: this.instance.name,
instanceId: this.instance.id,
integration: instance.integration,
},
{
instance: this.instance.name,
status: 'created',
},
);
}
}
public async profilePicture(number: string) {
const jid = this.createJid(number);
const jid = createJid(number);
return {
wuid: jid,
@@ -78,11 +114,12 @@ export class EvolutionStartupService extends ChannelStartupService {
}
public async connectToWhatsapp(data?: any): Promise<any> {
if (!data) return;
if (!data) {
this.loadChatwoot();
return;
}
try {
this.loadChatwoot();
this.eventHandler(data);
} catch (error) {
this.logger.error(error);
@@ -99,6 +136,7 @@ export class EvolutionStartupService extends ChannelStartupService {
id: received.key.id || v4(),
remoteJid: received.key.remoteJid,
fromMe: received.key.fromMe,
profilePicUrl: received.profilePicUrl,
};
messageRaw = {
key,
@@ -110,7 +148,9 @@ export class EvolutionStartupService extends ChannelStartupService {
instanceId: this.instanceId,
};
if (this.configService.get<Openai>('OPENAI').ENABLED) {
const isAudio = received?.message?.audioMessage;
if (this.configService.get<Openai>('OPENAI').ENABLED && isAudio) {
const openAiDefaultSettings = await this.prismaRepository.openaiSetting.findFirst({
where: {
instanceId: this.instanceId,
@@ -126,16 +166,14 @@ export class EvolutionStartupService extends ChannelStartupService {
openAiDefaultSettings.speechToText &&
received?.message?.audioMessage
) {
messageRaw.message.speechToText = await this.openaiService.speechToText(
openAiDefaultSettings.OpenaiCreds,
received,
this.client.updateMediaMessage,
);
messageRaw.message.speechToText = `[audio] ${await this.openaiService.speechToText(received, this)}`;
}
}
this.logger.log(messageRaw);
sendTelemetry(`received.message.${messageRaw.messageType ?? 'unknown'}`);
this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw);
await chatbotController.emit({
@@ -165,7 +203,7 @@ export class EvolutionStartupService extends ChannelStartupService {
await this.updateContact({
remoteJid: messageRaw.key.remoteJid,
pushName: messageRaw.key.fromMe ? '' : messageRaw.key.fromMe == null ? '' : received.pushName,
pushName: messageRaw.pushName,
profilePicUrl: received.profilePicUrl,
});
}
@@ -175,35 +213,6 @@ export class EvolutionStartupService extends ChannelStartupService {
}
private async updateContact(data: { remoteJid: string; pushName?: string; profilePicUrl?: string }) {
const contact = await this.prismaRepository.contact.findFirst({
where: { instanceId: this.instanceId, remoteJid: data.remoteJid },
});
if (contact) {
const contactRaw: any = {
remoteJid: data.remoteJid,
pushName: data?.pushName,
instanceId: this.instanceId,
profilePicUrl: data?.profilePicUrl,
};
this.sendDataWebhook(Events.CONTACTS_UPDATE, contactRaw);
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {
await this.chatwootService.eventWhatsapp(
Events.CONTACTS_UPDATE,
{ instanceName: this.instance.name, instanceId: this.instanceId },
contactRaw,
);
}
await this.prismaRepository.contact.updateMany({
where: { remoteJid: contact.remoteJid, instanceId: this.instanceId },
data: contactRaw,
});
return;
}
const contactRaw: any = {
remoteJid: data.remoteJid,
pushName: data?.pushName,
@@ -211,11 +220,40 @@ export class EvolutionStartupService extends ChannelStartupService {
profilePicUrl: data?.profilePicUrl,
};
const existingContact = await this.prismaRepository.contact.findFirst({
where: {
remoteJid: data.remoteJid,
instanceId: this.instanceId,
},
});
if (existingContact) {
await this.prismaRepository.contact.updateMany({
where: {
remoteJid: data.remoteJid,
instanceId: this.instanceId,
},
data: contactRaw,
});
} else {
await this.prismaRepository.contact.create({
data: contactRaw,
});
}
this.sendDataWebhook(Events.CONTACTS_UPSERT, contactRaw);
await this.prismaRepository.contact.create({
data: contactRaw,
});
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {
await this.chatwootService.eventWhatsapp(
Events.CONTACTS_UPDATE,
{
instanceName: this.instance.name,
instanceId: this.instanceId,
integration: this.instance.integration,
},
contactRaw,
);
}
const chat = await this.prismaRepository.chat.findFirst({
where: { instanceId: this.instanceId, remoteJid: data.remoteJid },
@@ -247,7 +285,13 @@ export class EvolutionStartupService extends ChannelStartupService {
});
}
protected async sendMessageWithTyping(number: string, message: any, options?: Options, isIntegration = false) {
protected async sendMessageWithTyping(
number: string,
message: any,
options?: Options,
file?: any,
isIntegration = false,
) {
try {
let quoted: any;
let webhookUrl: any;
@@ -272,64 +316,194 @@ export class EvolutionStartupService extends ChannelStartupService {
webhookUrl = options.webhookUrl;
}
let audioFile;
const messageId = v4();
let messageRaw: any = {
key: { fromMe: true, id: messageId, remoteJid: number },
messageTimestamp: Math.round(new Date().getTime() / 1000),
webhookUrl,
source: 'unknown',
instanceId: this.instanceId,
status: status[1],
};
let messageRaw: any;
if (message?.mediaType === 'image') {
messageRaw = {
...messageRaw,
key: { fromMe: true, id: messageId, remoteJid: number },
message: {
mediaUrl: message.media,
base64: isBase64(message.media) ? message.media : null,
mediaUrl: isURL(message.media) ? message.media : null,
quoted,
},
messageType: 'imageMessage',
messageTimestamp: Math.round(new Date().getTime() / 1000),
webhookUrl,
source: 'unknown',
instanceId: this.instanceId,
};
} else if (message?.mediaType === 'video') {
messageRaw = {
...messageRaw,
key: { fromMe: true, id: messageId, remoteJid: number },
message: {
mediaUrl: message.media,
base64: isBase64(message.media) ? message.media : null,
mediaUrl: isURL(message.media) ? message.media : null,
quoted,
},
messageType: 'videoMessage',
messageTimestamp: Math.round(new Date().getTime() / 1000),
webhookUrl,
source: 'unknown',
instanceId: this.instanceId,
};
} else if (message?.mediaType === 'audio') {
messageRaw = {
...messageRaw,
key: { fromMe: true, id: messageId, remoteJid: number },
message: {
mediaUrl: message.media,
base64: isBase64(message.media) ? message.media : null,
mediaUrl: isURL(message.media) ? message.media : null,
quoted,
},
messageType: 'audioMessage',
messageTimestamp: Math.round(new Date().getTime() / 1000),
webhookUrl,
source: 'unknown',
instanceId: this.instanceId,
};
const buffer = Buffer.from(message.media, 'base64');
audioFile = {
buffer,
mimetype: 'audio/mp4',
originalname: `${messageId}.mp4`,
};
} else if (message?.mediaType === 'document') {
messageRaw = {
...messageRaw,
key: { fromMe: true, id: messageId, remoteJid: number },
message: {
mediaUrl: message.media,
base64: isBase64(message.media) ? message.media : null,
mediaUrl: isURL(message.media) ? message.media : null,
quoted,
},
messageType: 'documentMessage',
messageTimestamp: Math.round(new Date().getTime() / 1000),
webhookUrl,
source: 'unknown',
instanceId: this.instanceId,
};
} else if (message.buttonMessage) {
messageRaw = {
key: { fromMe: true, id: messageId, remoteJid: number },
message: {
...message.buttonMessage,
buttons: message.buttonMessage.buttons,
footer: message.buttonMessage.footer,
body: message.buttonMessage.body,
quoted,
},
messageType: 'buttonMessage',
messageTimestamp: Math.round(new Date().getTime() / 1000),
webhookUrl,
source: 'unknown',
instanceId: this.instanceId,
};
} else if (message.listMessage) {
messageRaw = {
key: { fromMe: true, id: messageId, remoteJid: number },
message: {
...message.listMessage,
quoted,
},
messageType: 'listMessage',
messageTimestamp: Math.round(new Date().getTime() / 1000),
webhookUrl,
source: 'unknown',
instanceId: this.instanceId,
};
} else {
messageRaw = {
...messageRaw,
key: { fromMe: true, id: messageId, remoteJid: number },
message: {
...message,
quoted,
},
messageType: 'conversation',
messageTimestamp: Math.round(new Date().getTime() / 1000),
webhookUrl,
source: 'unknown',
instanceId: this.instanceId,
};
}
if (messageRaw.message.contextInfo) {
messageRaw.contextInfo = {
...messageRaw.message.contextInfo,
};
}
if (messageRaw.contextInfo?.stanzaId) {
const key: any = {
id: messageRaw.contextInfo.stanzaId,
};
const findMessage = await this.prismaRepository.message.findFirst({
where: {
instanceId: this.instanceId,
key,
},
});
if (findMessage) {
messageRaw.contextInfo.quotedMessage = findMessage.message;
}
}
const { base64 } = messageRaw.message;
delete messageRaw.message.base64;
if (base64 || file || audioFile) {
if (this.configService.get<S3>('S3').ENABLE) {
try {
// Verificação adicional para garantir que há conteúdo de mídia real
const hasRealMedia = this.hasValidMediaContent(messageRaw);
if (!hasRealMedia) {
this.logger.warn('Message detected as media but contains no valid media content');
} else {
const fileBuffer = audioFile?.buffer || file?.buffer;
const buffer = base64 ? Buffer.from(base64, 'base64') : fileBuffer;
let mediaType: string;
let mimetype = audioFile?.mimetype || file.mimetype;
if (messageRaw.messageType === 'documentMessage') {
mediaType = 'document';
mimetype = !mimetype ? 'application/pdf' : mimetype;
} else if (messageRaw.messageType === 'imageMessage') {
mediaType = 'image';
mimetype = !mimetype ? 'image/png' : mimetype;
} else if (messageRaw.messageType === 'audioMessage') {
mediaType = 'audio';
mimetype = !mimetype ? 'audio/mp4' : mimetype;
} else if (messageRaw.messageType === 'videoMessage') {
mediaType = 'video';
mimetype = !mimetype ? 'video/mp4' : mimetype;
}
const fileName = `${messageRaw.key.id}.${mimetype.split('/')[1]}`;
const size = buffer.byteLength;
const fullName = join(`${this.instance.id}`, messageRaw.key.remoteJid, mediaType, fileName);
await s3Service.uploadFile(fullName, buffer, size, {
'Content-Type': mimetype,
});
const mediaUrl = await s3Service.getObjectUrl(fullName);
messageRaw.message.mediaUrl = mediaUrl;
}
} catch (error) {
this.logger.error(['Error on upload file to minio', error?.message, error?.stack]);
}
}
}
this.logger.log(messageRaw);
this.sendDataWebhook(Events.SEND_MESSAGE, messageRaw);
@@ -375,6 +549,7 @@ export class EvolutionStartupService extends ChannelStartupService {
mentionsEveryOne: data?.mentionsEveryOne,
mentioned: data?.mentioned,
},
null,
isIntegration,
);
return res;
@@ -396,7 +571,7 @@ export class EvolutionStartupService extends ChannelStartupService {
mediaMessage.fileName = 'video.mp4';
}
let mimetype: string;
let mimetype: string | false;
const prepareMedia: any = {
caption: mediaMessage?.caption,
@@ -407,9 +582,9 @@ export class EvolutionStartupService extends ChannelStartupService {
};
if (isURL(mediaMessage.media)) {
mimetype = mime.getType(mediaMessage.media);
mimetype = mimeTypes.lookup(mediaMessage.media);
} else {
mimetype = mime.getType(mediaMessage.fileName);
mimetype = mimeTypes.lookup(mediaMessage.fileName);
}
prepareMedia.mimetype = mimetype;
@@ -439,33 +614,79 @@ export class EvolutionStartupService extends ChannelStartupService {
mentionsEveryOne: data?.mentionsEveryOne,
mentioned: data?.mentioned,
},
file,
isIntegration,
);
return mediaSent;
}
public async processAudio(audio: string, number: string) {
public async processAudio(audio: string, number: string, file: any) {
number = number.replace(/\D/g, '');
const hash = `${number}-${new Date().getTime()}`;
let mimetype: string;
const audioConverterConfig = this.configService.get<AudioConverter>('AUDIO_CONVERTER');
if (audioConverterConfig.API_URL) {
try {
this.logger.verbose('Using audio converter API');
const formData = new FormData();
const prepareMedia: any = {
fileName: `${hash}.mp4`,
mediaType: 'audio',
media: audio,
};
if (file) {
formData.append('file', file.buffer, {
filename: file.originalname,
contentType: file.mimetype,
});
} else if (isURL(audio)) {
formData.append('url', audio);
} else {
formData.append('base64', audio);
}
if (isURL(audio)) {
mimetype = mime.getType(audio);
formData.append('format', 'mp4');
const response = await axios.post(audioConverterConfig.API_URL, formData, {
headers: {
...formData.getHeaders(),
apikey: audioConverterConfig.API_KEY,
},
});
if (!response?.data?.audio) {
throw new InternalServerErrorException('Failed to convert audio');
}
const prepareMedia: any = {
fileName: `${hash}.mp4`,
mediaType: 'audio',
media: response?.data?.audio,
mimetype: 'audio/mpeg',
};
return prepareMedia;
} catch (error) {
this.logger.error(error?.response?.data || error);
throw new InternalServerErrorException(error?.response?.data?.message || error?.toString() || error);
}
} else {
mimetype = mime.getType(prepareMedia.fileName);
let mimetype: string;
const prepareMedia: any = {
fileName: `${hash}.mp3`,
mediaType: 'audio',
media: audio,
mimetype: 'audio/mpeg',
};
if (isURL(audio)) {
mimetype = mimeTypes.lookup(audio).toString();
} else {
mimetype = mimeTypes.lookup(prepareMedia.fileName).toString();
}
prepareMedia.mimetype = mimetype;
return prepareMedia;
}
prepareMedia.mimetype = mimetype;
return prepareMedia;
}
public async audioWhatsapp(data: SendAudioDto, file?: any, isIntegration = false) {
@@ -478,7 +699,7 @@ export class EvolutionStartupService extends ChannelStartupService {
throw new Error('File or buffer is undefined.');
}
const message = await this.processAudio(mediaData.audio, data.number);
const message = await this.processAudio(mediaData.audio, data.number, file);
const audioSent = await this.sendMessageWithTyping(
data.number,
@@ -491,14 +712,34 @@ export class EvolutionStartupService extends ChannelStartupService {
mentionsEveryOne: data?.mentionsEveryOne,
mentioned: data?.mentioned,
},
file,
isIntegration,
);
return audioSent;
}
public async buttonMessage() {
throw new BadRequestException('Method not available on Evolution Channel');
public async buttonMessage(data: SendButtonsDto, isIntegration = false) {
return await this.sendMessageWithTyping(
data.number,
{
buttonMessage: {
title: data.title,
description: data.description,
footer: data.footer,
buttons: data.buttons,
},
},
{
delay: data?.delay,
presence: 'composing',
quoted: data?.quoted,
mentionsEveryOne: data?.mentionsEveryOne,
mentioned: data?.mentioned,
},
null,
isIntegration,
);
}
public async locationMessage() {
throw new BadRequestException('Method not available on Evolution Channel');

View File

@@ -20,15 +20,16 @@ import { chatbotController } from '@api/server.module';
import { CacheService } from '@api/services/cache.service';
import { ChannelStartupService } from '@api/services/channel.service';
import { Events, wa } from '@api/types/wa.types';
import { Chatwoot, ConfigService, Database, Openai, S3, WaBusiness } from '@config/env.config';
import { AudioConverter, Chatwoot, ConfigService, Database, Openai, S3, WaBusiness } from '@config/env.config';
import { BadRequestException, InternalServerErrorException } from '@exceptions';
import { createJid } from '@utils/createJid';
import { status } from '@utils/renderStatus';
import { sendTelemetry } from '@utils/sendTelemetry';
import axios from 'axios';
import { arrayUnique, isURL } from 'class-validator';
import EventEmitter2 from 'eventemitter2';
import FormData from 'form-data';
import { createReadStream } from 'fs';
import mime from 'mime';
import mimeTypes from 'mime-types';
import { join } from 'path';
export class BusinessStartupService extends ChannelStartupService {
@@ -70,6 +71,10 @@ export class BusinessStartupService extends ChannelStartupService {
await this.closeClient();
}
private isMediaMessage(message: any) {
return message.document || message.image || message.audio || message.video;
}
private async post(message: any, params: string) {
try {
let urlServer = this.configService.get<WaBusiness>('WA_BUSINESS').URL;
@@ -84,7 +89,7 @@ export class BusinessStartupService extends ChannelStartupService {
}
public async profilePicture(number: string) {
const jid = this.createJid(number);
const jid = createJid(number);
return {
wuid: jid,
@@ -128,9 +133,7 @@ export class BusinessStartupService extends ChannelStartupService {
this.eventHandler(content);
this.phoneNumber = this.createJid(
content.messages ? content.messages[0].from : content.statuses[0]?.recipient_id,
);
this.phoneNumber = createJid(content.messages ? content.messages[0].from : content.statuses[0]?.recipient_id);
} catch (error) {
this.logger.error(error);
throw new InternalServerErrorException(error?.toString());
@@ -144,11 +147,20 @@ export class BusinessStartupService extends ChannelStartupService {
const version = this.configService.get<WaBusiness>('WA_BUSINESS').VERSION;
urlServer = `${urlServer}/${version}/${id}`;
const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${this.token}` };
// Primeiro, obtenha a URL do arquivo
let result = await axios.get(urlServer, { headers });
result = await axios.get(result.data.url, { headers, responseType: 'arraybuffer' });
// Depois, baixe o arquivo usando a URL retornada
result = await axios.get(result.data.url, {
headers: { Authorization: `Bearer ${this.token}` }, // Use apenas o token de autorização para download
responseType: 'arraybuffer',
});
return result.data;
} catch (e) {
this.logger.error(e);
this.logger.error(`Error downloading media: ${e}`);
throw e;
}
}
@@ -156,7 +168,23 @@ export class BusinessStartupService extends ChannelStartupService {
const message = received.messages[0];
let content: any = message.type + 'Message';
content = { [content]: message[message.type] };
message.context ? (content = { ...content, contextInfo: { stanzaId: message.context.id } }) : content;
if (message.context) {
content = { ...content, contextInfo: { stanzaId: message.context.id } };
}
return content;
}
private messageAudioJson(received: any) {
const message = received.messages[0];
let content: any = {
audioMessage: {
...message.audio,
ptt: message.audio.voice || false, // Define se é mensagem de voz
},
};
if (message.context) {
content = { ...content, contextInfo: { stanzaId: message.context.id } };
}
return content;
}
@@ -189,17 +217,77 @@ export class BusinessStartupService extends ChannelStartupService {
}
private messageTextJson(received: any) {
let content: any;
// Verificar que received y received.messages existen
if (!received || !received.messages || received.messages.length === 0) {
this.logger.error('Error: received object or messages array is undefined or empty');
return null;
}
const message = received.messages[0];
let content: any;
// Verificar si es un mensaje de tipo sticker, location u otro tipo que no tiene text
if (!message.text) {
// Si no hay texto, manejamos diferente según el tipo de mensaje
if (message.type === 'sticker') {
content = { stickerMessage: {} };
} else if (message.type === 'location') {
content = {
locationMessage: {
degreesLatitude: message.location?.latitude,
degreesLongitude: message.location?.longitude,
name: message.location?.name,
address: message.location?.address,
},
};
} else {
// Para otros tipos de mensajes sin texto, creamos un contenido genérico
this.logger.log(`Mensaje de tipo ${message.type} sin campo text`);
content = { [message.type + 'Message']: message[message.type] || {} };
}
// Añadir contexto si existe
if (message.context) {
content = { ...content, contextInfo: { stanzaId: message.context.id } };
}
return content;
}
// Si el mensaje tiene texto, procesamos normalmente
if (!received.metadata || !received.metadata.phone_number_id) {
this.logger.error('Error: metadata or phone_number_id is undefined');
return null;
}
if (message.from === received.metadata.phone_number_id) {
content = {
extendedTextMessage: { text: message.text.body },
};
message.context ? (content = { ...content, contextInfo: { stanzaId: message.context.id } }) : content;
if (message.context) {
content = { ...content, contextInfo: { stanzaId: message.context.id } };
}
} else {
content = { conversation: message.text.body };
message.context ? (content = { ...content, contextInfo: { stanzaId: message.context.id } }) : content;
if (message.context) {
content = { ...content, contextInfo: { stanzaId: message.context.id } };
}
}
return content;
}
private messageLocationJson(received: any) {
const message = received.messages[0];
let content: any = {
locationMessage: {
degreesLatitude: message.location.latitude,
degreesLongitude: message.location.longitude,
name: message.location?.name,
address: message.location?.address,
},
};
message.context ? (content = { ...content, contextInfo: { stanzaId: message.context.id } }) : content;
return content;
}
@@ -227,7 +315,7 @@ export class BusinessStartupService extends ChannelStartupService {
}
if (!contact.phones[0]?.wa_id) {
contact.phones[0].wa_id = this.createJid(contact.phones[0].phone);
contact.phones[0].wa_id = createJid(contact.phones[0].phone);
}
result +=
@@ -280,6 +368,12 @@ export class BusinessStartupService extends ChannelStartupService {
case 'template':
messageType = 'conversation';
break;
case 'location':
messageType = 'locationMessage';
break;
case 'sticker':
messageType = 'stickerMessage';
break;
default:
messageType = 'conversation';
break;
@@ -296,22 +390,36 @@ export class BusinessStartupService extends ChannelStartupService {
if (received.contacts) pushName = received.contacts[0].profile.name;
if (received.messages) {
const message = received.messages[0]; // Añadir esta línea para definir message
const key = {
id: received.messages[0].id,
id: message.id,
remoteJid: this.phoneNumber,
fromMe: received.messages[0].from === received.metadata.phone_number_id,
fromMe: message.from === received.metadata.phone_number_id,
};
if (
received?.messages[0].document ||
received?.messages[0].image ||
received?.messages[0].audio ||
received?.messages[0].video
) {
if (message.type === 'sticker') {
this.logger.log('Procesando mensaje de tipo sticker');
messageRaw = {
key,
pushName,
message: this.messageMediaJson(received),
contextInfo: this.messageMediaJson(received)?.contextInfo,
message: {
stickerMessage: message.sticker || {},
},
messageType: 'stickerMessage',
messageTimestamp: parseInt(message.timestamp) as number,
source: 'unknown',
instanceId: this.instanceId,
};
} else if (this.isMediaMessage(message)) {
const messageContent =
message.type === 'audio' ? this.messageAudioJson(received) : this.messageMediaJson(received);
messageRaw = {
key,
pushName,
message: messageContent,
contextInfo: messageContent?.contextInfo,
messageType: this.renderMessageType(received.messages[0].type),
messageTimestamp: parseInt(received.messages[0].timestamp) as number,
source: 'unknown',
@@ -322,62 +430,160 @@ export class BusinessStartupService extends ChannelStartupService {
try {
const message: any = received;
const id = message.messages[0][message.messages[0].type].id;
let urlServer = this.configService.get<WaBusiness>('WA_BUSINESS').URL;
const version = this.configService.get<WaBusiness>('WA_BUSINESS').VERSION;
urlServer = `${urlServer}/${version}/${id}`;
const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${this.token}` };
const result = await axios.get(urlServer, { headers });
// Verificação adicional para garantir que há conteúdo de mídia real
const hasRealMedia = this.hasValidMediaContent(messageRaw);
const buffer = await axios.get(result.data.url, { headers, responseType: 'arraybuffer' });
if (!hasRealMedia) {
this.logger.warn('Message detected as media but contains no valid media content');
} else {
const id = message.messages[0][message.messages[0].type].id;
let urlServer = this.configService.get<WaBusiness>('WA_BUSINESS').URL;
const version = this.configService.get<WaBusiness>('WA_BUSINESS').VERSION;
urlServer = `${urlServer}/${version}/${id}`;
const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${this.token}` };
const result = await axios.get(urlServer, { headers });
const mediaType = message.messages[0].document
? 'document'
: message.messages[0].image
? 'image'
: message.messages[0].audio
? 'audio'
: 'video';
const buffer = await axios.get(result.data.url, {
headers: { Authorization: `Bearer ${this.token}` }, // Use apenas o token de autorização para download
responseType: 'arraybuffer',
});
const mimetype = result.headers['content-type'];
let mediaType;
const contentDisposition = result.headers['content-disposition'];
let fileName = `${message.messages[0].id}.${mimetype.split('/')[1]}`;
if (contentDisposition) {
const match = contentDisposition.match(/filename="(.+?)"/);
if (match) {
fileName = match[1];
if (message.messages[0].document) {
mediaType = 'document';
} else if (message.messages[0].image) {
mediaType = 'image';
} else if (message.messages[0].audio) {
mediaType = 'audio';
} else {
mediaType = 'video';
}
if (mediaType == 'video' && !this.configService.get<S3>('S3').SAVE_VIDEO) {
this.logger?.info?.('Video upload attempted but is disabled by configuration.');
return {
success: false,
message:
'Video upload is currently disabled. Please contact support if you need this feature enabled.',
};
}
const mimetype = result.data?.mime_type || result.headers['content-type'];
const contentDisposition = result.headers['content-disposition'];
let fileName = `${message.messages[0].id}.${mimetype.split('/')[1]}`;
if (contentDisposition) {
const match = contentDisposition.match(/filename="(.+?)"/);
if (match) {
fileName = match[1];
}
}
// Para áudio, garantir extensão correta baseada no mimetype
if (mediaType === 'audio') {
if (mimetype.includes('ogg')) {
fileName = `${message.messages[0].id}.ogg`;
} else if (mimetype.includes('mp3')) {
fileName = `${message.messages[0].id}.mp3`;
} else if (mimetype.includes('m4a')) {
fileName = `${message.messages[0].id}.m4a`;
}
}
const size = result.headers['content-length'] || buffer.data.byteLength;
const fullName = join(`${this.instance.id}`, key.remoteJid, mediaType, fileName);
await s3Service.uploadFile(fullName, buffer.data, size, {
'Content-Type': mimetype,
});
const createdMessage = await this.prismaRepository.message.create({
data: messageRaw,
});
await this.prismaRepository.media.create({
data: {
messageId: createdMessage.id,
instanceId: this.instanceId,
type: mediaType,
fileName: fullName,
mimetype,
},
});
const mediaUrl = await s3Service.getObjectUrl(fullName);
messageRaw.message.mediaUrl = mediaUrl;
messageRaw.message.base64 = buffer.data.toString('base64');
// Processar OpenAI speech-to-text para áudio após o mediaUrl estar disponível
if (this.configService.get<Openai>('OPENAI').ENABLED && mediaType === 'audio') {
const openAiDefaultSettings = await this.prismaRepository.openaiSetting.findFirst({
where: {
instanceId: this.instanceId,
},
include: {
OpenaiCreds: true,
},
});
if (
openAiDefaultSettings &&
openAiDefaultSettings.openaiCredsId &&
openAiDefaultSettings.speechToText
) {
try {
messageRaw.message.speechToText = `[audio] ${await this.openaiService.speechToText(
openAiDefaultSettings.OpenaiCreds,
{
message: {
mediaUrl: messageRaw.message.mediaUrl,
...messageRaw,
},
},
)}`;
} catch (speechError) {
this.logger.error(`Error processing speech-to-text: ${speechError}`);
}
}
}
}
const size = result.headers['content-length'] || buffer.data.byteLength;
const fullName = join(`${this.instance.id}`, received.key.remoteJid, mediaType, fileName);
await s3Service.uploadFile(fullName, buffer.data, size, {
'Content-Type': mimetype,
});
await this.prismaRepository.media.create({
data: {
messageId: received.messages[0].id,
instanceId: this.instanceId,
type: mediaType,
fileName: fullName,
mimetype,
},
});
const mediaUrl = await s3Service.getObjectUrl(fullName);
messageRaw.message.mediaUrl = mediaUrl;
} catch (error) {
this.logger.error(['Error on upload file to minio', error?.message, error?.stack]);
}
} else {
const buffer = await this.downloadMediaMessage(received?.messages[0]);
messageRaw.message.base64 = buffer.toString('base64');
// Processar OpenAI speech-to-text para áudio mesmo sem S3
if (this.configService.get<Openai>('OPENAI').ENABLED && message.type === 'audio') {
const openAiDefaultSettings = await this.prismaRepository.openaiSetting.findFirst({
where: {
instanceId: this.instanceId,
},
include: {
OpenaiCreds: true,
},
});
if (openAiDefaultSettings && openAiDefaultSettings.openaiCredsId && openAiDefaultSettings.speechToText) {
try {
messageRaw.message.speechToText = `[audio] ${await this.openaiService.speechToText(
openAiDefaultSettings.OpenaiCreds,
{
message: {
base64: messageRaw.message.base64,
...messageRaw,
},
},
)}`;
} catch (speechError) {
this.logger.error(`Error processing speech-to-text: ${speechError}`);
}
}
}
}
} else if (received?.messages[0].interactive) {
messageRaw = {
@@ -448,32 +654,10 @@ export class BusinessStartupService extends ChannelStartupService {
// await this.client.readMessages([received.key]);
}
if (this.configService.get<Openai>('OPENAI').ENABLED) {
const openAiDefaultSettings = await this.prismaRepository.openaiSetting.findFirst({
where: {
instanceId: this.instanceId,
},
include: {
OpenaiCreds: true,
},
});
if (
openAiDefaultSettings &&
openAiDefaultSettings.openaiCredsId &&
openAiDefaultSettings.speechToText &&
received?.message?.audioMessage
) {
messageRaw.message.speechToText = await this.openaiService.speechToText(
openAiDefaultSettings.OpenaiCreds,
received,
this.client.updateMediaMessage,
);
}
}
this.logger.log(messageRaw);
sendTelemetry(`received.message.${messageRaw.messageType ?? 'unknown'}`);
this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw);
await chatbotController.emit({
@@ -497,9 +681,11 @@ export class BusinessStartupService extends ChannelStartupService {
}
}
await this.prismaRepository.message.create({
data: messageRaw,
});
if (!this.isMediaMessage(message) && message.type !== 'sticker') {
await this.prismaRepository.message.create({
data: messageRaw,
});
}
const contact = await this.prismaRepository.contact.findFirst({
where: { instanceId: this.instanceId, remoteJid: key.remoteJid },
@@ -698,17 +884,54 @@ export class BusinessStartupService extends ChannelStartupService {
}
protected async eventHandler(content: any) {
const database = this.configService.get<Database>('DATABASE');
const settings = await this.findSettings();
try {
// Registro para depuración
this.logger.log('Contenido recibido en eventHandler:');
this.logger.log(JSON.stringify(content, null, 2));
this.messageHandle(content, database, settings);
const database = this.configService.get<Database>('DATABASE');
const settings = await this.findSettings();
// Si hay mensajes, verificar primero el tipo
if (content.messages && content.messages.length > 0) {
const message = content.messages[0];
this.logger.log(`Tipo de mensaje recibido: ${message.type}`);
// Verificamos el tipo de mensaje antes de procesarlo
if (
message.type === 'text' ||
message.type === 'image' ||
message.type === 'video' ||
message.type === 'audio' ||
message.type === 'document' ||
message.type === 'sticker' ||
message.type === 'location' ||
message.type === 'contacts' ||
message.type === 'interactive' ||
message.type === 'button' ||
message.type === 'reaction'
) {
// Procesar el mensaje normalmente
this.messageHandle(content, database, settings);
} else {
this.logger.warn(`Tipo de mensaje no reconocido: ${message.type}`);
}
} else if (content.statuses) {
// Procesar actualizaciones de estado
this.messageHandle(content, database, settings);
} else {
this.logger.warn('No se encontraron mensajes ni estados en el contenido recibido');
}
} catch (error) {
this.logger.error('Error en eventHandler:');
this.logger.error(error);
}
}
protected async sendMessageWithTyping(number: string, message: any, options?: Options, isIntegration = false) {
try {
let quoted: any;
let webhookUrl: any;
const linkPreview = options?.linkPreview != false ? undefined : false;
if (options?.quoted) {
const m = options?.quoted;
@@ -776,13 +999,15 @@ export class BusinessStartupService extends ChannelStartupService {
to: number.replace(/\D/g, ''),
text: {
body: message['conversation'],
preview_url: linkPreview,
preview_url: Boolean(options?.linkPreview),
},
};
quoted ? (content.context = { message_id: quoted.id }) : content;
return await this.post(content, 'messages');
}
if (message['media']) {
const isImage = message['mimetype']?.startsWith('image/');
content = {
messaging_product: 'whatsapp',
recipient_type: 'individual',
@@ -790,8 +1015,10 @@ export class BusinessStartupService extends ChannelStartupService {
to: number.replace(/\D/g, ''),
[message['mediaType']]: {
[message['type']]: message['id'],
preview_url: linkPreview,
caption: message['caption'],
...(message['mediaType'] !== 'audio' &&
message['fileName'] &&
!isImage && { filename: message['fileName'] }),
...(message['mediaType'] !== 'audio' && message['caption'] && { caption: message['caption'] }),
},
};
quoted ? (content.context = { message_id: quoted.id }) : content;
@@ -889,13 +1116,13 @@ export class BusinessStartupService extends ChannelStartupService {
}
})();
if (messageSent?.error_data) {
if (messageSent?.error_data || messageSent.message) {
this.logger.error(messageSent);
return messageSent;
}
const messageRaw: any = {
key: { fromMe: true, id: messageSent?.messages[0]?.id, remoteJid: this.createJid(number) },
key: { fromMe: true, id: messageSent?.messages[0]?.id, remoteJid: createJid(number) },
message: this.convertMessageToRaw(message, content),
messageType: this.renderMessageType(content.type),
messageTimestamp: (messageSent?.messages[0]?.timestamp as number) || Math.round(new Date().getTime() / 1000),
@@ -956,29 +1183,50 @@ export class BusinessStartupService extends ChannelStartupService {
return res;
}
private async getIdMedia(mediaMessage: any) {
const formData = new FormData();
private async getIdMedia(mediaMessage: any, isFile = false) {
try {
const formData = new FormData();
const fileStream = createReadStream(mediaMessage.media);
if (isFile === false) {
if (isURL(mediaMessage.media)) {
const response = await axios.get(mediaMessage.media, { responseType: 'arraybuffer' });
const buffer = Buffer.from(response.data, 'base64');
formData.append('file', buffer, {
filename: mediaMessage.fileName || 'media',
contentType: mediaMessage.mimetype,
});
} else {
const buffer = Buffer.from(mediaMessage.media, 'base64');
formData.append('file', buffer, {
filename: mediaMessage.fileName || 'media',
contentType: mediaMessage.mimetype,
});
}
} else {
formData.append('file', mediaMessage.media.buffer, {
filename: mediaMessage.media.originalname,
contentType: mediaMessage.media.mimetype,
});
}
formData.append('file', fileStream, { filename: 'media', contentType: mediaMessage.mimetype });
formData.append('typeFile', mediaMessage.mimetype);
formData.append('messaging_product', 'whatsapp');
const mimetype = mediaMessage.mimetype || mediaMessage.media.mimetype;
// const fileBuffer = await fs.readFile(mediaMessage.media);
formData.append('typeFile', mimetype);
formData.append('messaging_product', 'whatsapp');
// const fileBlob = new Blob([fileBuffer], { type: mediaMessage.mimetype });
// formData.append('file', fileBlob);
// formData.append('typeFile', mediaMessage.mimetype);
// formData.append('messaging_product', 'whatsapp');
const token = this.token;
const headers = { Authorization: `Bearer ${this.token}` };
const res = await axios.post(
process.env.API_URL + '/' + process.env.VERSION + '/' + this.number + '/media',
formData,
{ headers },
);
return res.data.id;
const headers = { Authorization: `Bearer ${token}` };
const url = `${this.configService.get<WaBusiness>('WA_BUSINESS').URL}/${
this.configService.get<WaBusiness>('WA_BUSINESS').VERSION
}/${this.number}/media`;
const res = await axios.post(url, formData, { headers });
return res.data.id;
} catch (error) {
this.logger.error(error.response.data);
throw new InternalServerErrorException(error?.toString() || error);
}
}
protected async prepareMediaMessage(mediaMessage: MediaMessage) {
@@ -997,7 +1245,7 @@ export class BusinessStartupService extends ChannelStartupService {
mediaMessage.fileName = 'video.mp4';
}
let mimetype: string;
let mimetype: string | false;
const prepareMedia: any = {
caption: mediaMessage?.caption,
@@ -1008,11 +1256,11 @@ export class BusinessStartupService extends ChannelStartupService {
};
if (isURL(mediaMessage.media)) {
mimetype = mime.getType(mediaMessage.media);
mimetype = mimeTypes.lookup(mediaMessage.media);
prepareMedia.id = mediaMessage.media;
prepareMedia.type = 'link';
} else {
mimetype = mime.getType(mediaMessage.fileName);
mimetype = mimeTypes.lookup(mediaMessage.fileName);
const id = await this.getIdMedia(prepareMedia);
prepareMedia.id = id;
prepareMedia.type = 'id';
@@ -1051,45 +1299,88 @@ export class BusinessStartupService extends ChannelStartupService {
return mediaSent;
}
public async processAudio(audio: string, number: string) {
public async processAudio(audio: string, number: string, file: any) {
number = number.replace(/\D/g, '');
const hash = `${number}-${new Date().getTime()}`;
let mimetype: string;
const audioConverterConfig = this.configService.get<AudioConverter>('AUDIO_CONVERTER');
if (audioConverterConfig.API_URL) {
this.logger.verbose('Using audio converter API');
const formData = new FormData();
const prepareMedia: any = {
fileName: `${hash}.mp3`,
mediaType: 'audio',
media: audio,
};
if (file) {
formData.append('file', file.buffer, {
filename: file.originalname,
contentType: file.mimetype,
});
} else if (isURL(audio)) {
formData.append('url', audio);
} else {
formData.append('base64', audio);
}
formData.append('format', 'mp3');
const response = await axios.post(audioConverterConfig.API_URL, formData, {
headers: {
...formData.getHeaders(),
apikey: audioConverterConfig.API_KEY,
},
});
const audioConverter = response?.data?.audio || response?.data?.url;
if (!audioConverter) {
throw new InternalServerErrorException('Failed to convert audio');
}
const prepareMedia: any = {
fileName: `${hash}.mp3`,
mediaType: 'audio',
media: audioConverter,
mimetype: 'audio/mpeg',
};
if (isURL(audio)) {
mimetype = mime.getType(audio);
prepareMedia.id = audio;
prepareMedia.type = 'link';
} else {
mimetype = mime.getType(prepareMedia.fileName);
const id = await this.getIdMedia(prepareMedia);
prepareMedia.id = id;
prepareMedia.type = 'id';
this.logger.verbose('Audio converted');
return prepareMedia;
} else {
let mimetype: string | false;
const prepareMedia: any = {
fileName: `${hash}.mp3`,
mediaType: 'audio',
media: audio,
};
if (isURL(audio)) {
mimetype = mimeTypes.lookup(audio);
prepareMedia.id = audio;
prepareMedia.type = 'link';
} else if (audio && !file) {
mimetype = mimeTypes.lookup(prepareMedia.fileName);
const id = await this.getIdMedia(prepareMedia);
prepareMedia.id = id;
prepareMedia.type = 'id';
} else if (file) {
prepareMedia.media = file;
const id = await this.getIdMedia(prepareMedia, true);
prepareMedia.id = id;
prepareMedia.type = 'id';
mimetype = file.mimetype;
}
prepareMedia.mimetype = mimetype;
return prepareMedia;
}
prepareMedia.mimetype = mimetype;
return prepareMedia;
}
public async audioWhatsapp(data: SendAudioDto, file?: any, isIntegration = false) {
const mediaData: SendAudioDto = { ...data };
if (file?.buffer) {
mediaData.audio = file.buffer.toString('base64');
} else {
console.error('El archivo no tiene buffer o file es undefined');
throw new Error('File or buffer is undefined');
}
const message = await this.processAudio(mediaData.audio, data.number);
const message = await this.processAudio(data.audio, data.number, file);
const audioSent = await this.sendMessageWithTyping(
data.number,
@@ -1251,7 +1542,7 @@ export class BusinessStartupService extends ChannelStartupService {
}
if (!contact.wuid) {
contact.wuid = this.createJid(contact.phoneNumber);
contact.wuid = createJid(contact.phoneNumber);
}
result += `item1.TEL;waid=${contact.wuid}:${contact.phoneNumber}\n` + 'item1.X-ABLabel:Celular\n' + 'END:VCARD';

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