763e06c127
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
100 lines
4.2 KiB
Python
100 lines
4.2 KiB
Python
"""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("<mailbox origen>")` 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.")
|