Add Telegram bot support #23

Merged
Berack96 merged 23 commits from 6-telegram-interface into main 2025-10-13 10:49:46 +02:00
3 changed files with 54 additions and 28 deletions
Showing only changes of commit 52e9cb2996 - Show all commits

View File

@@ -3,12 +3,17 @@ from dotenv import load_dotenv
from agno.utils.log import log_info #type: ignore from agno.utils.log import log_info #type: ignore
from app.utils import ChatManager from app.utils import ChatManager
from app.agents import Pipeline from app.agents import Pipeline
from app.utils.telegram_app import BotFunctions
if __name__ == "__main__": # Disabilita TUTTI i log di livello inferiore a WARNING
# Inizializzazioni # La maggior parte arrivano da httpx
load_dotenv() import logging
pipeline = Pipeline() logging.getLogger().setLevel(logging.WARNING)
def gradio_app(pipeline: Pipeline, server: str = "0.0.0.0", port: int = 8000) -> str:
chat = ChatManager() chat = ChatManager()
######################################## ########################################
@@ -73,7 +78,18 @@ if __name__ == "__main__":
save_btn.click(save_current_chat, inputs=None, outputs=None) save_btn.click(save_current_chat, inputs=None, outputs=None)
load_btn.click(load_previous_chat, inputs=None, outputs=[chatbot, chatbot]) load_btn.click(load_previous_chat, inputs=None, outputs=[chatbot, chatbot])
server, port = ("0.0.0.0", 8000) # 0.0.0.0 per accesso esterno (Docker) _app, local, share = demo.launch(server_name=server, server_port=port, quiet=True, prevent_thread_lock=True)
server_log = "localhost" if server == "0.0.0.0" else server log_info(f"UPO AppAI Chat is running on {local} and {share}")
log_info(f"Starting UPO AppAI Chat on http://{server_log}:{port}") # noqa return share
demo.launch(server_name=server, server_port=port, quiet=True)
if __name__ == "__main__":
load_dotenv() # Carica le variabili d'ambiente dal file .env
pipeline = Pipeline()
url = gradio_app(pipeline)
telegram = BotFunctions.create_bot(pipeline, url)
telegram.run_polling()

View File

@@ -1,5 +1,6 @@
from app.utils.market_aggregation import aggregate_history_prices, aggregate_product_info from app.utils.market_aggregation import aggregate_history_prices, aggregate_product_info
from app.utils.wrapper_handler import WrapperHandler from app.utils.wrapper_handler import WrapperHandler
from app.utils.chat_manager import ChatManager from app.utils.chat_manager import ChatManager
from app.utils.telegram_app import BotFunctions
__all__ = ["aggregate_history_prices", "aggregate_product_info", "WrapperHandler", "ChatManager"] __all__ = ["aggregate_history_prices", "aggregate_product_info", "WrapperHandler", "ChatManager", "BotFunctions"]

View File

