diff --git a/correo_server/EmailTab.py b/correo_server/EmailTab.py index e10d999..3d86518 100644 --- a/correo_server/EmailTab.py +++ b/correo_server/EmailTab.py @@ -4,12 +4,20 @@ import threading class MailTab: def __init__(self, parent, mail_client): + """ + Inicializa la pestaña de envío de correos en la interfaz. + + Args: + parent (tk.Notebook): Contenedor de pestañas. + mail_client (MailClient): Cliente de correo que gestiona el envío de emails. + """ self.mail_client = mail_client + # Crear un nuevo frame dentro del notebook self.frame = ttk.Frame(parent) parent.add(self.frame, text="Correo") - # Campos para ingresar credenciales del usuario + # Campos de entrada para el correo, contraseña y destinatario ttk.Label(self.frame, text="Correo electrónico:").grid(row=0, column=0, sticky="e", padx=5, pady=5) self.entry_email = ttk.Entry(self.frame, width=40) self.entry_email.grid(row=0, column=1, padx=5, pady=5) @@ -18,17 +26,16 @@ class MailTab: self.entry_password = ttk.Entry(self.frame, show="*", width=40) self.entry_password.grid(row=1, column=1, padx=5, pady=5) - # Campo para ingresar destinatario ttk.Label(self.frame, text="Destinatario:").grid(row=2, column=0, sticky="e", padx=5, pady=5) self.entry_recipient = ttk.Entry(self.frame, width=40) self.entry_recipient.grid(row=2, column=1, padx=5, pady=5) - # Campo para ingresar asunto + # Campo para el asunto del correo ttk.Label(self.frame, text="Asunto:").grid(row=3, column=0, sticky="e", padx=5, pady=5) self.entry_subject = ttk.Entry(self.frame, width=40) self.entry_subject.grid(row=3, column=1, padx=5, pady=5) - # Campo para escribir el mensaje + # Área de texto para el mensaje ttk.Label(self.frame, text="Mensaje:").grid(row=4, column=0, sticky="ne", padx=5, pady=5) self.text_body = tk.Text(self.frame, width=50, height=10) self.text_body.grid(row=4, column=1, padx=5, pady=5) @@ -38,11 +45,11 @@ class MailTab: self.send_button.grid(row=5, column=1, padx=5, pady=5, sticky="e") def send_email_thread(self): - """Ejecuta el envío de correo en un hilo separado para no congelar la interfaz.""" + """Ejecuta el envío de correo en un hilo separado para evitar bloquear la interfaz.""" threading.Thread(target=self.send_email).start() def send_email(self): - """Envía el correo utilizando el cliente de correo.""" + """Envía un correo usando el cliente de correos.""" sender_email = self.entry_email.get() sender_password = self.entry_password.get() recipient = self.entry_recipient.get() @@ -53,5 +60,6 @@ class MailTab: messagebox.showerror("Error", "Todos los campos son obligatorios.") return + # Llamada al cliente de correo para enviar el email result = self.mail_client.send_email(sender_email, sender_password, recipient, subject, body) messagebox.showinfo("Resultado", result) diff --git a/correo_server/InboxTab.py b/correo_server/InboxTab.py index f66806f..cd04b70 100644 --- a/correo_server/InboxTab.py +++ b/correo_server/InboxTab.py @@ -4,11 +4,20 @@ import threading class InboxTab: def __init__(self, parent, email_client): + """ + Inicializa la pestaña de la bandeja de entrada. + + Args: + parent (tk.Notebook): Contenedor de pestañas. + email_client (MailClient): Cliente de correo para gestionar la recepción y eliminación de correos. + """ self.email_client = email_client + # Crear un nuevo frame en la pestaña self.frame = ttk.Frame(parent) parent.add(self.frame, text="Bandeja de Entrada") + # Campos de entrada para el correo y contraseña ttk.Label(self.frame, text="Correo electrónico:").grid(row=0, column=0, sticky="e", padx=5, pady=5) self.entry_email = ttk.Entry(self.frame, width=40) self.entry_email.grid(row=0, column=1, padx=5, pady=5) @@ -17,25 +26,31 @@ class InboxTab: self.entry_password = ttk.Entry(self.frame, show="*", width=40) self.entry_password.grid(row=1, column=1, padx=5, pady=5) + # Botón para cargar correos self.load_button = ttk.Button(self.frame, text="Cargar Correos", command=self.load_emails_thread) self.load_button.grid(row=2, column=1, sticky="e", padx=5, pady=5) + # Lista para mostrar los correos self.email_listbox = tk.Listbox(self.frame, width=80, height=20) self.email_listbox.grid(row=3, column=0, columnspan=2, padx=5, pady=5) self.email_listbox.bind("", self.show_email) + # Botón para eliminar un correo seleccionado self.delete_button = ttk.Button(self.frame, text="Eliminar Correo", command=self.delete_email_thread) self.delete_button.grid(row=4, column=1, sticky="e", padx=5, pady=5) self.emails = [] def load_emails_thread(self): + """Carga los correos en un hilo separado.""" threading.Thread(target=self.load_emails).start() def delete_email_thread(self): + """Elimina un correo en un hilo separado.""" threading.Thread(target=self.delete_email).start() def load_emails(self): + """Obtiene la lista de correos desde el servidor y la muestra en la lista.""" email_address = self.entry_email.get() password = self.entry_password.get() @@ -46,6 +61,7 @@ class InboxTab: self.emails = [] self.email_listbox.delete(0, tk.END) + # Obtener los correos usando el cliente de correos emails = self.email_client.fetch_emails(email_address, password) if isinstance(emails, str): @@ -57,6 +73,7 @@ class InboxTab: self.emails.append((email_id, subject, sender)) def delete_email(self): + """Elimina un correo seleccionado en la lista.""" selected_index = self.email_listbox.curselection() if not selected_index: messagebox.showwarning("Advertencia", "Seleccione un correo para eliminar.") @@ -66,6 +83,7 @@ class InboxTab: email_address = self.entry_email.get() password = self.entry_password.get() + # Eliminar el correo usando el cliente result = self.email_client.delete_email(email_address, password, email_id) if "eliminado" in result: @@ -75,6 +93,7 @@ class InboxTab: messagebox.showerror("Error", result) def show_email(self, event): + """Muestra el contenido del correo seleccionado.""" selected_index = self.email_listbox.curselection() if selected_index: email_id, subject, sender = self.emails[selected_index[0]] diff --git a/correo_server/MailClient.py b/correo_server/MailClient.py index 83427e6..8258d06 100644 --- a/correo_server/MailClient.py +++ b/correo_server/MailClient.py @@ -6,8 +6,16 @@ from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.utils import formatdate + class MailClient: def __init__(self): + """ + Inicializa el cliente de correo con la configuración del servidor. + + Atributos: + server_ip (str): Dirección del servidor de correo. + ports (dict): Diccionario con los puertos para SMTP e IMAP. + """ self.server_ip = "s1.ieslamar.org" self.ports = { "SMTP": 25, # SMTP sin seguridad @@ -16,7 +24,15 @@ class MailClient: } def check_smtp_connection(self, port): - """Verifica si el servidor SMTP responde en el puerto especificado.""" + """ + Verifica si el servidor SMTP responde en un puerto específico. + + Args: + port (int): Número del puerto a verificar. + + Returns: + bool: `True` si la conexión es exitosa, `False` en caso contrario. + """ try: with socket.create_connection((self.server_ip, port), timeout=15): return True @@ -24,8 +40,21 @@ class MailClient: return False def send_email(self, sender_email, sender_password, recipient, subject, body): - """Envía un correo utilizando SMTP en los puertos 587 o 25.""" + """ + Envía un correo utilizando el protocolo SMTP. + + Args: + sender_email (str): Dirección de correo del remitente. + sender_password (str): Contraseña del remitente. + recipient (str): Dirección de correo del destinatario. + subject (str): Asunto del correo. + body (str): Cuerpo del correo. + + Returns: + str: Mensaje indicando el resultado del envío. + """ try: + # Crear el mensaje de correo message = MIMEMultipart() message["From"] = sender_email message["To"] = recipient @@ -33,23 +62,27 @@ class MailClient: message["Subject"] = subject message.attach(MIMEText(body, "plain", "utf-8")) - smtp_ports = [587, 25] # Intenta primero 587, luego 25 + # Lista de puertos disponibles para SMTP + smtp_ports = [587, 25] # Prioriza 587 y luego 25 puerto_activo = None + # Verificar qué puerto está disponible for smtp_port in smtp_ports: if self.check_smtp_connection(smtp_port): puerto_activo = smtp_port - break # Si encuentra un puerto disponible, lo usa + break if not puerto_activo: return "Error: No se pudo conectar con el servidor SMTP en los puertos (587, 25)." try: + # Conectar al servidor SMTP with smtplib.SMTP(self.server_ip, puerto_activo, timeout=15) as server: if puerto_activo == 587: - server.starttls() # Activa TLS si está en el puerto 587 - server.login(sender_email, sender_password) - server.sendmail(sender_email, recipient, message.as_string()) + server.starttls() # Activar TLS si se usa el puerto 587 + server.login(sender_email, sender_password) # Autenticación + server.sendmail(sender_email, recipient, message.as_string()) # Enviar el correo + return f"Correo enviado correctamente a {recipient} usando el puerto {puerto_activo}" except smtplib.SMTPAuthenticationError: @@ -63,46 +96,76 @@ class MailClient: return f"Error inesperado al enviar el correo: {str(e)}" def fetch_emails(self, email_address, password): - """Recibe correos utilizando IMAP sin SSL.""" + """ + Recupera los correos electrónicos de la bandeja de entrada usando IMAP. + + Args: + email_address (str): Dirección de correo del usuario. + password (str): Contraseña del usuario. + + Returns: + list: Lista de tuplas con los correos (`email_id`, `subject`, `sender`). + str: Mensaje de error si la autenticación falla. + """ try: + # Conectar al servidor IMAP mail = imaplib.IMAP4(self.server_ip, self.ports["IMAP"]) mail.login(email_address, password) mail.select("inbox") + # Buscar todos los correos en la bandeja de entrada status, messages = mail.search(None, "ALL") email_ids = messages[0].split() emails = [] - for email_id in email_ids[-10:]: # Obtener los últimos 10 correos + # Obtener los últimos 10 correos + for email_id in email_ids[-10:]: status, msg_data = mail.fetch(email_id, "(RFC822)") for response_part in msg_data: if isinstance(response_part, tuple): msg = email.message_from_bytes(response_part[1]) + + # Decodificar el asunto del correo subject, encoding = email.header.decode_header(msg["Subject"])[0] if isinstance(subject, bytes): subject = subject.decode(encoding or "utf-8") + sender = msg.get("From") emails.append((email_id, subject, sender)) mail.logout() return emails + except imaplib.IMAP4.error: return "Error: Fallo de autenticación en IMAP. Verifica tu correo y contraseña." except Exception as e: return f"Error al recibir los correos: {str(e)}" def delete_email(self, email_address, password, email_id): - """Elimina un correo utilizando IMAP sin SSL.""" + """ + Elimina un correo electrónico utilizando IMAP. + + Args: + email_address (str): Dirección de correo del usuario. + password (str): Contraseña del usuario. + email_id (bytes): ID del correo a eliminar. + + Returns: + str: Mensaje indicando si la eliminación fue exitosa o no. + """ try: + # Conectar al servidor IMAP mail = imaplib.IMAP4(self.server_ip, self.ports["IMAP"]) mail.login(email_address, password) mail.select("inbox") - mail.store(email_id, "+FLAGS", "\\Deleted") # Marca el correo como eliminado - mail.expunge() # Borra permanentemente los correos marcados como eliminados + # Marcar el correo como eliminado + mail.store(email_id, "+FLAGS", "\\Deleted") + mail.expunge() # Eliminar definitivamente los correos marcados mail.logout() return f"Correo con ID {email_id} eliminado correctamente." + except imaplib.IMAP4.error: return "Error: Fallo de autenticación en IMAP. Verifica tu correo y contraseña." except Exception as e: