Compare commits

..

1 Commits

Author SHA1 Message Date
Davidson Gomes
cd800f2976 Merge branch 'release/2.3.7'
Some checks failed
Check Code Quality / check-lint-and-build (push) Has been cancelled
Build Docker image / Build and Deploy (push) Has been cancelled
Security Scan / CodeQL Analysis (javascript) (push) Has been cancelled
Security Scan / Dependency Review (push) Has been cancelled
2025-12-05 11:28:52 -03:00
5 changed files with 0 additions and 295 deletions

View File

@@ -1,7 +1,6 @@
import {
ArchiveChatDto,
BlockUserDto,
DecryptPollVoteDto,
DeleteMessage,
getBase64FromMediaMessageDto,
MarkChatUnreadDto,
@@ -114,12 +113,4 @@ export class ChatController {
public async blockUser({ instanceName }: InstanceDto, data: BlockUserDto) {
return await this.waMonitor.waInstances[instanceName].blockUser(data);
}
public async decryptPollVote({ instanceName }: InstanceDto, data: DecryptPollVoteDto) {
const pollCreationMessageKey = {
id: data.message.key.id,
remoteJid: data.remoteJid,
};
return await this.waMonitor.waInstances[instanceName].baileysDecryptPollVote(pollCreationMessageKey);
}
}

View File

@@ -127,12 +127,3 @@ export class BlockUserDto {
number: string;
status: 'block' | 'unblock';
}
export class DecryptPollVoteDto {
message: {
key: {
id: string;
};
};
remoteJid: string;
}

View File

@@ -5119,247 +5119,4 @@ export class BaileysStartupService extends ChannelStartupService {
},
};
}
public async baileysDecryptPollVote(pollCreationMessageKey: proto.IMessageKey) {
try {
this.logger.verbose('Starting poll vote decryption process');
// Buscar a mensagem de criação da enquete
const pollCreationMessage = (await this.getMessage(pollCreationMessageKey, true)) as proto.IWebMessageInfo;
if (!pollCreationMessage) {
throw new NotFoundException('Poll creation message not found');
}
// Extrair opções da enquete
const pollOptions =
(pollCreationMessage.message as any)?.pollCreationMessage?.options ||
(pollCreationMessage.message as any)?.pollCreationMessageV3?.options ||
[];
if (!pollOptions || pollOptions.length === 0) {
throw new NotFoundException('Poll options not found');
}
// Recuperar chave de criptografia
const pollMessageSecret = (await this.getMessage(pollCreationMessageKey)) as any;
let pollEncKey = pollMessageSecret?.messageContextInfo?.messageSecret;
if (!pollEncKey) {
throw new NotFoundException('Poll encryption key not found');
}
// Normalizar chave de criptografia
if (typeof pollEncKey === 'string') {
pollEncKey = Buffer.from(pollEncKey, 'base64');
} else if (pollEncKey?.type === 'Buffer' && Array.isArray(pollEncKey.data)) {
pollEncKey = Buffer.from(pollEncKey.data);
}
if (Buffer.isBuffer(pollEncKey) && pollEncKey.length === 44) {
pollEncKey = Buffer.from(pollEncKey.toString('utf8'), 'base64');
}
// Buscar todas as mensagens de atualização de votos
const allPollUpdateMessages = await this.prismaRepository.message.findMany({
where: {
instanceId: this.instanceId,
messageType: 'pollUpdateMessage',
},
select: {
id: true,
key: true,
message: true,
messageTimestamp: true,
},
});
this.logger.verbose(`Found ${allPollUpdateMessages.length} pollUpdateMessage messages in database`);
// Filtrar apenas mensagens relacionadas a esta enquete específica
const pollUpdateMessages = allPollUpdateMessages.filter((msg) => {
const pollUpdate = (msg.message as any)?.pollUpdateMessage;
if (!pollUpdate) return false;
const creationKey = pollUpdate.pollCreationMessageKey;
if (!creationKey) return false;
return (
creationKey.id === pollCreationMessageKey.id &&
jidNormalizedUser(creationKey.remoteJid || '') === jidNormalizedUser(pollCreationMessageKey.remoteJid || '')
);
});
this.logger.verbose(`Filtered to ${pollUpdateMessages.length} matching poll update messages`);
// Preparar candidatos de JID para descriptografia
const creatorCandidates = [
this.instance.wuid,
this.client.user?.lid,
pollCreationMessage.key.participant,
(pollCreationMessage.key as any).participantAlt,
pollCreationMessage.key.remoteJid,
(pollCreationMessage.key as any).remoteJidAlt,
].filter(Boolean);
const uniqueCreators = [...new Set(creatorCandidates.map((id) => jidNormalizedUser(id)))];
// Processar votos
const votesByUser = new Map<string, { timestamp: number; selectedOptions: string[]; voterJid: string }>();
this.logger.verbose(`Processing ${pollUpdateMessages.length} poll update messages for decryption`);
for (const pollUpdateMsg of pollUpdateMessages) {
const pollVote = (pollUpdateMsg.message as any)?.pollUpdateMessage?.vote;
if (!pollVote) continue;
const key = pollUpdateMsg.key as any;
const voterCandidates = [
this.instance.wuid,
this.client.user?.lid,
key.participant,
key.participantAlt,
key.remoteJidAlt,
key.remoteJid,
].filter(Boolean);
const uniqueVoters = [...new Set(voterCandidates.map((id) => jidNormalizedUser(id)))];
let selectedOptionNames: string[] = [];
let successfulVoterJid: string | undefined;
// Verificar se o voto já está descriptografado
if (pollVote.selectedOptions && Array.isArray(pollVote.selectedOptions)) {
const selectedOptions = pollVote.selectedOptions;
this.logger.verbose('Vote already has selectedOptions, checking format');
// Verificar se são strings (já descriptografado) ou buffers (precisa descriptografar)
if (selectedOptions.length > 0 && typeof selectedOptions[0] === 'string') {
// Já está descriptografado como nomes de opções
selectedOptionNames = selectedOptions;
successfulVoterJid = uniqueVoters[0];
this.logger.verbose(`Using already decrypted vote: voter=${successfulVoterJid}, options=${selectedOptionNames.join(',')}`);
} else {
// Está como hash, precisa converter para nomes
selectedOptionNames = pollOptions
.filter((option: any) => {
const hash = createHash('sha256').update(option.optionName).digest();
return selectedOptions.some((selected: any) => {
if (Buffer.isBuffer(selected)) {
return Buffer.compare(selected, hash) === 0;
}
return false;
});
})
.map((option: any) => option.optionName);
successfulVoterJid = uniqueVoters[0];
}
} else if (pollVote.encPayload && pollEncKey) {
// Tentar descriptografar
let decryptedVote: any = null;
for (const creator of uniqueCreators) {
for (const voter of uniqueVoters) {
try {
decryptedVote = decryptPollVote(pollVote, {
pollCreatorJid: creator,
pollMsgId: pollCreationMessage.key.id,
pollEncKey,
voterJid: voter,
} as any);
if (decryptedVote) {
successfulVoterJid = voter;
break;
}
} catch (error) {
// Continue tentando outras combinações
}
}
if (decryptedVote) break;
}
if (decryptedVote && decryptedVote.selectedOptions) {
// Converter hashes para nomes de opções
selectedOptionNames = pollOptions
.filter((option: any) => {
const hash = createHash('sha256').update(option.optionName).digest();
return decryptedVote.selectedOptions.some((selected: any) => {
if (Buffer.isBuffer(selected)) {
return Buffer.compare(selected, hash) === 0;
}
return false;
});
})
.map((option: any) => option.optionName);
this.logger.verbose(`Successfully decrypted vote for voter: ${successfulVoterJid}, creator: ${uniqueCreators[0]}`);
} else {
this.logger.warn(`Failed to decrypt vote. Last error: Could not decrypt with any combination`);
continue;
}
} else {
this.logger.warn('Vote has no encPayload and no selectedOptions, skipping');
continue;
}
if (selectedOptionNames.length > 0 && successfulVoterJid) {
const normalizedVoterJid = jidNormalizedUser(successfulVoterJid);
const existingVote = votesByUser.get(normalizedVoterJid);
// Manter apenas o voto mais recente de cada usuário
if (!existingVote || pollUpdateMsg.messageTimestamp > existingVote.timestamp) {
votesByUser.set(normalizedVoterJid, {
timestamp: pollUpdateMsg.messageTimestamp,
selectedOptions: selectedOptionNames,
voterJid: successfulVoterJid,
});
}
}
}
// Agrupar votos por opção
const results: Record<string, { votes: number; voters: string[] }> = {};
// Inicializar todas as opções com zero votos
pollOptions.forEach((option: any) => {
results[option.optionName] = {
votes: 0,
voters: [],
};
});
// Agregar votos
votesByUser.forEach((voteData) => {
voteData.selectedOptions.forEach((optionName) => {
if (results[optionName]) {
results[optionName].votes++;
if (!results[optionName].voters.includes(voteData.voterJid)) {
results[optionName].voters.push(voteData.voterJid);
}
}
});
});
// Obter nome da enquete
const pollName =
(pollCreationMessage.message as any)?.pollCreationMessage?.name ||
(pollCreationMessage.message as any)?.pollCreationMessageV3?.name ||
'Enquete sem nome';
// Calcular total de votos únicos
const totalVotes = votesByUser.size;
return {
poll: {
name: pollName,
totalVotes,
results,
},
};
} catch (error) {
this.logger.error(`Error decrypting poll votes: ${error}`);
throw new InternalServerErrorException('Error decrypting poll votes', error.toString());
}
}
}

