mirror of
https://github.com/DerrtSML/qbittorent_bot.git
synced 2025-10-28 05:20:09 +03:00
Update bot.py
This commit is contained in:
parent
8b887ea1b3
commit
9126ec38ac
158
bot.py
158
bot.py
@ -23,7 +23,6 @@ logging.basicConfig(
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# --- Переменные окружения для qBittorrent ---
|
# --- Переменные окружения для qBittorrent ---
|
||||||
# Важно: эти переменные должны быть установлены в Portainer
|
|
||||||
TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
|
TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
|
||||||
QBT_HOST = os.getenv("QBT_HOST")
|
QBT_HOST = os.getenv("QBT_HOST")
|
||||||
QBT_PORT = os.getenv("QBT_PORT")
|
QBT_PORT = os.getenv("QBT_PORT")
|
||||||
@ -36,20 +35,15 @@ qb = None
|
|||||||
# --- Инициализация qBittorrent клиента ---
|
# --- Инициализация qBittorrent клиента ---
|
||||||
def init_qbittorrent_client():
|
def init_qbittorrent_client():
|
||||||
global qb
|
global qb
|
||||||
# Проверяем, что все необходимые переменные окружения установлены
|
|
||||||
if not all([QBT_HOST, QBT_PORT, QBT_USERNAME, QBT_PASSWORD]):
|
if not all([QBT_HOST, QBT_PORT, QBT_USERNAME, QBT_PASSWORD]):
|
||||||
logger.error("QBittorrent credentials (host, port, username, password) are not fully set in environment variables.")
|
logger.error("QBittorrent credentials (host, port, username, password) are not fully set in environment variables.")
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
# Используем qbittorrentapi: передаем учетные данные прямо в конструктор Client
|
|
||||||
# host ожидает строку в формате "IP:PORT"
|
|
||||||
qb = Client(
|
qb = Client(
|
||||||
host=f"{QBT_HOST}:{QBT_PORT}",
|
host=f"{QBT_HOST}:{QBT_PORT}",
|
||||||
username=QBT_USERNAME,
|
username=QBT_USERNAME,
|
||||||
password=QBT_PASSWORD
|
password=QBT_PASSWORD
|
||||||
)
|
)
|
||||||
# Проверяем подключение, обращаясь к версии приложения qBittorrent.
|
|
||||||
# Это неявно проверяет соединение и аутентификацию.
|
|
||||||
qb_version = qb.app.version
|
qb_version = qb.app.version
|
||||||
logger.info(f"Connected to qBittorrent API v{qb_version} on {QBT_HOST}:{QBT_PORT}")
|
logger.info(f"Connected to qBittorrent API v{qb_version} on {QBT_HOST}:{QBT_PORT}")
|
||||||
return True
|
return True
|
||||||
@ -64,38 +58,34 @@ def init_qbittorrent_client():
|
|||||||
|
|
||||||
# --- Команда /start ---
|
# --- Команда /start ---
|
||||||
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
async def start(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 start handler.")
|
logger.warning("Received an update without a message object in start handler.")
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info(f"Received /start command from {update.effective_user.id}")
|
logger.info(f"Received /start command from {update.effective_user.id}")
|
||||||
|
|
||||||
# Попытка инициализации клиента qBittorrent
|
|
||||||
if not init_qbittorrent_client():
|
if not init_qbittorrent_client():
|
||||||
await update.message.reply_text(
|
await update.message.reply_text(
|
||||||
"Не удалось подключиться к qBittorrent. Проверьте переменные окружения и доступность сервера."
|
"Не удалось подключиться к qBittorrent. Проверьте переменные окружения и доступность сервера."
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Отправка приветственного сообщения
|
|
||||||
await update.message.reply_text(
|
await update.message.reply_text(
|
||||||
"Привет! Я бот для управления qBittorrent.\n"
|
"Привет! Я бот для управления qBittorrent.\n"
|
||||||
"Отправь мне magnet-ссылку или URL torrent-файла, "
|
"Отправь мне magnet-ссылку или URL torrent-файла, "
|
||||||
"чтобы добавить загрузку.\n"
|
"чтобы добавить загрузку.\n"
|
||||||
"Используй /status для просмотра текущих загрузок."
|
"Используй /status для просмотра текущих загрузок.\n"
|
||||||
|
"Используй /stop_torrent для остановки загрузки."
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- Команда /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.")
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info(f"Received /status command from {update.effective_user.id}")
|
logger.info(f"Received /status command from {update.effective_user.id}")
|
||||||
|
|
||||||
# Попытка инициализации клиента qBittorrent
|
|
||||||
if not init_qbittorrent_client():
|
if not init_qbittorrent_client():
|
||||||
await update.message.reply_text(
|
await update.message.reply_text(
|
||||||
"Не удалось подключиться к qBittorrent. Проверьте переменные окружения и доступность сервера."
|
"Не удалось подключиться к qBittorrent. Проверьте переменные окружения и доступность сервера."
|
||||||
@ -103,7 +93,6 @@ async def status(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Получаем информацию о торрентах
|
|
||||||
torrents = qb.torrents_info()
|
torrents = qb.torrents_info()
|
||||||
if not torrents:
|
if not torrents:
|
||||||
await update.message.reply_text("Загрузок не найдено.")
|
await update.message.reply_text("Загрузок не найдено.")
|
||||||
@ -111,16 +100,14 @@ async def status(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|||||||
|
|
||||||
status_messages = []
|
status_messages = []
|
||||||
for torrent in torrents:
|
for torrent in torrents:
|
||||||
# Преобразование скорости из B/s в KB/s или MB/s
|
|
||||||
download_speed = torrent.dlspeed / (1024 * 1024) if torrent.dlspeed >= (1024 * 1024) else torrent.dlspeed / 1024
|
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
|
upload_speed = torrent.upspeed / (1024 * 1024) if torrent.upspeed >= (1024 * 1024) else torrent.upspeed / 1024
|
||||||
|
|
||||||
dl_unit = "MB/s" if torrent.dlspeed >= (1024 * 1024) else "KB/s"
|
dl_unit = "MB/s" if torrent.dlspeed >= (1024 * 1024) else "KB/s"
|
||||||
up_unit = "MB/s" if torrent.upspeed >= (1024 * 1024) else "KB/s"
|
up_unit = "MB/s" if torrent.upspeed >= (1024 * 1024) else "KB/s"
|
||||||
|
|
||||||
# Форматирование времени выполнения (eta - Estimated Time of Arrival)
|
|
||||||
eta_str = ""
|
eta_str = ""
|
||||||
if torrent.eta == 8640000: # qBittorrent's way of saying infinite
|
if torrent.eta == 8640000:
|
||||||
eta_str = "∞"
|
eta_str = "∞"
|
||||||
elif torrent.eta > 0:
|
elif torrent.eta > 0:
|
||||||
hours, remainder = divmod(torrent.eta, 3600)
|
hours, remainder = divmod(torrent.eta, 3600)
|
||||||
@ -134,17 +121,15 @@ async def status(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|||||||
else:
|
else:
|
||||||
eta_str = "Завершено"
|
eta_str = "Завершено"
|
||||||
|
|
||||||
|
|
||||||
status_messages.append(
|
status_messages.append(
|
||||||
f"📊 *{torrent.name}*\n"
|
f"📊 *{torrent.name}*\n"
|
||||||
f" Состояние: {torrent.state}\n"
|
f" Состояние: {torrent.state}\n"
|
||||||
f" Прогресс: {torrent.progress:.2%}\n"
|
f" Прогресс: {torrent.progress:.2%}\n"
|
||||||
f" ⬇️ {download_speed:.2f} {dl_unit} ⬆️ {upload_speed:.2f} {up_unit}\n"
|
f" ⬇️ {download_speed:.2f} {dl_unit} ⬆️ {upload_speed:.2f} {up_unit}\n"
|
||||||
f" ETA: {eta_str}\n"
|
f" ETA: {eta_str}\n"
|
||||||
f" Размер: {(torrent.size / (1024*1024*1024)):.2f} ГБ" # Convert bytes to GB
|
f" Размер: {(torrent.size / (1024*1024*1024)):.2f} ГБ"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Отправляем сообщения о статусе торрентов
|
|
||||||
await update.message.reply_text(
|
await update.message.reply_text(
|
||||||
"\n\n".join(status_messages), parse_mode="Markdown"
|
"\n\n".join(status_messages), parse_mode="Markdown"
|
||||||
)
|
)
|
||||||
@ -156,17 +141,81 @@ async def status(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|||||||
logger.error(f"An unexpected error occurred in status command: {e}")
|
logger.error(f"An unexpected error occurred in status command: {e}")
|
||||||
await update.message.reply_text(f"Произошла непредвиденная ошибка: {e}")
|
await update.message.reply_text(f"Произошла непредвиденная ошибка: {e}")
|
||||||
|
|
||||||
# --- Обработка magnet-ссылок и URL ---
|
# --- Команда /stop_torrent (новая команда) ---
|
||||||
async def handle_url(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
async def stop_torrent(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 handle_url handler.")
|
logger.warning("Received an update without a message object in stop_torrent handler.")
|
||||||
return
|
return
|
||||||
|
|
||||||
text = update.message.text
|
logger.info(f"Received /stop_torrent command from {update.effective_user.id}")
|
||||||
logger.info(f"Received URL/Magnet: {text} from {update.effective_user.id}")
|
|
||||||
|
if not init_qbittorrent_client():
|
||||||
|
await update.message.reply_text(
|
||||||
|
"Не удалось подключиться к qBittorrent. Проверьте переменные окружения и доступность сервера."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
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] + "..."
|
||||||
|
|
||||||
|
# callback_data будет содержать полный хэш торрента
|
||||||
|
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:
|
||||||
|
query = update.callback_query
|
||||||
|
await query.answer() # Всегда отвечайте на CallbackQuery
|
||||||
|
|
||||||
|
# Извлекаем хэш торрента из callback_data
|
||||||
|
torrent_hash = query.data.replace("stop_hash_", "")
|
||||||
|
logger.info(f"Attempting to stop torrent with hash: {torrent_hash}")
|
||||||
|
|
||||||
|
if not init_qbittorrent_client():
|
||||||
|
await query.edit_message_text(
|
||||||
|
"Не удалось подключиться к qBittorrent. Проверьте переменные окружения и доступность сервера."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# qBittorrent API метод для остановки торрента
|
||||||
|
qb.torrents_stop(torrent_hashes=torrent_hash)
|
||||||
|
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}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"An unexpected error occurred during torrent stopping: {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:
|
||||||
|
logger.warning("Received an update without a message object in handle_url handler.")
|
||||||
|
return
|
||||||
|
|
||||||
|
text = update.message.text
|
||||||
|
logger.info(f"Received URL/Magnet: {text} from {update.effective_user.id}")
|
||||||
|
|
||||||
# Попытка инициализации клиента qBittorrent
|
|
||||||
if not init_qbittorrent_client():
|
if not init_qbittorrent_client():
|
||||||
await update.message.reply_text(
|
await update.message.reply_text(
|
||||||
"Не удалось подключиться к qBittorrent. Проверьте переменные окружения и доступность сервера."
|
"Не удалось подключиться к qBittorrent. Проверьте переменные окружения и доступность сервера."
|
||||||
@ -174,13 +223,12 @@ async def handle_url(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Сохраняем URL торрента в user_data контекста, чтобы использовать его позже
|
|
||||||
context.user_data['current_torrent_url'] = text
|
context.user_data['current_torrent_url'] = text
|
||||||
|
|
||||||
# Запрашиваем категории - ИЗМЕНЕНО НА qb.torrents_categories()
|
# Запрашиваем категории - ИЗМЕНЕНО НА qb.torrents_categories()
|
||||||
categories_dict = qb.torrents_categories() # Это вернет словарь категорий
|
categories_dict = qb.torrents_categories()
|
||||||
category_keyboard = []
|
category_keyboard = []
|
||||||
for category_name in categories_dict.keys(): # Итерируем по ключам словаря
|
for category_name in categories_dict.keys():
|
||||||
category_keyboard.append([InlineKeyboardButton(category_name, callback_data=f"select_category_{category_name}")])
|
category_keyboard.append([InlineKeyboardButton(category_name, callback_data=f"select_category_{category_name}")])
|
||||||
|
|
||||||
category_keyboard.append([InlineKeyboardButton("Без категории", callback_data="select_category_no_category")])
|
category_keyboard.append([InlineKeyboardButton("Без категории", callback_data="select_category_no_category")])
|
||||||
@ -202,22 +250,17 @@ async def select_category_callback(update: Update, context: ContextTypes.DEFAULT
|
|||||||
await query.answer()
|
await query.answer()
|
||||||
|
|
||||||
data = query.data.split('_')
|
data = query.data.split('_')
|
||||||
# action = data[0] # select
|
|
||||||
# type = data[1] # category
|
|
||||||
selected_category = data[2] if len(data) > 2 and data[2] != 'no_category' else None
|
selected_category = data[2] if len(data) > 2 and data[2] != 'no_category' else None
|
||||||
|
|
||||||
logger.info(f"Selected category: {selected_category}")
|
logger.info(f"Selected category: {selected_category}")
|
||||||
|
|
||||||
# Сохраняем выбранную категорию
|
|
||||||
context.user_data['selected_category'] = selected_category
|
context.user_data['selected_category'] = selected_category
|
||||||
|
|
||||||
# Теперь предложим выбор директорий
|
|
||||||
await query.edit_message_text("Выберите директорию загрузки:")
|
await query.edit_message_text("Выберите директорию загрузки:")
|
||||||
await send_directory_options(query, context)
|
await send_directory_options(query, context)
|
||||||
|
|
||||||
# --- Отправка опций директорий ---
|
# --- Отправка опций директорий ---
|
||||||
async def send_directory_options(query, context):
|
async def send_directory_options(query, context):
|
||||||
# Попытка инициализации клиента qBittorrent
|
|
||||||
if not init_qbittorrent_client():
|
if not init_qbittorrent_client():
|
||||||
await query.edit_message_text(
|
await query.edit_message_text(
|
||||||
"Не удалось подключиться к qBittorrent. Проверьте переменные окружения и доступность сервера."
|
"Не удалось подключиться к qBittorrent. Проверьте переменные окружения и доступность сервера."
|
||||||
@ -225,29 +268,22 @@ async def send_directory_options(query, context):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# !!! ВНИМАНИЕ: qBittorrent API не предоставляет список "доступных" директорий автоматически.
|
|
||||||
# Вам нужно будет вручную указать те директории, которые вы хотите предложить пользователю.
|
|
||||||
# ЗАМЕНИТЕ ЭТОТ СПИСОК СВОИМИ АКТУАЛЬНЫМИ ПУТЯМИ!
|
|
||||||
# Пример:
|
|
||||||
available_paths = [
|
available_paths = [
|
||||||
qb.app.default_save_path, # Дефолтный путь qBittorrent, например: /downloads/complete
|
qb.app.default_save_path,
|
||||||
"/share/Data/torrents", # Пример пути для Linux/NAS
|
"/share/Data/torrents", # ЗАМЕНИТЕ НА СВОИ АКТУАЛЬНЫЕ ПУТИ
|
||||||
"/share/Data/Films",
|
"/share/Data/Films",
|
||||||
"/share/Data/Serials", # Ещё один пример пути
|
"/share/Data/Serials",
|
||||||
# Добавьте здесь свои реальные пути здесь
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Удаляем дубликаты и пустые пути, нормализуем слеши для отображения
|
|
||||||
available_paths = sorted(list(set([p.replace(os.sep, '/') for p in available_paths if p])))
|
available_paths = sorted(list(set([p.replace(os.sep, '/') for p in available_paths if p])))
|
||||||
|
|
||||||
directory_keyboard = []
|
directory_keyboard = []
|
||||||
for path in available_paths:
|
for path in available_paths:
|
||||||
# Для отображения: используем basename, если путь очень длинный, или просто путь
|
|
||||||
display_path = os.path.basename(path) if len(path) > 30 else path
|
display_path = os.path.basename(path) if len(path) > 30 else path
|
||||||
directory_keyboard.append([InlineKeyboardButton(display_path, callback_data=f"select_dir_{path}")])
|
directory_keyboard.append([InlineKeyboardButton(display_path, callback_data=f"select_dir_{path}")])
|
||||||
|
|
||||||
reply_markup = InlineKeyboardMarkup(directory_keyboard)
|
reply_markup = InlineKeyboardMarkup(directory_keyboard)
|
||||||
await query.edit_message_reply_markup(reply_markup=reply_markup) # Изменяем только разметку, чтобы сообщение осталось
|
await query.edit_message_reply_markup(reply_markup=reply_markup)
|
||||||
|
|
||||||
except APIError as e:
|
except APIError as e:
|
||||||
logger.error(f"Error fetching default save path: {e}")
|
logger.error(f"Error fetching default save path: {e}")
|
||||||
@ -263,9 +299,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('_')
|
||||||
# action = data[0] # select
|
selected_directory = data[2]
|
||||||
# type = data[1] # dir
|
|
||||||
selected_directory = data[2] # выбранный путь
|
|
||||||
|
|
||||||
logger.info(f"Selected directory: {selected_directory}")
|
logger.info(f"Selected directory: {selected_directory}")
|
||||||
|
|
||||||
@ -276,7 +310,6 @@ async def select_directory_callback(update: Update, context: ContextTypes.DEFAUL
|
|||||||
await query.edit_message_text("Ошибка: URL торрента не найден. Пожалуйста, попробуйте снова.")
|
await query.edit_message_text("Ошибка: URL торрента не найден. Пожалуйста, попробуйте снова.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Попытка инициализации клиента qBittorrent
|
|
||||||
if not init_qbittorrent_client():
|
if not init_qbittorrent_client():
|
||||||
await query.edit_message_text(
|
await query.edit_message_text(
|
||||||
"Не удалось подключиться к qBittorrent. Проверьте переменные окружения и доступность сервера."
|
"Не удалось подключиться к qBittorrent. Проверьте переменные окружения и доступность сервера."
|
||||||
@ -284,18 +317,16 @@ async def select_directory_callback(update: Update, context: ContextTypes.DEFAUL
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Добавление торрента с выбранной категорией и директорией
|
|
||||||
qb.torrents_add(
|
qb.torrents_add(
|
||||||
urls=torrent_url,
|
urls=torrent_url,
|
||||||
category=category,
|
category=category,
|
||||||
save_path=selected_directory # Указываем директорию сохранения
|
save_path=selected_directory
|
||||||
)
|
)
|
||||||
await query.edit_message_text(
|
await query.edit_message_text(
|
||||||
f"Торрент успешно добавлен в qBittorrent.\n"
|
f"Торрент успешно добавлен в qBittorrent.\n"
|
||||||
f"Категория: {category or 'Без категории'}\n"
|
f"Категория: {category or 'Без категории'}\n"
|
||||||
f"Директория: {selected_directory}"
|
f"Директория: {selected_directory}"
|
||||||
)
|
)
|
||||||
# Очищаем данные из context.user_data после использования
|
|
||||||
if 'current_torrent_url' in context.user_data:
|
if 'current_torrent_url' in context.user_data:
|
||||||
del context.user_data['current_torrent_url']
|
del context.user_data['current_torrent_url']
|
||||||
if 'selected_category' in context.user_data:
|
if 'selected_category' in context.user_data:
|
||||||
@ -316,7 +347,6 @@ async def error_handler(update: object, context: ContextTypes.DEFAULT_TYPE) -> N
|
|||||||
|
|
||||||
# --- Обработчик для неизвестных команд ---
|
# --- Обработчик для неизвестных команд ---
|
||||||
async def unknown_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
async def unknown_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
# Защитная проверка на наличие объекта сообщения
|
|
||||||
if update.message is None:
|
if update.message is None:
|
||||||
logger.warning("Received an unknown command update without a message object.")
|
logger.warning("Received an unknown command update without a message object.")
|
||||||
return
|
return
|
||||||
@ -325,7 +355,6 @@ async def unknown_command(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
|
|||||||
|
|
||||||
# --- Обработчик для любого другого текста (для отладки) ---
|
# --- Обработчик для любого другого текста (для отладки) ---
|
||||||
async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||||
# Защитная проверка на наличие объекта сообщения
|
|
||||||
if update.message is None:
|
if update.message is None:
|
||||||
logger.warning(f"Received non-text update in echo handler: {update}")
|
logger.warning(f"Received non-text update in echo handler: {update}")
|
||||||
return
|
return
|
||||||
@ -335,7 +364,6 @@ async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|||||||
|
|
||||||
# --- Основная функция, запускающая бота ---
|
# --- Основная функция, запускающая бота ---
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
# Критическая проверка токена бота
|
|
||||||
if not TELEGRAM_BOT_TOKEN:
|
if not TELEGRAM_BOT_TOKEN:
|
||||||
logger.critical("TELEGRAM_BOT_TOKEN environment variable is not set. Exiting.")
|
logger.critical("TELEGRAM_BOT_TOKEN environment variable is not set. Exiting.")
|
||||||
exit(1)
|
exit(1)
|
||||||
@ -345,16 +373,11 @@ def main() -> None:
|
|||||||
# --- Добавление обработчиков команд ---
|
# --- Добавление обработчиков команд ---
|
||||||
application.add_handler(CommandHandler("start", start))
|
application.add_handler(CommandHandler("start", start))
|
||||||
application.add_handler(CommandHandler("status", status))
|
application.add_handler(CommandHandler("status", status))
|
||||||
|
# Новая команда для остановки торрентов
|
||||||
|
application.add_handler(CommandHandler("stop_torrent", stop_torrent))
|
||||||
|
|
||||||
# --- Добавление обработчиков сообщений ---
|
# --- Добавление обработчиков сообщений ---
|
||||||
# Перехватывает URL и Magnet-ссылки
|
url_regex = r"magnet:\?xt=urn:[a-z0-9]+"
|
||||||
url_regex = r"magnet:\?xt=urn:[a-z0-9]+" # Регулярное выражение для magnet-ссылок
|
|
||||||
# Обновленное регулярное выражение для URL торрент-файлов
|
|
||||||
# Включает:
|
|
||||||
# - обычные .torrent ссылки (например, example.com/file.torrent)
|
|
||||||
# - /torrent.php?hash= (некоторые трекеры используют это)
|
|
||||||
# - /download/<ID> (например, d.rutor.info/download/1042274)
|
|
||||||
# - любые URL, оканчивающиеся на число, которое может быть ID торрента
|
|
||||||
torrent_url_regex = r"https?://[^\s]+(?:/\d+|/\w+\.torrent|/download/\d+|\/torrent\.php\?hash=)[\S]*"
|
torrent_url_regex = r"https?://[^\s]+(?:/\d+|/\w+\.torrent|/download/\d+|\/torrent\.php\?hash=)[\S]*"
|
||||||
|
|
||||||
application.add_handler(MessageHandler(filters.TEXT & (filters.Regex(url_regex) | filters.Regex(torrent_url_regex)), handle_url))
|
application.add_handler(MessageHandler(filters.TEXT & (filters.Regex(url_regex) | filters.Regex(torrent_url_regex)), handle_url))
|
||||||
@ -363,13 +386,14 @@ def main() -> None:
|
|||||||
application.add_handler(CallbackQueryHandler(select_category_callback, pattern=r"^select_category_.*"))
|
application.add_handler(CallbackQueryHandler(select_category_callback, pattern=r"^select_category_.*"))
|
||||||
# Добавляем CallbackQueryHandler для кнопок выбора директории
|
# Добавляем CallbackQueryHandler для кнопок выбора директории
|
||||||
application.add_handler(CallbackQueryHandler(select_directory_callback, pattern=r"^select_dir_.*"))
|
application.add_handler(CallbackQueryHandler(select_directory_callback, pattern=r"^select_dir_.*"))
|
||||||
|
# Новый CallbackQueryHandler для кнопок остановки торрентов
|
||||||
|
application.add_handler(CallbackQueryHandler(stop_torrent_callback, pattern=r"^stop_hash_.*"))
|
||||||
|
|
||||||
|
|
||||||
# --- Обработчик для любого другого текста, не являющегося командой (для отладки) ---
|
# --- Обработчик для любого другого текста, не являющегося командой ---
|
||||||
# Должен быть перед обработчиком неизвестных команд
|
|
||||||
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
|
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
|
||||||
|
|
||||||
# --- Обработчик для неизвестных команд (должен быть последним, чтобы не перехватывать другие команды) ---
|
# --- Обработчик для неизвестных команд ---
|
||||||
application.add_handler(MessageHandler(filters.COMMAND, unknown_command))
|
application.add_handler(MessageHandler(filters.COMMAND, unknown_command))
|
||||||
|
|
||||||
# --- Добавление обработчика ошибок ---
|
# --- Добавление обработчика ошибок ---
|
||||||
@ -377,8 +401,6 @@ def main() -> None:
|
|||||||
|
|
||||||
# --- Запуск бота ---
|
# --- Запуск бота ---
|
||||||
logger.info("Bot started polling...")
|
logger.info("Bot started polling...")
|
||||||
# allowed_updates=Update.ALL_TYPES помогает убедиться, что бот получает все типы обновлений,
|
|
||||||
# что полезно для отладки, но обычно можно сузить список для продакшена.
|
|
||||||
application.run_polling(allowed_updates=Update.ALL_TYPES)
|
application.run_polling(allowed_updates=Update.ALL_TYPES)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user