Compare commits

...

41 Commits
1.5.2 ... 1.5.4

Author SHA1 Message Date
Davidson Gomes
8fe75cd210 Merge branch 'release/1.5.4' 2023-10-09 20:43:40 -03:00
Davidson Gomes
303effebbc version: 1.5.4 2023-10-09 20:43:30 -03:00
Davidson Gomes
f32a34190d fix: adjusts logger 2023-10-09 20:17:01 -03:00
Davidson Gomes
8588ef1d8a fix: adjusts logger 2023-10-09 20:15:53 -03:00
Davidson Gomes
51ec4821f3 fix: adjusts logger 2023-10-09 20:14:50 -03:00
Davidson Gomes
957033a7bb ajustes 2023-10-09 19:57:26 -03:00
Davidson Gomes
62c74deac3 fix: Solved problem with duplicate messages in chatwoot 2023-10-09 18:00:14 -03:00
Davidson Gomes
29fd448998 Merge tag '1.5.3' into develop
* Swagger documentation
* Added base 64 sending option via webhook

* Remove rabbitmq queues when delete instances
* Improvement in restart instance to completely redo the connection
* Update node version: v20
* Correction of messages sent by the api and typebot not appearing in chatwoot
* Adjustment to start typebot, added startSession parameter
* Chatwoot now receives messages sent via api and typebot
* Fixed problem with starting with an input in typebot
* Added check to ensure variables are not empty before executing foreach in start typebot
2023-10-06 18:55:52 -03:00
Davidson Gomes
e55cb08a6a Merge branch 'release/1.5.3' 2023-10-06 18:55:44 -03:00
Davidson Gomes
047359e8dc version: 1.5.3 2023-10-06 18:55:36 -03:00
Davidson Gomes
eb814e181a adjusts in swagger 2023-10-06 18:45:26 -03:00
Davidson Gomes
857031ff5a adjusts in swagger 2023-10-06 18:05:37 -03:00
Davidson Gomes
d5eeb68714 feat: webhook base64 option 2023-10-05 17:05:41 -03:00
Davidson Gomes
966b287026 feat: webhook base64 option 2023-10-05 16:01:47 -03:00
Davidson Gomes
a348729109 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2023-10-05 15:59:59 -03:00
Davidson Gomes
1f29b7733e Merge pull request #163 from w3nder/develop
Fix: Variables null
2023-10-05 15:59:39 -03:00
Davidson Gomes
e26ae30f6f feat: webhook base64 option 2023-10-05 15:57:53 -03:00
Davidson Gomes
547943a05c Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2023-10-05 15:54:53 -03:00
Davidson Gomes
46aa229531 fix: adjusts in swagger 2023-10-05 15:54:46 -03:00
Davidson Gomes
28bd796289 Merge pull request #161 from moraisamilton/develop
Recuperar base64 de media enviada por webhook
2023-10-05 15:54:20 -03:00
Wender Teixeira
6a3f82ed7e Fix: Variables null 2023-10-05 11:43:18 -03:00
Amilton Morais
523f3301c0 Update wa.types.ts
Foi criado novas propriedades para recuperar Base64 da media enviada por webhook.
2023-10-03 17:20:14 -03:00
Amilton Morais
f085343a99 Update whatsapp.service.ts
Foi criado novo código para recuperar Base64 da media enviada por webhook.
2023-10-03 17:18:58 -03:00
Amilton Morais
f76a924700 Update webhook.service.ts
Foi criado novo variavel "webhook_base64:false' para retorno erro.
2023-10-03 17:13:58 -03:00
Amilton Morais
f8e3b76a4a Update webhook.model.ts
Foi criado novas propriedades para recuperar Base64 da media enviada por webhook.
2023-10-03 16:59:06 -03:00
Amilton Morais
b8f1e8a7ef Update instance.dto.ts
Foi criado novas propriedades "webhook_base64?: boolean;" para recuperar Base64 da media enviada por webhook.
2023-10-03 16:56:44 -03:00
Amilton Morais
d007fc49d8 Update instance.controller.ts
Foi criado novas propriedades para recuperar Base64 da media enviada por webhook.
2023-10-03 16:45:05 -03:00
Amilton Morais
f6d8ebd8d3 Update dev-env.yml
Inserido nova variável WEBHOOK_BASE64 para recuperar Base64 da media enviar por webhook.
2023-10-03 16:39:04 -03:00
Davidson Gomes
6ff9c4578a Fixed problem with starting with an input in typebot 2023-10-02 18:23:03 -03:00
Davidson Gomes
33acfe1464 chatwoot now receives messages sent via api and typebot 2023-10-02 17:59:54 -03:00
Davidson Gomes
f5eeb16bb1 Adjustment to start typebot, added startSession parameter 2023-10-02 17:05:22 -03:00
Davidson Gomes
c35c5faaa4 correction of messages sent by the api and typebot not appearing in chatwoot 2023-10-02 16:17:28 -03:00
Davidson Gomes
8425ebc13f Merge pull request #156 from EvolutionAPI/revert-154-evolution-api-1.5.2-develop-francis
Revert "Start Typebot com opção de ativar chatbot ou não"
2023-10-02 16:10:45 -03:00
Davidson Gomes
6fc37a4298 Revert "Start Typebot com opção de ativar chatbot ou não" 2023-10-02 19:10:34 +00:00
Davidson Gomes
99f3e77c12 Merge pull request #154 from francisbreit/evolution-api-1.5.2-develop-francis
Start Typebot com opção de ativar chatbot ou não
2023-10-02 16:10:29 -03:00
Davidson Gomes
a9c087c45f correction of messages sent by the api and typebot not appearing in chatwoot 2023-10-02 16:10:04 -03:00
Francis Breit
3f4333087f Start Typebot com opção de ativar chatbot ou não
Foi criada a variável enabled_typebot (não obrigatória)
enabled_typebot igual a true ou vazio: o comportamento do "startTypebot" atua como a nova funcionalidadede sessões persistentes (v1.5.2) onde o flow do Typebot disparado atua como chatbot.
enabled_typebot igual a false: o comportamento do "startTypebot" atua como era na Evolution v.1.5.1 onde o flow do Typebot disparado atua como mensagem simples, nao ativa o chatbot e funciona apenas como mensagens simples.
Obs1: Se setada como true ou se for omitida essa variável no comando start Typebot, enabled_typebot será assumida como true, ou seja, start typebot aciona o flow com o chatbot ativado, imediatamente após o envio, aguardando interação do contato.. Se, antes do acionamento do start typebot, tiver um outro chatbot ativo, o mesmo será substituído pelo novo, ora enviado.
Obs2: Se setada como false, dispara apenas como notificação e não ativa o bot. se tiver outro bot ativo na instancia o mesmo continuará ativo, após disparada a mensagem
2023-10-02 11:56:24 -03:00
Davidson Gomes
e1ac29683d wip: swagger 2023-10-02 09:24:53 -03:00
Davidson Gomes
5c74cbfe19 improvement in restart instance to completely redo the connection 2023-09-30 16:36:32 -03:00
Davidson Gomes
3fdb3fa673 fix: Remove rabbitmq queues when delete instances 2023-09-30 07:20:57 -03:00
Davidson Gomes
ba584974cb Merge tag '1.5.2' into develop
v
2023-09-28 17:58:59 -03:00
25 changed files with 3136 additions and 158 deletions

