Merge branch 'EvolutionAPI:develop' into develop

This commit is contained in:
Edison Martins 2024-01-26 19:11:34 -03:00 committed by GitHub
commit 32a1a715ea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 119 additions and 53 deletions

View File

@ -46,7 +46,7 @@
"@figuro/chatwoot-sdk": "^1.1.16", "@figuro/chatwoot-sdk": "^1.1.16",
"@hapi/boom": "^10.0.1", "@hapi/boom": "^10.0.1",
"@sentry/node": "^7.59.2", "@sentry/node": "^7.59.2",
"@whiskeysockets/baileys": "^6.5.0", "@whiskeysockets/baileys": "6.5.0",
"amqplib": "^0.10.3", "amqplib": "^0.10.3",
"aws-sdk": "^2.1499.0", "aws-sdk": "^2.1499.0",
"axios": "^1.3.5", "axios": "^1.3.5",
@ -61,6 +61,7 @@
"express": "^4.18.2", "express": "^4.18.2",
"express-async-errors": "^3.1.1", "express-async-errors": "^3.1.1",
"hbs": "^4.2.0", "hbs": "^4.2.0",
"https-proxy-agent": "^7.0.2",
"jimp": "^0.16.13", "jimp": "^0.16.13",
"join": "^3.0.0", "join": "^3.0.0",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
@ -73,7 +74,6 @@
"node-mime-types": "^1.1.0", "node-mime-types": "^1.1.0",
"node-windows": "^1.0.0-beta.8", "node-windows": "^1.0.0-beta.8",
"pino": "^8.11.0", "pino": "^8.11.0",
"proxy-agent": "^6.3.0",
"qrcode": "^1.5.1", "qrcode": "^1.5.1",
"qrcode-terminal": "^0.12.0", "qrcode-terminal": "^0.12.0",
"redis": "^4.6.5", "redis": "^4.6.5",

View File

@ -0,0 +1,17 @@
import { HttpsProxyAgent } from 'https-proxy-agent';
import { wa } from '../whatsapp/types/wa.types';
export function makeProxyAgent(proxy: wa.Proxy | string) {
if (typeof proxy === 'string') {
return new HttpsProxyAgent(proxy);
}
const { host, password, port, protocol, username } = proxy;
let proxyUrl = `${protocol}://${host}:${port}`;
if (username && password) {
proxyUrl = `${protocol}://${username}:${password}@${host}:${port}`;
}
return new HttpsProxyAgent(proxyUrl);
}

View File

