"""Mueve un mensaje IMAP a otro mailbox por UID, con fallback COPY+EXPUNGE. Funcion IMPURA: traslada un mensaje (identificado por su UID) del mailbox seleccionado a `dest_mailbox` sobre una conexion `imaplib.IMAP4_SSL` ya autenticada. Intenta primero el comando UID MOVE (RFC 6851, soportado por Gmail y la mayoria de servidores modernos): es atomico y eficiente. Si el servidor NO anuncia la capacidad MOVE, cae a la secuencia clasica equivalente: UID COPY al destino, STORE +FLAGS (\\Deleted) en el origen y EXPUNGE para materializar el borrado del origen. Nunca lanza: devuelve un dict con `status` ("ok"/"error") y `method` para indicar que camino se uso. No abre la conexion ni resuelve credenciales: el caller pasa `conn` ya conectado, autenticado y con `conn.select("")` hecho. """ def _server_supports_move(conn) -> bool: """Devuelve True si la conexion anuncia la capability MOVE (RFC 6851). Inspecciona ``conn.capabilities`` (tupla de capacidades que imaplib cachea tras el login). La comparacion es case-insensitive porque distintos servidores devuelven "MOVE" en mayusculas/minusculas. Cualquier problema accediendo a las capacidades se trata como "no soportado" para forzar el fallback seguro. """ try: caps = getattr(conn, "capabilities", ()) or () return any(str(c).upper() == "MOVE" for c in caps) except Exception: # noqa: BLE001 return False def imap_move_message(conn, uid: int, dest_mailbox: str) -> dict: """Mueve el mensaje `uid` del mailbox seleccionado a `dest_mailbox`. Camino preferido (``method="move"``): ``conn.uid("MOVE", str(uid), dest_mailbox)``, atomico. Si el servidor no soporta MOVE (no esta en ``conn.capabilities``) o el comando MOVE falla, se usa el fallback (``method="copy_delete"``): ``UID COPY`` al destino, ``UID STORE +FLAGS (\\Deleted)`` en el origen y ``EXPUNGE``. Args: conn: objeto ``imaplib.IMAP4_SSL`` (o ``IMAP4``) YA conectado, autenticado y con el mailbox ORIGEN seleccionado (``conn.select(...)``). uid: UID del mensaje en el mailbox origen. Operacion siempre por UID, no por numero de secuencia. dest_mailbox: nombre del mailbox destino. En Gmail los nombres llevan el prefijo ``[Gmail]/`` (ej. ``"[Gmail]/Trash"`` para la papelera, ``"[Gmail]/Spam"``). Una carpeta de usuario es simplemente su nombre. Returns: dict. En exito: ``{"status": "ok", "uid": uid, "dest": dest_mailbox, "method": "move" | "copy_delete"}``. En fallo (sin lanzar): ``{"status": "error", "error": str}``. """ try: # Camino 1: UID MOVE (atomico) si el servidor lo anuncia. if _server_supports_move(conn): try: typ, data = conn.uid("MOVE", str(uid), dest_mailbox) if typ == "OK": return { "status": "ok", "uid": uid, "dest": dest_mailbox, "method": "move", } # MOVE anunciado pero rechazado: caemos al fallback. except Exception: # noqa: BLE001 # MOVE no soportado en la practica pese a la capability: fallback. pass # Camino 2 (fallback): COPY al destino + marcar \\Deleted en origen + EXPUNGE. typ, data = conn.uid("COPY", str(uid), dest_mailbox) if typ != "OK": return { "status": "error", "error": f"imap_move_message: COPY a {dest_mailbox!r} devolvio {typ!r}: {data!r}", } typ, data = conn.uid("STORE", str(uid), "+FLAGS", "(\\Deleted)") if typ != "OK": return { "status": "error", "error": f"imap_move_message: STORE \\Deleted devolvio {typ!r}: {data!r}", } conn.expunge() return { "status": "ok", "uid": uid, "dest": dest_mailbox, "method": "copy_delete", } except Exception as e: # noqa: BLE001 return {"status": "error", "error": str(e)} if __name__ == "__main__": print("imap_move_message: importable. Uso real requiere un conn IMAP autenticado.")