mirror of
https://github.com/DerrtSML/qbittorent_bot.git
synced 2025-10-28 13:30:09 +03:00
Update bot.py
This commit is contained in:
parent
2b488c3d4e
commit
33f24f6d13
124
bot.py
124
bot.py
@ -79,7 +79,7 @@ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|||||||
"Используй /help для списка команд."
|
"Используй /help для списка команд."
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- Команда /help (МАКСИМАЛЬНО УПРОЩЕНО ФОРМАТИРОВАНИЕ) ---
|
# --- Команда /help ---
|
||||||
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
if update.message is None:
|
if update.message is None:
|
||||||
logger.warning("Received an update without a message object in help handler.")
|
logger.warning("Received an update without a message object in help handler.")
|
||||||
@ -96,12 +96,10 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
|
|||||||
"Также вы можете отправить мне magnet-ссылку или URL torrent-файла "
|
"Также вы можете отправить мне magnet-ссылку или URL torrent-файла "
|
||||||
"для добавления загрузки. Бот предложит выбрать категорию и директорию."
|
"для добавления загрузки. Бот предложит выбрать категорию и директорию."
|
||||||
)
|
)
|
||||||
# Используем parse_mode="HTML" или вообще не указываем его, чтобы избежать проблем с Markdown
|
|
||||||
# Для самого простого текста, можно вообще убрать parse_mode
|
|
||||||
await update.message.reply_text(help_text)
|
await update.message.reply_text(help_text)
|
||||||
|
|
||||||
|
|
||||||
# --- Команда /status ---
|
# --- Команда /status (ОБНОВЛЕНО с общей статистикой и кнопками удаления) ---
|
||||||
async def status(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
async def status(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
if update.message is None:
|
if update.message is None:
|
||||||
logger.warning("Received an update without a message object in status handler.")
|
logger.warning("Received an update without a message object in status handler.")
|
||||||
@ -116,7 +114,44 @@ async def status(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Получаем общую статистику qBittorrent
|
||||||
|
global_transfer_info = qb.transfer_info()
|
||||||
|
|
||||||
|
global_dlspeed_bytes = global_transfer_info.dl_info_speed
|
||||||
|
global_upspeed_bytes = global_transfer_info.up_info_speed
|
||||||
|
|
||||||
|
# Форматирование скорости
|
||||||
|
def format_speed(speed_bytes):
|
||||||
|
if speed_bytes >= (1024 * 1024):
|
||||||
|
return f"{speed_bytes / (1024 * 1024):.2f} MB/s"
|
||||||
|
elif speed_bytes >= 1024:
|
||||||
|
return f"{speed_bytes / 1024:.2f} KB/s"
|
||||||
|
else:
|
||||||
|
return f"{speed_bytes} B/s"
|
||||||
|
|
||||||
|
global_dlspeed_formatted = format_speed(global_dlspeed_bytes)
|
||||||
|
global_upspeed_formatted = format_speed(global_upspeed_bytes)
|
||||||
|
|
||||||
torrents = qb.torrents_info()
|
torrents = qb.torrents_info()
|
||||||
|
|
||||||
|
# Считаем торренты по состояниям
|
||||||
|
active_count = sum(1 for t in torrents if t.state in ['downloading', 'stalledDL', 'uploading', 'checkingQT', 'queuedDL', 'checkingUP', 'queuedUP'])
|
||||||
|
paused_count = sum(1 for t in torrents if t.state in ['pausedDL', 'pausedUP', 'stoppedUP', 'stoppedDL'])
|
||||||
|
# completed и seeding - состояния завершенных загрузок
|
||||||
|
completed_count = sum(1 for t in torrents if t.state in ['completed', 'seeding', 'stalledUP'])
|
||||||
|
|
||||||
|
summary_text = (
|
||||||
|
f"⚡️ *Общий статус qBittorrent*\n"
|
||||||
|
f" ⬇️ Общая скорость загрузки: {global_dlspeed_formatted}\n"
|
||||||
|
f" ⬆️ Общая скорость отдачи: {global_upspeed_formatted}\n"
|
||||||
|
f" Активных торрентов: {active_count}\n"
|
||||||
|
f" На паузе/Остановлено: {paused_count}\n"
|
||||||
|
f" Завершено/Раздается: {completed_count}\n"
|
||||||
|
f"---\n"
|
||||||
|
)
|
||||||
|
await update.message.reply_text(summary_text, parse_mode="Markdown")
|
||||||
|
|
||||||
|
|
||||||
if not torrents:
|
if not torrents:
|
||||||
await update.message.reply_text("Загрузок не найдено.")
|
await update.message.reply_text("Загрузок не найдено.")
|
||||||
return
|
return
|
||||||
@ -138,6 +173,8 @@ async def status(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|||||||
eta_str = f"{int(hours)}ч {int(minutes)}мин"
|
eta_str = f"{int(hours)}ч {int(minutes)}мин"
|
||||||
elif minutes > 0:
|
elif minutes > 0:
|
||||||
eta_str = f"{int(minutes)}мин {int(seconds)}с"
|
eta_str = f"{int(minutes)}мин {int(seconds)}с"
|
||||||
|
else:
|
||||||
|
eta_str = f"{int(seconds)}с"
|
||||||
else:
|
else:
|
||||||
eta_str = "Завершено"
|
eta_str = "Завершено"
|
||||||
|
|
||||||
@ -151,26 +188,27 @@ async def status(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|||||||
f" Размер: {(torrent.size / (1024*1024*1024)):.2f} ГБ"
|
f" Размер: {(torrent.size / (1024*1024*1024)):.2f} ГБ"
|
||||||
)
|
)
|
||||||
|
|
||||||
keyboard = []
|
control_buttons = []
|
||||||
active_states = [
|
if torrent.state in ['downloading', 'stalledDL', 'uploading', 'checkingQT', 'queuedDL', 'checkingUP', 'queuedUP']:
|
||||||
'downloading', 'stalledDL', 'uploading', 'checkingQT',
|
control_buttons.append(InlineKeyboardButton("🔴 Остановить", callback_data=f"stop_hash_{torrent.hash}"))
|
||||||
'queuedDL', 'checkingUP', 'queuedUP'
|
elif torrent.state in ['pausedDL', 'pausedUP', 'stoppedUP', 'stoppedDL']:
|
||||||
]
|
control_buttons.append(InlineKeyboardButton("▶️ Запустить", callback_data=f"start_hash_{torrent.hash}"))
|
||||||
paused_states = [
|
|
||||||
'pausedDL', 'pausedUP', 'stoppedUP', 'stoppedDL'
|
|
||||||
]
|
|
||||||
|
|
||||||
if torrent.state in active_states:
|
|
||||||
keyboard.append(InlineKeyboardButton("🔴 Остановить", callback_data=f"stop_hash_{torrent.hash}"))
|
|
||||||
elif torrent.state in paused_states:
|
|
||||||
keyboard.append(InlineKeyboardButton("▶️ Запустить", callback_data=f"start_hash_{torrent.hash}"))
|
|
||||||
elif torrent.state == 'metaDL':
|
elif torrent.state == 'metaDL':
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
keyboard.append(InlineKeyboardButton("ℹ️ Неизвестное состояние", callback_data=f"info_hash_{torrent.hash}"))
|
control_buttons.append(InlineKeyboardButton("ℹ️ Неизвестное состояние", callback_data=f"info_hash_{torrent.hash}"))
|
||||||
|
|
||||||
|
delete_buttons = [
|
||||||
|
InlineKeyboardButton("🗑️ Удалить (без файлов)", callback_data=f"delete_hash_{torrent.hash}_false"),
|
||||||
|
InlineKeyboardButton("❌ Удалить (с файлами)", callback_data=f"delete_hash_{torrent.hash}_true")
|
||||||
|
]
|
||||||
|
|
||||||
reply_markup = InlineKeyboardMarkup([keyboard]) if keyboard else None
|
keyboard_rows = []
|
||||||
|
if control_buttons: # Добавляем кнопки управления, если они есть
|
||||||
|
keyboard_rows.append(control_buttons)
|
||||||
|
keyboard_rows.append(delete_buttons) # Всегда добавляем кнопки удаления
|
||||||
|
|
||||||
|
reply_markup = InlineKeyboardMarkup(keyboard_rows) if keyboard_rows else None
|
||||||
|
|
||||||
await update.message.reply_text(
|
await update.message.reply_text(
|
||||||
message_text,
|
message_text,
|
||||||
@ -247,6 +285,35 @@ async def start_torrent_callback(update: Update, context: ContextTypes.DEFAULT_T
|
|||||||
logger.error(f"An unexpected error occurred during torrent resuming: {e}")
|
logger.error(f"An unexpected error occurred during torrent resuming: {e}")
|
||||||
await query.edit_message_text(f"Произошла непредвиденная ошибка: {e}")
|
await query.edit_message_text(f"Произошла непредвиденная ошибка: {e}")
|
||||||
|
|
||||||
|
# --- Обработка кнопки "Удалить торрент" ---
|
||||||
|
async def delete_torrent_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
|
query = update.callback_query
|
||||||
|
await query.answer()
|
||||||
|
|
||||||
|
parts = query.data.split('_')
|
||||||
|
torrent_hash = parts[2]
|
||||||
|
delete_files_str = parts[3]
|
||||||
|
delete_files = True if delete_files_str == 'true' else False
|
||||||
|
|
||||||
|
logger.info(f"Attempting to delete torrent with hash: {torrent_hash}, delete_files: {delete_files}")
|
||||||
|
|
||||||
|
if not init_qbittorrent_client():
|
||||||
|
await query.edit_message_text(
|
||||||
|
"Не удалось подключиться к qBittorrent. Проверьте переменные окружения и доступность сервера."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
qb.torrents_delete(torrent_hashes=torrent_hash, delete_files=delete_files)
|
||||||
|
action_text = "с файлами" if delete_files else "без файлов"
|
||||||
|
await query.edit_message_text(f"Торрент ({torrent_hash[:6]}...) успешно удален {action_text}.")
|
||||||
|
except APIError as e:
|
||||||
|
logger.error(f"Error deleting torrent {torrent_hash}: {e}")
|
||||||
|
await query.edit_message_text(f"Ошибка при удалении торрента: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"An unexpected error occurred during torrent deletion: {e}")
|
||||||
|
await query.edit_message_text(f"Произошла непредвиденная ошибка при удалении торрента: {e}")
|
||||||
|
|
||||||
|
|
||||||
# --- Обработка magnet-ссылок и URL ---
|
# --- Обработка magnet-ссылок и URL ---
|
||||||
async def handle_url(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
async def handle_url(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
@ -339,7 +406,7 @@ async def select_directory_callback(update: Update, context: ContextTypes.DEFAUL
|
|||||||
await query.answer()
|
await query.answer()
|
||||||
|
|
||||||
data = query.data.split('_')
|
data = query.data.split('_')
|
||||||
selected_directory = data[2]
|
selected_directory = "_".join(data[2:])
|
||||||
|
|
||||||
logger.info(f"Selected directory: {selected_directory}")
|
logger.info(f"Selected directory: {selected_directory}")
|
||||||
|
|
||||||
@ -357,6 +424,11 @@ async def select_directory_callback(update: Update, context: ContextTypes.DEFAUL
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
logger.info(f"Attempting to add torrent:")
|
||||||
|
logger.info(f" URL: {torrent_url}")
|
||||||
|
logger.info(f" Category: {category or 'None'}")
|
||||||
|
logger.info(f" Save Path: {selected_directory}")
|
||||||
|
|
||||||
qb.torrents_add(
|
qb.torrents_add(
|
||||||
urls=torrent_url,
|
urls=torrent_url,
|
||||||
category=category,
|
category=category,
|
||||||
@ -372,11 +444,11 @@ async def select_directory_callback(update: Update, context: ContextTypes.DEFAUL
|
|||||||
if 'selected_category' in context.user_data:
|
if 'selected_category' in context.user_data:
|
||||||
del context.user_data['selected_category']
|
del context.user_data['selected_category']
|
||||||
except APIError as e:
|
except APIError as e:
|
||||||
logger.error(f"Error adding torrent with path: {e}")
|
logger.error(f"API Error adding torrent: {e}")
|
||||||
await query.edit_message_text(f"Ошибка при добавлении торрента: {e}")
|
await query.edit_message_text(f"Ошибка API при добавлении торрента: {e}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"An unexpected error occurred during torrent addition with path: {e}")
|
logger.error(f"An unexpected error occurred during torrent addition: {e}")
|
||||||
await query.edit_message_text(f"Произошла непредвиденная ошибка: {e}")
|
await query.edit_message_text(f"Произошла непредвиденная ошибка при добавлении торрента: {e}")
|
||||||
|
|
||||||
|
|
||||||
# --- Обработчик ошибок Telegram Bot API ---
|
# --- Обработчик ошибок Telegram Bot API ---
|
||||||
@ -430,6 +502,8 @@ def main() -> None:
|
|||||||
application.add_handler(CallbackQueryHandler(stop_torrent_callback, pattern=r"^stop_hash_.*"))
|
application.add_handler(CallbackQueryHandler(stop_torrent_callback, pattern=r"^stop_hash_.*"))
|
||||||
# CallbackQueryHandler для кнопок запуска торрентов
|
# CallbackQueryHandler для кнопок запуска торрентов
|
||||||
application.add_handler(CallbackQueryHandler(start_torrent_callback, pattern=r"^start_hash_.*"))
|
application.add_handler(CallbackQueryHandler(start_torrent_callback, pattern=r"^start_hash_.*"))
|
||||||
|
# НОВЫЙ CallbackQueryHandler для кнопок удаления торрентов
|
||||||
|
application.add_handler(CallbackQueryHandler(delete_torrent_callback, pattern=r"^delete_hash_.*"))
|
||||||
|
|
||||||
|
|
||||||
# --- Обработчик для любого другого текста, не являющегося командой ---
|
# --- Обработчик для любого другого текста, не являющегося командой ---
|
||||||
@ -439,7 +513,7 @@ def main() -> None:
|
|||||||
application.add_handler(MessageHandler(filters.COMMAND, unknown_command))
|
application.add_handler(MessageHandler(filters.COMMAND, unknown_command))
|
||||||
|
|
||||||
# --- Добавление обработчика ошибок ---
|
# --- Добавление обработчика ошибок ---
|
||||||
application.add_error_handler(error_handler)
|
application.add_handler(application.add_error_handler(error_handler)) # Исправлено: handler_manager.add_error_handler expects a handler, not its return value
|
||||||
|
|
||||||
# --- Запуск бота ---
|
# --- Запуск бота ---
|
||||||
logger.info("Bot started polling...")
|
logger.info("Bot started polling...")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user