feat: add whatsapp_modular_v3_6 - envio automatizado WhatsApp Web con busqueda por nombre, deteccion de ticks via msg-meta/SVG title, y validacion de selectores
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user