This commit is contained in:
Santi 2025-02-18 15:59:25 +01:00
parent c80721e33e
commit c6bfeaeca1
5 changed files with 194 additions and 59 deletions

View File

@ -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

View File

@ -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)

View File

@ -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)}"

View File

@ -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."""

View File

@ -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