From 3534fc5b79d52558efc2a3ee6240a075a88ed478 Mon Sep 17 00:00:00 2001 From: DerrtSML <93052047+DerrtSML@users.noreply.github.com> Date: Mon, 23 Jun 2025 19:48:52 +0300 Subject: [PATCH] Update bot.py --- bot.py | 127 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 76 insertions(+), 51 deletions(-) diff --git a/bot.py b/bot.py index b1d966f..64f6f53 100644 --- a/bot.py +++ b/bot.py @@ -70,7 +70,6 @@ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: ) return - # В этом сообщении нет Markdown, поэтому parse_mode не нужен await update.message.reply_text( "Привет! Я бот для управления qBittorrent.\n" "Отправь мне magnet-ссылку или URL torrent-файла, " @@ -80,7 +79,7 @@ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: "Используй /help для списка команд." ) -# --- Команда /help (без Markdown) --- +# --- Команда /help --- async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: if update.message is None: logger.warning("Received an update without a message object in help handler.") @@ -88,20 +87,19 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No logger.info(f"Received /help command from {update.effective_user.id}") - # Здесь Markdown был удален для отладки help_text = ( "Вот список доступных команд:\n\n" - "/start - Начать работу с ботом и проверить подключение к qBittorrent.\n" - "/status - Показать текущий статус всех активных загрузок.\n" - "/stop_torrent - Выбрать и остановить загрузку торрента.\n" - "/help - Показать это справочное сообщение.\n\n" - "Также вы можете отправить мне magnet-ссылку или URL torrent-файла " + "**/start** - Начать работу с ботом и проверить подключение к qBittorrent.\n" + "**/status** - Показать текущий статус всех активных загрузок и управлять ими.\n" # Обновил описание + "**/stop_torrent** - Выбрать и остановить загрузку торрента (устаревает, используйте /status).\n" # Отметил как устаревшую + "**/help** - Показать это справочное сообщение.\n\n" + "Также вы можете отправить мне *magnet-ссылку* или *URL torrent-файла* " "для добавления загрузки. Бот предложит выбрать категорию и директорию." ) - await update.message.reply_text(help_text) # parse_mode="Markdown" удален + await update.message.reply_text(help_text, parse_mode="Markdown") -# --- Команда /status (без Markdown) --- +# --- Команда /status (обновлено для кнопок старт/стоп) --- async def status(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: if update.message is None: logger.warning("Received an update without a message object in status handler.") @@ -121,7 +119,6 @@ async def status(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: await update.message.reply_text("Загрузок не найдено.") return - status_messages = [] for torrent in torrents: download_speed = torrent.dlspeed / (1024 * 1024) if torrent.dlspeed >= (1024 * 1024) else torrent.dlspeed / 1024 upload_speed = torrent.upspeed / (1024 * 1024) if torrent.upspeed >= (1024 * 1024) else torrent.upspeed / 1024 @@ -130,7 +127,7 @@ async def status(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: up_unit = "MB/s" if torrent.upspeed >= (1024 * 1024) else "KB/s" eta_str = "" - if torrent.eta == 8640000: + if torrent.eta == 8640000: # qBittorrent returns 8640000 for "infinite" ETA eta_str = "∞" elif torrent.eta > 0: hours, remainder = divmod(torrent.eta, 3600) @@ -145,19 +142,39 @@ async def status(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: eta_str = "Завершено" - # Здесь Markdown был удален для отладки (включая *) - status_messages.append( - f"📊 {torrent.name}\n" + message_text = ( + f"📊 *{torrent.name}*\n" f" Состояние: {torrent.state}\n" f" Прогресс: {torrent.progress:.2%}\n" f" ⬇️ {download_speed:.2f} {dl_unit} ⬆️ {upload_speed:.2f} {up_unit}\n" f" ETA: {eta_str}\n" f" Размер: {(torrent.size / (1024*1024*1024)):.2f} ГБ" ) - - await update.message.reply_text( - "\n\n".join(status_messages) - ) # parse_mode="Markdown" удален + + keyboard = [] + # Проверяем состояние торрента для отображения нужной кнопки + if torrent.state in ['downloading', 'stalledDL', 'uploading', 'checkingQT', 'queuedDL', 'checkingUP', 'pausedDL', 'queuedUP']: + # Если торрент активен или на паузе (но не в остановленном состоянии "pausedUP"), предлагаем остановить + keyboard.append(InlineKeyboardButton("🔴 Остановить", callback_data=f"stop_hash_{torrent.hash}")) + elif torrent.state == 'pausedUP': # Это состояние, когда торрент завершен, но на паузе. + keyboard.append(InlineKeyboardButton("▶️ Запустить", callback_data=f"start_hash_{torrent.hash}")) + elif torrent.state == 'metaDL': # Метаданные загружаются + # Пока не предлагаем кнопок, пока не начнется фактическая загрузка + pass # Можно добавить кнопку "Отменить" позже + else: + # Для других состояний, таких как 'error', 'missingFiles', 'unknown' + # или для тех, что не подразумевают активного управления через старт/стоп + keyboard.append(InlineKeyboardButton("❓ Неизвестное состояние", callback_data=f"info_hash_{torrent.hash}")) + + + reply_markup = InlineKeyboardMarkup([keyboard]) if keyboard else None + + # Отправляем каждое сообщение о торренте отдельно с его кнопками + await update.message.reply_text( + message_text, + parse_mode="Markdown", + reply_markup=reply_markup + ) except APIError as e: logger.error(f"Error getting torrent status: {e}") @@ -166,7 +183,9 @@ async def status(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: logger.error(f"An unexpected error occurred in status command: {e}") await update.message.reply_text(f"Произошла непредвиденная ошибка: {e}") -# --- Команда /stop_torrent --- + +# --- Команда /stop_torrent (Теперь это будет устаревшая команда) --- +# Ее можно удалить позже, когда все привыкнут к кнопкам в /status async def stop_torrent(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: if update.message is None: logger.warning("Received an update without a message object in stop_torrent handler.") @@ -174,35 +193,14 @@ async def stop_torrent(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No logger.info(f"Received /stop_torrent command from {update.effective_user.id}") - if not init_qbittorrent_client(): - await update.message.reply_text( - "Не удалось подключиться к qBittorrent. Проверьте переменные окружения и доступность сервера." - ) - return + await update.message.reply_text( + "Используйте команду /status для управления торрентами через кнопки 'Запустить' и 'Остановить'." + ) + # Если вы хотите полностью убрать эту команду, можете закомментировать или удалить ее + # и убрать из add_handler в main() + # Остальной код этой функции может быть полезен, если вы все же хотите сохранить старый механизм. + # Но для удобства, я предлагаю перенаправить пользователя на /status. - try: - torrents = qb.torrents_info(status_filter='downloading') - if not torrents: - await update.message.reply_text("Нет активных загрузок для остановки.") - return - - keyboard = [] - for torrent in torrents: - display_name = torrent.name - if len(display_name) > 40: - display_name = display_name[:37] + "..." - - keyboard.append([InlineKeyboardButton(display_name, callback_data=f"stop_hash_{torrent.hash}")]) - - reply_markup = InlineKeyboardMarkup(keyboard) - await update.message.reply_text("Выберите торрент для остановки:", reply_markup=reply_markup) - - except APIError as e: - logger.error(f"Error fetching torrents for stopping: {e}") - await update.message.reply_text(f"Ошибка при получении списка торрентов для остановки: {e}") - except Exception as e: - logger.error(f"An unexpected error occurred in stop_torrent command: {e}") - await update.message.reply_text(f"Произошла непредвиденная ошибка: {e}") # --- Обработка кнопки "Остановить торрент" --- async def stop_torrent_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: @@ -219,8 +217,8 @@ async def stop_torrent_callback(update: Update, context: ContextTypes.DEFAULT_TY return try: - qb.torrents_stop(torrent_hashes=torrent_hash) - await query.edit_message_text(f"Торрент ({torrent_hash[:6]}...) успешно остановлен.") + qb.torrents_pause(torrent_hashes=torrent_hash) # Используем pause вместо stop для возможности возобновления + await query.edit_message_text(f"Торрент ({torrent_hash[:6]}...) успешно остановлен (поставлен на паузу).") except APIError as e: logger.error(f"Error stopping torrent {torrent_hash}: {e}") await query.edit_message_text(f"Ошибка при остановке торрента: {e}") @@ -228,6 +226,31 @@ async def stop_torrent_callback(update: Update, context: ContextTypes.DEFAULT_TY logger.error(f"An unexpected error occurred during torrent stopping: {e}") await query.edit_message_text(f"Произошла непредвиденная ошибка: {e}") +# --- НОВАЯ функция: Обработка кнопки "Запустить торрент" --- +async def start_torrent_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + query = update.callback_query + await query.answer() + + torrent_hash = query.data.replace("start_hash_", "") + logger.info(f"Attempting to start torrent with hash: {torrent_hash}") + + if not init_qbittorrent_client(): + await query.edit_message_text( + "Не удалось подключиться к qBittorrent. Проверьте переменные окружения и доступность сервера." + ) + return + + try: + qb.torrents_resume(torrent_hashes=torrent_hash) + await query.edit_message_text(f"Торрент ({torrent_hash[:6]}...) успешно запущен (возобновлен).") + except APIError as e: + logger.error(f"Error starting torrent {torrent_hash}: {e}") + await query.edit_message_text(f"Ошибка при запуске торрента: {e}") + except Exception as e: + logger.error(f"An unexpected error occurred during torrent starting: {e}") + await query.edit_message_text(f"Произошла непредвиденная ошибка: {e}") + + # --- Обработка magnet-ссылок и URL --- async def handle_url(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: if update.message is None: @@ -406,8 +429,10 @@ def main() -> None: application.add_handler(CallbackQueryHandler(select_category_callback, pattern=r"^select_category_.*")) # Добавляем CallbackQueryHandler для кнопок выбора директории application.add_handler(CallbackQueryHandler(select_directory_callback, pattern=r"^select_dir_.*")) - # Новый CallbackQueryHandler для кнопок остановки торрентов + # ОБНОВЛЕНИЕ: CallbackQueryHandler для кнопок остановки торрентов application.add_handler(CallbackQueryHandler(stop_torrent_callback, pattern=r"^stop_hash_.*")) + # НОВЫЙ CallbackQueryHandler для кнопок запуска торрентов + application.add_handler(CallbackQueryHandler(start_torrent_callback, pattern=r"^start_hash_.*")) # --- Обработчик для любого другого текста, не являющегося командой ---