@ -771,6 +771,16 @@ export const groupInviteSchema: JSONSchema7 = {
...isNotEmpty('inviteCode'), ...isNotEmpty('inviteCode'),
}; };
export const AcceptGroupInviteSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
inviteCode: { type: 'string', pattern: '^[a-zA-Z0-9]{22}$' },
},
required: ['inviteCode'],
...isNotEmpty('inviteCode'),
};
export const updateParticipantsSchema: JSONSchema7 = { export const updateParticipantsSchema: JSONSchema7 = {
$id: v4(), $id: v4(),
type: 'object', type: 'object',

View File

@ -1,5 +1,6 @@
import { Logger } from '../../config/logger.config'; import { Logger } from '../../config/logger.config';
import { import {
AcceptGroupInvite,
CreateGroupDto, CreateGroupDto,
GetParticipant, GetParticipant,
GroupDescriptionDto, GroupDescriptionDto,
@ -65,6 +66,11 @@ export class GroupController {
return await this.waMonitor.waInstances[instance.instanceName].sendInvite(data); return await this.waMonitor.waInstances[instance.instanceName].sendInvite(data);
} }
public async acceptInviteCode(instance: InstanceDto, inviteCode: AcceptGroupInvite) {
logger.verbose('requested acceptInviteCode from ' + instance.instanceName + ' instance');
return await this.waMonitor.waInstances[instance.instanceName].acceptInviteCode(inviteCode);
}
public async revokeInviteCode(instance: InstanceDto, groupJid: GroupJid) { public async revokeInviteCode(instance: InstanceDto, groupJid: GroupJid) {
logger.verbose('requested revokeInviteCode from ' + instance.instanceName + ' instance'); logger.verbose('requested revokeInviteCode from ' + instance.instanceName + ' instance');
return await this.waMonitor.waInstances[instance.instanceName].revokeInviteCode(groupJid); return await this.waMonitor.waInstances[instance.instanceName].revokeInviteCode(groupJid);

View File

@ -1,19 +1,25 @@
import axios from 'axios'; import axios from 'axios';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../config/logger.config';
import { BadRequestException } from '../../exceptions'; import { BadRequestException, NotFoundException } from '../../exceptions';
import { makeProxyAgent } from '../../utils/makeProxyAgent';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../dto/instance.dto';
import { ProxyDto } from '../dto/proxy.dto'; import { ProxyDto } from '../dto/proxy.dto';
import { WAMonitoringService } from '../services/monitor.service';
import { ProxyService } from '../services/proxy.service'; import { ProxyService } from '../services/proxy.service';
const logger = new Logger('ProxyController'); const logger = new Logger('ProxyController');
export class ProxyController { export class ProxyController {
constructor(private readonly proxyService: ProxyService) {} constructor(private readonly proxyService: ProxyService, private readonly waMonitor: WAMonitoringService) {}
public async createProxy(instance: InstanceDto, data: ProxyDto) { public async createProxy(instance: InstanceDto, data: ProxyDto) {
logger.verbose('requested createProxy from ' + instance.instanceName + ' instance'); logger.verbose('requested createProxy from ' + instance.instanceName + ' instance');
if (!this.waMonitor.waInstances[instance.instanceName]) {
throw new NotFoundException(`The "${instance.instanceName}" instance does not exist`);
}
if (!data.enabled) { if (!data.enabled) {
logger.verbose('proxy disabled'); logger.verbose('proxy disabled');
data.proxy = null; data.proxy = null;
@ -21,8 +27,7 @@ export class ProxyController {
if (data.proxy) { if (data.proxy) {
logger.verbose('proxy enabled'); logger.verbose('proxy enabled');
const { host, port, protocol, username, password } = data.proxy; const testProxy = await this.testProxy(data.proxy);
const testProxy = await this.testProxy(host, port, protocol, username, password);
if (!testProxy) { if (!testProxy) {
throw new BadRequestException('Invalid proxy'); throw new BadRequestException('Invalid proxy');
} }
@ -33,37 +38,30 @@ export class ProxyController {
public async findProxy(instance: InstanceDto) { public async findProxy(instance: InstanceDto) {
logger.verbose('requested findProxy from ' + instance.instanceName + ' instance'); logger.verbose('requested findProxy from ' + instance.instanceName + ' instance');
if (!this.waMonitor.waInstances[instance.instanceName]) {
throw new NotFoundException(`The "${instance.instanceName}" instance does not exist`);
}
return this.proxyService.find(instance); return this.proxyService.find(instance);
} }
private async testProxy(host: string, port: string, protocol: string, username?: string, password?: string) { private async testProxy(proxy: ProxyDto['proxy']) {
logger.verbose('requested testProxy'); logger.verbose('requested testProxy');
try { try {
let proxyConfig: any = { const serverIp = await axios.get('https://icanhazip.com/');
host: host, const response = await axios.get('https://icanhazip.com/', {
port: parseInt(port), httpsAgent: makeProxyAgent(proxy),
protocol: protocol,
};
if (username && password) {
proxyConfig = {
...proxyConfig,
auth: {
username: username,
password: password,
},
};
}
const serverIp = await axios.get('http://meuip.com/api/meuip.php');
const response = await axios.get('http://meuip.com/api/meuip.php', {
proxy: proxyConfig,
}); });
logger.verbose('testProxy response: ' + response.data); logger.verbose('testProxy response: ' + response.data);
return response.data !== serverIp.data; return response.data !== serverIp.data;
} catch (error) { } catch (error) {
logger.error('testProxy error: ' + error); let errorMessage = error;
if (axios.isAxiosError(error) && error.response.data) {
errorMessage = error.response.data;
}
logger.error('testProxy error: ' + errorMessage);
return false; return false;
} }
} }

View File

@ -1,7 +1,12 @@
import { proto, WAPresence, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '@whiskeysockets/baileys'; import { proto, WAPresence, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '@whiskeysockets/baileys';
export class OnWhatsAppDto { export class OnWhatsAppDto {
constructor(public readonly jid: string, public readonly exists: boolean, public readonly name?: string) {} constructor(
public readonly jid: string,
public readonly exists: boolean,
public readonly number: string,
public readonly name?: string,
) {}
} }
export class getBase64FromMediaMessageDto { export class getBase64FromMediaMessageDto {

View File

@ -32,6 +32,10 @@ export class GroupInvite {
inviteCode: string; inviteCode: string;
} }
export class AcceptGroupInvite {
inviteCode: string;
}
export class GroupSendInvite { export class GroupSendInvite {
groupJid: string; groupJid: string;
description: string; description: string;

View File

@ -2,6 +2,7 @@ import { RequestHandler, Router } from 'express';
import { Logger } from '../../config/logger.config'; import { Logger } from '../../config/logger.config';
import { import {
AcceptGroupInviteSchema,
createGroupSchema, createGroupSchema,
getParticipantsSchema, getParticipantsSchema,
groupInviteSchema, groupInviteSchema,
@ -16,6 +17,7 @@ import {
} from '../../validate/validate.schema'; } from '../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router'; import { RouterBroker } from '../abstract/abstract.router';
import { import {
AcceptGroupInvite,
CreateGroupDto, CreateGroupDto,
GetParticipant, GetParticipant,
GroupDescriptionDto, GroupDescriptionDto,
@ -182,6 +184,22 @@ export class GroupRouter extends RouterBroker {
res.status(HttpStatus.OK).json(response); res.status(HttpStatus.OK).json(response);
}) })
.get(this.routerPath('acceptInviteCode'), ...guards, async (req, res) => {
logger.verbose('request received in acceptInviteCode');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.inviteCodeValidate<AcceptGroupInvite>({
request: req,
schema: AcceptGroupInviteSchema,
ClassRef: AcceptGroupInvite,
execute: (instance, data) => groupController.acceptInviteCode(instance, data),
});
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('sendInvite'), ...guards, async (req, res) => { .post(this.routerPath('sendInvite'), ...guards, async (req, res) => {
logger.verbose('request received in sendInvite'); logger.verbose('request received in sendInvite');
logger.verbose('request body: '); logger.verbose('request body: ');

View File

@ -45,7 +45,6 @@ import { getMIMEType } from 'node-mime-types';
import { release } from 'os'; import { release } from 'os';
import { join } from 'path'; import { join } from 'path';
import P from 'pino'; import P from 'pino';
import { ProxyAgent } from 'proxy-agent';
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';
@ -73,6 +72,7 @@ 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';
import { getSQS, removeQueues as removeQueuesSQS } from '../../libs/sqs.server'; import { getSQS, removeQueues as removeQueuesSQS } from '../../libs/sqs.server';
import { makeProxyAgent } from '../../utils/makeProxyAgent';
import { useMultiFileAuthStateDb } from '../../utils/use-multi-file-auth-state-db'; import { useMultiFileAuthStateDb } from '../../utils/use-multi-file-auth-state-db';
import { useMultiFileAuthStateRedisDb } from '../../utils/use-multi-file-auth-state-redis-db'; import { useMultiFileAuthStateRedisDb } from '../../utils/use-multi-file-auth-state-redis-db';
import { import {
@ -89,6 +89,7 @@ import {
WhatsAppNumberDto, WhatsAppNumberDto,
} from '../dto/chat.dto'; } from '../dto/chat.dto';
import { import {
AcceptGroupInvite,
CreateGroupDto, CreateGroupDto,
GetParticipant, GetParticipant,
GroupDescriptionDto, GroupDescriptionDto,
@ -1384,24 +1385,21 @@ export class WAStartupService {
this.logger.info('Proxy enabled: ' + this.localProxy.proxy); this.logger.info('Proxy enabled: ' + this.localProxy.proxy);
if (this.localProxy.proxy.host.includes('proxyscrape')) { if (this.localProxy.proxy.host.includes('proxyscrape')) {
const response = await axios.get(this.localProxy.proxy.host); try {
const text = response.data; const response = await axios.get(this.localProxy.proxy.host);
const proxyUrls = text.split('\r\n'); const text = response.data;
const rand = Math.floor(Math.random() * Math.floor(proxyUrls.length)); const proxyUrls = text.split('\r\n');
const proxyUrl = 'http://' + proxyUrls[rand]; const rand = Math.floor(Math.random() * Math.floor(proxyUrls.length));
options = { const proxyUrl = 'http://' + proxyUrls[rand];
agent: new ProxyAgent(proxyUrl as any), options = {
}; agent: makeProxyAgent(proxyUrl),
} else { };
let proxyUri = } catch (error) {
this.localProxy.proxy.protocol + '://' + this.localProxy.proxy.host + ':' + this.localProxy.proxy.port; this.localProxy.enabled = false;
if (this.localProxy.proxy.username && this.localProxy.proxy.password) {
proxyUri = `${this.localProxy.proxy.username}:${this.localProxy.proxy.password}@${proxyUri}`;
} }
} else {
options = { options = {
agent: new ProxyAgent(proxyUri as any), agent: makeProxyAgent(this.localProxy.proxy),
}; };
} }
} }
@ -1488,8 +1486,8 @@ export class WAStartupService {
if (this.localProxy.enabled) { if (this.localProxy.enabled) {
this.logger.verbose('Proxy enabled'); this.logger.verbose('Proxy enabled');
options = { options = {
agent: new ProxyAgent(this.localProxy.proxy as any), agent: makeProxyAgent(this.localProxy.proxy),
fetchAgent: new ProxyAgent(this.localProxy.proxy as any), fetchAgent: makeProxyAgent(this.localProxy.proxy),
}; };
} }
@ -2471,7 +2469,7 @@ export class WAStartupService {
); );
} }
if (!message['audio'] && sender != 'status@broadcast') { if (!message['audio'] && !message['poll'] && sender != 'status@broadcast') {
this.logger.verbose('Sending message'); this.logger.verbose('Sending message');
return await this.client.sendMessage( return await this.client.sendMessage(
sender, sender,
@ -3137,9 +3135,9 @@ export class WAStartupService {
if (!group) throw new BadRequestException('Group not found'); if (!group) throw new BadRequestException('Group not found');
onWhatsapp.push(new OnWhatsAppDto(group.id, !!group?.id, group?.subject)); onWhatsapp.push(new OnWhatsAppDto(group.id, !!group?.id, number, group?.subject));
} else if (jid === 'status@broadcast') { } else if (jid === 'status@broadcast') {
onWhatsapp.push(new OnWhatsAppDto(jid, false)); onWhatsapp.push(new OnWhatsAppDto(jid, false, number));
} else { } else {
jid = !jid.startsWith('+') ? `+${jid}` : jid; jid = !jid.startsWith('+') ? `+${jid}` : jid;
const verify = await this.client.onWhatsApp(jid); const verify = await this.client.onWhatsApp(jid);
@ -3147,9 +3145,9 @@ export class WAStartupService {
const result = verify[0]; const result = verify[0];
if (!result) { if (!result) {
onWhatsapp.push(new OnWhatsAppDto(jid, false)); onWhatsapp.push(new OnWhatsAppDto(jid, false, number));
} else { } else {
onWhatsapp.push(new OnWhatsAppDto(result.jid, result.exists)); onWhatsapp.push(new OnWhatsAppDto(result.jid, result.exists, number));
} }
} }
} }
@ -3744,6 +3742,16 @@ export class WAStartupService {
} }
} }
public async acceptInviteCode(id: AcceptGroupInvite) {
this.logger.verbose('Joining the group by invitation code: ' + id.inviteCode);
try {
const groupJid = await this.client.groupAcceptInvite(id.inviteCode);
return { accepted: true, groupJid: groupJid };
} catch (error) {
throw new NotFoundException('Accept invite error', error.toString());
}
}
public async revokeInviteCode(id: GroupJid) { public async revokeInviteCode(id: GroupJid) {
this.logger.verbose('Revoking invite code for group: ' + id.groupJid); this.logger.verbose('Revoking invite code for group: ' + id.groupJid);
try { try {

View File

@ -119,7 +119,7 @@ export const websocketController = new WebsocketController(websocketService);
const proxyService = new ProxyService(waMonitor); const proxyService = new ProxyService(waMonitor);
export const proxyController = new ProxyController(proxyService); export const proxyController = new ProxyController(proxyService, waMonitor);
const chamaaiService = new ChamaaiService(waMonitor, configService); const chamaaiService = new ChamaaiService(waMonitor, configService);