From 88a42ec6294628640dc047dba8e811b679a0e974 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Sat, 25 Apr 2026 15:10:02 -0500 Subject: [PATCH] Update CorreoImapService.php --- app/Services/CorreoImapService.php | 65 +++++++++++++++++------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/app/Services/CorreoImapService.php b/app/Services/CorreoImapService.php index 2340232..177c8aa 100644 --- a/app/Services/CorreoImapService.php +++ b/app/Services/CorreoImapService.php @@ -171,12 +171,15 @@ class CorreoImapService /** * Descarga cabeceras + cuerpo de un rango de secuencia (ej: "1:5"). + * + * Usamos BODY.PEEK[1] en lugar de BODY.PEEK[TEXT]: + * - Para emails multipart/alternative (como Bancolombia), BODY[1] devuelve + * ÚNICAMENTE la parte text/plain, sin boundaries ni HTML. + * - Para emails simples (solo texto), BODY[1] también devuelve el cuerpo. */ private function fetchMessages(string $set): array { - // Pedimos FLAGS, ENVELOPE y BODY para extraer remitente, asunto y cuerpo - $raw = $this->command("FETCH {$set} (FLAGS BODY.PEEK[HEADER.FIELDS (FROM SUBJECT DATE)] BODY.PEEK[TEXT])"); - + $raw = $this->command("FETCH {$set} (BODY.PEEK[HEADER.FIELDS (FROM SUBJECT DATE)] BODY.PEEK[1])"); return $this->parseMessages($raw); } @@ -215,19 +218,22 @@ class CorreoImapService $email['date'] = trim($m[1]); } - // BODY: tomamos el texto entre las dos secciones BODY - // La respuesta tiene dos literales {N}\r\n + // BODY: la respuesta IMAP usa literales {N}\r\n + // Con dos secciones pedidas habrá al menos 3 partes al dividir por "}\r\n". + // La tercera parte es BODY[1] (texto plano directo). $parts = preg_split('/\}\r?\n/', $block); + + $rawBody = ''; if (count($parts) >= 3) { - // La tercera sección suele ser el cuerpo de texto $rawBody = $parts[2]; - // Limpiar hasta el fin del literal (antes del siguiente *) - $rawBody = preg_replace('/\r?\n\)\s*\*.*$/s', '', $rawBody); - $rawBody = preg_replace('/\r?\n\)\s*[A-Z0-9]+ OK.*/s', '', $rawBody); - $email['body'] = $this->cleanBody(trim($rawBody)); + // Quitar el cierre del literal IMAP (\r\n) y la línea "OK" + $rawBody = preg_replace('/\r?\n\s*\)\s*(\r?\n[A-Z0-9]+ OK.*)?$/s', '', $rawBody); } elseif (count($parts) === 2) { - $rawBody = $parts[1]; - $rawBody = preg_replace('/\r?\n\).*$/s', '', $rawBody); + $rawBody = $parts[1]; + $rawBody = preg_replace('/\r?\n\s*\).*$/s', '', $rawBody); + } + + if ($rawBody !== '') { $email['body'] = $this->cleanBody(trim($rawBody)); } @@ -298,33 +304,38 @@ class CorreoImapService private function cleanBody(string $body): string { - // ── 1. MIME multipart: extraer SOLO la parte text/plain ────────── - // Los emails de Bancolombia son multipart/alternative. - // Detectamos el boundary en la primera línea --boundary - if (preg_match('/^--([^\r\n]+)/m', $body, $bm)) { - $plainPart = $this->extractMimePlainPart($body, $bm[1]); - if ($plainPart !== null) { - $body = $plainPart; - } - } - - // ── 2. Decodificar quoted-printable ────────────────────────────── + // ── 1. Decoded quoted-printable ────────────────────────────────── + // BODY.PEEK[1] retorna directamente el contenido de la parte (sin + // boundaries), pero puede estar codificado en quoted-printable. if (preg_match('/=[0-9A-F]{2}/i', $body) || preg_match('/=\r?\n/m', $body)) { $body = quoted_printable_decode($body); } - // ── 3. Eliminar cabeceras MIME residuales ──────────────────────── + // ── 2. Si aún hay boundaries MIME residuales, extraer text/plain ─ + if (preg_match('/^--([^\r\n]+)/m', $body, $bm)) { + $plain = $this->extractMimePlainPart($body, $bm[1]); + if ($plain !== null) { + // Decodificar QP en la parte extraída también + if (preg_match('/=[0-9A-F]{2}/i', $plain) || preg_match('/=\r?\n/m', $plain)) { + $plain = quoted_printable_decode($plain); + } + $body = $plain; + } + } + + // ── 3. Eliminar cabeceras MIME residuales de parte ─────────────── + // (Content-Type:, Content-Transfer-Encoding:, etc.) $body = preg_replace('/^(Content-[^\r\n]+\r?\n)+/mi', '', $body); - // ── 4. Limpiar URLs de imágenes entre corchetes: "Logo [http://...]" + // ── 4. Limpiar URLs entre corchetes: "Logo [http://...]" ───────── $body = preg_replace('/\[https?:\/\/[^\]]*\]/i', '', $body); - // ── 5. Si aún contiene HTML (fallback) quitar etiquetas ────────── + // ── 5. Fallback HTML: si queda HTML, quitar etiquetas ──────────── if (preg_match('/<[a-z][\s>]/i', $body)) { $body = self::stripHtmlFull($body); } - // ── 6. Normalizar espacios y líneas ────────────────────────────── + // ── 6. Normalizar saltos de línea y espacios ───────────────────── $body = str_replace(["\r\n", "\r"], "\n", $body); $body = preg_replace('/\n{3,}/', "\n\n", $body);