226 lines
8.8 KiB
Python
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())
|