#!/usr/bin/env python3 import argparse import os import sys sys.stdout.reconfigure(encoding='utf-8', errors='replace') import time from datetime import datetime from selenium.common.exceptions import StaleElementReferenceException, TimeoutException, WebDriverException from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import WebDriverWait from whatsapp_sender.driver_session import ensure_session_active, iniciar_driver from whatsapp_sender.whatsapp import buscar_caja_mensaje, click_seguro, elementos_visibles, obtener_nombre_resultado SEARCH_SELECTORS = [ ("input data-tab 3", By.CSS_SELECTOR, "input[role='textbox'][data-tab='3']"), ("input aria Buscar", By.CSS_SELECTOR, "input[aria-label*='Buscar' i]"), ("input placeholder Buscar", By.CSS_SELECTOR, "input[placeholder*='Buscar' i]"), ("input aria Search", By.CSS_SELECTOR, "input[aria-label*='Search' i]"), ("input placeholder Search", By.CSS_SELECTOR, "input[placeholder*='Search' i]"), ("data-testid chat-list-search", By.CSS_SELECTOR, "[data-testid='chat-list-search']"), ("role search", By.CSS_SELECTOR, "div[role='search']"), ("aria Buscar", By.CSS_SELECTOR, "div[aria-label*='Buscar' i]"), ("aria Search", By.CSS_SELECTOR, "div[aria-label*='Search' i]"), ("textbox data-tab 3", By.CSS_SELECTOR, "div[contenteditable='true'][data-tab='3']"), ("textbox buscar", By.CSS_SELECTOR, "div[contenteditable='true'][aria-label*='buscar' i]"), ("textbox search", By.CSS_SELECTOR, "div[contenteditable='true'][aria-label*='search' i]"), ("textbox inside side", By.XPATH, "//*[@contenteditable='true' and ancestor::*[@id='side' or @id='pane-side']]"), ] RESULT_SELECTORS = [ ("cell-frame-container", By.CSS_SELECTOR, "[data-testid='cell-frame-container']"), ("pane-side tabindex", By.CSS_SELECTOR, "#pane-side div[tabindex='-1']"), ("side tabindex", By.CSS_SELECTOR, "#side div[tabindex='-1']"), ("grid row", By.CSS_SELECTOR, "div[role='grid'] div[role='row']"), ("aria grid row", By.CSS_SELECTOR, "div[aria-label][role='grid'] div[role='row']"), ] SESSION_SELECTORS = [ ("pane-side", By.ID, "pane-side"), ("side", By.ID, "side"), ("chat-list", By.CSS_SELECTOR, "[data-testid='chat-list']"), ("role grid", By.CSS_SELECTOR, "div[role='grid']"), ] def contar_visibles(driver, by, selector): return len(elementos_visibles(driver, by, selector)) def imprimir_estado_selectores(driver, titulo, selectores): print(f"\n== {titulo} ==") for nombre, by, selector in selectores: try: total = len(driver.find_elements(by, selector)) visibles = contar_visibles(driver, by, selector) print(f"{nombre}: total={total}, visibles={visibles}") except Exception as exc: print(f"{nombre}: ERROR {exc}") def limpiar_campo(driver, elemento): click_seguro(driver, elemento) ActionChains(driver).key_down(Keys.CONTROL).send_keys("a").key_up(Keys.CONTROL).send_keys(Keys.DELETE).perform() time.sleep(0.2) def encontrar_buscador(driver, timeout=15): fin = time.time() + timeout while time.time() < fin: for nombre, by, selector in SEARCH_SELECTORS: for elemento in elementos_visibles(driver, by, selector): try: rect = elemento.rect if rect and rect.get("y", 9999) > 350: continue click_seguro(driver, elemento) time.sleep(0.3) tag_name = (elemento.tag_name or "").lower() if tag_name == "input" or elemento.get_attribute("contenteditable") == "true": print(f"Buscador detectado por: {nombre}") return elemento internos = elemento.find_elements( By.CSS_SELECTOR, "input[role='textbox'], input[type='text'], div[contenteditable='true'], div[role='textbox']", ) for interno in internos: if interno.is_displayed(): click_seguro(driver, interno) print(f"Buscador interno detectado por: {nombre}") return interno except (StaleElementReferenceException, WebDriverException): continue except Exception: continue time.sleep(0.5) return None def obtener_resultados(driver): resultados = [] vistos = set() for nombre_selector, by, selector in RESULT_SELECTORS: for elemento in elementos_visibles(driver, by, selector): try: key = elemento.id except Exception: key = id(elemento) if key in vistos: continue vistos.add(key) resultados.append((nombre_selector, elemento)) return resultados def guardar_captura(driver, prefijo): os.makedirs("diagnosticos", exist_ok=True) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") nombre = os.path.join("diagnosticos", f"{prefijo}_{timestamp}.png") driver.save_screenshot(nombre) print(f"\nCaptura guardada: {nombre}") def validar_busqueda(destino, abrir=False, cerrar=False): driver = iniciar_driver() driver.get("https://web.whatsapp.com") try: if not ensure_session_active(driver): print("No se pudo validar sesion activa. Revisa QR o captura diagnostica.") guardar_captura(driver, "validar_busqueda_sin_sesion") return 2 imprimir_estado_selectores(driver, "Selectores de sesion/panel", SESSION_SELECTORS) imprimir_estado_selectores(driver, "Selectores de buscador antes de buscar", SEARCH_SELECTORS) buscador = encontrar_buscador(driver) if not buscador: print("\nNo se encontro el buscador de chats.") guardar_captura(driver, "validar_busqueda_sin_buscador") return 3 limpiar_campo(driver, buscador) for caracter in destino: buscador.send_keys(caracter) time.sleep(0.04) try: WebDriverWait(driver, 10).until(lambda d: len(obtener_resultados(d)) > 0) except TimeoutException: print("\nNo aparecieron resultados durante la espera.") time.sleep(1) imprimir_estado_selectores(driver, "Selectores de resultados despues de buscar", RESULT_SELECTORS) destino_norm = destino.strip().lower() resultados = obtener_resultados(driver) print(f"\nResultados leidos: {len(resultados)}") coincidencia = None for indice, (selector, elemento) in enumerate(resultados, start=1): nombre = obtener_nombre_resultado(elemento) if not nombre: continue nombre_norm = nombre.strip().lower() tipo_match = "" if nombre_norm == destino_norm: tipo_match = "EXACTO" coincidencia = elemento elif destino_norm in nombre_norm or nombre_norm in destino_norm: tipo_match = "PARCIAL" coincidencia = coincidencia or elemento print(f"{indice}. [{selector}] {nombre} {tipo_match}".rstrip()) if not coincidencia: print("\nNo se detecto coincidencia exacta ni parcial.") guardar_captura(driver, "validar_busqueda_sin_coincidencia") return 4 print("\nCoincidencia encontrada.") if abrir: click_seguro(driver, coincidencia) if buscar_caja_mensaje(driver, timeout=10): print("Chat abierto y caja de mensaje detectada.") else: print("Se hizo clic en la coincidencia, pero no se detecto caja de mensaje.") guardar_captura(driver, "validar_busqueda_ok") return 0 finally: if cerrar: driver.quit() def main(): parser = argparse.ArgumentParser( description="Diagnostica selectores de busqueda de contactos/grupos en WhatsApp Web sin enviar mensajes." ) parser.add_argument("destino", nargs="?", help="Nombre del contacto o grupo a buscar.") parser.add_argument("--abrir", action="store_true", help="Abre la coincidencia encontrada para validar la caja de mensaje.") parser.add_argument("--cerrar", action="store_true", help="Cierra Chrome al terminar.") args = parser.parse_args() destino = args.destino or input("Contacto o grupo a buscar: ").strip() if not destino: print("Debes ingresar un contacto o grupo.") return 1 return validar_busqueda(destino, abrir=args.abrir, cerrar=args.cerrar) if __name__ == "__main__": raise SystemExit(main())