View File

@@ -2,7 +2,6 @@ import { RouterBroker } from '@api/abstract/abstract.router';
import {
ArchiveChatDto,
BlockUserDto,
DecryptPollVoteDto,
DeleteMessage,
getBase64FromMediaMessageDto,
MarkChatUnreadDto,
@@ -24,7 +23,6 @@ import {
archiveChatSchema,
blockUserSchema,
contactValidateSchema,
decryptPollVoteSchema,
deleteMessageSchema,
markChatUnreadSchema,
messageUpSchema,
@@ -283,16 +281,6 @@ export class ChatRouter extends RouterBroker {
});
return res.status(HttpStatus.CREATED).json(response);
})
.post(this.routerPath('getPollVote'), ...guards, async (req, res) => {
const response = await this.dataValidate<DecryptPollVoteDto>({
request: req,
schema: decryptPollVoteSchema,
ClassRef: DecryptPollVoteDto,
execute: (instance, data) => chatController.decryptPollVote(instance, data),
});
return res.status(HttpStatus.OK).json(response);
});
}

View File

@@ -447,25 +447,3 @@ export const buttonsMessageSchema: JSONSchema7 = {
},
required: ['number'],
};
export const decryptPollVoteSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
message: {
type: 'object',
properties: {
key: {
type: 'object',
properties: {
id: { type: 'string' },
},
required: ['id'],
},
},
required: ['key'],
},
remoteJid: { type: 'string' },
},
required: ['message', 'remoteJid'],
};