add conversations
This commit is contained in:
187
telegram_bot.py
187
telegram_bot.py
@@ -1,14 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
TF5 Mixer Telegram Bot - Controllo del mixer Yamaha TF5 tramite Telegram
|
||||
usando Google Gemini con function calling e long polling.
|
||||
TF5 Mixer Telegram Bot - Con salvataggio conversazioni
|
||||
"""
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
from typing import Dict, List, Optional
|
||||
from pathlib import Path
|
||||
from urllib.request import urlopen, Request
|
||||
from urllib.error import URLError, HTTPError
|
||||
from urllib.parse import urlencode
|
||||
@@ -16,12 +16,101 @@ from google import genai
|
||||
from google.genai import types
|
||||
from dotenv import load_dotenv
|
||||
from mixer_controller import TF5MixerController
|
||||
import re
|
||||
|
||||
load_dotenv()
|
||||
|
||||
from config import *
|
||||
|
||||
|
||||
class ConversationManager:
|
||||
"""Gestisce il salvataggio delle conversazioni su file JSON."""
|
||||
|
||||
def __init__(self, conversations_dir: str = "conversations"):
|
||||
self.conversations_dir = Path(conversations_dir)
|
||||
self.conversations_dir.mkdir(exist_ok=True)
|
||||
|
||||
def _get_filepath(self, user_id: int) -> Path:
|
||||
"""Restituisce il percorso del file per un utente."""
|
||||
return self.conversations_dir / f"{user_id}.json"
|
||||
|
||||
def save_conversation(self, user_id: int, history: List[Dict],
|
||||
username: str = "Unknown"):
|
||||
"""Salva la conversazione di un utente."""
|
||||
try:
|
||||
filepath = self._get_filepath(user_id)
|
||||
|
||||
data = {
|
||||
"user_id": user_id,
|
||||
"username": username,
|
||||
"last_updated": time.time(),
|
||||
"last_updated_str": time.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"message_count": len(history),
|
||||
"history": history
|
||||
}
|
||||
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Errore nel salvataggio conversazione {user_id}: {e}")
|
||||
return False
|
||||
|
||||
def load_conversation(self, user_id: int) -> Optional[List[Dict]]:
|
||||
"""Carica la conversazione di un utente."""
|
||||
try:
|
||||
filepath = self._get_filepath(user_id)
|
||||
|
||||
if not filepath.exists():
|
||||
return None
|
||||
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
return data.get("history", [])
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Errore nel caricamento conversazione {user_id}: {e}")
|
||||
return None
|
||||
|
||||
def delete_conversation(self, user_id: int) -> bool:
|
||||
"""Elimina la conversazione di un utente."""
|
||||
try:
|
||||
filepath = self._get_filepath(user_id)
|
||||
|
||||
if filepath.exists():
|
||||
filepath.unlink()
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Errore nell'eliminazione conversazione {user_id}: {e}")
|
||||
return False
|
||||
|
||||
def get_all_users(self) -> List[Dict]:
|
||||
"""Restituisce la lista di tutti gli utenti con conversazioni salvate."""
|
||||
users = []
|
||||
|
||||
for filepath in self.conversations_dir.glob("*.json"):
|
||||
try:
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
users.append({
|
||||
"user_id": data["user_id"],
|
||||
"username": data.get("username", "Unknown"),
|
||||
"message_count": data.get("message_count", 0),
|
||||
"last_updated": data.get("last_updated_str", "Unknown")
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Errore lettura {filepath}: {e}")
|
||||
|
||||
return users
|
||||
|
||||
|
||||
class TelegramBot:
|
||||
"""Bot Telegram base con long polling."""
|
||||
|
||||
@@ -73,7 +162,13 @@ class TelegramBot:
|
||||
return updates
|
||||
|
||||
return []
|
||||
|
||||
|
||||
def escape_markdown_v2(self, text: str) -> str:
|
||||
"""Escape dei caratteri speciali per MarkdownV2."""
|
||||
# Caratteri da escapare in MarkdownV2
|
||||
escape_chars = r'_*[]()~`>#+-=|{}.!'
|
||||
return re.sub(f'([{re.escape(escape_chars)}])', r'\\\1', text)
|
||||
|
||||
def send_message(self, chat_id: int, text: str,
|
||||
parse_mode: Optional[str] = None) -> Dict:
|
||||
"""Invia un messaggio a una chat."""
|
||||
@@ -83,6 +178,9 @@ class TelegramBot:
|
||||
}
|
||||
|
||||
if parse_mode:
|
||||
# Se usi MarkdownV2, applica l'escape automatico
|
||||
if parse_mode == "MarkdownV2":
|
||||
params["text"] = self.escape_markdown_v2(text)
|
||||
params["parse_mode"] = parse_mode
|
||||
|
||||
return self._make_request("sendMessage", params)
|
||||
@@ -100,9 +198,10 @@ class TF5TelegramBot:
|
||||
"""Bot Telegram per controllare il mixer TF5."""
|
||||
|
||||
def __init__(self, telegram_token: str, mixer_host: str = DEFAULT_HOST,
|
||||
mixer_port: int = DEFAULT_PORT):
|
||||
mixer_port: int = DEFAULT_PORT, conversations_dir: str = "conversations"):
|
||||
self.bot = TelegramBot(telegram_token)
|
||||
self.controller = TF5MixerController(mixer_host, mixer_port)
|
||||
self.conversation_manager = ConversationManager(conversations_dir)
|
||||
|
||||
# Configura Gemini
|
||||
api_key = os.getenv("GEMINI_API_KEY")
|
||||
@@ -136,8 +235,10 @@ class TF5TelegramBot:
|
||||
temperature=0,
|
||||
)
|
||||
|
||||
# Storia delle conversazioni per ogni utente
|
||||
# Storia delle conversazioni per ogni utente (in memoria)
|
||||
self.conversations: Dict[int, List[Dict]] = {}
|
||||
# Mappa user_id -> username per il salvataggio
|
||||
self.usernames: Dict[int, str] = {}
|
||||
|
||||
# System instruction
|
||||
self.system_instruction = """Sei un assistente per il controllo del mixer audio Yamaha TF5.
|
||||
@@ -223,25 +324,41 @@ Devi essere veloce, chiaro e capire anche richieste approssimative.
|
||||
def get_conversation_history(self, user_id: int) -> List[Dict]:
|
||||
"""Ottiene la storia della conversazione per un utente."""
|
||||
if user_id not in self.conversations:
|
||||
self.conversations[user_id] = []
|
||||
# Prova a caricare dal file
|
||||
history = self.conversation_manager.load_conversation(user_id)
|
||||
self.conversations[user_id] = history if history else []
|
||||
|
||||
return self.conversations[user_id]
|
||||
|
||||
def add_to_history(self, user_id: int, role: str, content: str):
|
||||
"""Aggiunge un messaggio alla storia."""
|
||||
"""Aggiunge un messaggio alla storia e salva."""
|
||||
history = self.get_conversation_history(user_id)
|
||||
history.append({"role": role, "content": content})
|
||||
history.append({
|
||||
"role": role,
|
||||
"content": content,
|
||||
"timestamp": time.time(),
|
||||
"timestamp_str": time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
})
|
||||
|
||||
# Mantieni solo gli ultimi 20 messaggi per evitare context overflow
|
||||
if len(history) > 20:
|
||||
self.conversations[user_id] = history[-20:]
|
||||
|
||||
# Salva su file
|
||||
username = self.usernames.get(user_id, "Unknown")
|
||||
self.conversation_manager.save_conversation(user_id, history, username)
|
||||
|
||||
def clear_history(self, user_id: int):
|
||||
"""Cancella la storia di un utente."""
|
||||
self.conversations[user_id] = []
|
||||
self.conversation_manager.delete_conversation(user_id)
|
||||
|
||||
def process_message(self, user_id: int, username: str, text: str) -> str:
|
||||
"""Elabora un messaggio dell'utente."""
|
||||
try:
|
||||
# Salva username
|
||||
self.usernames[user_id] = username
|
||||
|
||||
# Comandi speciali
|
||||
if text.lower() in ['/start', '/help']:
|
||||
return self._get_help_message()
|
||||
@@ -253,6 +370,9 @@ Devi essere veloce, chiaro e capire anche richieste approssimative.
|
||||
if text.lower() == '/status':
|
||||
return self._get_status_message()
|
||||
|
||||
if text.lower() == '/export':
|
||||
return self._export_conversation(user_id)
|
||||
|
||||
# Aggiungi il messaggio alla storia
|
||||
self.add_to_history(user_id, "user", text)
|
||||
|
||||
@@ -285,6 +405,24 @@ Devi essere veloce, chiaro e capire anche richieste approssimative.
|
||||
except Exception as e:
|
||||
return f"❌ Errore nell'elaborazione: {e}"
|
||||
|
||||
def _export_conversation(self, user_id: int) -> str:
|
||||
"""Genera un riepilogo della conversazione."""
|
||||
history = self.get_conversation_history(user_id)
|
||||
|
||||
if not history:
|
||||
return "📭 Non ci sono messaggi nella conversazione."
|
||||
|
||||
username = self.usernames.get(user_id, "Unknown")
|
||||
filepath = self.conversation_manager._get_filepath(user_id)
|
||||
|
||||
msg = f"💾 **Conversazione salvata**\n\n"
|
||||
msg += f"👤 Utente: @{username} (ID: {user_id})\n"
|
||||
msg += f"💬 Messaggi: {len(history)}\n"
|
||||
msg += f"📁 File: {filepath}\n\n"
|
||||
msg += "La conversazione viene salvata automaticamente dopo ogni messaggio."
|
||||
|
||||
return msg
|
||||
|
||||
def _get_help_message(self) -> str:
|
||||
"""Restituisce il messaggio di aiuto."""
|
||||
return """🎛️ **TF5 Mixer Bot**
|
||||
@@ -304,8 +442,11 @@ Controlla il mixer Yamaha TF5 con comandi in linguaggio naturale!
|
||||
/help - Mostra questo messaggio
|
||||
/reset - Cancella la storia della conversazione
|
||||
/status - Mostra stato del sistema
|
||||
/export - Info sul file della conversazione
|
||||
|
||||
Scrivi semplicemente cosa vuoi fare e ci penso io! 🎵"""
|
||||
Scrivi semplicemente cosa vuoi fare e ci penso io! 🎵
|
||||
|
||||
💾 Ogni conversazione viene salvata automaticamente."""
|
||||
|
||||
def _get_status_message(self) -> str:
|
||||
"""Restituisce lo stato del sistema."""
|
||||
@@ -326,7 +467,17 @@ Scrivi semplicemente cosa vuoi fare e ci penso io! 🎵"""
|
||||
users_count = len(self.conversations)
|
||||
total_messages = sum(len(hist) for hist in self.conversations.values())
|
||||
status += f"👥 Utenti attivi: {users_count}\n"
|
||||
status += f"💬 Messaggi totali: {total_messages}"
|
||||
status += f"💬 Messaggi totali: {total_messages}\n\n"
|
||||
|
||||
# Lista utenti con conversazioni salvate
|
||||
all_users = self.conversation_manager.get_all_users()
|
||||
if all_users:
|
||||
status += "📁 **Conversazioni salvate:**\n"
|
||||
for user in all_users[:5]: # Mostra solo i primi 5
|
||||
status += f"• @{user['username']}: {user['message_count']} msg ({user['last_updated']})\n"
|
||||
|
||||
if len(all_users) > 5:
|
||||
status += f"... e altri {len(all_users) - 5} utenti"
|
||||
|
||||
return status
|
||||
|
||||
@@ -355,7 +506,7 @@ Scrivi semplicemente cosa vuoi fare e ci penso io! 🎵"""
|
||||
response = self.process_message(user_id, username, text)
|
||||
|
||||
# Invia la risposta
|
||||
self.bot.send_message(chat_id, response)
|
||||
self.bot.send_message(chat_id, response, parse_mode='MarkdownV2')
|
||||
|
||||
print(f"✅ Risposta inviata a @{username}")
|
||||
|
||||
@@ -386,6 +537,12 @@ Scrivi semplicemente cosa vuoi fare e ci penso io! 🎵"""
|
||||
else:
|
||||
print("\n💾 Cache non disponibile, verrà creata al primo utilizzo")
|
||||
|
||||
# Mostra conversazioni salvate
|
||||
saved_convs = self.conversation_manager.get_all_users()
|
||||
if saved_convs:
|
||||
print(f"\n📁 Conversazioni salvate: {len(saved_convs)}")
|
||||
print(f" Directory: {self.conversation_manager.conversations_dir}")
|
||||
|
||||
print("\n🔄 In attesa di messaggi... (Ctrl+C per terminare)\n")
|
||||
|
||||
try:
|
||||
@@ -409,6 +566,7 @@ Scrivi semplicemente cosa vuoi fare e ci penso io! 🎵"""
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n👋 Bot terminato")
|
||||
print(f"💾 Conversazioni salvate in: {self.conversation_manager.conversations_dir}")
|
||||
|
||||
def close(self):
|
||||
"""Chiude le connessioni."""
|
||||
@@ -426,6 +584,8 @@ def main():
|
||||
help=f'IP del mixer (default: {DEFAULT_HOST})')
|
||||
parser.add_argument('--mixer-port', type=int, default=DEFAULT_PORT,
|
||||
help=f'Porta mixer (default: {DEFAULT_PORT})')
|
||||
parser.add_argument('--conversations-dir', default='conversations',
|
||||
help='Directory per salvare le conversazioni (default: conversations)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -450,7 +610,8 @@ def main():
|
||||
bot = TF5TelegramBot(
|
||||
telegram_token=telegram_token,
|
||||
mixer_host=args.mixer_host,
|
||||
mixer_port=args.mixer_port
|
||||
mixer_port=args.mixer_port,
|
||||
conversations_dir=args.conversations_dir
|
||||
)
|
||||
bot.run()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user