mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2026-01-09 21:32:20 -06:00
Compare commits
50 Commits
13f96a366b
...
4f642e17a7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f642e17a7 | ||
|
|
afa6d633c6 | ||
|
|
2e3e752719 | ||
|
|
de11e6f9ca | ||
|
|
26e7eefe51 | ||
|
|
b55c9fcab7 | ||
|
|
86b194af5f | ||
|
|
3c1573c400 | ||
|
|
178386594c | ||
|
|
cea1fa0979 | ||
|
|
38be0b49d9 | ||
|
|
04ac880fcc | ||
|
|
3864366e75 | ||
|
|
2756d7e61c | ||
|
|
bb36bfe424 | ||
|
|
6277c5d084 | ||
|
|
b1d77019f5 | ||
|
|
8d5c7d875e | ||
|
|
abd0351f8f | ||
|
|
c7a2aa51ee | ||
|
|
bbf60e30b0 | ||
|
|
2408384b0f | ||
|
|
250ddd2e89 | ||
|
|
bee309cd28 | ||
|
|
92c2ace7bc | ||
|
|
faed3f4574 | ||
|
|
baff4e8f5e | ||
|
|
1c3a7ab027 | ||
|
|
338cc93cfc | ||
|
|
930d32df3a | ||
|
|
fa6b5c28a6 | ||
|
|
8e7f348c12 | ||
|
|
5c58cb7eae | ||
|
|
879bee962b | ||
|
|
af47b859e4 | ||
|
|
1c61116a3e | ||
|
|
08a4795016 | ||
|
|
53a94af3f7 | ||
|
|
302e219f7f | ||
|
|
1e036ba3ae | ||
|
|
377993e4b0 | ||
|
|
ea88edd512 | ||
|
|
d3e3c458a0 | ||
|
|
067f0999b5 | ||
|
|
179af3f41c | ||
|
|
31a6f2d92e | ||
|
|
dc72f01625 | ||
|
|
3b139078c3 | ||
|
|
f2c2a6a64a | ||
|
|
e5a249109c |
85
CHANGELOG.md
85
CHANGELOG.md
@@ -1,4 +1,4 @@
|
|||||||
# 2.3.7 (develop)
|
# 2.3.7 (2025-12-05)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
@@ -7,6 +7,24 @@
|
|||||||
- Added DTOs and validation schemas for template management
|
- Added DTOs and validation schemas for template management
|
||||||
- Enhanced template lifecycle management capabilities
|
- 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
|
### Fixed
|
||||||
|
|
||||||
* **Baileys Message Processor**: Fix incoming message events not working after reconnection
|
* **Baileys Message Processor**: Fix incoming message events not working after reconnection
|
||||||
@@ -60,11 +78,76 @@
|
|||||||
- Fixed integration issues between Chatwoot and Baileys services
|
- Fixed integration issues between Chatwoot and Baileys services
|
||||||
- Improved message handling and delivery
|
- 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
|
### Code Quality & Refactoring
|
||||||
|
|
||||||
* **Template Management**: Remove unused template edit/delete DTOs after refactoring
|
* **Template Management**: Remove unused template edit/delete DTOs after refactoring
|
||||||
* **Proxy Utilities**: Improve makeProxyAgent for Undici compatibility
|
* **Proxy Utilities**: Improve makeProxyAgent for Undici compatibility
|
||||||
* **Code Formatting**: Enhance code formatting and consistency across services
|
* **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)
|
# 2.3.6 (2025-10-21)
|
||||||
|
|
||||||
|
|||||||
11
package-lock.json
generated
11
package-lock.json
generated
@@ -31,6 +31,7 @@
|
|||||||
"eventemitter2": "^6.4.9",
|
"eventemitter2": "^6.4.9",
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
"express-async-errors": "^3.1.1",
|
"express-async-errors": "^3.1.1",
|
||||||
|
"fetch-socks": "^1.3.2",
|
||||||
"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",
|
||||||
@@ -8578,6 +8579,16 @@
|
|||||||
"reusify": "^1.0.4"
|
"reusify": "^1.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fetch-socks": {
|
||||||
|
"version": "1.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fetch-socks/-/fetch-socks-1.3.2.tgz",
|
||||||
|
"integrity": "sha512-vkH5+Zgj2yEbU57Cei0iyLgTZ4OkEKJj56Xu3ViB5dpsl599JgEooQ3x6NVagIFRHWnWJ+7K0MO0aIV1TMgvnw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"socks": "^2.8.2",
|
||||||
|
"undici": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/figures": {
|
"node_modules/figures": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
|
||||||
|
|||||||
@@ -90,6 +90,7 @@
|
|||||||
"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",
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
-- 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,6 +132,7 @@ 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])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ import makeWASocket, {
|
|||||||
Chat,
|
Chat,
|
||||||
ConnectionState,
|
ConnectionState,
|
||||||
Contact,
|
Contact,
|
||||||
|
decryptPollVote,
|
||||||
delay,
|
delay,
|
||||||
DisconnectReason,
|
DisconnectReason,
|
||||||
downloadContentFromMessage,
|
downloadContentFromMessage,
|
||||||
@@ -113,6 +114,7 @@ import makeWASocket, {
|
|||||||
isJidGroup,
|
isJidGroup,
|
||||||
isJidNewsletter,
|
isJidNewsletter,
|
||||||
isPnUser,
|
isPnUser,
|
||||||
|
jidNormalizedUser,
|
||||||
makeCacheableSignalKeyStore,
|
makeCacheableSignalKeyStore,
|
||||||
MessageUpsertType,
|
MessageUpsertType,
|
||||||
MessageUserReceiptUpdate,
|
MessageUserReceiptUpdate,
|
||||||
@@ -133,6 +135,7 @@ import { Label } from 'baileys/lib/Types/Label';
|
|||||||
import { LabelAssociation } from 'baileys/lib/Types/LabelAssociation';
|
import { LabelAssociation } from 'baileys/lib/Types/LabelAssociation';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import { isArray, isBase64, isURL } from 'class-validator';
|
import { isArray, isBase64, isURL } from 'class-validator';
|
||||||
|
import { createHash } from 'crypto';
|
||||||
import EventEmitter2 from 'eventemitter2';
|
import EventEmitter2 from 'eventemitter2';
|
||||||
import ffmpeg from 'fluent-ffmpeg';
|
import ffmpeg from 'fluent-ffmpeg';
|
||||||
import FormData from 'form-data';
|
import FormData from 'form-data';
|
||||||
@@ -247,6 +250,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
private readonly userDevicesCache: CacheStore = new NodeCache({ stdTTL: 300000, useClones: false });
|
private readonly userDevicesCache: CacheStore = new NodeCache({ stdTTL: 300000, useClones: false });
|
||||||
private endSession = false;
|
private endSession = false;
|
||||||
private logBaileys = this.configService.get<Log>('LOG').BAILEYS;
|
private logBaileys = this.configService.get<Log>('LOG').BAILEYS;
|
||||||
|
private eventProcessingQueue: Promise<void> = Promise.resolve();
|
||||||
|
|
||||||
// Cache TTL constants (in seconds)
|
// Cache TTL constants (in seconds)
|
||||||
private readonly MESSAGE_CACHE_TTL_SECONDS = 5 * 60; // 5 minutes - avoid duplicate message processing
|
private readonly MESSAGE_CACHE_TTL_SECONDS = 5 * 60; // 5 minutes - avoid duplicate message processing
|
||||||
@@ -857,10 +861,12 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
this.sendDataWebhook(Events.CONTACTS_UPDATE, updatedContacts);
|
this.sendDataWebhook(Events.CONTACTS_UPDATE, updatedContacts);
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
updatedContacts.map(async (contact) => {
|
updatedContacts.map(async (contact) => {
|
||||||
const update = this.prismaRepository.contact.updateMany({
|
if (this.configService.get<Database>('DATABASE').SAVE_DATA.CONTACTS) {
|
||||||
where: { remoteJid: contact.remoteJid, instanceId: this.instanceId },
|
await this.prismaRepository.contact.updateMany({
|
||||||
data: { profilePicUrl: contact.profilePicUrl },
|
where: { remoteJid: contact.remoteJid, instanceId: this.instanceId },
|
||||||
});
|
data: { profilePicUrl: contact.profilePicUrl },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {
|
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {
|
||||||
const instance = { instanceName: this.instance.name, instanceId: this.instance.id };
|
const instance = { instanceName: this.instance.name, instanceId: this.instance.id };
|
||||||
@@ -879,8 +885,6 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
avatar_url: contact.profilePicUrl,
|
avatar_url: contact.profilePicUrl,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return update;
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -904,14 +908,16 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
this.sendDataWebhook(Events.CONTACTS_UPDATE, contactsRaw);
|
this.sendDataWebhook(Events.CONTACTS_UPDATE, contactsRaw);
|
||||||
|
|
||||||
const updateTransactions = contactsRaw.map((contact) =>
|
if (this.configService.get<Database>('DATABASE').SAVE_DATA.CONTACTS) {
|
||||||
this.prismaRepository.contact.upsert({
|
const updateTransactions = contactsRaw.map((contact) =>
|
||||||
where: { remoteJid_instanceId: { remoteJid: contact.remoteJid, instanceId: contact.instanceId } },
|
this.prismaRepository.contact.upsert({
|
||||||
create: contact,
|
where: { remoteJid_instanceId: { remoteJid: contact.remoteJid, instanceId: contact.instanceId } },
|
||||||
update: contact,
|
create: contact,
|
||||||
}),
|
update: contact,
|
||||||
);
|
}),
|
||||||
await this.prismaRepository.$transaction(updateTransactions);
|
);
|
||||||
|
await this.prismaRepository.$transaction(updateTransactions);
|
||||||
|
}
|
||||||
|
|
||||||
//const usersContacts = contactsRaw.filter((c) => c.remoteJid.includes('@s.whatsapp'));
|
//const usersContacts = contactsRaw.filter((c) => c.remoteJid.includes('@s.whatsapp'));
|
||||||
},
|
},
|
||||||
@@ -1040,7 +1046,10 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
messagesRaw.push(this.prepareMessage(m));
|
messagesRaw.push(this.prepareMessage(m));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sendDataWebhook(Events.MESSAGES_SET, [...messagesRaw]);
|
this.sendDataWebhook(Events.MESSAGES_SET, [...messagesRaw], true, undefined, {
|
||||||
|
isLatest,
|
||||||
|
progress,
|
||||||
|
});
|
||||||
|
|
||||||
if (this.configService.get<Database>('DATABASE').SAVE_DATA.HISTORIC) {
|
if (this.configService.get<Database>('DATABASE').SAVE_DATA.HISTORIC) {
|
||||||
await this.prismaRepository.message.createMany({ data: messagesRaw, skipDuplicates: true });
|
await this.prismaRepository.message.createMany({ data: messagesRaw, skipDuplicates: true });
|
||||||
@@ -1086,6 +1095,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
'Invalid PreKey ID',
|
'Invalid PreKey ID',
|
||||||
'No session record',
|
'No session record',
|
||||||
'No session found to decrypt message',
|
'No session found to decrypt message',
|
||||||
|
'Message absent from node',
|
||||||
].some((err) => param?.includes?.(err)),
|
].some((err) => param?.includes?.(err)),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
@@ -1121,6 +1131,11 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await this.sendDataWebhook(Events.MESSAGES_EDITED, editedMessage);
|
await this.sendDataWebhook(Events.MESSAGES_EDITED, editedMessage);
|
||||||
|
|
||||||
|
if (received.key?.id && editedMessage.key?.id) {
|
||||||
|
await this.baileysCache.set(`protocol_${received.key.id}`, editedMessage.key.id, 60 * 60 * 24);
|
||||||
|
}
|
||||||
|
|
||||||
const oldMessage = await this.getMessage(editedMessage.key, true);
|
const oldMessage = await this.getMessage(editedMessage.key, true);
|
||||||
if ((oldMessage as any)?.id) {
|
if ((oldMessage as any)?.id) {
|
||||||
const editedMessageTimestamp = Long.isLong(received?.messageTimestamp)
|
const editedMessageTimestamp = Long.isLong(received?.messageTimestamp)
|
||||||
@@ -1148,12 +1163,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if ((type !== 'notify' && type !== 'append') || editedMessage || !received?.message) {
|
||||||
(type !== 'notify' && type !== 'append') ||
|
|
||||||
editedMessage ||
|
|
||||||
received.message?.pollUpdateMessage ||
|
|
||||||
!received?.message
|
|
||||||
) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1193,6 +1203,107 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
const messageRaw = this.prepareMessage(received);
|
const messageRaw = this.prepareMessage(received);
|
||||||
|
|
||||||
|
if (messageRaw.messageType === 'pollUpdateMessage') {
|
||||||
|
const pollCreationKey = messageRaw.message.pollUpdateMessage.pollCreationMessageKey;
|
||||||
|
const pollMessage = (await this.getMessage(pollCreationKey, true)) as proto.IWebMessageInfo;
|
||||||
|
const pollMessageSecret = (await this.getMessage(pollCreationKey)) as any;
|
||||||
|
|
||||||
|
if (pollMessage) {
|
||||||
|
const pollOptions =
|
||||||
|
(pollMessage.message as any).pollCreationMessage?.options ||
|
||||||
|
(pollMessage.message as any).pollCreationMessageV3?.options ||
|
||||||
|
[];
|
||||||
|
const pollVote = messageRaw.message.pollUpdateMessage.vote;
|
||||||
|
|
||||||
|
const voterJid = received.key.fromMe
|
||||||
|
? this.instance.wuid
|
||||||
|
: received.key.participant || received.key.remoteJid;
|
||||||
|
|
||||||
|
let pollEncKey = pollMessageSecret?.messageContextInfo?.messageSecret;
|
||||||
|
|
||||||
|
let successfulVoterJid = voterJid;
|
||||||
|
|
||||||
|
if (typeof pollEncKey === 'string') {
|
||||||
|
pollEncKey = Buffer.from(pollEncKey, 'base64');
|
||||||
|
} else if (pollEncKey?.type === 'Buffer' && Array.isArray(pollEncKey.data)) {
|
||||||
|
pollEncKey = Buffer.from(pollEncKey.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Buffer.isBuffer(pollEncKey) && pollEncKey.length === 44) {
|
||||||
|
pollEncKey = Buffer.from(pollEncKey.toString('utf8'), 'base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pollVote.encPayload && pollEncKey) {
|
||||||
|
const creatorCandidates = [
|
||||||
|
this.instance.wuid,
|
||||||
|
this.client.user?.lid,
|
||||||
|
pollMessage.key.participant,
|
||||||
|
(pollMessage.key as any).participantAlt,
|
||||||
|
pollMessage.key.remoteJid,
|
||||||
|
];
|
||||||
|
|
||||||
|
const key = received.key as any;
|
||||||
|
const voterCandidates = [
|
||||||
|
this.instance.wuid,
|
||||||
|
this.client.user?.lid,
|
||||||
|
key.participant,
|
||||||
|
key.participantAlt,
|
||||||
|
key.remoteJidAlt,
|
||||||
|
key.remoteJid,
|
||||||
|
];
|
||||||
|
|
||||||
|
const uniqueCreators = [
|
||||||
|
...new Set(creatorCandidates.filter(Boolean).map((id) => jidNormalizedUser(id))),
|
||||||
|
];
|
||||||
|
const uniqueVoters = [...new Set(voterCandidates.filter(Boolean).map((id) => jidNormalizedUser(id)))];
|
||||||
|
|
||||||
|
let decryptedVote;
|
||||||
|
|
||||||
|
for (const creator of uniqueCreators) {
|
||||||
|
for (const voter of uniqueVoters) {
|
||||||
|
try {
|
||||||
|
decryptedVote = decryptPollVote(pollVote, {
|
||||||
|
pollCreatorJid: creator,
|
||||||
|
pollMsgId: pollMessage.key.id,
|
||||||
|
pollEncKey,
|
||||||
|
voterJid: voter,
|
||||||
|
} as any);
|
||||||
|
if (decryptedVote) {
|
||||||
|
successfulVoterJid = voter;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Continue trying
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (decryptedVote) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decryptedVote) {
|
||||||
|
Object.assign(pollVote, decryptedVote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedOptions = pollVote?.selectedOptions || [];
|
||||||
|
|
||||||
|
const selectedOptionNames = pollOptions
|
||||||
|
.filter((option) => {
|
||||||
|
const hash = createHash('sha256').update(option.optionName).digest();
|
||||||
|
return selectedOptions.some((selected) => Buffer.compare(selected, hash) === 0);
|
||||||
|
})
|
||||||
|
.map((option) => option.optionName);
|
||||||
|
|
||||||
|
messageRaw.message.pollUpdateMessage.vote.selectedOptions = selectedOptionNames;
|
||||||
|
|
||||||
|
const pollUpdates = pollOptions.map((option) => ({
|
||||||
|
name: option.optionName,
|
||||||
|
voters: selectedOptionNames.includes(option.optionName) ? [successfulVoterJid] : [],
|
||||||
|
}));
|
||||||
|
|
||||||
|
messageRaw.pollUpdates = pollUpdates;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const isMedia =
|
const isMedia =
|
||||||
received?.message?.imageMessage ||
|
received?.message?.imageMessage ||
|
||||||
received?.message?.videoMessage ||
|
received?.message?.videoMessage ||
|
||||||
@@ -1242,7 +1353,9 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.configService.get<Database>('DATABASE').SAVE_DATA.NEW_MESSAGE) {
|
if (this.configService.get<Database>('DATABASE').SAVE_DATA.NEW_MESSAGE) {
|
||||||
const msg = await this.prismaRepository.message.create({ data: messageRaw });
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const { pollUpdates, ...messageData } = messageRaw;
|
||||||
|
const msg = await this.prismaRepository.message.create({ data: messageData });
|
||||||
|
|
||||||
const { remoteJid } = received.key;
|
const { remoteJid } = received.key;
|
||||||
const timestamp = msg.messageTimestamp;
|
const timestamp = msg.messageTimestamp;
|
||||||
@@ -1290,6 +1403,11 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
} else {
|
} else {
|
||||||
const media = await this.getBase64FromMediaMessage({ message }, true);
|
const media = await this.getBase64FromMediaMessage({ message }, true);
|
||||||
|
|
||||||
|
if (!media) {
|
||||||
|
this.logger.verbose('No valid media to upload (messageContextInfo only), skipping MinIO');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { buffer, mediaType, fileName, size } = media;
|
const { buffer, mediaType, fileName, size } = media;
|
||||||
const mimetype = mimeTypes.lookup(fileName).toString();
|
const mimetype = mimeTypes.lookup(fileName).toString();
|
||||||
const fullName = join(
|
const fullName = join(
|
||||||
@@ -1447,18 +1565,26 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (update.message !== null && update.status === undefined) continue;
|
|
||||||
|
|
||||||
const updateKey = `${this.instance.id}_${key.id}_${update.status}`;
|
const updateKey = `${this.instance.id}_${key.id}_${update.status}`;
|
||||||
|
|
||||||
const cached = await this.baileysCache.get(updateKey);
|
const cached = await this.baileysCache.get(updateKey);
|
||||||
|
|
||||||
if (cached) {
|
const secondsSinceEpoch = Math.floor(Date.now() / 1000);
|
||||||
|
console.log('CACHE:', { cached, updateKey, messageTimestamp: update.messageTimestamp, secondsSinceEpoch });
|
||||||
|
|
||||||
|
if (
|
||||||
|
(update.messageTimestamp && update.messageTimestamp === cached) ||
|
||||||
|
(!update.messageTimestamp && secondsSinceEpoch === cached)
|
||||||
|
) {
|
||||||
this.logger.info(`Update Message duplicated ignored [avoid deadlock]: ${updateKey}`);
|
this.logger.info(`Update Message duplicated ignored [avoid deadlock]: ${updateKey}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.baileysCache.set(updateKey, true, 30 * 60);
|
if (update.messageTimestamp) {
|
||||||
|
await this.baileysCache.set(updateKey, update.messageTimestamp, 30 * 60);
|
||||||
|
} else {
|
||||||
|
await this.baileysCache.set(updateKey, secondsSinceEpoch, 30 * 60);
|
||||||
|
}
|
||||||
|
|
||||||
if (status[update.status] === 'READ' && key.fromMe) {
|
if (status[update.status] === 'READ' && key.fromMe) {
|
||||||
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {
|
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {
|
||||||
@@ -1489,19 +1615,32 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
remoteJid: key?.remoteJid,
|
remoteJid: key?.remoteJid,
|
||||||
fromMe: key.fromMe,
|
fromMe: key.fromMe,
|
||||||
participant: key?.participant,
|
participant: key?.participant,
|
||||||
status: status[update.status] ?? 'DELETED',
|
status: status[update.status] ?? 'SERVER_ACK',
|
||||||
pollUpdates,
|
pollUpdates,
|
||||||
instanceId: this.instanceId,
|
instanceId: this.instanceId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (update.message) {
|
||||||
|
message.message = update.message;
|
||||||
|
}
|
||||||
|
|
||||||
let findMessage: any;
|
let findMessage: any;
|
||||||
const configDatabaseData = this.configService.get<Database>('DATABASE').SAVE_DATA;
|
const configDatabaseData = this.configService.get<Database>('DATABASE').SAVE_DATA;
|
||||||
if (configDatabaseData.HISTORIC || configDatabaseData.NEW_MESSAGE) {
|
if (configDatabaseData.HISTORIC || configDatabaseData.NEW_MESSAGE) {
|
||||||
// Use raw SQL to avoid JSON path issues
|
// Use raw SQL to avoid JSON path issues
|
||||||
|
const protocolMapKey = `protocol_${key.id}`;
|
||||||
|
const originalMessageId = (await this.baileysCache.get(protocolMapKey)) as string;
|
||||||
|
|
||||||
|
if (originalMessageId) {
|
||||||
|
message.keyId = originalMessageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchId = originalMessageId || key.id;
|
||||||
|
|
||||||
const messages = (await this.prismaRepository.$queryRaw`
|
const messages = (await this.prismaRepository.$queryRaw`
|
||||||
SELECT * FROM "Message"
|
SELECT * FROM "Message"
|
||||||
WHERE "instanceId" = ${this.instanceId}
|
WHERE "instanceId" = ${this.instanceId}
|
||||||
AND "key"->>'id' = ${key.id}
|
AND "key"->>'id' = ${searchId}
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
`) as any[];
|
`) as any[];
|
||||||
findMessage = messages[0] || null;
|
findMessage = messages[0] || null;
|
||||||
@@ -1514,7 +1653,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (update.message === null && update.status === undefined) {
|
if (update.message === null && update.status === undefined) {
|
||||||
this.sendDataWebhook(Events.MESSAGES_DELETE, key);
|
this.sendDataWebhook(Events.MESSAGES_DELETE, { ...key, status: 'DELETED' });
|
||||||
|
|
||||||
if (this.configService.get<Database>('DATABASE').SAVE_DATA.MESSAGE_UPDATE)
|
if (this.configService.get<Database>('DATABASE').SAVE_DATA.MESSAGE_UPDATE)
|
||||||
await this.prismaRepository.messageUpdate.create({ data: message });
|
await this.prismaRepository.messageUpdate.create({ data: message });
|
||||||
@@ -1562,8 +1701,11 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
this.sendDataWebhook(Events.MESSAGES_UPDATE, message);
|
this.sendDataWebhook(Events.MESSAGES_UPDATE, message);
|
||||||
|
|
||||||
if (this.configService.get<Database>('DATABASE').SAVE_DATA.MESSAGE_UPDATE)
|
if (this.configService.get<Database>('DATABASE').SAVE_DATA.MESSAGE_UPDATE) {
|
||||||
await this.prismaRepository.messageUpdate.create({ data: message });
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const { message: _msg, ...messageData } = message;
|
||||||
|
await this.prismaRepository.messageUpdate.create({ data: messageData });
|
||||||
|
}
|
||||||
|
|
||||||
const existingChat = await this.prismaRepository.chat.findFirst({
|
const existingChat = await this.prismaRepository.chat.findFirst({
|
||||||
where: { instanceId: this.instanceId, remoteJid: message.remoteJid },
|
where: { instanceId: this.instanceId, remoteJid: message.remoteJid },
|
||||||
@@ -1614,9 +1756,9 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
// This enables LID to phoneNumber conversion without breaking existing webhook consumers
|
// This enables LID to phoneNumber conversion without breaking existing webhook consumers
|
||||||
|
|
||||||
// Helper to normalize participantId as phone number
|
// Helper to normalize participantId as phone number
|
||||||
const normalizePhoneNumber = (id: string): string => {
|
const normalizePhoneNumber = (id: string | null | undefined): string => {
|
||||||
// Remove @lid, @s.whatsapp.net suffixes and extract just the number part
|
// Remove @lid, @s.whatsapp.net suffixes and extract just the number part
|
||||||
return id.split('@')[0];
|
return String(id || '').split('@')[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -1732,135 +1874,141 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
private eventHandler() {
|
private eventHandler() {
|
||||||
this.client.ev.process(async (events) => {
|
this.client.ev.process(async (events) => {
|
||||||
if (!this.endSession) {
|
this.eventProcessingQueue = this.eventProcessingQueue.then(async () => {
|
||||||
const database = this.configService.get<Database>('DATABASE');
|
try {
|
||||||
const settings = await this.findSettings();
|
if (!this.endSession) {
|
||||||
|
const database = this.configService.get<Database>('DATABASE');
|
||||||
|
const settings = await this.findSettings();
|
||||||
|
|
||||||
if (events.call) {
|
if (events.call) {
|
||||||
const call = events.call[0];
|
const call = events.call[0];
|
||||||
|
|
||||||
if (settings?.rejectCall && call.status == 'offer') {
|
if (settings?.rejectCall && call.status == 'offer') {
|
||||||
this.client.rejectCall(call.id, call.from);
|
this.client.rejectCall(call.id, call.from);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings?.msgCall?.trim().length > 0 && call.status == 'offer') {
|
if (settings?.msgCall?.trim().length > 0 && call.status == 'offer') {
|
||||||
if (call.from.endsWith('@lid')) {
|
if (call.from.endsWith('@lid')) {
|
||||||
call.from = await this.client.signalRepository.lidMapping.getPNForLID(call.from as string);
|
call.from = await this.client.signalRepository.lidMapping.getPNForLID(call.from as string);
|
||||||
|
}
|
||||||
|
const msg = await this.client.sendMessage(call.from, { text: settings.msgCall });
|
||||||
|
|
||||||
|
this.client.ev.emit('messages.upsert', { messages: [msg], type: 'notify' });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendDataWebhook(Events.CALL, call);
|
||||||
}
|
}
|
||||||
const msg = await this.client.sendMessage(call.from, { text: settings.msgCall });
|
|
||||||
|
|
||||||
this.client.ev.emit('messages.upsert', { messages: [msg], type: 'notify' });
|
if (events['connection.update']) {
|
||||||
}
|
this.connectionUpdate(events['connection.update']);
|
||||||
|
}
|
||||||
|
|
||||||
this.sendDataWebhook(Events.CALL, call);
|
if (events['creds.update']) {
|
||||||
}
|
this.instance.authState.saveCreds();
|
||||||
|
}
|
||||||
|
|
||||||
if (events['connection.update']) {
|
if (events['messaging-history.set']) {
|
||||||
this.connectionUpdate(events['connection.update']);
|
const payload = events['messaging-history.set'];
|
||||||
}
|
await this.messageHandle['messaging-history.set'](payload);
|
||||||
|
}
|
||||||
|
|
||||||
if (events['creds.update']) {
|
if (events['messages.upsert']) {
|
||||||
this.instance.authState.saveCreds();
|
const payload = events['messages.upsert'];
|
||||||
}
|
|
||||||
|
|
||||||
if (events['messaging-history.set']) {
|
// this.messageProcessor.processMessage(payload, settings);
|
||||||
const payload = events['messaging-history.set'];
|
await this.messageHandle['messages.upsert'](payload, settings);
|
||||||
this.messageHandle['messaging-history.set'](payload);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (events['messages.upsert']) {
|
if (events['messages.update']) {
|
||||||
const payload = events['messages.upsert'];
|
const payload = events['messages.update'];
|
||||||
|
await this.messageHandle['messages.update'](payload, settings);
|
||||||
|
}
|
||||||
|
|
||||||
this.messageProcessor.processMessage(payload, settings);
|
if (events['message-receipt.update']) {
|
||||||
// this.messageHandle['messages.upsert'](payload, settings);
|
const payload = events['message-receipt.update'] as MessageUserReceiptUpdate[];
|
||||||
}
|
const remotesJidMap: Record<string, number> = {};
|
||||||
|
|
||||||
if (events['messages.update']) {
|
for (const event of payload) {
|
||||||
const payload = events['messages.update'];
|
if (typeof event.key.remoteJid === 'string' && typeof event.receipt.readTimestamp === 'number') {
|
||||||
this.messageHandle['messages.update'](payload, settings);
|
remotesJidMap[event.key.remoteJid] = event.receipt.readTimestamp;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (events['message-receipt.update']) {
|
await Promise.all(
|
||||||
const payload = events['message-receipt.update'] as MessageUserReceiptUpdate[];
|
Object.keys(remotesJidMap).map(async (remoteJid) =>
|
||||||
const remotesJidMap: Record<string, number> = {};
|
this.updateMessagesReadedByTimestamp(remoteJid, remotesJidMap[remoteJid]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for (const event of payload) {
|
if (events['presence.update']) {
|
||||||
if (typeof event.key.remoteJid === 'string' && typeof event.receipt.readTimestamp === 'number') {
|
const payload = events['presence.update'];
|
||||||
remotesJidMap[event.key.remoteJid] = event.receipt.readTimestamp;
|
|
||||||
|
if (settings?.groupsIgnore && payload.id.includes('@g.us')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendDataWebhook(Events.PRESENCE_UPDATE, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!settings?.groupsIgnore) {
|
||||||
|
if (events['groups.upsert']) {
|
||||||
|
const payload = events['groups.upsert'];
|
||||||
|
this.groupHandler['groups.upsert'](payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events['groups.update']) {
|
||||||
|
const payload = events['groups.update'];
|
||||||
|
this.groupHandler['groups.update'](payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events['group-participants.update']) {
|
||||||
|
const payload = events['group-participants.update'] as any;
|
||||||
|
this.groupHandler['group-participants.update'](payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events['chats.upsert']) {
|
||||||
|
const payload = events['chats.upsert'];
|
||||||
|
this.chatHandle['chats.upsert'](payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events['chats.update']) {
|
||||||
|
const payload = events['chats.update'];
|
||||||
|
this.chatHandle['chats.update'](payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events['chats.delete']) {
|
||||||
|
const payload = events['chats.delete'];
|
||||||
|
this.chatHandle['chats.delete'](payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events['contacts.upsert']) {
|
||||||
|
const payload = events['contacts.upsert'];
|
||||||
|
this.contactHandle['contacts.upsert'](payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events['contacts.update']) {
|
||||||
|
const payload = events['contacts.update'];
|
||||||
|
this.contactHandle['contacts.update'](payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events[Events.LABELS_ASSOCIATION]) {
|
||||||
|
const payload = events[Events.LABELS_ASSOCIATION];
|
||||||
|
this.labelHandle[Events.LABELS_ASSOCIATION](payload, database);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (events[Events.LABELS_EDIT]) {
|
||||||
|
const payload = events[Events.LABELS_EDIT];
|
||||||
|
this.labelHandle[Events.LABELS_EDIT](payload);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
await Promise.all(
|
this.logger.error(error);
|
||||||
Object.keys(remotesJidMap).map(async (remoteJid) =>
|
|
||||||
this.updateMessagesReadedByTimestamp(remoteJid, remotesJidMap[remoteJid]),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
if (events['presence.update']) {
|
|
||||||
const payload = events['presence.update'];
|
|
||||||
|
|
||||||
if (settings?.groupsIgnore && payload.id.includes('@g.us')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.sendDataWebhook(Events.PRESENCE_UPDATE, payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!settings?.groupsIgnore) {
|
|
||||||
if (events['groups.upsert']) {
|
|
||||||
const payload = events['groups.upsert'];
|
|
||||||
this.groupHandler['groups.upsert'](payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (events['groups.update']) {
|
|
||||||
const payload = events['groups.update'];
|
|
||||||
this.groupHandler['groups.update'](payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (events['group-participants.update']) {
|
|
||||||
const payload = events['group-participants.update'] as any;
|
|
||||||
this.groupHandler['group-participants.update'](payload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (events['chats.upsert']) {
|
|
||||||
const payload = events['chats.upsert'];
|
|
||||||
this.chatHandle['chats.upsert'](payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (events['chats.update']) {
|
|
||||||
const payload = events['chats.update'];
|
|
||||||
this.chatHandle['chats.update'](payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (events['chats.delete']) {
|
|
||||||
const payload = events['chats.delete'];
|
|
||||||
this.chatHandle['chats.delete'](payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (events['contacts.upsert']) {
|
|
||||||
const payload = events['contacts.upsert'];
|
|
||||||
this.contactHandle['contacts.upsert'](payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (events['contacts.update']) {
|
|
||||||
const payload = events['contacts.update'];
|
|
||||||
this.contactHandle['contacts.update'](payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (events[Events.LABELS_ASSOCIATION]) {
|
|
||||||
const payload = events[Events.LABELS_ASSOCIATION];
|
|
||||||
this.labelHandle[Events.LABELS_ASSOCIATION](payload, database);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (events[Events.LABELS_EDIT]) {
|
|
||||||
const payload = events[Events.LABELS_EDIT];
|
|
||||||
this.labelHandle[Events.LABELS_EDIT](payload);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2326,6 +2474,11 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
} else {
|
} else {
|
||||||
const media = await this.getBase64FromMediaMessage({ message }, true);
|
const media = await this.getBase64FromMediaMessage({ message }, true);
|
||||||
|
|
||||||
|
if (!media) {
|
||||||
|
this.logger.verbose('No valid media to upload (messageContextInfo only), skipping MinIO');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { buffer, mediaType, fileName, size } = media;
|
const { buffer, mediaType, fileName, size } = media;
|
||||||
|
|
||||||
const mimetype = mimeTypes.lookup(fileName).toString();
|
const mimetype = mimeTypes.lookup(fileName).toString();
|
||||||
@@ -3699,7 +3852,8 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ('messageContextInfo' in msg.message && Object.keys(msg.message).length === 1) {
|
if ('messageContextInfo' in msg.message && Object.keys(msg.message).length === 1) {
|
||||||
throw 'The message is messageContextInfo';
|
this.logger.verbose('Message contains only messageContextInfo, skipping media processing');
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mediaMessage: any;
|
let mediaMessage: any;
|
||||||
@@ -4882,7 +5036,6 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
AND: [
|
AND: [
|
||||||
keyFilters?.id ? { key: { path: ['id'], equals: keyFilters?.id } } : {},
|
keyFilters?.id ? { key: { path: ['id'], equals: keyFilters?.id } } : {},
|
||||||
keyFilters?.fromMe ? { key: { path: ['fromMe'], equals: keyFilters?.fromMe } } : {},
|
keyFilters?.fromMe ? { key: { path: ['fromMe'], equals: keyFilters?.fromMe } } : {},
|
||||||
keyFilters?.remoteJid ? { key: { path: ['remoteJid'], equals: keyFilters?.remoteJid } } : {},
|
|
||||||
keyFilters?.participant ? { key: { path: ['participant'], equals: keyFilters?.participant } } : {},
|
keyFilters?.participant ? { key: { path: ['participant'], equals: keyFilters?.participant } } : {},
|
||||||
{
|
{
|
||||||
OR: [
|
OR: [
|
||||||
@@ -4912,7 +5065,6 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
AND: [
|
AND: [
|
||||||
keyFilters?.id ? { key: { path: ['id'], equals: keyFilters?.id } } : {},
|
keyFilters?.id ? { key: { path: ['id'], equals: keyFilters?.id } } : {},
|
||||||
keyFilters?.fromMe ? { key: { path: ['fromMe'], equals: keyFilters?.fromMe } } : {},
|
keyFilters?.fromMe ? { key: { path: ['fromMe'], equals: keyFilters?.fromMe } } : {},
|
||||||
keyFilters?.remoteJid ? { key: { path: ['remoteJid'], equals: keyFilters?.remoteJid } } : {},
|
|
||||||
keyFilters?.participant ? { key: { path: ['participant'], equals: keyFilters?.participant } } : {},
|
keyFilters?.participant ? { key: { path: ['participant'], equals: keyFilters?.participant } } : {},
|
||||||
{
|
{
|
||||||
OR: [
|
OR: [
|
||||||
|
|||||||
@@ -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.split('@')[0],
|
number: remoteJid.includes('@lid') ? remoteJid : 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.split('@')[0],
|
number: remoteJid.includes('@lid') ? remoteJid : 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.split('@')[0],
|
number: remoteJid.includes('@lid') ? remoteJid : remoteJid.split('@')[0],
|
||||||
delay: settings?.delayMessage || 1000,
|
delay: settings?.delayMessage || 1000,
|
||||||
text: message,
|
text: message,
|
||||||
linkPreview,
|
linkPreview,
|
||||||
|
|||||||
@@ -346,6 +346,16 @@ export class ChatwootService {
|
|||||||
|
|
||||||
return contact;
|
return contact;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if ((error.status === 422 || error.response?.status === 422) && jid) {
|
||||||
|
this.logger.warn(`Contact with identifier ${jid} creation failed (422). Checking if it already exists...`);
|
||||||
|
const existingContact = await this.findContactByIdentifier(instance, jid);
|
||||||
|
if (existingContact) {
|
||||||
|
const contactId = existingContact.id;
|
||||||
|
await this.addLabelToContact(this.provider.nameInbox, contactId);
|
||||||
|
return existingContact;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.error('Error creating contact');
|
this.logger.error('Error creating contact');
|
||||||
console.log(error);
|
console.log(error);
|
||||||
return null;
|
return null;
|
||||||
@@ -415,6 +425,55 @@ export class ChatwootService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async findContactByIdentifier(instance: InstanceDto, identifier: string) {
|
||||||
|
const client = await this.clientCw(instance);
|
||||||
|
|
||||||
|
if (!client) {
|
||||||
|
this.logger.warn('client not found');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Direct search by query (q) - most common way to search by identifier/email/phone
|
||||||
|
const contact = (await (client as any).get('contacts/search', {
|
||||||
|
params: {
|
||||||
|
q: identifier,
|
||||||
|
sort: 'name',
|
||||||
|
},
|
||||||
|
})) as any;
|
||||||
|
|
||||||
|
if (contact && contact.data && contact.data.payload && contact.data.payload.length > 0) {
|
||||||
|
return contact.data.payload[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback for older API versions or different response structures
|
||||||
|
if (contact && contact.payload && contact.payload.length > 0) {
|
||||||
|
return contact.payload[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try search by attribute
|
||||||
|
const contactByAttr = (await (client as any).post('contacts/filter', {
|
||||||
|
payload: [
|
||||||
|
{
|
||||||
|
attribute_key: 'identifier',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: [identifier],
|
||||||
|
query_operator: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})) as any;
|
||||||
|
|
||||||
|
if (contactByAttr && contactByAttr.payload && contactByAttr.payload.length > 0) {
|
||||||
|
return contactByAttr.payload[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check inside data property if using axios interceptors wrapper
|
||||||
|
if (contactByAttr && contactByAttr.data && contactByAttr.data.payload && contactByAttr.data.payload.length > 0) {
|
||||||
|
return contactByAttr.data.payload[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public async findContact(instance: InstanceDto, phoneNumber: string) {
|
public async findContact(instance: InstanceDto, phoneNumber: string) {
|
||||||
const client = await this.clientCw(instance);
|
const client = await this.clientCw(instance);
|
||||||
|
|
||||||
@@ -1574,7 +1633,11 @@ export class ChatwootService {
|
|||||||
this.logger.verbose(`Update result: ${result} rows affected`);
|
this.logger.verbose(`Update result: ${result} rows affected`);
|
||||||
|
|
||||||
if (this.isImportHistoryAvailable()) {
|
if (this.isImportHistoryAvailable()) {
|
||||||
chatwootImport.updateMessageSourceID(chatwootMessageIds.messageId, key.id);
|
try {
|
||||||
|
await chatwootImport.updateMessageSourceID(chatwootMessageIds.messageId, key.id);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Error updating Chatwoot message source ID: ${error}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2024,7 +2087,7 @@ export class ChatwootService {
|
|||||||
if (body.key.remoteJid.includes('@g.us')) {
|
if (body.key.remoteJid.includes('@g.us')) {
|
||||||
const participantName = body.pushName;
|
const participantName = body.pushName;
|
||||||
const rawPhoneNumber =
|
const rawPhoneNumber =
|
||||||
body.key.addressingMode === 'lid' && !body.key.fromMe
|
body.key.addressingMode === 'lid' && !body.key.fromMe && body.key.participantAlt
|
||||||
? body.key.participantAlt.split('@')[0].split(':')[0]
|
? body.key.participantAlt.split('@')[0].split(':')[0]
|
||||||
: body.key.participant.split('@')[0].split(':')[0];
|
: body.key.participant.split('@')[0].split(':')[0];
|
||||||
const formattedPhoneNumber = parsePhoneNumberFromString(`+${rawPhoneNumber}`).formatInternational();
|
const formattedPhoneNumber = parsePhoneNumberFromString(`+${rawPhoneNumber}`).formatInternational();
|
||||||
@@ -2206,7 +2269,7 @@ export class ChatwootService {
|
|||||||
if (body.key.remoteJid.includes('@g.us')) {
|
if (body.key.remoteJid.includes('@g.us')) {
|
||||||
const participantName = body.pushName;
|
const participantName = body.pushName;
|
||||||
const rawPhoneNumber =
|
const rawPhoneNumber =
|
||||||
body.key.addressingMode === 'lid' && !body.key.fromMe
|
body.key.addressingMode === 'lid' && !body.key.fromMe && body.key.participantAlt
|
||||||
? body.key.participantAlt.split('@')[0].split(':')[0]
|
? body.key.participantAlt.split('@')[0].split(':')[0]
|
||||||
: body.key.participant.split('@')[0].split(':')[0];
|
: body.key.participant.split('@')[0].split(':')[0];
|
||||||
const formattedPhoneNumber = parsePhoneNumberFromString(`+${rawPhoneNumber}`).formatInternational();
|
const formattedPhoneNumber = parsePhoneNumberFromString(`+${rawPhoneNumber}`).formatInternational();
|
||||||
@@ -2464,7 +2527,13 @@ export class ChatwootService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getNumberFromRemoteJid(remoteJid: string) {
|
public normalizeJidIdentifier(remoteJid: string) {
|
||||||
|
if (!remoteJid) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (remoteJid.includes('@lid')) {
|
||||||
|
return remoteJid;
|
||||||
|
}
|
||||||
return remoteJid.replace(/:\d+/, '').split('@')[0];
|
return remoteJid.replace(/:\d+/, '').split('@')[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ 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.split('@')[0],
|
number: session.remoteJid,
|
||||||
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.split('@')[0],
|
number: session.remoteJid,
|
||||||
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.split('@')[0],
|
number: session.remoteJid,
|
||||||
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.split('@')[0],
|
number: remoteJid,
|
||||||
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.split('@')[0],
|
number: remoteJid,
|
||||||
thumbnailUrl: undefined,
|
thumbnailUrl: undefined,
|
||||||
title: '',
|
title: '',
|
||||||
description: '',
|
description: '',
|
||||||
|
|||||||
@@ -14,12 +14,24 @@ 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({ instanceName, origin, event, data, serverUrl, dateTime, sender, apiKey, local }: EmitData): Promise<void>;
|
emit({
|
||||||
|
instanceName,
|
||||||
|
origin,
|
||||||
|
event,
|
||||||
|
data,
|
||||||
|
serverUrl,
|
||||||
|
dateTime,
|
||||||
|
sender,
|
||||||
|
apiKey,
|
||||||
|
local,
|
||||||
|
extra,
|
||||||
|
}: EmitData): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EventController {
|
export class EventController {
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ 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,6 +262,7 @@ 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;
|
||||||
@@ -284,6 +285,7 @@ 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,6 +47,7 @@ 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;
|
||||||
@@ -65,6 +66,7 @@ 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,6 +121,7 @@ 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;
|
||||||
@@ -133,6 +134,7 @@ 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,6 +209,7 @@ 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;
|
||||||
@@ -233,6 +234,7 @@ 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,6 +93,7 @@ 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;
|
||||||
@@ -128,6 +129,7 @@ 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,6 +65,7 @@ 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;
|
||||||
@@ -90,6 +91,7 @@ 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,10 +33,13 @@ 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 isAllowedHost = allowedHosts
|
const allowAllHosts = allowedHosts.trim() === '*';
|
||||||
.split(',')
|
const isAllowedHost =
|
||||||
.map((h) => h.trim())
|
allowAllHosts ||
|
||||||
.includes(remoteAddress);
|
allowedHosts
|
||||||
|
.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);
|
||||||
@@ -115,6 +118,7 @@ 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;
|
||||||
@@ -127,6 +131,7 @@ 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,
|
||||||
|
|||||||
@@ -432,7 +432,13 @@ export class ChannelStartupService {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async sendDataWebhook<T extends object = any>(event: Events, data: T, local = true, integration?: string[]) {
|
public async sendDataWebhook<T extends object = any>(
|
||||||
|
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();
|
||||||
@@ -453,6 +459,7 @@ export class ChannelStartupService {
|
|||||||
apiKey: expose && instanceApikey ? instanceApikey : null,
|
apiKey: expose && instanceApikey ? instanceApikey : null,
|
||||||
local,
|
local,
|
||||||
integration,
|
integration,
|
||||||
|
extra,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
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';
|
import { ProxyAgent } from 'undici';
|
||||||
@@ -18,12 +19,23 @@ 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:
|
||||||
return new SocksProxyAgent(url);
|
case PROXY_SOCKS5_PROTOCOL: {
|
||||||
|
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}`);
|
||||||
}
|
}
|
||||||
@@ -64,6 +76,8 @@ export function makeProxyAgentUndici(proxy: Proxy | string): ProxyAgent {
|
|||||||
proxyUrl = `${protocol}://${auth}${host}:${port}`;
|
proxyUrl = `${protocol}://${auth}${host}:${port}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protocol = protocol.toLowerCase();
|
||||||
|
|
||||||
const PROXY_HTTP_PROTOCOL = 'http';
|
const PROXY_HTTP_PROTOCOL = 'http';
|
||||||
const PROXY_HTTPS_PROTOCOL = 'https';
|
const PROXY_HTTPS_PROTOCOL = 'https';
|
||||||
const PROXY_SOCKS4_PROTOCOL = 'socks4';
|
const PROXY_SOCKS4_PROTOCOL = 'socks4';
|
||||||
@@ -72,10 +86,25 @@ export function makeProxyAgentUndici(proxy: Proxy | string): ProxyAgent {
|
|||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
case PROXY_HTTP_PROTOCOL:
|
case PROXY_HTTP_PROTOCOL:
|
||||||
case PROXY_HTTPS_PROTOCOL:
|
case PROXY_HTTPS_PROTOCOL:
|
||||||
case PROXY_SOCKS4_PROTOCOL:
|
|
||||||
case PROXY_SOCKS5_PROTOCOL:
|
|
||||||
return new ProxyAgent(proxyUrl);
|
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:
|
default:
|
||||||
throw new Error(`Unsupported proxy protocol: ${protocol}`);
|
throw new Error(`Unsupported proxy protocol: ${protocol}`);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user