View File

@@ -1,3 +1,27 @@
# 1.5.4 (2023-10-09 20:43)
### Fixed
* Baileys logger typing issue resolved
* Solved problem with duplicate messages in chatwoot
# 1.5.3 (2023-10-06 18:55)
### Feature
* Swagger documentation
* Added base 64 sending option via webhook
### Fixed
* Remove rabbitmq queues when delete instances
* Improvement in restart instance to completely redo the connection
* Update node version: v20
* Correction of messages sent by the api and typebot not appearing in chatwoot
* Adjustment to start typebot, added startSession parameter
* Chatwoot now receives messages sent via api and typebot
* Fixed problem with starting with an input in typebot
* Added check to ensure variables are not empty before executing foreach in start typebot
# 1.5.2 (2023-09-28 17:56) # 1.5.2 (2023-09-28 17:56)

View File

@@ -1,6 +1,6 @@
FROM node:16.18-alpine FROM node:20.7.0-alpine
LABEL version="1.5.2" description="Api to control whatsapp features through http requests." LABEL version="1.5.4" description="Api to control whatsapp features through http requests."
LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes" LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
LABEL contact="contato@agenciadgcode.com" LABEL contact="contato@agenciadgcode.com"

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
{ {
"name": "evolution-api", "name": "evolution-api",
"version": "1.5.2", "version": "1.5.4",
"description": "Rest api for communication with WhatsApp", "description": "Rest api for communication with WhatsApp",
"main": "./dist/src/main.js", "main": "./dist/src/main.js",
"scripts": { "scripts": {
@@ -78,7 +78,9 @@
"sharp": "^0.30.7", "sharp": "^0.30.7",
"socket.io": "^4.7.1", "socket.io": "^4.7.1",
"socks-proxy-agent": "^8.0.1", "socks-proxy-agent": "^8.0.1",
"uuid": "^9.0.0" "swagger-ui-express": "^5.0.0",
"uuid": "^9.0.0",
"yamljs": "^0.3.0"
}, },
"devDependencies": { "devDependencies": {
"@types/compression": "^1.7.2", "@types/compression": "^1.7.2",

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

17
src/docs/swagger.conf.ts Normal file
View File

@@ -0,0 +1,17 @@
import { Router } from 'express';
import { join } from 'path';
import swaggerUi from 'swagger-ui-express';
import YAML from 'yamljs';
const document = YAML.load(join(process.cwd(), 'src', 'docs', 'swagger.yaml'));
const router = Router();
export const swaggerRouter = router.use('/docs', swaggerUi.serve).get(
'/docs',
swaggerUi.setup(document, {
customCssUrl: '/css/dark-theme-swagger.css',
customSiteTitle: 'Evolution API',
customfavIcon: '/images/logo.svg',
}),
);

2597
src/docs/swagger.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -71,3 +71,30 @@ export const initQueues = (instanceName: string, events: string[]) => {
amqp.bindQueue(queueName, exchangeName, event); amqp.bindQueue(queueName, exchangeName, event);
}); });
}; };
export const removeQueues = (instanceName: string, events: string[]) => {
if (!events || !events.length) return;
const channel = getAMQP();
const queues = events.map((event) => {
return `${event.replace(/_/g, '.').toLowerCase()}`;
});
const exchangeName = instanceName ?? 'evolution_exchange';
queues.forEach((event) => {
const amqp = getAMQP();
amqp.assertExchange(exchangeName, 'topic', {
durable: true,
autoDelete: false,
});
const queueName = `${instanceName}.${event}`;
amqp.deleteQueue(queueName);
});
channel.deleteExchange(exchangeName);
};

View File

