mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-12-25 14:47:45 -06:00
merging with develop
This commit is contained in:
@@ -206,6 +206,20 @@ export class BusinessStartupService extends ChannelStartupService {
|
||||
return content;
|
||||
}
|
||||
|
||||
private messageLocationJson(received: any) {
|
||||
const message = received.messages[0];
|
||||
let content: any = {
|
||||
locationMessage: {
|
||||
degreesLatitude: message.location.latitude,
|
||||
degreesLongitude: message.location.longitude,
|
||||
name: message.location?.name,
|
||||
address: message.location?.address,
|
||||
},
|
||||
};
|
||||
message.context ? (content = { ...content, contextInfo: { stanzaId: message.context.id } }) : content;
|
||||
return content;
|
||||
}
|
||||
|
||||
private messageContactsJson(received: any) {
|
||||
const message = received.messages[0];
|
||||
let content: any = {};
|
||||
@@ -283,6 +297,9 @@ export class BusinessStartupService extends ChannelStartupService {
|
||||
case 'template':
|
||||
messageType = 'conversation';
|
||||
break;
|
||||
case 'location':
|
||||
messageType = 'locationMessage';
|
||||
break;
|
||||
default:
|
||||
messageType = 'conversation';
|
||||
break;
|
||||
@@ -438,6 +455,17 @@ export class BusinessStartupService extends ChannelStartupService {
|
||||
source: 'unknown',
|
||||
instanceId: this.instanceId,
|
||||
};
|
||||
} else if (received?.messages[0].location) {
|
||||
messageRaw = {
|
||||
key,
|
||||
pushName,
|
||||
message: this.messageLocationJson(received),
|
||||
contextInfo: this.messageLocationJson(received)?.contextInfo,
|
||||
messageType: this.renderMessageType(received.messages[0].type),
|
||||
messageTimestamp: parseInt(received.messages[0].timestamp) as number,
|
||||
source: 'unknown',
|
||||
instanceId: this.instanceId,
|
||||
};
|
||||
} else {
|
||||
messageRaw = {
|
||||
key,
|
||||
@@ -800,6 +828,7 @@ export class BusinessStartupService extends ChannelStartupService {
|
||||
}
|
||||
if (message['media']) {
|
||||
const isImage = message['mimetype']?.startsWith('image/');
|
||||
const isVideo = message['mimetype']?.startsWith('video/');
|
||||
|
||||
content = {
|
||||
messaging_product: 'whatsapp',
|
||||
@@ -809,7 +838,7 @@ export class BusinessStartupService extends ChannelStartupService {
|
||||
[message['mediaType']]: {
|
||||
[message['type']]: message['id'],
|
||||
preview_url: linkPreview,
|
||||
...(message['fileName'] && !isImage && { filename: message['fileName'] }),
|
||||
...(message['fileName'] && !isImage && !isVideo && { filename: message['fileName'] }),
|
||||
caption: message['caption'],
|
||||
},
|
||||
};
|
||||
@@ -977,8 +1006,10 @@ export class BusinessStartupService extends ChannelStartupService {
|
||||
|
||||
private async getIdMedia(mediaMessage: any) {
|
||||
const formData = new FormData();
|
||||
const media = mediaMessage.media || mediaMessage.audio;
|
||||
if (!media) throw new Error('Media or audio not found');
|
||||
|
||||
const fileStream = createReadStream(mediaMessage.media);
|
||||
const fileStream = createReadStream(media);
|
||||
|
||||
formData.append('file', fileStream, { filename: 'media', contentType: mediaMessage.mimetype });
|
||||
formData.append('typeFile', mediaMessage.mimetype);
|
||||
@@ -1079,7 +1110,7 @@ export class BusinessStartupService extends ChannelStartupService {
|
||||
const prepareMedia: any = {
|
||||
fileName: `${hash}.mp3`,
|
||||
mediaType: 'audio',
|
||||
media: audio,
|
||||
audio,
|
||||
};
|
||||
|
||||
if (isURL(audio)) {
|
||||
@@ -1101,15 +1132,7 @@ export class BusinessStartupService extends ChannelStartupService {
|
||||
public async audioWhatsapp(data: SendAudioDto, file?: any, isIntegration = false) {
|
||||
const mediaData: SendAudioDto = { ...data };
|
||||
|
||||
if (file?.buffer) {
|
||||
mediaData.audio = file.buffer.toString('base64');
|
||||
} else if (isURL(mediaData.audio)) {
|
||||
// DO NOTHING
|
||||
// mediaData.audio = mediaData.audio;
|
||||
} else {
|
||||
console.error('El archivo no tiene buffer o file es undefined');
|
||||
throw new Error('File or buffer is undefined');
|
||||
}
|
||||
if (file) mediaData.audio = file.buffer.toString('base64');
|
||||
|
||||
const message = await this.processAudio(mediaData.audio, data.number);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getCollectionsDto } from '@api/dto/business.dto';
|
||||
import { OfferCallDto } from '@api/dto/call.dto';
|
||||
import {
|
||||
ArchiveChatDto,
|
||||
@@ -91,6 +92,7 @@ import makeWASocket, {
|
||||
BufferedEventData,
|
||||
BufferJSON,
|
||||
CacheStore,
|
||||
CatalogCollection,
|
||||
Chat,
|
||||
ConnectionState,
|
||||
Contact,
|
||||
@@ -100,6 +102,7 @@ import makeWASocket, {
|
||||
fetchLatestBaileysVersion,
|
||||
generateWAMessageFromContent,
|
||||
getAggregateVotesInPollMessage,
|
||||
GetCatalogOptions,
|
||||
getContentType,
|
||||
getDevice,
|
||||
GroupMetadata,
|
||||
@@ -113,6 +116,7 @@ import makeWASocket, {
|
||||
MiscMessageGenerationOptions,
|
||||
ParticipantAction,
|
||||
prepareWAMessageMedia,
|
||||
Product,
|
||||
proto,
|
||||
UserFacingSocketConfig,
|
||||
WABrowserDescription,
|
||||
@@ -378,7 +382,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
qrcodeTerminal.generate(qr, { small: true }, (qrcode) =>
|
||||
this.logger.log(
|
||||
`\n{ instance: ${this.instance.name} pairingCode: ${this.instance.qrcode.pairingCode}, qrcodeCount: ${this.instance.qrcode.count} }\n` +
|
||||
qrcode,
|
||||
qrcode,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1019,18 +1023,18 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
|
||||
const messagesRepository: Set<string> = new Set(
|
||||
chatwootImport.getRepositoryMessagesCache(instance) ??
|
||||
(
|
||||
await this.prismaRepository.message.findMany({
|
||||
select: { key: true },
|
||||
where: { instanceId: this.instanceId },
|
||||
})
|
||||
).map((message) => {
|
||||
const key = message.key as {
|
||||
id: string;
|
||||
};
|
||||
(
|
||||
await this.prismaRepository.message.findMany({
|
||||
select: { key: true },
|
||||
where: { instanceId: this.instanceId },
|
||||
})
|
||||
).map((message) => {
|
||||
const key = message.key as {
|
||||
id: string;
|
||||
};
|
||||
|
||||
return key.id;
|
||||
}),
|
||||
return key.id;
|
||||
}),
|
||||
);
|
||||
|
||||
if (chatwootImport.getRepositoryMessagesCache(instance) === null) {
|
||||
@@ -1128,37 +1132,74 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
}
|
||||
}
|
||||
|
||||
if (received.message?.protocolMessage?.editedMessage || received.message?.editedMessage?.message) {
|
||||
const editedMessage =
|
||||
received.message?.protocolMessage || received.message?.editedMessage?.message?.protocolMessage;
|
||||
if (editedMessage) {
|
||||
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled)
|
||||
this.chatwootService.eventWhatsapp(
|
||||
'messages.edit',
|
||||
{ instanceName: this.instance.name, instanceId: this.instance.id },
|
||||
editedMessage,
|
||||
);
|
||||
await this.sendDataWebhook(Events.MESSAGES_EDITED, editedMessage);
|
||||
const editedMessage = received?.message?.protocolMessage || received?.message?.editedMessage?.message?.protocolMessage;
|
||||
|
||||
if (received.message?.protocolMessage?.editedMessage && editedMessage) {
|
||||
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled)
|
||||
this.chatwootService.eventWhatsapp(
|
||||
'messages.edit',
|
||||
{ instanceName: this.instance.name, instanceId: this.instance.id },
|
||||
editedMessage,
|
||||
);
|
||||
|
||||
await this.sendDataWebhook(Events.MESSAGES_EDITED, editedMessage);
|
||||
const oldMessage = await this.getMessage(editedMessage.key, true);
|
||||
if ((oldMessage as any)?.id) {
|
||||
|
||||
if (Long.isLong(editedMessage?.timestampMs)) {
|
||||
editedMessage.timestampMs = editedMessage.timestampMs?.toNumber();
|
||||
}
|
||||
|
||||
await this.prismaRepository.message.update({
|
||||
where: { id: (oldMessage as any).id },
|
||||
data: {
|
||||
message: editedMessage.editedMessage as any,
|
||||
messageTimestamp: editedMessage.timestampMs,
|
||||
status: 'EDITED',
|
||||
},
|
||||
});
|
||||
await this.prismaRepository.messageUpdate.create({
|
||||
data: {
|
||||
fromMe: editedMessage.key.fromMe,
|
||||
keyId: editedMessage.key.id,
|
||||
remoteJid: editedMessage.key.remoteJid,
|
||||
status: 'EDITED',
|
||||
instanceId: this.instanceId,
|
||||
messageId: (oldMessage as any).id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (received.messageStubParameters && received.messageStubParameters[0] === 'Message absent from node') {
|
||||
this.logger.info(`Recovering message lost messageId: ${received.key.id}`);
|
||||
// if (received.messageStubParameters && received.messageStubParameters[0] === 'Message absent from node') {
|
||||
// this.logger.info(`Recovering message lost messageId: ${received.key.id}`);
|
||||
|
||||
await this.baileysCache.set(received.key.id, {
|
||||
message: received,
|
||||
retry: 0,
|
||||
});
|
||||
// await this.baileysCache.set(received.key.id, {
|
||||
// message: received,
|
||||
// retry: 0,
|
||||
// });
|
||||
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// const retryCache = (await this.baileysCache.get(received.key.id)) || null;
|
||||
|
||||
// if (retryCache) {
|
||||
// this.logger.info('Recovered message lost');
|
||||
// await this.baileysCache.delete(received.key.id);
|
||||
// }
|
||||
|
||||
// Cache to avoid duplicate messages
|
||||
const messageKey = `${this.instance.id}_${received.key.id}`;
|
||||
const cached = await this.baileysCache.get(messageKey);
|
||||
|
||||
if (cached && !editedMessage) {
|
||||
this.logger.info(`Message duplicated ignored: ${received.key.id}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const retryCache = (await this.baileysCache.get(received.key.id)) || null;
|
||||
|
||||
if (retryCache) {
|
||||
this.logger.info('Recovered message lost');
|
||||
await this.baileysCache.delete(received.key.id);
|
||||
}
|
||||
await this.baileysCache.set(messageKey, true, 30 * 60);
|
||||
|
||||
if (
|
||||
(type !== 'notify' && type !== 'append') ||
|
||||
@@ -1185,7 +1226,8 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
existingChat &&
|
||||
received.pushName &&
|
||||
existingChat.name !== received.pushName &&
|
||||
received.pushName.trim().length > 0
|
||||
received.pushName.trim().length > 0 &&
|
||||
!received.key.remoteJid.includes('@g.us')
|
||||
) {
|
||||
this.sendDataWebhook(Events.CHATS_UPSERT, [{ ...existingChat, name: received.pushName }]);
|
||||
if (this.configService.get<Database>('DATABASE').SAVE_DATA.CHATS) {
|
||||
@@ -1421,6 +1463,17 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
continue;
|
||||
}
|
||||
|
||||
const updateKey = `${this.instance.id}_${key.id}_${update.status}`;
|
||||
|
||||
const cached = await this.baileysCache.get(updateKey);
|
||||
|
||||
if (cached) {
|
||||
this.logger.info(`Message duplicated ignored: ${key.id}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
await this.baileysCache.set(updateKey, true, 30 * 60);
|
||||
|
||||
if (status[update.status] === 'READ' && key.fromMe) {
|
||||
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {
|
||||
this.chatwootService.eventWhatsapp(
|
||||
@@ -2716,7 +2769,9 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
|
||||
if (file) mediaData.sticker = file.buffer.toString('base64');
|
||||
|
||||
const convert = await this.convertToWebP(data.sticker);
|
||||
const convert = data?.notConvertSticker
|
||||
? Buffer.from(data.sticker, 'base64')
|
||||
: await this.convertToWebP(data.sticker);
|
||||
const gifPlayback = data.sticker.includes('.gif');
|
||||
const result = await this.sendMessageWithTyping(
|
||||
data.number,
|
||||
@@ -3572,6 +3627,18 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
status: 'DELETED',
|
||||
},
|
||||
});
|
||||
const messageUpdate: any = {
|
||||
messageId: message.id,
|
||||
keyId: messageId,
|
||||
remoteJid: response.key.remoteJid,
|
||||
fromMe: response.key.fromMe,
|
||||
participant: response.key?.remoteJid,
|
||||
status: 'DELETED',
|
||||
instanceId: this.instanceId,
|
||||
};
|
||||
await this.prismaRepository.messageUpdate.create({
|
||||
data: messageUpdate,
|
||||
});
|
||||
} else {
|
||||
await this.prismaRepository.message.deleteMany({
|
||||
where: {
|
||||
@@ -3898,28 +3965,75 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
}
|
||||
|
||||
try {
|
||||
const oldMessage: any = await this.getMessage(data.key, true);
|
||||
if (!oldMessage) throw new NotFoundException('Message not found');
|
||||
if (oldMessage?.key?.remoteJid !== jid) {
|
||||
throw new BadRequestException('RemoteJid does not match');
|
||||
}
|
||||
if (oldMessage?.messageTimestamp > Date.now() + 900000) {
|
||||
// 15 minutes in milliseconds
|
||||
throw new BadRequestException('Message is older than 15 minutes');
|
||||
}
|
||||
|
||||
const messageSent = await this.client.sendMessage(jid, {
|
||||
...(options as any),
|
||||
edit: data.key,
|
||||
});
|
||||
if (messageSent) {
|
||||
const messageId = messageSent.message?.protocolMessage?.key?.id;
|
||||
if (messageId) {
|
||||
let message = await this.prismaRepository.message.findFirst({
|
||||
where: {
|
||||
key: {
|
||||
path: ['id'],
|
||||
equals: messageId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!message) throw new NotFoundException('Message not found');
|
||||
|
||||
const updatedMessage =
|
||||
messageSent.message?.protocolMessage || messageSent.message?.editedMessage?.message?.protocolMessage;
|
||||
if (!(message.key.valueOf() as any).fromMe) {
|
||||
new BadRequestException('You cannot edit others messages');
|
||||
}
|
||||
if ((message.key.valueOf() as any)?.deleted) {
|
||||
new BadRequestException('You cannot edit deleted messages');
|
||||
}
|
||||
if (oldMessage.messageType === 'conversation' || oldMessage.messageType === 'extendedTextMessage') {
|
||||
oldMessage.message.conversation = data.text;
|
||||
} else {
|
||||
oldMessage.message[oldMessage.messageType].caption = data.text;
|
||||
}
|
||||
message = await this.prismaRepository.message.update({
|
||||
where: { id: message.id },
|
||||
data: {
|
||||
message: oldMessage.message,
|
||||
status: 'EDITED',
|
||||
messageTimestamp: Math.floor(Date.now() / 1000), // Convert to int32 by dividing by 1000 to get seconds
|
||||
},
|
||||
});
|
||||
const messageUpdate: any = {
|
||||
messageId: message.id,
|
||||
keyId: messageId,
|
||||
remoteJid: messageSent.key.remoteJid,
|
||||
fromMe: messageSent.key.fromMe,
|
||||
participant: messageSent.key?.remoteJid,
|
||||
status: 'EDITED',
|
||||
instanceId: this.instanceId,
|
||||
};
|
||||
await this.prismaRepository.messageUpdate.create({
|
||||
data: messageUpdate,
|
||||
});
|
||||
|
||||
if (updatedMessage) {
|
||||
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled)
|
||||
this.chatwootService.eventWhatsapp(
|
||||
'send.message.update',
|
||||
{ instanceName: this.instance.name, instanceId: this.instance.id },
|
||||
updatedMessage,
|
||||
);
|
||||
await this.sendDataWebhook(Events.SEND_MESSAGE_UPDATE, updatedMessage);
|
||||
const editedMessage = messageSent?.message?.protocolMessage || messageSent?.message?.editedMessage?.message?.protocolMessage;
|
||||
|
||||
this.sendDataWebhook(Events.SEND_MESSAGE_UPDATE, editedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
return messageSent;
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
throw new BadRequestException(error.toString());
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4548,4 +4662,137 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
//Business Controller
|
||||
public async fetchCatalog(instanceName: string, data: getCollectionsDto) {
|
||||
const jid = data.number ? createJid(data.number) : this.client?.user?.id;
|
||||
const limit = data.limit || 10;
|
||||
const cursor = null;
|
||||
|
||||
const onWhatsapp = (await this.whatsappNumber({ numbers: [jid] }))?.shift();
|
||||
|
||||
if (!onWhatsapp.exists) {
|
||||
throw new BadRequestException(onWhatsapp);
|
||||
}
|
||||
|
||||
try {
|
||||
const info = (await this.whatsappNumber({ numbers: [jid] }))?.shift();
|
||||
const business = await this.fetchBusinessProfile(info?.jid);
|
||||
|
||||
let catalog = await this.getCatalog({ jid: info?.jid, limit, cursor });
|
||||
let nextPageCursor = catalog.nextPageCursor;
|
||||
let nextPageCursorJson = nextPageCursor ? JSON.parse(atob(nextPageCursor)) : null;
|
||||
let pagination = nextPageCursorJson?.pagination_cursor
|
||||
? JSON.parse(atob(nextPageCursorJson.pagination_cursor))
|
||||
: null;
|
||||
let fetcherHasMore = pagination?.fetcher_has_more === true ? true : false;
|
||||
|
||||
let productsCatalog = catalog.products || [];
|
||||
let countLoops = 0;
|
||||
while (fetcherHasMore && countLoops < 4) {
|
||||
catalog = await this.getCatalog({ jid: info?.jid, limit, cursor: nextPageCursor });
|
||||
nextPageCursor = catalog.nextPageCursor;
|
||||
nextPageCursorJson = nextPageCursor ? JSON.parse(atob(nextPageCursor)) : null;
|
||||
pagination = nextPageCursorJson?.pagination_cursor
|
||||
? JSON.parse(atob(nextPageCursorJson.pagination_cursor))
|
||||
: null;
|
||||
fetcherHasMore = pagination?.fetcher_has_more === true ? true : false;
|
||||
productsCatalog = [...productsCatalog, ...catalog.products];
|
||||
countLoops++;
|
||||
}
|
||||
|
||||
return {
|
||||
wuid: info?.jid || jid,
|
||||
numberExists: info?.exists,
|
||||
isBusiness: business.isBusiness,
|
||||
catalogLength: productsCatalog.length,
|
||||
catalog: productsCatalog,
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return {
|
||||
wuid: jid,
|
||||
name: null,
|
||||
isBusiness: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public async getCatalog({
|
||||
jid,
|
||||
limit,
|
||||
cursor,
|
||||
}: GetCatalogOptions): Promise<{ products: Product[]; nextPageCursor: string | undefined }> {
|
||||
try {
|
||||
jid = jid ? createJid(jid) : this.instance.wuid;
|
||||
|
||||
const catalog = await this.client.getCatalog({ jid, limit: limit, cursor: cursor });
|
||||
|
||||
if (!catalog) {
|
||||
return {
|
||||
products: undefined,
|
||||
nextPageCursor: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return catalog;
|
||||
} catch (error) {
|
||||
throw new InternalServerErrorException('Error getCatalog', error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public async fetchCollections(instanceName: string, data: getCollectionsDto) {
|
||||
const jid = data.number ? createJid(data.number) : this.client?.user?.id;
|
||||
const limit = data.limit <= 20 ? data.limit : 20; //(tem esse limite, não sei porque)
|
||||
|
||||
const onWhatsapp = (await this.whatsappNumber({ numbers: [jid] }))?.shift();
|
||||
|
||||
if (!onWhatsapp.exists) {
|
||||
throw new BadRequestException(onWhatsapp);
|
||||
}
|
||||
|
||||
try {
|
||||
const info = (await this.whatsappNumber({ numbers: [jid] }))?.shift();
|
||||
const business = await this.fetchBusinessProfile(info?.jid);
|
||||
const collections = await this.getCollections(info?.jid, limit);
|
||||
|
||||
return {
|
||||
wuid: info?.jid || jid,
|
||||
name: info?.name,
|
||||
numberExists: info?.exists,
|
||||
isBusiness: business.isBusiness,
|
||||
collectionsLength: collections?.length,
|
||||
collections: collections,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
wuid: jid,
|
||||
name: null,
|
||||
isBusiness: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public async getCollections(jid?: string | undefined, limit?: number): Promise<CatalogCollection[]> {
|
||||
try {
|
||||
jid = jid ? createJid(jid) : this.instance.wuid;
|
||||
|
||||
const result = await this.client.getCollections(jid, limit);
|
||||
|
||||
if (!result) {
|
||||
return [
|
||||
{
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
products: [],
|
||||
status: undefined,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return result.collections;
|
||||
} catch (error) {
|
||||
throw new InternalServerErrorException('Error getCatalog', error.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user