26 julio 2006

Leyendo el correo con INDY

Vamos a dividir el proceso de descargar el correo en dos partes. Primero leemos las cabeceras de nuestros mensajes y después descargamos los mensajes que nos interesen. Esto puede ser útil para descartar el correo spam desde el mismo servidor.

Vamos a crear dos componentes de la paleta INDY en tiempo real, con lo cual hay que añadir al comienzo de nuestra unidad:

uses
IdPOP3, IdMessage;

A continuación vamos a crear un procedimiento donde le pasamos los datos de nuestra cuenta de correo y vuelca el contenido en un ListView (el cual se supone que contiene tres columnas: Dirección, Asunto y Fecha/hora).

procedure LeerCorreo( sServidor, sUsuario, sClave: String; Mensajes: TListView );
var
POP3: TIdPOP3;
Mensaje: TIdMessage;
i: Integer;
begin
// creamos el objeto POP3
POP3 := TIdPOP3.Create( nil );
POP3.Host := sServidor;
POP3.Username := sUsuario;
POP3.Password := sClave;
POP3.Port := 110;

// conectamos con el servidor
try
POP3.Connect;
except
raise Exception.Create( 'Error al conectar con el servidor.' );
end;

Mensaje := TIdMessage.Create( nil );
for i := 1 to POP3.CheckMessages do
begin
// Leemos la cabecera del mensaje
Mensaje.Clear;
POP3.RetrieveHeader( i, Mensaje );

Mensajes.Items.Add;
Mensajes.Items[i-1].SubItems.Add( Mensaje.From.Address ); // dirección
Mensajes.Items[i-1].SubItems.Add( Mensaje.Subject ); // asunto
Mensajes.Items[i-1].SubItems.Add( DateTimeToStr( Mensaje.Date ) ); // Fecha-hora
end;

FreeAndNil( Mensaje );
FreeAndNil( POP3 );
end;

Una vez tenemos las cabeceras del mensaje en nuestra lista ListView supongamos que haciendo doble clic se descarga el mensaje del servidor a nuestro disco duro. Antes de eso vamos a crear una clase llamada TMensaje que contenga el mensaje descargado del servidor. La implementación de la misma sería:

type
TMensaje = class
iNumero: Integer; // Nº de mensaje dentro de nuestro buzón de correo
sServidor, sUsuario, sClave: String;
sAsunto, sMensaje: String;
Adjuntos: TStringList;
sRutaAdjuntos: String; // Ruta donde se guardaran los archivos adjuntos

constructor Create;
destructor Destroy; override;
function Descargar: Boolean;
end;

Y aquí viene la implementación de sus métodos:

constructor TMensaje.Create;
begin
Adjuntos := TStringList.Create;
end;

destructor TMensaje.Destroy;
begin
FreeAndNil( Adjuntos );
inherited;
end;

function TMensaje.Descargar: Boolean;
var
POP3: TIdPOP3;
Mensaje: TIdMessage;
i: Integer;
sAdjunto: String; // Nombre del archivo adjunto
begin
// creamos el objeto POP3
POP3 := TIdPOP3.Create( nil );
POP3.Host := sServidor;
POP3.Username := sUsuario;
POP3.Password := sClave;
POP3.Port := 110;

// Conectamos con el servidor
try
POP3.Connect;
except
raise Exception.Create( 'Error al conectar con el servidor.' );
end;

// Leemos todo el mensaje
Mensaje := TIdMessage.Create( nil );
try
POP3.Retrieve( iNumero, Mensaje );
except
raise Exception.Create( 'Error al leer el mensaje.' );
end;

// y desconectamos del servidor
POP3.Disconnect;

// Mostramos el mensaje en otro formulario
sAsunto := Mensaje.Subject;

// ¿Tiene mensajes algún mensaje adjunto?
if Mensaje.MessageParts.Count > 0 then
begin
// Leemos todas las partes del mensaje
for i := 0 to Mensaje.MessageParts.Count - 1 do
begin
// ¿Esta parte es de texto?
if ( Mensaje.MessageParts.Items[i] is TIdText ) then
sMensaje := TIdText( Mensaje.MessageParts.Items[i] ).Body.Text
else
// ¿Esta parte es un archivo binaro adjunto?
if ( Mensaje.MessageParts.Items[i] is TIdAttachment ) then
begin
// Guardamos el nombre del archivo adjunto en una variable para hacerlo más legible
sAdjunto := TIdAttachment( Mensaje.MessageParts.Items[i] ).FileName;

