Fix: Snippets
This commit is contained in:
@@ -3,171 +3,172 @@ namespace WhatsappPromo.Engine.Wapi
|
|||||||
public static class JsSnippets
|
public static class JsSnippets
|
||||||
{
|
{
|
||||||
public const string MediaExtractor = @"
|
public const string MediaExtractor = @"
|
||||||
console.log('Injected MediaExtractor initialized');
|
(function() {
|
||||||
|
var log = function(m) { if (window.onLog) window.onLog(m); };
|
||||||
// Set to keep track of processed message IDs
|
log('[WAPP] Injected MediaExtractor initialized');
|
||||||
window.processedMessages = new Set();
|
|
||||||
|
|
||||||
window.scanForMedia = async () => {
|
|
||||||
// Buscar todos los contenedores de mensajes
|
|
||||||
const messages = document.querySelectorAll('div[role=""row""]');
|
|
||||||
|
|
||||||
for (const msgRow of messages) {
|
if (!window.processedMessages) window.processedMessages = new Set();
|
||||||
// Chequear si es mensaje entrante (ignorar mensajes enviados)
|
|
||||||
if (!msgRow.classList.contains('message-in')) continue;
|
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
|
window.getIdentityStrict = function() {
|
||||||
const img = msgRow.querySelector('img[src^=""blob:""]');
|
// El usuario nos dio el selector exacto: span[data-testid=""selectable-text""]
|
||||||
if (!img) continue;
|
// Buscamos cualquier elemento con ese ID que contenga un ""+""
|
||||||
|
var potentialNumbers = Array.from(document.querySelectorAll('span[data-testid=""selectable-text""]'));
|
||||||
const blobUrl = img.src;
|
|
||||||
|
for (var i = 0; i < potentialNumbers.length; i++) {
|
||||||
// Usar ID único para evitar duplicados
|
var el = potentialNumbers[i];
|
||||||
const msgId = msgRow.getAttribute('data-id') || blobUrl;
|
var text = (el.textContent || '').trim();
|
||||||
if (window.processedMessages.has(msgId)) continue;
|
|
||||||
|
// Solo tomamos números que empiecen con + y tengan longitud válida
|
||||||
window.processedMessages.add(msgId);
|
if (text.startsWith('+') && text.replace(/\D/g, '').length >= 10) {
|
||||||
|
// IMPORTANTE: El panel lateral siempre está a la derecha (más del 50% del ancho)
|
||||||
console.log('Found new media:', blobUrl);
|
var rect = el.getBoundingClientRect();
|
||||||
|
if (rect.left > (window.innerWidth / 2)) {
|
||||||
try {
|
return text.replace(/[^\d+]/g, '');
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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
|
// Si llegamos aquÃ, el número no se encontró en la parte derecha
|
||||||
setInterval(window.scanForMedia, 10000);
|
// 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();
|
var page = await _browserService.GetPageAsync();
|
||||||
|
|
||||||
// Configurar bindings
|
// 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 page.ExposeFunctionAsync("onNewMessage", (Func<string, Task>)(async (string jsonMessage) =>
|
||||||
{
|
{
|
||||||
await SafeSendAsync("LogUpdate", $"Mensaje recibido: {jsonMessage}");
|
await SafeSendAsync("LogUpdate", $"Mensaje recibido: {jsonMessage}");
|
||||||
@@ -113,7 +118,7 @@ namespace WhatsappPromo.Worker
|
|||||||
}, stoppingToken);
|
}, stoppingToken);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await page.EvaluateFunctionOnNewDocumentAsync(JsSnippets.MediaExtractor);
|
await page.EvaluateExpressionOnNewDocumentAsync(JsSnippets.MediaExtractor);
|
||||||
await page.ReloadAsync();
|
await page.ReloadAsync();
|
||||||
|
|
||||||
// Procesamiento en segundo plano
|
// Procesamiento en segundo plano
|
||||||
|
|||||||
Reference in New Issue
Block a user