mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-12-19 11:52:20 -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_USE_SSL=true
|
||||||
# S3_REGION=eu-south
|
# 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.
|
# Define a global apikey to access all instances.
|
||||||
# OBS: This key must be inserted in the request header to create an instance.
|
# OBS: This key must be inserted in the request header to create an instance.
|
||||||
AUTHENTICATION_API_KEY=429683C4C977415CAAFCCE10F7D57E11
|
AUTHENTICATION_API_KEY=429683C4C977415CAAFCCE10F7D57E11
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
* Added unreadMessages to chats
|
* Added unreadMessages to chats
|
||||||
* Pusher event integration
|
* Pusher event integration
|
||||||
* Add support for splitMessages and timePerChar in Integrations
|
* Add support for splitMessages and timePerChar in Integrations
|
||||||
|
* Audio Converter via API
|
||||||
|
* Send PTV messages with Baileys
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
@@ -19,6 +21,8 @@
|
|||||||
* Add indexes to improve performance in Evolution
|
* Add indexes to improve performance in Evolution
|
||||||
* Add logical or permanent message deletion based on env config
|
* Add logical or permanent message deletion based on env config
|
||||||
* Add support for fetching multiple instances by key
|
* 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)
|
# 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:
|
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)
|
[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.
|
# Donate to the project.
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
services:
|
services:
|
||||||
api:
|
api:
|
||||||
container_name: evolution_api
|
container_name: evolution_api
|
||||||
image: atendai/evolution-api:v2.0.9-rc
|
image: atendai/evolution-api:homolog
|
||||||
restart: always
|
restart: always
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
- postgres
|
||||||
ports:
|
ports:
|
||||||
- 8080:8080
|
- 8080:8080
|
||||||
volumes:
|
volumes:
|
||||||
@@ -14,8 +17,38 @@ services:
|
|||||||
expose:
|
expose:
|
||||||
- 8080
|
- 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:
|
volumes:
|
||||||
evolution_instances:
|
evolution_instances:
|
||||||
|
evolution_redis:
|
||||||
|
postgres_data:
|
||||||
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
"build": "tsc --noEmit && tsup",
|
"build": "tsc --noEmit && tsup",
|
||||||
"start": "tsnd -r tsconfig-paths/register --files --transpile-only ./src/main.ts",
|
"start": "tsnd -r tsconfig-paths/register --files --transpile-only ./src/main.ts",
|
||||||
"start:prod": "node dist/main",
|
"start:prod": "node dist/main",
|
||||||
"dev:server": "clear && tsnd -r tsconfig-paths/register --files --transpile-only --respawn --ignore-watch node_modules ./src/main.ts",
|
"dev:server": "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",
|
"test": "tsnd -r tsconfig-paths/register --files --transpile-only --respawn --ignore-watch node_modules ./test/all.test.ts",
|
||||||
"lint": "eslint --fix --ext .ts src",
|
"lint": "eslint --fix --ext .ts src",
|
||||||
"db:generate": "node runWithProvider.js \"npx prisma generate --schema ./prisma/DATABASE_PROVIDER-schema.prisma\"",
|
"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\"",
|
"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",
|
"jsonschema": "^1.4.1",
|
||||||
"link-preview-js": "^3.0.4",
|
"link-preview-js": "^3.0.4",
|
||||||
"long": "^5.2.3",
|
"long": "^5.2.3",
|
||||||
|
"mediainfo.js": "^0.3.2",
|
||||||
"mime": "^3.0.0",
|
"mime": "^3.0.0",
|
||||||
"minio": "^8.0.1",
|
"minio": "^8.0.1",
|
||||||
"multer": "^1.4.5-lts.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.instanceInfoById(instanceId, number);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.waMonitor.instanceInfo();
|
const instanceNames = instanceName ? [instanceName] : null;
|
||||||
|
|
||||||
|
return this.waMonitor.instanceInfo(instanceNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setPresence({ instanceName }: InstanceDto, data: SetPresenceDto) {
|
public async setPresence({ instanceName }: InstanceDto, data: SetPresenceDto) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
SendLocationDto,
|
SendLocationDto,
|
||||||
SendMediaDto,
|
SendMediaDto,
|
||||||
SendPollDto,
|
SendPollDto,
|
||||||
|
SendPtvDto,
|
||||||
SendReactionDto,
|
SendReactionDto,
|
||||||
SendStatusDto,
|
SendStatusDto,
|
||||||
SendStickerDto,
|
SendStickerDto,
|
||||||
@@ -39,6 +40,13 @@ export class SendMessageController {
|
|||||||
throw new BadRequestException('Owned media must be a url or base64');
|
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) {
|
public async sendSticker({ instanceName }: InstanceDto, data: SendStickerDto, file?: any) {
|
||||||
if (file || isURL(data.sticker) || isBase64(data.sticker)) {
|
if (file || isURL(data.sticker) || isBase64(data.sticker)) {
|
||||||
return await this.waMonitor.waInstances[instanceName].mediaSticker(data, file);
|
return await this.waMonitor.waInstances[instanceName].mediaSticker(data, file);
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export class SendPollDto extends Metadata {
|
|||||||
messageSecret?: Uint8Array;
|
messageSecret?: Uint8Array;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MediaType = 'image' | 'document' | 'video' | 'audio';
|
export type MediaType = 'image' | 'document' | 'video' | 'audio' | 'ptv';
|
||||||
|
|
||||||
export class SendMediaDto extends Metadata {
|
export class SendMediaDto extends Metadata {
|
||||||
mediatype: MediaType;
|
mediatype: MediaType;
|
||||||
@@ -82,6 +82,10 @@ export class SendMediaDto extends Metadata {
|
|||||||
media: string;
|
media: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class SendPtvDto extends Metadata {
|
||||||
|
video: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class SendStickerDto extends Metadata {
|
export class SendStickerDto extends Metadata {
|
||||||
sticker: string;
|
sticker: string;
|
||||||
}
|
}
|
||||||
@@ -90,15 +94,21 @@ export class SendAudioDto extends Metadata {
|
|||||||
audio: string;
|
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 {
|
export class Button {
|
||||||
type: TypeButton;
|
type: TypeButton;
|
||||||
displayText: string;
|
displayText?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
copyCode?: string;
|
copyCode?: string;
|
||||||
phoneNumber?: string;
|
phoneNumber?: string;
|
||||||
|
currency?: string;
|
||||||
|
name?: string;
|
||||||
|
keyType?: KeyType;
|
||||||
|
key?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SendButtonsDto extends Metadata {
|
export class SendButtonsDto extends Metadata {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { NumberBusiness } from '@api/dto/chat.dto';
|
import { NumberBusiness } from '@api/dto/chat.dto';
|
||||||
import {
|
import {
|
||||||
Button,
|
|
||||||
ContactMessage,
|
ContactMessage,
|
||||||
MediaMessage,
|
MediaMessage,
|
||||||
Options,
|
Options,
|
||||||
@@ -13,7 +12,6 @@ import {
|
|||||||
SendReactionDto,
|
SendReactionDto,
|
||||||
SendTemplateDto,
|
SendTemplateDto,
|
||||||
SendTextDto,
|
SendTextDto,
|
||||||
TypeButton,
|
|
||||||
} from '@api/dto/sendMessage.dto';
|
} from '@api/dto/sendMessage.dto';
|
||||||
import * as s3Service from '@api/integrations/storage/s3/libs/minio.server';
|
import * as s3Service from '@api/integrations/storage/s3/libs/minio.server';
|
||||||
import { ProviderFiles } from '@api/provider/sessions';
|
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 { BadRequestException, InternalServerErrorException } from '@exceptions';
|
||||||
import { status } from '@utils/renderStatus';
|
import { status } from '@utils/renderStatus';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { proto } from 'baileys';
|
|
||||||
import { arrayUnique, isURL } from 'class-validator';
|
import { arrayUnique, isURL } from 'class-validator';
|
||||||
import EventEmitter2 from 'eventemitter2';
|
import EventEmitter2 from 'eventemitter2';
|
||||||
import FormData from 'form-data';
|
import FormData from 'form-data';
|
||||||
import { createReadStream } from 'fs';
|
import { createReadStream } from 'fs';
|
||||||
import mime from 'mime';
|
import mime from 'mime';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { v4 } from 'uuid';
|
|
||||||
|
|
||||||
export class BusinessStartupService extends ChannelStartupService {
|
export class BusinessStartupService extends ChannelStartupService {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -1112,97 +1108,42 @@ export class BusinessStartupService extends ChannelStartupService {
|
|||||||
return audioSent;
|
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) {
|
public async buttonMessage(data: SendButtonsDto) {
|
||||||
const generate = await (async () => {
|
const embeddedMedia: any = {};
|
||||||
if (data?.thumbnailUrl) {
|
|
||||||
return await this.prepareMediaMessage({
|
|
||||||
mediatype: 'image',
|
|
||||||
media: data.thumbnailUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
const buttons = data.buttons.map((value) => {
|
const btnItems = {
|
||||||
return {
|
text: data.buttons.map((btn) => btn.displayText),
|
||||||
name: this.mapType.get(value.type),
|
ids: data.buttons.map((btn) => btn.id),
|
||||||
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(),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return await this.sendMessageWithTyping(data.number, message, {
|
if (!arrayUnique(btnItems.text) || !arrayUnique(btnItems.ids)) {
|
||||||
delay: data?.delay,
|
throw new BadRequestException('Button texts cannot be repeated', 'Button IDs cannot be repeated.');
|
||||||
presence: 'composing',
|
}
|
||||||
quoted: data?.quoted,
|
|
||||||
mentionsEveryOne: data?.mentionsEveryOne,
|
return await this.sendMessageWithTyping(
|
||||||
mentioned: data?.mentioned,
|
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) {
|
public async locationMessage(data: SendLocationDto) {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import { HandleLabelDto, LabelDto } from '@api/dto/label.dto';
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
ContactMessage,
|
ContactMessage,
|
||||||
|
KeyType,
|
||||||
MediaMessage,
|
MediaMessage,
|
||||||
Options,
|
Options,
|
||||||
SendAudioDto,
|
SendAudioDto,
|
||||||
@@ -42,6 +43,7 @@ import {
|
|||||||
SendLocationDto,
|
SendLocationDto,
|
||||||
SendMediaDto,
|
SendMediaDto,
|
||||||
SendPollDto,
|
SendPollDto,
|
||||||
|
SendPtvDto,
|
||||||
SendReactionDto,
|
SendReactionDto,
|
||||||
SendStatusDto,
|
SendStatusDto,
|
||||||
SendStickerDto,
|
SendStickerDto,
|
||||||
@@ -125,6 +127,7 @@ import { isArray, isBase64, isURL } from 'class-validator';
|
|||||||
import { randomBytes } from 'crypto';
|
import { randomBytes } from 'crypto';
|
||||||
import EventEmitter2 from 'eventemitter2';
|
import EventEmitter2 from 'eventemitter2';
|
||||||
import ffmpeg from 'fluent-ffmpeg';
|
import ffmpeg from 'fluent-ffmpeg';
|
||||||
|
import FormData from 'form-data';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import Long from 'long';
|
import Long from 'long';
|
||||||
import mime from 'mime';
|
import mime from 'mime';
|
||||||
@@ -136,11 +139,71 @@ import P from 'pino';
|
|||||||
import qrcode, { QRCodeToDataURLOptions } from 'qrcode';
|
import qrcode, { QRCodeToDataURLOptions } from 'qrcode';
|
||||||
import qrcodeTerminal from 'qrcode-terminal';
|
import qrcodeTerminal from 'qrcode-terminal';
|
||||||
import sharp from 'sharp';
|
import sharp from 'sharp';
|
||||||
import { PassThrough } from 'stream';
|
import { PassThrough, Readable } from 'stream';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
const groupMetadataCache = new CacheService(new CacheEngine(configService, 'groups').getEngine());
|
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 {
|
export class BaileysStartupService extends ChannelStartupService {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly configService: ConfigService,
|
public readonly configService: ConfigService,
|
||||||
@@ -1070,6 +1133,25 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
if (settings?.groupsIgnore && received.key.remoteJid.includes('@g.us')) {
|
if (settings?.groupsIgnore && received.key.remoteJid.includes('@g.us')) {
|
||||||
continue;
|
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);
|
const messageRaw = this.prepareMessage(received);
|
||||||
|
|
||||||
@@ -1079,6 +1161,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
received?.message?.stickerMessage ||
|
received?.message?.stickerMessage ||
|
||||||
received?.message?.documentMessage ||
|
received?.message?.documentMessage ||
|
||||||
received?.message?.documentWithCaptionMessage ||
|
received?.message?.documentWithCaptionMessage ||
|
||||||
|
received?.message?.ptvMessage ||
|
||||||
received?.message?.audioMessage;
|
received?.message?.audioMessage;
|
||||||
|
|
||||||
if (this.localSettings.readMessages && received.key.id !== 'status@broadcast') {
|
if (this.localSettings.readMessages && received.key.id !== 'status@broadcast') {
|
||||||
@@ -1385,6 +1468,26 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
await this.prismaRepository.messageUpdate.create({
|
await this.prismaRepository.messageUpdate.create({
|
||||||
data: message,
|
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(),
|
website: business?.website?.shift(),
|
||||||
};
|
};
|
||||||
} else {
|
} 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);
|
const business = await this.fetchBusinessProfile(jid);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -2051,8 +2155,10 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
messageSent?.message?.imageMessage ||
|
messageSent?.message?.imageMessage ||
|
||||||
messageSent?.message?.videoMessage ||
|
messageSent?.message?.videoMessage ||
|
||||||
messageSent?.message?.stickerMessage ||
|
messageSent?.message?.stickerMessage ||
|
||||||
|
messageSent?.message?.ptvMessage ||
|
||||||
messageSent?.message?.documentMessage ||
|
messageSent?.message?.documentMessage ||
|
||||||
messageSent?.message?.documentWithCaptionMessage ||
|
messageSent?.message?.documentWithCaptionMessage ||
|
||||||
|
messageSent?.message?.ptvMessage ||
|
||||||
messageSent?.message?.audioMessage;
|
messageSent?.message?.audioMessage;
|
||||||
|
|
||||||
if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled && !isIntegration) {
|
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) {
|
private async prepareMediaMessage(mediaMessage: MediaMessage) {
|
||||||
try {
|
try {
|
||||||
|
const type = mediaMessage.mediatype === 'ptv' ? 'video' : mediaMessage.mediatype;
|
||||||
|
|
||||||
const prepareMedia = await prepareWAMessageMedia(
|
const prepareMedia = await prepareWAMessageMedia(
|
||||||
{
|
{
|
||||||
[mediaMessage.mediatype]: isURL(mediaMessage.media)
|
[type]: isURL(mediaMessage.media) ? { url: mediaMessage.media } : Buffer.from(mediaMessage.media, 'base64'),
|
||||||
? { url: mediaMessage.media }
|
|
||||||
: Buffer.from(mediaMessage.media, 'base64'),
|
|
||||||
} as any,
|
} as any,
|
||||||
{ upload: this.client.waUploadToServer },
|
{ 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].caption = mediaMessage?.caption;
|
||||||
prepareMedia[mediaType].mimetype = mimetype;
|
prepareMedia[mediaType].mimetype = mimetype;
|
||||||
prepareMedia[mediaType].fileName = mediaMessage.fileName;
|
prepareMedia[mediaType].fileName = mediaMessage.fileName;
|
||||||
@@ -2563,6 +2703,37 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
return mediaSent;
|
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) {
|
public async processAudioMp4(audio: string) {
|
||||||
let inputStream: PassThrough;
|
let inputStream: PassThrough;
|
||||||
|
|
||||||
@@ -2631,53 +2802,78 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async processAudio(audio: string): Promise<Buffer> {
|
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)) {
|
if (isURL(audio)) {
|
||||||
const timestamp = new Date().getTime();
|
formData.append('url', audio);
|
||||||
const url = `${audio}?timestamp=${timestamp}`;
|
} else {
|
||||||
|
formData.append('base64', audio);
|
||||||
|
}
|
||||||
|
|
||||||
const config: any = {
|
const { data } = await axios.post(process.env.API_AUDIO_CONVERTER, formData, {
|
||||||
responseType: 'stream',
|
headers: {
|
||||||
};
|
...formData.getHeaders(),
|
||||||
|
apikey: process.env.API_AUDIO_CONVERTER_KEY,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const response = await axios.get(url, config);
|
if (!data.audio) {
|
||||||
inputAudioStream = response.data.pipe(new PassThrough());
|
throw new InternalServerErrorException('Failed to convert audio');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.verbose('Audio converted');
|
||||||
|
return Buffer.from(data.audio, 'base64');
|
||||||
} else {
|
} else {
|
||||||
const audioBuffer = Buffer.from(audio, 'base64');
|
let inputAudioStream: PassThrough;
|
||||||
inputAudioStream = new PassThrough();
|
|
||||||
inputAudioStream.end(audioBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
if (isURL(audio)) {
|
||||||
const outputAudioStream = new PassThrough();
|
const timestamp = new Date().getTime();
|
||||||
const chunks: Buffer[] = [];
|
const url = `${audio}?timestamp=${timestamp}`;
|
||||||
|
|
||||||
outputAudioStream.on('data', (chunk) => chunks.push(chunk));
|
const config: any = {
|
||||||
outputAudioStream.on('end', () => {
|
responseType: 'stream',
|
||||||
const outputBuffer = Buffer.concat(chunks);
|
};
|
||||||
resolve(outputBuffer);
|
|
||||||
});
|
|
||||||
|
|
||||||
outputAudioStream.on('error', (error) => {
|
const response = await axios.get(url, config);
|
||||||
console.log('error', error);
|
inputAudioStream = response.data.pipe(new PassThrough());
|
||||||
reject(error);
|
} 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)
|
outputAudioStream.on('data', (chunk) => chunks.push(chunk));
|
||||||
.outputFormat('ogg')
|
outputAudioStream.on('end', () => {
|
||||||
.noVideo()
|
const outputBuffer = Buffer.concat(chunks);
|
||||||
.audioCodec('libopus')
|
resolve(outputBuffer);
|
||||||
.addOutputOptions('-avoid_negative_ts make_zero')
|
});
|
||||||
.audioChannels(1)
|
|
||||||
.pipe(outputAudioStream, { end: true })
|
outputAudioStream.on('error', (error) => {
|
||||||
.on('error', function (error) {
|
|
||||||
console.log('error', error);
|
console.log('error', error);
|
||||||
reject(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) {
|
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 {
|
private toJSONString(button: Button): string {
|
||||||
const toString = (obj: any) => JSON.stringify(obj);
|
const toString = (obj: any) => JSON.stringify(obj);
|
||||||
|
|
||||||
@@ -2740,6 +2945,49 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
url: button.url,
|
url: button.url,
|
||||||
merchant_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]?.() || '';
|
return json[button.type]?.() || '';
|
||||||
@@ -2750,9 +2998,75 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
['copy', 'cta_copy'],
|
['copy', 'cta_copy'],
|
||||||
['url', 'cta_url'],
|
['url', 'cta_url'],
|
||||||
['call', 'cta_call'],
|
['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) {
|
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 () => {
|
const generate = await (async () => {
|
||||||
if (data?.thumbnailUrl) {
|
if (data?.thumbnailUrl) {
|
||||||
return await this.prepareMediaMessage({
|
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, {
|
return await this.sendMessageWithTyping(data.number, message, {
|
||||||
delay: data?.delay,
|
delay: data?.delay,
|
||||||
presence: 'composing',
|
presence: 'composing',
|
||||||
|
|||||||
@@ -401,7 +401,6 @@ export class ChatwootService {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(error);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -933,9 +932,11 @@ export class ChatwootService {
|
|||||||
) {
|
) {
|
||||||
if (sourceId && this.isImportHistoryAvailable()) {
|
if (sourceId && this.isImportHistoryAvailable()) {
|
||||||
const messageAlreadySaved = await chatwootImport.getExistingSourceIds([sourceId]);
|
const messageAlreadySaved = await chatwootImport.getExistingSourceIds([sourceId]);
|
||||||
if (messageAlreadySaved.size > 0) {
|
if (messageAlreadySaved) {
|
||||||
this.logger.warn('Message already saved on chatwoot');
|
if (messageAlreadySaved.size > 0) {
|
||||||
return null;
|
this.logger.warn('Message already saved on chatwoot');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const data = new FormData();
|
const data = new FormData();
|
||||||
@@ -2442,57 +2443,61 @@ export class ChatwootService {
|
|||||||
chatwootConfig: ChatwootDto,
|
chatwootConfig: ChatwootDto,
|
||||||
prepareMessage: (message: any) => any,
|
prepareMessage: (message: any) => any,
|
||||||
) {
|
) {
|
||||||
if (!this.isImportHistoryAvailable()) {
|
try {
|
||||||
return;
|
if (!this.isImportHistoryAvailable()) {
|
||||||
}
|
return;
|
||||||
if (!this.configService.get<Database>('DATABASE').SAVE_DATA.MESSAGE_UPDATE) {
|
}
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Long.isLong(m?.messageTimestamp)) {
|
const inbox = await this.getInbox(instance);
|
||||||
m.messageTimestamp = m.messageTimestamp?.toNumber();
|
|
||||||
|
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>> {
|
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;
|
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(
|
public async importHistoryMessages(
|
||||||
|
|||||||
@@ -64,17 +64,25 @@ export class DifyController extends ChatbotController implements ChatbotControll
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!data.expire) data.expire = defaultSettingCheck?.expire || 0;
|
if (data.expire === undefined || data.expire === null) data.expire = defaultSettingCheck.expire;
|
||||||
if (!data.keywordFinish) data.keywordFinish = defaultSettingCheck?.keywordFinish || '';
|
if (data.keywordFinish === undefined || data.keywordFinish === null)
|
||||||
if (!data.delayMessage) data.delayMessage = defaultSettingCheck?.delayMessage || 1000;
|
data.keywordFinish = defaultSettingCheck.keywordFinish;
|
||||||
if (!data.unknownMessage) data.unknownMessage = defaultSettingCheck?.unknownMessage || '';
|
if (data.delayMessage === undefined || data.delayMessage === null)
|
||||||
if (!data.listeningFromMe) data.listeningFromMe = defaultSettingCheck?.listeningFromMe || false;
|
data.delayMessage = defaultSettingCheck.delayMessage;
|
||||||
if (!data.stopBotFromMe) data.stopBotFromMe = defaultSettingCheck?.stopBotFromMe || false;
|
if (data.unknownMessage === undefined || data.unknownMessage === null)
|
||||||
if (!data.keepOpen) data.keepOpen = defaultSettingCheck?.keepOpen || false;
|
data.unknownMessage = defaultSettingCheck.unknownMessage;
|
||||||
if (!data.debounceTime) data.debounceTime = defaultSettingCheck?.debounceTime || 0;
|
if (data.listeningFromMe === undefined || data.listeningFromMe === null)
|
||||||
if (!data.ignoreJids) data.ignoreJids = defaultSettingCheck?.ignoreJids || [];
|
data.listeningFromMe = defaultSettingCheck.listeningFromMe;
|
||||||
if (!data.splitMessages) data.splitMessages = defaultSettingCheck?.splitMessages || false;
|
if (data.stopBotFromMe === undefined || data.stopBotFromMe === null)
|
||||||
if (!data.timePerChar) data.timePerChar = defaultSettingCheck?.timePerChar || 0;
|
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) {
|
if (!defaultSettingCheck) {
|
||||||
await this.settings(instance, {
|
await this.settings(instance, {
|
||||||
@@ -788,15 +796,15 @@ export class DifyController extends ChatbotController implements ChatbotControll
|
|||||||
let splitMessages = findBot?.splitMessages;
|
let splitMessages = findBot?.splitMessages;
|
||||||
let timePerChar = findBot?.timePerChar;
|
let timePerChar = findBot?.timePerChar;
|
||||||
|
|
||||||
if (!expire) expire = settings.expire;
|
if (expire === undefined || expire === null) expire = settings.expire;
|
||||||
if (!keywordFinish) keywordFinish = settings.keywordFinish;
|
if (keywordFinish === undefined || keywordFinish === null) keywordFinish = settings.keywordFinish;
|
||||||
if (!delayMessage) delayMessage = settings.delayMessage;
|
if (delayMessage === undefined || delayMessage === null) delayMessage = settings.delayMessage;
|
||||||
if (!unknownMessage) unknownMessage = settings.unknownMessage;
|
if (unknownMessage === undefined || unknownMessage === null) unknownMessage = settings.unknownMessage;
|
||||||
if (!listeningFromMe) listeningFromMe = settings.listeningFromMe;
|
if (listeningFromMe === undefined || listeningFromMe === null) listeningFromMe = settings.listeningFromMe;
|
||||||
if (!stopBotFromMe) stopBotFromMe = settings.stopBotFromMe;
|
if (stopBotFromMe === undefined || stopBotFromMe === null) stopBotFromMe = settings.stopBotFromMe;
|
||||||
if (!keepOpen) keepOpen = settings.keepOpen;
|
if (keepOpen === undefined || keepOpen === null) keepOpen = settings.keepOpen;
|
||||||
if (debounceTime === undefined || debounceTime === null) debounceTime = settings.debounceTime;
|
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 (splitMessages === undefined || splitMessages === null) splitMessages = settings?.splitMessages ?? false;
|
||||||
if (timePerChar === undefined || timePerChar === null) timePerChar = settings?.timePerChar ?? 0;
|
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.expire === undefined || data.expire === null) data.expire = defaultSettingCheck.expire;
|
||||||
if (!data.keywordFinish) data.keywordFinish = defaultSettingCheck?.keywordFinish || '';
|
if (data.keywordFinish === undefined || data.keywordFinish === null)
|
||||||
if (!data.delayMessage) data.delayMessage = defaultSettingCheck?.delayMessage || 1000;
|
data.keywordFinish = defaultSettingCheck.keywordFinish;
|
||||||
if (!data.unknownMessage) data.unknownMessage = defaultSettingCheck?.unknownMessage || '';
|
if (data.delayMessage === undefined || data.delayMessage === null)
|
||||||
if (!data.listeningFromMe) data.listeningFromMe = defaultSettingCheck?.listeningFromMe || false;
|
data.delayMessage = defaultSettingCheck.delayMessage;
|
||||||
if (!data.stopBotFromMe) data.stopBotFromMe = defaultSettingCheck?.stopBotFromMe || false;
|
if (data.unknownMessage === undefined || data.unknownMessage === null)
|
||||||
if (!data.keepOpen) data.keepOpen = defaultSettingCheck?.keepOpen || false;
|
data.unknownMessage = defaultSettingCheck.unknownMessage;
|
||||||
if (!data.debounceTime) data.debounceTime = defaultSettingCheck?.debounceTime || 0;
|
if (data.listeningFromMe === undefined || data.listeningFromMe === null)
|
||||||
if (!data.ignoreJids) data.ignoreJids = defaultSettingCheck?.ignoreJids || [];
|
data.listeningFromMe = defaultSettingCheck.listeningFromMe;
|
||||||
if (!data.splitMessages) data.splitMessages = defaultSettingCheck?.splitMessages || false;
|
if (data.stopBotFromMe === undefined || data.stopBotFromMe === null)
|
||||||
if (!data.timePerChar) data.timePerChar = defaultSettingCheck?.timePerChar || 0;
|
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) {
|
if (!defaultSettingCheck) {
|
||||||
await this.settings(instance, {
|
await this.settings(instance, {
|
||||||
@@ -760,15 +768,15 @@ export class EvolutionBotController extends ChatbotController implements Chatbot
|
|||||||
let splitMessages = findBot?.splitMessages;
|
let splitMessages = findBot?.splitMessages;
|
||||||
let timePerChar = findBot?.timePerChar;
|
let timePerChar = findBot?.timePerChar;
|
||||||
|
|
||||||
if (!expire) expire = settings.expire;
|
if (expire === undefined || expire === null) expire = settings.expire;
|
||||||
if (!keywordFinish) keywordFinish = settings.keywordFinish;
|
if (keywordFinish === undefined || keywordFinish === null) keywordFinish = settings.keywordFinish;
|
||||||
if (!delayMessage) delayMessage = settings.delayMessage;
|
if (delayMessage === undefined || delayMessage === null) delayMessage = settings.delayMessage;
|
||||||
if (!unknownMessage) unknownMessage = settings.unknownMessage;
|
if (unknownMessage === undefined || unknownMessage === null) unknownMessage = settings.unknownMessage;
|
||||||
if (!listeningFromMe) listeningFromMe = settings.listeningFromMe;
|
if (listeningFromMe === undefined || listeningFromMe === null) listeningFromMe = settings.listeningFromMe;
|
||||||
if (!stopBotFromMe) stopBotFromMe = settings.stopBotFromMe;
|
if (stopBotFromMe === undefined || stopBotFromMe === null) stopBotFromMe = settings.stopBotFromMe;
|
||||||
if (!keepOpen) keepOpen = settings.keepOpen;
|
if (keepOpen === undefined || keepOpen === null) keepOpen = settings.keepOpen;
|
||||||
if (debounceTime === undefined || debounceTime === null) debounceTime = settings.debounceTime;
|
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 (splitMessages === undefined || splitMessages === null) splitMessages = settings?.splitMessages ?? false;
|
||||||
if (timePerChar === undefined || timePerChar === null) timePerChar = settings?.timePerChar ?? 0;
|
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.expire === undefined || data.expire === null) data.expire = defaultSettingCheck.expire;
|
||||||
if (!data.keywordFinish) data.keywordFinish = defaultSettingCheck?.keywordFinish || '';
|
if (data.keywordFinish === undefined || data.keywordFinish === null)
|
||||||
if (!data.delayMessage) data.delayMessage = defaultSettingCheck?.delayMessage || 1000;
|
data.keywordFinish = defaultSettingCheck.keywordFinish;
|
||||||
if (!data.unknownMessage) data.unknownMessage = defaultSettingCheck?.unknownMessage || '';
|
if (data.delayMessage === undefined || data.delayMessage === null)
|
||||||
if (!data.listeningFromMe) data.listeningFromMe = defaultSettingCheck?.listeningFromMe || false;
|
data.delayMessage = defaultSettingCheck.delayMessage;
|
||||||
if (!data.stopBotFromMe) data.stopBotFromMe = defaultSettingCheck?.stopBotFromMe || false;
|
if (data.unknownMessage === undefined || data.unknownMessage === null)
|
||||||
if (!data.keepOpen) data.keepOpen = defaultSettingCheck?.keepOpen || false;
|
data.unknownMessage = defaultSettingCheck.unknownMessage;
|
||||||
if (!data.debounceTime) data.debounceTime = defaultSettingCheck?.debounceTime || 0;
|
if (data.listeningFromMe === undefined || data.listeningFromMe === null)
|
||||||
if (!data.ignoreJids) data.ignoreJids = defaultSettingCheck?.ignoreJids || [];
|
data.listeningFromMe = defaultSettingCheck.listeningFromMe;
|
||||||
if (!data.splitMessages) data.splitMessages = defaultSettingCheck?.splitMessages || false;
|
if (data.stopBotFromMe === undefined || data.stopBotFromMe === null)
|
||||||
if (!data.timePerChar) data.timePerChar = defaultSettingCheck?.timePerChar || 0;
|
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) {
|
if (!defaultSettingCheck) {
|
||||||
await this.settings(instance, {
|
await this.settings(instance, {
|
||||||
@@ -760,15 +768,15 @@ export class FlowiseController extends ChatbotController implements ChatbotContr
|
|||||||
let splitMessages = findBot?.splitMessages;
|
let splitMessages = findBot?.splitMessages;
|
||||||
let timePerChar = findBot?.timePerChar;
|
let timePerChar = findBot?.timePerChar;
|
||||||
|
|
||||||
if (!expire) expire = settings.expire;
|
if (expire === undefined || expire === null) expire = settings.expire;
|
||||||
if (!keywordFinish) keywordFinish = settings.keywordFinish;
|
if (keywordFinish === undefined || keywordFinish === null) keywordFinish = settings.keywordFinish;
|
||||||
if (!delayMessage) delayMessage = settings.delayMessage;
|
if (delayMessage === undefined || delayMessage === null) delayMessage = settings.delayMessage;
|
||||||
if (!unknownMessage) unknownMessage = settings.unknownMessage;
|
if (unknownMessage === undefined || unknownMessage === null) unknownMessage = settings.unknownMessage;
|
||||||
if (!listeningFromMe) listeningFromMe = settings.listeningFromMe;
|
if (listeningFromMe === undefined || listeningFromMe === null) listeningFromMe = settings.listeningFromMe;
|
||||||
if (!stopBotFromMe) stopBotFromMe = settings.stopBotFromMe;
|
if (stopBotFromMe === undefined || stopBotFromMe === null) stopBotFromMe = settings.stopBotFromMe;
|
||||||
if (!keepOpen) keepOpen = settings.keepOpen;
|
if (keepOpen === undefined || keepOpen === null) keepOpen = settings.keepOpen;
|
||||||
if (debounceTime === undefined || debounceTime === null) debounceTime = settings.debounceTime;
|
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 (splitMessages === undefined || splitMessages === null) splitMessages = settings?.splitMessages ?? false;
|
||||||
if (timePerChar === undefined || timePerChar === null) timePerChar = settings?.timePerChar ?? 0;
|
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 === undefined || data.expire === null) data.expire = defaultSettingCheck.expire;
|
||||||
if (!data.expire) data.expire = defaultSettingCheck?.expire || 0;
|
if (data.keywordFinish === undefined || data.keywordFinish === null)
|
||||||
if (!data.keywordFinish) data.keywordFinish = defaultSettingCheck?.keywordFinish || '';
|
data.keywordFinish = defaultSettingCheck.keywordFinish;
|
||||||
if (!data.delayMessage) data.delayMessage = defaultSettingCheck?.delayMessage || 1000;
|
if (data.delayMessage === undefined || data.delayMessage === null)
|
||||||
if (!data.unknownMessage) data.unknownMessage = defaultSettingCheck?.unknownMessage || '';
|
data.delayMessage = defaultSettingCheck.delayMessage;
|
||||||
if (!data.listeningFromMe) data.listeningFromMe = defaultSettingCheck?.listeningFromMe || false;
|
if (data.unknownMessage === undefined || data.unknownMessage === null)
|
||||||
if (!data.stopBotFromMe) data.stopBotFromMe = defaultSettingCheck?.stopBotFromMe || false;
|
data.unknownMessage = defaultSettingCheck.unknownMessage;
|
||||||
if (!data.keepOpen) data.keepOpen = defaultSettingCheck?.keepOpen || false;
|
if (data.listeningFromMe === undefined || data.listeningFromMe === null)
|
||||||
if (!data.debounceTime) data.debounceTime = defaultSettingCheck?.debounceTime || 0;
|
data.listeningFromMe = defaultSettingCheck.listeningFromMe;
|
||||||
if (!data.ignoreJids) data.ignoreJids = defaultSettingCheck?.ignoreJids || [];
|
if (data.stopBotFromMe === undefined || data.stopBotFromMe === null)
|
||||||
if (!data.splitMessages) data.splitMessages = defaultSettingCheck?.splitMessages || false;
|
data.stopBotFromMe = defaultSettingCheck.stopBotFromMe;
|
||||||
if (!data.timePerChar) data.timePerChar = defaultSettingCheck?.timePerChar || 0;
|
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) {
|
if (!data.openaiCredsId) {
|
||||||
throw new Error('Openai Creds Id is required');
|
throw new Error('Openai Creds Id is required');
|
||||||
@@ -998,15 +1005,15 @@ export class OpenaiController extends ChatbotController implements ChatbotContro
|
|||||||
let splitMessages = findBot?.splitMessages;
|
let splitMessages = findBot?.splitMessages;
|
||||||
let timePerChar = findBot?.timePerChar;
|
let timePerChar = findBot?.timePerChar;
|
||||||
|
|
||||||
if (!expire) expire = settings.expire;
|
if (expire === undefined || expire === null) expire = settings.expire;
|
||||||
if (!keywordFinish) keywordFinish = settings.keywordFinish;
|
if (keywordFinish === undefined || keywordFinish === null) keywordFinish = settings.keywordFinish;
|
||||||
if (!delayMessage) delayMessage = settings.delayMessage;
|
if (delayMessage === undefined || delayMessage === null) delayMessage = settings.delayMessage;
|
||||||
if (!unknownMessage) unknownMessage = settings.unknownMessage;
|
if (unknownMessage === undefined || unknownMessage === null) unknownMessage = settings.unknownMessage;
|
||||||
if (!listeningFromMe) listeningFromMe = settings.listeningFromMe;
|
if (listeningFromMe === undefined || listeningFromMe === null) listeningFromMe = settings.listeningFromMe;
|
||||||
if (!stopBotFromMe) stopBotFromMe = settings.stopBotFromMe;
|
if (stopBotFromMe === undefined || stopBotFromMe === null) stopBotFromMe = settings.stopBotFromMe;
|
||||||
if (!keepOpen) keepOpen = settings.keepOpen;
|
if (keepOpen === undefined || keepOpen === null) keepOpen = settings.keepOpen;
|
||||||
if (debounceTime === undefined || debounceTime === null) debounceTime = settings.debounceTime;
|
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 (splitMessages === undefined || splitMessages === null) splitMessages = settings?.splitMessages ?? false;
|
||||||
if (timePerChar === undefined || timePerChar === null) timePerChar = settings?.timePerChar ?? 0;
|
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 listeningFromMe = data?.typebot?.listeningFromMe;
|
||||||
let stopBotFromMe = data?.typebot?.stopBotFromMe;
|
let stopBotFromMe = data?.typebot?.stopBotFromMe;
|
||||||
let keepOpen = data?.typebot?.keepOpen;
|
let keepOpen = data?.typebot?.keepOpen;
|
||||||
|
let debounceTime = data?.typebot?.debounceTime;
|
||||||
|
let ignoreJids = data?.typebot?.ignoreJids;
|
||||||
|
|
||||||
const defaultSettingCheck = await this.settingsRepository.findFirst({
|
const defaultSettingCheck = await this.settingsRepository.findFirst({
|
||||||
where: {
|
where: {
|
||||||
@@ -586,15 +588,20 @@ export class TypebotController extends ChatbotController implements ChatbotContr
|
|||||||
!unknownMessage ||
|
!unknownMessage ||
|
||||||
!listeningFromMe ||
|
!listeningFromMe ||
|
||||||
!stopBotFromMe ||
|
!stopBotFromMe ||
|
||||||
!keepOpen
|
!keepOpen ||
|
||||||
|
!debounceTime ||
|
||||||
|
!ignoreJids
|
||||||
) {
|
) {
|
||||||
if (!expire) expire = defaultSettingCheck?.expire || 0;
|
if (expire === undefined || expire === null) expire = defaultSettingCheck.expire;
|
||||||
if (!keywordFinish) keywordFinish = defaultSettingCheck?.keywordFinish || '#SAIR';
|
if (keywordFinish === undefined || keywordFinish === null) keywordFinish = defaultSettingCheck.keywordFinish;
|
||||||
if (!delayMessage) delayMessage = defaultSettingCheck?.delayMessage || 1000;
|
if (delayMessage === undefined || delayMessage === null) delayMessage = defaultSettingCheck.delayMessage;
|
||||||
if (!unknownMessage) unknownMessage = defaultSettingCheck?.unknownMessage || 'Desculpe, não entendi';
|
if (unknownMessage === undefined || unknownMessage === null) unknownMessage = defaultSettingCheck.unknownMessage;
|
||||||
if (!listeningFromMe) listeningFromMe = defaultSettingCheck?.listeningFromMe || false;
|
if (listeningFromMe === undefined || listeningFromMe === null)
|
||||||
if (!stopBotFromMe) stopBotFromMe = defaultSettingCheck?.stopBotFromMe || false;
|
listeningFromMe = defaultSettingCheck.listeningFromMe;
|
||||||
if (!keepOpen) keepOpen = defaultSettingCheck?.keepOpen || false;
|
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) {
|
if (!defaultSettingCheck) {
|
||||||
await this.settings(instance, {
|
await this.settings(instance, {
|
||||||
@@ -605,6 +612,8 @@ export class TypebotController extends ChatbotController implements ChatbotContr
|
|||||||
listeningFromMe: listeningFromMe,
|
listeningFromMe: listeningFromMe,
|
||||||
stopBotFromMe: stopBotFromMe,
|
stopBotFromMe: stopBotFromMe,
|
||||||
keepOpen: keepOpen,
|
keepOpen: keepOpen,
|
||||||
|
debounceTime: debounceTime,
|
||||||
|
ignoreJids: ignoreJids,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -980,15 +989,15 @@ export class TypebotController extends ChatbotController implements ChatbotContr
|
|||||||
let debounceTime = findBot?.debounceTime;
|
let debounceTime = findBot?.debounceTime;
|
||||||
let ignoreJids = findBot?.ignoreJids;
|
let ignoreJids = findBot?.ignoreJids;
|
||||||
|
|
||||||
if (!expire) expire = settings.expire;
|
if (expire === undefined || expire === null) expire = settings.expire;
|
||||||
if (!keywordFinish) keywordFinish = settings.keywordFinish;
|
if (keywordFinish === undefined || keywordFinish === null) keywordFinish = settings.keywordFinish;
|
||||||
if (!delayMessage) delayMessage = settings.delayMessage;
|
if (delayMessage === undefined || delayMessage === null) delayMessage = settings.delayMessage;
|
||||||
if (!unknownMessage) unknownMessage = settings.unknownMessage;
|
if (unknownMessage === undefined || unknownMessage === null) unknownMessage = settings.unknownMessage;
|
||||||
if (!listeningFromMe) listeningFromMe = settings.listeningFromMe;
|
if (listeningFromMe === undefined || listeningFromMe === null) listeningFromMe = settings.listeningFromMe;
|
||||||
if (!stopBotFromMe) stopBotFromMe = settings.stopBotFromMe;
|
if (stopBotFromMe === undefined || stopBotFromMe === null) stopBotFromMe = settings.stopBotFromMe;
|
||||||
if (!keepOpen) keepOpen = settings.keepOpen;
|
if (keepOpen === undefined || keepOpen === null) keepOpen = settings.keepOpen;
|
||||||
if (!debounceTime) debounceTime = settings.debounceTime;
|
if (debounceTime === undefined || debounceTime === null) debounceTime = settings.debounceTime;
|
||||||
if (!ignoreJids) ignoreJids = settings.ignoreJids;
|
if (ignoreJids === undefined || ignoreJids === null) ignoreJids = settings.ignoreJids;
|
||||||
|
|
||||||
if (this.checkIgnoreJids(ignoreJids, remoteJid)) return;
|
if (this.checkIgnoreJids(ignoreJids, remoteJid)) return;
|
||||||
|
|
||||||
|
|||||||
@@ -223,14 +223,130 @@ export class TypebotService {
|
|||||||
|
|
||||||
formattedText = formattedText.replace(/\n$/, '');
|
formattedText = formattedText.replace(/\n$/, '');
|
||||||
|
|
||||||
await instance.textMessage(
|
if (formattedText.includes('[list]')) {
|
||||||
{
|
const listJson = {
|
||||||
number: remoteJid.split('@')[0],
|
number: remoteJid.split('@')[0],
|
||||||
delay: settings?.delayMessage || 1000,
|
title: '',
|
||||||
text: formattedText,
|
description: '',
|
||||||
},
|
buttonText: '',
|
||||||
false,
|
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');
|
sendTelemetry('/message/sendText');
|
||||||
}
|
}
|
||||||
@@ -299,14 +415,130 @@ export class TypebotService {
|
|||||||
|
|
||||||
formattedText = formattedText.replace(/\n$/, '');
|
formattedText = formattedText.replace(/\n$/, '');
|
||||||
|
|
||||||
await instance.textMessage(
|
if (formattedText.includes('[list]')) {
|
||||||
{
|
const listJson = {
|
||||||
number: remoteJid.split('@')[0],
|
number: remoteJid.split('@')[0],
|
||||||
delay: settings?.delayMessage || 1000,
|
title: '',
|
||||||
text: formattedText,
|
description: '',
|
||||||
},
|
buttonText: '',
|
||||||
false,
|
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');
|
sendTelemetry('/message/sendText');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
SendLocationDto,
|
SendLocationDto,
|
||||||
SendMediaDto,
|
SendMediaDto,
|
||||||
SendPollDto,
|
SendPollDto,
|
||||||
|
SendPtvDto,
|
||||||
SendReactionDto,
|
SendReactionDto,
|
||||||
SendStatusDto,
|
SendStatusDto,
|
||||||
SendStickerDto,
|
SendStickerDto,
|
||||||
@@ -22,6 +23,7 @@ import {
|
|||||||
locationMessageSchema,
|
locationMessageSchema,
|
||||||
mediaMessageSchema,
|
mediaMessageSchema,
|
||||||
pollMessageSchema,
|
pollMessageSchema,
|
||||||
|
ptvMessageSchema,
|
||||||
reactionMessageSchema,
|
reactionMessageSchema,
|
||||||
statusMessageSchema,
|
statusMessageSchema,
|
||||||
stickerMessageSchema,
|
stickerMessageSchema,
|
||||||
@@ -71,6 +73,18 @@ export class MessageRouter extends RouterBroker {
|
|||||||
|
|
||||||
return res.status(HttpStatus.CREATED).json(response);
|
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) => {
|
.post(this.routerPath('sendWhatsAppAudio'), ...guards, upload.single('file'), async (req, res) => {
|
||||||
const bodyData = req.body;
|
const bodyData = req.body;
|
||||||
|
|
||||||
|
|||||||
@@ -60,23 +60,25 @@ export class WAMonitoringService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async instanceInfo(instanceNames?: string[]): Promise<any> {
|
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) {
|
if (inexistentInstances.length > 0) {
|
||||||
throw new NotFoundException(
|
throw new NotFoundException(
|
||||||
`Instance${inexistentInstances.length > 1 ? 's' : ''} "${inexistentInstances.join(', ')}" not found`,
|
`Instance${inexistentInstances.length > 1 ? 's' : ''} "${inexistentInstances.join(', ')}" not found`,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const clientName = this.configService.get<Database>('DATABASE').CONNECTION.CLIENT_NAME;
|
const clientName = this.configService.get<Database>('DATABASE').CONNECTION.CLIENT_NAME;
|
||||||
|
|
||||||
const where = instanceNames
|
const where = instanceNames && instanceNames.length > 0
|
||||||
? {
|
? {
|
||||||
name: {
|
name: {
|
||||||
in: instanceNames,
|
in: instanceNames,
|
||||||
},
|
},
|
||||||
clientName,
|
clientName,
|
||||||
}
|
}
|
||||||
: { clientName };
|
: { clientName };
|
||||||
|
|
||||||
const instances = await this.prismaRepository.instance.findMany({
|
const instances = await this.prismaRepository.instance.findMany({
|
||||||
@@ -123,7 +125,9 @@ export class WAMonitoringService {
|
|||||||
throw new NotFoundException(`Instance "${instanceName}" not found`);
|
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) {
|
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 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 = [
|
export const MessageSubtype = [
|
||||||
'ephemeralMessage',
|
'ephemeralMessage',
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ const getTypeMessage = (msg: any) => {
|
|||||||
msg?.message?.viewOnceMessageV2?.message?.imageMessage?.url ||
|
msg?.message?.viewOnceMessageV2?.message?.imageMessage?.url ||
|
||||||
msg?.message?.viewOnceMessageV2?.message?.videoMessage?.url ||
|
msg?.message?.viewOnceMessageV2?.message?.videoMessage?.url ||
|
||||||
msg?.message?.viewOnceMessageV2?.message?.audioMessage?.url,
|
msg?.message?.viewOnceMessageV2?.message?.audioMessage?.url,
|
||||||
listResponseMessage: msg?.message?.listResponseMessage?.singleSelectReply?.selectedRowId,
|
listResponseMessage: msg?.message?.listResponseMessage?.title,
|
||||||
responseRowId: msg?.message?.listResponseMessage?.singleSelectReply?.selectedRowId,
|
responseRowId: msg?.message?.listResponseMessage?.singleSelectReply?.selectedRowId,
|
||||||
|
templateButtonReplyMessage: msg?.message?.templateButtonReplyMessage?.selectedId,
|
||||||
// Medias
|
// Medias
|
||||||
audioMessage: msg?.message?.speechToText
|
audioMessage: msg?.message?.speechToText
|
||||||
? msg?.message?.speechToText
|
? msg?.message?.speechToText
|
||||||
|
|||||||
@@ -122,6 +122,32 @@ export const mediaMessageSchema: JSONSchema7 = {
|
|||||||
required: ['number', 'mediatype'],
|
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 = {
|
export const audioMessageSchema: JSONSchema7 = {
|
||||||
$id: v4(),
|
$id: v4(),
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -387,14 +413,18 @@ export const buttonsMessageSchema: JSONSchema7 = {
|
|||||||
properties: {
|
properties: {
|
||||||
type: {
|
type: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
enum: ['reply', 'copy', 'url', 'call'],
|
enum: ['reply', 'copy', 'url', 'call', 'pix'],
|
||||||
},
|
},
|
||||||
displayText: { type: 'string' },
|
displayText: { type: 'string' },
|
||||||
id: { type: 'string' },
|
id: { type: 'string' },
|
||||||
url: { type: 'string' },
|
url: { type: 'string' },
|
||||||
phoneNumber: { 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'),
|
...isNotEmpty('id', 'url', 'phoneNumber'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user