-
-
Save eros18123/62e88fc31f5c354f78c39ccd60350970 to your computer and use it in GitHub Desktop.
| #agente servidor.pyw | |
| # agente.py (Versão Final com Notificação Customizada e 100% Confiável) | |
| import socket | |
| import subprocess | |
| import sys | |
| import os | |
| import threading | |
| import queue | |
| import time | |
| from PIL import Image, ImageDraw | |
| from pystray import MenuItem as item, Icon | |
| # --- Configurações --- | |
| MEU_PAPEL = 'servidor' # <-- MUDE AQUI CONFORME O PC ('servidor' ou 'cliente') | |
| # Portas de comunicação de rede | |
| PORTA_CHAT = 55555 | |
| PORTA_COMANDO_REMOTO = 50001 | |
| PORTA_PRESENCA = 50000 | |
| # Portas de comunicação local (Agente <-> GUI) | |
| PORTA_GUI_LOCAL = 50003 | |
| # Comandos e Mensagens | |
| COMANDO_INICIAR_SERVIDOR = b'INICIE_O_SERVIDOR_DE_CHAT' | |
| MSG_PRESENCA_SERVIDOR = b'SERVIDOR_ONLINE' | |
| # Caminhos dos scripts | |
| base_path = os.path.dirname(os.path.realpath(sys.argv[0])) | |
| caminho_script_servidor = os.path.join(base_path, 'servidor.py') | |
| caminho_script_cliente = os.path.join(base_path, 'cliente.py') | |
| caminho_script_notificador = os.path.join(base_path, 'notificador.py') # <<< NOVO CAMINHO | |
| HISTORICO_CHAT_ARQUIVO_SERVIDOR = os.path.join(base_path, "chat_history.log") | |
| HISTORICO_CHAT_ARQUIVO_CLIENTE = os.path.join(base_path, "chat_history_client.log") | |
| # --- Variáveis Globais de Estado --- | |
| icon = None | |
| socket_remoto = None | |
| socket_gui = None | |
| fila_para_gui = queue.Queue() | |
| fila_mensagens_offline = [] | |
| # --- Funções de Persistência --- | |
| def salvar_mensagem_no_historico(mensagem): | |
| arquivo = HISTORICO_CHAT_ARQUIVO_SERVIDOR if MEU_PAPEL == 'servidor' else HISTORICO_CHAT_ARQUIVO_CLIENTE | |
| try: | |
| with open(arquivo, "a", encoding="utf-8") as f: | |
| f.write(mensagem) | |
| except Exception as e: | |
| print(f"!!! ERRO AO SALVAR HISTÓRICO: {e}") | |
| # --- Funções da Bandeja do Sistema (pystray) --- | |
| def criar_imagem_estrela(): | |
| width, height = 64, 64 | |
| image = Image.new('RGBA', (width, height), (0, 0, 0, 0)) | |
| dc = ImageDraw.Draw(image) | |
| dc.polygon([(32, 0), (40, 24), (64, 24), (44, 40), (52, 64), (32, 48), (12, 64), (20, 40), (0, 24), (24, 24)], fill='red') | |
| return image | |
| def iniciar_programa_chat(ignore_arg1=None, ignore_arg2=None): | |
| script_path = caminho_script_servidor if MEU_PAPEL == 'servidor' else caminho_script_cliente | |
| print(f"Iniciando GUI: {script_path}...") | |
| subprocess.Popen(['pythonw', script_path]) | |
| def fechar_agente(icon, item): | |
| print("Fechando agente...") | |
| icon.stop() | |
| os._exit(0) | |
| # --- Funções de Notificação --- | |
| # <<< FUNÇÃO TOTALMENTE REESCRITA PARA USAR NOSSO PRÓPRIO NOTIFICADOR >>> | |
| def mostrar_notificacao(titulo, mensagem): | |
| try: | |
| print(f"--- CHAMANDO NOTIFICADOR CUSTOMIZADO: Título='{titulo}' ---") | |
| # Executa o script notificador.py em segundo plano, passando o título e a mensagem | |
| subprocess.Popen(['pythonw', caminho_script_notificador, titulo, mensagem]) | |
| except Exception as e: | |
| print(f"!!!!!!!!!! ERRO CRÍTICO AO CHAMAR O SCRIPT NOTIFICADOR: {e} !!!!!!!!!!") | |
| # --- LÓGICA DE REDE PRINCIPAL (código restante sem alterações) --- | |
| def logica_servidor(): | |
| global socket_remoto | |
| servidor = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
| servidor.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |
| servidor.bind(('0.0.0.0', PORTA_CHAT)) | |
| servidor.listen(1) | |
| print("Lógica do Servidor iniciada. Aguardando conexões...") | |
| threading.Thread(target=anunciar_presenca_servidor, daemon=True).start() | |
| while True: | |
| conn, addr = servidor.accept() | |
| print(f"Cliente {addr[0]} conectou.") | |
| socket_remoto = conn | |
| if fila_mensagens_offline: | |
| for msg in fila_mensagens_offline: | |
| try: | |
| socket_remoto.send(msg.encode('utf-8')) | |
| time.sleep(0.1) | |
| except Exception as e: | |
| print(f"Erro ao enviar msg offline: {e}") | |
| fila_mensagens_offline.clear() | |
| threading.Thread(target=receber_mensagens_remotas, args=(conn,), daemon=True).start() | |
| def anunciar_presenca_servidor(): | |
| with socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) as sock: | |
| sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) | |
| while True: | |
| if not socket_remoto: | |
| sock.sendto(MSG_PRESENCA_SERVIDOR, ("<broadcast>", PORTA_PRESENCA)) | |
| time.sleep(3) | |
| def logica_cliente(): | |
| global socket_remoto | |
| while True: | |
| servidor_ip = None | |
| try: | |
| with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: | |
| s.settimeout(3) | |
| s.bind(('', PORTA_PRESENCA)) | |
| dados, addr = s.recvfrom(1024) | |
| if dados == MSG_PRESENCA_SERVIDOR: | |
| servidor_ip = addr[0] | |
| except socket.timeout: | |
| with socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) as sock: | |
| sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) | |
| sock.sendto(COMANDO_INICIAR_SERVIDOR, ("<broadcast>", PORTA_COMANDO_REMOTO)) | |
| time.sleep(5) | |
| continue | |
| if servidor_ip: | |
| try: | |
| sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
| sock.connect((servidor_ip, PORTA_CHAT)) | |
| socket_remoto = sock | |
| print("Conectado ao servidor.") | |
| receber_mensagens_remotas(sock) | |
| except Exception as e: | |
| print(f"Falha ao conectar: {e}. Tentando novamente em 5s.") | |
| socket_remoto = None | |
| time.sleep(5) | |
| def receber_mensagens_remotas(sock): | |
| global socket_remoto | |
| while True: | |
| try: | |
| msg = sock.recv(1024).decode('utf-8') | |
| if not msg: break | |
| print(f"Mensagem remota recebida: {msg}") | |
| titulo = "Nova Mensagem do Cliente" if MEU_PAPEL == 'servidor' else "Nova Mensagem do Servidor" | |
| mostrar_notificacao(titulo, msg) | |
| prefixo = "Cliente: " if MEU_PAPEL == 'servidor' else "Servidor: " | |
| mensagem_completa = f"{prefixo}{msg}\n" | |
| fila_para_gui.put(mensagem_completa) | |
| salvar_mensagem_no_historico(mensagem_completa) | |
| except: | |
| break | |
| print("Conexão remota perdida.") | |
| socket_remoto = None | |
| sock.close() | |
| def servidor_gui_local(): | |
| global socket_gui | |
| with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: | |
| s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |
| s.bind(('127.0.0.1', PORTA_GUI_LOCAL)) | |
| s.listen(1) | |
| print(f"Agente escutando a GUI local na porta {PORTA_GUI_LOCAL}.") | |
| while True: | |
| conn, addr = s.accept() | |
| print("GUI conectou ao agente.") | |
| socket_gui = conn | |
| threading.Thread(target=enviar_para_gui, daemon=True).start() | |
| threading.Thread(target=receber_da_gui, daemon=True).start() | |
| def enviar_para_gui(): | |
| while socket_gui: | |
| try: | |
| msg = fila_para_gui.get(timeout=1) | |
| socket_gui.sendall(msg.encode('utf-8')) | |
| except queue.Empty: | |
| continue | |
| except Exception as e: | |
| print(f"Erro ao enviar para GUI: {e}") | |
| break | |
| def receber_da_gui(): | |
| global socket_gui | |
| while True: | |
| try: | |
| msg_bytes = socket_gui.recv(1024) | |
| if not msg_bytes: break | |
| msg = msg_bytes.decode('utf-8') | |
| print(f"Recebido da GUI para enviar: {msg}") | |
| mensagem_completa = f"Você: {msg}\n" | |
| fila_para_gui.put(mensagem_completa) | |
| salvar_mensagem_no_historico(mensagem_completa) | |
| if socket_remoto: | |
| socket_remoto.sendall(msg_bytes) | |
| else: | |
| fila_mensagens_offline.append(msg) | |
| fila_para_gui.put(f"Você (guardado): {msg}\n") | |
| except Exception as e: | |
| print(f"Erro ao receber da GUI: {e}") | |
| break | |
| print("GUI desconectou.") | |
| socket_gui = None | |
| def escutar_comandos_remotos(): | |
| with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: | |
| s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |
| s.bind(('', PORTA_COMANDO_REMOTO)) | |
| while True: | |
| dados, _ = s.recvfrom(1024) | |
| if dados == COMANDO_INICIAR_SERVIDOR and MEU_PAPEL == 'servidor': | |
| iniciar_programa_chat() | |
| if __name__ == '__main__': | |
| threading.Thread(target=servidor_gui_local, daemon=True).start() | |
| if MEU_PAPEL == 'servidor': | |
| threading.Thread(target=logica_servidor, daemon=True).start() | |
| threading.Thread(target=escutar_comandos_remotos, daemon=True).start() | |
| elif MEU_PAPEL == 'cliente': | |
| threading.Thread(target=logica_cliente, daemon=True).start() | |
| else: | |
| print("ERRO: MEU_PAPEL deve ser 'servidor' ou 'cliente'.") | |
| sys.exit(1) | |
| menu = (item('Abrir Chat', iniciar_programa_chat, default=True), item('Fechar', fechar_agente)) | |
| icon = Icon("ChatApp", criar_imagem_estrela(), f"Chat App ({MEU_PAPEL})", menu) | |
| icon.run() |
#notificador.py
notificador.py
import tkinter as tk
import sys
--- Configurações ---
LARGURA_NOTIFICACAO = 350
ALTURA_NOTIFICACAO = 100
TEMPO_NA_TELA_MS = 5000 # 5 segundos
COR_FUNDO = "#333333"
COR_TEXTO_TITULO = "#FFFFFF"
COR_TEXTO_MSG = "#DDDDDD"
class NotificationWindow:
def init(self, title, message):
self.root = tk.Tk()
self.root.overrideredirect(True) # Remove a barra de título e bordas
self.root.attributes('-topmost', True) # Mantém a janela sempre na frente
self.root.config(bg=COR_FUNDO, bd=1, relief="solid")
# Posiciona a janela no canto inferior direito
screen_width = self.root.winfo_screenwidth()
screen_height = self.root.winfo_screenheight()
x = screen_width - LARGURA_NOTIFICACAO - 20
y = screen_height - ALTURA_NOTIFICACAO - 60 # Um pouco acima da barra de tarefas
self.root.geometry(f'{LARGURA_NOTIFICACAO}x{ALTURA_NOTIFICACAO}+{x}+{y}')
# Adiciona os textos
title_label = tk.Label(self.root, text=title, font=("Arial", 12, "bold"), bg=COR_FUNDO, fg=COR_TEXTO_TITULO, wraplength=LARGURA_NOTIFICACAO-20, justify="left")
title_label.pack(pady=(10, 2), padx=10, anchor="w")
message_label = tk.Label(self.root, text=message, font=("Arial", 10), bg=COR_FUNDO, fg=COR_TEXTO_MSG, wraplength=LARGURA_NOTIFICACAO-20, justify="left")
message_label.pack(pady=2, padx=10, anchor="w")
# Faz a janela fechar ao ser clicada
self.root.bind("<Button-1>", self.close_window)
title_label.bind("<Button-1>", self.close_window)
message_label.bind("<Button-1>", self.close_window)
# Agenda o fechamento automático
self.root.after(TEMPO_NA_TELA_MS, self.close_window)
self.root.mainloop()
def close_window(self, event=None):
self.root.destroy()
if name == "main":
# Pega o título e a mensagem dos argumentos da linha de comando
if len(sys.argv) >= 3:
popup_title = sys.argv[1]
popup_message = " ".join(sys.argv[2:])
NotificationWindow(popup_title, popup_message)
#agente cliente.pyw
#obs: é so copiar do agente servidor.pyw e mudar onde tem servidor para cliente
pode ser necessario algumas instalaçoes via cmd, alem do python
pip install pystray Pillow win10toast-persist
py -m pip uninstall win10toast-persist
py -m pip install win10toast-persist
py -m pip install --upgrade pip
#servidor.py
servidor.py (Versão Definitiva e Robusta)
import socket
import threading
import tkinter as tk
from tkinter import scrolledtext, messagebox
import queue
import os
import sys
import time
--- Configurações ---
PORTA_GUI_LOCAL = 50003
HISTORICO_CHAT_ARQUIVO = "chat_history.log"
SINGLETON_LOCK_PORT = 50004
class ChatGUI:
def init(self):
self.lock_socket = None
self.agente_socket = None
self.root = None
self.gui_queue = queue.Queue()
if name == "main":
app = ChatGUI()