// Si ya existe el archivo adjunto lo borramos para que no de error
if FileExists( sRutaAdjuntos + sAdjunto ) then
DeleteFile( sRutaAdjuntos + sAdjunto );

// Guardamos el archivo adjunto y lo añadimos a la lista de adjuntos
TIdAttachment( Mensaje.MessageParts.Items[i] ).SaveToFile( sRutaAdjuntos + sAdjunto );
Adjuntos.Add( sAdjunto );
end
end
end
else
// Tomamos todo el mensaje como un mensaje de texto
sMensaje := Mensaje.Body.Text;

FreeAndNil( Mensaje );
FreeAndNil( POP3 );

Result := True;
end;

Si nos fijamos en el código fuente vemos que el método Descargar controla si el mensaje lleva archivos adjuntos o no. Aunque los mensajes de correo electrónico suelen codificarse de cuarenta leches distintas, generalmente hay dos tipos:

1º Texto plano o página web sin contenido.
2º Multiparte, donde cada parte puede ser texto plano, página web, archivo binario, etc.

En ambos casos la codificación utilizada es MIME.

Si el archivo que nos envían tiene una plantilla de página web (casi todos hoy en día) hay que complicarse un poco la vida y sacarlo mediante un navegador (browser). Eso lo dejaré para otra ocasión.

Pues bien, por último creamos el método que hace doble clic en nuestra lista de mensajes y muestra el contenido del mensaje en otro formulario:

procedure TFFormPrincipal.MensajesDblClick( Sender: TObject );
var i: Integer;
Mensaje: TMensaje;
begin
// ¿Ha selecionado un mensaje de la lista?
if Mensajes.Selected <> nil then
begin
Mensaje := TMensaje.Create;
Mensaje.iNumero := Mensajes.Selected.Index+1;
Mensaje.sServidor := Servidor.Text;
Mensaje.sUsuario := Usuario.Text;
Mensaje.sClave := Clave.Text;
Mensaje.sRutaAdjuntos := ExtractFilePath( Application.ExeName ) + 'Adjuntos\';

if Mensaje.Descargar then
begin
Application.CreateForm( TFMensaje, FMensaje );
FMensaje.Asunto.Text := Mensaje.sAsunto;
FMensaje.Mensaje.Text := Mensaje.sMensaje;

for i := 0 to Mensaje.Adjuntos.Count-1 do
FMensaje.Adjuntos.Items.Add( Mensaje.Adjuntos[i] );

FMensaje.ShowModal;
end;

FreeAndNil( Mensaje );
end;
end;

Esta es la base de un programa lector de correo POP3 aunque realmente hay que controlar muchas más cosas, como por ejemplo el borrar del servidor los mensajes ya descargados (Ahora no lo hace).


Pruebas realizadas en Delphi 7.

4 comentarios:

Inma dijo...

Hola, el programa funciona perfectamente pero a mí me da problema en la parte de mensaje.body.text

Estoy haciendo el ejemplo más sencillo donde el cuerpo es sólo texto. No tengo archivos adjuntos ni nada. Pero al ver lo que contiene el body, me muestra el código html del mensaje. Creo que es por ello que como sólo trabajo con texto no me muestra nada. Lo sé porque he llegado a leer un mensaje de bienvenida que te suelen mandar que está en html, pero como digo, si creo un correo cuyo cuerpo sea por ejemplo "Hola, ¿qué tal?" no me sale nada en la variable donde guardo body.text.

Utilizo Indy 9 y la cuenta de correo es yahoo.es

Cualquier ayuda es bienvenida.
Muchas gracias.

Administrador dijo...

En principio no debería darte ningún problema porque cuando se envía un mensaje en texto plano funciona perfectamente en todos los gestores de correo.

Revisa la clase TIdMensaje a ver si lo tienes todo correctamente.

Saludos.

Jorge DG dijo...

Hola, primero quiero agradecer al programador que hizo este código, que después de haber estado probando con muchas clases y en otros lenguajes, lo dificultoso que era implementar esta clase en definitiva, leer este post, fue algo tan confortante ya que tenía ese problema y de este forma se soluciona tan elegantemente.
Gracias al foro, estaré apoyándolo con mis humildes comentarios y pruebas.
Sigan adelante!

Tito dijo...

Estoy haciendo un programa para leer varias cuentas a la vez, me sirve el codigo, pero si leo 2 veces o mas se me duplican los mensajes, hay alguna propiedad que me indique el numero de mensaje para poder validar si ya lo recibi, estoy guardando los datos en MySql.

Publicidad