mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-07-14 01:41:24 -06:00
feat: added reply, delete and message reaction in chatwoot v3.3.1
This commit is contained in:
parent
3e904aa160
commit
ae66be197e
@ -6,6 +6,7 @@
|
||||
* Added endpoint sendPresence
|
||||
* New Instance Manager
|
||||
* Added auto_create to the chatwoot set to create the inbox automatically or not
|
||||
* Added reply, delete and message reaction in chatwoot v3.3.1
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -5,13 +5,18 @@ import { Logger } from '../../config/logger.config';
|
||||
import { BadRequestException } from '../../exceptions';
|
||||
import { ChatwootDto } from '../dto/chatwoot.dto';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { RepositoryBroker } from '../repository/repository.manager';
|
||||
import { ChatwootService } from '../services/chatwoot.service';
|
||||
import { waMonitor } from '../whatsapp.module';
|
||||
|
||||
const logger = new Logger('ChatwootController');
|
||||
|
||||
export class ChatwootController {
|
||||
constructor(private readonly chatwootService: ChatwootService, private readonly configService: ConfigService) {}
|
||||
constructor(
|
||||
private readonly chatwootService: ChatwootService,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly repository: RepositoryBroker,
|
||||
) {}
|
||||
|
||||
public async createChatwoot(instance: InstanceDto, data: ChatwootDto) {
|
||||
logger.verbose('requested createChatwoot from ' + instance.instanceName + ' instance');
|
||||
@ -87,7 +92,7 @@ export class ChatwootController {
|
||||
|
||||
public async receiveWebhook(instance: InstanceDto, data: any) {
|
||||
logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance');
|
||||
const chatwootService = new ChatwootService(waMonitor, this.configService);
|
||||
const chatwootService = new ChatwootService(waMonitor, this.configService, this.repository);
|
||||
|
||||
return chatwootService.receiveWebhook(instance, data);
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ export class MessageRaw {
|
||||
source?: 'android' | 'web' | 'ios';
|
||||
source_id?: string;
|
||||
source_reply_id?: string;
|
||||
chatwootMessageId?: string;
|
||||
}
|
||||
|
||||
const messageSchema = new Schema<MessageRaw>({
|
||||
@ -39,6 +40,7 @@ const messageSchema = new Schema<MessageRaw>({
|
||||
source: { type: String, minlength: 3, enum: ['android', 'web', 'ios'] },
|
||||
messageTimestamp: { type: Number, required: true },
|
||||
owner: { type: String, required: true, minlength: 1 },
|
||||
chatwootMessageId: { type: String, required: false },
|
||||
});
|
||||
|
||||
export const MessageModel = dbserver?.model(MessageRaw.name, messageSchema, 'messages');
|
||||
|
@ -144,4 +144,55 @@ export class MessageRepository extends Repository {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async update(data: MessageRaw[], instanceName: string, saveDb?: boolean): Promise<IInsert> {
|
||||
try {
|
||||
if (this.dbSettings.ENABLED && saveDb) {
|
||||
this.logger.verbose('updating messages in db');
|
||||
|
||||
const messages = data.map((message) => {
|
||||
return {
|
||||
updateOne: {
|
||||
filter: { 'key.id': message.key.id },
|
||||
update: { ...message },
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const { nModified } = await this.messageModel.bulkWrite(messages);
|
||||
|
||||
this.logger.verbose('messages updated in db: ' + nModified + ' messages');
|
||||
return { insertCount: nModified };
|
||||
}
|
||||
|
||||
this.logger.verbose('updating messages in store');
|
||||
|
||||
const store = this.configService.get<StoreConf>('STORE');
|
||||
|
||||
if (store.MESSAGES) {
|
||||
this.logger.verbose('updating messages in store');
|
||||
data.forEach((message) => {
|
||||
this.writeStore({
|
||||
path: join(this.storePath, 'messages', instanceName),
|
||||
fileName: message.key.id,
|
||||
data: message,
|
||||
});
|
||||
this.logger.verbose(
|
||||
'messages updated in store in path: ' +
|
||||
join(this.storePath, 'messages', instanceName) +
|
||||
'/' +
|
||||
message.key.id,
|
||||
);
|
||||
});
|
||||
|
||||
this.logger.verbose('messages updated in store: ' + data.length + ' messages');
|
||||
return { insertCount: data.length };
|
||||
}
|
||||
|
||||
this.logger.verbose('messages not updated');
|
||||
return { insertCount: 0 };
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,9 @@ import { Logger } from '../../config/logger.config';
|
||||
import { ROOT_DIR } from '../../config/path.config';
|
||||
import { ChatwootDto } from '../dto/chatwoot.dto';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { SendAudioDto, SendMediaDto, SendTextDto } from '../dto/sendMessage.dto';
|
||||
import { Options, Quoted, SendAudioDto, SendMediaDto, SendTextDto } from '../dto/sendMessage.dto';
|
||||
import { MessageRaw } from '../models';
|
||||
import { RepositoryBroker } from '../repository/repository.manager';
|
||||
import { WAMonitoringService } from './monitor.service';
|
||||
|
||||
export class ChatwootService {
|
||||
@ -22,7 +24,11 @@ export class ChatwootService {
|
||||
|
||||
private provider: any;
|
||||
|
||||
constructor(private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService) {
|
||||
constructor(
|
||||
private readonly waMonitor: WAMonitoringService,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly repository: RepositoryBroker,
|
||||
) {
|
||||
this.messageCache = new Set();
|
||||
}
|
||||
|
||||
@ -640,6 +646,7 @@ export class ChatwootService {
|
||||
encoding: string;
|
||||
filename: string;
|
||||
}[],
|
||||
messageBody?: any,
|
||||
) {
|
||||
this.logger.verbose('create message to instance: ' + instance.instanceName);
|
||||
|
||||
@ -650,6 +657,8 @@ export class ChatwootService {
|
||||
return null;
|
||||
}
|
||||
|
||||
const replyToIds = await this.getReplyToIds(messageBody, instance);
|
||||
|
||||
this.logger.verbose('create message in chatwoot');
|
||||
const message = await client.messages.create({
|
||||
accountId: this.provider.account_id,
|
||||
@ -659,6 +668,9 @@ export class ChatwootService {
|
||||
message_type: messageType,
|
||||
attachments: attachments,
|
||||
private: privateMessage || false,
|
||||
content_attributes: {
|
||||
...replyToIds,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -754,6 +766,8 @@ export class ChatwootService {
|
||||
file: string,
|
||||
messageType: 'incoming' | 'outgoing' | undefined,
|
||||
content?: string,
|
||||
instance?: InstanceDto,
|
||||
messageBody?: any,
|
||||
) {
|
||||
this.logger.verbose('send data to chatwoot');
|
||||
|
||||
@ -770,6 +784,16 @@ export class ChatwootService {
|
||||
this.logger.verbose('temp file found');
|
||||
data.append('attachments[]', createReadStream(file));
|
||||
|
||||
if (messageBody && instance) {
|
||||
const replyToIds = await this.getReplyToIds(messageBody, instance);
|
||||
|
||||
if (replyToIds.in_reply_to || replyToIds.in_reply_to_external_id) {
|
||||
data.append('content_attributes', {
|
||||
...replyToIds,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.verbose('get client to instance: ' + this.provider.instanceName);
|
||||
const config = {
|
||||
method: 'post',
|
||||
@ -890,7 +914,7 @@ export class ChatwootService {
|
||||
}
|
||||
}
|
||||
|
||||
public async sendAttachment(waInstance: any, number: string, media: any, caption?: string) {
|
||||
public async sendAttachment(waInstance: any, number: string, media: any, caption?: string, options?: Options) {
|
||||
this.logger.verbose('send attachment to instance: ' + waInstance.instanceName);
|
||||
|
||||
try {
|
||||
@ -932,13 +956,14 @@ export class ChatwootService {
|
||||
options: {
|
||||
delay: 1200,
|
||||
presence: 'recording',
|
||||
...options,
|
||||
},
|
||||
};
|
||||
|
||||
await waInstance?.audioWhatsapp(data, true);
|
||||
const messageSent = await waInstance?.audioWhatsapp(data, true);
|
||||
|
||||
this.logger.verbose('audio sent');
|
||||
return;
|
||||
return messageSent;
|
||||
}
|
||||
|
||||
this.logger.verbose('send media to instance: ' + waInstance.instanceName);
|
||||
@ -952,6 +977,7 @@ export class ChatwootService {
|
||||
options: {
|
||||
delay: 1200,
|
||||
presence: 'composing',
|
||||
...options,
|
||||
},
|
||||
};
|
||||
|
||||
@ -960,10 +986,10 @@ export class ChatwootService {
|
||||
data.mediaMessage.caption = caption;
|
||||
}
|
||||
|
||||
await waInstance?.mediaMessage(data, true);
|
||||
const messageSent = await waInstance?.mediaMessage(data, true);
|
||||
|
||||
this.logger.verbose('media sent');
|
||||
return;
|
||||
return messageSent;
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
@ -982,7 +1008,13 @@ export class ChatwootService {
|
||||
}
|
||||
|
||||
this.logger.verbose('check if is bot');
|
||||
if (!body?.conversation || body.private || body.event === 'message_updated') return { message: 'bot' };
|
||||
if (
|
||||
!body?.conversation ||
|
||||
body.private ||
|
||||
(body.event === 'message_updated' && !body.content_attributes?.deleted)
|
||||
) {
|
||||
return { message: 'bot' };
|
||||
}
|
||||
|
||||
this.logger.verbose('check if is group');
|
||||
const chatId =
|
||||
@ -991,6 +1023,21 @@ export class ChatwootService {
|
||||
const senderName = body?.sender?.name;
|
||||
const waInstance = this.waMonitor.waInstances[instance.instanceName];
|
||||
|
||||
this.logger.verbose('check if is a message deletion');
|
||||
if (body.event === 'message_updated' && body.content_attributes?.deleted) {
|
||||
const message = await this.repository.message.find({
|
||||
where: {
|
||||
owner: instance.instanceName,
|
||||
chatwootMessageId: body.id,
|
||||
},
|
||||
limit: 1,
|
||||
});
|
||||
if (message.length && message[0].key?.id) {
|
||||
await waInstance?.client.sendMessage(message[0].key.remoteJid, { delete: message[0].key });
|
||||
}
|
||||
return { message: 'bot' };
|
||||
}
|
||||
|
||||
if (chatId === '123456' && body.message_type === 'outgoing') {
|
||||
this.logger.verbose('check if is command');
|
||||
|
||||
@ -1082,7 +1129,26 @@ export class ChatwootService {
|
||||
formatText = null;
|
||||
}
|
||||
|
||||
await this.sendAttachment(waInstance, chatId, attachment.data_url, formatText);
|
||||
const options: Options = {
|
||||
quoted: await this.getQuotedMessage(body, instance),
|
||||
};
|
||||
|
||||
const messageSent = await this.sendAttachment(
|
||||
waInstance,
|
||||
chatId,
|
||||
attachment.data_url,
|
||||
formatText,
|
||||
options,
|
||||
);
|
||||
|
||||
this.updateChatwootMessageId(
|
||||
{
|
||||
...messageSent,
|
||||
owner: instance.instanceName,
|
||||
},
|
||||
body.id,
|
||||
instance,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.logger.verbose('message is text');
|
||||
@ -1096,10 +1162,20 @@ export class ChatwootService {
|
||||
options: {
|
||||
delay: 1200,
|
||||
presence: 'composing',
|
||||
quoted: await this.getQuotedMessage(body, instance),
|
||||
},
|
||||
};
|
||||
|
||||
await waInstance?.textMessage(data, true);
|
||||
const messageSent = await waInstance?.textMessage(data, true);
|
||||
|
||||
this.updateChatwootMessageId(
|
||||
{
|
||||
...messageSent,
|
||||
owner: instance.instanceName,
|
||||
},
|
||||
body.id,
|
||||
instance,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1131,6 +1207,65 @@ export class ChatwootService {
|
||||
}
|
||||
}
|
||||
|
||||
private updateChatwootMessageId(message: MessageRaw, chatwootMessageId: string, instance: InstanceDto) {
|
||||
if (!chatwootMessageId) {
|
||||
return;
|
||||
}
|
||||
message.chatwootMessageId = chatwootMessageId;
|
||||
this.repository.message.update([message], instance.instanceName, true);
|
||||
}
|
||||
|
||||
private async getReplyToIds(
|
||||
msg: any,
|
||||
instance: InstanceDto,
|
||||
): Promise<{ in_reply_to: string; in_reply_to_external_id: string }> {
|
||||
let inReplyTo = null;
|
||||
let inReplyToExternalId = null;
|
||||
|
||||
if (msg) {
|
||||
inReplyToExternalId = msg.message?.extendedTextMessage?.contextInfo?.stanzaId;
|
||||
if (inReplyToExternalId) {
|
||||
const message = await this.repository.message.find({
|
||||
where: {
|
||||
key: {
|
||||
id: inReplyToExternalId,
|
||||
},
|
||||
owner: instance.instanceName,
|
||||
},
|
||||
limit: 1,
|
||||
});
|
||||
if (message.length && message[0]?.chatwootMessageId) {
|
||||
inReplyTo = message[0].chatwootMessageId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
in_reply_to: inReplyTo,
|
||||
in_reply_to_external_id: inReplyToExternalId,
|
||||
};
|
||||
}
|
||||
|
||||
private async getQuotedMessage(msg: any, instance: InstanceDto): Promise<Quoted> {
|
||||
if (msg?.content_attributes?.in_reply_to) {
|
||||
const message = await this.repository.message.find({
|
||||
where: {
|
||||
chatwootMessageId: msg?.content_attributes?.in_reply_to,
|
||||
owner: instance.instanceName,
|
||||
},
|
||||
limit: 1,
|
||||
});
|
||||
if (message.length && message[0]?.key?.id) {
|
||||
return {
|
||||
key: message[0].key,
|
||||
message: message[0].message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private isMediaMessage(message: any) {
|
||||
this.logger.verbose('check if is media message');
|
||||
const media = [
|
||||
@ -1164,6 +1299,18 @@ export class ChatwootService {
|
||||
return adsMessage;
|
||||
}
|
||||
|
||||
private getReactionMessage(msg: any) {
|
||||
interface ReactionMessage {
|
||||
key: MessageRaw['key'];
|
||||
text: string;
|
||||
}
|
||||
const reactionMessage: ReactionMessage | undefined = msg?.reactionMessage;
|
||||
|
||||
this.logger.verbose('Get reaction message if it exists');
|
||||
reactionMessage && this.logger.verbose('Reaction message: ' + reactionMessage);
|
||||
return reactionMessage;
|
||||
}
|
||||
|
||||
private getTypeMessage(msg: any) {
|
||||
this.logger.verbose('get type message');
|
||||
|
||||
@ -1319,7 +1466,9 @@ export class ChatwootService {
|
||||
|
||||
const adsMessage = this.getAdsMessage(body.message);
|
||||
|
||||
if (!bodyMessage && !isMedia) {
|
||||
const reactionMessage = this.getReactionMessage(body.message);
|
||||
|
||||
if (!bodyMessage && !isMedia && !reactionMessage) {
|
||||
this.logger.warn('no body message found');
|
||||
return;
|
||||
}
|
||||
@ -1378,7 +1527,7 @@ export class ChatwootService {
|
||||
}
|
||||
|
||||
this.logger.verbose('send data to chatwoot');
|
||||
const send = await this.sendData(getConversation, fileName, messageType, content);
|
||||
const send = await this.sendData(getConversation, fileName, messageType, content, instance, body);
|
||||
|
||||
if (!send) {
|
||||
this.logger.warn('message not sent');
|
||||
@ -1399,7 +1548,7 @@ export class ChatwootService {
|
||||
this.logger.verbose('message is not group');
|
||||
|
||||
this.logger.verbose('send data to chatwoot');
|
||||
const send = await this.sendData(getConversation, fileName, messageType, bodyMessage);
|
||||
const send = await this.sendData(getConversation, fileName, messageType, bodyMessage, instance, body);
|
||||
|
||||
if (!send) {
|
||||
this.logger.warn('message not sent');
|
||||
@ -1419,6 +1568,35 @@ export class ChatwootService {
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.verbose('check if has ReactionMessage');
|
||||
if (reactionMessage) {
|
||||
this.logger.verbose('send data to chatwoot');
|
||||
if (reactionMessage.text) {
|
||||
const send = await this.createMessage(
|
||||
instance,
|
||||
getConversation,
|
||||
reactionMessage.text,
|
||||
messageType,
|
||||
false,
|
||||
[],
|
||||
{
|
||||
message: { extendedTextMessage: { contextInfo: { stanzaId: reactionMessage.key.id } } },
|
||||
},
|
||||
);
|
||||
if (!send) {
|
||||
this.logger.warn('message not sent');
|
||||
return;
|
||||
}
|
||||
this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`);
|
||||
this.messageCache = this.loadMessageCache();
|
||||
this.messageCache.add(send.id.toString());
|
||||
this.logger.verbose('save message cache');
|
||||
this.saveMessageCache();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.verbose('check if has Ads Message');
|
||||
if (adsMessage) {
|
||||
this.logger.verbose('message is from Ads');
|
||||
@ -1461,6 +1639,8 @@ export class ChatwootService {
|
||||
fileName,
|
||||
messageType,
|
||||
`${bodyMessage}\n\n\n**${title}**\n${description}\n${adsMessage.sourceUrl}`,
|
||||
instance,
|
||||
body,
|
||||
);
|
||||
|
||||
if (!send) {
|
||||
@ -1496,7 +1676,7 @@ export class ChatwootService {
|
||||
}
|
||||
|
||||
this.logger.verbose('send data to chatwoot');
|
||||
const send = await this.createMessage(instance, getConversation, content, messageType);
|
||||
const send = await this.createMessage(instance, getConversation, content, messageType, false, [], body);
|
||||
|
||||
if (!send) {
|
||||
this.logger.warn('message not sent');
|
||||
@ -1517,7 +1697,7 @@ export class ChatwootService {
|
||||
this.logger.verbose('message is not group');
|
||||
|
||||
this.logger.verbose('send data to chatwoot');
|
||||
const send = await this.createMessage(instance, getConversation, bodyMessage, messageType);
|
||||
const send = await this.createMessage(instance, getConversation, bodyMessage, messageType, false, [], body);
|
||||
|
||||
if (!send) {
|
||||
this.logger.warn('message not sent');
|
||||
|
@ -166,7 +166,7 @@ export class WAStartupService {
|
||||
|
||||
private phoneNumber: string;
|
||||
|
||||
private chatwootService = new ChatwootService(waMonitor, this.configService);
|
||||
private chatwootService = new ChatwootService(waMonitor, this.configService, this.repository);
|
||||
|
||||
private typebotService = new TypebotService(waMonitor, this.configService);
|
||||
|
||||
@ -1778,11 +1778,15 @@ export class WAStartupService {
|
||||
this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw);
|
||||
|
||||
if (this.localChatwoot.enabled) {
|
||||
await this.chatwootService.eventWhatsapp(
|
||||
const chatwootSentMessage = await this.chatwootService.eventWhatsapp(
|
||||
Events.MESSAGES_UPSERT,
|
||||
{ instanceName: this.instance.name },
|
||||
messageRaw,
|
||||
);
|
||||
|
||||
if (chatwootSentMessage?.id) {
|
||||
messageRaw.chatwootMessageId = chatwootSentMessage.id;
|
||||
}
|
||||
}
|
||||
|
||||
const typebotSessionRemoteJid = this.localTypebot.sessions?.find(
|
||||
|
@ -129,9 +129,9 @@ const sqsService = new SqsService(waMonitor);
|
||||
|
||||
export const sqsController = new SqsController(sqsService);
|
||||
|
||||
const chatwootService = new ChatwootService(waMonitor, configService);
|
||||
const chatwootService = new ChatwootService(waMonitor, configService, repository);
|
||||
|
||||
export const chatwootController = new ChatwootController(chatwootService, configService);
|
||||
export const chatwootController = new ChatwootController(chatwootService, configService, repository);
|
||||
|
||||
const settingsService = new SettingsService(waMonitor);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user