@@ -10,6 +10,7 @@ import { Auth, configService, Cors, HttpServer, Rabbitmq, Webhook } from './conf
import { onUnexpectedError } from './config/error.config'; import { onUnexpectedError } from './config/error.config';
import { Logger } from './config/logger.config'; import { Logger } from './config/logger.config';
import { ROOT_DIR } from './config/path.config'; import { ROOT_DIR } from './config/path.config';
import { swaggerRouter } from './docs/swagger.conf';
import { initAMQP } from './libs/amqp.server'; import { initAMQP } from './libs/amqp.server';
import { initIO } from './libs/socket.server'; import { initIO } from './libs/socket.server';
import { ServerUP } from './utils/server-up'; import { ServerUP } from './utils/server-up';
@@ -51,6 +52,7 @@ function bootstrap() {
app.use('/store', express.static(join(ROOT_DIR, 'store'))); app.use('/store', express.static(join(ROOT_DIR, 'store')));
app.use('/', router); app.use('/', router);
app.use(swaggerRouter);
app.use( app.use(
(err: Error, req: Request, res: Response, next: NextFunction) => { (err: Error, req: Request, res: Response, next: NextFunction) => {

View File

@@ -41,6 +41,7 @@ export class InstanceController {
instanceName, instanceName,
webhook, webhook,
webhook_by_events, webhook_by_events,
webhook_base64,
events, events,
qrcode, qrcode,
number, number,
@@ -139,6 +140,7 @@ export class InstanceController {
url: webhook, url: webhook,
events: newEvents, events: newEvents,
webhook_by_events, webhook_by_events,
webhook_base64,
}); });
webhookEvents = (await this.webhookService.find(instance)).events; webhookEvents = (await this.webhookService.find(instance)).events;
@@ -297,6 +299,7 @@ export class InstanceController {
webhook: { webhook: {
webhook, webhook,
webhook_by_events, webhook_by_events,
webhook_base64,
events: webhookEvents, events: webhookEvents,
}, },
websocket: { websocket: {
@@ -390,6 +393,7 @@ export class InstanceController {
webhook: { webhook: {
webhook, webhook,
webhook_by_events, webhook_by_events,
webhook_base64,
events: webhookEvents, events: webhookEvents,
}, },
websocket: { websocket: {
@@ -475,10 +479,19 @@ export class InstanceController {
try { try {
this.logger.verbose('requested restartInstance from ' + instanceName + ' instance'); this.logger.verbose('requested restartInstance from ' + instanceName + ' instance');
this.logger.verbose('logging out instance: ' + instanceName); const instance = this.waMonitor.waInstances[instanceName];
this.waMonitor.waInstances[instanceName]?.client?.ws?.close(); const state = instance?.connectionStatus?.state;
return { status: 'SUCCESS', error: false, response: { message: 'Instance restarted' } }; switch (state) {
case 'open':
this.logger.verbose('logging out instance: ' + instanceName);
await instance.reloadConnection();
await delay(2000);
return await this.connectionState({ instanceName });
default:
return await this.connectionState({ instanceName });
}
} catch (error) { } catch (error) {
this.logger.error(error); this.logger.error(error);
} }
@@ -534,6 +547,8 @@ export class InstanceController {
throw new BadRequestException('The "' + instanceName + '" instance needs to be disconnected'); throw new BadRequestException('The "' + instanceName + '" instance needs to be disconnected');
} }
try { try {
this.waMonitor.waInstances[instanceName]?.removeRabbitmqQueues();
if (instance.state === 'connecting') { if (instance.state === 'connecting') {
this.logger.verbose('logging out instance: ' + instanceName); this.logger.verbose('logging out instance: ' + instanceName);

View File

@@ -1,15 +1,21 @@
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { ConfigService } from '../../config/env.config'; import { Auth, ConfigService, HttpServer } from '../../config/env.config';
import { HttpStatus } from '../routers/index.router'; import { HttpStatus } from '../routers/index.router';
import { WAMonitoringService } from '../services/monitor.service'; import { WAMonitoringService } from '../services/monitor.service';
export class ViewsController { export class ViewsController {
constructor(private readonly waMonit: WAMonitoringService, private readonly configService: ConfigService) {} constructor(private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService) {}
public async manager(request: Request, response: Response) { public async manager(request: Request, response: Response) {
try { try {
return response.status(HttpStatus.OK).render('manager'); const token = this.configService.get<Auth>('AUTHENTICATION').API_KEY.KEY;
const port = this.configService.get<HttpServer>('SERVER').PORT;
const instances = await this.waMonitor.instanceInfo();
console.log('INSTANCES: ', instances);
return response.status(HttpStatus.OK).render('manager', { token, port, instances });
} catch (error) { } catch (error) {
console.log('ERROR: ', error); console.log('ERROR: ', error);
} }

View File

@@ -5,6 +5,7 @@ export class InstanceDto {
token?: string; token?: string;
webhook?: string; webhook?: string;
webhook_by_events?: boolean; webhook_by_events?: boolean;
webhook_base64?: boolean;
events?: string[]; events?: string[];
reject_call?: boolean; reject_call?: boolean;
msg_call?: string; msg_call?: string;

View File

@@ -3,4 +3,5 @@ export class WebhookDto {
url?: string; url?: string;
events?: string[]; events?: string[];
webhook_by_events?: boolean; webhook_by_events?: boolean;
webhook_base64?: boolean;
} }

View File

@@ -65,6 +65,7 @@ export async function instanceLoggedGuard(req: Request, _: Response, next: NextF
} }
if (waMonitor.waInstances[instance.instanceName]) { if (waMonitor.waInstances[instance.instanceName]) {
waMonitor.waInstances[instance.instanceName]?.removeRabbitmqQueues();
delete waMonitor.waInstances[instance.instanceName]; delete waMonitor.waInstances[instance.instanceName];
} }
} }

View File

@@ -48,7 +48,7 @@ const typebotSchema = new Schema<TypebotRaw>({
prefilledVariables: { prefilledVariables: {
remoteJid: { type: String, required: false }, remoteJid: { type: String, required: false },
pushName: { type: String, required: false }, pushName: { type: String, required: false },
additionalData: { type: Schema.Types.Mixed, required: false } additionalData: { type: Schema.Types.Mixed, required: false },
}, },
}, },
], ],

View File

@@ -8,6 +8,7 @@ export class WebhookRaw {
enabled?: boolean; enabled?: boolean;
events?: string[]; events?: string[];
webhook_by_events?: boolean; webhook_by_events?: boolean;
webhook_base64?: boolean;
} }
const webhookSchema = new Schema<WebhookRaw>({ const webhookSchema = new Schema<WebhookRaw>({
@@ -16,6 +17,7 @@ const webhookSchema = new Schema<WebhookRaw>({
enabled: { type: Boolean, required: true }, enabled: { type: Boolean, required: true },
events: { type: [String], required: true }, events: { type: [String], required: true },
webhook_by_events: { type: Boolean, required: true }, webhook_by_events: { type: Boolean, required: true },
webhook_base64: { type: Boolean, required: true },
}); });
export const WebhookModel = dbserver?.model(WebhookRaw.name, webhookSchema, 'webhook'); export const WebhookModel = dbserver?.model(WebhookRaw.name, webhookSchema, 'webhook');

View File

@@ -40,6 +40,7 @@ router
status: HttpStatus.OK, status: HttpStatus.OK,
message: 'Welcome to the Evolution API, it is working!', message: 'Welcome to the Evolution API, it is working!',
version: packageJson.version, version: packageJson.version,
documentation: `${req.protocol}://${req.get('host')}/docs`,
}); });
}) })
.use('/instance', new InstanceRouter(configService, ...guards).router) .use('/instance', new InstanceRouter(configService, ...guards).router)

View File

@@ -914,7 +914,7 @@ export class ChatwootService {
}, },
}; };
await waInstance?.audioWhatsapp(data); await waInstance?.audioWhatsapp(data, true);
this.logger.verbose('audio sent'); this.logger.verbose('audio sent');
return; return;
@@ -939,7 +939,7 @@ export class ChatwootService {
data.mediaMessage.caption = caption; data.mediaMessage.caption = caption;
} }
await waInstance?.mediaMessage(data); await waInstance?.mediaMessage(data, true);
this.logger.verbose('media sent'); this.logger.verbose('media sent');
return; return;
@@ -1074,7 +1074,7 @@ export class ChatwootService {
}, },
}; };
await waInstance?.textMessage(data); await waInstance?.textMessage(data, true);
} }
} }
} }
@@ -1132,7 +1132,7 @@ export class ChatwootService {
thumbnailUrl: string; thumbnailUrl: string;
sourceUrl: string; sourceUrl: string;
} }
const adsMessage: AdsMessage | undefined = msg.extendedTextMessage?.contextInfo.externalAdReply; const adsMessage: AdsMessage | undefined = msg.extendedTextMessage?.contextInfo?.externalAdReply;
this.logger.verbose('Get ads message if it exist'); this.logger.verbose('Get ads message if it exist');
adsMessage && this.logger.verbose('Ads message: ' + adsMessage); adsMessage && this.logger.verbose('Ads message: ' + adsMessage);
@@ -1279,7 +1279,7 @@ export class ChatwootService {
return null; return null;
} }
if (event === 'messages.upsert') { if (event === 'messages.upsert' || event === 'send.message') {
this.logger.verbose('event messages.upsert'); this.logger.verbose('event messages.upsert');
if (body.key.remoteJid === 'status@broadcast') { if (body.key.remoteJid === 'status@broadcast') {

View File

@@ -7,9 +7,7 @@ import { join } from 'path';
import { Auth, ConfigService, Database, DelInstance, HttpServer, Redis } from '../../config/env.config'; import { Auth, ConfigService, Database, DelInstance, HttpServer, Redis } from '../../config/env.config';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../config/logger.config';
import { INSTANCE_DIR, STORE_DIR } from '../../config/path.config'; import { INSTANCE_DIR, STORE_DIR } from '../../config/path.config';
// inserido por francis inicio
import { NotFoundException } from '../../exceptions'; import { NotFoundException } from '../../exceptions';
// inserido por francis fim
import { dbserver } from '../../libs/db.connect'; import { dbserver } from '../../libs/db.connect';
import { RedisCache } from '../../libs/redis.client'; import { RedisCache } from '../../libs/redis.client';
import { import {
@@ -66,8 +64,10 @@ export class WAMonitoringService {
await this.waInstances[instance]?.client?.logout('Log out instance: ' + instance); await this.waInstances[instance]?.client?.logout('Log out instance: ' + instance);
this.waInstances[instance]?.client?.ws?.close(); this.waInstances[instance]?.client?.ws?.close();
this.waInstances[instance]?.client?.end(undefined); this.waInstances[instance]?.client?.end(undefined);
this.waInstances[instance]?.removeRabbitmqQueues();
delete this.waInstances[instance]; delete this.waInstances[instance];
} else { } else {
this.waInstances[instance]?.removeRabbitmqQueues();
delete this.waInstances[instance]; delete this.waInstances[instance];
this.eventEmitter.emit('remove.instance', instance, 'inner'); this.eventEmitter.emit('remove.instance', instance, 'inner');
} }
@@ -75,68 +75,9 @@ export class WAMonitoringService {
}, 1000 * 60 * time); }, 1000 * 60 * time);
} }
} }
/* ocultado por francis inicio
public async instanceInfo(instanceName?: string) { public async instanceInfo(instanceName?: string) {
this.logger.verbose('get instance info'); this.logger.verbose('get instance info');
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
const instances: any[] = await Promise.all(
Object.entries(this.waInstances).map(async ([key, value]) => {
const status = value?.connectionStatus?.state || 'unknown';
if (status === 'unknown') {
return null;
}
if (status === 'open') {
this.logger.verbose('instance: ' + key + ' - connectionStatus: open');
}
const instanceData: any = {
instance: {
instanceName: key,
owner: value.wuid,
profileName: (await value.getProfileName()) || 'not loaded',
profilePictureUrl: value.profilePictureUrl,
profileStatus: (await value.getProfileStatus()) || '',
status: status,
},
};
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
instanceData.instance.serverUrl = urlServer;
instanceData.instance.apikey = (await this.repository.auth.find(key))?.apikey;
const findChatwoot = await this.waInstances[key].findChatwoot();
if (findChatwoot && findChatwoot.enabled) {
instanceData.instance.chatwoot = {
...findChatwoot,
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(key)}`,
};
}
}
return instanceData;
}),
).then((results) => results.filter((instance) => instance !== null));
this.logger.verbose('return instance info: ' + instances.length);
if (instanceName) {
const instance = instances.find((i) => i.instance.instanceName === instanceName);
return instance || [];
}
return instances;
}
ocultado por francis fim */
// inserido por francis inicio
public async instanceInfo(instanceName?: string) {
this.logger.verbose('get instance info');
if (instanceName && !this.waInstances[instanceName]) { if (instanceName && !this.waInstances[instanceName]) {
throw new NotFoundException(`Instance "${instanceName}" not found`); throw new NotFoundException(`Instance "${instanceName}" not found`);
} }
@@ -210,17 +151,6 @@ public async instanceInfo(instanceName?: string) {
return instances.find((i) => i.instance.instanceName === instanceName) ?? instances; return instances.find((i) => i.instance.instanceName === instanceName) ?? instances;
} }
// inserido por francis fim
private delInstanceFiles() { private delInstanceFiles() {
this.logger.verbose('cron to delete instance files started'); this.logger.verbose('cron to delete instance files started');
setInterval(async () => { setInterval(async () => {

View File

@@ -99,6 +99,7 @@ export class TypebotService {
const remoteJid = data.remoteJid; const remoteJid = data.remoteJid;
const url = data.url; const url = data.url;
const typebot = data.typebot; const typebot = data.typebot;
const startSession = data.startSession;
const variables = data.variables; const variables = data.variables;
const findTypebot = await this.find(instance); const findTypebot = await this.find(instance);
const sessions = (findTypebot.sessions as Session[]) ?? []; const sessions = (findTypebot.sessions as Session[]) ?? [];
@@ -110,12 +111,16 @@ export class TypebotService {
const prefilledVariables = { const prefilledVariables = {
remoteJid: remoteJid, remoteJid: remoteJid,
instanceName: instance.instanceName,
}; };
variables.forEach((variable) => { if (variables?.length) {
variables.forEach((variable: { name: string | number; value: string }) => {
prefilledVariables[variable.name] = variable.value; prefilledVariables[variable.name] = variable.value;
}); });
}
if (startSession) {
const response = await this.createNewSession(instance, { const response = await this.createNewSession(instance, {
url: url, url: url,
typebot: typebot, typebot: typebot,
@@ -130,13 +135,7 @@ export class TypebotService {
}); });
if (response.sessionId) { if (response.sessionId) {
await this.sendWAMessage( await this.sendWAMessage(instance, remoteJid, response.messages, response.input, response.clientSideActions);
instance,
remoteJid,
response.messages,
response.input,
response.clientSideActions,
);
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, { this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, {
remoteJid: remoteJid, remoteJid: remoteJid,
@@ -146,7 +145,35 @@ export class TypebotService {
sessionId: `${response.sessionId}`, sessionId: `${response.sessionId}`,
}); });
} else { } else {
throw new Error("Session ID not found in response"); throw new Error('Session ID not found in response');
}
} else {
const id = Math.floor(Math.random() * 10000000000).toString();
const reqData = {
startParams: {
typebot: data.typebot,
prefilledVariables: prefilledVariables,
},
};
const request = await axios.post(data.url + '/api/v1/sendMessage', reqData);
await this.sendWAMessage(
instance,
remoteJid,
request.data.messages,
request.data.input,
request.data.clientSideActions,
);
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, {
remoteJid: remoteJid,
url: url,
typebot: typebot,
variables: variables,
sessionId: id,
});
} }
return { return {
@@ -201,13 +228,12 @@ export class TypebotService {
public async createNewSession(instance: InstanceDto, data: any) { public async createNewSession(instance: InstanceDto, data: any) {
const id = Math.floor(Math.random() * 10000000000).toString(); const id = Math.floor(Math.random() * 10000000000).toString();
const reqData = { const reqData = {
sessionId: id,
startParams: { startParams: {
typebot: data.typebot, typebot: data.typebot,
prefilledVariables: { prefilledVariables: {
...data.prefilledVariables, ...data.prefilledVariables,
remoteJid: data.remoteJid, remoteJid: data.remoteJid,
pushName: data.pushName || 'Default Name', pushName: data.pushName || '',
instanceName: instance.instanceName, instanceName: instance.instanceName,
}, },
}, },
@@ -225,9 +251,9 @@ export class TypebotService {
prefilledVariables: { prefilledVariables: {
...data.prefilledVariables, ...data.prefilledVariables,
remoteJid: data.remoteJid, remoteJid: data.remoteJid,
pushName: data.pushName || 'Default Name', pushName: data.pushName || '',
instanceName: instance.instanceName, instanceName: instance.instanceName,
} },
}); });
const typebotData = { const typebotData = {
@@ -435,6 +461,62 @@ export class TypebotService {
await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions); await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions);
if (data.messages.length === 0) {
const content = this.getConversationMessage(msg.message);
if (!content) {
if (unknown_message) {
this.waMonitor.waInstances[instance.instanceName].textMessage({
number: remoteJid.split('@')[0],
options: {
delay: delay_message || 1000,
presence: 'composing',
},
textMessage: {
text: unknown_message,
},
});
}
return;
}
if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) {
sessions.splice(sessions.indexOf(session), 1);
const typebotData = {
enabled: true,
url: url,
typebot: typebot,
expire: expire,
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
listening_from_me: listening_from_me,
sessions,
};
this.create(instance, typebotData);
return;
}
const reqData = {
message: content,
sessionId: data.sessionId,
};
const request = await axios.post(url + '/api/v1/sendMessage', reqData);
console.log('request', request);
await this.sendWAMessage(
instance,
remoteJid,
request.data.messages,
request.data.input,
request.data.clientSideActions,
);
}
return; return;
} }
} }
@@ -459,6 +541,61 @@ export class TypebotService {
await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions); await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions);
if (data.messages.length === 0) {
const content = this.getConversationMessage(msg.message);
if (!content) {
if (unknown_message) {
this.waMonitor.waInstances[instance.instanceName].textMessage({
number: remoteJid.split('@')[0],
options: {
delay: delay_message || 1000,
presence: 'composing',
},
textMessage: {
text: unknown_message,
},
});
}
return;
}
if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) {
sessions.splice(sessions.indexOf(session), 1);
const typebotData = {
enabled: true,
url: url,
typebot: typebot,
expire: expire,
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
listening_from_me: listening_from_me,
sessions,
};
this.create(instance, typebotData);
return;
}
const reqData = {
message: content,
sessionId: data.sessionId,
};
const request = await axios.post(url + '/api/v1/sendMessage', reqData);
console.log('request', request);
await this.sendWAMessage(
instance,
remoteJid,
request.data.messages,
request.data.input,
request.data.clientSideActions,
);
}
return; return;
} }
@@ -500,7 +637,7 @@ export class TypebotService {
return; return;
} }
if (content.toLowerCase() === keyword_finish.toLowerCase()) { if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) {
sessions.splice(sessions.indexOf(session), 1); sessions.splice(sessions.indexOf(session), 1);
const typebotData = { const typebotData = {

View File

@@ -26,7 +26,7 @@ export class WebhookService {
return result; return result;
} catch (error) { } catch (error) {
return { enabled: false, url: '', events: [], webhook_by_events: false }; return { enabled: false, url: '', events: [], webhook_by_events: false, webhook_base64: false };
} }
} }
} }

