From c6bfeaeca134e9ffe8debf484d5679fc847e961a Mon Sep 17 00:00:00 2001 From: Santi Date: Tue, 18 Feb 2025 15:59:25 +0100 Subject: [PATCH] Proyecto --- app/main.py | 14 ++--- correo_server/EmailTab.py | 31 ++++++---- correo_server/MailClient.py | 112 ++++++++++++++++++++++++++++++++---- hilos/MusicPlayer.py | 59 +++++++++++++------ solapas/MusicDownloader.py | 37 +++++++----- 5 files changed, 194 insertions(+), 59 deletions(-) diff --git a/app/main.py b/app/main.py index cf9877c..d915686 100644 --- a/app/main.py +++ b/app/main.py @@ -5,6 +5,7 @@ import datetime from tkinter import Menu # Importar el widget Menu from tkinter import ttk # Importar el widget ttk from correo_server.EmailTab import MailTab +from correo_server.InboxTab import InboxTab from correo_server.MailClient import MailClient from hilos.ChatWidget import ChatWidget from hilos.MusicPlayer import MusicPlayer @@ -19,16 +20,12 @@ from solapas.TicTacToe import TicTacToe from solapas.WebScraperToDB import WebScraperToDB -# Configuración del servidor de correos -SMTP_SERVER = "192.168.120.103" -SMTP_PORT = 587 # Usar SSL para mayor seguridad +# Crear instancia del cliente de correo con configuración de puertos +email_client = MailClient() # Clave de API de OpenWeatherMap API_KEY = "1fa8fd05b650773bbc3f2130657e808a" -# Crear cliente de correo -mail_client = MailClient(SMTP_SERVER, SMTP_PORT) - def update_time(status_bar): """Función que actualiza la hora y el día de la semana en un label""" while True: @@ -184,8 +181,9 @@ notebook.add(tab5, text="Web Scraper", padding=4) # Añadir el widget de Web Scraper a la Solapa 5 web_scraper = WebScraperToDB(tab5) -# Crear e inicializar la pestaña de correos -email_tab = MailTab(notebook, mail_client) +# Crear pestañas de correo +MailTab(notebook, email_client) +InboxTab(notebook, email_client) # Barra de estado # Dividir la barra de estado en 4 labels diff --git a/correo_server/EmailTab.py b/correo_server/EmailTab.py index 412550d..e10d999 100644 --- a/correo_server/EmailTab.py +++ b/correo_server/EmailTab.py @@ -6,11 +6,10 @@ class MailTab: def __init__(self, parent, mail_client): self.mail_client = mail_client - # Crear el frame de la pestaña self.frame = ttk.Frame(parent) parent.add(self.frame, text="Correo") - # Campos de entrada para enviar correo + # Campos para ingresar credenciales del usuario 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) @@ -19,30 +18,40 @@ 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.recipient_entry = ttk.Entry(self.frame, width=40) - self.recipient_entry.grid(row=2, column=1, 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 ttk.Label(self.frame, text="Asunto:").grid(row=3, column=0, sticky="e", padx=5, pady=5) - self.subject_entry = ttk.Entry(self.frame, width=40) - self.subject_entry.grid(row=3, column=1, 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 ttk.Label(self.frame, text="Mensaje:").grid(row=4, column=0, sticky="ne", padx=5, pady=5) - self.body_text = tk.Text(self.frame, width=50, height=10) - self.body_text.grid(row=4, column=1, 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) + # Botón para enviar el correo self.send_button = ttk.Button(self.frame, text="Enviar", command=self.send_email_thread) 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.""" threading.Thread(target=self.send_email).start() def send_email(self): + """Envía el correo utilizando el cliente de correo.""" sender_email = self.entry_email.get() sender_password = self.entry_password.get() - recipient = self.recipient_entry.get() - subject = self.subject_entry.get() - body = self.body_text.get("1.0", tk.END).strip() + recipient = self.entry_recipient.get() + subject = self.entry_subject.get() + body = self.text_body.get("1.0", tk.END).strip() + + if not sender_email or not sender_password or not recipient or not subject or not body: + messagebox.showerror("Error", "Todos los campos son obligatorios.") + return result = self.mail_client.send_email(sender_email, sender_password, recipient, subject, body) messagebox.showinfo("Resultado", result) diff --git a/correo_server/MailClient.py b/correo_server/MailClient.py index 3e731d5..83427e6 100644 --- a/correo_server/MailClient.py +++ b/correo_server/MailClient.py @@ -1,17 +1,109 @@ import smtplib +import socket +import imaplib +import email +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +from email.utils import formatdate class MailClient: - def __init__(self, smtp_server, smtp_port): - self.smtp_server = smtp_server - self.smtp_port = smtp_port + def __init__(self): + self.server_ip = "s1.ieslamar.org" + self.ports = { + "SMTP": 25, # SMTP sin seguridad + "SMTP-SUBMISSION": 587, # SMTP autenticado sin SSL + "IMAP": 143 # IMAP sin SSL + } + + def check_smtp_connection(self, port): + """Verifica si el servidor SMTP responde en el puerto especificado.""" + try: + with socket.create_connection((self.server_ip, port), timeout=15): + return True + except (socket.timeout, ConnectionRefusedError): + return False def send_email(self, sender_email, sender_password, recipient, subject, body): - """Envía un correo utilizando el servidor SMTP con SSL.""" + """Envía un correo utilizando SMTP en los puertos 587 o 25.""" try: - with smtplib.SMTP(self.smtp_server, self.smtp_port) as server: - server.login(sender_email, sender_password) - message = f"Subject: {subject}\n\n{body}" - server.sendmail(sender_email, recipient, message) - return "Correo enviado correctamente" + message = MIMEMultipart() + message["From"] = sender_email + message["To"] = recipient + message["Date"] = formatdate(localtime=True) + message["Subject"] = subject + message.attach(MIMEText(body, "plain", "utf-8")) + + smtp_ports = [587, 25] # Intenta primero 587, luego 25 + puerto_activo = None + + 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 + + if not puerto_activo: + return "Error: No se pudo conectar con el servidor SMTP en los puertos (587, 25)." + + try: + 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()) + return f"Correo enviado correctamente a {recipient} usando el puerto {puerto_activo}" + + except smtplib.SMTPAuthenticationError: + return "Error: Autenticación fallida. Verifica tu correo y contraseña." + except smtplib.SMTPConnectError: + return f"Error: No se pudo conectar al servidor en el puerto {puerto_activo}." + except smtplib.SMTPException as e: + return f"Error SMTP en el puerto {puerto_activo}: {str(e)}" + except Exception as e: - return f"Error al enviar el correo: {str(e)}" + return f"Error inesperado al enviar el correo: {str(e)}" + + def fetch_emails(self, email_address, password): + """Recibe correos utilizando IMAP sin SSL.""" + try: + mail = imaplib.IMAP4(self.server_ip, self.ports["IMAP"]) + mail.login(email_address, password) + mail.select("inbox") + + status, messages = mail.search(None, "ALL") + email_ids = messages[0].split() + emails = [] + + for email_id in email_ids[-10:]: # Obtener los últimos 10 correos + 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]) + 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.""" + try: + 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 + 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: + return f"Error al eliminar el correo: {str(e)}" diff --git a/hilos/MusicPlayer.py b/hilos/MusicPlayer.py index 5402a8a..039a8b6 100644 --- a/hilos/MusicPlayer.py +++ b/hilos/MusicPlayer.py @@ -1,8 +1,11 @@ +import os import tkinter as tk from tkinter import filedialog import threading -import pygame # Necesitas instalar pygame: pip install pygame +import pygame +# Definir la carpeta donde se guardarán los archivos descargados +DOWNLOAD_FOLDER = "musicplayer" class MusicPlayer: def __init__(self, parent): @@ -22,19 +25,24 @@ class MusicPlayer: ) self.title_label.pack(pady=5) - # Botón para seleccionar archivo - self.select_button = tk.Button( - self.frame, text="Seleccionar Archivo", command=self.select_file, width=20 - ) - self.select_button.pack(pady=5) + # Lista de canciones descargadas + self.song_listbox = tk.Listbox(self.frame, width=50, height=10) + self.song_listbox.pack(pady=5) + self.load_songs() # Crear un marco para los botones de control self.controls_frame = tk.Frame(self.frame, bg="lightgreen") self.controls_frame.pack(pady=10) - # Botones de control (centrados) + # Botón para seleccionar un archivo manualmente + self.select_button = tk.Button( + self.frame, text="Seleccionar Archivo", command=self.select_file, width=20 + ) + self.select_button.pack(pady=5) + + # Botones de control self.play_button = tk.Button( - self.controls_frame, text="▶ Reproducir", command=self.play_music, width=12 + self.controls_frame, text="▶ Reproducir", command=self.play_selected_music, width=12 ) self.play_button.grid(row=0, column=0, padx=5) @@ -43,22 +51,39 @@ class MusicPlayer: ) self.stop_button.grid(row=0, column=1, padx=5) + def load_songs(self): + """Carga la lista de canciones descargadas en la carpeta 'musicplayer/'.""" + if not os.path.exists(DOWNLOAD_FOLDER): + os.makedirs(DOWNLOAD_FOLDER) + + self.song_listbox.delete(0, tk.END) # Limpiar lista antes de recargar + + for file in os.listdir(DOWNLOAD_FOLDER): + if file.endswith(".mp3"): + self.song_listbox.insert(tk.END, file) + def select_file(self): - """Abrir el selector de archivos para elegir un archivo de música.""" + """Abrir el selector de archivos para elegir un archivo de música manualmente.""" self.music_file = filedialog.askopenfilename( filetypes=[("Archivos de audio", "*.mp3 *.wav"), ("Todos los archivos", "*.*")] ) if self.music_file: - self.title_label.config(text=f"Archivo: {self.music_file.split('/')[-1]}") + self.title_label.config(text=f"Archivo: {os.path.basename(self.music_file)}") - def play_music(self): - """Iniciar la reproducción de música.""" - if hasattr(self, "music_file"): - self.is_playing = True - self.play_button.config(state="disabled") - self.stop_button.config(state="normal") + def play_selected_music(self): + """Reproducir la canción seleccionada desde la lista de descargas.""" + selected_index = self.song_listbox.curselection() + if not selected_index: + return - threading.Thread(target=self._play_music_thread, daemon=True).start() + selected_song = self.song_listbox.get(selected_index) + self.music_file = os.path.join(DOWNLOAD_FOLDER, selected_song) + + self.is_playing = True + self.play_button.config(state="disabled") + self.stop_button.config(state="normal") + + threading.Thread(target=self._play_music_thread, daemon=True).start() def _play_music_thread(self): """Hilo que reproduce la música.""" diff --git a/solapas/MusicDownloader.py b/solapas/MusicDownloader.py index 5d7859c..3dfe309 100644 --- a/solapas/MusicDownloader.py +++ b/solapas/MusicDownloader.py @@ -1,19 +1,23 @@ +import os import tkinter as tk from tkinter import ttk import threading -from pytube import YouTube +import yt_dlp +# Definir la carpeta donde se guardarán los archivos descargados +DOWNLOAD_FOLDER = "musicplayer" class MusicDownloader: def __init__(self, parent): """ Inicializa la interfaz para descargar música de YouTube en MP3. - - Args: - parent (tk.Frame): Frame donde se colocará el downloader. """ self.parent = parent + # Crear carpeta de descargas si no existe + if not os.path.exists(DOWNLOAD_FOLDER): + os.makedirs(DOWNLOAD_FOLDER) + # Etiqueta de título title = tk.Label(self.parent, text="Descargar Música MP3", font=("Helvetica", 14, "bold")) title.pack(pady=10) @@ -46,20 +50,27 @@ class MusicDownloader: threading.Thread(target=self.download_music, args=(url,), daemon=True).start() def download_music(self, url): - """Descarga el audio de YouTube como MP3.""" + """Descarga el audio de YouTube en MP3 usando yt-dlp.""" try: - # Inicializa la descarga - yt = YouTube(url, on_progress_callback=self.update_progress) - stream = yt.streams.filter(only_audio=True).first() + self.status_label.config(text="Descargando...", fg="blue") + output_template = os.path.join(DOWNLOAD_FOLDER, "%(title)s.%(ext)s") + + ydl_opts = { + 'format': 'bestaudio/best', + 'outtmpl': output_template, + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': 'mp3', + 'preferredquality': '192', + }], + } + + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + ydl.download([url]) - # Descargar archivo - self.status_label.config(text="Descargando...") - self.progress["value"] = 0 - stream.download(filename=f"{yt.title}.mp3") self.status_label.config(text="¡Descarga completada!", fg="green") except Exception as e: self.status_label.config(text=f"Error: {str(e)}", fg="red") - def update_progress(self, stream, chunk, bytes_remaining): """Actualiza la barra de progreso durante la descarga.""" total_size = stream.filesize