feat: Added audio to mp4 converter in optionally get Base64 From MediaMessage

This commit is contained in:
Davidson Gomes 2023-07-04 17:16:31 -03:00
parent 870968a7c5
commit f51c3b6519
12 changed files with 132 additions and 39 deletions

View File

@ -1,5 +1,10 @@
# 1.1.3 (homolog) # 1.1.3 (homolog)
### Features
* Added configuration for Baileys log level in env
* Added audio to mp4 converter in optionally get Base64 From MediaMessage
### Fixed ### Fixed
* Added timestamp internally in urls to avoid caching * Added timestamp internally in urls to avoid caching
@ -8,6 +13,7 @@
* Fixed cash when sending stickers via url * Fixed cash when sending stickers via url
* Improved how Redis works for instances * Improved how Redis works for instances
* Fixed problem when disconnecting the instance it removes the instance * Fixed problem when disconnecting the instance it removes the instance
* Fixed problem sending ack when preview is done by me
# 1.1.2 (2023-06-28 13:43) # 1.1.2 (2023-06-28 13:43)

View File

@ -16,7 +16,7 @@ ENV CORS_ORIGIN="*"
ENV CORS_METHODS="POST,GET,PUT,DELETE" ENV CORS_METHODS="POST,GET,PUT,DELETE"
ENV CORS_CREDENTIALS=true ENV CORS_CREDENTIALS=true
ENV LOG_LEVEL="ERROR,WARN,DEBUG,INFO,LOG,VERBOSE,DARK" ENV LOG_LEVEL=$LOG_LEVEL
ENV LOG_COLOR=true ENV LOG_COLOR=true
ENV DEL_INSTANCE=$DEL_INSTANCE ENV DEL_INSTANCE=$DEL_INSTANCE

View File

@ -14,10 +14,12 @@ services:
- evolution_instances:/evolution/instances - evolution_instances:/evolution/instances
- evolution_store:/evolution/store - evolution_store:/evolution/store
environment: environment:
- LOG_LEVEL=ERROR,WARN,DEBUG,INFO,LOG,VERBOSE,DARK,WEBHOOKS
- LOG_BAILEYS=error
# Determine how long the instance should be deleted from memory in case of no connection. # Determine how long the instance should be deleted from memory in case of no connection.
# Default time: 5 minutes # Default time: 5 minutes
# If you don't even want an expiration, enter the value false # If you don't even want an expiration, enter the value false
- DEL_INSTANCE=5 # or false - DEL_INSTANCE=false # 5 or false
# Temporary data storage # Temporary data storage
- STORE_MESSAGES=true - STORE_MESSAGES=true
- STORE_MESSAGE_UP=true - STORE_MESSAGE_UP=true

View File