@@ -1,4 +1,6 @@
import os import os
import json
import httpx
from enum import Enum from enum import Enum
from typing import Any from typing import Any
from agno.utils.log import log_info # type: ignore from agno.utils.log import log_info # type: ignore
@@ -6,6 +8,7 @@ from telegram import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup,
from telegram.constants import ChatAction from telegram.constants import ChatAction
from telegram.ext import Application, CallbackQueryHandler, CommandHandler, ContextTypes, ConversationHandler, ExtBot, JobQueue, MessageHandler, filters from telegram.ext import Application, CallbackQueryHandler, CommandHandler, ContextTypes, ConversationHandler, ExtBot, JobQueue, MessageHandler, filters
from app.agents import AppModels, PredictorStyle from app.agents import AppModels, PredictorStyle
from app.agents.pipeline import Pipeline
# Lo stato cambia in base al valore di ritorno delle funzioni async # Lo stato cambia in base al valore di ritorno delle funzioni async
# END state è già definito in telegram.ext.ConversationHandler # END state è già definito in telegram.ext.ConversationHandler
@@ -29,9 +32,9 @@ class ConfigsChat(Enum):
class ConfigsRun: class ConfigsRun:
def __init__(self): def __init__(self):
self.model_team = BotFunctions.app_models[0] self.model_team = BotFunctions.pipeline.available_models[0]
self.model_output = BotFunctions.app_models[0] self.model_output = BotFunctions.pipeline.available_models[0]
self.strategy = PredictorStyle.CONSERVATIVE self.strategy = BotFunctions.pipeline.all_styles[0]
self.user_query = "" self.user_query = ""
@@ -39,13 +42,12 @@ class ConfigsRun:
class BotFunctions: class BotFunctions:
# In theory this is already thread-safe if run with CPython # In theory this is already thread-safe if run with CPython
users_req: dict[User, ConfigsRun] = {} users_req: dict[User, ConfigsRun]
app_models: list[AppModels] = AppModels.availables() pipeline: Pipeline
strategies: list[PredictorStyle] = list(PredictorStyle)
# che incubo di typing # che incubo di typing
@staticmethod @staticmethod
def create_bot() -> Application[ExtBot[None], ContextTypes.DEFAULT_TYPE, dict[str, Any], dict[str, Any], dict[str, Any], JobQueue[ContextTypes.DEFAULT_TYPE]]: def create_bot(pipeline: Pipeline, miniapp_url: str | None = None) -> Application[ExtBot[None], ContextTypes.DEFAULT_TYPE, dict[str, Any], dict[str, Any], dict[str, Any], JobQueue[ContextTypes.DEFAULT_TYPE]]:
""" """
Create a Telegram bot application instance. Create a Telegram bot application instance.
Assumes the TELEGRAM_BOT_TOKEN environment variable is set. Assumes the TELEGRAM_BOT_TOKEN environment variable is set.
@@ -54,10 +56,13 @@ class BotFunctions:
Raises: Raises:
AssertionError: If the TELEGRAM_BOT_TOKEN environment variable is not set. AssertionError: If the TELEGRAM_BOT_TOKEN environment variable is not set.
""" """
BotFunctions.users_req = {}
BotFunctions.pipeline = pipeline
token = os.getenv("TELEGRAM_BOT_TOKEN", '') token = os.getenv("TELEGRAM_BOT_TOKEN", '')
assert token, "TELEGRAM_BOT_TOKEN environment variable not set" assert token, "TELEGRAM_BOT_TOKEN environment variable not set"
if miniapp_url: BotFunctions.update_miniapp_url(miniapp_url, token)
app = Application.builder().token(token).build() app = Application.builder().token(token).build()
conv_handler = ConversationHandler( conv_handler = ConversationHandler(
@@ -113,7 +118,7 @@ class BotFunctions:
async def handle_configs(update: Update, state: ConfigsChat, msg: str | None = None) -> int: async def handle_configs(update: Update, state: ConfigsChat, msg: str | None = None) -> int:
query, _ = await BotFunctions.handle_callbackquery(update) query, _ = await BotFunctions.handle_callbackquery(update)
models = [(m.name, f"__select_config:{state}:{m.name}") for m in BotFunctions.app_models] models = [(m.name, f"__select_config:{state}:{m.name}") for m in BotFunctions.pipeline.available_models]
inline_btns = [[InlineKeyboardButton(name, callback_data=callback_data)] for name, callback_data in models] inline_btns = [[InlineKeyboardButton(name, callback_data=callback_data)] for name, callback_data in models]
await query.edit_message_text(msg or state.value, reply_markup=InlineKeyboardMarkup(inline_btns)) await query.edit_message_text(msg or state.value, reply_markup=InlineKeyboardMarkup(inline_btns))
@@ -131,6 +136,20 @@ class BotFunctions:
assert update.message and update.message.from_user, "Update message or user is None" assert update.message and update.message.from_user, "Update message or user is None"
return update.message, update.message.from_user return update.message, update.message.from_user
@staticmethod
def update_miniapp_url(url: str, token: str) -> None:
try:
endpoint = f"https://api.telegram.org/bot{token}/setChatMenuButton"
payload = {"menu_button": json.dumps({
"type": "web_app",
"text": "Apri Mini App", # Il testo che appare sul pulsante
"web_app": {
"url": url
}
})}
httpx.post(endpoint, data=payload)
except httpx.HTTPError as e:
log_info(f"Failed to update mini app URL: {e}")
######################################### #########################################
# Funzioni async per i comandi e messaggi # Funzioni async per i comandi e messaggi
@@ -154,7 +173,7 @@ class BotFunctions:
async def __strategy(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: async def __strategy(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
query, _ = await BotFunctions.handle_callbackquery(update) query, _ = await BotFunctions.handle_callbackquery(update)
strategies = [(s.name, f"__select_config:{ConfigsChat.STRATEGY}:{s.name}") for s in BotFunctions.strategies] strategies = [(s.name, f"__select_config:{ConfigsChat.STRATEGY}:{s.name}") for s in BotFunctions.pipeline.all_styles]
inline_btns = [[InlineKeyboardButton(name, callback_data=callback_data)] for name, callback_data in strategies] inline_btns = [[InlineKeyboardButton(name, callback_data=callback_data)] for name, callback_data in strategies]
await query.edit_message_text("Select a strategy", reply_markup=InlineKeyboardMarkup(inline_btns)) await query.edit_message_text("Select a strategy", reply_markup=InlineKeyboardMarkup(inline_btns))
@@ -238,13 +257,3 @@ class BotFunctions:
document = io.BytesIO(report_content.encode('utf-8')) document = io.BytesIO(report_content.encode('utf-8'))
await bot.send_document(chat_id=chat_id, document=document, filename="report.md", parse_mode='MarkdownV2', caption=full_message) await bot.send_document(chat_id=chat_id, document=document, filename="report.md", parse_mode='MarkdownV2', caption=full_message)
if __name__ == "__main__":
from dotenv import load_dotenv
load_dotenv()
bot_app = BotFunctions.create_bot()
bot_app.run_polling()