mirror of
https://github.com/DerrtSML/qbittorent_bot.git
synced 2025-10-26 04:20:08 +03:00
Update bot.py
This commit is contained in:
parent
217fd52b34
commit
2808b35310
204
bot.py
204
bot.py
@ -174,17 +174,18 @@ async def handle_url(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Получаем список категорий из qBittorrent
|
# Сохраняем URL торрента в user_data контекста, чтобы использовать его позже
|
||||||
categories = qb.categories.info()
|
context.user_data['current_torrent_url'] = text
|
||||||
keyboard = []
|
|
||||||
for category_name in categories.keys():
|
|
||||||
# Формируем данные для кнопки: "add_URL_CATEGORYNAME"
|
|
||||||
keyboard.append([InlineKeyboardButton(category_name, callback_data=f"add_{text}_{category_name}")])
|
|
||||||
|
|
||||||
# Добавляем кнопку для "Без категории"
|
|
||||||
keyboard.append([InlineKeyboardButton("Без категории", callback_data=f"add_{text}_no_category")])
|
|
||||||
|
|
||||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
# Запрашиваем категории
|
||||||
|
categories = qb.categories.info()
|
||||||
|
category_keyboard = []
|
||||||
|
for category_name in categories.keys():
|
||||||
|
category_keyboard.append([InlineKeyboardButton(category_name, callback_data=f"select_category_{category_name}")])
|
||||||
|
|
||||||
|
category_keyboard.append([InlineKeyboardButton("Без категории", callback_data="select_category_no_category")])
|
||||||
|
|
||||||
|
reply_markup = InlineKeyboardMarkup(category_keyboard)
|
||||||
await update.message.reply_text('Выберите категорию для загрузки:', reply_markup=reply_markup)
|
await update.message.reply_text('Выберите категорию для загрузки:', reply_markup=reply_markup)
|
||||||
|
|
||||||
except APIError as e:
|
except APIError as e:
|
||||||
@ -195,18 +196,88 @@ async def handle_url(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None
|
|||||||
await update.message.reply_text(f"Произошла непредвиденная ошибка: {e}")
|
await update.message.reply_text(f"Произошла непредвиденная ошибка: {e}")
|
||||||
|
|
||||||
|
|
||||||
# --- Обработка нажатий Inline-кнопок ---
|
# --- Обработка выбора категории ---
|
||||||
async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
async def select_category_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
query = update.callback_query
|
query = update.callback_query
|
||||||
await query.answer() # Обязательно ответьте на запрос обратного вызова, чтобы он не зависал
|
await query.answer()
|
||||||
|
|
||||||
data = query.data.split('_')
|
data = query.data.split('_')
|
||||||
action = data[0]
|
# action = data[0] # select
|
||||||
torrent_url = data[1]
|
# type = data[1] # category
|
||||||
# Если категория не указана (например, "no_category"), устанавливаем ее в None
|
selected_category = data[2] if len(data) > 2 and data[2] != 'no_category' else None
|
||||||
category = data[2] if len(data) > 2 and data[2] != 'no_category' else None
|
|
||||||
|
|
||||||
logger.info(f"Button callback - Action: {action}, URL: {torrent_url}, Category: {category}")
|
logger.info(f"Selected category: {selected_category}")
|
||||||
|
|
||||||
|
# Сохраняем выбранную категорию
|
||||||
|
context.user_data['selected_category'] = selected_category
|
||||||
|
|
||||||
|
# Теперь предложим выбор директорий
|
||||||
|
await query.edit_message_text("Выберите директорию загрузки:")
|
||||||
|
await send_directory_options(query, context)
|
||||||
|
|
||||||
|
# --- Отправка опций директорий ---
|
||||||
|
async def send_directory_options(query, context):
|
||||||
|
try:
|
||||||
|
# Получаем пути сохранения из qBittorrent
|
||||||
|
# qbittorrentapi.app.default_save_path - это дефолтная директория
|
||||||
|
# qbittorrentapi.app.preferences().save_path - это та же дефолтная директория,
|
||||||
|
# но если хотим показать другие, нам нужно их получить откуда-то еще,
|
||||||
|
# например, из существующих торрентов или заранее заданного списка.
|
||||||
|
# Для простоты пока используем дефолтный и какой-нибудь пример.
|
||||||
|
|
||||||
|
# !!! ВНИМАНИЕ: qBittorrent API не предоставляет список "доступных" директорий автоматически.
|
||||||
|
# Вам нужно будет вручную указать те директории, которые вы хотите предложить пользователю.
|
||||||
|
# Здесь приведен ПРИМЕР, как это можно сделать.
|
||||||
|
# ЗАМЕНИТЕ ЭТОТ СПИСОК СВОИМИ АКТУАЛЬНЫМИ ПУТЯМИ!
|
||||||
|
available_paths = [
|
||||||
|
qb.app.default_save_path, # Дефолтный путь qBittorrent
|
||||||
|
"/downloads/movies",
|
||||||
|
"/downloads/series",
|
||||||
|
"/downloads/other",
|
||||||
|
# Добавьте здесь другие пути, если они у вас есть
|
||||||
|
]
|
||||||
|
|
||||||
|
# Удаляем дубликаты и пустые пути
|
||||||
|
available_paths = list(set([p for p in available_paths if p]))
|
||||||
|
|
||||||
|
directory_keyboard = []
|
||||||
|
for path in available_paths:
|
||||||
|
# path.replace(os.sep, '/') для кроссплатформенности и читаемости
|
||||||
|
display_path = path.replace(os.sep, '/')
|
||||||
|
directory_keyboard.append([InlineKeyboardButton(display_path, callback_data=f"select_dir_{path}")])
|
||||||
|
|
||||||
|
# Опция для использования дефолтной директории qBittorrent (если пользователь не хочет выбирать)
|
||||||
|
directory_keyboard.append([InlineKeyboardButton("Использовать дефолтную qBittorrent", callback_data=f"select_dir_{qb.app.default_save_path}")])
|
||||||
|
|
||||||
|
reply_markup = InlineKeyboardMarkup(directory_keyboard)
|
||||||
|
await query.edit_message_reply_markup(reply_markup=reply_markup) # Изменяем только разметку, чтобы сообщение осталось
|
||||||
|
|
||||||
|
except APIError as e:
|
||||||
|
logger.error(f"Error fetching default save path: {e}")
|
||||||
|
await query.edit_message_text(f"Ошибка при получении директорий сохранения: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"An unexpected error occurred in send_directory_options: {e}")
|
||||||
|
await query.edit_message_text(f"Произошла непредвиденная ошибка: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
# --- Обработка выбора директории и добавление торрента ---
|
||||||
|
async def select_directory_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
|
query = update.callback_query
|
||||||
|
await query.answer()
|
||||||
|
|
||||||
|
data = query.data.split('_')
|
||||||
|
# action = data[0] # select
|
||||||
|
# type = data[1] # dir
|
||||||
|
selected_directory = data[2] # выбранный путь
|
||||||
|
|
||||||
|
logger.info(f"Selected directory: {selected_directory}")
|
||||||
|
|
||||||
|
torrent_url = context.user_data.get('current_torrent_url')
|
||||||
|
category = context.user_data.get('selected_category')
|
||||||
|
|
||||||
|
if not torrent_url:
|
||||||
|
await query.edit_message_text("Ошибка: URL торрента не найден. Пожалуйста, попробуйте снова.")
|
||||||
|
return
|
||||||
|
|
||||||
# Попытка инициализации клиента qBittorrent
|
# Попытка инициализации клиента qBittorrent
|
||||||
if not init_qbittorrent_client():
|
if not init_qbittorrent_client():
|
||||||
@ -215,8 +286,95 @@ async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if action == 'add':
|
try:
|
||||||
try:
|
# Добавление торрента с выбранной категорией и директорией
|
||||||
# Добавление торрента
|
qb.torrents_add(
|
||||||
qb.torrents_add(urls=torrent_url, category=category)
|
urls=torrent_url,
|
||||||
await query.edit_message_text(f"Торрент успешно добавлен в qBittorrent (Категория: {category or 'Без категории'})!")
|
category=category,
|
||||||
|
save_path=selected_directory # Указываем директорию сохранения
|
||||||
|
)
|
||||||
|
await query.edit_message_text(
|
||||||
|
f"Торрент успешно добавлен в qBittorrent.\n"
|
||||||
|
f"Категория: {category or 'Без категории'}\n"
|
||||||
|
f"Директория: {selected_directory}"
|
||||||
|
)
|
||||||
|
# Очищаем данные из context.user_data после использования
|
||||||
|
del context.user_data['current_torrent_url']
|
||||||
|
del context.user_data['selected_category']
|
||||||
|
except APIError as e:
|
||||||
|
logger.error(f"Error adding torrent with path: {e}")
|
||||||
|
await query.edit_message_text(f"Ошибка при добавлении торрента: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"An unexpected error occurred during torrent addition with path: {e}")
|
||||||
|
await query.edit_message_text(f"Произошла непредвиденная ошибка: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
# --- Обработчик ошибок Telegram Bot API ---
|
||||||
|
async def error_handler(update: object, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
|
logger.error(f"Exception while handling an update: {context.error}")
|
||||||
|
if update and update.effective_message:
|
||||||
|
await update.effective_message.reply_text(f"Произошла внутренняя ошибка бота: {context.error}")
|
||||||
|
|
||||||
|
# --- Обработчик для неизвестных команд ---
|
||||||
|
async def unknown_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
|
# Защитная проверка на наличие объекта сообщения
|
||||||
|
if update.message is None:
|
||||||
|
logger.warning("Received an unknown command update without a message object.")
|
||||||
|
return
|
||||||
|
logger.info(f"Received unknown command: {update.message.text} from {update.effective_user.id}")
|
||||||
|
await update.message.reply_text("Извините, я не понял эту команду.")
|
||||||
|
|
||||||
|
# --- Обработчик для любого другого текста (для отладки) ---
|
||||||
|
async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
|
# Защитная проверка на наличие объекта сообщения
|
||||||
|
if update.message is None:
|
||||||
|
logger.warning(f"Received non-text update in echo handler: {update}")
|
||||||
|
return
|
||||||
|
logger.info(f"Received non-command text: {update.message.text} from {update.effective_user.id}")
|
||||||
|
await update.message.reply_text(f"Вы сказали: {update.message.text}")
|
||||||
|
|
||||||
|
|
||||||
|
# --- Основная функция, запускающая бота ---
|
||||||
|
def main() -> None:
|
||||||
|
# Критическая проверка токена бота
|
||||||
|
if not TELEGRAM_BOT_TOKEN:
|
||||||
|
logger.critical("TELEGRAM_BOT_TOKEN environment variable is not set. Exiting.")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
application = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
|
||||||
|
|
||||||
|
# --- Добавление обработчиков команд ---
|
||||||
|
application.add_handler(CommandHandler("start", start))
|
||||||
|
application.add_handler(CommandHandler("status", status))
|
||||||
|
|
||||||
|
# --- Добавление обработчиков сообщений ---
|
||||||
|
# Перехватывает URL и Magnet-ссылки
|
||||||
|
url_regex = r"magnet:\?xt=urn:[a-z0-9]+" # Регулярное выражение для magnet-ссылок
|
||||||
|
torrent_url_regex = r"https?://[^\s]+(?:\.torrent|\/torrent\.php\?hash=)[\S]*" # Регулярное выражение для URL торрент-файлов
|
||||||
|
|
||||||
|
application.add_handler(MessageHandler(filters.TEXT & (filters.Regex(url_regex) | filters.Regex(torrent_url_regex)), handle_url))
|
||||||
|
|
||||||
|
# Добавляем CallbackQueryHandler для кнопок выбора категории
|
||||||
|
application.add_handler(CallbackQueryHandler(select_category_callback, pattern=r"^select_category_.*"))
|
||||||
|
# Добавляем CallbackQueryHandler для кнопок выбора директории
|
||||||
|
application.add_handler(CallbackQueryHandler(select_directory_callback, pattern=r"^select_dir_.*"))
|
||||||
|
|
||||||
|
|
||||||
|
# --- Обработчик для любого другого текста, не являющегося командой (для отладки) ---
|
||||||
|
# Должен быть перед обработчиком неизвестных команд
|
||||||
|
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
|
||||||
|
|
||||||
|
# --- Обработчик для неизвестных команд (должен быть последним, чтобы не перехватывать другие команды) ---
|
||||||
|
application.add_handler(MessageHandler(filters.COMMAND, unknown_command))
|
||||||
|
|
||||||
|
# --- Добавление обработчика ошибок ---
|
||||||
|
application.add_error_handler(error_handler)
|
||||||
|
|
||||||
|
# --- Запуск бота ---
|
||||||
|
logger.info("Bot started polling...")
|
||||||
|
# allowed_updates=Update.ALL_TYPES помогает убедиться, что бот получает все типы обновлений,
|
||||||
|
# что полезно для отладки, но обычно можно сузить список для продакшена.
|
||||||
|
application.run_polling(allowed_updates=Update.ALL_TYPES)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user