View File

@@ -66,7 +66,7 @@ import {
import { Logger } from '../../config/logger.config'; import { Logger } from '../../config/logger.config';
import { INSTANCE_DIR, ROOT_DIR } from '../../config/path.config'; import { INSTANCE_DIR, ROOT_DIR } from '../../config/path.config';
import { BadRequestException, InternalServerErrorException, NotFoundException } from '../../exceptions'; import { BadRequestException, InternalServerErrorException, NotFoundException } from '../../exceptions';
import { getAMQP } from '../../libs/amqp.server'; import { getAMQP, removeQueues } from '../../libs/amqp.server';
import { dbserver } from '../../libs/db.connect'; import { dbserver } from '../../libs/db.connect';
import { RedisCache } from '../../libs/redis.client'; import { RedisCache } from '../../libs/redis.client';
import { getIO } from '../../libs/socket.server'; import { getIO } from '../../libs/socket.server';
@@ -276,6 +276,9 @@ export class WAStartupService {
this.localWebhook.webhook_by_events = data?.webhook_by_events; this.localWebhook.webhook_by_events = data?.webhook_by_events;
this.logger.verbose(`Webhook by events: ${this.localWebhook.webhook_by_events}`); this.logger.verbose(`Webhook by events: ${this.localWebhook.webhook_by_events}`);
this.localWebhook.webhook_base64 = data?.webhook_base64;
this.logger.verbose(`Webhook by webhook_base64: ${this.localWebhook.webhook_base64}`);
this.logger.verbose('Webhook loaded'); this.logger.verbose('Webhook loaded');
} }
@@ -495,6 +498,14 @@ export class WAStartupService {
return data; return data;
} }
public async removeRabbitmqQueues() {
this.logger.verbose('Removing rabbitmq');
if (this.localRabbitmq.enabled) {
removeQueues(this.instanceName, this.localRabbitmq.events);
}
}
private async loadTypebot() { private async loadTypebot() {
this.logger.verbose('Loading typebot'); this.logger.verbose('Loading typebot');
const data = await this.repository.typebot.find(this.instanceName); const data = await this.repository.typebot.find(this.instanceName);
@@ -1182,7 +1193,7 @@ export class WAStartupService {
...options, ...options,
auth: { auth: {
creds: this.instance.authState.state.creds, creds: this.instance.authState.state.creds,
keys: makeCacheableSignalKeyStore(this.instance.authState.state.keys, P({ level: 'error' })), keys: makeCacheableSignalKeyStore(this.instance.authState.state.keys, P({ level: 'error' }) as any),
}, },
logger: P({ level: this.logBaileys }), logger: P({ level: this.logBaileys }),
printQRInTerminal: false, printQRInTerminal: false,
@@ -1240,6 +1251,74 @@ export class WAStartupService {
} }
} }
public async reloadConnection(): Promise<WASocket> {
try {
this.instance.authState = await this.defineAuthState();
const { version } = await fetchLatestBaileysVersion();
const session = this.configService.get<ConfigSessionPhone>('CONFIG_SESSION_PHONE');
const browser: WABrowserDescription = [session.CLIENT, session.NAME, release()];
let options;
if (this.localProxy.enabled) {
this.logger.verbose('Proxy enabled');
options = {
agent: new ProxyAgent(this.localProxy.proxy as any),
fetchAgent: new ProxyAgent(this.localProxy.proxy as any),
};
}
const socketConfig: UserFacingSocketConfig = {
...options,
auth: {
creds: this.instance.authState.state.creds,
keys: makeCacheableSignalKeyStore(this.instance.authState.state.keys, P({ level: 'error' }) as any),
},
logger: P({ level: this.logBaileys }),
printQRInTerminal: false,
browser,
version,
markOnlineOnConnect: this.localSettings.always_online,
connectTimeoutMs: 60_000,
qrTimeout: 40_000,
defaultQueryTimeoutMs: undefined,
emitOwnEvents: false,
msgRetryCounterCache: this.msgRetryCounterCache,
getMessage: async (key) => (await this.getMessage(key)) as Promise<proto.IMessage>,
generateHighQualityLinkPreview: true,
syncFullHistory: true,
userDevicesCache: this.userDevicesCache,
transactionOpts: { maxCommitRetries: 1, delayBetweenTriesMs: 10 },
patchMessageBeforeSending: (message) => {
const requiresPatch = !!(message.buttonsMessage || message.listMessage || message.templateMessage);
if (requiresPatch) {
message = {
viewOnceMessageV2: {
message: {
messageContextInfo: {
deviceListMetadataVersion: 2,
deviceListMetadata: {},
},
...message,
},
},
};
}
return message;
},
};
this.client = makeWASocket(socketConfig);
return this.client;
} catch (error) {
this.logger.error(error);
throw new InternalServerErrorException(error?.toString());
}
}
private readonly chatHandle = { private readonly chatHandle = {
'chats.upsert': async (chats: Chat[], database: Database) => { 'chats.upsert': async (chats: Chat[], database: Database) => {
this.logger.verbose('Event received: chats.upsert'); this.logger.verbose('Event received: chats.upsert');
@@ -1452,7 +1531,36 @@ export class WAStartupService {
return; return;
} }
const messageRaw: MessageRaw = { let messageRaw: MessageRaw;
if (
(this.localWebhook.webhook_base64 === true && received?.message.documentMessage) ||
received?.message.imageMessage
) {
const buffer = await downloadMediaMessage(
{ key: received.key, message: received?.message },
'buffer',
{},
{
logger: P({ level: 'error' }) as any,
reuploadRequest: this.client.updateMediaMessage,
},
);
console.log(buffer);
messageRaw = {
key: received.key,
pushName: received.pushName,
message: {
...received.message,
base64: buffer ? buffer.toString('base64') : undefined,
},
messageType: getContentType(received.message),
messageTimestamp: received.messageTimestamp as number,
owner: this.instance.name,
source: getDevice(received.key.id),
};
} else {
messageRaw = {
key: received.key, key: received.key,
pushName: received.pushName, pushName: received.pushName,
message: { ...received.message }, message: { ...received.message },
@@ -1461,6 +1569,7 @@ export class WAStartupService {
owner: this.instance.name, owner: this.instance.name,
source: getDevice(received.key.id), source: getDevice(received.key.id),
}; };
}
if (this.localSettings.read_messages && received.key.id !== 'status@broadcast') { if (this.localSettings.read_messages && received.key.id !== 'status@broadcast') {
await this.client.readMessages([received.key]); await this.client.readMessages([received.key]);
@@ -1952,7 +2061,12 @@ export class WAStartupService {
} }
} }
private async sendMessageWithTyping<T = proto.IMessage>(number: string, message: T, options?: Options) { private async sendMessageWithTyping<T = proto.IMessage>(
number: string,
message: T,
options?: Options,
isChatwoot = false,
) {
this.logger.verbose('Sending message with typing'); this.logger.verbose('Sending message with typing');
this.logger.verbose(`Check if number "${number}" is WhatsApp`); this.logger.verbose(`Check if number "${number}" is WhatsApp`);
@@ -2110,7 +2224,7 @@ export class WAStartupService {
this.logger.verbose('Sending data to webhook in event SEND_MESSAGE'); this.logger.verbose('Sending data to webhook in event SEND_MESSAGE');
await this.sendDataWebhook(Events.SEND_MESSAGE, messageRaw); await this.sendDataWebhook(Events.SEND_MESSAGE, messageRaw);
if (this.localChatwoot.enabled) { if (this.localChatwoot.enabled && !isChatwoot) {
this.chatwootService.eventWhatsapp(Events.SEND_MESSAGE, { instanceName: this.instance.name }, messageRaw); this.chatwootService.eventWhatsapp(Events.SEND_MESSAGE, { instanceName: this.instance.name }, messageRaw);
} }
@@ -2135,7 +2249,7 @@ export class WAStartupService {
} }
// Send Message Controller // Send Message Controller
public async textMessage(data: SendTextDto) { public async textMessage(data: SendTextDto, isChatwoot = false) {
this.logger.verbose('Sending text message'); this.logger.verbose('Sending text message');
return await this.sendMessageWithTyping( return await this.sendMessageWithTyping(
data.number, data.number,
@@ -2143,6 +2257,7 @@ export class WAStartupService {
conversation: data.textMessage.text, conversation: data.textMessage.text,
}, },
data?.options, data?.options,
isChatwoot,
); );
} }
@@ -2308,36 +2423,22 @@ export class WAStartupService {
mediaMessage.fileName = arrayMatch[1]; mediaMessage.fileName = arrayMatch[1];
this.logger.verbose('File name: ' + mediaMessage.fileName); this.logger.verbose('File name: ' + mediaMessage.fileName);
} }
// *inserido francis inicio
let mimetype: string;
// *inserido francis final
if (mediaMessage.mediatype === 'image' && !mediaMessage.fileName) { if (mediaMessage.mediatype === 'image' && !mediaMessage.fileName) {
mediaMessage.fileName = 'image.png'; mediaMessage.fileName = 'image.png';
// inserido francis inicio
mimetype = 'image/png';
// inserido francis inicio
} }
if (mediaMessage.mediatype === 'video' && !mediaMessage.fileName) { if (mediaMessage.mediatype === 'video' && !mediaMessage.fileName) {
mediaMessage.fileName = 'video.mp4'; mediaMessage.fileName = 'video.mp4';
// inserido francis inicio
mimetype = 'video/mp4';
// inserido francis final
} }
// ocultado francis inicio let mimetype: string;
// let mimetype: string;
if (isURL(mediaMessage.media)) {
// if (isURL(mediaMessage.media)) { mimetype = getMIMEType(mediaMessage.media);
// mimetype = getMIMEType(mediaMessage.media); } else {
// } else { mimetype = getMIMEType(mediaMessage.fileName);
// mimetype = getMIMEType(mediaMessage.fileName); }
// }
// ocultado francis final
this.logger.verbose('Mimetype: ' + mimetype); this.logger.verbose('Mimetype: ' + mimetype);
@@ -2433,11 +2534,11 @@ export class WAStartupService {
return result; return result;
} }
public async mediaMessage(data: SendMediaDto) { public async mediaMessage(data: SendMediaDto, isChatwoot = false) {
this.logger.verbose('Sending media message'); this.logger.verbose('Sending media message');
const generate = await this.prepareMediaMessage(data.mediaMessage); const generate = await this.prepareMediaMessage(data.mediaMessage);
return await this.sendMessageWithTyping(data.number, { ...generate.message }, data?.options); return await this.sendMessageWithTyping(data.number, { ...generate.message }, data?.options, isChatwoot);
} }
public async processAudio(audio: string, number: string) { public async processAudio(audio: string, number: string) {
@@ -2494,7 +2595,7 @@ export class WAStartupService {
}); });
} }
public async audioWhatsapp(data: SendAudioDto) { public async audioWhatsapp(data: SendAudioDto, isChatwoot = false) {
this.logger.verbose('Sending audio whatsapp'); this.logger.verbose('Sending audio whatsapp');
if (!data.options?.encoding && data.options?.encoding !== false) { if (!data.options?.encoding && data.options?.encoding !== false) {
@@ -2513,6 +2614,7 @@ export class WAStartupService {
mimetype: 'audio/mp4', mimetype: 'audio/mp4',
}, },
{ presence: 'recording', delay: data?.options?.delay }, { presence: 'recording', delay: data?.options?.delay },
isChatwoot,
); );
fs.unlinkSync(convert); fs.unlinkSync(convert);
@@ -2534,6 +2636,7 @@ export class WAStartupService {
mimetype: 'audio/ogg; codecs=opus', mimetype: 'audio/ogg; codecs=opus',
}, },
{ presence: 'recording', delay: data?.options?.delay }, { presence: 'recording', delay: data?.options?.delay },
isChatwoot,
); );
} }
@@ -2714,6 +2817,7 @@ export class WAStartupService {
public async markMessageAsRead(data: ReadMessageDto) { public async markMessageAsRead(data: ReadMessageDto) {
this.logger.verbose('Marking message as read'); this.logger.verbose('Marking message as read');
try { try {
const keys: proto.IMessageKey[] = []; const keys: proto.IMessageKey[] = [];
data.read_messages.forEach((read) => { data.read_messages.forEach((read) => {
@@ -2843,7 +2947,7 @@ export class WAStartupService {
'buffer', 'buffer',
{}, {},
{ {
logger: P({ level: 'error' }), logger: P({ level: 'error' }) as any,
reuploadRequest: this.client.updateMediaMessage, reuploadRequest: this.client.updateMediaMessage,
}, },
); );

View File

@@ -50,6 +50,7 @@ export declare namespace wa {
url?: string; url?: string;
events?: string[]; events?: string[];
webhook_by_events?: boolean; webhook_by_events?: boolean;
webhook_base64?: boolean;
}; };
export type LocalChatwoot = { export type LocalChatwoot = {

110
views/manager-wip.hbs Normal file
View File

@@ -0,0 +1,110 @@
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="https://evolution-api.com/files/evolution-api-favicon.png" type="image/x-icon">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"
integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
<title>Instance Manager</title>
</head>
<body>
<div class="container mt-4">
<!-- Botão para abrir o modal de adicionar nova instância -->
<button class="btn btn-primary mb-3" data-toggle="modal" data-target="#actionModal" data-action="add">Nova
Instância</button>
<!-- Tabela de instâncias -->
<table class="table table-bordered">
<thead>
<tr>
<th>Nome da Instância</th>
<th>Status</th>
<th>API Key</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
<!-- Iterando sobre as instâncias e preenchendo a tabela -->
{{#each instances}}
<tr>
<td>{{this.instance.instanceName}}</td>
<td>{{this.instance.status}}</td>
<td>{{this.instance.apikey}}</td>
<td>
<!-- Dropdown de ações para cada instância -->
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="actionDropdown"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Ações
</button>
<div class="dropdown-menu" aria-labelledby="actionDropdown">
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#actionModal"
data-action="connect">Connect</a>
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#actionModal"
data-action="restart">Restart</a>
<!-- Adicione mais itens de ação aqui -->
<!-- ... -->
</div>
</div>
</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
<!-- Modal de ações -->
<div class="modal fade" id="actionModal" tabindex="-1" role="dialog" aria-labelledby="actionModalLabel"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="actionModalLabel">Ação</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Fechar</button>
<button type="button" class="btn btn-primary">Salvar</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.min.js"
integrity="sha384-+sLIOodYLS7CIrQpBjl+C7nPvqq+FbNUBDunl/OZv93DB7Ln/533i8e/mZXLi/P+"
crossorigin="anonymous"></script>
<script>
$(document).ready(function(){
$('#actionModal').on('show.bs.modal', function(event) {
var button = $(event.relatedTarget);
var action = button.data('action');
console.log(action);
if (action === 'connect') {
} else if (action === 'restart') {
}
});
})
</script>
</body>
</html>

View File

@@ -11,7 +11,7 @@
<body> <body>
<iframe src="https://app.smith.dgcode.com.br/app/evolutionapi-public/home-64ca60783615e270291978b4?embed=true" frameborder="0" style="width: 100%; height: 100vh;"></iframe> <iframe src="https://manager.evolution-api.com" frameborder="0" style="width: 100%; height: 100vh;"></iframe>
</body> </body>