mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-12-15 21:09:33 -06:00
Compare commits
No commits in common. "main" and "2.3.5" have entirely different histories.
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -59,7 +59,7 @@ body:
|
|||||||
value: |
|
value: |
|
||||||
- OS: [e.g. Ubuntu 20.04, Windows 10, macOS 12.0]
|
- OS: [e.g. Ubuntu 20.04, Windows 10, macOS 12.0]
|
||||||
- Node.js version: [e.g. 18.17.0]
|
- Node.js version: [e.g. 18.17.0]
|
||||||
- Evolution API version: [e.g. 2.3.7]
|
- Evolution API version: [e.g. 2.3.5]
|
||||||
- Database: [e.g. PostgreSQL 14, MySQL 8.0]
|
- Database: [e.g. PostgreSQL 14, MySQL 8.0]
|
||||||
- Connection type: [e.g. Baileys, WhatsApp Business API]
|
- Connection type: [e.g. Baileys, WhatsApp Business API]
|
||||||
validations:
|
validations:
|
||||||
|
|||||||
179
CHANGELOG.md
179
CHANGELOG.md
@ -1,182 +1,3 @@
|
|||||||
# 2.3.7 (2025-12-05)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **WhatsApp Business Meta Templates**: Add update and delete endpoints for Meta templates
|
|
||||||
- New endpoints to edit and delete WhatsApp Business templates
|
|
||||||
- Added DTOs and validation schemas for template management
|
|
||||||
- Enhanced template lifecycle management capabilities
|
|
||||||
|
|
||||||
* **Events API**: Add isLatest and progress to messages.set event
|
|
||||||
- Allows consumers to know when history sync is complete (isLatest=true)
|
|
||||||
- Track sync progress percentage through webhooks
|
|
||||||
- Added extra field to EmitData type for additional payload properties
|
|
||||||
- Updated all event controllers (webhook, rabbitmq, sqs, websocket, pusher, kafka, nats)
|
|
||||||
|
|
||||||
* **N8N Integration**: Add quotedMessage to payload in sendMessageToBot
|
|
||||||
- Support for quoted messages in N8N chatbot integration
|
|
||||||
- Enhanced message context information
|
|
||||||
|
|
||||||
* **WebSocket**: Add wildcard "*" to allow all hosts to connect via websocket
|
|
||||||
- More flexible host configuration for WebSocket connections
|
|
||||||
- Improved host validation logic in WebsocketController
|
|
||||||
|
|
||||||
* **Pix Support**: Handle interactive button message for pix
|
|
||||||
- Support for interactive Pix button messages
|
|
||||||
- Enhanced payment flow integration
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
* **Baileys Message Processor**: Fix incoming message events not working after reconnection
|
|
||||||
- Added cleanup logic in mount() to prevent memory leaks from multiple subscriptions
|
|
||||||
- Recreate messageSubject if it was completed during logout
|
|
||||||
- Remount messageProcessor in connectToWhatsapp() to ensure subscription is active
|
|
||||||
- Fixed issue where onDestroy() calls complete() on RxJS Subject, making it permanently closed
|
|
||||||
- Ensures old subscriptions are properly cleaned up before creating new ones
|
|
||||||
|
|
||||||
* **Baileys Authentication**: Resolve "waiting for message" state after reconnection
|
|
||||||
- Fixed Redis keys not being properly removed during instance logout
|
|
||||||
- Prevented loading of old/invalid cryptographic keys on reconnection
|
|
||||||
- Fixed blocking state where instances authenticate but cannot send messages
|
|
||||||
- Ensures new credentials (creds) are properly used after reconnection
|
|
||||||
|
|
||||||
* **OnWhatsapp Cache**: Prevent unique constraint errors and optimize database writes
|
|
||||||
- Fixed `Unique constraint failed on the fields: (remoteJid)` error when sending to groups
|
|
||||||
- Refactored query to use OR condition finding by jidOptions or remoteJid
|
|
||||||
- Added deep comparison to skip unnecessary database updates
|
|
||||||
- Replaced sequential processing with Promise.allSettled for parallel execution
|
|
||||||
- Sorted JIDs alphabetically in jidOptions for accurate change detection
|
|
||||||
- Added normalizeJid helper function for cleaner code
|
|
||||||
|
|
||||||
* **Proxy Integration**: Fix "Media upload failed on all hosts" error when using proxy
|
|
||||||
- Created makeProxyAgentUndici() for Undici-compatible proxy agents
|
|
||||||
- Fixed compatibility with Node.js 18+ native fetch() implementation
|
|
||||||
- Replaced traditional HttpsProxyAgent/SocksProxyAgent with Undici ProxyAgent
|
|
||||||
- Maintained legacy makeProxyAgent() for Axios compatibility
|
|
||||||
- Fixed protocol handling in makeProxyAgent to prevent undefined errors
|
|
||||||
|
|
||||||
* **WhatsApp Business API**: Fix base64, filename and caption handling
|
|
||||||
- Corrected base64 media conversion in Business API
|
|
||||||
- Fixed filename handling for document messages
|
|
||||||
- Improved caption processing for media messages
|
|
||||||
- Enhanced remoteJid validation and processing
|
|
||||||
|
|
||||||
* **Chat Service**: Fix fetchChats and message panel errors
|
|
||||||
- Fixed cleanMessageData errors in Manager message panel
|
|
||||||
- Improved chat fetching reliability
|
|
||||||
- Enhanced message data sanitization
|
|
||||||
|
|
||||||
* **Contact Filtering**: Apply where filters correctly in findContacts endpoint
|
|
||||||
- Fixed endpoint to process all where clause fields (id, remoteJid, pushName)
|
|
||||||
- Previously only processed remoteJid field, ignoring other filters
|
|
||||||
- Added remoteJid field to contactValidateSchema for proper validation
|
|
||||||
- Maintained multi-tenant isolation with instanceId filtering
|
|
||||||
- Allows filtering contacts by any supported field instead of returning all contacts
|
|
||||||
|
|
||||||
* **Chatwoot and Baileys Integration**: Multiple integration improvements
|
|
||||||
- Enhanced code formatting and consistency
|
|
||||||
- Fixed integration issues between Chatwoot and Baileys services
|
|
||||||
- Improved message handling and delivery
|
|
||||||
|
|
||||||
* **Baileys Message Loss**: Prevent message loss from WhatsApp stub placeholders
|
|
||||||
- Fixed messages being lost and not saved to database, especially for channels/newsletters (@lid)
|
|
||||||
- Detects WhatsApp stubs through messageStubParameters containing 'Message absent from node'
|
|
||||||
- Prevents adding stubs to duplicate message cache
|
|
||||||
- Allows real message to be processed when it arrives after decryption
|
|
||||||
- Maintains stub discard to avoid saving empty placeholders
|
|
||||||
|
|
||||||
* **Database Contacts**: Respect DATABASE_SAVE_DATA_CONTACTS in contact updates
|
|
||||||
- Added missing conditional checks for DATABASE_SAVE_DATA_CONTACTS configuration
|
|
||||||
- Fixed profile picture updates attempting to save when database save is disabled
|
|
||||||
- Fixed unawaited promise in contacts.upsert handler
|
|
||||||
|
|
||||||
* **Prisma/PostgreSQL**: Add unique constraint to Chat model
|
|
||||||
- Generated migration to add unique index on instanceId and remoteJid
|
|
||||||
- Added deduplication step before creating index to prevent constraint violations
|
|
||||||
- Prevents chat duplication in database
|
|
||||||
|
|
||||||
* **MinIO Upload**: Handle messageContextInfo in media upload to prevent MinIO errors
|
|
||||||
- Prevents errors when uploading media with messageContextInfo metadata
|
|
||||||
- Improved error handling for media storage operations
|
|
||||||
|
|
||||||
* **Typebot**: Fix message routing for @lid JIDs
|
|
||||||
- Typebot now responds to messages from JIDs ending with @lid
|
|
||||||
- Maintains complete JID for @lid instead of extracting only number
|
|
||||||
- Fixed condition: `remoteJid.includes('@lid') ? remoteJid : remoteJid.split('@')[0]`
|
|
||||||
- Handles both @s.whatsapp.net and @lid message formats
|
|
||||||
|
|
||||||
* **Message Filtering**: Unify remoteJid filtering using OR with remoteJidAlt
|
|
||||||
- Improved message filtering with alternative JID support
|
|
||||||
- Better handling of messages with different JID formats
|
|
||||||
|
|
||||||
* **@lid Integration**: Multiple fixes for @lid problems, message events and chatwoot errors
|
|
||||||
- Reorganized imports and improved message handling in BaileysStartupService
|
|
||||||
- Enhanced remoteJid processing to handle @lid cases
|
|
||||||
- Improved jid normalization and type safety in Chatwoot integration
|
|
||||||
- Streamlined message handling logic and cache management
|
|
||||||
- Refactored message handling and polling updates with decryption logic for poll votes
|
|
||||||
- Improved event processing flow for various message types
|
|
||||||
|
|
||||||
* **Chatwoot Contacts**: Fix contact duplication error on import
|
|
||||||
- Resolved 'ON CONFLICT DO UPDATE command cannot affect row a second time' error
|
|
||||||
- Removed attempt to update identifier field in conflict (part of constraint)
|
|
||||||
- Changed to update only updated_at field: `updated_at = NOW()`
|
|
||||||
- Allows duplicate contacts to be updated correctly without errors
|
|
||||||
|
|
||||||
* **Chatwoot Service**: Fix async handling in update_last_seen method
|
|
||||||
- Added missing await for chatwootRequest in read message processing
|
|
||||||
- Prevents service failure when processing read messages
|
|
||||||
|
|
||||||
* **Metrics Access**: Fix IP validation including x-forwarded-for
|
|
||||||
- Uses all IPs including x-forwarded-for header when checking metrics access
|
|
||||||
- Improved security and access control for metrics endpoint
|
|
||||||
|
|
||||||
### Dependencies
|
|
||||||
|
|
||||||
* **Baileys**: Updated to version 7.0.0-rc.9
|
|
||||||
- Latest release candidate with multiple improvements and bug fixes
|
|
||||||
|
|
||||||
* **AWS SDK**: Updated packages to version 3.936.0
|
|
||||||
- Enhanced functionality and compatibility
|
|
||||||
- Performance improvements
|
|
||||||
|
|
||||||
### Code Quality & Refactoring
|
|
||||||
|
|
||||||
* **Template Management**: Remove unused template edit/delete DTOs after refactoring
|
|
||||||
* **Proxy Utilities**: Improve makeProxyAgent for Undici compatibility
|
|
||||||
* **Code Formatting**: Enhance code formatting and consistency across services
|
|
||||||
* **BaileysStartupService**: Fix indentation and remove unnecessary blank lines
|
|
||||||
* **Event Controllers**: Guard extra spread and prevent core field override in all event controllers
|
|
||||||
* **Import Organization**: Reorganize imports for better code structure and maintainability
|
|
||||||
|
|
||||||
# 2.3.6 (2025-10-21)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **Baileys, Chatwoot, OnWhatsapp Cache**: Multiple implementations and fixes
|
|
||||||
- Fixed cache for PN, LID and g.us numbers to send correct number
|
|
||||||
- Fixed audio and document sending via Chatwoot in Baileys channel
|
|
||||||
- Multiple fixes in Chatwoot integration
|
|
||||||
- Fixed ignored messages when receiving leads
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
* **Baileys**: Fix buffer storage in database
|
|
||||||
- Correctly save Uint8Array values to database
|
|
||||||
* **Baileys**: Simplify logging of messageSent object
|
|
||||||
- Fixed "this.isZero not is function" error
|
|
||||||
|
|
||||||
### Chore
|
|
||||||
|
|
||||||
* **Version**: Bump version to 2.3.6 and update Baileys dependency to 7.0.0-rc.6
|
|
||||||
* **Workflows**: Update checkout step to include submodules
|
|
||||||
- Added 'submodules: recursive' option to checkout step in multiple workflow files to ensure submodules are properly initialized during CI/CD processes
|
|
||||||
* **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
|
|
||||||
- Replaced old JavaScript asset file with a new version for improved performance
|
|
||||||
- Added a new CSS file for consistent styling across the application
|
|
||||||
|
|
||||||
# 2.3.5 (2025-10-15)
|
# 2.3.5 (2025-10-15)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@ -2,7 +2,7 @@ version: "3.7"
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
evolution_v2:
|
evolution_v2:
|
||||||
image: evoapicloud/evolution-api:v2.3.7
|
image: evoapicloud/evolution-api:v2.3.5
|
||||||
volumes:
|
volumes:
|
||||||
- evolution_instances:/evolution/instances
|
- evolution_instances:/evolution/instances
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
4935
package-lock.json
generated
4935
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "evolution-api",
|
"name": "evolution-api",
|
||||||
"version": "2.3.7",
|
"version": "2.3.5",
|
||||||
"description": "Rest api for communication with WhatsApp",
|
"description": "Rest api for communication with WhatsApp",
|
||||||
"main": "./dist/main.js",
|
"main": "./dist/main.js",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
@ -77,7 +77,7 @@
|
|||||||
"amqplib": "^0.10.5",
|
"amqplib": "^0.10.5",
|
||||||
"audio-decode": "^2.2.3",
|
"audio-decode": "^2.2.3",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"baileys": "7.0.0-rc.9",
|
"baileys": "^7.0.0-rc.5",
|
||||||
"class-validator": "^0.14.1",
|
"class-validator": "^0.14.1",
|
||||||
"compression": "^1.7.5",
|
"compression": "^1.7.5",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
@ -90,14 +90,12 @@
|
|||||||
"fluent-ffmpeg": "^2.1.3",
|
"fluent-ffmpeg": "^2.1.3",
|
||||||
"form-data": "^4.0.1",
|
"form-data": "^4.0.1",
|
||||||
"https-proxy-agent": "^7.0.6",
|
"https-proxy-agent": "^7.0.6",
|
||||||
"fetch-socks": "^1.3.2",
|
|
||||||
"i18next": "^23.7.19",
|
"i18next": "^23.7.19",
|
||||||
"jimp": "^1.6.0",
|
"jimp": "^1.6.0",
|
||||||
"json-schema": "^0.4.0",
|
"json-schema": "^0.4.0",
|
||||||
"jsonschema": "^1.4.1",
|
"jsonschema": "^1.4.1",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"kafkajs": "^2.2.4",
|
"kafkajs": "^2.2.4",
|
||||||
"libphonenumber-js": "^1.12.25",
|
|
||||||
"link-preview-js": "^3.0.13",
|
"link-preview-js": "^3.0.13",
|
||||||
"long": "^5.2.3",
|
"long": "^5.2.3",
|
||||||
"mediainfo.js": "^0.3.4",
|
"mediainfo.js": "^0.3.4",
|
||||||
@ -123,7 +121,6 @@
|
|||||||
"socks-proxy-agent": "^8.0.5",
|
"socks-proxy-agent": "^8.0.5",
|
||||||
"swagger-ui-express": "^5.0.1",
|
"swagger-ui-express": "^5.0.1",
|
||||||
"tsup": "^8.3.5",
|
"tsup": "^8.3.5",
|
||||||
"undici": "^7.16.0",
|
|
||||||
"uuid": "^13.0.0"
|
"uuid": "^13.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -1,16 +0,0 @@
|
|||||||
-- 1. Cleanup: Remove duplicate chats, keeping the most recently updated one
|
|
||||||
DELETE FROM "Chat"
|
|
||||||
WHERE id IN (
|
|
||||||
SELECT id FROM (
|
|
||||||
SELECT id,
|
|
||||||
ROW_NUMBER() OVER (
|
|
||||||
PARTITION BY "instanceId", "remoteJid"
|
|
||||||
ORDER BY "updatedAt" DESC
|
|
||||||
) as row_num
|
|
||||||
FROM "Chat"
|
|
||||||
) t
|
|
||||||
WHERE t.row_num > 1
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 2. Create the unique index (Constraint)
|
|
||||||
CREATE UNIQUE INDEX "Chat_instanceId_remoteJid_key" ON "Chat"("instanceId", "remoteJid");
|
|
||||||
@ -132,7 +132,6 @@ model Chat {
|
|||||||
instanceId String
|
instanceId String
|
||||||
unreadMessages Int @default(0)
|
unreadMessages Int @default(0)
|
||||||
|
|
||||||
@@unique([instanceId, remoteJid])
|
|
||||||
@@index([instanceId])
|
@@index([instanceId])
|
||||||
@@index([remoteJid])
|
@@index([remoteJid])
|
||||||
}
|
}
|
||||||
|
|||||||
@ -92,15 +92,6 @@ export class InstanceController {
|
|||||||
instanceId: instanceId,
|
instanceId: instanceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const instanceDto: InstanceDto = {
|
|
||||||
instanceName: instance.instanceName,
|
|
||||||
instanceId: instance.instanceId,
|
|
||||||
connectionStatus:
|
|
||||||
typeof instance.connectionStatus === 'string'
|
|
||||||
? instance.connectionStatus
|
|
||||||
: instance.connectionStatus?.state || 'unknown',
|
|
||||||
};
|
|
||||||
|
|
||||||
if (instanceData.proxyHost && instanceData.proxyPort && instanceData.proxyProtocol) {
|
if (instanceData.proxyHost && instanceData.proxyPort && instanceData.proxyProtocol) {
|
||||||
const testProxy = await this.proxyService.testProxy({
|
const testProxy = await this.proxyService.testProxy({
|
||||||
host: instanceData.proxyHost,
|
host: instanceData.proxyHost,
|
||||||
@ -112,7 +103,8 @@ export class InstanceController {
|
|||||||
if (!testProxy) {
|
if (!testProxy) {
|
||||||
throw new BadRequestException('Invalid proxy');
|
throw new BadRequestException('Invalid proxy');
|
||||||
}
|
}
|
||||||
await this.proxyService.createProxy(instanceDto, {
|
|
||||||
|
await this.proxyService.createProxy(instance, {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
host: instanceData.proxyHost,
|
host: instanceData.proxyHost,
|
||||||
port: instanceData.proxyPort,
|
port: instanceData.proxyPort,
|
||||||
@ -133,7 +125,7 @@ export class InstanceController {
|
|||||||
wavoipToken: instanceData.wavoipToken || '',
|
wavoipToken: instanceData.wavoipToken || '',
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.settingsService.create(instanceDto, settings);
|
await this.settingsService.create(instance, settings);
|
||||||
|
|
||||||
let webhookWaBusiness = null,
|
let webhookWaBusiness = null,
|
||||||
accessTokenWaBusiness = '';
|
accessTokenWaBusiness = '';
|
||||||
@ -163,10 +155,7 @@ export class InstanceController {
|
|||||||
integration: instanceData.integration,
|
integration: instanceData.integration,
|
||||||
webhookWaBusiness,
|
webhookWaBusiness,
|
||||||
accessTokenWaBusiness,
|
accessTokenWaBusiness,
|
||||||
status:
|
status: instance.connectionStatus.state,
|
||||||
typeof instance.connectionStatus === 'string'
|
|
||||||
? instance.connectionStatus
|
|
||||||
: instance.connectionStatus?.state || 'unknown',
|
|
||||||
},
|
},
|
||||||
hash,
|
hash,
|
||||||
webhook: {
|
webhook: {
|
||||||
@ -228,7 +217,7 @@ export class InstanceController {
|
|||||||
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
|
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.chatwootService.create(instanceDto, {
|
this.chatwootService.create(instance, {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
accountId: instanceData.chatwootAccountId,
|
accountId: instanceData.chatwootAccountId,
|
||||||
token: instanceData.chatwootToken,
|
token: instanceData.chatwootToken,
|
||||||
@ -257,10 +246,7 @@ export class InstanceController {
|
|||||||
integration: instanceData.integration,
|
integration: instanceData.integration,
|
||||||
webhookWaBusiness,
|
webhookWaBusiness,
|
||||||
accessTokenWaBusiness,
|
accessTokenWaBusiness,
|
||||||
status:
|
status: instance.connectionStatus.state,
|
||||||
typeof instance.connectionStatus === 'string'
|
|
||||||
? instance.connectionStatus
|
|
||||||
: instance.connectionStatus?.state || 'unknown',
|
|
||||||
},
|
},
|
||||||
hash,
|
hash,
|
||||||
webhook: {
|
webhook: {
|
||||||
@ -352,38 +338,20 @@ export class InstanceController {
|
|||||||
throw new BadRequestException('The "' + instanceName + '" instance does not exist');
|
throw new BadRequestException('The "' + instanceName + '" instance does not exist');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state === 'close') {
|
if (state == 'close') {
|
||||||
throw new BadRequestException('The "' + instanceName + '" instance is not connected');
|
throw new BadRequestException('The "' + instanceName + '" instance is not connected');
|
||||||
}
|
} else if (state == 'open') {
|
||||||
this.logger.info(`Restarting instance: ${instanceName}`);
|
|
||||||
|
|
||||||
if (typeof instance.restart === 'function') {
|
|
||||||
await instance.restart();
|
|
||||||
// Wait a bit for the reconnection to be established
|
|
||||||
await new Promise((r) => setTimeout(r, 2000));
|
|
||||||
return {
|
|
||||||
instance: {
|
|
||||||
instanceName: instanceName,
|
|
||||||
status: instance.connectionStatus?.state || 'connecting',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback for Baileys (uses different mechanism)
|
|
||||||
if (state === 'open' || state === 'connecting') {
|
|
||||||
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED) instance.clearCacheChatwoot();
|
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED) instance.clearCacheChatwoot();
|
||||||
|
this.logger.info('restarting instance' + instanceName);
|
||||||
|
|
||||||
|
instance.client?.ws?.close();
|
||||||
|
instance.client?.end(new Error('restart'));
|
||||||
|
return await this.connectToWhatsapp({ instanceName });
|
||||||
|
} else if (state == 'connecting') {
|
||||||
instance.client?.ws?.close();
|
instance.client?.ws?.close();
|
||||||
instance.client?.end(new Error('restart'));
|
instance.client?.end(new Error('restart'));
|
||||||
return await this.connectToWhatsapp({ instanceName });
|
return await this.connectToWhatsapp({ instanceName });
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
instance: {
|
|
||||||
instanceName: instanceName,
|
|
||||||
status: state,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(error);
|
this.logger.error(error);
|
||||||
return { error: true, message: error.toString() };
|
return { error: true, message: error.toString() };
|
||||||
@ -441,7 +409,7 @@ export class InstanceController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.waMonitor.waInstances[instanceName]?.logoutInstance();
|
this.waMonitor.waInstances[instanceName]?.logoutInstance();
|
||||||
|
|
||||||
return { status: 'SUCCESS', error: false, response: { message: 'Instance logged out' } };
|
return { status: 'SUCCESS', error: false, response: { message: 'Instance logged out' } };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -12,15 +12,4 @@ export class TemplateController {
|
|||||||
public async findTemplate(instance: InstanceDto) {
|
public async findTemplate(instance: InstanceDto) {
|
||||||
return this.templateService.find(instance);
|
return this.templateService.find(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async editTemplate(
|
|
||||||
instance: InstanceDto,
|
|
||||||
data: { templateId: string; category?: string; components?: any; allowCategoryChange?: boolean; ttl?: number },
|
|
||||||
) {
|
|
||||||
return this.templateService.edit(instance, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async deleteTemplate(instance: InstanceDto, data: { name: string; hsmId?: string }) {
|
|
||||||
return this.templateService.delete(instance, data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,6 @@ export class InstanceDto extends IntegrationDto {
|
|||||||
token?: string;
|
token?: string;
|
||||||
status?: string;
|
status?: string;
|
||||||
ownerJid?: string;
|
ownerJid?: string;
|
||||||
connectionStatus?: string;
|
|
||||||
profileName?: string;
|
profileName?: string;
|
||||||
profilePicUrl?: string;
|
profilePicUrl?: string;
|
||||||
// settings
|
// settings
|
||||||
|
|||||||
@ -6,16 +6,3 @@ export class TemplateDto {
|
|||||||
components: any;
|
components: any;
|
||||||
webhookUrl?: string;
|
webhookUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TemplateEditDto {
|
|
||||||
templateId: string;
|
|
||||||
category?: 'AUTHENTICATION' | 'MARKETING' | 'UTILITY';
|
|
||||||
allowCategoryChange?: boolean;
|
|
||||||
ttl?: number;
|
|
||||||
components?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TemplateDeleteDto {
|
|
||||||
name: string;
|
|
||||||
hsmId?: string;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -516,9 +516,7 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
const mediaUrl = await s3Service.getObjectUrl(fullName);
|
const mediaUrl = await s3Service.getObjectUrl(fullName);
|
||||||
|
|
||||||
messageRaw.message.mediaUrl = mediaUrl;
|
messageRaw.message.mediaUrl = mediaUrl;
|
||||||
if (this.localWebhook.enabled && this.localWebhook.webhookBase64) {
|
messageRaw.message.base64 = buffer.data.toString('base64');
|
||||||
messageRaw.message.base64 = buffer.data.toString('base64');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Processar OpenAI speech-to-text para áudio após o mediaUrl estar disponível
|
// Processar OpenAI speech-to-text para áudio após o mediaUrl estar disponível
|
||||||
if (this.configService.get<Openai>('OPENAI').ENABLED && mediaType === 'audio') {
|
if (this.configService.get<Openai>('OPENAI').ENABLED && mediaType === 'audio') {
|
||||||
@ -556,19 +554,11 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
this.logger.error(['Error on upload file to minio', error?.message, error?.stack]);
|
this.logger.error(['Error on upload file to minio', error?.message, error?.stack]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.localWebhook.enabled && this.localWebhook.webhookBase64) {
|
const buffer = await this.downloadMediaMessage(received?.messages[0]);
|
||||||
const buffer = await this.downloadMediaMessage(received?.messages[0]);
|
messageRaw.message.base64 = buffer.toString('base64');
|
||||||
messageRaw.message.base64 = buffer.toString('base64');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Processar OpenAI speech-to-text para áudio mesmo sem S3
|
// Processar OpenAI speech-to-text para áudio mesmo sem S3
|
||||||
if (this.configService.get<Openai>('OPENAI').ENABLED && message.type === 'audio') {
|
if (this.configService.get<Openai>('OPENAI').ENABLED && message.type === 'audio') {
|
||||||
let openAiBase64 = messageRaw.message.base64;
|
|
||||||
if (!openAiBase64) {
|
|
||||||
const buffer = await this.downloadMediaMessage(received?.messages[0]);
|
|
||||||
openAiBase64 = buffer.toString('base64');
|
|
||||||
}
|
|
||||||
|
|
||||||
const openAiDefaultSettings = await this.prismaRepository.openaiSetting.findFirst({
|
const openAiDefaultSettings = await this.prismaRepository.openaiSetting.findFirst({
|
||||||
where: {
|
where: {
|
||||||
instanceId: this.instanceId,
|
instanceId: this.instanceId,
|
||||||
@ -584,7 +574,7 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
openAiDefaultSettings.OpenaiCreds,
|
openAiDefaultSettings.OpenaiCreds,
|
||||||
{
|
{
|
||||||
message: {
|
message: {
|
||||||
base64: openAiBase64,
|
base64: messageRaw.message.base64,
|
||||||
...messageRaw,
|
...messageRaw,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1026,7 +1016,6 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
[message['mediaType']]: {
|
[message['mediaType']]: {
|
||||||
[message['type']]: message['id'],
|
[message['type']]: message['id'],
|
||||||
...(message['mediaType'] !== 'audio' &&
|
...(message['mediaType'] !== 'audio' &&
|
||||||
message['mediaType'] !== 'video' &&
|
|
||||||
message['fileName'] &&
|
message['fileName'] &&
|
||||||
!isImage && { filename: message['fileName'] }),
|
!isImage && { filename: message['fileName'] }),
|
||||||
...(message['mediaType'] !== 'audio' && message['caption'] && { caption: message['caption'] }),
|
...(message['mediaType'] !== 'audio' && message['caption'] && { caption: message['caption'] }),
|
||||||
@ -1617,14 +1606,9 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
const messageType = msg.messageType.includes('Message') ? msg.messageType : msg.messageType + 'Message';
|
const messageType = msg.messageType.includes('Message') ? msg.messageType : msg.messageType + 'Message';
|
||||||
const mediaMessage = msg.message[messageType];
|
const mediaMessage = msg.message[messageType];
|
||||||
|
|
||||||
if (!msg.message?.base64) {
|
|
||||||
const buffer = await this.downloadMediaMessage({ type: messageType, ...msg.message });
|
|
||||||
msg.message.base64 = buffer.toString('base64');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mediaType: msg.messageType,
|
mediaType: msg.messageType,
|
||||||
fileName: mediaMessage?.fileName || mediaMessage?.filename,
|
fileName: mediaMessage?.fileName,
|
||||||
caption: mediaMessage?.caption,
|
caption: mediaMessage?.caption,
|
||||||
size: {
|
size: {
|
||||||
fileLength: mediaMessage?.fileLength,
|
fileLength: mediaMessage?.fileLength,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Logger } from '@config/logger.config';
|
import { Logger } from '@config/logger.config';
|
||||||
import { BaileysEventMap, MessageUpsertType, WAMessage } from 'baileys';
|
import { BaileysEventMap, MessageUpsertType, proto } from 'baileys';
|
||||||
import { catchError, concatMap, delay, EMPTY, from, retryWhen, Subject, Subscription, take, tap } from 'rxjs';
|
import { catchError, concatMap, delay, EMPTY, from, retryWhen, Subject, Subscription, take, tap } from 'rxjs';
|
||||||
|
|
||||||
type MessageUpsertPayload = BaileysEventMap['messages.upsert'];
|
type MessageUpsertPayload = BaileysEventMap['messages.upsert'];
|
||||||
@ -12,29 +12,13 @@ export class BaileysMessageProcessor {
|
|||||||
private subscription?: Subscription;
|
private subscription?: Subscription;
|
||||||
|
|
||||||
protected messageSubject = new Subject<{
|
protected messageSubject = new Subject<{
|
||||||
messages: WAMessage[];
|
messages: proto.IWebMessageInfo[];
|
||||||
type: MessageUpsertType;
|
type: MessageUpsertType;
|
||||||
requestId?: string;
|
requestId?: string;
|
||||||
settings: any;
|
settings: any;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
mount({ onMessageReceive }: MountProps) {
|
mount({ onMessageReceive }: MountProps) {
|
||||||
// Se já existe subscription, fazer cleanup primeiro
|
|
||||||
if (this.subscription && !this.subscription.closed) {
|
|
||||||
this.subscription.unsubscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Se o Subject foi completado, recriar
|
|
||||||
if (this.messageSubject.closed) {
|
|
||||||
this.processorLogs.warn('MessageSubject was closed, recreating...');
|
|
||||||
this.messageSubject = new Subject<{
|
|
||||||
messages: WAMessage[];
|
|
||||||
type: MessageUpsertType;
|
|
||||||
requestId?: string;
|
|
||||||
settings: any;
|
|
||||||
}>();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.subscription = this.messageSubject
|
this.subscription = this.messageSubject
|
||||||
.pipe(
|
.pipe(
|
||||||
tap(({ messages }) => {
|
tap(({ messages }) => {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -211,7 +211,7 @@ export abstract class BaseChatbotService<BotType = any, SettingsType = any> {
|
|||||||
try {
|
try {
|
||||||
if (mediaType === 'audio') {
|
if (mediaType === 'audio') {
|
||||||
await instance.audioWhatsapp({
|
await instance.audioWhatsapp({
|
||||||
number: remoteJid.includes('@lid') ? remoteJid : remoteJid.split('@')[0],
|
number: remoteJid.split('@')[0],
|
||||||
delay: (settings as any)?.delayMessage || 1000,
|
delay: (settings as any)?.delayMessage || 1000,
|
||||||
audio: url,
|
audio: url,
|
||||||
caption: altText,
|
caption: altText,
|
||||||
@ -219,7 +219,7 @@ export abstract class BaseChatbotService<BotType = any, SettingsType = any> {
|
|||||||
} else {
|
} else {
|
||||||
await instance.mediaMessage(
|
await instance.mediaMessage(
|
||||||
{
|
{
|
||||||
number: remoteJid.includes('@lid') ? remoteJid : remoteJid.split('@')[0],
|
number: remoteJid.split('@')[0],
|
||||||
delay: (settings as any)?.delayMessage || 1000,
|
delay: (settings as any)?.delayMessage || 1000,
|
||||||
mediatype: mediaType,
|
mediatype: mediaType,
|
||||||
media: url,
|
media: url,
|
||||||
@ -290,7 +290,7 @@ export abstract class BaseChatbotService<BotType = any, SettingsType = any> {
|
|||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
await instance.textMessage(
|
await instance.textMessage(
|
||||||
{
|
{
|
||||||
number: remoteJid.includes('@lid') ? remoteJid : remoteJid.split('@')[0],
|
number: remoteJid.split('@')[0],
|
||||||
delay: settings?.delayMessage || 1000,
|
delay: settings?.delayMessage || 1000,
|
||||||
text: message,
|
text: message,
|
||||||
linkPreview,
|
linkPreview,
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import { InstanceDto } from '@api/dto/instance.dto';
|
import { InstanceDto } from '@api/dto/instance.dto';
|
||||||
import { ChatwootDto } from '@api/integrations/chatbot/chatwoot/dto/chatwoot.dto';
|
import { ChatwootDto } from '@api/integrations/chatbot/chatwoot/dto/chatwoot.dto';
|
||||||
import { ChatwootService } from '@api/integrations/chatbot/chatwoot/services/chatwoot.service';
|
import { ChatwootService } from '@api/integrations/chatbot/chatwoot/services/chatwoot.service';
|
||||||
|
import { PrismaRepository } from '@api/repository/repository.service';
|
||||||
|
import { waMonitor } from '@api/server.module';
|
||||||
|
import { CacheService } from '@api/services/cache.service';
|
||||||
|
import { CacheEngine } from '@cache/cacheengine';
|
||||||
import { Chatwoot, ConfigService, HttpServer } from '@config/env.config';
|
import { Chatwoot, ConfigService, HttpServer } from '@config/env.config';
|
||||||
import { BadRequestException } from '@exceptions';
|
import { BadRequestException } from '@exceptions';
|
||||||
import { isURL } from 'class-validator';
|
import { isURL } from 'class-validator';
|
||||||
@ -9,6 +13,7 @@ export class ChatwootController {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly chatwootService: ChatwootService,
|
private readonly chatwootService: ChatwootService,
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
|
private readonly prismaRepository: PrismaRepository,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async createChatwoot(instance: InstanceDto, data: ChatwootDto) {
|
public async createChatwoot(instance: InstanceDto, data: ChatwootDto) {
|
||||||
@ -79,6 +84,9 @@ export class ChatwootController {
|
|||||||
public async receiveWebhook(instance: InstanceDto, data: any) {
|
public async receiveWebhook(instance: InstanceDto, data: any) {
|
||||||
if (!this.configService.get<Chatwoot>('CHATWOOT').ENABLED) throw new BadRequestException('Chatwoot is disabled');
|
if (!this.configService.get<Chatwoot>('CHATWOOT').ENABLED) throw new BadRequestException('Chatwoot is disabled');
|
||||||
|
|
||||||
return this.chatwootService.receiveWebhook(instance, data);
|
const chatwootCache = new CacheService(new CacheEngine(this.configService, ChatwootService.name).getEngine());
|
||||||
|
const chatwootService = new ChatwootService(waMonitor, this.configService, this.prismaRepository, chatwootCache);
|
||||||
|
|
||||||
|
return chatwootService.receiveWebhook(instance, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -137,7 +137,7 @@ class ChatwootImport {
|
|||||||
DO UPDATE SET
|
DO UPDATE SET
|
||||||
name = EXCLUDED.name,
|
name = EXCLUDED.name,
|
||||||
phone_number = EXCLUDED.phone_number,
|
phone_number = EXCLUDED.phone_number,
|
||||||
updated_at = NOW()`;
|
identifier = EXCLUDED.identifier`;
|
||||||
|
|
||||||
totalContactsImported += (await pgClient.query(sqlInsert, bindInsert))?.rowCount ?? 0;
|
totalContactsImported += (await pgClient.query(sqlInsert, bindInsert))?.rowCount ?? 0;
|
||||||
|
|
||||||
|
|||||||
@ -51,7 +51,6 @@ export class N8nService extends BaseChatbotService<N8n, N8nSetting> {
|
|||||||
pushName: pushName,
|
pushName: pushName,
|
||||||
keyId: msg?.key?.id,
|
keyId: msg?.key?.id,
|
||||||
fromMe: msg?.key?.fromMe,
|
fromMe: msg?.key?.fromMe,
|
||||||
quotedMessage: msg?.contextInfo?.quotedMessage,
|
|
||||||
instanceName: instance.instanceName,
|
instanceName: instance.instanceName,
|
||||||
serverUrl: this.configService.get<HttpServer>('SERVER').URL,
|
serverUrl: this.configService.get<HttpServer>('SERVER').URL,
|
||||||
apiKey: instance.token,
|
apiKey: instance.token,
|
||||||
|
|||||||
@ -327,7 +327,7 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
|
|||||||
if (message.type === 'image') {
|
if (message.type === 'image') {
|
||||||
await instance.mediaMessage(
|
await instance.mediaMessage(
|
||||||
{
|
{
|
||||||
number: session.remoteJid,
|
number: session.remoteJid.split('@')[0],
|
||||||
delay: settings?.delayMessage || 1000,
|
delay: settings?.delayMessage || 1000,
|
||||||
mediatype: 'image',
|
mediatype: 'image',
|
||||||
media: message.content.url,
|
media: message.content.url,
|
||||||
@ -342,7 +342,7 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
|
|||||||
if (message.type === 'video') {
|
if (message.type === 'video') {
|
||||||
await instance.mediaMessage(
|
await instance.mediaMessage(
|
||||||
{
|
{
|
||||||
number: session.remoteJid,
|
number: session.remoteJid.split('@')[0],
|
||||||
delay: settings?.delayMessage || 1000,
|
delay: settings?.delayMessage || 1000,
|
||||||
mediatype: 'video',
|
mediatype: 'video',
|
||||||
media: message.content.url,
|
media: message.content.url,
|
||||||
@ -357,7 +357,7 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
|
|||||||
if (message.type === 'audio') {
|
if (message.type === 'audio') {
|
||||||
await instance.audioWhatsapp(
|
await instance.audioWhatsapp(
|
||||||
{
|
{
|
||||||
number: session.remoteJid,
|
number: session.remoteJid.split('@')[0],
|
||||||
delay: settings?.delayMessage || 1000,
|
delay: settings?.delayMessage || 1000,
|
||||||
encoding: true,
|
encoding: true,
|
||||||
audio: message.content.url,
|
audio: message.content.url,
|
||||||
@ -441,7 +441,7 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
|
|||||||
*/
|
*/
|
||||||
private async processListMessage(instance: any, formattedText: string, remoteJid: string) {
|
private async processListMessage(instance: any, formattedText: string, remoteJid: string) {
|
||||||
const listJson = {
|
const listJson = {
|
||||||
number: remoteJid,
|
number: remoteJid.split('@')[0],
|
||||||
title: '',
|
title: '',
|
||||||
description: '',
|
description: '',
|
||||||
buttonText: '',
|
buttonText: '',
|
||||||
@ -490,7 +490,7 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
|
|||||||
*/
|
*/
|
||||||
private async processButtonMessage(instance: any, formattedText: string, remoteJid: string) {
|
private async processButtonMessage(instance: any, formattedText: string, remoteJid: string) {
|
||||||
const buttonJson = {
|
const buttonJson = {
|
||||||
number: remoteJid,
|
number: remoteJid.split('@')[0],
|
||||||
thumbnailUrl: undefined,
|
thumbnailUrl: undefined,
|
||||||
title: '',
|
title: '',
|
||||||
description: '',
|
description: '',
|
||||||
|
|||||||
@ -14,24 +14,12 @@ export type EmitData = {
|
|||||||
apiKey?: string;
|
apiKey?: string;
|
||||||
local?: boolean;
|
local?: boolean;
|
||||||
integration?: string[];
|
integration?: string[];
|
||||||
extra?: Record<string, any>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface EventControllerInterface {
|
export interface EventControllerInterface {
|
||||||
set(instanceName: string, data: any): Promise<any>;
|
set(instanceName: string, data: any): Promise<any>;
|
||||||
get(instanceName: string): Promise<any>;
|
get(instanceName: string): Promise<any>;
|
||||||
emit({
|
emit({ instanceName, origin, event, data, serverUrl, dateTime, sender, apiKey, local }: EmitData): Promise<void>;
|
||||||
instanceName,
|
|
||||||
origin,
|
|
||||||
event,
|
|
||||||
data,
|
|
||||||
serverUrl,
|
|
||||||
dateTime,
|
|
||||||
sender,
|
|
||||||
apiKey,
|
|
||||||
local,
|
|
||||||
extra,
|
|
||||||
}: EmitData): Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EventController {
|
export class EventController {
|
||||||
|
|||||||
@ -123,7 +123,6 @@ export class EventManager {
|
|||||||
apiKey?: string;
|
apiKey?: string;
|
||||||
local?: boolean;
|
local?: boolean;
|
||||||
integration?: string[];
|
integration?: string[];
|
||||||
extra?: Record<string, any>;
|
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
await this.websocket.emit(eventData);
|
await this.websocket.emit(eventData);
|
||||||
await this.rabbitmq.emit(eventData);
|
await this.rabbitmq.emit(eventData);
|
||||||
|
|||||||
@ -262,7 +262,6 @@ export class KafkaController extends EventController implements EventControllerI
|
|||||||
sender,
|
sender,
|
||||||
apiKey,
|
apiKey,
|
||||||
integration,
|
integration,
|
||||||
extra,
|
|
||||||
}: EmitData): Promise<void> {
|
}: EmitData): Promise<void> {
|
||||||
if (integration && !integration.includes('kafka')) {
|
if (integration && !integration.includes('kafka')) {
|
||||||
return;
|
return;
|
||||||
@ -285,7 +284,6 @@ export class KafkaController extends EventController implements EventControllerI
|
|||||||
const logEnabled = configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS');
|
const logEnabled = configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS');
|
||||||
|
|
||||||
const message = {
|
const message = {
|
||||||
...(extra ?? {}),
|
|
||||||
event,
|
event,
|
||||||
instance: instanceName,
|
instance: instanceName,
|
||||||
data,
|
data,
|
||||||
|
|||||||
@ -47,7 +47,6 @@ export class NatsController extends EventController implements EventControllerIn
|
|||||||
sender,
|
sender,
|
||||||
apiKey,
|
apiKey,
|
||||||
integration,
|
integration,
|
||||||
extra,
|
|
||||||
}: EmitData): Promise<void> {
|
}: EmitData): Promise<void> {
|
||||||
if (integration && !integration.includes('nats')) {
|
if (integration && !integration.includes('nats')) {
|
||||||
return;
|
return;
|
||||||
@ -66,7 +65,6 @@ export class NatsController extends EventController implements EventControllerIn
|
|||||||
const logEnabled = configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS');
|
const logEnabled = configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS');
|
||||||
|
|
||||||
const message = {
|
const message = {
|
||||||
...(extra ?? {}),
|
|
||||||
event,
|
event,
|
||||||
instance: instanceName,
|
instance: instanceName,
|
||||||
data,
|
data,
|
||||||
|
|||||||
@ -121,7 +121,6 @@ export class PusherController extends EventController implements EventController
|
|||||||
apiKey,
|
apiKey,
|
||||||
local,
|
local,
|
||||||
integration,
|
integration,
|
||||||
extra,
|
|
||||||
}: EmitData): Promise<void> {
|
}: EmitData): Promise<void> {
|
||||||
if (integration && !integration.includes('pusher')) {
|
if (integration && !integration.includes('pusher')) {
|
||||||
return;
|
return;
|
||||||
@ -134,7 +133,6 @@ export class PusherController extends EventController implements EventController
|
|||||||
const enabledLog = configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS');
|
const enabledLog = configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS');
|
||||||
const eventName = event.replace(/_/g, '.').toLowerCase();
|
const eventName = event.replace(/_/g, '.').toLowerCase();
|
||||||
const pusherData = {
|
const pusherData = {
|
||||||
...(extra ?? {}),
|
|
||||||
event,
|
event,
|
||||||
instance: instanceName,
|
instance: instanceName,
|
||||||
data,
|
data,
|
||||||
|
|||||||
@ -209,7 +209,6 @@ export class RabbitmqController extends EventController implements EventControll
|
|||||||
sender,
|
sender,
|
||||||
apiKey,
|
apiKey,
|
||||||
integration,
|
integration,
|
||||||
extra,
|
|
||||||
}: EmitData): Promise<void> {
|
}: EmitData): Promise<void> {
|
||||||
if (integration && !integration.includes('rabbitmq')) {
|
if (integration && !integration.includes('rabbitmq')) {
|
||||||
return;
|
return;
|
||||||
@ -234,7 +233,6 @@ export class RabbitmqController extends EventController implements EventControll
|
|||||||
const logEnabled = configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS');
|
const logEnabled = configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS');
|
||||||
|
|
||||||
const message = {
|
const message = {
|
||||||
...(extra ?? {}),
|
|
||||||
event,
|
event,
|
||||||
instance: instanceName,
|
instance: instanceName,
|
||||||
data,
|
data,
|
||||||
|
|||||||
@ -93,7 +93,6 @@ export class SqsController extends EventController implements EventControllerInt
|
|||||||
sender,
|
sender,
|
||||||
apiKey,
|
apiKey,
|
||||||
integration,
|
integration,
|
||||||
extra,
|
|
||||||
}: EmitData): Promise<void> {
|
}: EmitData): Promise<void> {
|
||||||
if (integration && !integration.includes('sqs')) {
|
if (integration && !integration.includes('sqs')) {
|
||||||
return;
|
return;
|
||||||
@ -129,7 +128,6 @@ export class SqsController extends EventController implements EventControllerInt
|
|||||||
const sqsUrl = `https://sqs.${sqsConfig.REGION}.amazonaws.com/${sqsConfig.ACCOUNT_ID}/${queueName}`;
|
const sqsUrl = `https://sqs.${sqsConfig.REGION}.amazonaws.com/${sqsConfig.ACCOUNT_ID}/${queueName}`;
|
||||||
|
|
||||||
const message = {
|
const message = {
|
||||||
...(extra ?? {}),
|
|
||||||
event,
|
event,
|
||||||
instance: instanceName,
|
instance: instanceName,
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
|
|||||||
@ -65,7 +65,6 @@ export class WebhookController extends EventController implements EventControlle
|
|||||||
apiKey,
|
apiKey,
|
||||||
local,
|
local,
|
||||||
integration,
|
integration,
|
||||||
extra,
|
|
||||||
}: EmitData): Promise<void> {
|
}: EmitData): Promise<void> {
|
||||||
if (integration && !integration.includes('webhook')) {
|
if (integration && !integration.includes('webhook')) {
|
||||||
return;
|
return;
|
||||||
@ -91,7 +90,6 @@ export class WebhookController extends EventController implements EventControlle
|
|||||||
const regex = /^(https?:\/\/)/;
|
const regex = /^(https?:\/\/)/;
|
||||||
|
|
||||||
const webhookData = {
|
const webhookData = {
|
||||||
...(extra ?? {}),
|
|
||||||
event,
|
event,
|
||||||
instance: instanceName,
|
instance: instanceName,
|
||||||
data,
|
data,
|
||||||
|
|||||||
@ -33,13 +33,10 @@ export class WebsocketController extends EventController implements EventControl
|
|||||||
const { remoteAddress } = req.socket;
|
const { remoteAddress } = req.socket;
|
||||||
const websocketConfig = configService.get<Websocket>('WEBSOCKET');
|
const websocketConfig = configService.get<Websocket>('WEBSOCKET');
|
||||||
const allowedHosts = websocketConfig.ALLOWED_HOSTS || '127.0.0.1,::1,::ffff:127.0.0.1';
|
const allowedHosts = websocketConfig.ALLOWED_HOSTS || '127.0.0.1,::1,::ffff:127.0.0.1';
|
||||||
const allowAllHosts = allowedHosts.trim() === '*';
|
const isAllowedHost = allowedHosts
|
||||||
const isAllowedHost =
|
.split(',')
|
||||||
allowAllHosts ||
|
.map((h) => h.trim())
|
||||||
allowedHosts
|
.includes(remoteAddress);
|
||||||
.split(',')
|
|
||||||
.map((h) => h.trim())
|
|
||||||
.includes(remoteAddress);
|
|
||||||
|
|
||||||
if (params.has('EIO') && isAllowedHost) {
|
if (params.has('EIO') && isAllowedHost) {
|
||||||
return callback(null, true);
|
return callback(null, true);
|
||||||
@ -118,7 +115,6 @@ export class WebsocketController extends EventController implements EventControl
|
|||||||
sender,
|
sender,
|
||||||
apiKey,
|
apiKey,
|
||||||
integration,
|
integration,
|
||||||
extra,
|
|
||||||
}: EmitData): Promise<void> {
|
}: EmitData): Promise<void> {
|
||||||
if (integration && !integration.includes('websocket')) {
|
if (integration && !integration.includes('websocket')) {
|
||||||
return;
|
return;
|
||||||
@ -131,7 +127,6 @@ export class WebsocketController extends EventController implements EventControl
|
|||||||
const configEv = event.replace(/[.-]/gm, '_').toUpperCase();
|
const configEv = event.replace(/[.-]/gm, '_').toUpperCase();
|
||||||
const logEnabled = configService.get<Log>('LOG').LEVEL.includes('WEBSOCKET');
|
const logEnabled = configService.get<Log>('LOG').LEVEL.includes('WEBSOCKET');
|
||||||
const message = {
|
const message = {
|
||||||
...(extra ?? {}),
|
|
||||||
event,
|
event,
|
||||||
instance: instanceName,
|
instance: instanceName,
|
||||||
data,
|
data,
|
||||||
|
|||||||
@ -48,14 +48,9 @@ const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
|||||||
const metricsIPWhitelist = (req: Request, res: Response, next: NextFunction) => {
|
const metricsIPWhitelist = (req: Request, res: Response, next: NextFunction) => {
|
||||||
const metricsConfig = configService.get('METRICS');
|
const metricsConfig = configService.get('METRICS');
|
||||||
const allowedIPs = metricsConfig.ALLOWED_IPS?.split(',').map((ip) => ip.trim()) || ['127.0.0.1'];
|
const allowedIPs = metricsConfig.ALLOWED_IPS?.split(',').map((ip) => ip.trim()) || ['127.0.0.1'];
|
||||||
const clientIPs = [
|
const clientIP = req.ip || req.connection.remoteAddress || req.socket.remoteAddress;
|
||||||
req.ip,
|
|
||||||
req.connection.remoteAddress,
|
|
||||||
req.socket.remoteAddress,
|
|
||||||
req.headers['x-forwarded-for'],
|
|
||||||
].filter((ip) => ip !== undefined);
|
|
||||||
|
|
||||||
if (allowedIPs.filter((ip) => clientIPs.includes(ip)) === 0) {
|
if (!allowedIPs.includes(clientIP)) {
|
||||||
return res.status(403).send('Forbidden: IP not allowed');
|
return res.status(403).send('Forbidden: IP not allowed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
import { RouterBroker } from '@api/abstract/abstract.router';
|
import { RouterBroker } from '@api/abstract/abstract.router';
|
||||||
import { InstanceDto } from '@api/dto/instance.dto';
|
import { InstanceDto } from '@api/dto/instance.dto';
|
||||||
import { TemplateDeleteDto, TemplateDto, TemplateEditDto } from '@api/dto/template.dto';
|
import { TemplateDto } from '@api/dto/template.dto';
|
||||||
import { templateController } from '@api/server.module';
|
import { templateController } from '@api/server.module';
|
||||||
import { ConfigService } from '@config/env.config';
|
import { ConfigService } from '@config/env.config';
|
||||||
import { createMetaErrorResponse } from '@utils/errorResponse';
|
import { createMetaErrorResponse } from '@utils/errorResponse';
|
||||||
import { templateDeleteSchema } from '@validate/templateDelete.schema';
|
|
||||||
import { templateEditSchema } from '@validate/templateEdit.schema';
|
|
||||||
import { instanceSchema, templateSchema } from '@validate/validate.schema';
|
import { instanceSchema, templateSchema } from '@validate/validate.schema';
|
||||||
import { RequestHandler, Router } from 'express';
|
import { RequestHandler, Router } from 'express';
|
||||||
|
|
||||||
@ -37,38 +35,6 @@ export class TemplateRouter extends RouterBroker {
|
|||||||
res.status(errorResponse.status).json(errorResponse);
|
res.status(errorResponse.status).json(errorResponse);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.post(this.routerPath('edit'), ...guards, async (req, res) => {
|
|
||||||
try {
|
|
||||||
const response = await this.dataValidate<TemplateEditDto>({
|
|
||||||
request: req,
|
|
||||||
schema: templateEditSchema,
|
|
||||||
ClassRef: TemplateEditDto,
|
|
||||||
execute: (instance, data) => templateController.editTemplate(instance, data),
|
|
||||||
});
|
|
||||||
|
|
||||||
res.status(HttpStatus.OK).json(response);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Template edit error:', error);
|
|
||||||
const errorResponse = createMetaErrorResponse(error, 'template_edit');
|
|
||||||
res.status(errorResponse.status).json(errorResponse);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.delete(this.routerPath('delete'), ...guards, async (req, res) => {
|
|
||||||
try {
|
|
||||||
const response = await this.dataValidate<TemplateDeleteDto>({
|
|
||||||
request: req,
|
|
||||||
schema: templateDeleteSchema,
|
|
||||||
ClassRef: TemplateDeleteDto,
|
|
||||||
execute: (instance, data) => templateController.deleteTemplate(instance, data),
|
|
||||||
});
|
|
||||||
|
|
||||||
res.status(HttpStatus.OK).json(response);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Template delete error:', error);
|
|
||||||
const errorResponse = createMetaErrorResponse(error, 'template_delete');
|
|
||||||
res.status(errorResponse.status).json(errorResponse);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.get(this.routerPath('find'), ...guards, async (req, res) => {
|
.get(this.routerPath('find'), ...guards, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const response = await this.dataValidate<InstanceDto>({
|
const response = await this.dataValidate<InstanceDto>({
|
||||||
|
|||||||
@ -82,7 +82,7 @@ const proxyService = new ProxyService(waMonitor);
|
|||||||
export const proxyController = new ProxyController(proxyService, waMonitor);
|
export const proxyController = new ProxyController(proxyService, waMonitor);
|
||||||
|
|
||||||
const chatwootService = new ChatwootService(waMonitor, configService, prismaRepository, chatwootCache);
|
const chatwootService = new ChatwootService(waMonitor, configService, prismaRepository, chatwootCache);
|
||||||
export const chatwootController = new ChatwootController(chatwootService, configService);
|
export const chatwootController = new ChatwootController(chatwootService, configService, prismaRepository);
|
||||||
|
|
||||||
const settingsService = new SettingsService(waMonitor);
|
const settingsService = new SettingsService(waMonitor);
|
||||||
export const settingsController = new SettingsController(settingsService);
|
export const settingsController = new SettingsController(settingsService);
|
||||||
|
|||||||
@ -60,7 +60,6 @@ export class ChannelStartupService {
|
|||||||
this.instance.number = instance.number;
|
this.instance.number = instance.number;
|
||||||
this.instance.token = instance.token;
|
this.instance.token = instance.token;
|
||||||
this.instance.businessId = instance.businessId;
|
this.instance.businessId = instance.businessId;
|
||||||
this.instance.ownerJid = instance.ownerJid;
|
|
||||||
|
|
||||||
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {
|
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {
|
||||||
this.chatwootService.eventWhatsapp(
|
this.chatwootService.eventWhatsapp(
|
||||||
@ -432,13 +431,7 @@ export class ChannelStartupService {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async sendDataWebhook<T extends object = any>(
|
public async sendDataWebhook<T extends object = any>(event: Events, data: T, local = true, integration?: string[]) {
|
||||||
event: Events,
|
|
||||||
data: T,
|
|
||||||
local = true,
|
|
||||||
integration?: string[],
|
|
||||||
extra?: Record<string, any>,
|
|
||||||
) {
|
|
||||||
const serverUrl = this.configService.get<HttpServer>('SERVER').URL;
|
const serverUrl = this.configService.get<HttpServer>('SERVER').URL;
|
||||||
const tzoffset = new Date().getTimezoneOffset() * 60000; //offset in milliseconds
|
const tzoffset = new Date().getTimezoneOffset() * 60000; //offset in milliseconds
|
||||||
const localISOTime = new Date(Date.now() - tzoffset).toISOString();
|
const localISOTime = new Date(Date.now() - tzoffset).toISOString();
|
||||||
@ -459,7 +452,6 @@ export class ChannelStartupService {
|
|||||||
apiKey: expose && instanceApikey ? instanceApikey : null,
|
apiKey: expose && instanceApikey ? instanceApikey : null,
|
||||||
local,
|
local,
|
||||||
integration,
|
integration,
|
||||||
extra,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -498,23 +490,20 @@ export class ChannelStartupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async fetchContacts(query: Query<Contact>) {
|
public async fetchContacts(query: Query<Contact>) {
|
||||||
const where: any = {
|
const remoteJid = query?.where?.remoteJid
|
||||||
|
? query?.where?.remoteJid.includes('@')
|
||||||
|
? query.where?.remoteJid
|
||||||
|
: createJid(query.where?.remoteJid)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const where = {
|
||||||
instanceId: this.instanceId,
|
instanceId: this.instanceId,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (query?.where?.remoteJid) {
|
if (remoteJid) {
|
||||||
const remoteJid = query.where.remoteJid.includes('@') ? query.where.remoteJid : createJid(query.where.remoteJid);
|
|
||||||
where['remoteJid'] = remoteJid;
|
where['remoteJid'] = remoteJid;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query?.where?.id) {
|
|
||||||
where['id'] = query.where.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query?.where?.pushName) {
|
|
||||||
where['pushName'] = query.where.pushName;
|
|
||||||
}
|
|
||||||
|
|
||||||
const contactFindManyArgs: Prisma.ContactFindManyArgs = {
|
const contactFindManyArgs: Prisma.ContactFindManyArgs = {
|
||||||
where,
|
where,
|
||||||
};
|
};
|
||||||
@ -543,12 +532,14 @@ export class ChannelStartupService {
|
|||||||
|
|
||||||
public cleanMessageData(message: any) {
|
public cleanMessageData(message: any) {
|
||||||
if (!message) return message;
|
if (!message) return message;
|
||||||
|
|
||||||
const cleanedMessage = { ...message };
|
const cleanedMessage = { ...message };
|
||||||
|
|
||||||
if (cleanedMessage.message) {
|
const mediaUrl = cleanedMessage.message.mediaUrl;
|
||||||
const { mediaUrl } = cleanedMessage.message;
|
|
||||||
delete cleanedMessage.message.base64;
|
|
||||||
|
|
||||||
|
delete cleanedMessage.message.base64;
|
||||||
|
|
||||||
|
if (cleanedMessage.message) {
|
||||||
// Limpa imageMessage
|
// Limpa imageMessage
|
||||||
if (cleanedMessage.message.imageMessage) {
|
if (cleanedMessage.message.imageMessage) {
|
||||||
cleanedMessage.message.imageMessage = {
|
cleanedMessage.message.imageMessage = {
|
||||||
@ -590,10 +581,10 @@ export class ChannelStartupService {
|
|||||||
name: cleanedMessage.message.documentWithCaptionMessage.name,
|
name: cleanedMessage.message.documentWithCaptionMessage.name,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mediaUrl) cleanedMessage.message.mediaUrl = mediaUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mediaUrl) cleanedMessage.message.mediaUrl = mediaUrl;
|
||||||
|
|
||||||
return cleanedMessage;
|
return cleanedMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -38,37 +38,25 @@ export class WAMonitoringService {
|
|||||||
|
|
||||||
private readonly logger = new Logger('WAMonitoringService');
|
private readonly logger = new Logger('WAMonitoringService');
|
||||||
public readonly waInstances: Record<string, any> = {};
|
public readonly waInstances: Record<string, any> = {};
|
||||||
private readonly delInstanceTimeouts: Record<string, NodeJS.Timeout> = {};
|
|
||||||
|
|
||||||
private readonly providerSession: ProviderSession;
|
private readonly providerSession: ProviderSession;
|
||||||
|
|
||||||
public delInstanceTime(instance: string) {
|
public delInstanceTime(instance: string) {
|
||||||
const time = this.configService.get<DelInstance>('DEL_INSTANCE');
|
const time = this.configService.get<DelInstance>('DEL_INSTANCE');
|
||||||
if (typeof time === 'number' && time > 0) {
|
if (typeof time === 'number' && time > 0) {
|
||||||
// Clear previous timeout if exists
|
setTimeout(
|
||||||
if (this.delInstanceTimeouts[instance]) {
|
|
||||||
clearTimeout(this.delInstanceTimeouts[instance]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set new timeout and store reference
|
|
||||||
this.delInstanceTimeouts[instance] = setTimeout(
|
|
||||||
async () => {
|
async () => {
|
||||||
try {
|
if (this.waInstances[instance]?.connectionStatus?.state !== 'open') {
|
||||||
if (this.waInstances[instance]?.connectionStatus?.state !== 'open') {
|
if (this.waInstances[instance]?.connectionStatus?.state === 'connecting') {
|
||||||
if (this.waInstances[instance]?.connectionStatus?.state === 'connecting') {
|
if ((await this.waInstances[instance].integration) === Integration.WHATSAPP_BAILEYS) {
|
||||||
if ((await this.waInstances[instance].integration) === Integration.WHATSAPP_BAILEYS) {
|
await this.waInstances[instance]?.client?.logout('Log out instance: ' + instance);
|
||||||
await this.waInstances[instance]?.client?.logout('Log out instance: ' + instance);
|
this.waInstances[instance]?.client?.ws?.close();
|
||||||
this.waInstances[instance]?.client?.ws?.close();
|
this.waInstances[instance]?.client?.end(undefined);
|
||||||
this.waInstances[instance]?.client?.end(undefined);
|
|
||||||
}
|
|
||||||
this.eventEmitter.emit('remove.instance', instance, 'inner');
|
|
||||||
} else {
|
|
||||||
this.eventEmitter.emit('remove.instance', instance, 'inner');
|
|
||||||
}
|
}
|
||||||
|
this.eventEmitter.emit('remove.instance', instance, 'inner');
|
||||||
|
} else {
|
||||||
|
this.eventEmitter.emit('remove.instance', instance, 'inner');
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
// Clean up timeout reference
|
|
||||||
delete this.delInstanceTimeouts[instance];
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
1000 * 60 * time,
|
1000 * 60 * time,
|
||||||
@ -76,13 +64,6 @@ export class WAMonitoringService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public clearDelInstanceTime(instance: string) {
|
|
||||||
if (this.delInstanceTimeouts[instance]) {
|
|
||||||
clearTimeout(this.delInstanceTimeouts[instance]);
|
|
||||||
delete this.delInstanceTimeouts[instance];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async instanceInfo(instanceNames?: string[]): Promise<any> {
|
public async instanceInfo(instanceNames?: string[]): Promise<any> {
|
||||||
if (instanceNames && instanceNames.length > 0) {
|
if (instanceNames && instanceNames.length > 0) {
|
||||||
const inexistentInstances = instanceNames ? instanceNames.filter((instance) => !this.waInstances[instance]) : [];
|
const inexistentInstances = instanceNames ? instanceNames.filter((instance) => !this.waInstances[instance]) : [];
|
||||||
@ -290,19 +271,9 @@ export class WAMonitoringService {
|
|||||||
token: instanceData.token,
|
token: instanceData.token,
|
||||||
number: instanceData.number,
|
number: instanceData.number,
|
||||||
businessId: instanceData.businessId,
|
businessId: instanceData.businessId,
|
||||||
ownerJid: instanceData.ownerJid,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (instanceData.connectionStatus === 'open' || instanceData.connectionStatus === 'connecting') {
|
await instance.connectToWhatsapp();
|
||||||
this.logger.info(
|
|
||||||
`Auto-connecting instance "${instanceData.instanceName}" (status: ${instanceData.connectionStatus})`,
|
|
||||||
);
|
|
||||||
await instance.connectToWhatsapp();
|
|
||||||
} else {
|
|
||||||
this.logger.info(
|
|
||||||
`Skipping auto-connect for instance "${instanceData.instanceName}" (status: ${instanceData.connectionStatus || 'close'})`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.waInstances[instanceData.instanceName] = instance;
|
this.waInstances[instanceData.instanceName] = instance;
|
||||||
}
|
}
|
||||||
@ -328,7 +299,6 @@ export class WAMonitoringService {
|
|||||||
token: instanceData.token,
|
token: instanceData.token,
|
||||||
number: instanceData.number,
|
number: instanceData.number,
|
||||||
businessId: instanceData.businessId,
|
businessId: instanceData.businessId,
|
||||||
connectionStatus: instanceData.connectionStatus as any, // Pass connection status
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setInstance(instance);
|
this.setInstance(instance);
|
||||||
@ -357,8 +327,6 @@ export class WAMonitoringService {
|
|||||||
token: instance.token,
|
token: instance.token,
|
||||||
number: instance.number,
|
number: instance.number,
|
||||||
businessId: instance.businessId,
|
businessId: instance.businessId,
|
||||||
ownerJid: instance.ownerJid,
|
|
||||||
connectionStatus: instance.connectionStatus as any, // Pass connection status
|
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -383,7 +351,6 @@ export class WAMonitoringService {
|
|||||||
integration: instance.integration,
|
integration: instance.integration,
|
||||||
token: instance.token,
|
token: instance.token,
|
||||||
businessId: instance.businessId,
|
businessId: instance.businessId,
|
||||||
connectionStatus: instance.connectionStatus as any, // Pass connection status
|
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -394,8 +361,6 @@ export class WAMonitoringService {
|
|||||||
try {
|
try {
|
||||||
await this.waInstances[instanceName]?.sendDataWebhook(Events.REMOVE_INSTANCE, null);
|
await this.waInstances[instanceName]?.sendDataWebhook(Events.REMOVE_INSTANCE, null);
|
||||||
|
|
||||||
this.clearDelInstanceTime(instanceName);
|
|
||||||
|
|
||||||
this.cleaningUp(instanceName);
|
this.cleaningUp(instanceName);
|
||||||
this.cleaningStoreData(instanceName);
|
this.cleaningStoreData(instanceName);
|
||||||
} finally {
|
} finally {
|
||||||
@ -412,8 +377,6 @@ export class WAMonitoringService {
|
|||||||
try {
|
try {
|
||||||
await this.waInstances[instanceName]?.sendDataWebhook(Events.LOGOUT_INSTANCE, null);
|
await this.waInstances[instanceName]?.sendDataWebhook(Events.LOGOUT_INSTANCE, null);
|
||||||
|
|
||||||
this.clearDelInstanceTime(instanceName);
|
|
||||||
|
|
||||||
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED) {
|
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED) {
|
||||||
this.waInstances[instanceName]?.clearCacheChatwoot();
|
this.waInstances[instanceName]?.clearCacheChatwoot();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -88,77 +88,6 @@ export class TemplateService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async edit(
|
|
||||||
instance: InstanceDto,
|
|
||||||
data: { templateId: string; category?: string; components?: any; allowCategoryChange?: boolean; ttl?: number },
|
|
||||||
) {
|
|
||||||
const getInstance = await this.waMonitor.waInstances[instance.instanceName].instance;
|
|
||||||
if (!getInstance) {
|
|
||||||
throw new Error('Instance not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.businessId = getInstance.businessId;
|
|
||||||
this.token = getInstance.token;
|
|
||||||
|
|
||||||
const payload: Record<string, unknown> = {};
|
|
||||||
if (typeof data.category === 'string') payload.category = data.category;
|
|
||||||
if (typeof data.allowCategoryChange === 'boolean') payload.allow_category_change = data.allowCategoryChange;
|
|
||||||
if (typeof data.ttl === 'number') payload.time_to_live = data.ttl;
|
|
||||||
if (data.components) payload.components = data.components;
|
|
||||||
|
|
||||||
const response = await this.requestEditTemplate(data.templateId, payload);
|
|
||||||
|
|
||||||
if (!response || response.error) {
|
|
||||||
if (response && response.error) {
|
|
||||||
const metaError = new Error(response.error.message || 'WhatsApp API Error');
|
|
||||||
(metaError as any).template = response.error;
|
|
||||||
throw metaError;
|
|
||||||
}
|
|
||||||
throw new Error('Error to edit template');
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async delete(instance: InstanceDto, data: { name: string; hsmId?: string }) {
|
|
||||||
const getInstance = await this.waMonitor.waInstances[instance.instanceName].instance;
|
|
||||||
if (!getInstance) {
|
|
||||||
throw new Error('Instance not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.businessId = getInstance.businessId;
|
|
||||||
this.token = getInstance.token;
|
|
||||||
|
|
||||||
const response = await this.requestDeleteTemplate({ name: data.name, hsm_id: data.hsmId });
|
|
||||||
|
|
||||||
if (!response || response.error) {
|
|
||||||
if (response && response.error) {
|
|
||||||
const metaError = new Error(response.error.message || 'WhatsApp API Error');
|
|
||||||
(metaError as any).template = response.error;
|
|
||||||
throw metaError;
|
|
||||||
}
|
|
||||||
throw new Error('Error to delete template');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Best-effort local cleanup of stored template metadata
|
|
||||||
await this.prismaRepository.template.deleteMany({
|
|
||||||
where: {
|
|
||||||
OR: [
|
|
||||||
{ name: data.name, instanceId: getInstance.id },
|
|
||||||
data.hsmId ? { templateId: data.hsmId, instanceId: getInstance.id } : undefined,
|
|
||||||
].filter(Boolean) as any,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
this.logger.warn(
|
|
||||||
`Failed to cleanup local template records after delete: ${(err as Error)?.message || String(err)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async requestTemplate(data: any, method: string) {
|
private async requestTemplate(data: any, method: string) {
|
||||||
try {
|
try {
|
||||||
let urlServer = this.configService.get<WaBusiness>('WA_BUSINESS').URL;
|
let urlServer = this.configService.get<WaBusiness>('WA_BUSINESS').URL;
|
||||||
@ -187,38 +116,4 @@ export class TemplateService {
|
|||||||
throw new Error(`Connection error: ${e.message}`);
|
throw new Error(`Connection error: ${e.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async requestEditTemplate(templateId: string, data: any) {
|
|
||||||
try {
|
|
||||||
let urlServer = this.configService.get<WaBusiness>('WA_BUSINESS').URL;
|
|
||||||
const version = this.configService.get<WaBusiness>('WA_BUSINESS').VERSION;
|
|
||||||
urlServer = `${urlServer}/${version}/${templateId}`;
|
|
||||||
const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${this.token}` };
|
|
||||||
const result = await axios.post(urlServer, data, { headers });
|
|
||||||
return result.data;
|
|
||||||
} catch (e) {
|
|
||||||
this.logger.error(
|
|
||||||
'WhatsApp API request error: ' + (e.response?.data ? JSON.stringify(e.response?.data) : e.message),
|
|
||||||
);
|
|
||||||
if (e.response?.data) return e.response.data;
|
|
||||||
throw new Error(`Connection error: ${e.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async requestDeleteTemplate(params: { name: string; hsm_id?: string }) {
|
|
||||||
try {
|
|
||||||
let urlServer = this.configService.get<WaBusiness>('WA_BUSINESS').URL;
|
|
||||||
const version = this.configService.get<WaBusiness>('WA_BUSINESS').VERSION;
|
|
||||||
urlServer = `${urlServer}/${version}/${this.businessId}/message_templates`;
|
|
||||||
const headers = { Authorization: `Bearer ${this.token}` };
|
|
||||||
const result = await axios.delete(urlServer, { headers, params });
|
|
||||||
return result.data;
|
|
||||||
} catch (e) {
|
|
||||||
this.logger.error(
|
|
||||||
'WhatsApp API request error: ' + (e.response?.data ? JSON.stringify(e.response?.data) : e.message),
|
|
||||||
);
|
|
||||||
if (e.response?.data) return e.response.data;
|
|
||||||
throw new Error(`Connection error: ${e.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,7 +52,6 @@ export declare namespace wa {
|
|||||||
pairingCode?: string;
|
pairingCode?: string;
|
||||||
authState?: { state: AuthenticationState; saveCreds: () => void };
|
authState?: { state: AuthenticationState; saveCreds: () => void };
|
||||||
name?: string;
|
name?: string;
|
||||||
ownerJid?: string;
|
|
||||||
wuid?: string;
|
wuid?: string;
|
||||||
profileName?: string;
|
profileName?: string;
|
||||||
profilePictureUrl?: string;
|
profilePictureUrl?: string;
|
||||||
|
|||||||
@ -26,8 +26,8 @@ import cors from 'cors';
|
|||||||
import express, { json, NextFunction, Request, Response, urlencoded } from 'express';
|
import express, { json, NextFunction, Request, Response, urlencoded } from 'express';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
|
||||||
async function initWA() {
|
function initWA() {
|
||||||
await waMonitor.loadInstance();
|
waMonitor.loadInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
@ -159,9 +159,7 @@ async function bootstrap() {
|
|||||||
|
|
||||||
server.listen(httpServer.PORT, () => logger.log(httpServer.TYPE.toUpperCase() + ' - ON: ' + httpServer.PORT));
|
server.listen(httpServer.PORT, () => logger.log(httpServer.TYPE.toUpperCase() + ' - ON: ' + httpServer.PORT));
|
||||||
|
|
||||||
initWA().catch((error) => {
|
initWA();
|
||||||
logger.error('Error loading instances: ' + error);
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnexpectedError();
|
onUnexpectedError();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import { socksDispatcher } from 'fetch-socks';
|
|
||||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||||
import { SocksProxyAgent } from 'socks-proxy-agent';
|
import { SocksProxyAgent } from 'socks-proxy-agent';
|
||||||
import { ProxyAgent } from 'undici';
|
|
||||||
|
|
||||||
type Proxy = {
|
type Proxy = {
|
||||||
host: string;
|
host: string;
|
||||||
@ -19,23 +17,12 @@ function selectProxyAgent(proxyUrl: string): HttpsProxyAgent<string> | SocksProx
|
|||||||
// the end so, we add the protocol constants without the `:` to avoid confusion.
|
// the end so, we add the protocol constants without the `:` to avoid confusion.
|
||||||
const PROXY_HTTP_PROTOCOL = 'http:';
|
const PROXY_HTTP_PROTOCOL = 'http:';
|
||||||
const PROXY_SOCKS_PROTOCOL = 'socks:';
|
const PROXY_SOCKS_PROTOCOL = 'socks:';
|
||||||
const PROXY_SOCKS5_PROTOCOL = 'socks5:';
|
|
||||||
|
|
||||||
switch (url.protocol) {
|
switch (url.protocol) {
|
||||||
case PROXY_HTTP_PROTOCOL:
|
case PROXY_HTTP_PROTOCOL:
|
||||||
return new HttpsProxyAgent(url);
|
return new HttpsProxyAgent(url);
|
||||||
case PROXY_SOCKS_PROTOCOL:
|
case PROXY_SOCKS_PROTOCOL:
|
||||||
case PROXY_SOCKS5_PROTOCOL: {
|
return new SocksProxyAgent(url);
|
||||||
let urlSocks = '';
|
|
||||||
|
|
||||||
if (url.username && url.password) {
|
|
||||||
urlSocks = `socks://${url.username}:${url.password}@${url.hostname}:${url.port}`;
|
|
||||||
} else {
|
|
||||||
urlSocks = `socks://${url.hostname}:${url.port}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SocksProxyAgent(urlSocks);
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported proxy protocol: ${url.protocol}`);
|
throw new Error(`Unsupported proxy protocol: ${url.protocol}`);
|
||||||
}
|
}
|
||||||
@ -55,57 +42,3 @@ export function makeProxyAgent(proxy: Proxy | string): HttpsProxyAgent<string> |
|
|||||||
|
|
||||||
return selectProxyAgent(proxyUrl);
|
return selectProxyAgent(proxyUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeProxyAgentUndici(proxy: Proxy | string): ProxyAgent {
|
|
||||||
let proxyUrl: string;
|
|
||||||
let protocol: string;
|
|
||||||
|
|
||||||
if (typeof proxy === 'string') {
|
|
||||||
const url = new URL(proxy);
|
|
||||||
protocol = url.protocol.replace(':', '');
|
|
||||||
proxyUrl = proxy;
|
|
||||||
} else {
|
|
||||||
const { host, password, port, protocol: proto, username } = proxy;
|
|
||||||
protocol = (proto || 'http').replace(':', '');
|
|
||||||
|
|
||||||
if (protocol === 'socks') {
|
|
||||||
protocol = 'socks5';
|
|
||||||
}
|
|
||||||
|
|
||||||
const auth = username && password ? `${username}:${password}@` : '';
|
|
||||||
proxyUrl = `${protocol}://${auth}${host}:${port}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol = protocol.toLowerCase();
|
|
||||||
|
|
||||||
const PROXY_HTTP_PROTOCOL = 'http';
|
|
||||||
const PROXY_HTTPS_PROTOCOL = 'https';
|
|
||||||
const PROXY_SOCKS4_PROTOCOL = 'socks4';
|
|
||||||
const PROXY_SOCKS5_PROTOCOL = 'socks5';
|
|
||||||
|
|
||||||
switch (protocol) {
|
|
||||||
case PROXY_HTTP_PROTOCOL:
|
|
||||||
case PROXY_HTTPS_PROTOCOL:
|
|
||||||
return new ProxyAgent(proxyUrl);
|
|
||||||
|
|
||||||
case PROXY_SOCKS4_PROTOCOL:
|
|
||||||
case PROXY_SOCKS5_PROTOCOL: {
|
|
||||||
let type: 4 | 5 = 5;
|
|
||||||
|
|
||||||
if (PROXY_SOCKS4_PROTOCOL === protocol) type = 4;
|
|
||||||
|
|
||||||
const url = new URL(proxyUrl);
|
|
||||||
|
|
||||||
return socksDispatcher({
|
|
||||||
type: type,
|
|
||||||
host: url.hostname,
|
|
||||||
port: Number(url.port),
|
|
||||||
userId: url.username || undefined,
|
|
||||||
password: url.password || undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported proxy protocol: ${protocol}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,10 +1,7 @@
|
|||||||
import { prismaRepository } from '@api/server.module';
|
import { prismaRepository } from '@api/server.module';
|
||||||
import { configService, Database } from '@config/env.config';
|
import { configService, Database } from '@config/env.config';
|
||||||
import { Logger } from '@config/logger.config';
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
const logger = new Logger('OnWhatsappCache');
|
|
||||||
|
|
||||||
function getAvailableNumbers(remoteJid: string) {
|
function getAvailableNumbers(remoteJid: string) {
|
||||||
const numbersAvailable: string[] = [];
|
const numbersAvailable: string[] = [];
|
||||||
|
|
||||||
@ -14,11 +11,6 @@ function getAvailableNumbers(remoteJid: string) {
|
|||||||
|
|
||||||
const [number, domain] = remoteJid.split('@');
|
const [number, domain] = remoteJid.split('@');
|
||||||
|
|
||||||
// TODO: Se já for @lid, retornar apenas ele mesmo SEM adicionar @domain novamente
|
|
||||||
if (domain === 'lid' || domain === 'g.us') {
|
|
||||||
return [remoteJid]; // Retorna direto para @lid e @g.us
|
|
||||||
}
|
|
||||||
|
|
||||||
// Brazilian numbers
|
// Brazilian numbers
|
||||||
if (remoteJid.startsWith('55')) {
|
if (remoteJid.startsWith('55')) {
|
||||||
const numberWithDigit =
|
const numberWithDigit =
|
||||||
@ -55,128 +47,36 @@ function getAvailableNumbers(remoteJid: string) {
|
|||||||
numbersAvailable.push(remoteJid);
|
numbersAvailable.push(remoteJid);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Adiciona @domain apenas para números que não são @lid
|
|
||||||
return numbersAvailable.map((number) => `${number}@${domain}`);
|
return numbersAvailable.map((number) => `${number}@${domain}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ISaveOnWhatsappCacheParams {
|
interface ISaveOnWhatsappCacheParams {
|
||||||
remoteJid: string;
|
remoteJid: string;
|
||||||
remoteJidAlt?: string;
|
lid?: string;
|
||||||
lid?: 'lid' | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeJid(jid: string | null | undefined): string | null {
|
|
||||||
if (!jid) return null;
|
|
||||||
return jid.startsWith('+') ? jid.slice(1) : jid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveOnWhatsappCache(data: ISaveOnWhatsappCacheParams[]) {
|
export async function saveOnWhatsappCache(data: ISaveOnWhatsappCacheParams[]) {
|
||||||
if (!configService.get<Database>('DATABASE').SAVE_DATA.IS_ON_WHATSAPP) {
|
if (configService.get<Database>('DATABASE').SAVE_DATA.IS_ON_WHATSAPP) {
|
||||||
return;
|
const upsertsQuery = data.map((item) => {
|
||||||
}
|
const remoteJid = item.remoteJid.startsWith('+') ? item.remoteJid.slice(1) : item.remoteJid;
|
||||||
|
const numbersAvailable = getAvailableNumbers(remoteJid);
|
||||||
|
|
||||||
// Processa todos os itens em paralelo para melhor performance
|
return prismaRepository.isOnWhatsapp.upsert({
|
||||||
const processingPromises = data.map(async (item) => {
|
create: {
|
||||||
try {
|
remoteJid: remoteJid,
|
||||||
const remoteJid = normalizeJid(item.remoteJid);
|
jidOptions: numbersAvailable.join(','),
|
||||||
if (!remoteJid) {
|
lid: item.lid,
|
||||||
logger.warn('[saveOnWhatsappCache] Item skipped, missing remoteJid.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const altJidNormalized = normalizeJid(item.remoteJidAlt);
|
|
||||||
const lidAltJid = altJidNormalized && altJidNormalized.includes('@lid') ? altJidNormalized : null;
|
|
||||||
|
|
||||||
const baseJids = [remoteJid]; // Garante que o remoteJid esteja na lista inicial
|
|
||||||
if (lidAltJid) {
|
|
||||||
baseJids.push(lidAltJid);
|
|
||||||
}
|
|
||||||
|
|
||||||
const expandedJids = baseJids.flatMap((jid) => getAvailableNumbers(jid));
|
|
||||||
|
|
||||||
// 1. Busca entrada por jidOptions e também remoteJid
|
|
||||||
// Às vezes acontece do remoteJid atual NÃO ESTAR no jidOptions ainda, ocasionando o erro:
|
|
||||||
// 'Unique constraint failed on the fields: (`remoteJid`)'
|
|
||||||
// Isso acontece principalmente em grupos que possuem o número do criador no ID (ex.: '559911223345-1234567890@g.us')
|
|
||||||
const existingRecord = await prismaRepository.isOnWhatsapp.findFirst({
|
|
||||||
where: {
|
|
||||||
OR: [
|
|
||||||
...expandedJids.map((jid) => ({ jidOptions: { contains: jid } })),
|
|
||||||
{ remoteJid: remoteJid }, // TODO: Descobrir o motivo que causa o remoteJid não estar (às vezes) incluso na lista de jidOptions
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
|
update: {
|
||||||
|
jidOptions: numbersAvailable.join(','),
|
||||||
|
lid: item.lid,
|
||||||
|
},
|
||||||
|
where: { remoteJid: remoteJid },
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
logger.verbose(
|
await prismaRepository.$transaction(upsertsQuery);
|
||||||
`[saveOnWhatsappCache] Register exists for [${expandedJids.join(',')}]? => ${existingRecord ? existingRecord.remoteJid : 'Not found'}`,
|
}
|
||||||
);
|
|
||||||
|
|
||||||
// 2. Unifica todos os JIDs usando um Set para garantir valores únicos
|
|
||||||
const finalJidOptions = new Set(expandedJids);
|
|
||||||
|
|
||||||
if (lidAltJid) {
|
|
||||||
finalJidOptions.add(lidAltJid);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingRecord?.jidOptions) {
|
|
||||||
existingRecord.jidOptions.split(',').forEach((jid) => finalJidOptions.add(jid));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Prepara o payload final
|
|
||||||
// Ordena os JIDs para garantir consistência na string final
|
|
||||||
const sortedJidOptions = [...finalJidOptions].sort();
|
|
||||||
const newJidOptionsString = sortedJidOptions.join(',');
|
|
||||||
const newLid = item.lid === 'lid' || item.remoteJid?.includes('@lid') ? 'lid' : null;
|
|
||||||
|
|
||||||
const dataPayload = {
|
|
||||||
remoteJid: remoteJid,
|
|
||||||
jidOptions: newJidOptionsString,
|
|
||||||
lid: newLid,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 4. Decide entre Criar ou Atualizar
|
|
||||||
if (existingRecord) {
|
|
||||||
// Compara a string de JIDs ordenada existente com a nova
|
|
||||||
const existingJidOptionsString = existingRecord.jidOptions
|
|
||||||
? existingRecord.jidOptions.split(',').sort().join(',')
|
|
||||||
: '';
|
|
||||||
|
|
||||||
const isDataSame =
|
|
||||||
existingRecord.remoteJid === dataPayload.remoteJid &&
|
|
||||||
existingJidOptionsString === dataPayload.jidOptions &&
|
|
||||||
existingRecord.lid === dataPayload.lid;
|
|
||||||
|
|
||||||
if (isDataSame) {
|
|
||||||
logger.verbose(`[saveOnWhatsappCache] Data for ${remoteJid} is already up-to-date. Skipping update.`);
|
|
||||||
return; // Pula para o próximo item
|
|
||||||
}
|
|
||||||
|
|
||||||
// Os dados são diferentes, então atualiza
|
|
||||||
logger.verbose(
|
|
||||||
`[saveOnWhatsappCache] Register exists, updating: remoteJid=${remoteJid}, jidOptions=${dataPayload.jidOptions}, lid=${dataPayload.lid}`,
|
|
||||||
);
|
|
||||||
await prismaRepository.isOnWhatsapp.update({
|
|
||||||
where: { id: existingRecord.id },
|
|
||||||
data: dataPayload,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Cria nova entrada
|
|
||||||
logger.verbose(
|
|
||||||
`[saveOnWhatsappCache] Register does not exist, creating: remoteJid=${remoteJid}, jidOptions=${dataPayload.jidOptions}, lid=${dataPayload.lid}`,
|
|
||||||
);
|
|
||||||
await prismaRepository.isOnWhatsapp.create({
|
|
||||||
data: dataPayload,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Loga o erro mas não para a execução dos outros promises
|
|
||||||
logger.error(`[saveOnWhatsappCache] Error processing item for ${item.remoteJid}: `);
|
|
||||||
logger.error(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Espera todas as operações paralelas terminarem
|
|
||||||
await Promise.allSettled(processingPromises);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getOnWhatsappCache(remoteJids: string[]) {
|
export async function getOnWhatsappCache(remoteJids: string[]) {
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { prismaRepository } from '@api/server.module';
|
import { prismaRepository } from '@api/server.module';
|
||||||
import { CacheService } from '@api/services/cache.service';
|
import { CacheService } from '@api/services/cache.service';
|
||||||
import { CacheConf, configService } from '@config/env.config';
|
import { CacheConf, configService } from '@config/env.config';
|
||||||
import { Logger } from '@config/logger.config';
|
|
||||||
import { INSTANCE_DIR } from '@config/path.config';
|
import { INSTANCE_DIR } from '@config/path.config';
|
||||||
import { AuthenticationState, BufferJSON, initAuthCreds, WAProto as proto } from 'baileys';
|
import { AuthenticationState, BufferJSON, initAuthCreds, WAProto as proto } from 'baileys';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
@ -74,15 +73,12 @@ async function fileExists(file: string): Promise<any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const logger = new Logger('useMultiFileAuthStatePrisma');
|
|
||||||
|
|
||||||
export default async function useMultiFileAuthStatePrisma(
|
export default async function useMultiFileAuthStatePrisma(
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
cache: CacheService,
|
cache: CacheService,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
state: AuthenticationState;
|
state: AuthenticationState;
|
||||||
saveCreds: () => Promise<void>;
|
saveCreds: () => Promise<void>;
|
||||||
removeCreds: () => Promise<void>;
|
|
||||||
}> {
|
}> {
|
||||||
const localFolder = path.join(INSTANCE_DIR, sessionId);
|
const localFolder = path.join(INSTANCE_DIR, sessionId);
|
||||||
const localFile = (key: string) => path.join(localFolder, fixFileName(key) + '.json');
|
const localFile = (key: string) => path.join(localFolder, fixFileName(key) + '.json');
|
||||||
@ -146,26 +142,6 @@ export default async function useMultiFileAuthStatePrisma(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeCreds(): Promise<any> {
|
|
||||||
const cacheConfig = configService.get<CacheConf>('CACHE');
|
|
||||||
|
|
||||||
// Redis
|
|
||||||
try {
|
|
||||||
if (cacheConfig.REDIS.ENABLED) {
|
|
||||||
await cache.delete(sessionId);
|
|
||||||
logger.info({ action: 'redis.delete', sessionId });
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
logger.warn({ action: 'redis.delete', sessionId, err });
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info({ action: 'auth.key.delete', sessionId });
|
|
||||||
|
|
||||||
await deleteAuthKey(sessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
let creds = await readData('creds');
|
let creds = await readData('creds');
|
||||||
if (!creds) {
|
if (!creds) {
|
||||||
creds = initAuthCreds();
|
creds = initAuthCreds();
|
||||||
@ -207,7 +183,5 @@ export default async function useMultiFileAuthStatePrisma(
|
|||||||
saveCreds: () => {
|
saveCreds: () => {
|
||||||
return writeData(creds, 'creds');
|
return writeData(creds, 'creds');
|
||||||
},
|
},
|
||||||
|
|
||||||
removeCreds,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,11 +39,7 @@ import { Logger } from '@config/logger.config';
|
|||||||
import { AuthenticationCreds, AuthenticationState, BufferJSON, initAuthCreds, proto, SignalDataTypeMap } from 'baileys';
|
import { AuthenticationCreds, AuthenticationState, BufferJSON, initAuthCreds, proto, SignalDataTypeMap } from 'baileys';
|
||||||
import { isNotEmpty } from 'class-validator';
|
import { isNotEmpty } from 'class-validator';
|
||||||
|
|
||||||
export type AuthState = {
|
export type AuthState = { state: AuthenticationState; saveCreds: () => Promise<void> };
|
||||||
state: AuthenticationState;
|
|
||||||
saveCreds: () => Promise<void>;
|
|
||||||
removeCreds: () => Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class AuthStateProvider {
|
export class AuthStateProvider {
|
||||||
constructor(private readonly providerFiles: ProviderFiles) {}
|
constructor(private readonly providerFiles: ProviderFiles) {}
|
||||||
@ -90,18 +86,6 @@ export class AuthStateProvider {
|
|||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeCreds = async () => {
|
|
||||||
const [response, error] = await this.providerFiles.removeSession(instance);
|
|
||||||
if (error) {
|
|
||||||
// this.logger.error(['removeData', error?.message, error?.stack]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info({ action: 'remove.session', instance, response });
|
|
||||||
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
const creds: AuthenticationCreds = (await readData('creds')) || initAuthCreds();
|
const creds: AuthenticationCreds = (await readData('creds')) || initAuthCreds();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -142,10 +126,6 @@ export class AuthStateProvider {
|
|||||||
saveCreds: async () => {
|
saveCreds: async () => {
|
||||||
return await writeData(creds, 'creds');
|
return await writeData(creds, 'creds');
|
||||||
},
|
},
|
||||||
|
|
||||||
removeCreds,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const logger = new Logger('useMultiFileAuthStatePrisma');
|
|
||||||
|
|||||||
@ -8,7 +8,6 @@ export async function useMultiFileAuthStateRedisDb(
|
|||||||
): Promise<{
|
): Promise<{
|
||||||
state: AuthenticationState;
|
state: AuthenticationState;
|
||||||
saveCreds: () => Promise<void>;
|
saveCreds: () => Promise<void>;
|
||||||
removeCreds: () => Promise<void>;
|
|
||||||
}> {
|
}> {
|
||||||
const logger = new Logger('useMultiFileAuthStateRedisDb');
|
const logger = new Logger('useMultiFileAuthStateRedisDb');
|
||||||
|
|
||||||
@ -37,16 +36,6 @@ export async function useMultiFileAuthStateRedisDb(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function removeCreds(): Promise<any> {
|
|
||||||
try {
|
|
||||||
logger.warn({ action: 'redis.delete', instanceName });
|
|
||||||
|
|
||||||
return await cache.delete(instanceName);
|
|
||||||
} catch {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const creds: AuthenticationCreds = (await readData('creds')) || initAuthCreds();
|
const creds: AuthenticationCreds = (await readData('creds')) || initAuthCreds();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -87,7 +76,5 @@ export async function useMultiFileAuthStateRedisDb(
|
|||||||
saveCreds: async () => {
|
saveCreds: async () => {
|
||||||
return await writeData(creds, 'creds');
|
return await writeData(creds, 'creds');
|
||||||
},
|
},
|
||||||
|
|
||||||
removeCreds,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -195,9 +195,8 @@ export const contactValidateSchema: JSONSchema7 = {
|
|||||||
_id: { type: 'string', minLength: 1 },
|
_id: { type: 'string', minLength: 1 },
|
||||||
pushName: { type: 'string', minLength: 1 },
|
pushName: { type: 'string', minLength: 1 },
|
||||||
id: { type: 'string', minLength: 1 },
|
id: { type: 'string', minLength: 1 },
|
||||||
remoteJid: { type: 'string', minLength: 1 },
|
|
||||||
},
|
},
|
||||||
...isNotEmpty('_id', 'id', 'pushName', 'remoteJid'),
|
...isNotEmpty('_id', 'id', 'pushName'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,32 +0,0 @@
|
|||||||
import { JSONSchema7 } from 'json-schema';
|
|
||||||
import { v4 } from 'uuid';
|
|
||||||
|
|
||||||
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
|
|
||||||
const properties: Record<string, unknown> = {};
|
|
||||||
propertyNames.forEach(
|
|
||||||
(property) =>
|
|
||||||
(properties[property] = {
|
|
||||||
minLength: 1,
|
|
||||||
description: `The "${property}" cannot be empty`,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
if: {
|
|
||||||
propertyNames: {
|
|
||||||
enum: [...propertyNames],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
then: { properties },
|
|
||||||
} as JSONSchema7;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const templateDeleteSchema: JSONSchema7 = {
|
|
||||||
$id: v4(),
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
name: { type: 'string' },
|
|
||||||
hsmId: { type: 'string' },
|
|
||||||
},
|
|
||||||
required: ['name'],
|
|
||||||
...isNotEmpty('name'),
|
|
||||||
};
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
import { JSONSchema7 } from 'json-schema';
|
|
||||||
import { v4 } from 'uuid';
|
|
||||||
|
|
||||||
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
|
|
||||||
const properties: Record<string, unknown> = {};
|
|
||||||
propertyNames.forEach(
|
|
||||||
(property) =>
|
|
||||||
(properties[property] = {
|
|
||||||
minLength: 1,
|
|
||||||
description: `The "${property}" cannot be empty`,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
if: {
|
|
||||||
propertyNames: {
|
|
||||||
enum: [...propertyNames],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
then: { properties },
|
|
||||||
} as JSONSchema7;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const templateEditSchema: JSONSchema7 = {
|
|
||||||
$id: v4(),
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
templateId: { type: 'string' },
|
|
||||||
category: { type: 'string', enum: ['AUTHENTICATION', 'MARKETING', 'UTILITY'] },
|
|
||||||
allowCategoryChange: { type: 'boolean' },
|
|
||||||
ttl: { type: 'number' },
|
|
||||||
components: { type: 'array' },
|
|
||||||
},
|
|
||||||
required: ['templateId'],
|
|
||||||
...isNotEmpty('templateId'),
|
|
||||||
};
|
|
||||||
@ -8,7 +8,5 @@ export * from './message.schema';
|
|||||||
export * from './proxy.schema';
|
export * from './proxy.schema';
|
||||||
export * from './settings.schema';
|
export * from './settings.schema';
|
||||||
export * from './template.schema';
|
export * from './template.schema';
|
||||||
export * from './templateDelete.schema';
|
|
||||||
export * from './templateEdit.schema';
|
|
||||||
export * from '@api/integrations/chatbot/chatbot.schema';
|
export * from '@api/integrations/chatbot/chatbot.schema';
|
||||||
export * from '@api/integrations/event/event.schema';
|
export * from '@api/integrations/event/event.schema';
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user