feat(browser): auto-commit con 3 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
"""Envia una imagen (con caption opcional) a un chat de WhatsApp Web via Chrome DevTools Protocol.
|
||||
|
||||
Compone `whatsapp_open_chat` (abrir + verificar destinatario) con primitivas CDP del
|
||||
registry (`cdp_eval`, `cdp_click_xy`, `cdp_set_file_input`, `cdp_type_chars`) para adjuntar
|
||||
y enviar una imagen a un contacto/grupo SIN abrir ventana nueva ni darle foco al sistema.
|
||||
registry (`cdp_eval`, `cdp_click_xy`, `cdp_set_file_input`) y `whatsapp_send_message` para
|
||||
adjuntar y enviar una imagen a un contacto/grupo SIN abrir ventana nueva ni darle foco al
|
||||
sistema.
|
||||
|
||||
Flujo (modelo de bandeja de medios INLINE de la WhatsApp Web actual), con salvaguarda
|
||||
anti-envio-al-contacto-equivocado:
|
||||
@@ -14,15 +15,17 @@ anti-envio-al-contacto-equivocado:
|
||||
`<input type=file>` "vivo" que escucha la SPA (antes de pulsarlo el input persistente
|
||||
no dispara el preview).
|
||||
3. Asigna la imagen al input via `cdp_set_file_input` (`DOM.setFileInputFiles`): la
|
||||
imagen aparece como miniatura en la bandeja inline sobre el composer.
|
||||
4. Espera a que el preview aparezca (boton "Quitar archivo adjunto" presente).
|
||||
5. Si `caption` no esta vacio, enfoca el composer del footer y teclea el texto con
|
||||
teclado CDP real (`cdp_type_chars`); verifica el `innerText` antes de enviar.
|
||||
6. Click de raton real en el boton enviar (icono `wds-ic-send-filled`) y verifica que la
|
||||
bandeja se cerro (sin adjuntos) para confirmar el envio.
|
||||
imagen aparece como miniatura en la bandeja inline.
|
||||
4. Espera a que la bandeja aparezca (boton "Quitar archivo adjunto" presente) y hace click
|
||||
real en el boton de enviar la bandeja (icono `wds-ic-send-filled`); verifica que la
|
||||
bandeja se cerro (sin adjuntos) para confirmar el envio de la imagen.
|
||||
5. Si `caption` no esta vacio, lo envia como un MENSAJE DE TEXTO de seguimiento via
|
||||
`whatsapp_send_message` (con `open_first=False`, el chat ya esta abierto). En la
|
||||
WhatsApp Web compacta actual el caption embebido en la imagen no es automatizable de
|
||||
forma fiable, asi que la descripcion viaja como una segunda burbuja: [imagen][caption].
|
||||
|
||||
Validado contra WhatsApp Web real. Accion CON EFECTO REAL E IRREVERSIBLE: envia la imagen
|
||||
de verdad.
|
||||
(y el caption) de verdad.
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -34,9 +37,9 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||
|
||||
from browser.cdp_eval import cdp_eval
|
||||
from browser.cdp_click_xy import cdp_click_xy
|
||||
from browser.cdp_type_chars import cdp_type_chars
|
||||
from browser.cdp_set_file_input import cdp_set_file_input
|
||||
from browser.whatsapp_open_chat import whatsapp_open_chat
|
||||
from browser.whatsapp_send_message import whatsapp_send_message
|
||||
|
||||
|
||||
def _center(expr: str, port: int, substr: str):
|
||||
@@ -70,7 +73,7 @@ def whatsapp_send_image(
|
||||
target_url_substr: str = "whatsapp",
|
||||
open_first: bool = True,
|
||||
) -> dict:
|
||||
"""Envia una imagen (con caption opcional) a un chat de WhatsApp Web ya logueado.
|
||||
"""Envia una imagen (con caption opcional de seguimiento) a un chat de WhatsApp Web.
|
||||
|
||||
Accion CON EFECTO: envia la imagen DE VERDAD (no reversible). Verifica `name`.
|
||||
|
||||
@@ -80,8 +83,9 @@ def whatsapp_send_image(
|
||||
al destinatario correcto antes de adjuntar.
|
||||
image_path: Ruta de la imagen a enviar. Se expande (`~`) y se convierte a ruta
|
||||
ABSOLUTA; debe existir en disco.
|
||||
caption: Texto opcional que acompana la imagen. Se teclea en el composer con teclado
|
||||
CDP real; "" (default) envia la imagen sin caption.
|
||||
caption: Texto opcional descriptivo. Se envia como un MENSAJE DE TEXTO de seguimiento
|
||||
(segunda burbuja [imagen][caption]) via `whatsapp_send_message`; "" (default)
|
||||
envia solo la imagen.
|
||||
port: Puerto de remote debugging de Chrome. Default 9222.
|
||||
target_url_substr: Substring que debe contener la URL del target (pestana). Default
|
||||
"whatsapp".
|
||||
@@ -91,12 +95,14 @@ def whatsapp_send_image(
|
||||
|
||||
Returns:
|
||||
dict con claves:
|
||||
ok: bool — alias de sent (True si la imagen se envio).
|
||||
sent: bool — True si la imagen se adjunto, (caption escrito) y se envio.
|
||||
ok: bool — True si la imagen se envio y (si habia caption) el caption tambien.
|
||||
sent: bool — True si la IMAGEN se envio.
|
||||
caption_sent: bool — True si el caption de seguimiento se envio (False si no
|
||||
habia caption o si fallo).
|
||||
recipient: str — el nombre solicitado.
|
||||
image: str — ruta absoluta de la imagen.
|
||||
caption: str — caption solicitado.
|
||||
error: str — motivo del fallo (vacio si sent=True).
|
||||
error: str — motivo del fallo (vacio si todo ok).
|
||||
|
||||
Nunca lanza: los fallos se reportan en "sent"/"ok" + "error".
|
||||
"""
|
||||
@@ -104,7 +110,7 @@ def whatsapp_send_image(
|
||||
abs_img = os.path.abspath(os.path.expanduser(image_path))
|
||||
|
||||
def fail(error: str) -> dict:
|
||||
return {"ok": False, "sent": False, "recipient": name,
|
||||
return {"ok": False, "sent": False, "caption_sent": False, "recipient": name,
|
||||
"image": abs_img, "caption": caption, "error": error}
|
||||
|
||||
# 0. La imagen debe existir.
|
||||
@@ -147,7 +153,8 @@ def whatsapp_send_image(
|
||||
if not r.get("ok"):
|
||||
return fail("no se pudo adjuntar la imagen: " + r.get("error", ""))
|
||||
|
||||
# 4. Esperar a que el preview/bandeja aparezca (adjunto presente).
|
||||
# 4. Esperar a que la bandeja aparezca (adjunto presente). El composer queda VACIO,
|
||||
# asi que el unico boton wds-ic-send-filled es el de enviar la bandeja.
|
||||
attached = False
|
||||
for _ in range(15):
|
||||
time.sleep(0.2)
|
||||
@@ -157,27 +164,7 @@ def whatsapp_send_image(
|
||||
if not attached:
|
||||
return fail("el preview no aparecio tras adjuntar la imagen")
|
||||
|
||||
# 5. Caption opcional: enfocar el composer y teclear con teclado real.
|
||||
if caption:
|
||||
cdp_eval(
|
||||
'/*FOCUS*/var b=document.querySelector(\'div[contenteditable="true"]\'); '
|
||||
"if(b){b.focus();}",
|
||||
port=port, target_url_substr=S,
|
||||
)
|
||||
time.sleep(0.25)
|
||||
cdp_type_chars(caption, port=port, target_url_substr=S, delay_ms=15)
|
||||
time.sleep(0.3)
|
||||
chk = cdp_eval(
|
||||
'/*COMPOSER*/var b=document.querySelector(\'div[contenteditable="true"]\'); '
|
||||
"b?b.innerText.replace(/\\n/g,''):''",
|
||||
port=port, target_url_substr=S,
|
||||
)
|
||||
composer = chk.get("value") or ""
|
||||
if composer != caption:
|
||||
return fail("el composer no contiene el caption esperado (no enviado): "
|
||||
+ repr(composer))
|
||||
|
||||
# 6. Click real en el boton enviar (icono wds-ic-send-filled).
|
||||
# 5. Click real en el boton de enviar la bandeja (icono wds-ic-send-filled).
|
||||
snd = _center(
|
||||
'/*SEND*/(() => {const e=document.querySelector(\'span[data-icon="wds-ic-send-filled"]\');'
|
||||
"if(!e)return null;const b=e.getBoundingClientRect();"
|
||||
@@ -189,14 +176,29 @@ def whatsapp_send_image(
|
||||
return fail("boton de enviar (wds-ic-send-filled) no encontrado")
|
||||
cdp_click_xy(snd["x"], snd["y"], port=port, target_url_substr=S)
|
||||
|
||||
# 7. Verificar que la bandeja se cerro (sin adjuntos) -> envio confirmado.
|
||||
# 6. Verificar que la bandeja se cerro (sin adjuntos) -> imagen enviada.
|
||||
image_sent = False
|
||||
for _ in range(15):
|
||||
time.sleep(0.2)
|
||||
if _attachment_count(port, S) == 0:
|
||||
return {"ok": True, "sent": True, "recipient": name,
|
||||
"image": abs_img, "caption": caption, "error": ""}
|
||||
image_sent = True
|
||||
break
|
||||
if not image_sent:
|
||||
return fail("la bandeja no se cerro tras pulsar enviar; envio incierto")
|
||||
|
||||
return fail("la bandeja no se cerro tras pulsar enviar; envio incierto")
|
||||
# 7. Caption opcional como mensaje de texto de seguimiento (segunda burbuja).
|
||||
caption_sent = False
|
||||
if caption:
|
||||
m = whatsapp_send_message(name, caption, port=port, target_url_substr=S,
|
||||
open_first=False)
|
||||
caption_sent = bool(m.get("sent"))
|
||||
if not caption_sent:
|
||||
return {"ok": False, "sent": True, "caption_sent": False, "recipient": name,
|
||||
"image": abs_img, "caption": caption,
|
||||
"error": "imagen enviada pero el caption fallo: " + m.get("reason", "")}
|
||||
|
||||
return {"ok": True, "sent": True, "caption_sent": caption_sent, "recipient": name,
|
||||
"image": abs_img, "caption": caption, "error": ""}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user