@ -13,10 +13,22 @@ export type Cors = {
CREDENTIALS: boolean; CREDENTIALS: boolean;
}; };
export type LogLevel = 'ERROR' | 'WARN' | 'DEBUG' | 'INFO' | 'LOG' | 'VERBOSE' | 'DARK'; export type LogBaileys = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace';
export type LogLevel =
| 'ERROR'
| 'WARN'
| 'DEBUG'
| 'INFO'
| 'LOG'
| 'VERBOSE'
| 'DARK'
| 'WEBHOOKS';
export type Log = { export type Log = {
LEVEL: LogLevel[]; LEVEL: LogLevel[];
COLOR: boolean; COLOR: boolean;
BAILEYS: LogBaileys;
}; };
export type SaveData = { export type SaveData = {
@ -204,6 +216,7 @@ export class ConfigService {
LOG: { LOG: {
LEVEL: process.env?.LOG_LEVEL.split(',') as LogLevel[], LEVEL: process.env?.LOG_LEVEL.split(',') as LogLevel[],
COLOR: process.env?.LOG_COLOR === 'true', COLOR: process.env?.LOG_COLOR === 'true',
BAILEYS: (process.env?.LOG_BAILEYS as LogBaileys) || 'error',
}, },
DEL_INSTANCE: isBooleanString(process.env?.DEL_INSTANCE) DEL_INSTANCE: isBooleanString(process.env?.DEL_INSTANCE)
? process.env.DEL_INSTANCE === 'true' ? process.env.DEL_INSTANCE === 'true'

View File

@ -36,12 +36,14 @@ LOG:
- LOG - LOG
- VERBOSE - VERBOSE
- DARK - DARK
- WEBHOOKS
COLOR: true COLOR: true
BAILEYS: error # "fatal" | "error" | "warn" | "info" | "debug" | "trace"
# Determine how long the instance should be deleted from memory in case of no connection. # Determine how long the instance should be deleted from memory in case of no connection.
# Default time: 5 minutes # Default time: 5 minutes
# If you don't even want an expiration, enter the value false # If you don't even want an expiration, enter the value false
DEL_INSTANCE: 5 # or false DEL_INSTANCE: false # or false
# Temporary data storage # Temporary data storage
STORE: STORE:

View File

@ -9,6 +9,7 @@ import {
ProfileStatusDto, ProfileStatusDto,
ReadMessageDto, ReadMessageDto,
WhatsAppNumberDto, WhatsAppNumberDto,
getBase64FromMediaMessageDto,
} from '../dto/chat.dto'; } from '../dto/chat.dto';
import { InstanceDto } from '../dto/instance.dto'; import { InstanceDto } from '../dto/instance.dto';
import { ContactQuery } from '../repository/contact.repository'; import { ContactQuery } from '../repository/contact.repository';
@ -45,11 +46,9 @@ export class ChatController {
public async getBase64FromMediaMessage( public async getBase64FromMediaMessage(
{ instanceName }: InstanceDto, { instanceName }: InstanceDto,
message: proto.IWebMessageInfo, data: getBase64FromMediaMessageDto,
) { ) {
return await this.waMonitor.waInstances[instanceName].getBase64FromMediaMessage( return await this.waMonitor.waInstances[instanceName].getBase64FromMediaMessage(data);
message,
);
} }
public async fetchMessages({ instanceName }: InstanceDto, query: MessageQuery) { public async fetchMessages({ instanceName }: InstanceDto, query: MessageQuery) {

View File

@ -207,7 +207,6 @@ export class InstanceController {
); );
this.waMonitor.waInstances[instanceName]?.client?.ws?.close(); this.waMonitor.waInstances[instanceName]?.client?.ws?.close();
this.waMonitor.waInstances[instanceName]?.client?.end(undefined);
return { error: false, message: 'Instance logged out' }; return { error: false, message: 'Instance logged out' };
} catch (error) { } catch (error) {

View File

@ -2,6 +2,7 @@ import {
WAPrivacyOnlineValue, WAPrivacyOnlineValue,
WAPrivacyValue, WAPrivacyValue,
WAReadReceiptsValue, WAReadReceiptsValue,
proto,
} from '@whiskeysockets/baileys'; } from '@whiskeysockets/baileys';
export class OnWhatsAppDto { export class OnWhatsAppDto {
@ -12,6 +13,11 @@ export class OnWhatsAppDto {
) {} ) {}
} }
export class getBase64FromMediaMessageDto {
message: proto.WebMessageInfo;
convertToMp4?: boolean;
}
export class WhatsAppNumberDto { export class WhatsAppNumberDto {
numbers: string[]; numbers: string[];
} }

View File

@ -22,6 +22,7 @@ import {
ProfileStatusDto, ProfileStatusDto,
ReadMessageDto, ReadMessageDto,
WhatsAppNumberDto, WhatsAppNumberDto,
getBase64FromMediaMessageDto,
} from '../dto/chat.dto'; } from '../dto/chat.dto';
import { ContactQuery } from '../repository/contact.repository'; import { ContactQuery } from '../repository/contact.repository';
import { MessageQuery } from '../repository/message.repository'; import { MessageQuery } from '../repository/message.repository';
@ -101,10 +102,10 @@ export class ChatRouter extends RouterBroker {
return res.status(HttpStatus.OK).json(response); return res.status(HttpStatus.OK).json(response);
}) })
.post(this.routerPath('getBase64FromMediaMessage'), ...guards, async (req, res) => { .post(this.routerPath('getBase64FromMediaMessage'), ...guards, async (req, res) => {
const response = await this.dataValidate<proto.IWebMessageInfo>({ const response = await this.dataValidate<getBase64FromMediaMessageDto>({
request: req, request: req,
schema: null, schema: null,
ClassRef: Object, ClassRef: getBase64FromMediaMessageDto,
execute: (instance, data) => execute: (instance, data) =>
chatController.getBase64FromMediaMessage(instance, data), chatController.getBase64FromMediaMessage(instance, data),
}); });

View File

@ -253,6 +253,13 @@ export class WAMonitoringService {
this.logger.warn(`Instance "${instanceName}" - REMOVED`); this.logger.warn(`Instance "${instanceName}" - REMOVED`);
} }
}); });
this.eventEmitter.on('logout.instance', async (instanceName: string) => {
try {
this.cleaningUp(instanceName);
} finally {
this.logger.warn(`Instance "${instanceName}" - LOGOUT`);
}
});
} }
private noConnection() { private noConnection() {

View File

@ -87,6 +87,7 @@ import {
PrivacySettingDto, PrivacySettingDto,
ReadMessageDto, ReadMessageDto,
WhatsAppNumberDto, WhatsAppNumberDto,
getBase64FromMediaMessageDto,
} from '../dto/chat.dto'; } from '../dto/chat.dto';
import { MessageQuery } from '../repository/message.repository'; import { MessageQuery } from '../repository/message.repository';
import { ContactQuery } from '../repository/contact.repository'; import { ContactQuery } from '../repository/contact.repository';
@ -116,6 +117,7 @@ import NodeCache from 'node-cache';
import { useMultiFileAuthStateRedisDb } from '../../utils/use-multi-file-auth-state-redis-db'; import { useMultiFileAuthStateRedisDb } from '../../utils/use-multi-file-auth-state-redis-db';
import sharp from 'sharp'; import sharp from 'sharp';
import { RedisCache } from '../../db/redis.client'; import { RedisCache } from '../../db/redis.client';
import { Log } from '../../config/env.config';
export class WAStartupService { export class WAStartupService {
constructor( constructor(
@ -137,7 +139,8 @@ export class WAStartupService {
private readonly msgRetryCounterCache: CacheStore = new NodeCache(); private readonly msgRetryCounterCache: CacheStore = new NodeCache();
private readonly userDevicesCache: CacheStore = new NodeCache(); private readonly userDevicesCache: CacheStore = new NodeCache();
private endSession = false; private endSession = false;
private store = makeInMemoryStore({ logger: P({ level: 'error' }) }); private logBaileys = this.configService.get<Log>('LOG').BAILEYS;
private store = makeInMemoryStore({ logger: P({ level: this.logBaileys }) });
public set instanceName(name: string) { public set instanceName(name: string) {
if (!name) { if (!name) {
@ -154,7 +157,7 @@ export class WAStartupService {
public get instanceName() { public get instanceName() {
return this.instance.name; return this.instance.name;
} }
s;
public get wuid() { public get wuid() {
return this.instance.wuid; return this.instance.wuid;
} }
@ -238,14 +241,16 @@ export class WAStartupService {
baseURL = this.localWebhook.url; baseURL = this.localWebhook.url;
} }
this.logger.log({ if (this.configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS')) {
local: WAStartupService.name + '.sendDataWebhook-local', this.logger.log({
url: baseURL, local: WAStartupService.name + '.sendDataWebhook-local',
event, url: baseURL,
instance: this.instance.name, event,
data, instance: this.instance.name,
destination: this.localWebhook.url, data,
}); destination: this.localWebhook.url,
});
}
try { try {
if (this.localWebhook.enabled && isURL(this.localWebhook.url)) { if (this.localWebhook.enabled && isURL(this.localWebhook.url)) {
@ -293,14 +298,16 @@ export class WAStartupService {
localUrl = this.localWebhook.url; localUrl = this.localWebhook.url;
} }
this.logger.log({ if (this.configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS')) {
local: WAStartupService.name + '.sendDataWebhook-global', this.logger.log({
url: globalURL, local: WAStartupService.name + '.sendDataWebhook-global',
event, url: globalURL,
instance: this.instance.name, event,
data, instance: this.instance.name,
destination: localUrl, data,
}); destination: localUrl,
});
}
try { try {
if (globalWebhook && globalWebhook?.ENABLED && isURL(globalURL)) { if (globalWebhook && globalWebhook?.ENABLED && isURL(globalURL)) {
@ -409,6 +416,7 @@ export class WAStartupService {
instance: this.instance.name, instance: this.instance.name,
status: 'removed', status: 'removed',
}); });
this.eventEmitter.emit('logout.instance', this.instance.name, 'inner');
this.client?.ws?.close(); this.client?.ws?.close();
this.client.end(new Error('Close connection')); this.client.end(new Error('Close connection'));
} }
@ -444,7 +452,7 @@ export class WAStartupService {
private async getMessageStore(key: proto.IMessageKey) { private async getMessageStore(key: proto.IMessageKey) {
if (this.store) { if (this.store) {
const msg = await this.store.loadMessage(key.remoteJid!, key.id!); const msg = await this.store.loadMessage(key.remoteJid, key.id);
return msg?.message || undefined; return msg?.message || undefined;
} }
@ -466,7 +474,6 @@ export class WAStartupService {
this.instance.wuid, this.instance.wuid,
)}/*.json`, )}/*.json`,
); );
this.store?.writeToFile(`${this.storePath}/baileys_store_multi.json`);
} }
} }
} catch (error) {} } catch (error) {}
@ -500,7 +507,11 @@ export class WAStartupService {
const session = this.configService.get<ConfigSessionPhone>('CONFIG_SESSION_PHONE'); const session = this.configService.get<ConfigSessionPhone>('CONFIG_SESSION_PHONE');
const browser: WABrowserDescription = [session.CLIENT, session.NAME, release()]; const browser: WABrowserDescription = [session.CLIENT, session.NAME, release()];
this.store?.readFromFile(`${this.storePath}/baileys_store_multi.json`); this.store?.readFromFile(`${this.storePath}/baileys/store.json`);
setInterval(() => {
this.store?.writeToFile(`${this.storePath}/baileys/store.json`);
}, 10_000);
const socketConfig: UserFacingSocketConfig = { const socketConfig: UserFacingSocketConfig = {
auth: { auth: {
@ -510,7 +521,7 @@ export class WAStartupService {
P({ level: 'error' }), P({ level: 'error' }),
), ),
}, },
logger: P({ level: 'error' }), logger: P({ level: this.logBaileys }),
printQRInTerminal: false, printQRInTerminal: false,
browser, browser,
version, version,
@ -724,7 +735,11 @@ export class WAStartupService {
) => { ) => {
const received = messages[0]; const received = messages[0];
if (type !== 'notify' || received.message?.protocolMessage) { if (
type !== 'notify' ||
received.message?.protocolMessage ||
received.message?.pollUpdateMessage
) {
return; return;
} }
@ -742,7 +757,7 @@ export class WAStartupService {
source: getDevice(received.key.id), source: getDevice(received.key.id),
}; };
this.logger.log(received); this.logger.log(messageRaw);
await this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw); await this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw);
await this.repository.message.insert([messageRaw], database.SAVE_DATA.NEW_MESSAGE); await this.repository.message.insert([messageRaw], database.SAVE_DATA.NEW_MESSAGE);
@ -758,7 +773,11 @@ export class WAStartupService {
5: 'PLAYED', 5: 'PLAYED',
}; };
for await (const { key, update } of args) { for await (const { key, update } of args) {
if (key.remoteJid !== 'status@broadcast' && !key?.remoteJid?.match(/(:\d+)/)) { if (
key.remoteJid !== 'status@broadcast' &&
!key?.remoteJid?.match(/(:\d+)/) &&
!key.fromMe
) {
let pollUpdates: any; let pollUpdates: any;
if (update.pollUpdates) { if (update.pollUpdates) {
const pollCreation = await this.getMessageStore(key); const pollCreation = await this.getMessageStore(key);
@ -1053,10 +1072,19 @@ export class WAStartupService {
); );
})(); })();
messageSent['messageType'] = getContentType(messageSent.message); const messageRaw: proto.IWebMessageInfo = {
key: messageSent.key,
messageTimestamp: Long.isLong(messageSent.messageTimestamp)
? messageSent.messageTimestamp?.toNumber()
: messageSent.messageTimestamp,
pushName: messageSent.pushName,
broadcast: messageSent.broadcast,
status: 2,
message: { ...messageSent.message },
};
this.client.ev.emit('messages.upsert', { this.client.ev.emit('messages.upsert', {
messages: [messageSent], messages: [messageRaw],
type: 'notify', type: 'notify',
}); });
@ -1471,12 +1499,19 @@ export class WAStartupService {
} }
} }
public async getBase64FromMediaMessage(m: proto.IWebMessageInfo) { public async getBase64FromMediaMessage(data: getBase64FromMediaMessageDto) {
try { try {
const m = data?.message;
const convertToMp4 = data?.convertToMp4 ?? false;
const msg = m?.message const msg = m?.message
? m ? m
: ((await this.getMessage(m.key, true)) as proto.IWebMessageInfo); : ((await this.getMessage(m.key, true)) as proto.IWebMessageInfo);
if (!msg) {
throw 'Message not found';
}
for (const subtype of MessageSubtype) { for (const subtype of MessageSubtype) {
if (msg.message[subtype]) { if (msg.message[subtype]) {
msg.message = msg.message[subtype].message; msg.message = msg.message[subtype].message;
@ -1511,6 +1546,29 @@ export class WAStartupService {
reuploadRequest: this.client.updateMediaMessage, reuploadRequest: this.client.updateMediaMessage,
}, },
); );
const typeMessage = getContentType(msg.message);
// if for audioMessage converte para mp3
if (convertToMp4 && typeMessage === 'audioMessage') {
const convert = await this.processAudio(buffer.toString('base64'));
if (typeof convert === 'string') {
const audio = fs.readFileSync(convert).toString('base64');
return {
mediaType,
fileName: mediaMessage['fileName'],
caption: mediaMessage['caption'],
size: {
fileLength: mediaMessage['fileLength'],
height: mediaMessage['height'],
width: mediaMessage['width'],
},
mimetype: 'audio/mp4',
base64: Buffer.from(audio, 'base64').toString('base64'),
};
}
}
return { return {
mediaType, mediaType,

Binary file not shown.