mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-12-19 03:42:23 -06:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1665654676 | ||
|
|
133eddd742 | ||
|
|
c55312d206 | ||
|
|
52216ec08e | ||
|
|
7a01cdf0ef | ||
|
|
65a9c78d86 | ||
|
|
fbccf2eb2a | ||
|
|
23640a71b8 | ||
|
|
fce3e55e91 | ||
|
|
9f39ec2110 | ||
|
|
89c4c194df | ||
|
|
a4e7baa41c | ||
|
|
e22ff6c0d9 | ||
|
|
11d31123ac | ||
|
|
0fdc47e8f0 | ||
|
|
60db8081bd | ||
|
|
37b003f169 | ||
|
|
891c3eb5d3 | ||
|
|
3b99699f1a | ||
|
|
e1de70542b | ||
|
|
c10680df41 | ||
|
|
171f460f3b | ||
|
|
6d0ad5f3db | ||
|
|
f34115fdcb | ||
|
|
f9705c07dc | ||
|
|
e986768716 | ||
|
|
34769e2293 | ||
|
|
5401ecd2c4 |
@@ -248,6 +248,10 @@ S3_USE_SSL=true
|
||||
# S3_USE_SSL=true
|
||||
# S3_REGION=eu-south
|
||||
|
||||
# Evolution Audio Converter - Environment variables - https://github.com/EvolutionAPI/evolution-audio-converter
|
||||
# API_AUDIO_CONVERTER=http://localhost:4040/process-audio
|
||||
# API_AUDIO_CONVERTER_KEY=429683C4C977415CAAFCCE10F7D57E11
|
||||
|
||||
# Define a global apikey to access all instances.
|
||||
# OBS: This key must be inserted in the request header to create an instance.
|
||||
AUTHENTICATION_API_KEY=429683C4C977415CAAFCCE10F7D57E11
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
* Added unreadMessages to chats
|
||||
* Pusher event integration
|
||||
* Add support for splitMessages and timePerChar in Integrations
|
||||
* Audio Converter via API
|
||||
* Send PTV messages with Baileys
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -19,6 +21,8 @@
|
||||
* Add indexes to improve performance in Evolution
|
||||
* Add logical or permanent message deletion based on env config
|
||||
* Add support for fetching multiple instances by key
|
||||
* Update instance.controller.ts to filter by instanceName
|
||||
* Receive template button reply message
|
||||
|
||||
# 2.1.2 (2024-10-06 10:09)
|
||||
|
||||
|
||||
@@ -75,10 +75,6 @@ To continuously improve our services, we have implemented telemetry that collect
|
||||
Join our Evolution Pro community for expert support and a weekly call to answer questions. Visit the link below to learn more and subscribe:
|
||||
|
||||
[Click here to learn more](https://evolution-api.com/suporte-pro)
|
||||
<br>
|
||||
<a href="https://evolution-api.com/suporte-pro">
|
||||
<img src="./public/images/evolution-pro.png" alt="Subscribe" width="600">
|
||||
</a>
|
||||
|
||||
# Donate to the project.
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
services:
|
||||
api:
|
||||
container_name: evolution_api
|
||||
image: atendai/evolution-api:v2.0.9-rc
|
||||
image: atendai/evolution-api:homolog
|
||||
restart: always
|
||||
depends_on:
|
||||
- redis
|
||||
- postgres
|
||||
ports:
|
||||
- 8080:8080
|
||||
volumes:
|
||||
@@ -14,8 +17,38 @@ services:
|
||||
expose:
|
||||
- 8080
|
||||
|
||||
redis:
|
||||
image: redis:latest
|
||||
networks:
|
||||
- evolution-net
|
||||
container_name: redis
|
||||
command: >
|
||||
redis-server --port 6379 --appendonly yes
|
||||
volumes:
|
||||
- evolution_redis:/data
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
postgres:
|
||||
container_name: postgres
|
||||
image: postgres:15
|
||||
networks:
|
||||
- evolution-net
|
||||
command: ["postgres", "-c", "max_connections=1000"]
|
||||
restart: always
|
||||
ports:
|
||||
- 5432:5432
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=PASSWORD
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
expose:
|
||||
- 5432
|
||||
|
||||
volumes:
|
||||
evolution_instances:
|
||||
evolution_redis:
|
||||
postgres_data:
|
||||
|
||||
|
||||
networks:
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
"build": "tsc --noEmit && tsup",
|
||||
"start": "tsnd -r tsconfig-paths/register --files --transpile-only ./src/main.ts",
|
||||
"start:prod": "node dist/main",
|
||||
"dev:server": "clear && tsnd -r tsconfig-paths/register --files --transpile-only --respawn --ignore-watch node_modules ./src/main.ts",
|
||||
"test": "clear && tsnd -r tsconfig-paths/register --files --transpile-only --respawn --ignore-watch node_modules ./test/all.test.ts",
|
||||
"dev:server": "tsnd -r tsconfig-paths/register --files --transpile-only --respawn --ignore-watch node_modules ./src/main.ts",
|
||||
"test": "tsnd -r tsconfig-paths/register --files --transpile-only --respawn --ignore-watch node_modules ./test/all.test.ts",
|
||||
"lint": "eslint --fix --ext .ts src",
|
||||
"db:generate": "node runWithProvider.js \"npx prisma generate --schema ./prisma/DATABASE_PROVIDER-schema.prisma\"",
|
||||
"db:deploy": "node runWithProvider.js \"rm -rf ./prisma/migrations && cp -r ./prisma/DATABASE_PROVIDER-migrations ./prisma/migrations && npx prisma migrate deploy --schema ./prisma/DATABASE_PROVIDER-schema.prisma\"",
|
||||
@@ -73,6 +73,7 @@
|
||||
"jsonschema": "^1.4.1",
|
||||
"link-preview-js": "^3.0.4",
|
||||
"long": "^5.2.3",
|
||||
"mediainfo.js": "^0.3.2",
|
||||
"mime": "^3.0.0",
|
||||
"minio": "^8.0.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 25 KiB |
@@ -382,7 +382,9 @@ export class InstanceController {
|
||||
return this.waMonitor.instanceInfoById(instanceId, number);
|
||||
}
|
||||
|
||||
return this.waMonitor.instanceInfo();
|
||||
const instanceNames = instanceName ? [instanceName] : null;
|
||||
|
||||
return this.waMonitor.instanceInfo(instanceNames);
|
||||
}
|
||||
|
||||
public async setPresence({ instanceName }: InstanceDto, data: SetPresenceDto) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
SendLocationDto,
|
||||
SendMediaDto,
|
||||
SendPollDto,
|
||||
SendPtvDto,
|
||||
SendReactionDto,
|
||||
SendStatusDto,
|
||||
SendStickerDto,
|
||||
@@ -39,6 +40,13 @@ export class SendMessageController {
|
||||
throw new BadRequestException('Owned media must be a url or base64');
|
||||
}
|
||||
|
||||
public async sendPtv({ instanceName }: InstanceDto, data: SendPtvDto, file?: any) {
|
||||
if (file || isURL(data?.video) || isBase64(data?.video)) {
|
||||
return await this.waMonitor.waInstances[instanceName].ptvMessage(data, file);
|
||||
}
|
||||
throw new BadRequestException('Owned media must be a url or base64');
|
||||
}
|
||||
|
||||
public async sendSticker({ instanceName }: InstanceDto, data: SendStickerDto, file?: any) {
|
||||
if (file || isURL(data.sticker) || isBase64(data.sticker)) {
|
||||
return await this.waMonitor.waInstances[instanceName].mediaSticker(data, file);
|
||||
|
||||
@@ -70,7 +70,7 @@ export class SendPollDto extends Metadata {
|
||||
messageSecret?: Uint8Array;
|
||||
}
|
||||
|
||||
export type MediaType = 'image' | 'document' | 'video' | 'audio';
|
||||
export type MediaType = 'image' | 'document' | 'video' | 'audio' | 'ptv';
|
||||
|
||||
export class SendMediaDto extends Metadata {
|
||||
mediatype: MediaType;
|
||||
@@ -82,6 +82,10 @@ export class SendMediaDto extends Metadata {
|
||||
media: string;
|
||||
}
|
||||
|
||||
export class SendPtvDto extends Metadata {
|
||||
video: string;
|
||||
}
|
||||
|
||||
export class SendStickerDto extends Metadata {
|
||||
sticker: string;
|
||||
}
|
||||
@@ -90,15 +94,21 @@ export class SendAudioDto extends Metadata {
|
||||
audio: string;
|
||||
}
|
||||
|
||||
export type TypeButton = 'reply' | 'copy' | 'url' | 'call';
|
||||
export type TypeButton = 'reply' | 'copy' | 'url' | 'call' | 'pix';
|
||||
|
||||
export type KeyType = 'phone' | 'email' | 'cpf' | 'cnpj' | 'random';
|
||||
|
||||
export class Button {
|
||||
type: TypeButton;
|
||||
displayText: string;
|
||||
displayText?: string;
|
||||
id?: string;
|
||||
url?: string;
|
||||
copyCode?: string;
|
||||
phoneNumber?: string;
|
||||
currency?: string;
|
||||
name?: string;
|
||||
keyType?: KeyType;
|
||||
key?: string;
|
||||
}
|
||||
|
||||
export class SendButtonsDto extends Metadata {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { NumberBusiness } from '@api/dto/chat.dto';
|
||||
import {
|
||||
Button,
|
||||
ContactMessage,
|
||||
MediaMessage,
|
||||
Options,
|
||||
@@ -13,7 +12,6 @@ import {
|
||||
SendReactionDto,
|
||||
SendTemplateDto,
|
||||
SendTextDto,
|
||||
TypeButton,
|
||||
} from '@api/dto/sendMessage.dto';
|
||||
import * as s3Service from '@api/integrations/storage/s3/libs/minio.server';
|
||||
import { ProviderFiles } from '@api/provider/sessions';
|
||||
@@ -26,14 +24,12 @@ import { Chatwoot, ConfigService, Database, Openai, S3, WaBusiness } from '@conf
|
||||
import { BadRequestException, InternalServerErrorException } from '@exceptions';
|
||||
import { status } from '@utils/renderStatus';
|
||||
import axios from 'axios';
|
||||
import { proto } from 'baileys';
|
||||
import { arrayUnique, isURL } from 'class-validator';
|
||||
import EventEmitter2 from 'eventemitter2';
|
||||
import FormData from 'form-data';
|
||||
import { createReadStream } from 'fs';
|
||||
import mime from 'mime';
|
||||
import { join } from 'path';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export class BusinessStartupService extends ChannelStartupService {
|
||||
constructor(
|
||||
@@ -1112,97 +1108,42 @@ export class BusinessStartupService extends ChannelStartupService {
|
||||
return audioSent;
|
||||
}
|
||||
|
||||
private toJSONString(button: Button): string {
|
||||
const toString = (obj: any) => JSON.stringify(obj);
|
||||
|
||||
const json = {
|
||||
call: () => toString({ display_text: button.displayText, phone_number: button.phoneNumber }),
|
||||
reply: () => toString({ display_text: button.displayText, id: button.id }),
|
||||
copy: () => toString({ display_text: button.displayText, copy_code: button.copyCode }),
|
||||
url: () =>
|
||||
toString({
|
||||
display_text: button.displayText,
|
||||
url: button.url,
|
||||
merchant_url: button.url,
|
||||
}),
|
||||
};
|
||||
|
||||
return json[button.type]?.() || '';
|
||||
}
|
||||
|
||||
private readonly mapType = new Map<TypeButton, string>([
|
||||
['reply', 'quick_reply'],
|
||||
['copy', 'cta_copy'],
|
||||
['url', 'cta_url'],
|
||||
['call', 'cta_call'],
|
||||
]);
|
||||
|
||||
public async buttonMessage(data: SendButtonsDto) {
|
||||
const generate = await (async () => {
|
||||
if (data?.thumbnailUrl) {
|
||||
return await this.prepareMediaMessage({
|
||||
mediatype: 'image',
|
||||
media: data.thumbnailUrl,
|
||||
});
|
||||
}
|
||||
})();
|
||||
const embeddedMedia: any = {};
|
||||
|
||||
const buttons = data.buttons.map((value) => {
|
||||
return {
|
||||
name: this.mapType.get(value.type),
|
||||
buttonParamsJson: this.toJSONString(value),
|
||||
};
|
||||
});
|
||||
|
||||
const message: proto.IMessage = {
|
||||
viewOnceMessage: {
|
||||
message: {
|
||||
messageContextInfo: {
|
||||
deviceListMetadata: {},
|
||||
deviceListMetadataVersion: 2,
|
||||
},
|
||||
interactiveMessage: {
|
||||
body: {
|
||||
text: (() => {
|
||||
let t = '*' + data.title + '*';
|
||||
if (data?.description) {
|
||||
t += '\n\n';
|
||||
t += data.description;
|
||||
t += '\n';
|
||||
}
|
||||
return t;
|
||||
})(),
|
||||
},
|
||||
footer: {
|
||||
text: data?.footer,
|
||||
},
|
||||
header: (() => {
|
||||
if (generate?.message?.imageMessage) {
|
||||
return {
|
||||
hasMediaAttachment: !!generate.message.imageMessage,
|
||||
imageMessage: generate.message.imageMessage,
|
||||
};
|
||||
}
|
||||
})(),
|
||||
nativeFlowMessage: {
|
||||
buttons: buttons,
|
||||
messageParamsJson: JSON.stringify({
|
||||
from: 'api',
|
||||
templateId: v4(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
const btnItems = {
|
||||
text: data.buttons.map((btn) => btn.displayText),
|
||||
ids: data.buttons.map((btn) => btn.id),
|
||||
};
|
||||
|
||||
return await this.sendMessageWithTyping(data.number, message, {
|
||||
delay: data?.delay,
|
||||
presence: 'composing',
|
||||
quoted: data?.quoted,
|
||||
mentionsEveryOne: data?.mentionsEveryOne,
|
||||
mentioned: data?.mentioned,
|
||||
});
|
||||
if (!arrayUnique(btnItems.text) || !arrayUnique(btnItems.ids)) {
|
||||
throw new BadRequestException('Button texts cannot be repeated', 'Button IDs cannot be repeated.');
|
||||
}
|
||||
|
||||
return await this.sendMessageWithTyping(
|
||||
data.number,
|
||||
{
|
||||
text: !embeddedMedia?.mediaKey ? data.title : undefined,
|
||||
buttons: data.buttons.map((button) => {
|
||||
return {
|
||||
type: 'reply',
|
||||
reply: {
|
||||
title: button.displayText,
|
||||
id: button.id,
|
||||
},
|
||||
};
|
||||
}),
|
||||
[embeddedMedia?.mediaKey]: embeddedMedia?.message,
|
||||
},
|
||||
{
|
||||
delay: data?.delay,
|
||||
presence: 'composing',
|
||||
quoted: data?.quoted,
|
||||
linkPreview: data?.linkPreview,
|
||||
mentionsEveryOne: data?.mentionsEveryOne,
|
||||
mentioned: data?.mentioned,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public async locationMessage(data: SendLocationDto) {
|
||||
|
||||
@@ -33,6 +33,7 @@ import { HandleLabelDto, LabelDto } from '@api/dto/label.dto';
|
||||
import {
|
||||
Button,
|
||||
ContactMessage,
|
||||
KeyType,
|
||||
MediaMessage,
|
||||
Options,
|
||||
SendAudioDto,
|
||||
@@ -42,6 +43,7 @@ import {
|
||||
SendLocationDto,
|
||||
SendMediaDto,
|
||||
SendPollDto,
|
||||
SendPtvDto,
|
||||
SendReactionDto,
|
||||
SendStatusDto,
|
||||
SendStickerDto,
|
||||
@@ -125,6 +127,7 @@ import { isArray, isBase64, isURL } from 'class-validator';
|
||||
import { randomBytes } from 'crypto';
|
||||
import EventEmitter2 from 'eventemitter2';
|
||||
import ffmpeg from 'fluent-ffmpeg';
|
||||
import FormData from 'form-data';
|
||||
import { readFileSync } from 'fs';
|
||||
import Long from 'long';
|
||||
import mime from 'mime';
|
||||
@@ -136,11 +139,71 @@ import P from 'pino';
|
||||
import qrcode, { QRCodeToDataURLOptions } from 'qrcode';
|
||||
import qrcodeTerminal from 'qrcode-terminal';
|
||||
import sharp from 'sharp';
|
||||
import { PassThrough } from 'stream';
|
||||
import { PassThrough, Readable } from 'stream';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
const groupMetadataCache = new CacheService(new CacheEngine(configService, 'groups').getEngine());
|
||||
|
||||
// Adicione a função getVideoDuration no início do arquivo
|
||||
async function getVideoDuration(input: Buffer | string | Readable): Promise<number> {
|
||||
const MediaInfoFactory = (await import('mediainfo.js')).default;
|
||||
const mediainfo = await MediaInfoFactory({ format: 'JSON' });
|
||||
|
||||
let fileSize: number;
|
||||
let readChunk: (size: number, offset: number) => Promise<Buffer>;
|
||||
|
||||
if (Buffer.isBuffer(input)) {
|
||||
fileSize = input.length;
|
||||
readChunk = async (size: number, offset: number): Promise<Buffer> => {
|
||||
return input.slice(offset, offset + size);
|
||||
};
|
||||
} else if (typeof input === 'string') {
|
||||
const fs = await import('fs');
|
||||
const stat = await fs.promises.stat(input);
|
||||
fileSize = stat.size;
|
||||
const fd = await fs.promises.open(input, 'r');
|
||||
|
||||
readChunk = async (size: number, offset: number): Promise<Buffer> => {
|
||||
const buffer = Buffer.alloc(size);
|
||||
await fd.read(buffer, 0, size, offset);
|
||||
return buffer;
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await mediainfo.analyzeData(() => fileSize, readChunk);
|
||||
const jsonResult = JSON.parse(result);
|
||||
|
||||
const generalTrack = jsonResult.media.track.find((t: any) => t['@type'] === 'General');
|
||||
const duration = generalTrack.Duration;
|
||||
|
||||
return Math.round(parseFloat(duration));
|
||||
} finally {
|
||||
await fd.close();
|
||||
}
|
||||
} else if (input instanceof Readable) {
|
||||
const chunks: Buffer[] = [];
|
||||
for await (const chunk of input) {
|
||||
chunks.push(chunk);
|
||||
}
|
||||
const data = Buffer.concat(chunks);
|
||||
fileSize = data.length;
|
||||
|
||||
readChunk = async (size: number, offset: number): Promise<Buffer> => {
|
||||
return data.slice(offset, offset + size);
|
||||
};
|
||||
} else {
|
||||
throw new Error('Tipo de entrada não suportado');
|
||||
}
|
||||
|
||||
const result = await mediainfo.analyzeData(() => fileSize, readChunk);
|
||||
const jsonResult = JSON.parse(result);
|
||||
|
||||
const generalTrack = jsonResult.media.track.find((t: any) => t['@type'] === 'General');
|
||||
const duration = generalTrack.Duration;
|
||||
|
||||
return Math.round(parseFloat(duration));
|
||||
}
|
||||
|
||||
export class BaileysStartupService extends ChannelStartupService {
|
||||
constructor(
|
||||
public readonly configService: ConfigService,
|
||||
@@ -1070,6 +1133,25 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
if (settings?.groupsIgnore && received.key.remoteJid.includes('@g.us')) {
|
||||
continue;
|
||||
}
|
||||
const existingChat = await this.prismaRepository.chat.findFirst({
|
||||
where: { instanceId: this.instanceId, remoteJid: received.key.remoteJid },
|
||||
});
|
||||
|
||||
if (existingChat) {
|
||||
const chatToInsert = {
|
||||
remoteJid: received.key.remoteJid,
|
||||
instanceId: this.instanceId,
|
||||
name: received.pushName || '',
|
||||
unreadMessages: 0,
|
||||
};
|
||||
|
||||
this.sendDataWebhook(Events.CHATS_UPSERT, [chatToInsert]);
|
||||
if (this.configService.get<Database>('DATABASE').SAVE_DATA.CHATS) {
|
||||
await this.prismaRepository.chat.create({
|
||||
data: chatToInsert,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const messageRaw = this.prepareMessage(received);
|
||||
|
||||
@@ -1079,6 +1161,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
received?.message?.stickerMessage ||
|
||||
received?.message?.documentMessage ||
|
||||
received?.message?.documentWithCaptionMessage ||
|
||||
received?.message?.ptvMessage ||
|
||||
received?.message?.audioMessage;
|
||||
|
||||
if (this.localSettings.readMessages && received.key.id !== 'status@broadcast') {
|
||||
@@ -1385,6 +1468,26 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
await this.prismaRepository.messageUpdate.create({
|
||||
data: message,
|
||||
});
|
||||
|
||||
const existingChat = await this.prismaRepository.chat.findFirst({
|
||||
where: { instanceId: this.instanceId, remoteJid: message.remoteJid },
|
||||
});
|
||||
|
||||
if (existingChat) {
|
||||
const chatToInsert = {
|
||||
remoteJid: message.remoteJid,
|
||||
instanceId: this.instanceId,
|
||||
name: message.pushName || '',
|
||||
unreadMessages: 0,
|
||||
};
|
||||
|
||||
this.sendDataWebhook(Events.CHATS_UPSERT, [chatToInsert]);
|
||||
if (this.configService.get<Database>('DATABASE').SAVE_DATA.CHATS) {
|
||||
await this.prismaRepository.chat.create({
|
||||
data: chatToInsert,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1720,7 +1823,8 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
website: business?.website?.shift(),
|
||||
};
|
||||
} else {
|
||||
const info: Instance = await waMonitor.instanceInfo([instanceName]);
|
||||
const instanceNames = instanceName ? [instanceName] : null;
|
||||
const info: Instance = await waMonitor.instanceInfo(instanceNames);
|
||||
const business = await this.fetchBusinessProfile(jid);
|
||||
|
||||
return {
|
||||
@@ -2051,8 +2155,10 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
messageSent?.message?.imageMessage ||
|
||||
messageSent?.message?.videoMessage ||
|
||||
messageSent?.message?.stickerMessage ||
|
||||
messageSent?.message?.ptvMessage ||
|
||||
messageSent?.message?.documentMessage ||
|
||||
messageSent?.message?.documentWithCaptionMessage ||
|
||||
messageSent?.message?.ptvMessage ||
|
||||
messageSent?.message?.audioMessage;
|
||||
|
||||
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled && !isIntegration) {
|
||||
@@ -2396,11 +2502,11 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
|
||||
private async prepareMediaMessage(mediaMessage: MediaMessage) {
|
||||
try {
|
||||
const type = mediaMessage.mediatype === 'ptv' ? 'video' : mediaMessage.mediatype;
|
||||
|
||||
const prepareMedia = await prepareWAMessageMedia(
|
||||
{
|
||||
[mediaMessage.mediatype]: isURL(mediaMessage.media)
|
||||
? { url: mediaMessage.media }
|
||||
: Buffer.from(mediaMessage.media, 'base64'),
|
||||
[type]: isURL(mediaMessage.media) ? { url: mediaMessage.media } : Buffer.from(mediaMessage.media, 'base64'),
|
||||
} as any,
|
||||
{ upload: this.client.waUploadToServer },
|
||||
);
|
||||
@@ -2452,6 +2558,40 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
}
|
||||
}
|
||||
|
||||
if (mediaMessage.mediatype === 'ptv') {
|
||||
prepareMedia[mediaType] = prepareMedia[type + 'Message'];
|
||||
mimetype = 'video/mp4';
|
||||
|
||||
if (!prepareMedia[mediaType]) {
|
||||
throw new Error('Failed to prepare video message');
|
||||
}
|
||||
|
||||
try {
|
||||
let mediaInput;
|
||||
if (isURL(mediaMessage.media)) {
|
||||
mediaInput = mediaMessage.media;
|
||||
} else {
|
||||
const mediaBuffer = Buffer.from(mediaMessage.media, 'base64');
|
||||
if (!mediaBuffer || mediaBuffer.length === 0) {
|
||||
throw new Error('Invalid media buffer');
|
||||
}
|
||||
mediaInput = mediaBuffer;
|
||||
}
|
||||
|
||||
const duration = await getVideoDuration(mediaInput);
|
||||
if (!duration || duration <= 0) {
|
||||
throw new Error('Invalid media duration');
|
||||
}
|
||||
|
||||
this.logger.verbose(`Video duration: ${duration} seconds`);
|
||||
prepareMedia[mediaType].seconds = duration;
|
||||
} catch (error) {
|
||||
this.logger.error('Error getting video duration:');
|
||||
this.logger.error(error);
|
||||
throw new Error(`Failed to get video duration: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
prepareMedia[mediaType].caption = mediaMessage?.caption;
|
||||
prepareMedia[mediaType].mimetype = mimetype;
|
||||
prepareMedia[mediaType].fileName = mediaMessage.fileName;
|
||||
@@ -2563,6 +2703,37 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
return mediaSent;
|
||||
}
|
||||
|
||||
public async ptvMessage(data: SendPtvDto, file?: any, isIntegration = false) {
|
||||
const mediaData: SendMediaDto = {
|
||||
number: data.number,
|
||||
media: data.video,
|
||||
mediatype: 'ptv',
|
||||
delay: data?.delay,
|
||||
quoted: data?.quoted,
|
||||
mentionsEveryOne: data?.mentionsEveryOne,
|
||||
mentioned: data?.mentioned,
|
||||
};
|
||||
|
||||
if (file) mediaData.media = file.buffer.toString('base64');
|
||||
|
||||
const generate = await this.prepareMediaMessage(mediaData);
|
||||
|
||||
const mediaSent = await this.sendMessageWithTyping(
|
||||
data.number,
|
||||
{ ...generate.message },
|
||||
{
|
||||
delay: data?.delay,
|
||||
presence: 'composing',
|
||||
quoted: data?.quoted,
|
||||
mentionsEveryOne: data?.mentionsEveryOne,
|
||||
mentioned: data?.mentioned,
|
||||
},
|
||||
isIntegration,
|
||||
);
|
||||
|
||||
return mediaSent;
|
||||
}
|
||||
|
||||
public async processAudioMp4(audio: string) {
|
||||
let inputStream: PassThrough;
|
||||
|
||||
@@ -2631,53 +2802,78 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
}
|
||||
|
||||
public async processAudio(audio: string): Promise<Buffer> {
|
||||
let inputAudioStream: PassThrough;
|
||||
if (process.env.API_AUDIO_CONVERTER) {
|
||||
this.logger.verbose('Using audio converter API');
|
||||
const formData = new FormData();
|
||||
|
||||
if (isURL(audio)) {
|
||||
const timestamp = new Date().getTime();
|
||||
const url = `${audio}?timestamp=${timestamp}`;
|
||||
if (isURL(audio)) {
|
||||
formData.append('url', audio);
|
||||
} else {
|
||||
formData.append('base64', audio);
|
||||
}
|
||||
|
||||
const config: any = {
|
||||
responseType: 'stream',
|
||||
};
|
||||
const { data } = await axios.post(process.env.API_AUDIO_CONVERTER, formData, {
|
||||
headers: {
|
||||
...formData.getHeaders(),
|
||||
apikey: process.env.API_AUDIO_CONVERTER_KEY,
|
||||
},
|
||||
});
|
||||
|
||||
const response = await axios.get(url, config);
|
||||
inputAudioStream = response.data.pipe(new PassThrough());
|
||||
if (!data.audio) {
|
||||
throw new InternalServerErrorException('Failed to convert audio');
|
||||
}
|
||||
|
||||
this.logger.verbose('Audio converted');
|
||||
return Buffer.from(data.audio, 'base64');
|
||||
} else {
|
||||
const audioBuffer = Buffer.from(audio, 'base64');
|
||||
inputAudioStream = new PassThrough();
|
||||
inputAudioStream.end(audioBuffer);
|
||||
}
|
||||
let inputAudioStream: PassThrough;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const outputAudioStream = new PassThrough();
|
||||
const chunks: Buffer[] = [];
|
||||
if (isURL(audio)) {
|
||||
const timestamp = new Date().getTime();
|
||||
const url = `${audio}?timestamp=${timestamp}`;
|
||||
|
||||
outputAudioStream.on('data', (chunk) => chunks.push(chunk));
|
||||
outputAudioStream.on('end', () => {
|
||||
const outputBuffer = Buffer.concat(chunks);
|
||||
resolve(outputBuffer);
|
||||
});
|
||||
const config: any = {
|
||||
responseType: 'stream',
|
||||
};
|
||||
|
||||
outputAudioStream.on('error', (error) => {
|
||||
console.log('error', error);
|
||||
reject(error);
|
||||
});
|
||||
const response = await axios.get(url, config);
|
||||
inputAudioStream = response.data.pipe(new PassThrough());
|
||||
} else {
|
||||
const audioBuffer = Buffer.from(audio, 'base64');
|
||||
inputAudioStream = new PassThrough();
|
||||
inputAudioStream.end(audioBuffer);
|
||||
}
|
||||
|
||||
ffmpeg.setFfmpegPath(ffmpegPath.path);
|
||||
return new Promise((resolve, reject) => {
|
||||
const outputAudioStream = new PassThrough();
|
||||
const chunks: Buffer[] = [];
|
||||
|
||||
ffmpeg(inputAudioStream)
|
||||
.outputFormat('ogg')
|
||||
.noVideo()
|
||||
.audioCodec('libopus')
|
||||
.addOutputOptions('-avoid_negative_ts make_zero')
|
||||
.audioChannels(1)
|
||||
.pipe(outputAudioStream, { end: true })
|
||||
.on('error', function (error) {
|
||||
outputAudioStream.on('data', (chunk) => chunks.push(chunk));
|
||||
outputAudioStream.on('end', () => {
|
||||
const outputBuffer = Buffer.concat(chunks);
|
||||
resolve(outputBuffer);
|
||||
});
|
||||
|
||||
outputAudioStream.on('error', (error) => {
|
||||
console.log('error', error);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
ffmpeg.setFfmpegPath(ffmpegPath.path);
|
||||
|
||||
ffmpeg(inputAudioStream)
|
||||
.outputFormat('ogg')
|
||||
.noVideo()
|
||||
.audioCodec('libopus')
|
||||
.addOutputOptions('-avoid_negative_ts make_zero')
|
||||
.audioChannels(1)
|
||||
.pipe(outputAudioStream, { end: true })
|
||||
.on('error', function (error) {
|
||||
console.log('error', error);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async audioWhatsapp(data: SendAudioDto, file?: any, isIntegration = false) {
|
||||
@@ -2727,6 +2923,15 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
);
|
||||
}
|
||||
|
||||
private generateRandomId(length = 11) {
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private toJSONString(button: Button): string {
|
||||
const toString = (obj: any) => JSON.stringify(obj);
|
||||
|
||||
@@ -2740,6 +2945,49 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
url: button.url,
|
||||
merchant_url: button.url,
|
||||
}),
|
||||
pix: () =>
|
||||
toString({
|
||||
currency: button.currency,
|
||||
total_amount: {
|
||||
value: 0,
|
||||
offset: 100,
|
||||
},
|
||||
reference_id: this.generateRandomId(),
|
||||
type: 'physical-goods',
|
||||
order: {
|
||||
status: 'pending',
|
||||
subtotal: {
|
||||
value: 0,
|
||||
offset: 100,
|
||||
},
|
||||
order_type: 'ORDER',
|
||||
items: [
|
||||
{
|
||||
name: '',
|
||||
amount: {
|
||||
value: 0,
|
||||
offset: 100,
|
||||
},
|
||||
quantity: 0,
|
||||
sale_amount: {
|
||||
value: 0,
|
||||
offset: 100,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
payment_settings: [
|
||||
{
|
||||
type: 'pix_static_code',
|
||||
pix_static_code: {
|
||||
merchant_name: button.name,
|
||||
key: button.key,
|
||||
key_type: this.mapKeyType.get(button.keyType),
|
||||
},
|
||||
},
|
||||
],
|
||||
share_payment_status: false,
|
||||
}),
|
||||
};
|
||||
|
||||
return json[button.type]?.() || '';
|
||||
@@ -2750,9 +2998,75 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
['copy', 'cta_copy'],
|
||||
['url', 'cta_url'],
|
||||
['call', 'cta_call'],
|
||||
['pix', 'payment_info'],
|
||||
]);
|
||||
|
||||
private readonly mapKeyType = new Map<KeyType, string>([
|
||||
['phone', 'PHONE'],
|
||||
['email', 'EMAIL'],
|
||||
['cpf', 'CPF'],
|
||||
['cnpj', 'CNPJ'],
|
||||
['random', 'EVP'],
|
||||
]);
|
||||
|
||||
public async buttonMessage(data: SendButtonsDto) {
|
||||
if (data.buttons.length === 0) {
|
||||
throw new BadRequestException('At least one button is required');
|
||||
}
|
||||
|
||||
const hasReplyButtons = data.buttons.some((btn) => btn.type === 'reply');
|
||||
|
||||
const hasPixButton = data.buttons.some((btn) => btn.type === 'pix');
|
||||
|
||||
const hasOtherButtons = data.buttons.some((btn) => btn.type !== 'reply' && btn.type !== 'pix');
|
||||
|
||||
if (hasReplyButtons) {
|
||||
if (data.buttons.length > 3) {
|
||||
throw new BadRequestException('Maximum of 3 reply buttons allowed');
|
||||
}
|
||||
if (hasOtherButtons) {
|
||||
throw new BadRequestException('Reply buttons cannot be mixed with other button types');
|
||||
}
|
||||
}
|
||||
|
||||
if (hasPixButton) {
|
||||
if (data.buttons.length > 1) {
|
||||
throw new BadRequestException('Only one PIX button is allowed');
|
||||
}
|
||||
if (hasOtherButtons) {
|
||||
throw new BadRequestException('PIX button cannot be mixed with other button types');
|
||||
}
|
||||
|
||||
const message: proto.IMessage = {
|
||||
viewOnceMessage: {
|
||||
message: {
|
||||
interactiveMessage: {
|
||||
nativeFlowMessage: {
|
||||
buttons: [
|
||||
{
|
||||
name: this.mapType.get('pix'),
|
||||
buttonParamsJson: this.toJSONString(data.buttons[0]),
|
||||
},
|
||||
],
|
||||
messageParamsJson: JSON.stringify({
|
||||
from: 'api',
|
||||
templateId: v4(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return await this.sendMessageWithTyping(data.number, message, {
|
||||
delay: data?.delay,
|
||||
presence: 'composing',
|
||||
quoted: data?.quoted,
|
||||
mentionsEveryOne: data?.mentionsEveryOne,
|
||||
mentioned: data?.mentioned,
|
||||
});
|
||||
}
|
||||
|
||||
const generate = await (async () => {
|
||||
if (data?.thumbnailUrl) {
|
||||
return await this.prepareMediaMessage({
|
||||
@@ -2807,8 +3121,6 @@ export class BaileysStartupService extends ChannelStartupService {
|
||||
},
|
||||
};
|
||||
|
||||
console.log(JSON.stringify(message));
|
||||
|
||||
return await this.sendMessageWithTyping(data.number, message, {
|
||||
delay: data?.delay,
|
||||
presence: 'composing',
|
||||
|
||||
@@ -401,7 +401,6 @@ export class ChatwootService {
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -933,9 +932,11 @@ export class ChatwootService {
|
||||
) {
|
||||
if (sourceId && this.isImportHistoryAvailable()) {
|
||||
const messageAlreadySaved = await chatwootImport.getExistingSourceIds([sourceId]);
|
||||
if (messageAlreadySaved.size > 0) {
|
||||
this.logger.warn('Message already saved on chatwoot');
|
||||
return null;
|
||||
if (messageAlreadySaved) {
|
||||
if (messageAlreadySaved.size > 0) {
|
||||
this.logger.warn('Message already saved on chatwoot');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
const data = new FormData();
|
||||
@@ -2442,57 +2443,61 @@ export class ChatwootService {
|
||||
chatwootConfig: ChatwootDto,
|
||||
prepareMessage: (message: any) => any,
|
||||
) {
|
||||
if (!this.isImportHistoryAvailable()) {
|
||||
return;
|
||||
}
|
||||
if (!this.configService.get<Database>('DATABASE').SAVE_DATA.MESSAGE_UPDATE) {
|
||||
return;
|
||||
}
|
||||
|
||||
const inbox = await this.getInbox(instance);
|
||||
|
||||
const sqlMessages = `select * from messages m
|
||||
where account_id = ${chatwootConfig.accountId}
|
||||
and inbox_id = ${inbox.id}
|
||||
and created_at >= now() - interval '6h'
|
||||
order by created_at desc`;
|
||||
|
||||
const messagesData = (await this.pgClient.query(sqlMessages))?.rows;
|
||||
const ids: string[] = messagesData
|
||||
.filter((message) => !!message.source_id)
|
||||
.map((message) => message.source_id.replace('WAID:', ''));
|
||||
|
||||
const savedMessages = await this.prismaRepository.message.findMany({
|
||||
where: {
|
||||
Instance: { name: instance.instanceName },
|
||||
messageTimestamp: { gte: dayjs().subtract(6, 'hours').unix() },
|
||||
AND: ids.map((id) => ({ key: { path: ['id'], not: id } })),
|
||||
},
|
||||
});
|
||||
|
||||
const filteredMessages = savedMessages.filter(
|
||||
(msg: any) => !chatwootImport.isIgnorePhoneNumber(msg.key?.remoteJid),
|
||||
);
|
||||
const messagesRaw: any[] = [];
|
||||
for (const m of filteredMessages) {
|
||||
if (!m.message || !m.key || !m.messageTimestamp) {
|
||||
continue;
|
||||
try {
|
||||
if (!this.isImportHistoryAvailable()) {
|
||||
return;
|
||||
}
|
||||
if (!this.configService.get<Database>('DATABASE').SAVE_DATA.MESSAGE_UPDATE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Long.isLong(m?.messageTimestamp)) {
|
||||
m.messageTimestamp = m.messageTimestamp?.toNumber();
|
||||
const inbox = await this.getInbox(instance);
|
||||
|
||||
const sqlMessages = `select * from messages m
|
||||
where account_id = ${chatwootConfig.accountId}
|
||||
and inbox_id = ${inbox.id}
|
||||
and created_at >= now() - interval '6h'
|
||||
order by created_at desc`;
|
||||
|
||||
const messagesData = (await this.pgClient.query(sqlMessages))?.rows;
|
||||
const ids: string[] = messagesData
|
||||
.filter((message) => !!message.source_id)
|
||||
.map((message) => message.source_id.replace('WAID:', ''));
|
||||
|
||||
const savedMessages = await this.prismaRepository.message.findMany({
|
||||
where: {
|
||||
Instance: { name: instance.instanceName },
|
||||
messageTimestamp: { gte: dayjs().subtract(6, 'hours').unix() },
|
||||
AND: ids.map((id) => ({ key: { path: ['id'], not: id } })),
|
||||
},
|
||||
});
|
||||
|
||||
const filteredMessages = savedMessages.filter(
|
||||
(msg: any) => !chatwootImport.isIgnorePhoneNumber(msg.key?.remoteJid),
|
||||
);
|
||||
const messagesRaw: any[] = [];
|
||||
for (const m of filteredMessages) {
|
||||
if (!m.message || !m.key || !m.messageTimestamp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Long.isLong(m?.messageTimestamp)) {
|
||||
m.messageTimestamp = m.messageTimestamp?.toNumber();
|
||||
}
|
||||
|
||||
messagesRaw.push(prepareMessage(m as any));
|
||||
}
|
||||
|
||||
messagesRaw.push(prepareMessage(m as any));
|
||||
this.addHistoryMessages(
|
||||
instance,
|
||||
messagesRaw.filter((msg) => !chatwootImport.isIgnorePhoneNumber(msg.key?.remoteJid)),
|
||||
);
|
||||
|
||||
await chatwootImport.importHistoryMessages(instance, this, inbox, this.provider);
|
||||
const waInstance = this.waMonitor.waInstances[instance.instanceName];
|
||||
waInstance.clearCacheChatwoot();
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.addHistoryMessages(
|
||||
instance,
|
||||
messagesRaw.filter((msg) => !chatwootImport.isIgnorePhoneNumber(msg.key?.remoteJid)),
|
||||
);
|
||||
|
||||
await chatwootImport.importHistoryMessages(instance, this, inbox, this.provider);
|
||||
const waInstance = this.waMonitor.waInstances[instance.instanceName];
|
||||
waInstance.clearCacheChatwoot();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,22 +170,26 @@ class ChatwootImport {
|
||||
}
|
||||
|
||||
public async getExistingSourceIds(sourceIds: string[]): Promise<Set<string>> {
|
||||
const existingSourceIdsSet = new Set<string>();
|
||||
try {
|
||||
const existingSourceIdsSet = new Set<string>();
|
||||
|
||||
if (sourceIds.length === 0) {
|
||||
return existingSourceIdsSet;
|
||||
}
|
||||
|
||||
const formattedSourceIds = sourceIds.map((sourceId) => `WAID:${sourceId.replace('WAID:', '')}`); // Make sure the sourceId is always formatted as WAID:1234567890
|
||||
const query = 'SELECT source_id FROM messages WHERE source_id = ANY($1)';
|
||||
const pgClient = postgresClient.getChatwootConnection();
|
||||
const result = await pgClient.query(query, [formattedSourceIds]);
|
||||
|
||||
for (const row of result.rows) {
|
||||
existingSourceIdsSet.add(row.source_id);
|
||||
}
|
||||
|
||||
if (sourceIds.length === 0) {
|
||||
return existingSourceIdsSet;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const formattedSourceIds = sourceIds.map((sourceId) => `WAID:${sourceId.replace('WAID:', '')}`); // Make sure the sourceId is always formatted as WAID:1234567890
|
||||
const query = 'SELECT source_id FROM messages WHERE source_id = ANY($1)';
|
||||
const pgClient = postgresClient.getChatwootConnection();
|
||||
const result = await pgClient.query(query, [formattedSourceIds]);
|
||||
|
||||
for (const row of result.rows) {
|
||||
existingSourceIdsSet.add(row.source_id);
|
||||
}
|
||||
|
||||
return existingSourceIdsSet;
|
||||
}
|
||||
|
||||
public async importHistoryMessages(
|
||||
|
||||
@@ -64,17 +64,25 @@ export class DifyController extends ChatbotController implements ChatbotControll
|
||||
},
|
||||
});
|
||||
|
||||
if (!data.expire) data.expire = defaultSettingCheck?.expire || 0;
|
||||
if (!data.keywordFinish) data.keywordFinish = defaultSettingCheck?.keywordFinish || '';
|
||||
if (!data.delayMessage) data.delayMessage = defaultSettingCheck?.delayMessage || 1000;
|
||||
if (!data.unknownMessage) data.unknownMessage = defaultSettingCheck?.unknownMessage || '';
|
||||
if (!data.listeningFromMe) data.listeningFromMe = defaultSettingCheck?.listeningFromMe || false;
|
||||
if (!data.stopBotFromMe) data.stopBotFromMe = defaultSettingCheck?.stopBotFromMe || false;
|
||||
if (!data.keepOpen) data.keepOpen = defaultSettingCheck?.keepOpen || false;
|
||||
if (!data.debounceTime) data.debounceTime = defaultSettingCheck?.debounceTime || 0;
|
||||
if (!data.ignoreJids) data.ignoreJids = defaultSettingCheck?.ignoreJids || [];
|
||||
if (!data.splitMessages) data.splitMessages = defaultSettingCheck?.splitMessages || false;
|
||||
if (!data.timePerChar) data.timePerChar = defaultSettingCheck?.timePerChar || 0;
|
||||
if (data.expire === undefined || data.expire === null) data.expire = defaultSettingCheck.expire;
|
||||
if (data.keywordFinish === undefined || data.keywordFinish === null)
|
||||
data.keywordFinish = defaultSettingCheck.keywordFinish;
|
||||
if (data.delayMessage === undefined || data.delayMessage === null)
|
||||
data.delayMessage = defaultSettingCheck.delayMessage;
|
||||
if (data.unknownMessage === undefined || data.unknownMessage === null)
|
||||
data.unknownMessage = defaultSettingCheck.unknownMessage;
|
||||
if (data.listeningFromMe === undefined || data.listeningFromMe === null)
|
||||
data.listeningFromMe = defaultSettingCheck.listeningFromMe;
|
||||
if (data.stopBotFromMe === undefined || data.stopBotFromMe === null)
|
||||
data.stopBotFromMe = defaultSettingCheck.stopBotFromMe;
|
||||
if (data.keepOpen === undefined || data.keepOpen === null) data.keepOpen = defaultSettingCheck.keepOpen;
|
||||
if (data.debounceTime === undefined || data.debounceTime === null)
|
||||
data.debounceTime = defaultSettingCheck.debounceTime;
|
||||
if (data.ignoreJids === undefined || data.ignoreJids === null) data.ignoreJids = defaultSettingCheck.ignoreJids;
|
||||
if (data.splitMessages === undefined || data.splitMessages === null)
|
||||
data.splitMessages = defaultSettingCheck?.splitMessages ?? false;
|
||||
if (data.timePerChar === undefined || data.timePerChar === null)
|
||||
data.timePerChar = defaultSettingCheck?.timePerChar ?? 0;
|
||||
|
||||
if (!defaultSettingCheck) {
|
||||
await this.settings(instance, {
|
||||
@@ -788,15 +796,15 @@ export class DifyController extends ChatbotController implements ChatbotControll
|
||||
let splitMessages = findBot?.splitMessages;
|
||||
let timePerChar = findBot?.timePerChar;
|
||||
|
||||
if (!expire) expire = settings.expire;
|
||||
if (!keywordFinish) keywordFinish = settings.keywordFinish;
|
||||
if (!delayMessage) delayMessage = settings.delayMessage;
|
||||
if (!unknownMessage) unknownMessage = settings.unknownMessage;
|
||||
if (!listeningFromMe) listeningFromMe = settings.listeningFromMe;
|
||||
if (!stopBotFromMe) stopBotFromMe = settings.stopBotFromMe;
|
||||
if (!keepOpen) keepOpen = settings.keepOpen;
|
||||
if (expire === undefined || expire === null) expire = settings.expire;
|
||||
if (keywordFinish === undefined || keywordFinish === null) keywordFinish = settings.keywordFinish;
|
||||
if (delayMessage === undefined || delayMessage === null) delayMessage = settings.delayMessage;
|
||||
if (unknownMessage === undefined || unknownMessage === null) unknownMessage = settings.unknownMessage;
|
||||
if (listeningFromMe === undefined || listeningFromMe === null) listeningFromMe = settings.listeningFromMe;
|
||||
if (stopBotFromMe === undefined || stopBotFromMe === null) stopBotFromMe = settings.stopBotFromMe;
|
||||
if (keepOpen === undefined || keepOpen === null) keepOpen = settings.keepOpen;
|
||||
if (debounceTime === undefined || debounceTime === null) debounceTime = settings.debounceTime;
|
||||
if (!ignoreJids) ignoreJids = settings.ignoreJids;
|
||||
if (ignoreJids === undefined || ignoreJids === null) ignoreJids = settings.ignoreJids;
|
||||
if (splitMessages === undefined || splitMessages === null) splitMessages = settings?.splitMessages ?? false;
|
||||
if (timePerChar === undefined || timePerChar === null) timePerChar = settings?.timePerChar ?? 0;
|
||||
|
||||
|
||||
@@ -60,17 +60,25 @@ export class EvolutionBotController extends ChatbotController implements Chatbot
|
||||
},
|
||||
});
|
||||
|
||||
if (!data.expire) data.expire = defaultSettingCheck?.expire || 0;
|
||||
if (!data.keywordFinish) data.keywordFinish = defaultSettingCheck?.keywordFinish || '';
|
||||
if (!data.delayMessage) data.delayMessage = defaultSettingCheck?.delayMessage || 1000;
|
||||
if (!data.unknownMessage) data.unknownMessage = defaultSettingCheck?.unknownMessage || '';
|
||||
if (!data.listeningFromMe) data.listeningFromMe = defaultSettingCheck?.listeningFromMe || false;
|
||||
if (!data.stopBotFromMe) data.stopBotFromMe = defaultSettingCheck?.stopBotFromMe || false;
|
||||
if (!data.keepOpen) data.keepOpen = defaultSettingCheck?.keepOpen || false;
|
||||
if (!data.debounceTime) data.debounceTime = defaultSettingCheck?.debounceTime || 0;
|
||||
if (!data.ignoreJids) data.ignoreJids = defaultSettingCheck?.ignoreJids || [];
|
||||
if (!data.splitMessages) data.splitMessages = defaultSettingCheck?.splitMessages || false;
|
||||
if (!data.timePerChar) data.timePerChar = defaultSettingCheck?.timePerChar || 0;
|
||||
if (data.expire === undefined || data.expire === null) data.expire = defaultSettingCheck.expire;
|
||||
if (data.keywordFinish === undefined || data.keywordFinish === null)
|
||||
data.keywordFinish = defaultSettingCheck.keywordFinish;
|
||||
if (data.delayMessage === undefined || data.delayMessage === null)
|
||||
data.delayMessage = defaultSettingCheck.delayMessage;
|
||||
if (data.unknownMessage === undefined || data.unknownMessage === null)
|
||||
data.unknownMessage = defaultSettingCheck.unknownMessage;
|
||||
if (data.listeningFromMe === undefined || data.listeningFromMe === null)
|
||||
data.listeningFromMe = defaultSettingCheck.listeningFromMe;
|
||||
if (data.stopBotFromMe === undefined || data.stopBotFromMe === null)
|
||||
data.stopBotFromMe = defaultSettingCheck.stopBotFromMe;
|
||||
if (data.keepOpen === undefined || data.keepOpen === null) data.keepOpen = defaultSettingCheck.keepOpen;
|
||||
if (data.debounceTime === undefined || data.debounceTime === null)
|
||||
data.debounceTime = defaultSettingCheck.debounceTime;
|
||||
if (data.ignoreJids === undefined || data.ignoreJids === null) data.ignoreJids = defaultSettingCheck.ignoreJids;
|
||||
if (data.splitMessages === undefined || data.splitMessages === null)
|
||||
data.splitMessages = defaultSettingCheck?.splitMessages ?? false;
|
||||
if (data.timePerChar === undefined || data.timePerChar === null)
|
||||
data.timePerChar = defaultSettingCheck?.timePerChar ?? 0;
|
||||
|
||||
if (!defaultSettingCheck) {
|
||||
await this.settings(instance, {
|
||||
@@ -760,15 +768,15 @@ export class EvolutionBotController extends ChatbotController implements Chatbot
|
||||
let splitMessages = findBot?.splitMessages;
|
||||
let timePerChar = findBot?.timePerChar;
|
||||
|
||||
if (!expire) expire = settings.expire;
|
||||
if (!keywordFinish) keywordFinish = settings.keywordFinish;
|
||||
if (!delayMessage) delayMessage = settings.delayMessage;
|
||||
if (!unknownMessage) unknownMessage = settings.unknownMessage;
|
||||
if (!listeningFromMe) listeningFromMe = settings.listeningFromMe;
|
||||
if (!stopBotFromMe) stopBotFromMe = settings.stopBotFromMe;
|
||||
if (!keepOpen) keepOpen = settings.keepOpen;
|
||||
if (expire === undefined || expire === null) expire = settings.expire;
|
||||
if (keywordFinish === undefined || keywordFinish === null) keywordFinish = settings.keywordFinish;
|
||||
if (delayMessage === undefined || delayMessage === null) delayMessage = settings.delayMessage;
|
||||
if (unknownMessage === undefined || unknownMessage === null) unknownMessage = settings.unknownMessage;
|
||||
if (listeningFromMe === undefined || listeningFromMe === null) listeningFromMe = settings.listeningFromMe;
|
||||
if (stopBotFromMe === undefined || stopBotFromMe === null) stopBotFromMe = settings.stopBotFromMe;
|
||||
if (keepOpen === undefined || keepOpen === null) keepOpen = settings.keepOpen;
|
||||
if (debounceTime === undefined || debounceTime === null) debounceTime = settings.debounceTime;
|
||||
if (!ignoreJids) ignoreJids = settings.ignoreJids;
|
||||
if (ignoreJids === undefined || ignoreJids === null) ignoreJids = settings.ignoreJids;
|
||||
if (splitMessages === undefined || splitMessages === null) splitMessages = settings?.splitMessages ?? false;
|
||||
if (timePerChar === undefined || timePerChar === null) timePerChar = settings?.timePerChar ?? 0;
|
||||
|
||||
|
||||
@@ -60,17 +60,25 @@ export class FlowiseController extends ChatbotController implements ChatbotContr
|
||||
},
|
||||
});
|
||||
|
||||
if (!data.expire) data.expire = defaultSettingCheck?.expire || 0;
|
||||
if (!data.keywordFinish) data.keywordFinish = defaultSettingCheck?.keywordFinish || '';
|
||||
if (!data.delayMessage) data.delayMessage = defaultSettingCheck?.delayMessage || 1000;
|
||||
if (!data.unknownMessage) data.unknownMessage = defaultSettingCheck?.unknownMessage || '';
|
||||
if (!data.listeningFromMe) data.listeningFromMe = defaultSettingCheck?.listeningFromMe || false;
|
||||
if (!data.stopBotFromMe) data.stopBotFromMe = defaultSettingCheck?.stopBotFromMe || false;
|
||||
if (!data.keepOpen) data.keepOpen = defaultSettingCheck?.keepOpen || false;
|
||||
if (!data.debounceTime) data.debounceTime = defaultSettingCheck?.debounceTime || 0;
|
||||
if (!data.ignoreJids) data.ignoreJids = defaultSettingCheck?.ignoreJids || [];
|
||||
if (!data.splitMessages) data.splitMessages = defaultSettingCheck?.splitMessages || false;
|
||||
if (!data.timePerChar) data.timePerChar = defaultSettingCheck?.timePerChar || 0;
|
||||
if (data.expire === undefined || data.expire === null) data.expire = defaultSettingCheck.expire;
|
||||
if (data.keywordFinish === undefined || data.keywordFinish === null)
|
||||
data.keywordFinish = defaultSettingCheck.keywordFinish;
|
||||
if (data.delayMessage === undefined || data.delayMessage === null)
|
||||
data.delayMessage = defaultSettingCheck.delayMessage;
|
||||
if (data.unknownMessage === undefined || data.unknownMessage === null)
|
||||
data.unknownMessage = defaultSettingCheck.unknownMessage;
|
||||
if (data.listeningFromMe === undefined || data.listeningFromMe === null)
|
||||
data.listeningFromMe = defaultSettingCheck.listeningFromMe;
|
||||
if (data.stopBotFromMe === undefined || data.stopBotFromMe === null)
|
||||
data.stopBotFromMe = defaultSettingCheck.stopBotFromMe;
|
||||
if (data.keepOpen === undefined || data.keepOpen === null) data.keepOpen = defaultSettingCheck.keepOpen;
|
||||
if (data.debounceTime === undefined || data.debounceTime === null)
|
||||
data.debounceTime = defaultSettingCheck.debounceTime;
|
||||
if (data.ignoreJids === undefined || data.ignoreJids === null) data.ignoreJids = defaultSettingCheck.ignoreJids;
|
||||
if (data.splitMessages === undefined || data.splitMessages === null)
|
||||
data.splitMessages = defaultSettingCheck?.splitMessages ?? false;
|
||||
if (data.timePerChar === undefined || data.timePerChar === null)
|
||||
data.timePerChar = defaultSettingCheck?.timePerChar ?? 0;
|
||||
|
||||
if (!defaultSettingCheck) {
|
||||
await this.settings(instance, {
|
||||
@@ -760,15 +768,15 @@ export class FlowiseController extends ChatbotController implements ChatbotContr
|
||||
let splitMessages = findBot?.splitMessages;
|
||||
let timePerChar = findBot?.timePerChar;
|
||||
|
||||
if (!expire) expire = settings.expire;
|
||||
if (!keywordFinish) keywordFinish = settings.keywordFinish;
|
||||
if (!delayMessage) delayMessage = settings.delayMessage;
|
||||
if (!unknownMessage) unknownMessage = settings.unknownMessage;
|
||||
if (!listeningFromMe) listeningFromMe = settings.listeningFromMe;
|
||||
if (!stopBotFromMe) stopBotFromMe = settings.stopBotFromMe;
|
||||
if (!keepOpen) keepOpen = settings.keepOpen;
|
||||
if (expire === undefined || expire === null) expire = settings.expire;
|
||||
if (keywordFinish === undefined || keywordFinish === null) keywordFinish = settings.keywordFinish;
|
||||
if (delayMessage === undefined || delayMessage === null) delayMessage = settings.delayMessage;
|
||||
if (unknownMessage === undefined || unknownMessage === null) unknownMessage = settings.unknownMessage;
|
||||
if (listeningFromMe === undefined || listeningFromMe === null) listeningFromMe = settings.listeningFromMe;
|
||||
if (stopBotFromMe === undefined || stopBotFromMe === null) stopBotFromMe = settings.stopBotFromMe;
|
||||
if (keepOpen === undefined || keepOpen === null) keepOpen = settings.keepOpen;
|
||||
if (debounceTime === undefined || debounceTime === null) debounceTime = settings.debounceTime;
|
||||
if (!ignoreJids) ignoreJids = settings.ignoreJids;
|
||||
if (ignoreJids === undefined || ignoreJids === null) ignoreJids = settings.ignoreJids;
|
||||
if (splitMessages === undefined || splitMessages === null) splitMessages = settings?.splitMessages ?? false;
|
||||
if (timePerChar === undefined || timePerChar === null) timePerChar = settings?.timePerChar ?? 0;
|
||||
|
||||
|
||||
@@ -201,18 +201,25 @@ export class OpenaiController extends ChatbotController implements ChatbotContro
|
||||
},
|
||||
});
|
||||
|
||||
if (!data.openaiCredsId) data.openaiCredsId = defaultSettingCheck?.openaiCredsId || null;
|
||||
if (!data.expire) data.expire = defaultSettingCheck?.expire || 0;
|
||||
if (!data.keywordFinish) data.keywordFinish = defaultSettingCheck?.keywordFinish || '';
|
||||
if (!data.delayMessage) data.delayMessage = defaultSettingCheck?.delayMessage || 1000;
|
||||
if (!data.unknownMessage) data.unknownMessage = defaultSettingCheck?.unknownMessage || '';
|
||||
if (!data.listeningFromMe) data.listeningFromMe = defaultSettingCheck?.listeningFromMe || false;
|
||||
if (!data.stopBotFromMe) data.stopBotFromMe = defaultSettingCheck?.stopBotFromMe || false;
|
||||
if (!data.keepOpen) data.keepOpen = defaultSettingCheck?.keepOpen || false;
|
||||
if (!data.debounceTime) data.debounceTime = defaultSettingCheck?.debounceTime || 0;
|
||||
if (!data.ignoreJids) data.ignoreJids = defaultSettingCheck?.ignoreJids || [];
|
||||
if (!data.splitMessages) data.splitMessages = defaultSettingCheck?.splitMessages || false;
|
||||
if (!data.timePerChar) data.timePerChar = defaultSettingCheck?.timePerChar || 0;
|
||||
if (data.expire === undefined || data.expire === null) data.expire = defaultSettingCheck.expire;
|
||||
if (data.keywordFinish === undefined || data.keywordFinish === null)
|
||||
data.keywordFinish = defaultSettingCheck.keywordFinish;
|
||||
if (data.delayMessage === undefined || data.delayMessage === null)
|
||||
data.delayMessage = defaultSettingCheck.delayMessage;
|
||||
if (data.unknownMessage === undefined || data.unknownMessage === null)
|
||||
data.unknownMessage = defaultSettingCheck.unknownMessage;
|
||||
if (data.listeningFromMe === undefined || data.listeningFromMe === null)
|
||||
data.listeningFromMe = defaultSettingCheck.listeningFromMe;
|
||||
if (data.stopBotFromMe === undefined || data.stopBotFromMe === null)
|
||||
data.stopBotFromMe = defaultSettingCheck.stopBotFromMe;
|
||||
if (data.keepOpen === undefined || data.keepOpen === null) data.keepOpen = defaultSettingCheck.keepOpen;
|
||||
if (data.debounceTime === undefined || data.debounceTime === null)
|
||||
data.debounceTime = defaultSettingCheck.debounceTime;
|
||||
if (data.ignoreJids === undefined || data.ignoreJids === null) data.ignoreJids = defaultSettingCheck.ignoreJids;
|
||||
if (data.splitMessages === undefined || data.splitMessages === null)
|
||||
data.splitMessages = defaultSettingCheck?.splitMessages ?? false;
|
||||
if (data.timePerChar === undefined || data.timePerChar === null)
|
||||
data.timePerChar = defaultSettingCheck?.timePerChar ?? 0;
|
||||
|
||||
if (!data.openaiCredsId) {
|
||||
throw new Error('Openai Creds Id is required');
|
||||
@@ -998,15 +1005,15 @@ export class OpenaiController extends ChatbotController implements ChatbotContro
|
||||
let splitMessages = findBot?.splitMessages;
|
||||
let timePerChar = findBot?.timePerChar;
|
||||
|
||||
if (!expire) expire = settings.expire;
|
||||
if (!keywordFinish) keywordFinish = settings.keywordFinish;
|
||||
if (!delayMessage) delayMessage = settings.delayMessage;
|
||||
if (!unknownMessage) unknownMessage = settings.unknownMessage;
|
||||
if (!listeningFromMe) listeningFromMe = settings.listeningFromMe;
|
||||
if (!stopBotFromMe) stopBotFromMe = settings.stopBotFromMe;
|
||||
if (!keepOpen) keepOpen = settings.keepOpen;
|
||||
if (expire === undefined || expire === null) expire = settings.expire;
|
||||
if (keywordFinish === undefined || keywordFinish === null) keywordFinish = settings.keywordFinish;
|
||||
if (delayMessage === undefined || delayMessage === null) delayMessage = settings.delayMessage;
|
||||
if (unknownMessage === undefined || unknownMessage === null) unknownMessage = settings.unknownMessage;
|
||||
if (listeningFromMe === undefined || listeningFromMe === null) listeningFromMe = settings.listeningFromMe;
|
||||
if (stopBotFromMe === undefined || stopBotFromMe === null) stopBotFromMe = settings.stopBotFromMe;
|
||||
if (keepOpen === undefined || keepOpen === null) keepOpen = settings.keepOpen;
|
||||
if (debounceTime === undefined || debounceTime === null) debounceTime = settings.debounceTime;
|
||||
if (!ignoreJids) ignoreJids = settings.ignoreJids;
|
||||
if (ignoreJids === undefined || ignoreJids === null) ignoreJids = settings.ignoreJids;
|
||||
if (splitMessages === undefined || splitMessages === null) splitMessages = settings?.splitMessages ?? false;
|
||||
if (timePerChar === undefined || timePerChar === null) timePerChar = settings?.timePerChar ?? 0;
|
||||
|
||||
|
||||
@@ -570,6 +570,8 @@ export class TypebotController extends ChatbotController implements ChatbotContr
|
||||
let listeningFromMe = data?.typebot?.listeningFromMe;
|
||||
let stopBotFromMe = data?.typebot?.stopBotFromMe;
|
||||
let keepOpen = data?.typebot?.keepOpen;
|
||||
let debounceTime = data?.typebot?.debounceTime;
|
||||
let ignoreJids = data?.typebot?.ignoreJids;
|
||||
|
||||
const defaultSettingCheck = await this.settingsRepository.findFirst({
|
||||
where: {
|
||||
@@ -586,15 +588,20 @@ export class TypebotController extends ChatbotController implements ChatbotContr
|
||||
!unknownMessage ||
|
||||
!listeningFromMe ||
|
||||
!stopBotFromMe ||
|
||||
!keepOpen
|
||||
!keepOpen ||
|
||||
!debounceTime ||
|
||||
!ignoreJids
|
||||
) {
|
||||
if (!expire) expire = defaultSettingCheck?.expire || 0;
|
||||
if (!keywordFinish) keywordFinish = defaultSettingCheck?.keywordFinish || '#SAIR';
|
||||
if (!delayMessage) delayMessage = defaultSettingCheck?.delayMessage || 1000;
|
||||
if (!unknownMessage) unknownMessage = defaultSettingCheck?.unknownMessage || 'Desculpe, não entendi';
|
||||
if (!listeningFromMe) listeningFromMe = defaultSettingCheck?.listeningFromMe || false;
|
||||
if (!stopBotFromMe) stopBotFromMe = defaultSettingCheck?.stopBotFromMe || false;
|
||||
if (!keepOpen) keepOpen = defaultSettingCheck?.keepOpen || false;
|
||||
if (expire === undefined || expire === null) expire = defaultSettingCheck.expire;
|
||||
if (keywordFinish === undefined || keywordFinish === null) keywordFinish = defaultSettingCheck.keywordFinish;
|
||||
if (delayMessage === undefined || delayMessage === null) delayMessage = defaultSettingCheck.delayMessage;
|
||||
if (unknownMessage === undefined || unknownMessage === null) unknownMessage = defaultSettingCheck.unknownMessage;
|
||||
if (listeningFromMe === undefined || listeningFromMe === null)
|
||||
listeningFromMe = defaultSettingCheck.listeningFromMe;
|
||||
if (stopBotFromMe === undefined || stopBotFromMe === null) stopBotFromMe = defaultSettingCheck.stopBotFromMe;
|
||||
if (keepOpen === undefined || keepOpen === null) keepOpen = defaultSettingCheck.keepOpen;
|
||||
if (debounceTime === undefined || debounceTime === null) debounceTime = defaultSettingCheck.debounceTime;
|
||||
if (ignoreJids === undefined || ignoreJids === null) ignoreJids = defaultSettingCheck.ignoreJids;
|
||||
|
||||
if (!defaultSettingCheck) {
|
||||
await this.settings(instance, {
|
||||
@@ -605,6 +612,8 @@ export class TypebotController extends ChatbotController implements ChatbotContr
|
||||
listeningFromMe: listeningFromMe,
|
||||
stopBotFromMe: stopBotFromMe,
|
||||
keepOpen: keepOpen,
|
||||
debounceTime: debounceTime,
|
||||
ignoreJids: ignoreJids,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -980,15 +989,15 @@ export class TypebotController extends ChatbotController implements ChatbotContr
|
||||
let debounceTime = findBot?.debounceTime;
|
||||
let ignoreJids = findBot?.ignoreJids;
|
||||
|
||||
if (!expire) expire = settings.expire;
|
||||
if (!keywordFinish) keywordFinish = settings.keywordFinish;
|
||||
if (!delayMessage) delayMessage = settings.delayMessage;
|
||||
if (!unknownMessage) unknownMessage = settings.unknownMessage;
|
||||
if (!listeningFromMe) listeningFromMe = settings.listeningFromMe;
|
||||
if (!stopBotFromMe) stopBotFromMe = settings.stopBotFromMe;
|
||||
if (!keepOpen) keepOpen = settings.keepOpen;
|
||||
if (!debounceTime) debounceTime = settings.debounceTime;
|
||||
if (!ignoreJids) ignoreJids = settings.ignoreJids;
|
||||
if (expire === undefined || expire === null) expire = settings.expire;
|
||||
if (keywordFinish === undefined || keywordFinish === null) keywordFinish = settings.keywordFinish;
|
||||
if (delayMessage === undefined || delayMessage === null) delayMessage = settings.delayMessage;
|
||||
if (unknownMessage === undefined || unknownMessage === null) unknownMessage = settings.unknownMessage;
|
||||
if (listeningFromMe === undefined || listeningFromMe === null) listeningFromMe = settings.listeningFromMe;
|
||||
if (stopBotFromMe === undefined || stopBotFromMe === null) stopBotFromMe = settings.stopBotFromMe;
|
||||
if (keepOpen === undefined || keepOpen === null) keepOpen = settings.keepOpen;
|
||||
if (debounceTime === undefined || debounceTime === null) debounceTime = settings.debounceTime;
|
||||
if (ignoreJids === undefined || ignoreJids === null) ignoreJids = settings.ignoreJids;
|
||||
|
||||
if (this.checkIgnoreJids(ignoreJids, remoteJid)) return;
|
||||
|
||||
|
||||
@@ -223,14 +223,130 @@ export class TypebotService {
|
||||
|
||||
formattedText = formattedText.replace(/\n$/, '');
|
||||
|
||||
await instance.textMessage(
|
||||
{
|
||||
if (formattedText.includes('[list]')) {
|
||||
const listJson = {
|
||||
number: remoteJid.split('@')[0],
|
||||
delay: settings?.delayMessage || 1000,
|
||||
text: formattedText,
|
||||
},
|
||||
false,
|
||||
);
|
||||
title: '',
|
||||
description: '',
|
||||
buttonText: '',
|
||||
footerText: '',
|
||||
sections: [],
|
||||
};
|
||||
|
||||
const titleMatch = formattedText.match(/\[title\]([\s\S]*?)(?=\[description\])/);
|
||||
const descriptionMatch = formattedText.match(/\[description\]([\s\S]*?)(?=\[buttonText\])/);
|
||||
const buttonTextMatch = formattedText.match(/\[buttonText\]([\s\S]*?)(?=\[footerText\])/);
|
||||
const footerTextMatch = formattedText.match(/\[footerText\]([\s\S]*?)(?=\[menu\])/);
|
||||
|
||||
if (titleMatch) listJson.title = titleMatch[1].trim();
|
||||
if (descriptionMatch) listJson.description = descriptionMatch[1].trim();
|
||||
if (buttonTextMatch) listJson.buttonText = buttonTextMatch[1].trim();
|
||||
if (footerTextMatch) listJson.footerText = footerTextMatch[1].trim();
|
||||
|
||||
const menuContent = formattedText.match(/\[menu\]([\s\S]*?)\[\/menu\]/)?.[1];
|
||||
if (menuContent) {
|
||||
const sections = menuContent.match(/\[section\]([\s\S]*?)(?=\[section\]|\[\/section\]|\[\/menu\])/g);
|
||||
if (sections) {
|
||||
sections.forEach((section) => {
|
||||
const sectionTitle = section.match(/title: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
const rows = section.match(/\[row\]([\s\S]*?)(?=\[row\]|\[\/row\]|\[\/section\]|\[\/menu\])/g);
|
||||
|
||||
const sectionData = {
|
||||
title: sectionTitle,
|
||||
rows:
|
||||
rows?.map((row) => ({
|
||||
title: row.match(/title: (.*?)(?:\n|$)/)?.[1]?.trim(),
|
||||
description: row.match(/description: (.*?)(?:\n|$)/)?.[1]?.trim(),
|
||||
rowId: row.match(/rowId: (.*?)(?:\n|$)/)?.[1]?.trim(),
|
||||
})) || [],
|
||||
};
|
||||
|
||||
listJson.sections.push(sectionData);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await instance.listMessage(listJson);
|
||||
} else if (formattedText.includes('[buttons]')) {
|
||||
const buttonJson = {
|
||||
number: remoteJid.split('@')[0],
|
||||
thumbnailUrl: undefined,
|
||||
title: '',
|
||||
description: '',
|
||||
footer: '',
|
||||
buttons: [],
|
||||
};
|
||||
|
||||
const thumbnailUrlMatch = formattedText.match(/\[thumbnailUrl\]([\s\S]*?)(?=\[title\])/);
|
||||
const titleMatch = formattedText.match(/\[title\]([\s\S]*?)(?=\[description\])/);
|
||||
const descriptionMatch = formattedText.match(/\[description\]([\s\S]*?)(?=\[footer\])/);
|
||||
const footerMatch = formattedText.match(/\[footer\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url))/);
|
||||
|
||||
if (titleMatch) buttonJson.title = titleMatch[1].trim();
|
||||
if (thumbnailUrlMatch) buttonJson.thumbnailUrl = thumbnailUrlMatch[1].trim();
|
||||
if (descriptionMatch) buttonJson.description = descriptionMatch[1].trim();
|
||||
if (footerMatch) buttonJson.footer = footerMatch[1].trim();
|
||||
|
||||
const buttonTypes = {
|
||||
reply: /\[reply\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url)|$)/g,
|
||||
pix: /\[pix\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url)|$)/g,
|
||||
copy: /\[copy\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url)|$)/g,
|
||||
call: /\[call\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url)|$)/g,
|
||||
url: /\[url\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url)|$)/g,
|
||||
};
|
||||
|
||||
for (const [type, pattern] of Object.entries(buttonTypes)) {
|
||||
let match;
|
||||
while ((match = pattern.exec(formattedText)) !== null) {
|
||||
const content = match[1].trim();
|
||||
const button: any = { type };
|
||||
|
||||
switch (type) {
|
||||
case 'pix':
|
||||
button.currency = content.match(/currency: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
button.name = content.match(/name: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
button.keyType = content.match(/keyType: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
button.key = content.match(/key: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
break;
|
||||
|
||||
case 'reply':
|
||||
button.displayText = content.match(/displayText: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
button.id = content.match(/id: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
break;
|
||||
|
||||
case 'copy':
|
||||
button.displayText = content.match(/displayText: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
button.copyCode = content.match(/copyCode: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
break;
|
||||
|
||||
case 'call':
|
||||
button.displayText = content.match(/displayText: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
button.phoneNumber = content.match(/phone: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
break;
|
||||
|
||||
case 'url':
|
||||
button.displayText = content.match(/displayText: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
button.url = content.match(/url: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
break;
|
||||
}
|
||||
|
||||
if (Object.keys(button).length > 1) {
|
||||
buttonJson.buttons.push(button);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await instance.buttonMessage(buttonJson);
|
||||
} else {
|
||||
await instance.textMessage(
|
||||
{
|
||||
number: remoteJid.split('@')[0],
|
||||
delay: settings?.delayMessage || 1000,
|
||||
text: formattedText,
|
||||
},
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
sendTelemetry('/message/sendText');
|
||||
}
|
||||
@@ -299,14 +415,130 @@ export class TypebotService {
|
||||
|
||||
formattedText = formattedText.replace(/\n$/, '');
|
||||
|
||||
await instance.textMessage(
|
||||
{
|
||||
if (formattedText.includes('[list]')) {
|
||||
const listJson = {
|
||||
number: remoteJid.split('@')[0],
|
||||
delay: settings?.delayMessage || 1000,
|
||||
text: formattedText,
|
||||
},
|
||||
false,
|
||||
);
|
||||
title: '',
|
||||
description: '',
|
||||
buttonText: '',
|
||||
footerText: '',
|
||||
sections: [],
|
||||
};
|
||||
|
||||
const titleMatch = formattedText.match(/\[title\]([\s\S]*?)(?=\[description\])/);
|
||||
const descriptionMatch = formattedText.match(/\[description\]([\s\S]*?)(?=\[buttonText\])/);
|
||||
const buttonTextMatch = formattedText.match(/\[buttonText\]([\s\S]*?)(?=\[footerText\])/);
|
||||
const footerTextMatch = formattedText.match(/\[footerText\]([\s\S]*?)(?=\[menu\])/);
|
||||
|
||||
if (titleMatch) listJson.title = titleMatch[1].trim();
|
||||
if (descriptionMatch) listJson.description = descriptionMatch[1].trim();
|
||||
if (buttonTextMatch) listJson.buttonText = buttonTextMatch[1].trim();
|
||||
if (footerTextMatch) listJson.footerText = footerTextMatch[1].trim();
|
||||
|
||||
const menuContent = formattedText.match(/\[menu\]([\s\S]*?)\[\/menu\]/)?.[1];
|
||||
if (menuContent) {
|
||||
const sections = menuContent.match(/\[section\]([\s\S]*?)(?=\[section\]|\[\/section\]|\[\/menu\])/g);
|
||||
if (sections) {
|
||||
sections.forEach((section) => {
|
||||
const sectionTitle = section.match(/title: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
const rows = section.match(/\[row\]([\s\S]*?)(?=\[row\]|\[\/row\]|\[\/section\]|\[\/menu\])/g);
|
||||
|
||||
const sectionData = {
|
||||
title: sectionTitle,
|
||||
rows:
|
||||
rows?.map((row) => ({
|
||||
title: row.match(/title: (.*?)(?:\n|$)/)?.[1]?.trim(),
|
||||
description: row.match(/description: (.*?)(?:\n|$)/)?.[1]?.trim(),
|
||||
rowId: row.match(/rowId: (.*?)(?:\n|$)/)?.[1]?.trim(),
|
||||
})) || [],
|
||||
};
|
||||
|
||||
listJson.sections.push(sectionData);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await instance.listMessage(listJson);
|
||||
} else if (formattedText.includes('[buttons]')) {
|
||||
const buttonJson = {
|
||||
number: remoteJid.split('@')[0],
|
||||
thumbnailUrl: undefined,
|
||||
title: '',
|
||||
description: '',
|
||||
footer: '',
|
||||
buttons: [],
|
||||
};
|
||||
|
||||
const thumbnailUrlMatch = formattedText.match(/\[thumbnailUrl\]([\s\S]*?)(?=\[title\])/);
|
||||
const titleMatch = formattedText.match(/\[title\]([\s\S]*?)(?=\[description\])/);
|
||||
const descriptionMatch = formattedText.match(/\[description\]([\s\S]*?)(?=\[footer\])/);
|
||||
const footerMatch = formattedText.match(/\[footer\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url))/);
|
||||
|
||||
if (titleMatch) buttonJson.title = titleMatch[1].trim();
|
||||
if (thumbnailUrlMatch) buttonJson.thumbnailUrl = thumbnailUrlMatch[1].trim();
|
||||
if (descriptionMatch) buttonJson.description = descriptionMatch[1].trim();
|
||||
if (footerMatch) buttonJson.footer = footerMatch[1].trim();
|
||||
|
||||
const buttonTypes = {
|
||||
reply: /\[reply\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url)|$)/g,
|
||||
pix: /\[pix\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url)|$)/g,
|
||||
copy: /\[copy\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url)|$)/g,
|
||||
call: /\[call\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url)|$)/g,
|
||||
url: /\[url\]([\s\S]*?)(?=\[(?:reply|pix|copy|call|url)|$)/g,
|
||||
};
|
||||
|
||||
for (const [type, pattern] of Object.entries(buttonTypes)) {
|
||||
let match;
|
||||
while ((match = pattern.exec(formattedText)) !== null) {
|
||||
const content = match[1].trim();
|
||||
const button: any = { type };
|
||||
|
||||
switch (type) {
|
||||
case 'pix':
|
||||
button.currency = content.match(/currency: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
button.name = content.match(/name: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
button.keyType = content.match(/keyType: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
button.key = content.match(/key: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
break;
|
||||
|
||||
case 'reply':
|
||||
button.displayText = content.match(/displayText: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
button.id = content.match(/id: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
break;
|
||||
|
||||
case 'copy':
|
||||
button.displayText = content.match(/displayText: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
button.copyCode = content.match(/copyCode: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
break;
|
||||
|
||||
case 'call':
|
||||
button.displayText = content.match(/displayText: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
button.phoneNumber = content.match(/phone: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
break;
|
||||
|
||||
case 'url':
|
||||
button.displayText = content.match(/displayText: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
button.url = content.match(/url: (.*?)(?:\n|$)/)?.[1]?.trim();
|
||||
break;
|
||||
}
|
||||
|
||||
if (Object.keys(button).length > 1) {
|
||||
buttonJson.buttons.push(button);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await instance.buttonMessage(buttonJson);
|
||||
} else {
|
||||
await instance.textMessage(
|
||||
{
|
||||
number: remoteJid.split('@')[0],
|
||||
delay: settings?.delayMessage || 1000,
|
||||
text: formattedText,
|
||||
},
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
sendTelemetry('/message/sendText');
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
SendLocationDto,
|
||||
SendMediaDto,
|
||||
SendPollDto,
|
||||
SendPtvDto,
|
||||
SendReactionDto,
|
||||
SendStatusDto,
|
||||
SendStickerDto,
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
locationMessageSchema,
|
||||
mediaMessageSchema,
|
||||
pollMessageSchema,
|
||||
ptvMessageSchema,
|
||||
reactionMessageSchema,
|
||||
statusMessageSchema,
|
||||
stickerMessageSchema,
|
||||
@@ -71,6 +73,18 @@ export class MessageRouter extends RouterBroker {
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('sendPtv'), ...guards, upload.single('file'), async (req, res) => {
|
||||
const bodyData = req.body;
|
||||
|
||||
const response = await this.dataValidate<SendPtvDto>({
|
||||
request: req,
|
||||
schema: ptvMessageSchema,
|
||||
ClassRef: SendPtvDto,
|
||||
execute: (instance) => sendMessageController.sendPtv(instance, bodyData, req.file as any),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('sendWhatsAppAudio'), ...guards, upload.single('file'), async (req, res) => {
|
||||
const bodyData = req.body;
|
||||
|
||||
|
||||
@@ -60,23 +60,25 @@ export class WAMonitoringService {
|
||||
}
|
||||
|
||||
public async instanceInfo(instanceNames?: string[]): Promise<any> {
|
||||
const inexistentInstances = instanceNames ? instanceNames.filter((instance) => !this.waInstances[instance]) : [];
|
||||
if (instanceNames && instanceNames.length > 0) {
|
||||
const inexistentInstances = instanceNames ? instanceNames.filter((instance) => !this.waInstances[instance]) : [];
|
||||
|
||||
if (inexistentInstances.length > 0) {
|
||||
throw new NotFoundException(
|
||||
`Instance${inexistentInstances.length > 1 ? 's' : ''} "${inexistentInstances.join(', ')}" not found`,
|
||||
);
|
||||
if (inexistentInstances.length > 0) {
|
||||
throw new NotFoundException(
|
||||
`Instance${inexistentInstances.length > 1 ? 's' : ''} "${inexistentInstances.join(', ')}" not found`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const clientName = this.configService.get<Database>('DATABASE').CONNECTION.CLIENT_NAME;
|
||||
|
||||
const where = instanceNames
|
||||
const where = instanceNames && instanceNames.length > 0
|
||||
? {
|
||||
name: {
|
||||
in: instanceNames,
|
||||
},
|
||||
clientName,
|
||||
}
|
||||
name: {
|
||||
in: instanceNames,
|
||||
},
|
||||
clientName,
|
||||
}
|
||||
: { clientName };
|
||||
|
||||
const instances = await this.prismaRepository.instance.findMany({
|
||||
@@ -123,7 +125,9 @@ export class WAMonitoringService {
|
||||
throw new NotFoundException(`Instance "${instanceName}" not found`);
|
||||
}
|
||||
|
||||
return this.instanceInfo([instanceName]);
|
||||
const instanceNames = instanceName ? [instanceName] : null;
|
||||
|
||||
return this.instanceInfo(instanceNames);
|
||||
}
|
||||
|
||||
public async cleaningUp(instanceName: string) {
|
||||
|
||||
@@ -131,7 +131,7 @@ export declare namespace wa {
|
||||
export type StatusMessage = 'ERROR' | 'PENDING' | 'SERVER_ACK' | 'DELIVERY_ACK' | 'READ' | 'DELETED' | 'PLAYED';
|
||||
}
|
||||
|
||||
export const TypeMediaMessage = ['imageMessage', 'documentMessage', 'audioMessage', 'videoMessage', 'stickerMessage'];
|
||||
export const TypeMediaMessage = ['imageMessage', 'documentMessage', 'audioMessage', 'videoMessage', 'stickerMessage', 'ptvMessage'];
|
||||
|
||||
export const MessageSubtype = [
|
||||
'ephemeralMessage',
|
||||
|
||||
@@ -15,8 +15,9 @@ const getTypeMessage = (msg: any) => {
|
||||
msg?.message?.viewOnceMessageV2?.message?.imageMessage?.url ||
|
||||
msg?.message?.viewOnceMessageV2?.message?.videoMessage?.url ||
|
||||
msg?.message?.viewOnceMessageV2?.message?.audioMessage?.url,
|
||||
listResponseMessage: msg?.message?.listResponseMessage?.singleSelectReply?.selectedRowId,
|
||||
listResponseMessage: msg?.message?.listResponseMessage?.title,
|
||||
responseRowId: msg?.message?.listResponseMessage?.singleSelectReply?.selectedRowId,
|
||||
templateButtonReplyMessage: msg?.message?.templateButtonReplyMessage?.selectedId,
|
||||
// Medias
|
||||
audioMessage: msg?.message?.speechToText
|
||||
? msg?.message?.speechToText
|
||||
|
||||
@@ -122,6 +122,32 @@ export const mediaMessageSchema: JSONSchema7 = {
|
||||
required: ['number', 'mediatype'],
|
||||
};
|
||||
|
||||
export const ptvMessageSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
number: { ...numberDefinition },
|
||||
video: { type: 'string' },
|
||||
delay: {
|
||||
type: 'integer',
|
||||
description: 'Enter a value in milliseconds',
|
||||
},
|
||||
quoted: { ...quotedOptionsSchema },
|
||||
everyOne: { type: 'boolean', enum: [true, false] },
|
||||
mentioned: {
|
||||
type: 'array',
|
||||
minItems: 1,
|
||||
uniqueItems: true,
|
||||
items: {
|
||||
type: 'string',
|
||||
pattern: '^\\d+',
|
||||
description: '"mentioned" must be an array of numeric strings',
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ['number'],
|
||||
};
|
||||
|
||||
export const audioMessageSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
@@ -387,14 +413,18 @@ export const buttonsMessageSchema: JSONSchema7 = {
|
||||
properties: {
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['reply', 'copy', 'url', 'call'],
|
||||
enum: ['reply', 'copy', 'url', 'call', 'pix'],
|
||||
},
|
||||
displayText: { type: 'string' },
|
||||
id: { type: 'string' },
|
||||
url: { type: 'string' },
|
||||
phoneNumber: { type: 'string' },
|
||||
currency: { type: 'string' },
|
||||
name: { type: 'string' },
|
||||
keyType: { type: 'string', enum: ['phone', 'email', 'cpf', 'cnpj', 'random'] },
|
||||
key: { type: 'string' },
|
||||
},
|
||||
required: ['type', 'displayText'],
|
||||
required: ['type'],
|
||||
...isNotEmpty('id', 'url', 'phoneNumber'),
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user