Proyecto
This commit is contained in:
parent
7ca109af88
commit
a74d546a39
218
app/main.py
218
app/main.py
|
@ -1,58 +1,202 @@
|
|||
import tkinter as tk
|
||||
import threading
|
||||
import time
|
||||
import datetime
|
||||
from tkinter import Menu, ttk
|
||||
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
|
||||
from hilos.WeatherWidget import WeatherWidget
|
||||
from hilos.SystemMonitor import SystemMonitor
|
||||
from hilos.ApplicationLauncher import ApplicationLauncher
|
||||
from hilos.LanguageChart import LanguageChart
|
||||
from solapas.MusicDownloader import MusicDownloader
|
||||
from solapas.EconomyBitcoinChart import EconomyBitcoinChart
|
||||
from solapas.SQLQueryExecutor import SQLQueryExecutor
|
||||
from solapas.TicTacToe import TicTacToe
|
||||
from solapas.WebScraperToDB import WebScraperToDB
|
||||
|
||||
# Importación de widgets personalizados
|
||||
from hilos.ChatWidget import ChatWidget # Widget de chat en tiempo real
|
||||
from hilos.MusicPlayer import MusicPlayer # Reproductor de música
|
||||
from hilos.WeatherWidget import WeatherWidget # Widget del clima con API de OpenWeather
|
||||
from hilos.SystemMonitor import SystemMonitor # Monitor del sistema (CPU, RAM, etc.)
|
||||
from hilos.ApplicationLauncher import ApplicationLauncher # Lanzador de aplicaciones
|
||||
from hilos.LanguageChart import LanguageChart # Gráfico de lenguajes de programación
|
||||
|
||||
# Crear instancia del cliente de correo con configuración de puertos
|
||||
email_client = MailClient()
|
||||
|
||||
# Clave de API de OpenWeatherMap
|
||||
API_KEY = "1fa8fd05b650773bbc3f2130657e808a"
|
||||
|
||||
def update_time(status_bar):
|
||||
"""Función que actualiza la hora y el día de la semana en un label"""
|
||||
while True:
|
||||
# Obtener la fecha y hora actual
|
||||
now = datetime.datetime.now()
|
||||
day_of_week = now.strftime("%A") # Día de la semana
|
||||
time_str = now.strftime("%H:%M:%S") # Hora en formato HH:MM:SS
|
||||
date_str = now.strftime("%Y-%m-%d") # Fecha en formato YYYY-MM-DD
|
||||
label_text = f"{day_of_week}, {date_str} - {time_str}"
|
||||
|
||||
# Actualizar el label (debemos usar `after` para asegurarnos que se actualice en el hilo principal de Tkinter)
|
||||
label_fecha_hora.after(1000, status_bar.config, {"text": label_text})
|
||||
|
||||
# Espera 1 segundo antes de actualizar de nuevo
|
||||
time.sleep(1)
|
||||
|
||||
# Crear la ventana principal
|
||||
root = tk.Tk()
|
||||
root.title("Ventana Responsive") # Título de la ventana
|
||||
root.geometry("1000x700") # Definir el tamaño inicial de la ventana
|
||||
root.title("Ventana Responsive")
|
||||
root.geometry("1000x700") # Tamaño inicial
|
||||
|
||||
# Crear el menú superior de la aplicación (actualmente vacío)
|
||||
# Configurar la ventana principal para que sea responsive
|
||||
root.columnconfigure(0, weight=0) # Columna izquierda, tamaño fijo
|
||||
root.columnconfigure(1, weight=1) # Columna central, tamaño variable
|
||||
root.columnconfigure(2, weight=0) # Columna derecha, tamaño fijo
|
||||
root.rowconfigure(0, weight=1) # Fila principal, tamaño variable
|
||||
root.rowconfigure(1, weight=0) # Barra de estado, tamaño fijo
|
||||
|
||||
# Crear el menú superior
|
||||
menu_bar = Menu(root)
|
||||
|
||||
file_menu = Menu(menu_bar, tearoff=0)
|
||||
file_menu.add_command(label="Nuevo")
|
||||
file_menu.add_command(label="Abrir")
|
||||
file_menu.add_separator()
|
||||
file_menu.add_command(label="Salir", command=root.quit)
|
||||
|
||||
edit_menu = Menu(menu_bar, tearoff=0)
|
||||
edit_menu.add_command(label="Copiar")
|
||||
edit_menu.add_command(label="Pegar")
|
||||
|
||||
help_menu = Menu(menu_bar, tearoff=0)
|
||||
help_menu.add_command(label="Acerca de")
|
||||
|
||||
menu_bar.add_cascade(label="Archivo", menu=file_menu)
|
||||
menu_bar.add_cascade(label="Editar", menu=edit_menu)
|
||||
menu_bar.add_cascade(label="Ayuda", menu=help_menu)
|
||||
|
||||
root.config(menu=menu_bar)
|
||||
|
||||
# Crear los frames (paneles) laterales y central
|
||||
frame_izquierdo = tk.Frame(root, bg="lightblue", width=150) # Panel lateral izquierdo
|
||||
frame_central = tk.Frame(root, bg="white") # Panel central principal
|
||||
frame_derecho = tk.Frame(root, bg="lightgreen", width=150) # Panel lateral derecho
|
||||
# Crear los frames laterales y el central
|
||||
frame_izquierdo = tk.Frame(root, bg="lightblue", width=150)
|
||||
frame_central = tk.Frame(root, bg="white")
|
||||
frame_derecho = tk.Frame(root, bg="lightgreen", width=150)
|
||||
|
||||
# Ubicar los frames en la cuadrícula de la ventana
|
||||
frame_izquierdo.grid(row=0, column=0, sticky="ns") # Se extiende en dirección norte-sur
|
||||
frame_central.grid(row=0, column=1, sticky="nsew") # Se expande en ambas direcciones
|
||||
frame_derecho.grid(row=0, column=2, sticky="ns") # Se extiende en dirección norte-sur
|
||||
# Colocar los frames laterales y el central
|
||||
frame_izquierdo.grid(row=0, column=0, sticky="ns")
|
||||
frame_central.grid(row=0, column=1, sticky="nsew")
|
||||
frame_derecho.grid(row=0, column=2, sticky="ns")
|
||||
|
||||
# Añadir widgets a los paneles
|
||||
weather_widget = WeatherWidget(frame_izquierdo, "API_KEY") # Widget del clima en el panel izquierdo
|
||||
app_launcher = ApplicationLauncher(frame_izquierdo) # Lanzador de aplicaciones en el panel izquierdo
|
||||
language_chart = LanguageChart(frame_izquierdo) # Gráfico de lenguajes de programación en el panel izquierdo
|
||||
chat_widget = ChatWidget(frame_derecho) # Chat en vivo en el panel derecho
|
||||
music_player = MusicPlayer(frame_derecho) # Reproductor de música en el panel derecho
|
||||
# Configurar los tamaños fijos de los frames laterales
|
||||
frame_izquierdo.grid_propagate(False)
|
||||
frame_derecho.grid_propagate(False)
|
||||
|
||||
# Crear la barra de estado en la parte inferior de la ventana
|
||||
barra_estado = tk.Label(root, text="Barra de estado", bg="lightgray", anchor="w")
|
||||
barra_estado.grid(row=1, column=0, columnspan=3, sticky="ew") # Se extiende horizontalmente
|
||||
# Integrar el widget del clima en el panel izquierdo
|
||||
weather_widget = WeatherWidget(frame_izquierdo, API_KEY)
|
||||
|
||||
# Inicializar el monitor del sistema para mostrar uso de CPU, RAM, etc.
|
||||
stop_event = threading.Event() # Evento para detener el monitor al cerrar la app
|
||||
system_monitor = SystemMonitor(barra_estado, stop_event) # Monitoriza el sistema y actualiza la barra de estado
|
||||
# Añadir el lanzador de aplicaciones al panel izquierdo
|
||||
app_launcher = ApplicationLauncher(frame_izquierdo)
|
||||
|
||||
# Función para manejar el cierre de la aplicación
|
||||
# Añadir gráfico de lenguajes al panel izquierdo
|
||||
language_chart = LanguageChart(frame_izquierdo)
|
||||
|
||||
# Crear el widget de Chat en el panel derecho con más espacio
|
||||
chat_widget = ChatWidget(frame_derecho)
|
||||
|
||||
# Agregar el reproductor de música al panel derecho, en la parte inferior
|
||||
music_player = MusicPlayer(frame_derecho)
|
||||
|
||||
# Dividir el frame central en dos partes (superior variable e inferior fija)
|
||||
frame_central.rowconfigure(0, weight=1) # Parte superior, tamaño variable
|
||||
frame_central.rowconfigure(1, weight=0) # Parte inferior, tamaño fijo
|
||||
frame_central.columnconfigure(0, weight=1) # Ocupa toda la anchura
|
||||
|
||||
# Crear subframes dentro del frame central
|
||||
frame_superior = tk.Frame(frame_central, bg="lightyellow")
|
||||
frame_inferior = tk.Frame(frame_central, bg="lightgray", height=100)
|
||||
|
||||
# Colocar los subframes dentro del frame central
|
||||
frame_superior.grid(row=0, column=0, sticky="nsew")
|
||||
frame_inferior.grid(row=1, column=0, sticky="ew")
|
||||
|
||||
# Fijar el tamaño de la parte inferior
|
||||
frame_inferior.grid_propagate(False)
|
||||
|
||||
# Crear un evento de parada
|
||||
stop_event = threading.Event()
|
||||
|
||||
# Definir el manejador para el cierre de la ventana
|
||||
def on_closing():
|
||||
"""Se ejecuta al cerrar la ventana, deteniendo los procesos activos."""
|
||||
stop_event.set() # Detiene el monitor del sistema
|
||||
root.destroy() # Cierra la ventana principal
|
||||
"""Cerrar correctamente la aplicación."""
|
||||
stop_event.set() # Detener los hilos
|
||||
root.destroy() # Destruir la ventana principal
|
||||
|
||||
# Asignar la función de cierre cuando el usuario intenta cerrar la ventana
|
||||
# Configurar el manejador de cierre
|
||||
root.protocol("WM_DELETE_WINDOW", on_closing)
|
||||
|
||||
# Iniciar el bucle principal de la aplicación
|
||||
# Crear la barra de estado
|
||||
barra_estado = tk.Label(root, text="Barra de estado", bg="lightgray", anchor="w")
|
||||
barra_estado.grid(row=1, column=0, columnspan=3, sticky="ew")
|
||||
|
||||
# Inicializar el monitor del sistema
|
||||
system_monitor = SystemMonitor(barra_estado, stop_event)
|
||||
|
||||
# Notebook para las pestañas
|
||||
style = ttk.Style()
|
||||
style.configure("CustomNotebook.TNotebook.Tab", font=("Arial", 12, "bold"))
|
||||
notebook = ttk.Notebook(frame_superior, style="CustomNotebook.TNotebook")
|
||||
notebook.pack(fill="both", expand=True)
|
||||
|
||||
# Crear la Solapa 1 y añadir el downloader
|
||||
tab1 = ttk.Frame(notebook)
|
||||
notebook.add(tab1, text="Downloader", padding=4)
|
||||
|
||||
# Añadir el downloader a la Solapa 1
|
||||
music_downloader = MusicDownloader(tab1)
|
||||
|
||||
# Crear la Solapa 2 y añadir los gráficos
|
||||
tab2 = ttk.Frame(notebook)
|
||||
notebook.add(tab2, text="Graphics", padding=4)
|
||||
|
||||
# Añadir los gráficos de economía mundial y Bitcoin a la Solapa 2
|
||||
economy_bitcoin_chart = EconomyBitcoinChart(tab2)
|
||||
|
||||
# Crear la Solapa 3 y añadir el Tic Tac Toe
|
||||
tab3 = ttk.Frame(notebook)
|
||||
notebook.add(tab3, text="Tic Tac Toe", padding=4)
|
||||
|
||||
# Añadir el juego de Tic Tac Toe a la Solapa 3
|
||||
tic_tac_toe = TicTacToe(tab3)
|
||||
|
||||
# Crear la Solapa 4 y añadir el SQL Query Executor
|
||||
tab4 = ttk.Frame(notebook)
|
||||
notebook.add(tab4, text="SQL Querys", padding=4)
|
||||
|
||||
# Añadir el ejecutor de consultas SQL a la Solapa 4
|
||||
sql_query_executor = SQLQueryExecutor(tab4)
|
||||
|
||||
# Crear la Solapa 5 y añadir el Web Scraper
|
||||
tab5 = ttk.Frame(notebook)
|
||||
notebook.add(tab5, text="Web Scraper", padding=4)
|
||||
|
||||
# Añadir el widget de Web Scraper a la Solapa 5
|
||||
web_scraper = WebScraperToDB(tab5)
|
||||
|
||||
# Crear pestañas de correo
|
||||
MailTab(notebook, email_client)
|
||||
InboxTab(notebook, email_client)
|
||||
|
||||
# Barra de estado
|
||||
# Dividir la barra de estado en 4 labels
|
||||
# Usar pack para alinear los labels horizontalmente
|
||||
label_fecha_hora = tk.Label(barra_estado, text="Hilo fecha-hora", font=("Helvetica", 14), bd=1, fg="blue", relief="sunken", anchor="w", width=20, padx=10)
|
||||
|
||||
label_fecha_hora.pack(side="right", fill="x", expand=True)
|
||||
# barra_estado.grid(row=1, column=0, columnspan=3, sticky="ew")
|
||||
|
||||
|
||||
update_thread = threading.Thread(target=update_time, args=(label_fecha_hora,))
|
||||
update_thread.daemon = True # Hacemos el hilo un demonio para que termine con la app
|
||||
update_thread.start()
|
||||
|
||||
# Ejecución de la aplicación
|
||||
root.mainloop()
|
|
@ -4,20 +4,12 @@ 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 de entrada para el correo, contraseña y destinatario
|
||||
# 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)
|
||||
|
@ -26,16 +18,17 @@ 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 el asunto del correo
|
||||
# Campo para ingresar asunto
|
||||
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)
|
||||
|
||||
# Área de texto para el mensaje
|
||||
# Campo para escribir 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)
|
||||
|
@ -45,11 +38,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 evitar bloquear la interfaz."""
|
||||
"""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 un correo usando el cliente de correos."""
|
||||
"""Envía el correo utilizando el cliente de correo."""
|
||||
sender_email = self.entry_email.get()
|
||||
sender_password = self.entry_password.get()
|
||||
recipient = self.entry_recipient.get()
|
||||
|
@ -60,6 +53,5 @@ 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)
|
|
@ -4,20 +4,11 @@ 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)
|
||||
|
@ -26,31 +17,25 @@ 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("<Double-Button-1>", 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()
|
||||
|
||||
|
@ -61,7 +46,6 @@ 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):
|
||||
|
@ -73,7 +57,6 @@ 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.")
|
||||
|
@ -83,7 +66,6 @@ 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:
|
||||
|
@ -93,7 +75,6 @@ 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]]
|
||||
|
|
|
@ -6,16 +6,8 @@ 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
|
||||
|
@ -24,15 +16,7 @@ class MailClient:
|
|||
}
|
||||
|
||||
def check_smtp_connection(self, port):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
"""Verifica si el servidor SMTP responde en el puerto especificado."""
|
||||
try:
|
||||
with socket.create_connection((self.server_ip, port), timeout=15):
|
||||
return True
|
||||
|
@ -40,21 +24,8 @@ class MailClient:
|
|||
return False
|
||||
|
||||
def send_email(self, sender_email, sender_password, recipient, subject, body):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
"""Envía un correo utilizando SMTP en los puertos 587 o 25."""
|
||||
try:
|
||||
# Crear el mensaje de correo
|
||||
message = MIMEMultipart()
|
||||
message["From"] = sender_email
|
||||
message["To"] = recipient
|
||||
|
@ -62,27 +33,23 @@ class MailClient:
|
|||
message["Subject"] = subject
|
||||
message.attach(MIMEText(body, "plain", "utf-8"))
|
||||
|
||||
# Lista de puertos disponibles para SMTP
|
||||
smtp_ports = [587, 25] # Prioriza 587 y luego 25
|
||||
smtp_ports = [587, 25] # Intenta primero 587, 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
|
||||
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:
|
||||
# Conectar al servidor SMTP
|
||||
with smtplib.SMTP(self.server_ip, puerto_activo, timeout=15) as server:
|
||||
if puerto_activo == 587:
|
||||
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
|
||||
|
||||
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:
|
||||
|
@ -96,76 +63,46 @@ class MailClient:
|
|||
return f"Error inesperado al enviar el correo: {str(e)}"
|
||||
|
||||
def fetch_emails(self, email_address, password):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
"""Recibe correos utilizando IMAP sin SSL."""
|
||||
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 = []
|
||||
|
||||
# Obtener los últimos 10 correos
|
||||
for email_id in email_ids[-10:]:
|
||||
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])
|
||||
|
||||
# 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 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.
|
||||
"""
|
||||
"""Elimina un correo utilizando IMAP sin SSL."""
|
||||
try:
|
||||
# Conectar al servidor IMAP
|
||||
mail = imaplib.IMAP4(self.server_ip, self.ports["IMAP"])
|
||||
mail.login(email_address, password)
|
||||
mail.select("inbox")
|
||||
|
||||
# Marcar el correo como eliminado
|
||||
mail.store(email_id, "+FLAGS", "\\Deleted")
|
||||
mail.expunge() # Eliminar definitivamente los correos marcados
|
||||
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:
|
||||
|
|
|
@ -3,40 +3,93 @@ import threading
|
|||
import subprocess
|
||||
import os
|
||||
|
||||
|
||||
class ApplicationLauncher:
|
||||
def __init__(self, parent):
|
||||
"""Crea botones para abrir aplicaciones como VS Code, Eclipse y PyCharm."""
|
||||
"""
|
||||
Inicializa los botones para lanzar aplicaciones con detección automática de rutas.
|
||||
|
||||
Args:
|
||||
parent (tk.Frame): Frame donde se colocarán los botones.
|
||||
"""
|
||||
self.parent = parent
|
||||
|
||||
# Detectar rutas de instalación
|
||||
self.vscode_path = self.detect_path(["C:\\Program Files\\Microsoft VS Code\\Code.exe"])
|
||||
self.eclipse_path = self.detect_path(["C:\\eclipse\\eclipse.exe"])
|
||||
self.pycharm_path = self.detect_path(["C:\\Program Files\\JetBrains\\PyCharm\\bin\\pycharm64.exe"])
|
||||
# Detectar rutas automáticamente
|
||||
self.vscode_path = self.detect_path(["C:\\Program Files\\Microsoft VS Code\\Code.exe",
|
||||
"C:\\Users\\%USERNAME%\\AppData\\Local\\Programs\\Microsoft VS Code\\Code.exe"])
|
||||
self.eclipse_path = self.detect_path(["C:\\eclipse\\eclipse.exe",
|
||||
"C:\\Program Files\\Eclipse Foundation\\eclipse.exe"])
|
||||
self.pycharm_path = self.detect_path(["C:\\Program Files\\JetBrains\\PyCharm\\bin\\pycharm64.exe",
|
||||
"C:\\Program Files\\JetBrains\\PyCharm Community Edition 2023.1.4\\bin\\pycharm64.exe"])
|
||||
|
||||
# Crear botones de lanzamiento
|
||||
self.create_button("Visual Code", self.launch_vscode)
|
||||
self.create_button("Eclipse", self.launch_eclipse)
|
||||
self.create_button("PyCharm", self.launch_pycharm)
|
||||
# Título para el grupo de botones
|
||||
title = tk.Label(self.parent, text="Aplicaciones", font=("Helvetica", 14, "bold"), bg="lightblue")
|
||||
title.pack(pady=10)
|
||||
|
||||
# Botón para abrir Visual Studio Code
|
||||
self.vscode_button = tk.Button(self.parent, text="Visual Code", command=self.launch_vscode, bg="lightgreen",
|
||||
font=("Helvetica", 10))
|
||||
self.vscode_button.pack(fill="x", pady=2)
|
||||
|
||||
# Botón para abrir Eclipse
|
||||
self.eclipse_button = tk.Button(self.parent, text="Eclipse", command=self.launch_eclipse, bg="lightgreen",
|
||||
font=("Helvetica", 10))
|
||||
self.eclipse_button.pack(fill="x", pady=2)
|
||||
|
||||
# Botón para abrir PyCharm
|
||||
self.pycharm_button = tk.Button(self.parent, text="PyCharm", command=self.launch_pycharm, bg="lightgreen",
|
||||
font=("Helvetica", 10))
|
||||
self.pycharm_button.pack(fill="x", pady=2)
|
||||
|
||||
def detect_path(self, paths):
|
||||
"""Detecta la primera ruta válida de una lista de rutas posibles."""
|
||||
"""
|
||||
Detecta automáticamente la primera ruta existente de una lista de posibles rutas.
|
||||
|
||||
Args:
|
||||
paths (list): Lista de rutas posibles para un ejecutable.
|
||||
|
||||
Returns:
|
||||
str: La primera ruta válida encontrada, o None si no se encuentra ninguna.
|
||||
"""
|
||||
for path in paths:
|
||||
path = os.path.expandvars(path) # Expande variables como %USERNAME%
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
return None
|
||||
|
||||
def create_button(self, name, command):
|
||||
"""Crea un botón con la función de lanzar una aplicación."""
|
||||
button = tk.Button(self.parent, text=name, command=command, bg="lightgreen")
|
||||
button.pack(fill="x", pady=2)
|
||||
|
||||
def launch_vscode(self):
|
||||
"""Lanza Visual Studio Code."""
|
||||
"""Lanza Visual Studio Code si se encuentra la ruta."""
|
||||
self.launch_application(self.vscode_path, "Visual Studio Code")
|
||||
|
||||
def launch_eclipse(self):
|
||||
"""Lanza Eclipse si se encuentra la ruta."""
|
||||
self.launch_application(self.eclipse_path, "Eclipse")
|
||||
|
||||
def launch_pycharm(self):
|
||||
"""Lanza PyCharm si se encuentra la ruta."""
|
||||
self.launch_application(self.pycharm_path, "PyCharm")
|
||||
|
||||
def launch_application(self, path, name):
|
||||
"""Ejecuta la aplicación si la ruta es válida."""
|
||||
"""
|
||||
Lanza una aplicación si la ruta es válida.
|
||||
|
||||
Args:
|
||||
path (str): Ruta al ejecutable.
|
||||
name (str): Nombre de la aplicación (para mensajes de error).
|
||||
"""
|
||||
if path:
|
||||
threading.Thread(target=subprocess.run, args=([path],), daemon=True).start()
|
||||
threading.Thread(target=self.run_command, args=([path],), daemon=True).start()
|
||||
else:
|
||||
print(f"{name} no está instalado.")
|
||||
print(f"No se encontró {name}. Por favor, instálalo o configura la ruta.")
|
||||
|
||||
def run_command(self, command):
|
||||
"""
|
||||
Ejecuta un comando del sistema operativo para abrir una aplicación.
|
||||
|
||||
Args:
|
||||
command (list): Comando a ejecutar (lista de argumentos).
|
||||
"""
|
||||
try:
|
||||
subprocess.run(command, check=True)
|
||||
except Exception as e:
|
||||
print(f"Error al intentar abrir la aplicación: {e}")
|
||||
|
|
|
@ -3,55 +3,71 @@ from tkinter import scrolledtext
|
|||
import socket
|
||||
import threading
|
||||
|
||||
|
||||
class ChatWidget:
|
||||
def __init__(self, parent):
|
||||
"""Widget de chat con conexión a un servidor por sockets."""
|
||||
self.parent = parent
|
||||
self.frame = tk.Frame(self.parent, bg="lightgreen")
|
||||
self.frame.pack(fill="x", padx=10, pady=10)
|
||||
self.frame = tk.Frame(self.parent, bg="lightgreen", width=200, height=300) # Ajustar tamaño del frame
|
||||
self.frame.pack(fill="x", expand=False, padx=10, pady=10)
|
||||
|
||||
# Área de texto para mostrar mensajes
|
||||
self.chat_display = scrolledtext.ScrolledText(self.frame, wrap=tk.WORD, state="disabled", width=40, height=10)
|
||||
# Label superior
|
||||
self.label = tk.Label(self.frame, text="Chat", font=("Arial", 14, "bold"), fg="red", bg="lightgreen")
|
||||
self.label.pack(pady=5)
|
||||
|
||||
# Caja de texto para los mensajes
|
||||
self.chat_display = scrolledtext.ScrolledText(
|
||||
self.frame, wrap=tk.WORD, state="disabled", width=40, height=10 # Reducir dimensiones
|
||||
)
|
||||
self.chat_display.pack(pady=5)
|
||||
|
||||
# Campo de entrada de mensajes
|
||||
self.message_entry = tk.Entry(self.frame, width=35)
|
||||
# Campo de entrada para escribir mensajes
|
||||
self.message_entry = tk.Entry(self.frame, width=35) # Reducir ancho
|
||||
self.message_entry.pack(pady=5)
|
||||
self.message_entry.bind("<Return>", self.send_message)
|
||||
|
||||
# Botón de enviar mensaje
|
||||
self.send_button = tk.Button(self.frame, text="Enviar", command=self.send_message)
|
||||
# Botón para enviar mensajes
|
||||
self.send_button = tk.Button(self.frame, text="Enviar", command=self.send_message, width=10) # Reducir tamaño
|
||||
self.send_button.pack(pady=5)
|
||||
|
||||
# Configuración del socket
|
||||
# Configuración del cliente socket
|
||||
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.server_address = ("127.0.0.1", 3333)
|
||||
self.server_address = ("127.0.0.1", 3333) # Cambiar a la IP del servidor si es necesario
|
||||
|
||||
try:
|
||||
self.client_socket.connect(self.server_address)
|
||||
threading.Thread(target=self.receive_messages, daemon=True).start()
|
||||
except:
|
||||
self.display_message("[ERROR] No se pudo conectar al servidor.")
|
||||
except Exception as e:
|
||||
self.display_message(f"[ERROR] No se pudo conectar al servidor: {e}")
|
||||
|
||||
def send_message(self, event=None):
|
||||
"""Envía un mensaje al servidor."""
|
||||
message = self.message_entry.get()
|
||||
if message:
|
||||
self.client_socket.send(message.encode("utf-8"))
|
||||
self.message_entry.delete(0, tk.END)
|
||||
try:
|
||||
self.client_socket.send(message.encode("utf-8"))
|
||||
self.message_entry.delete(0, tk.END)
|
||||
except Exception as e:
|
||||
self.display_message(f"[ERROR] No se pudo enviar el mensaje: {e}")
|
||||
|
||||
def receive_messages(self):
|
||||
"""Recibe mensajes del servidor y los muestra en la interfaz."""
|
||||
while True:
|
||||
try:
|
||||
message = self.client_socket.recv(1024).decode("utf-8")
|
||||
self.display_message(message)
|
||||
if message:
|
||||
self.display_message(message)
|
||||
else:
|
||||
break
|
||||
except:
|
||||
self.display_message("[DESCONECTADO] Conexión perdida con el servidor.")
|
||||
break
|
||||
|
||||
def display_message(self, message):
|
||||
"""Muestra un mensaje en la ventana de chat."""
|
||||
self.chat_display.config(state="normal")
|
||||
self.chat_display.insert(tk.END, message + "\n")
|
||||
self.chat_display.config(state="disabled")
|
||||
self.chat_display.see(tk.END)
|
||||
|
||||
def close_connection(self):
|
||||
try:
|
||||
self.client_socket.close()
|
||||
except:
|
||||
pass
|
|
@ -7,44 +7,45 @@ import time
|
|||
class LanguageChart:
|
||||
def __init__(self, parent):
|
||||
"""
|
||||
Inicializa un gráfico de barras que muestra los lenguajes de programación más usados.
|
||||
Inicializa el gráfico de los lenguajes de programación más usados.
|
||||
|
||||
Args:
|
||||
parent (tk.Frame): Frame donde se colocará el gráfico.
|
||||
"""
|
||||
self.parent = parent
|
||||
|
||||
# Datos iniciales de lenguajes más utilizados
|
||||
# Datos iniciales (puedes actualizar esto dinámicamente)
|
||||
self.languages = ["Python", "JavaScript", "Java", "C++", "C#"]
|
||||
self.usage = [30, 25, 20, 15, 10] # Porcentajes de uso
|
||||
|
||||
# Crear el gráfico de barras
|
||||
# Crear figura para el gráfico
|
||||
self.figure = Figure(figsize=(4, 3), dpi=100)
|
||||
self.ax = self.figure.add_subplot(111)
|
||||
self.ax.bar(self.languages, self.usage, color="skyblue")
|
||||
self.ax.set_title("Lenguajes más usados")
|
||||
self.ax.set_ylabel("Porcentaje de uso")
|
||||
|
||||
# Incrustar el gráfico en Tkinter
|
||||
# Embebiendo el gráfico en Tkinter
|
||||
self.canvas = FigureCanvasTkAgg(self.figure, master=self.parent)
|
||||
self.canvas.get_tk_widget().pack(fill="both", expand=True)
|
||||
|
||||
# Iniciar el hilo para actualizar el gráfico periódicamente
|
||||
# Iniciar hilo para actualizar el gráfico
|
||||
threading.Thread(target=self.update_chart, daemon=True).start()
|
||||
|
||||
def fetch_data(self):
|
||||
"""
|
||||
Simula la actualización de datos sobre el uso de lenguajes de programación.
|
||||
Simula la obtención de datos actualizados de lenguajes de programación.
|
||||
|
||||
Returns:
|
||||
list: Lista de nuevos porcentajes de uso.
|
||||
"""
|
||||
# Simulación: aquí puedes conectar a una API real
|
||||
self.usage = [value + 1 if value < 50 else value - 10 for value in self.usage]
|
||||
time.sleep(5) # Simular un retraso en la actualización
|
||||
time.sleep(5) # Simular retraso de actualización
|
||||
|
||||
def update_chart(self):
|
||||
"""
|
||||
Actualiza el gráfico de lenguajes de programación periódicamente en un hilo.
|
||||
Actualiza el gráfico periódicamente en un hilo.
|
||||
"""
|
||||
while True:
|
||||
self.fetch_data()
|
||||
|
|
|
@ -4,90 +4,98 @@ from tkinter import filedialog
|
|||
import threading
|
||||
import pygame
|
||||
|
||||
# Carpeta donde se almacenarán los archivos de música descargados
|
||||
# Definir la carpeta donde se guardarán los archivos descargados
|
||||
DOWNLOAD_FOLDER = "musicplayer"
|
||||
|
||||
class MusicPlayer:
|
||||
def __init__(self, parent):
|
||||
"""Inicializa el reproductor de música con botones de control y lista de canciones."""
|
||||
self.parent = parent
|
||||
self.is_playing = False
|
||||
|
||||
# Inicializar el motor de audio
|
||||
# Inicializar el reproductor de música
|
||||
pygame.mixer.init()
|
||||
|
||||
# Crear la interfaz del reproductor
|
||||
# Crear marco para el reproductor
|
||||
self.frame = tk.Frame(self.parent, bg="lightgreen", width=200, height=100)
|
||||
self.frame.pack(side="bottom", padx=10, pady=10, fill="both", expand=False)
|
||||
|
||||
# Etiqueta de título
|
||||
self.title_label = tk.Label(self.frame, text="Reproductor de Música", font=("Arial", 12, "bold"), bg="lightgreen")
|
||||
self.title_label = tk.Label(
|
||||
self.frame, text="Reproductor de Música", font=("Arial", 12, "bold"), bg="lightgreen"
|
||||
)
|
||||
self.title_label.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() # Cargar canciones almacenadas
|
||||
self.load_songs()
|
||||
|
||||
# Botón para seleccionar archivo manualmente
|
||||
self.select_button = tk.Button(self.frame, text="Seleccionar Archivo", command=self.select_file, width=20)
|
||||
self.select_button.pack(pady=5)
|
||||
|
||||
# Contenedor de botones de control
|
||||
# Crear un marco para los botones de control
|
||||
self.controls_frame = tk.Frame(self.frame, bg="lightgreen")
|
||||
self.controls_frame.pack(pady=10)
|
||||
|
||||
# Botón de reproducción
|
||||
self.play_button = tk.Button(self.controls_frame, text="▶ Reproducir", command=self.play_selected_music, width=12)
|
||||
# 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_selected_music, width=12
|
||||
)
|
||||
self.play_button.grid(row=0, column=0, padx=5)
|
||||
|
||||
# Botón para detener la reproducción
|
||||
self.stop_button = tk.Button(self.controls_frame, text="■ Detener", command=self.stop_music, state="disabled", width=12)
|
||||
self.stop_button = tk.Button(
|
||||
self.controls_frame, text="■ Detener", command=self.stop_music, state="disabled", width=12
|
||||
)
|
||||
self.stop_button.grid(row=0, column=1, padx=5)
|
||||
|
||||
def load_songs(self):
|
||||
"""Carga la lista de canciones almacenadas en la carpeta 'musicplayer/'."""
|
||||
"""Carga la lista de canciones descargadas en la carpeta 'musicplayer/'."""
|
||||
if not os.path.exists(DOWNLOAD_FOLDER):
|
||||
os.makedirs(DOWNLOAD_FOLDER) # Crear la carpeta si no existe
|
||||
os.makedirs(DOWNLOAD_FOLDER)
|
||||
|
||||
self.song_listbox.delete(0, tk.END) # Limpiar lista antes de actualizar
|
||||
self.song_listbox.delete(0, tk.END) # Limpiar lista antes de recargar
|
||||
|
||||
for file in os.listdir(DOWNLOAD_FOLDER):
|
||||
if file.endswith(".mp3"): # Filtrar solo archivos MP3
|
||||
if file.endswith(".mp3"):
|
||||
self.song_listbox.insert(tk.END, file)
|
||||
|
||||
def select_file(self):
|
||||
"""Permite al usuario seleccionar un archivo de audio manualmente."""
|
||||
self.music_file = filedialog.askopenfilename(filetypes=[("Archivos de audio", "*.mp3 *.wav"), ("Todos los archivos", "*.*")])
|
||||
"""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: {os.path.basename(self.music_file)}") # Mostrar el archivo seleccionado
|
||||
self.title_label.config(text=f"Archivo: {os.path.basename(self.music_file)}")
|
||||
|
||||
def play_selected_music(self):
|
||||
"""Reproduce la canción seleccionada en la lista."""
|
||||
"""Reproducir la canción seleccionada desde la lista de descargas."""
|
||||
selected_index = self.song_listbox.curselection()
|
||||
if not selected_index:
|
||||
return # Si no hay selección, no hacer nada
|
||||
return
|
||||
|
||||
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") # Deshabilitar botón de reproducción
|
||||
self.stop_button.config(state="normal") # Habilitar botón de detener
|
||||
self.play_button.config(state="disabled")
|
||||
self.stop_button.config(state="normal")
|
||||
|
||||
threading.Thread(target=self._play_music_thread, daemon=True).start() # Iniciar hilo para reproducir
|
||||
threading.Thread(target=self._play_music_thread, daemon=True).start()
|
||||
|
||||
def _play_music_thread(self):
|
||||
"""Maneja la reproducción de la música en un hilo separado."""
|
||||
"""Hilo que reproduce la música."""
|
||||
pygame.mixer.music.load(self.music_file)
|
||||
pygame.mixer.music.play()
|
||||
while pygame.mixer.music.get_busy():
|
||||
if not self.is_playing:
|
||||
pygame.mixer.music.stop()
|
||||
break # Salir del bucle si la música se detiene
|
||||
break
|
||||
|
||||
def stop_music(self):
|
||||
"""Detiene la reproducción de música."""
|
||||
"""Detener la reproducción de música."""
|
||||
self.is_playing = False
|
||||
self.play_button.config(state="normal")
|
||||
self.stop_button.config(state="disabled")
|
||||
|
|
|
@ -4,30 +4,66 @@ import tkinter as tk
|
|||
|
||||
class SystemMonitor:
|
||||
def __init__(self, parent, stop_event):
|
||||
"""Muestra el uso del CPU, RAM y red en tiempo real."""
|
||||
self.parent = parent
|
||||
self.stop_event = stop_event
|
||||
|
||||
# Etiquetas para mostrar métricas
|
||||
self.cpu_label = tk.Label(parent, text="CPU: 0%", bg="lightgreen")
|
||||
self.ram_label = tk.Label(parent, text="RAM: 0%", bg="lightcoral")
|
||||
self.cpu_label.pack(side="left", expand=True)
|
||||
self.ram_label.pack(side="left", expand=True)
|
||||
# Crear labels para cada métrica
|
||||
self.cpu_label = tk.Label(parent, text="CPU: 0%", bg="lightgreen", font=("Helvetica", 12), relief="groove")
|
||||
self.ram_label = tk.Label(parent, text="RAM: 0%", bg="lightcoral", font=("Helvetica", 12), relief="groove")
|
||||
self.battery_label = tk.Label(parent, text="Battery: N/A", bg="lightblue", font=("Helvetica", 12), relief="groove")
|
||||
self.network_label = tk.Label(parent, text="Net: N/A", bg="lightpink", font=("Helvetica", 12), relief="groove")
|
||||
|
||||
# Hilos para actualizar los datos
|
||||
# Posicionar los labels
|
||||
self.cpu_label.pack(side="left", fill="both", expand=True)
|
||||
self.ram_label.pack(side="left", fill="both", expand=True)
|
||||
self.battery_label.pack(side="left", fill="both", expand=True)
|
||||
self.network_label.pack(side="left", fill="both", expand=True)
|
||||
|
||||
# Iniciar hilos
|
||||
threading.Thread(target=self.update_cpu, daemon=True).start()
|
||||
threading.Thread(target=self.update_ram, daemon=True).start()
|
||||
threading.Thread(target=self.update_battery, daemon=True).start()
|
||||
threading.Thread(target=self.update_network, daemon=True).start()
|
||||
|
||||
def update_cpu(self):
|
||||
"""Actualiza la etiqueta del uso de CPU."""
|
||||
"""Actualizar el uso de CPU."""
|
||||
while not self.stop_event.is_set():
|
||||
cpu_usage = psutil.cpu_percent()
|
||||
self.cpu_label.config(text=f"CPU: {cpu_usage}%")
|
||||
self.cpu_label.after(1000, lambda: None) # Evitar bloqueo
|
||||
self.stop_event.wait(1)
|
||||
|
||||
def update_ram(self):
|
||||
"""Actualiza la etiqueta del uso de RAM."""
|
||||
"""Actualizar el uso de RAM."""
|
||||
while not self.stop_event.is_set():
|
||||
ram_usage = psutil.virtual_memory().percent
|
||||
self.ram_label.config(text=f"RAM: {ram_usage}%")
|
||||
self.ram_label.after(1000, lambda: None) # Evitar bloqueo
|
||||
self.stop_event.wait(1)
|
||||
|
||||
def update_battery(self):
|
||||
"""Actualizar el estado de la batería."""
|
||||
while not self.stop_event.is_set():
|
||||
battery = psutil.sensors_battery()
|
||||
if battery:
|
||||
percent = battery.percent
|
||||
time_left = battery.secsleft // 3600 if battery.secsleft > 0 else "N/A"
|
||||
self.battery_label.config(text=f"Battery: {percent}%, ({time_left}h left)")
|
||||
else:
|
||||
self.battery_label.config(text="Battery: N/A")
|
||||
self.battery_label.after(1000, lambda: None) # Evitar bloqueo
|
||||
self.stop_event.wait(5)
|
||||
|
||||
def update_network(self):
|
||||
"""Actualizar el uso de red."""
|
||||
old_sent = psutil.net_io_counters().bytes_sent
|
||||
old_recv = psutil.net_io_counters().bytes_recv
|
||||
while not self.stop_event.is_set():
|
||||
new_sent = psutil.net_io_counters().bytes_sent
|
||||
new_recv = psutil.net_io_counters().bytes_recv
|
||||
sent_mb = (new_sent - old_sent) / (1024 * 1024)
|
||||
recv_mb = (new_recv - old_recv) / (1024 * 1024)
|
||||
self.network_label.config(text=f"Net: {sent_mb:.2f} MB sent, {recv_mb:.2f} MB recv")
|
||||
old_sent, old_recv = new_sent, new_recv
|
||||
self.network_label.after(1000, lambda: None) # Evitar bloqueo
|
||||
self.stop_event.wait(1)
|
||||
|
|
|
@ -3,45 +3,127 @@ import threading
|
|||
import requests
|
||||
import time
|
||||
|
||||
|
||||
class WeatherWidget:
|
||||
def __init__(self, parent, api_key):
|
||||
"""
|
||||
Crea un widget para mostrar información meteorológica usando OpenWeatherMap.
|
||||
Inicializa el widget del clima con detalles adicionales.
|
||||
|
||||
Args:
|
||||
parent (tk.Frame): Frame donde se colocará el widget.
|
||||
parent (tk.Frame): Frame en el que se colocará el widget.
|
||||
api_key (str): Clave de la API de OpenWeatherMap.
|
||||
"""
|
||||
self.parent = parent
|
||||
self.api_key = api_key
|
||||
|
||||
# Configurar interfaz del widget
|
||||
# Crear un Frame para contener los datos
|
||||
self.frame = tk.Frame(self.parent, bg="white", bd=2, relief="groove")
|
||||
self.frame.pack(padx=10, pady=10, fill="x", anchor="n")
|
||||
|
||||
# Encabezado del clima
|
||||
self.header_label = tk.Label(self.frame, text="Weather in ...", font=("Helvetica", 14, "bold"), bg="white")
|
||||
self.header_label.pack(pady=5)
|
||||
|
||||
# Temperatura principal
|
||||
self.temp_label = tk.Label(self.frame, text="--°C", font=("Helvetica", 28, "bold"), bg="white")
|
||||
self.temp_label.pack()
|
||||
|
||||
# Detalles adicionales
|
||||
self.details_label = tk.Label(self.frame, text="", font=("Helvetica", 12), bg="white", justify="left")
|
||||
self.details_label.pack(pady=5)
|
||||
|
||||
# Iniciar actualización automática del clima
|
||||
# Iniciar el hilo para actualizar el clima
|
||||
self.start_weather_updates()
|
||||
|
||||
def get_location(self):
|
||||
"""
|
||||
Obtiene la ubicación actual (latitud y longitud) usando ip-api.
|
||||
"""
|
||||
try:
|
||||
response = requests.get("http://ip-api.com/json/")
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
return data["lat"], data["lon"], data["city"]
|
||||
except Exception as e:
|
||||
return None, None, f"Error al obtener ubicación: {e}"
|
||||
|
||||
def get_weather(self, lat, lon):
|
||||
"""
|
||||
Obtiene la temperatura, viento y calidad del aire desde OpenWeatherMap.
|
||||
Obtiene el clima actual usando OpenWeatherMap.
|
||||
|
||||
Args:
|
||||
lat (float): Latitud de la ubicación.
|
||||
lon (float): Longitud de la ubicación.
|
||||
"""
|
||||
try:
|
||||
weather_url = f"http://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={self.api_key}&units=metric"
|
||||
response = requests.get(weather_url)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
# Información principal
|
||||
city = data["name"]
|
||||
temp = data["main"]["temp"]
|
||||
real_feel = data["main"]["feels_like"]
|
||||
wind_speed = data["wind"]["speed"]
|
||||
wind_gusts = data["wind"].get("gust", "N/A")
|
||||
weather = data["weather"][0]["description"].capitalize()
|
||||
|
||||
# Obtener calidad del aire (Air Quality)
|
||||
air_quality = self.get_air_quality(lat, lon)
|
||||
|
||||
# Formatear detalles adicionales
|
||||
details = (
|
||||
f"RealFeel: {real_feel}°\n"
|
||||
f"Wind: {wind_speed} km/h\n"
|
||||
f"Wind Gusts: {wind_gusts} km/h\n"
|
||||
f"Air Quality: {air_quality}"
|
||||
)
|
||||
|
||||
return city, temp, details
|
||||
except Exception as e:
|
||||
return None, None, f"Error al obtener el clima: {e}"
|
||||
|
||||
def get_air_quality(self, lat, lon):
|
||||
"""
|
||||
Obtiene la calidad del aire usando OpenWeatherMap.
|
||||
|
||||
Args:
|
||||
lat (float): Latitud.
|
||||
lon (float): Longitud.
|
||||
"""
|
||||
# Llamada a la API de OpenWeatherMap para obtener datos meteorológicos
|
||||
pass
|
||||
try:
|
||||
aqi_url = f"http://api.openweathermap.org/data/2.5/air_pollution?lat={lat}&lon={lon}&appid={self.api_key}"
|
||||
response = requests.get(aqi_url)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
aqi = data["list"][0]["main"]["aqi"]
|
||||
|
||||
# Mapear AQI a descripciones
|
||||
aqi_mapping = {1: "Good", 2: "Fair", 3: "Moderate", 4: "Poor", 5: "Very Poor"}
|
||||
return aqi_mapping.get(aqi, "Unknown")
|
||||
except Exception as e:
|
||||
return f"Error: {e}"
|
||||
|
||||
def update_weather(self):
|
||||
"""
|
||||
Actualiza la información del clima periódicamente.
|
||||
"""
|
||||
while True:
|
||||
lat, lon, location_info = self.get_location()
|
||||
if lat and lon:
|
||||
city, temp, details = self.get_weather(lat, lon)
|
||||
self.header_label.config(text=f"Weather in {city}")
|
||||
self.temp_label.config(text=f"{temp}°C")
|
||||
self.details_label.config(text=details)
|
||||
else:
|
||||
self.header_label.config(text=location_info) # Error de ubicación
|
||||
|
||||
time.sleep(60) # Actualizar cada 60 segundos
|
||||
|
||||
def start_weather_updates(self):
|
||||
"""Inicia un hilo que actualiza la información meteorológica cada 60 segundos."""
|
||||
threading.Thread(target=self.update_weather, daemon=True).start()
|
||||
"""
|
||||
Inicia el hilo para actualizar el clima.
|
||||
"""
|
||||
weather_thread = threading.Thread(target=self.update_weather, daemon=True)
|
||||
weather_thread.start()
|
||||
|
|
|
@ -23,22 +23,21 @@ class EconomyBitcoinChart:
|
|||
self.ax_bitcoin = self.figure.add_subplot(212) # Gráfico inferior
|
||||
|
||||
# Inicializar datos simulados
|
||||
self.economy_data = [random.randint(50, 100) for _ in range(10)] # Índice económico en meses
|
||||
self.bitcoin_data = [random.randint(20000, 60000) for _ in range(10)] # Precio del Bitcoin en días
|
||||
self.economy_data = [random.randint(50, 100) for _ in range(10)] # Economía en meses
|
||||
self.bitcoin_data = [random.randint(20000, 60000) for _ in range(10)] # Bitcoin en días
|
||||
|
||||
# Dibujar los gráficos iniciales
|
||||
self.update_economy_chart()
|
||||
self.update_bitcoin_chart()
|
||||
|
||||
# Embebiendo los gráficos en la interfaz de Tkinter
|
||||
# Embebiendo los gráficos en Tkinter
|
||||
self.canvas = FigureCanvasTkAgg(self.figure, master=self.parent)
|
||||
self.canvas.get_tk_widget().pack(fill="both", expand=True, padx=10, pady=10)
|
||||
|
||||
# Iniciar hilos para actualizar los gráficos periódicamente
|
||||
# Iniciar hilos para actualizar los gráficos
|
||||
threading.Thread(target=self.update_charts, daemon=True).start()
|
||||
|
||||
def update_economy_chart(self):
|
||||
"""Actualiza el gráfico de economía mundial con nuevos datos."""
|
||||
"""Actualiza el gráfico de economía mundial."""
|
||||
self.ax_economy.clear()
|
||||
self.ax_economy.plot(self.economy_data, marker="o", color="blue")
|
||||
self.ax_economy.set_title("Economía Mundial")
|
||||
|
@ -46,22 +45,22 @@ class EconomyBitcoinChart:
|
|||
self.ax_economy.grid(True)
|
||||
|
||||
def update_bitcoin_chart(self):
|
||||
"""Actualiza el gráfico del precio de Bitcoin."""
|
||||
"""Actualiza el gráfico de Bitcoin."""
|
||||
self.ax_bitcoin.clear()
|
||||
self.ax_bitcoin.plot(self.bitcoin_data, marker="o", color="green")
|
||||
self.ax_bitcoin.set_title("Precio de Bitcoin")
|
||||
self.ax_bitcoin.set_ylabel("Precio en USD")
|
||||
self.ax_bitcoin.set_xlabel("Días") # Etiqueta del eje X
|
||||
self.ax_bitcoin.set_xlabel("Días") # Etiqueta para los días
|
||||
self.ax_bitcoin.grid(True)
|
||||
|
||||
def update_charts(self):
|
||||
"""Actualiza ambos gráficos con nuevos datos periódicamente."""
|
||||
"""Actualiza ambos gráficos periódicamente."""
|
||||
while True:
|
||||
# Generar nuevos datos simulados
|
||||
self.economy_data = self.economy_data[1:] + [random.randint(50, 100)]
|
||||
self.bitcoin_data = self.bitcoin_data[1:] + [random.randint(20000, 60000)]
|
||||
# Actualizar datos simulados
|
||||
self.economy_data = self.economy_data[1:] + [random.randint(50, 100)] # Economía en meses
|
||||
self.bitcoin_data = self.bitcoin_data[1:] + [random.randint(20000, 60000)] # Bitcoin en días
|
||||
|
||||
# Redibujar gráficos con los nuevos datos
|
||||
# Actualizar gráficos
|
||||
self.update_economy_chart()
|
||||
self.update_bitcoin_chart()
|
||||
self.canvas.draw()
|
||||
|
|
|
@ -10,7 +10,7 @@ DOWNLOAD_FOLDER = "musicplayer"
|
|||
class MusicDownloader:
|
||||
def __init__(self, parent):
|
||||
"""
|
||||
Inicializa la interfaz gráfica para descargar música de YouTube en formato MP3.
|
||||
Inicializa la interfaz para descargar música de YouTube en MP3.
|
||||
"""
|
||||
self.parent = parent
|
||||
|
||||
|
@ -22,7 +22,7 @@ class MusicDownloader:
|
|||
title = tk.Label(self.parent, text="Descargar Música MP3", font=("Helvetica", 14, "bold"))
|
||||
title.pack(pady=10)
|
||||
|
||||
# Campo de entrada para la URL de YouTube
|
||||
# Entrada para la URL
|
||||
self.url_label = tk.Label(self.parent, text="URL de YouTube:")
|
||||
self.url_label.pack(pady=5)
|
||||
self.url_entry = tk.Entry(self.parent, width=50)
|
||||
|
@ -32,16 +32,16 @@ class MusicDownloader:
|
|||
self.download_button = tk.Button(self.parent, text="Descargar MP3", command=self.start_download, bg="lightblue")
|
||||
self.download_button.pack(pady=10)
|
||||
|
||||
# Barra de progreso de la descarga
|
||||
# Barra de progreso
|
||||
self.progress = ttk.Progressbar(self.parent, orient="horizontal", length=300, mode="determinate")
|
||||
self.progress.pack(pady=10)
|
||||
|
||||
# Etiqueta de estado para mostrar mensajes
|
||||
# Etiqueta de estado
|
||||
self.status_label = tk.Label(self.parent, text="", font=("Helvetica", 10))
|
||||
self.status_label.pack(pady=5)
|
||||
|
||||
def start_download(self):
|
||||
"""Inicia la descarga en un hilo separado para no bloquear la interfaz."""
|
||||
"""Inicia la descarga en un hilo separado."""
|
||||
url = self.url_entry.get()
|
||||
if not url:
|
||||
self.status_label.config(text="Por favor, ingrese una URL válida.", fg="red")
|
||||
|
@ -50,12 +50,11 @@ class MusicDownloader:
|
|||
threading.Thread(target=self.download_music, args=(url,), daemon=True).start()
|
||||
|
||||
def download_music(self, url):
|
||||
"""Descarga el audio de YouTube en formato MP3 usando `yt-dlp`."""
|
||||
"""Descarga el audio de YouTube en MP3 usando yt-dlp."""
|
||||
try:
|
||||
self.status_label.config(text="Descargando...", fg="blue")
|
||||
output_template = os.path.join(DOWNLOAD_FOLDER, "%(title)s.%(ext)s")
|
||||
|
||||
# Opciones para descargar solo el audio y convertirlo a MP3
|
||||
ydl_opts = {
|
||||
'format': 'bestaudio/best',
|
||||
'outtmpl': output_template,
|
||||
|
@ -72,7 +71,6 @@ class MusicDownloader:
|
|||
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
|
||||
|
|
|
@ -4,32 +4,33 @@ import threading
|
|||
import mysql.connector
|
||||
from mysql.connector import Error
|
||||
|
||||
|
||||
class SQLQueryExecutor:
|
||||
def __init__(self, parent):
|
||||
"""
|
||||
Clase que permite ejecutar consultas SQL en una base de datos MySQL.
|
||||
Clase para ejecutar consultas SQL en una base de datos MySQL.
|
||||
|
||||
Args:
|
||||
parent (tk.Frame): Frame donde se colocarán los widgets de la interfaz gráfica.
|
||||
parent (tk.Frame): Frame donde se colocarán los widgets.
|
||||
"""
|
||||
self.parent = parent
|
||||
|
||||
# Frame para ingresar los datos de conexión a la base de datos
|
||||
# Campos para ingresar información de conexión
|
||||
self.db_info_frame = tk.Frame(self.parent)
|
||||
self.db_info_frame.pack(pady=10, padx=10, fill="x")
|
||||
|
||||
tk.Label(self.db_info_frame, text="Host:").grid(row=0, column=0, sticky="w")
|
||||
self.host_entry = tk.Entry(self.db_info_frame)
|
||||
self.host_entry.insert(0, "localhost") # Valor por defecto
|
||||
self.host_entry.insert(0, "localhost")
|
||||
self.host_entry.grid(row=0, column=1)
|
||||
|
||||
tk.Label(self.db_info_frame, text="Usuario:").grid(row=1, column=0, sticky="w")
|
||||
self.user_entry = tk.Entry(self.db_info_frame)
|
||||
self.user_entry.insert(0, "root") # Usuario por defecto
|
||||
self.user_entry.insert(0, "root")
|
||||
self.user_entry.grid(row=1, column=1)
|
||||
|
||||
tk.Label(self.db_info_frame, text="Contraseña:").grid(row=2, column=0, sticky="w")
|
||||
self.password_entry = tk.Entry(self.db_info_frame, show="*") # Campo oculto
|
||||
self.password_entry = tk.Entry(self.db_info_frame, show="*")
|
||||
self.password_entry.grid(row=2, column=1)
|
||||
|
||||
tk.Label(self.db_info_frame, text="Base de datos:").grid(row=3, column=0, sticky="w")
|
||||
|
@ -40,7 +41,7 @@ class SQLQueryExecutor:
|
|||
self.connect_button = tk.Button(self.db_info_frame, text="Conectar", command=self.connect_to_database)
|
||||
self.connect_button.grid(row=4, column=0, columnspan=2, pady=5)
|
||||
|
||||
# Área para escribir la consulta SQL
|
||||
# Área para ingresar consultas SQL
|
||||
self.query_frame = tk.Frame(self.parent)
|
||||
self.query_frame.pack(pady=10, padx=10, fill="both", expand=True)
|
||||
|
||||
|
@ -48,11 +49,11 @@ class SQLQueryExecutor:
|
|||
self.query_text = tk.Text(self.query_frame, height=10)
|
||||
self.query_text.pack(fill="both", expand=True)
|
||||
|
||||
# Botón para ejecutar la consulta
|
||||
# Botón para ejecutar consultas
|
||||
self.execute_button = tk.Button(self.query_frame, text="Ejecutar", command=self.execute_query)
|
||||
self.execute_button.pack(pady=5)
|
||||
|
||||
# Área para mostrar los resultados
|
||||
# Área para mostrar resultados
|
||||
self.result_frame = tk.Frame(self.parent)
|
||||
self.result_frame.pack(pady=10, padx=10, fill="both", expand=True)
|
||||
|
||||
|
@ -61,7 +62,7 @@ class SQLQueryExecutor:
|
|||
self.result_text.pack(fill="both", expand=True)
|
||||
|
||||
def connect_to_database(self):
|
||||
"""Conecta a la base de datos MySQL utilizando los datos ingresados por el usuario."""
|
||||
"""Conecta a la base de datos utilizando los datos proporcionados."""
|
||||
self.host = self.host_entry.get()
|
||||
self.user = self.user_entry.get()
|
||||
self.password = self.password_entry.get()
|
||||
|
@ -80,7 +81,7 @@ class SQLQueryExecutor:
|
|||
messagebox.showerror("Error de Conexión", str(e))
|
||||
|
||||
def execute_query(self):
|
||||
"""Ejecuta la consulta SQL en un hilo separado para evitar bloquear la interfaz."""
|
||||
"""Ejecuta la consulta SQL en un hilo separado."""
|
||||
query = self.query_text.get("1.0", tk.END).strip()
|
||||
if not query:
|
||||
messagebox.showwarning("Consulta Vacía", "Por favor, ingrese una consulta SQL.")
|
||||
|
@ -89,7 +90,7 @@ class SQLQueryExecutor:
|
|||
threading.Thread(target=self.run_query, args=(query,), daemon=True).start()
|
||||
|
||||
def run_query(self, query):
|
||||
"""Ejecuta la consulta en la base de datos y muestra los resultados."""
|
||||
"""Ejecuta la consulta y muestra los resultados."""
|
||||
try:
|
||||
cursor = self.connection.cursor()
|
||||
cursor.execute(query)
|
||||
|
|
|
@ -4,6 +4,7 @@ from tkinter import messagebox
|
|||
import threading
|
||||
import random
|
||||
|
||||
|
||||
class TicTacToe:
|
||||
def __init__(self, parent):
|
||||
"""
|
||||
|
@ -13,19 +14,19 @@ class TicTacToe:
|
|||
parent (tk.Frame): Frame donde se colocará el juego.
|
||||
"""
|
||||
self.parent = parent
|
||||
self.board = [""] * 9 # Representación del tablero 3x3
|
||||
self.current_player = "X" # Jugador que inicia la partida
|
||||
self.board = [""] * 9 # Tablero de 3x3 representado como una lista
|
||||
self.current_player = "X" # Jugador inicial
|
||||
self.vs_computer = False # Modo jugador vs máquina
|
||||
|
||||
# Título del juego
|
||||
# Etiqueta para el título
|
||||
title = tk.Label(self.parent, text="Tic Tac Toe", font=("Helvetica", 16, "bold"))
|
||||
title.pack(pady=10)
|
||||
|
||||
# Botón para alternar entre modos de juego
|
||||
# Botón para alternar entre modos
|
||||
self.mode_button = tk.Button(self.parent, text="Modo: Jugador vs Jugador", command=self.toggle_mode)
|
||||
self.mode_button.pack(pady=5)
|
||||
|
||||
# Crear la cuadrícula del tablero
|
||||
# Crear el tablero
|
||||
self.buttons = []
|
||||
self.board_frame = tk.Frame(self.parent)
|
||||
self.board_frame.pack()
|
||||
|
@ -37,12 +38,12 @@ class TicTacToe:
|
|||
font=("Helvetica", 20),
|
||||
width=5,
|
||||
height=2,
|
||||
command=self.create_button_command(i)
|
||||
command=self.create_button_command(i) # Aquí usamos la función auxiliar
|
||||
)
|
||||
button.grid(row=i // 3, column=i % 3)
|
||||
self.buttons.append(button)
|
||||
|
||||
# Mensaje que indica el turno del jugador
|
||||
# Etiqueta para el estado del juego
|
||||
self.status_label = tk.Label(self.parent, text="Turno: X", font=("Helvetica", 12))
|
||||
self.status_label.pack(pady=5)
|
||||
|
||||
|
@ -54,7 +55,7 @@ class TicTacToe:
|
|||
self.reset_game()
|
||||
|
||||
def reset_game(self):
|
||||
"""Reinicia el juego a su estado inicial."""
|
||||
"""Reinicia el tablero y el estado del juego."""
|
||||
self.board = [""] * 9
|
||||
self.current_player = "X"
|
||||
for button in self.buttons:
|
||||
|
@ -62,11 +63,12 @@ class TicTacToe:
|
|||
self.status_label.config(text="Turno: X")
|
||||
|
||||
def make_move(self, index):
|
||||
"""Registra un movimiento y actualiza el tablero."""
|
||||
"""Realiza un movimiento en el tablero."""
|
||||
if self.board[index] == "":
|
||||
self.board[index] = self.current_player
|
||||
self.buttons[index].config(text=self.current_player)
|
||||
|
||||
# Verificar si hay un ganador
|
||||
winner = self.check_winner()
|
||||
if winner:
|
||||
self.end_game(f"¡Ganador: {winner}!")
|
||||
|
@ -75,11 +77,28 @@ class TicTacToe:
|
|||
self.end_game("¡Empate!")
|
||||
return
|
||||
|
||||
# Cambiar de jugador
|
||||
self.current_player = "O" if self.current_player == "X" else "X"
|
||||
self.status_label.config(text=f"Turno: {self.current_player}")
|
||||
|
||||
# Si está en modo Jugador vs Máquina y es el turno de la máquina
|
||||
if self.vs_computer and self.current_player == "O":
|
||||
threading.Thread(target=self.computer_move).start()
|
||||
|
||||
def computer_move(self):
|
||||
"""Simula el movimiento de la máquina."""
|
||||
self.status_label.config(text="Turno: Máquina (O)")
|
||||
available_moves = [i for i, v in enumerate(self.board) if v == ""]
|
||||
move = random.choice(available_moves)
|
||||
|
||||
def delayed_move():
|
||||
time.sleep(1) # Simular el tiempo de "pensar"
|
||||
self.make_move(move)
|
||||
|
||||
threading.Thread(target=delayed_move).start()
|
||||
|
||||
def check_winner(self):
|
||||
"""Verifica si hay un ganador en el juego."""
|
||||
"""Verifica si hay un ganador."""
|
||||
winning_combinations = [
|
||||
(0, 1, 2), (3, 4, 5), (6, 7, 8), # Filas
|
||||
(0, 3, 6), (1, 4, 7), (2, 5, 8), # Columnas
|
||||
|
@ -89,3 +108,16 @@ class TicTacToe:
|
|||
if self.board[a] == self.board[b] == self.board[c] and self.board[a] != "":
|
||||
return self.board[a]
|
||||
return None
|
||||
|
||||
def end_game(self, message):
|
||||
"""Finaliza el juego mostrando un mensaje."""
|
||||
messagebox.showinfo("Fin del Juego", message)
|
||||
self.reset_game()
|
||||
|
||||
def create_button_command(self, index):
|
||||
"""Crea un comando para un botón con un índice específico."""
|
||||
|
||||
def command():
|
||||
self.make_move(index)
|
||||
|
||||
return command
|
|
@ -9,59 +9,52 @@ from tkinter import messagebox
|
|||
class WebScraperToDB:
|
||||
def __init__(self, parent):
|
||||
"""
|
||||
Inicializa la interfaz gráfica para scraping web con integración a base de datos.
|
||||
|
||||
Args:
|
||||
parent (tk.Frame): Contenedor en el que se mostrará la interfaz gráfica.
|
||||
Inicializa el widget de scraping con integración a base de datos.
|
||||
"""
|
||||
self.parent = parent
|
||||
self.scraping_thread = None # Hilo que ejecutará el scraping
|
||||
self.stop_event = threading.Event() # Evento para detener el scraping
|
||||
self.scraping_thread = None
|
||||
self.stop_event = threading.Event()
|
||||
|
||||
# Crear la sección de conexión a la base de datos
|
||||
# Crear campos de conexión para la base de datos
|
||||
db_frame = tk.Frame(self.parent)
|
||||
db_frame.pack(pady=5)
|
||||
|
||||
# Campos de entrada para la conexión a MySQL
|
||||
tk.Label(db_frame, text="Host:").grid(row=0, column=0)
|
||||
self.host_entry = tk.Entry(db_frame)
|
||||
self.host_entry.insert(0, "localhost") # Valor predeterminado
|
||||
self.host_entry.insert(0, "localhost")
|
||||
self.host_entry.grid(row=0, column=1)
|
||||
|
||||
tk.Label(db_frame, text="Usuario:").grid(row=1, column=0)
|
||||
self.user_entry = tk.Entry(db_frame)
|
||||
self.user_entry.insert(0, "root") # Usuario predeterminado
|
||||
self.user_entry.insert(0, "root")
|
||||
self.user_entry.grid(row=1, column=1)
|
||||
|
||||
tk.Label(db_frame, text="Contraseña:").grid(row=2, column=0)
|
||||
self.password_entry = tk.Entry(db_frame, show="*") # Campo oculto para seguridad
|
||||
self.password_entry = tk.Entry(db_frame, show="*")
|
||||
self.password_entry.grid(row=2, column=1)
|
||||
|
||||
tk.Label(db_frame, text="Nombre BD:").grid(row=3, column=0)
|
||||
self.database_entry = tk.Entry(db_frame)
|
||||
self.database_entry.insert(0, "scraping_db") # Base de datos predeterminada
|
||||
self.database_entry.insert(0, "scraping_db")
|
||||
self.database_entry.grid(row=3, column=1)
|
||||
|
||||
# Botón para crear la base de datos
|
||||
tk.Button(db_frame, text="Crear Base de Datos", command=self.create_database).grid(row=4, column=0, columnspan=2, pady=5)
|
||||
|
||||
# Sección de controles para scraping
|
||||
# Área para URL y botones de control
|
||||
control_frame = tk.Frame(self.parent)
|
||||
control_frame.pack(pady=5)
|
||||
|
||||
# Campo de entrada para la URL a scrape
|
||||
tk.Label(control_frame, text="URL para Scraping:").grid(row=0, column=0)
|
||||
self.url_entry = tk.Entry(control_frame, width=50)
|
||||
self.url_entry.insert(0, "https://quotes.toscrape.com/") # URL de prueba
|
||||
self.url_entry.insert(0, "https://quotes.toscrape.com/")
|
||||
self.url_entry.grid(row=0, column=1)
|
||||
|
||||
# Campo para ingresar el selector HTML
|
||||
# Campo para Selector HTML
|
||||
tk.Label(control_frame, text="Selector HTML:").grid(row=2, column=0)
|
||||
self.selector_entry = tk.Entry(control_frame, width=50)
|
||||
self.selector_entry.insert(0, "h1") # Selector predeterminado
|
||||
self.selector_entry.insert(0, "h1") # Valor predeterminado
|
||||
self.selector_entry.grid(row=2, column=1)
|
||||
|
||||
# Botones de control
|
||||
self.start_button = tk.Button(control_frame, text="Iniciar Scraping", command=self.start_scraping)
|
||||
self.start_button.grid(row=1, column=0, pady=5)
|
||||
|
||||
|
@ -71,7 +64,7 @@ class WebScraperToDB:
|
|||
self.reset_button = tk.Button(control_frame, text="Resetear Scraping", command=self.reset_database)
|
||||
self.reset_button.grid(row=1, column=2, pady=5)
|
||||
|
||||
# Etiqueta para mostrar el estado del scraping
|
||||
# Área para mostrar el estado
|
||||
self.status_label = tk.Label(self.parent, text="Estado: Inactivo", fg="red")
|
||||
self.status_label.pack(pady=5)
|
||||
|
||||
|
@ -85,7 +78,7 @@ class WebScraperToDB:
|
|||
self.scraped_data_text.pack(fill="both", expand=True)
|
||||
|
||||
def create_database(self):
|
||||
"""Crea la base de datos y la tabla para almacenar los datos de scraping."""
|
||||
"""Crea la base de datos y la tabla para almacenar datos de scraping."""
|
||||
try:
|
||||
connection = mysql.connector.connect(
|
||||
host=self.host_entry.get(),
|
||||
|
@ -126,7 +119,9 @@ class WebScraperToDB:
|
|||
selector = self.selector_entry.get()
|
||||
|
||||
try:
|
||||
headers = {"User-Agent": "Mozilla/5.0"}
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
||||
}
|
||||
connection = mysql.connector.connect(
|
||||
host=self.host_entry.get(),
|
||||
user=self.user_entry.get(),
|
||||
|
@ -139,7 +134,7 @@ class WebScraperToDB:
|
|||
response = requests.get(url, headers=headers)
|
||||
soup = BeautifulSoup(response.text, "html.parser")
|
||||
|
||||
# Busca elementos en la página usando el selector proporcionado
|
||||
# Busca elementos según el selector ingresado por el usuario
|
||||
elements = soup.select(selector)
|
||||
if not elements:
|
||||
self.status_label.config(text="Estado: Sin datos encontrados.", fg="orange")
|
||||
|
@ -148,9 +143,9 @@ class WebScraperToDB:
|
|||
|
||||
for element in elements:
|
||||
title_text = element.get_text(strip=True)
|
||||
link = element.get("href", "Sin enlace") # Extrae el enlace si está disponible
|
||||
link = element.get("href", "Sin enlace") # Asegúrate de que el selector apunte a elementos <a>
|
||||
|
||||
# Insertar datos en la base de datos
|
||||
# Insertar en la base de datos
|
||||
cursor.execute("INSERT INTO scraped_data (title, link) VALUES (%s, %s)", (title_text, link))
|
||||
connection.commit()
|
||||
|
||||
|
@ -162,7 +157,8 @@ class WebScraperToDB:
|
|||
|
||||
self.status_label.config(text=f"Estado: Scrapeando {title_text}...", fg="green")
|
||||
|
||||
time.sleep(5) # Pausa entre iteraciones
|
||||
# Pausa entre iteraciones
|
||||
time.sleep(5)
|
||||
|
||||
connection.close()
|
||||
self.status_label.config(text="Estado: Inactivo", fg="red")
|
||||
|
@ -177,7 +173,7 @@ class WebScraperToDB:
|
|||
self.stop_button.config(state="disabled")
|
||||
|
||||
def reset_database(self):
|
||||
"""Elimina todos los datos de la tabla scraped_data."""
|
||||
"""Elimina todos los datos de la tabla."""
|
||||
try:
|
||||
connection = mysql.connector.connect(
|
||||
host=self.host_entry.get(),
|
||||
|
|
Loading…
Reference in New Issue