Fix: Snippets
This commit is contained in:
@@ -3,171 +3,172 @@ namespace WhatsappPromo.Engine.Wapi
|
||||
public static class JsSnippets
|
||||
{
|
||||
public const string MediaExtractor = @"
|
||||
console.log('Injected MediaExtractor initialized');
|
||||
|
||||
// Set to keep track of processed message IDs
|
||||
window.processedMessages = new Set();
|
||||
|
||||
window.scanForMedia = async () => {
|
||||
// Buscar todos los contenedores de mensajes
|
||||
const messages = document.querySelectorAll('div[role=""row""]');
|
||||
(function() {
|
||||
var log = function(m) { if (window.onLog) window.onLog(m); };
|
||||
log('[WAPP] Injected MediaExtractor initialized');
|
||||
|
||||
for (const msgRow of messages) {
|
||||
// Chequear si es mensaje entrante (ignorar mensajes enviados)
|
||||
if (!msgRow.classList.contains('message-in')) continue;
|
||||
if (!window.processedMessages) window.processedMessages = new Set();
|
||||
|
||||
var triggerHumanClick = function(el) {
|
||||
if (!el) return;
|
||||
try {
|
||||
el.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'instant' });
|
||||
var rect = el.getBoundingClientRect();
|
||||
var x = rect.left + rect.width / 2;
|
||||
var y = rect.top + rect.height / 2;
|
||||
var opt = { bubbles: true, cancelable: true, view: window, clientX: x, clientY: y, buttons: 1 };
|
||||
|
||||
el.dispatchEvent(new MouseEvent('mouseover', opt));
|
||||
el.dispatchEvent(new MouseEvent('mousedown', opt));
|
||||
el.focus();
|
||||
|
||||
setTimeout(function() {
|
||||
el.dispatchEvent(new MouseEvent('mouseup', opt));
|
||||
el.dispatchEvent(new MouseEvent('click', opt));
|
||||
if (el.click) el.click();
|
||||
}, 50);
|
||||
} catch(e) { log('Error en clic: ' + e.message); }
|
||||
};
|
||||
|
||||
// Chequear si tiene imagen/video
|
||||
const img = msgRow.querySelector('img[src^=""blob:""]');
|
||||
if (!img) continue;
|
||||
|
||||
const blobUrl = img.src;
|
||||
|
||||
// Usar ID único para evitar duplicados
|
||||
const msgId = msgRow.getAttribute('data-id') || blobUrl;
|
||||
if (window.processedMessages.has(msgId)) continue;
|
||||
|
||||
window.processedMessages.add(msgId);
|
||||
|
||||
console.log('Found new media:', blobUrl);
|
||||
|
||||
try {
|
||||
// EXTRACCIÓN AUTOMÁTICA DE TELÉFONO (múltiples métodos)
|
||||
let phone = '';
|
||||
|
||||
// MÉTODO 1: Buscar en atributos data-* del mensaje (más confiable)
|
||||
// WhatsApp Web usa atributos internos que pueden contener el JID (phone@c.us)
|
||||
const dataPrePlainText = msgRow.querySelector('[data-pre-plain-text]');
|
||||
if (dataPrePlainText && !phone) {
|
||||
const prePlainText = dataPrePlainText.getAttribute('data-pre-plain-text');
|
||||
// Formato típico: ""[HH:MM, DD/MM/YYYY] Nombre: ""
|
||||
// Pero en algunos casos incluye el número
|
||||
const match = prePlainText.match(/(\d{10,15})/);
|
||||
if (match) {
|
||||
phone = match[1];
|
||||
console.log('Phone extracted from data-pre-plain-text:', phone);
|
||||
}
|
||||
}
|
||||
|
||||
// MÉTODO 2: Buscar en el elemento padre que podría tener data-id con formato phone@c.us
|
||||
if (!phone) {
|
||||
let parentEl = msgRow;
|
||||
for (let i = 0; i < 5; i++) { // Subir hasta 5 niveles
|
||||
if (!parentEl) break;
|
||||
const dataId = parentEl.getAttribute('data-id');
|
||||
if (dataId && dataId.includes('@')) {
|
||||
// Formato: ""false_5491112345678@c.us_MESSAGEID""
|
||||
const parts = dataId.split('_');
|
||||
for (const part of parts) {
|
||||
if (part.includes('@c.us')) {
|
||||
const phoneMatch = part.match(/(\d{10,15})@/);
|
||||
if (phoneMatch) {
|
||||
phone = phoneMatch[1];
|
||||
console.log('Phone extracted from data-id:', phone);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (phone) break;
|
||||
}
|
||||
parentEl = parentEl.parentElement;
|
||||
}
|
||||
}
|
||||
|
||||
// MÉTODO 3: Buscar en elementos cercanos con atributos title o aria-label
|
||||
if (!phone) {
|
||||
const titleElements = msgRow.querySelectorAll('[title], [aria-label]');
|
||||
for (const el of titleElements) {
|
||||
const text = el.getAttribute('title') || el.getAttribute('aria-label') || '';
|
||||
const phoneMatch = text.match(/\+?(\d{10,15})/);
|
||||
if (phoneMatch && phoneMatch[1].length >= 10) {
|
||||
phone = phoneMatch[1].replace(/\D/g, '');
|
||||
console.log('Phone extracted from title/aria-label:', phone);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MÉTODO 4 (FALLBACK): Header del chat abierto (requiere intervención humana)
|
||||
// Solo se usa si los métodos anteriores fallaron
|
||||
if (!phone) {
|
||||
const headerTitle = document.querySelector('header span[title]');
|
||||
if (headerTitle) {
|
||||
phone = headerTitle.getAttribute('title').replace(/\D/g, '');
|
||||
console.log('Phone extracted from header (fallback):', phone);
|
||||
}
|
||||
}
|
||||
|
||||
// MÉTODO 5 (ÚLTIMO RECURSO): Buscar el chat activo en la lista lateral
|
||||
if (!phone) {
|
||||
const activeChat = document.querySelector('div[aria-selected=""true""]');
|
||||
if (activeChat) {
|
||||
const chatTitle = activeChat.querySelector('span[title]');
|
||||
if (chatTitle) {
|
||||
const titleText = chatTitle.getAttribute('title');
|
||||
const phoneMatch = titleText.match(/\+?(\d{10,15})/);
|
||||
if (phoneMatch) {
|
||||
phone = phoneMatch[1].replace(/\D/g, '');
|
||||
console.log('Phone extracted from active chat:', phone);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// VALIDACIÓN FINAL
|
||||
if (!phone || phone.length < 8) {
|
||||
console.warn('❌ No se pudo detectar el teléfono con ningún método. Mensaje ignorado.');
|
||||
console.warn('💡 Sugerencia: Asegúrate de tener WhatsApp Web completamente cargado.');
|
||||
// NO llamar a onMediaDownloaded, simplemente ignorar este mensaje
|
||||
// y continuar con el siguiente
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log('✅ Teléfono detectado exitosamente:', phone);
|
||||
|
||||
// Simular pequeño retraso random antes de descargar
|
||||
await new Promise(r => setTimeout(r, Math.random() * 2000 + 500));
|
||||
|
||||
const response = await fetch(blobUrl);
|
||||
const blob = await response.blob();
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.readAsDataURL(blob);
|
||||
reader.onloadend = () => {
|
||||
const base64data = reader.result.split(',')[1];
|
||||
const mimeType = blob.type;
|
||||
|
||||
// Enviar a C#
|
||||
window.onMediaDownloaded(phone, base64data, mimeType);
|
||||
};
|
||||
} catch (err) {
|
||||
console.error('Error downloading media:', err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Anti-detección: Usar MutationObserver en lugar de polling constante
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
let shouldScan = false;
|
||||
for(const mutation of mutations) {
|
||||
if (mutation.addedNodes.length > 0) {
|
||||
shouldScan = true;
|
||||
break;
|
||||
window.getIdentityStrict = function() {
|
||||
// El usuario nos dio el selector exacto: span[data-testid=""selectable-text""]
|
||||
// Buscamos cualquier elemento con ese ID que contenga un ""+""
|
||||
var potentialNumbers = Array.from(document.querySelectorAll('span[data-testid=""selectable-text""]'));
|
||||
|
||||
for (var i = 0; i < potentialNumbers.length; i++) {
|
||||
var el = potentialNumbers[i];
|
||||
var text = (el.textContent || '').trim();
|
||||
|
||||
// Solo tomamos números que empiecen con + y tengan longitud válida
|
||||
if (text.startsWith('+') && text.replace(/\D/g, '').length >= 10) {
|
||||
// IMPORTANTE: El panel lateral siempre está a la derecha (más del 50% del ancho)
|
||||
var rect = el.getBoundingClientRect();
|
||||
if (rect.left > (window.innerWidth / 2)) {
|
||||
return text.replace(/[^\d+]/g, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldScan) {
|
||||
// Debounce ligero
|
||||
if (window.scanTimeout) clearTimeout(window.scanTimeout);
|
||||
window.scanTimeout = setTimeout(window.scanForMedia, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
// Conectar al contenedor principal de la app
|
||||
const appRoot = document.getElementById('app') || document.body;
|
||||
observer.observe(appRoot, { childList: true, subtree: true });
|
||||
|
||||
// Fallback: ejecutar scan ocasionalmente por si acaso
|
||||
setInterval(window.scanForMedia, 10000);
|
||||
// Si llegamos aquÃ, el número no se encontró en la parte derecha
|
||||
// Intentamos abrir el panel clicando en la cabecera del chat
|
||||
var header = document.querySelector('#main header');
|
||||
if (header) {
|
||||
log('[WAPP] No se detecta número en la derecha. Intentando abrir/recargar panel lateral...');
|
||||
// Clicamos en la zona del nombre/avatar
|
||||
var clickArea = header.querySelector('div[role=""button""]') ||
|
||||
header.querySelector('div._akz_') ||
|
||||
header;
|
||||
triggerHumanClick(clickArea);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
window.openUnreadChats = async function() {
|
||||
var markers = Array.from(document.querySelectorAll('span[aria-label]')).filter(function(s) {
|
||||
var lb = (s.getAttribute('aria-label') || '').toLowerCase();
|
||||
return (lb.indexOf('unread') !== -1 || lb.indexOf('leÃdo') !== -1 || lb.indexOf('leido') !== -1) && /^\d+$/.test(s.textContent.trim());
|
||||
});
|
||||
|
||||
if (markers.length > 0) {
|
||||
for (var i = 0; i < markers.length; i++) {
|
||||
try {
|
||||
var marker = markers[i];
|
||||
var row = marker.closest('div[role=""listitem""]') ||
|
||||
marker.closest('div[role=""gridcell""]') ||
|
||||
marker.closest('[data-testid^=""list-item""]');
|
||||
|
||||
if (!row || row.getAttribute('aria-selected') === 'true' || row.querySelector('[aria-selected=""true""]')) continue;
|
||||
|
||||
log('[WAPP] >>> Navegando a chat pendiente...');
|
||||
var target = row.querySelector('div[data-testid=""cell-frame-container""]') || row;
|
||||
triggerHumanClick(target);
|
||||
await new Promise(r => setTimeout(r, 6000));
|
||||
break;
|
||||
} catch (e) { log('Error nav: ' + e.message); }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.scanForMedia = async function() {
|
||||
// 1. Descargas pendientes (kB/MB)
|
||||
var btns = document.querySelectorAll('div[role=""button""]');
|
||||
for (var i = 0; i < btns.length; i++) {
|
||||
var text = btns[i].textContent || '';
|
||||
if (text.match(/\d+ (kB|MB)/) && !btns[i].getAttribute('data-scan-clicked')) {
|
||||
log('[WAPP] Forzando clic de descarga: ' + text);
|
||||
btns[i].setAttribute('data-scan-clicked', 'true');
|
||||
triggerHumanClick(btns[i]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Identificación ESTRICTA (Solo derecha de la pantalla)
|
||||
var iden = window.getIdentityStrict();
|
||||
if (!iden) {
|
||||
// Espera más larga para que el panel cargue número (+54...)
|
||||
await new Promise(r => setTimeout(r, 5000));
|
||||
iden = window.getIdentityStrict();
|
||||
}
|
||||
|
||||
if (!iden) {
|
||||
console.error('[WAPP] ERROR CRÃTICO: No se encontró número con ""+"" en el panel lateral derecho. Abortando guardado.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Captura de contenido
|
||||
var messages = document.querySelectorAll('.message-in, [data-testid=""msg-container""]');
|
||||
for (var j = 0; j < messages.length; j++) {
|
||||
var msg = messages[j];
|
||||
var msgId = msg.getAttribute('data-id') || msg.closest('[data-id]')?.getAttribute('data-id') || '';
|
||||
var contents = msg.querySelectorAll('img[src^=""blob:""], video[src^=""blob:""]');
|
||||
|
||||
for (var k = 0; k < contents.length; k++) {
|
||||
var media = contents[k];
|
||||
var url = media.src || media.getAttribute('src');
|
||||
if (!url || !url.startsWith('blob:')) continue;
|
||||
|
||||
var key = msgId + '_' + k;
|
||||
if (window.processedMessages.has(key)) continue;
|
||||
window.processedMessages.add(key);
|
||||
|
||||
log('[WAPP] Número validado: ' + iden + '. Guardando archivo...');
|
||||
|
||||
try {
|
||||
await new Promise(r => setTimeout(r, 2000));
|
||||
var res = await fetch(url);
|
||||
var b = await res.blob();
|
||||
var rd = new FileReader();
|
||||
rd.readAsDataURL(b);
|
||||
rd.onloadend = function() {
|
||||
var b64 = rd.result.split(',')[1];
|
||||
window.onMediaDownloaded(iden, b64, b.type);
|
||||
};
|
||||
} catch (err) { log('Error de guardado: ' + err.message); }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var obs = new MutationObserver(function(muts) {
|
||||
var hasNew = false;
|
||||
for(var m=0; m<muts.length; m++) { if(muts[m].addedNodes.length > 0) { hasNew = true; break; } }
|
||||
if (hasNew) {
|
||||
if (window.st) clearTimeout(window.st);
|
||||
window.st = setTimeout(window.scanForMedia, 2000);
|
||||
}
|
||||
});
|
||||
|
||||
var startMonitor = function() {
|
||||
var target = document.getElementById('app') || document.body;
|
||||
if (!target) { setTimeout(startMonitor, 1000); return; }
|
||||
obs.observe(target, { childList: true, subtree: true });
|
||||
log('[WAPP] Monitor activo');
|
||||
};
|
||||
|
||||
startMonitor();
|
||||
setInterval(window.scanForMedia, 14000);
|
||||
setInterval(window.openUnreadChats, 11000);
|
||||
})();
|
||||
";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,11 @@ namespace WhatsappPromo.Worker
|
||||
var page = await _browserService.GetPageAsync();
|
||||
|
||||
// Configurar bindings
|
||||
await page.ExposeFunctionAsync("onLog", (Func<string, Task>)(async (string msg) =>
|
||||
{
|
||||
await SafeSendAsync("LogUpdate", $"[DEBUG] {msg}");
|
||||
}));
|
||||
|
||||
await page.ExposeFunctionAsync("onNewMessage", (Func<string, Task>)(async (string jsonMessage) =>
|
||||
{
|
||||
await SafeSendAsync("LogUpdate", $"Mensaje recibido: {jsonMessage}");
|
||||
@@ -113,7 +118,7 @@ namespace WhatsappPromo.Worker
|
||||
}, stoppingToken);
|
||||
}));
|
||||
|
||||
await page.EvaluateFunctionOnNewDocumentAsync(JsSnippets.MediaExtractor);
|
||||
await page.EvaluateExpressionOnNewDocumentAsync(JsSnippets.MediaExtractor);
|
||||
await page.ReloadAsync();
|
||||
|
||||
// Procesamiento en segundo plano
|
||||
|
||||
Reference in New Issue
Block a user