Files
Agent-IA/scripts/validar_busqueda_whatsapp.py

226 lines
8.8 KiB
Python

#!/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())