import logging import asyncio from dotenv import load_dotenv import os from telegram import Update, ForceReply from telegram.ext import ( Application, CommandHandler, MessageHandler, ContextTypes, filters, ConversationHandler ) from tools import add_task, list_tasks, edit_task, delete_task from runner import create_session_async, run_agent from db_utils import save_message, load_chat_history, init_db logging.basicConfig( format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO ) logger = logging.getLogger(__name__) load_dotenv() session = None NEW_TASK, EDIT_TASK_NAME, EDIT_TASK_NEW_NAME, DELETE_TASK_NAME, DELETE_TASK_CONFIRM = range(5) async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: menu = ( "Welcome to Task Manager Bot!\n" "Choose a method:\n" "/newtask - Add a new task\n" "/listtask - View all tasks\n" "/edittask - Edit an existing task\n" "/deletetask - Delete a task\n" "/history - Show last 5 chats" ) await update.message.reply_text(menu, reply_markup=ForceReply(selective=True)) async def newtask_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: await update.message.reply_text("Enter task name:") return NEW_TASK async def newtask_save(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: user_id = str(update.effective_user.id) task_name = update.message.text.strip() if task_name: result = add_task(user_id, task_name) await update.message.reply_text(result) else: await update.message.reply_text("Task name cannot be empty. Please try again.") return NEW_TASK return ConversationHandler.END async def listtask(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: user_id = str(update.effective_user.id) result = list_tasks(user_id) await update.message.reply_text(result) async def edittask_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: await update.message.reply_text("Enter the task name you want to edit:") return EDIT_TASK_NAME async def edittask_get_new_name(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: user_id = str(update.effective_user.id) old_name = update.message.text.strip() context.user_data['edit_task_old_name'] = old_name tasks_list = list_tasks(user_id) if old_name not in tasks_list: await update.message.reply_text("Task not found. Please enter a valid task name:") return EDIT_TASK_NAME await update.message.reply_text("Enter the new task name:") return EDIT_TASK_NEW_NAME async def edittask_save(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: user_id = str(update.effective_user.id) new_name = update.message.text.strip() old_name = context.user_data.get('edit_task_old_name') result = edit_task( user_id, old_name, new_name) await update.message.reply_text(result) return ConversationHandler.END async def deletetask_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: await update.message.reply_text("Enter the task name you want to delete:") return DELETE_TASK_NAME async def deletetask_confirm(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: user_id = str(update.effective_user.id) task_name = update.message.text.strip() context.user_data['delete_task_name'] = task_name tasks_list = list_tasks(user_id) if task_name not in tasks_list: await update.message.reply_text("Task not found. Please enter a valid task name:") return DELETE_TASK_NAME await update.message.reply_text(f'Are you sure you want to delete "{task_name}"? If yes, type "yes" else type /cancel') return DELETE_TASK_CONFIRM async def deletetask_finish(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: user_id = str(update.effective_user.id) response = update.message.text.strip().lower() task_name = context.user_data.get('delete_task_name') if response == "yes": result = delete_task(user_id, task_name) await update.message.reply_text(result) else: await update.message.reply_text("Delete cancelled ❌") return ConversationHandler.END async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: await update.message.reply_text("Cancelled ❌") return ConversationHandler.END async def history(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: user_id = str(update.effective_user.id) messages = load_chat_history(user_id, limit=5) if not messages: await update.message.reply_text("No history yet.") return history_text = "\n".join([f"[{m.role}] {m.message}" for m in messages]) await update.message.reply_text("🕑 Your recent chats:\n\n" + history_text) async def handle_free_text(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: user_id = int(update.effective_user.id) user_message = update.message.text.strip() save_message(user_id, "user", user_message) await update.message.reply_text(f"🤖 Thinking... {user_id}") try: response = await run_agent(user_id, session.id, user_message) if not response: response_text = "⚠️ I didn't get any response." elif isinstance(response, dict): response_text = response.get("text") or response.get("message") or str(response) else: response_text = str(response).strip() # if not response_text: # response_text = "⚠️ Sorry, I got an empty reply." save_message(user_id, "bot", response_text) await update.message.reply_text(response_text) except Exception as e: await update.message.reply_text(f"❌ Error: {str(e)}") def main(): global session init_db() loop = asyncio.get_event_loop() session = loop.run_until_complete(create_session_async()) telegram_token = os.getenv("TELEGRAM_BOT_TOKEN") application = Application.builder().token(telegram_token).build() application.add_handler(CommandHandler("start", start)) application.add_handler(CommandHandler("listtask", listtask)) application.add_handler(CommandHandler("history", history)) newtask_conv = ConversationHandler( entry_points=[CommandHandler("newtask", newtask_start)], states={NEW_TASK: [MessageHandler(filters.TEXT & ~filters.COMMAND, newtask_save)]}, fallbacks=[] ) application.add_handler(newtask_conv) edittask_conv = ConversationHandler( entry_points=[CommandHandler("edittask", edittask_start)], states={ EDIT_TASK_NAME: [MessageHandler(filters.TEXT & ~filters.COMMAND, edittask_get_new_name)], EDIT_TASK_NEW_NAME: [MessageHandler(filters.TEXT & ~filters.COMMAND, edittask_save)] }, fallbacks=[] ) application.add_handler(edittask_conv) deletetask_conv = ConversationHandler( entry_points=[CommandHandler("deletetask", deletetask_start)], states={ DELETE_TASK_NAME: [MessageHandler(filters.TEXT & ~filters.COMMAND, deletetask_confirm)], DELETE_TASK_CONFIRM: [MessageHandler(filters.TEXT & ~filters.COMMAND, deletetask_finish)] }, fallbacks=[CommandHandler("cancel", cancel)] ) application.add_handler(deletetask_conv) application.add_handler(CommandHandler("cancel", cancel)) application.add_handler( MessageHandler(filters.TEXT & ~filters.COMMAND, handle_free_text) ) application.run_polling() if __name__ == "__main__": main()