diff --git a/app/Dockerfile b/Dockerfile similarity index 100% rename from app/Dockerfile rename to Dockerfile diff --git a/app/Dockerfile.telegram b/Dockerfile.telegram similarity index 100% rename from app/Dockerfile.telegram rename to Dockerfile.telegram diff --git a/app/MANAGEMENT.md b/MANAGEMENT.md similarity index 100% rename from app/MANAGEMENT.md rename to MANAGEMENT.md diff --git a/PROJECT_SUMMARY.md b/PROJECT_SUMMARY.md index 2d278b7..57b2143 100644 --- a/PROJECT_SUMMARY.md +++ b/PROJECT_SUMMARY.md @@ -54,35 +54,29 @@ ## 📊 Технические детали -### Архитектура (два стека): +### Архитектура: ``` -┌─────────────────────────────────────────┐ -│ searchFilms/ — NL-хост (72.56.91.135) │ -│ ┌──────────┐ ┌──────────────────────┐ │ -│ │ tmdb- │ │ torapi-search │ │ -│ │ proxy │ │ (rutracker, kinozal, │ │ -│ │ (:8001) │ │ rutor, nnmclub) │ │ -│ └────┬─────┘ └──────────┬───────────┘ │ -│ │ │ │ -│ ┌────┴───────────────────┴───────────┐ │ -│ │ torapi-qbittorrent (:8444) │ │ -│ │ bridge → magnet ссылки │ │ -│ └────────────┬────────────────────────┘ │ -└───────────────┼──────────────────────────┘ - │ интернет -┌───────────────┼──────────────────────────┐ -│ │ app/ — RU-хост │ -│ ┌────────────┴──────────────────────┐ │ -│ │ movie-search (:8089) FastAPI API │ │ -│ │ веб-интерфейс + API endpoints │ │ -│ └────┬──────────────────────┬───────┘ │ -│ │ │ │ -│ ┌────┴──────────┐ ┌──────┴────────┐ │ -│ │ qBittorrent │ │ telegram-bot │ │ -│ │ (192.168.8.177│ │ │ │ -│ │ :8080) │ │ @your_bot │ │ -│ └───────────────┘ └───────────────┘ │ -└─────────────────────────────────────────┘ +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Telegram Bot │ │ Web Interface │ │ qBittorrent │ +│ (Docker) │ │ (Docker) │ │ (Host) │ +└─────────┬───────┘ └─────────┬───────┘ └─────────┬───────┘ + │ │ │ + └──────────────────────┼──────────────────────┘ + │ + ┌─────────────┴─────────────┐ + │ FastAPI App │ + │ (Movie Search API) │ + └─────────────┬─────────────┘ + │ + ┌─────────────┴─────────────┐ + │ TMDB API │ + │ (Movie Information) │ + └─────────────┬─────────────┘ + │ + ┌─────────────┴─────────────┐ + │ TorAPI │ + │ (Torrent Search) │ + └───────────────────────────┘ ``` ### Поддерживаемые трекеры: @@ -142,26 +136,24 @@ docker ps ## 📁 Структура файлов ``` -findFilms/ -├── README.md # Инструкция по запуску -├── PROJECT_SUMMARY.md # Архитектура проекта -├── .gitignore -│ -├── searchFilms/ # 🌍 NL-стек (Голландия, 72.56.91.135) -│ ├── docker-compose.yml # tmdb-proxy + torapi-search + bridge -│ ├── .env.example -│ └── tmdb-proxy/ # build-зависимость -│ ├── Dockerfile -│ └── tmdb_proxy.py -│ -└── app/ # 🏠 RU-стек (Россия, 192.168.8.173) - ├── docker-compose.yml # movie-search + telegram-bot - ├── .env.example - ├── app.py # FastAPI приложение - ├── telegram_bot.py # Telegram бот - ├── Dockerfile - ├── Dockerfile.telegram - └── templates/ # HTML шаблоны +searchTorrentDownl/ +├── app.py # Основное веб-приложение +├── telegram_bot.py # Telegram бот +├── run_telegram_bot.py # Скрипт запуска бота +├── test_telegram_bot.py # Тестирование бота +├── start_all.sh # Скрипт запуска всего проекта +├── requirements.txt # Python зависимости +├── Dockerfile # Docker образ веб-приложения +├── Dockerfile.telegram # Docker образ Telegram бота +├── docker-compose.yml # Docker Compose конфигурация +├── templates/ # HTML шаблоны +│ ├── index.html # Главная страница +│ ├── results.html # Результаты поиска фильмов +│ ├── torrents.html # Результаты поиска торрентов +│ └── error.html # Страница ошибок +├── README.md # Основная документация +├── TELEGRAM_BOT_README.md # Документация Telegram бота +└── PROJECT_SUMMARY.md # Эта сводка ``` ## 🎯 Ключевые особенности diff --git a/README.md b/README.md index 0c41586..d6559a7 100644 --- a/README.md +++ b/README.md @@ -1,79 +1,469 @@ -# findFilms — поиск и скачивание фильмов +# 🎬 searchTorrentDownl -Двухстековая архитектура: **searchFilms/** на NL-хосте (Голландия, без блокировок), **app/** на RU-хосте (Россия). Между собой общаются по HTTP через интернет. +**Полнофункциональная система для поиска и загрузки фильмов через торренты с Telegram ботом** ---- +[![Docker](https://img.shields.io/badge/Docker-Ready-blue?logo=docker)](https://www.docker.com/) +[![Python](https://img.shields.io/badge/Python-3.12+-green?logo=python)](https://python.org/) +[![FastAPI](https://img.shields.io/badge/FastAPI-0.115.0-red?logo=fastapi)](https://fastapi.tiangolo.com/) +[![Telegram](https://img.shields.io/badge/Telegram-Bot-blue?logo=telegram)](https://telegram.org/) -## Архитектура +## 🎯 Описание проекта + +**searchTorrentDownl** - это современная система для поиска и загрузки фильмов, которая объединяет: +- 🌐 **Веб-интерфейс** с адаптивным дизайном +- 🤖 **Telegram бот** с полной функциональностью +- 🔍 **Поиск фильмов** через TMDB API +- 🎬 **Поиск торрентов** на популярных трекерах +- ⬇️ **Автоматическое добавление** в qBittorrent +- 🔔 **Уведомления** о завершении загрузки + +### ✨ Основные возможности + +- **🎬 Поиск фильмов** - интеллектуальный поиск с постерами и описаниями +- **🔍 Поиск торрентов** - на всех популярных трекерах (RuTracker, Kinozal, RuTor, NoNameClub) +- **📱 Telegram бот** - полная функциональность в мессенджере +- **🌐 Веб-интерфейс** - удобный поиск и навигация +- **⬇️ Автоматическая загрузка** - добавление торрентов в qBittorrent одним кликом +- **🔔 Уведомления** - сообщения о завершении загрузки в Telegram +- **🐳 Docker** - полная контейнеризация для простого развертывания + +## 🏗️ Архитектура системы ``` -searchFilms/ (NL, 72.56.91.135) internet app/ (RU, 192.168.8.173) -┌─────────────────────────┐ HTTP ┌─────────────────────────────┐ -│ tmdb-proxy (:8001) │ ←──────────→ │ movie-search (:8089) │ -│ torapi-search (:8443) │ поиск │ telegram-bot │ -│ │ │ qBittorrent (:8080) │ -└─────────────────────────┘ └─────────────────────────────┘ +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Telegram Bot │ │ Web Interface │ │ qBittorrent │ +│ (Docker) │ │ (Docker) │ │ (Host) │ +└─────────┬───────┘ └─────────┬───────┘ └─────────┬───────┘ + │ │ │ + └──────────────────────┼──────────────────────┘ + │ + ┌─────────────┴─────────────┐ + │ FastAPI App │ + │ (Movie Search API) │ + └─────────────┬─────────────┘ + │ + ┌─────────────┴─────────────┐ + │ TMDB API │ + │ (Movie Information) │ + └─────────────┬─────────────┘ + │ + ┌─────────────┴─────────────┐ + │ TorAPI │ + │ (Torrent Search) │ + └───────────────────────────┘ ``` -**Поток данных:** -1. Пользователь ищет фильм → movie-search делает запрос к **tmdb-proxy** (NL) -2. Пользователь ищет торренты → movie-search делает запрос к **torapi-search** (NL) -3. Пользователь добавляет торрент → movie-search отдаёт magnet-ссылку в **qBittorrent** (локально) +### 🔧 Компоненты системы -NL-хосту **не нужен** доступ к RU — только RU ходит к NL. +1. **FastAPI приложение** (Docker) - основной веб-сервис и API +2. **Telegram Bot** (Docker) - бот для мессенджера +3. **TorAPI-Search** (Docker) - поиск торрентов по названию +4. **TorAPI-qBittorrent** (Docker) - получение magnet ссылок +5. **qBittorrent-nox** (Host) - клиент для загрузки торрентов ---- +## 📋 Системные требования -## Быстрый старт +### Минимальные требования +- **ОС**: Linux (Ubuntu 20.04+, Debian 11+) +- **RAM**: 2GB +- **Диск**: 10GB свободного места +- **CPU**: 2 ядра -### 🌍 SearchFilms Stack — на голландском хосте (72.56.91.135) +### Необходимое ПО +- **Docker**: 20.10+ +- **Docker Compose**: 2.0+ +- **qBittorrent-nox**: 4.6.7+ + +## 🚀 Быстрый старт + +### 1️⃣ Автоматическое развертывание (рекомендуется) ```bash -cd searchFilms -cp .env.example .env # указать TMDB_API_KEY -docker compose up -d --build +# Клонируйте репозиторий +git clone +cd searchTorrentDownl + +# Запустите скрипт развертывания +chmod +x deploy.sh +./deploy.sh ``` -Откроет порты: -- `:8001` — TMDB API Proxy -- `:8443` — TorAPI Search +Скрипт автоматически: +- Установит все зависимости +- Настроит qBittorrent +- Запустит все сервисы +- Покажет статус системы -### 🏠 App Stack — на хосте в России +### 2️⃣ Ручное развертывание + +#### Шаг 1: Установка зависимостей ```bash -cd app -cp .env.example .env # указать TELEGRAM_BOT_TOKEN -docker compose up -d --build +# Обновление системы +sudo apt update && sudo apt upgrade -y + +# Установка Docker +curl -fsSL https://get.docker.com -o get-docker.sh +sudo sh get-docker.sh +sudo usermod -aG docker $USER + +# Установка Docker Compose +sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose +sudo chmod +x /usr/local/bin/docker-compose + +# Установка qBittorrent +sudo apt install -y qbittorrent-nox ``` -Откроет: -- `:8089` — веб-интерфейс -- Telegram бот (ждёт команды `/start`) +#### Шаг 2: Настройка qBittorrent -### Переменные окружения +```bash +# Создание пользователя +sudo useradd -r -s /bin/false qbittorrent -| Переменная | Где | Назначение | -|---|---|---| -| `NL_HOST` | `app/.env` | IP голландского хоста (по умолч. `72.56.91.135`) | -| `TELEGRAM_BOT_TOKEN` | `app/.env` | Токен бота от @BotFather | -| `TMDB_API_KEY` | `search/.env` | Ключ TMDB API | -| `QBITTORRENT_USERNAME` | `app/.env` | Логин qBittorrent | -| `QBITTORRENT_PASSWORD` | `app/.env` | Пароль qBittorrent | +# Создание systemd сервиса +sudo tee /etc/systemd/system/qbittorrent.service > /dev/null < API](https://www.themoviedb.org/settings/api) +3. Создайте новый API ключ +4. Скопируйте ключ в файл `.env` + +#### Telegram Bot Token +1. Найдите [@BotFather](https://t.me/BotFather) в Telegram +2. Отправьте команду `/newbot` +3. Следуйте инструкциям для создания бота +4. Скопируйте токен в файл `.env` + +### 📝 Файл конфигурации (.env) + +```bash +# TMDB API Key +TMDB_API_KEY=your_tmdb_api_key_here + +# Telegram Bot Token +TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here + +# qBittorrent настройки +QBITTORRENT_USERNAME=admin +QBITTORRENT_PASSWORD=admin +QBITTORRENT_HOST=host.docker.internal +QBITTORRENT_PORT=8080 + +# TorAPI настройки +TORRENT_SEARCH_URL=http://host.docker.internal:8443 +TORRENT_ADD_URL=http://host.docker.internal:8088 +``` + +## 🌐 Использование + +### Веб-интерфейс +1. Откройте http://localhost:8089 +2. Введите название фильма +3. Выберите фильм из результатов +4. Выберите торрент для скачивания +5. Торрент автоматически добавится в qBittorrent + +### Telegram Bot +1. Найдите вашего бота в Telegram +2. Отправьте команду `/start` или `/find` +3. Введите название фильма +4. Выберите фильм из списка +5. Нажмите "Найти торренты" +6. Выберите нужный торрент +7. Получите уведомление о завершении загрузки + +### qBittorrent +- **Веб-интерфейс**: http://localhost:8080 +- **Логин по умолчанию**: admin / admin +- **Настройка папок**: Settings > Downloads + +## 📁 Структура проекта + +``` +searchTorrentDownl/ +├── app.py # Основное FastAPI приложение +├── telegram_bot.py # Telegram бот +├── run_telegram_bot.py # Скрипт запуска бота +├── deploy.sh # Скрипт автоматического развертывания +├── start_all.sh # Скрипт запуска (локально) +├── requirements.txt # Python зависимости +├── env.example # Пример конфигурации +├── Dockerfile # Docker образ основного приложения +├── Dockerfile.telegram # Docker образ Telegram бота +├── docker-compose.yml # Docker Compose конфигурация +├── templates/ # HTML шаблоны +│ ├── index.html # Главная страница +│ ├── results.html # Результаты поиска фильмов +│ ├── torrents.html # Результаты поиска торрентов +│ └── error.html # Страница ошибок +├── README.md # Основная документация +├── TELEGRAM_BOT_README.md # Документация Telegram бота +└── PROJECT_SUMMARY.md # Сводка проекта +``` + +## 🔧 Управление сервисами + +### Основные команды + +```bash +# Запуск всех сервисов +docker compose up -d + +# Остановка всех сервисов +docker compose down + +# Перезапуск сервисов +docker compose restart + +# Просмотр логов +docker compose logs -f + +# Просмотр статуса +docker ps +``` + +### Управление отдельными сервисами + +```bash +# Запуск только веб-приложения +docker compose up -d movie-search + +# Запуск только Telegram бота +docker compose up -d telegram-bot + +# Перезапуск TorAPI +docker compose restart torapi-search torapi-qbittorrent +``` + +### Управление qBittorrent + +```bash +# Запуск +sudo systemctl start qbittorrent + +# Остановка +sudo systemctl stop qbittorrent + +# Статус +sudo systemctl status qbittorrent + +# Логи +sudo journalctl -u qbittorrent -f +``` + +## 🐛 Устранение неполадок + +### Проблемы с Docker + +```bash +# Проверка статуса контейнеров +docker ps -a + +# Просмотр логов +docker logs + +# Пересборка контейнеров +docker compose up -d --build --force-recreate +``` + +### Проблемы с qBittorrent + +```bash +# Проверка статуса +sudo systemctl status qbittorrent + +# Перезапуск +sudo systemctl restart qbittorrent + +# Проверка портов +netstat -tlnp | grep 8080 +``` + +### Проблемы с сетью + +```bash +# Проверка Docker сетей +docker network ls + +# Создание сети заново +docker network rm torrentvideo_default +docker network create torrentvideo_default +``` + +### Проблемы с API + +```bash +# Проверка TMDB API +curl "https://api.themoviedb.org/3/movie/550?api_key=YOUR_API_KEY" + +# Проверка Telegram Bot +curl "https://api.telegram.org/botYOUR_BOT_TOKEN/getMe" + +# Проверка TorAPI +curl "http://localhost:8443/api/provider/list" +``` + +## 📊 Мониторинг + +### Логи сервисов + +```bash +# Все сервисы +docker compose logs -f + +# Конкретный сервис +docker logs -f movie-search +docker logs -f telegram-bot +docker logs -f TorAPI-Search +docker logs -f TorAPI-qBittorrent +``` + +### Мониторинг ресурсов + +```bash +# Использование ресурсов контейнерами +docker stats + +# Использование диска +df -h + +# Использование памяти +free -h +``` + +## 🔒 Безопасность + +### Рекомендации + +1. **Измените пароли по умолчанию** + ```bash + # В файле .env + QBITTORRENT_PASSWORD=your_secure_password + ``` + +2. **Настройте файрвол** + ```bash + sudo ufw allow 8080 # qBittorrent + sudo ufw allow 8089 # Web interface + sudo ufw enable + ``` + +3. **Используйте HTTPS в продакшене** + - Настройте reverse proxy (nginx) + - Получите SSL сертификат + +4. **Регулярно обновляйте зависимости** + ```bash + docker compose pull + docker compose up -d --build + ``` + +## 🚀 Развертывание в продакшене + +### Настройка reverse proxy (nginx) + +```nginx +server { + listen 80; + server_name your-domain.com; + + location / { + proxy_pass http://localhost:8089; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } +} +``` + +### Использование Docker Swarm + +```bash +# Инициализация Swarm +docker swarm init + +# Развертывание стека +docker stack deploy -c docker-compose.yml searchtorrentdownl +``` + +## 🤝 Вклад в проект + +1. Форкните репозиторий +2. Создайте ветку для новой функции +3. Внесите изменения +4. Создайте Pull Request + +## 📄 Лицензия + +Этот проект распространяется под лицензией MIT. См. файл [LICENSE](LICENSE) для подробностей. + +## 🆘 Поддержка + +При возникновении проблем: + +1. Проверьте [раздел устранения неполадок](#-устранение-неполадок) +2. Изучите логи сервисов +3. Создайте issue в репозитории +4. Опишите проблему и приложите логи + +## 📈 Планы развития + +- [ ] Поддержка дополнительных трекеров +- [ ] Веб-интерфейс для управления ботом +- [ ] Система уведомлений по email +- [ ] API для интеграции с другими приложениями +- [ ] Поддержка сериалов и аниме +- [ ] Мобильное приложение --- -## Структура - -``` -findFilms/ -├── README.md ← этот файл -├── PROJECT_SUMMARY.md ← архитектура -├── searchFilms/ ← NL-стек -│ ├── docker-compose.yml -│ ├── .env.example -│ └── tmdb-proxy/ ← build-зависимость -└── app/ ← RU-стек - ├── docker-compose.yml - ├── .env.example - ├── app.py / telegram_bot.py / templates / Dockerfile / ... -``` +**Создано с ❤️ для удобного поиска и загрузки фильмов** \ No newline at end of file diff --git a/app/SAMBA_ACCESS.md b/SAMBA_ACCESS.md similarity index 100% rename from app/SAMBA_ACCESS.md rename to SAMBA_ACCESS.md diff --git a/app/TELEGRAM_BOT_README.md b/TELEGRAM_BOT_README.md similarity index 100% rename from app/TELEGRAM_BOT_README.md rename to TELEGRAM_BOT_README.md diff --git a/app/UBUNTU_DEPLOYMENT.md b/UBUNTU_DEPLOYMENT.md similarity index 100% rename from app/UBUNTU_DEPLOYMENT.md rename to UBUNTU_DEPLOYMENT.md diff --git a/app/app.py b/app.py similarity index 75% rename from app/app.py rename to app.py index 4e3225b..cfa2d4d 100644 --- a/app/app.py +++ b/app.py @@ -4,12 +4,11 @@ import asyncio import httpx import requests from bs4 import BeautifulSoup -from fastapi import FastAPI, Request, Form, HTTPException, Query -from fastapi.responses import HTMLResponse, Response +from fastapi import FastAPI, Request, Form, HTTPException +from fastapi.responses import HTMLResponse from fastapi.templating import Jinja2Templates from fastapi.staticfiles import StaticFiles from fastapi.middleware.cors import CORSMiddleware -from urllib.parse import quote app = FastAPI(title="Movie Search API", version="1.0.0") @@ -25,13 +24,6 @@ app.add_middleware( # Настройка шаблонов templates = Jinja2Templates(directory="templates") -# Регистрируем фильтр urlencode для шаблонов -def urlencode_filter(s): - if s: - return quote(s, safe='') - return '' -templates.env.filters['urlencode'] = urlencode_filter - # URL прокси-сервиса TMDB TMDB_PROXY_URL = os.getenv("TMDB_PROXY_URL", "http://localhost:8001") @@ -291,29 +283,15 @@ async def search_torrents(movie_title: str, year: str = None, original_title: st print(f"Searching '{search_query}' on {provider_name}") # Поиск на конкретном провайдере - исправленный эндпоинт - try: - search_response = await client.get( - f"{TORRENT_SEARCH_URL}/api/search/title/{provider_name}", - params={"query": search_query}, - timeout=30.0 - ) - except Exception as request_error: - print(f"Request error on {provider_name} for '{search_query}': {request_error}") - import traceback - traceback.print_exc() - continue + search_response = await client.get( + f"{TORRENT_SEARCH_URL}/api/search/title/{provider_name}", + params={"query": search_query} + ) if search_response.status_code == 200: - try: - results = search_response.json() - except Exception as json_error: - print(f"JSON parse error on {provider_name} for '{search_query}': {json_error}") - print(f"Response text (first 500 chars): {search_response.text[:500]}") - continue + results = search_response.json() if not isinstance(results, list): print(f"Unexpected response format from {provider_name}: {type(results)}") - if isinstance(results, dict): - print(f"Dict keys: {list(results.keys())}") continue print(f"Found {len(results)} results for '{search_query}' on {provider_name}") @@ -362,9 +340,7 @@ async def search_torrents(movie_title: str, year: str = None, original_title: st break except Exception as e: - print(f"Error searching on {provider_name}: {type(e).__name__}: {e}") - import traceback - traceback.print_exc() + print(f"Error searching on {provider_name}: {e}") continue # Сортируем по количеству сидов и ограничиваем общее количество @@ -426,45 +402,31 @@ async def search_torrent_by_id(torrent_id: str) -> dict: torrent_name = result.get('Name', '') or result.get('Original_Name', '') print(f"Found torrent by ID on {provider_name}: {torrent_name[:100]}...") - # Получаем хэш — используем единую функцию извлечения - hash_value = extract_hash_from_result(result) + # Получаем хэш и создаем чистую magnet-ссылку с публичными трекерами + hash_value = result.get('Hash', '') + torrent_title = torrent_name + + # Если хэша нет в Hash, пытаемся извлечь из Magnet ссылки + if not hash_value: + original_magnet = result.get('Magnet', '') + if original_magnet: + # Извлекаем хэш из magnet ссылки + hash_match = re.search(r'urn:btih:([a-fA-F0-9]{40}|[a-zA-Z0-9]{32})', original_magnet) + if hash_match: + hash_value = hash_match.group(1) + print(f"Extracted hash from magnet link: {hash_value[:10]}...") if hash_value: # Генерируем чистую magnet-ссылку с публичными трекерами - magnet = generate_clean_magnet(hash_value, torrent_name) - print(f"Generated clean magnet with hash {hash_value[:10]}...: {magnet[:100]}...") + magnet = generate_clean_magnet(hash_value, torrent_title) + print(f"Generated clean magnet with public trackers: {magnet[:100]}...") else: # Fallback на оригинальную magnet-ссылку если нет хэша magnet = result.get('Magnet', '') if not magnet or not magnet.startswith('magnet:'): - print(f"Warning: No hash found and no valid magnet link. Hash fields: Hash={result.get('Hash', 'None')[:20]}, Magnet={result.get('Magnet', 'None')[:50]}") + print(f"Warning: No hash found and no valid magnet link. Hash: {hash_value}, Magnet: {result.get('Magnet', 'None')[:50]}") magnet = "" - - # Пробуем локальный torapi-qbit если хэш пустой или битый - if not magnet or not re.search(r'urn:btih:([a-fA-F0-9]{40}|[a-zA-Z0-9]{32})', magnet): - try: - torapi_add_url = os.getenv("TORAPI_ADD_URL", "http://localhost:8444") - fb_resp = await client.get( - f"{torapi_add_url}/api/search/id/{provider_name}", - params={"query": torrent_id}, - timeout=15.0 - ) - if fb_resp.status_code == 200: - fb_data = fb_resp.json() - if isinstance(fb_data, list) and len(fb_data) > 0: - fb_result = fb_data[0] - fb_hash = extract_hash_from_result(fb_result) - if fb_hash: - magnet = generate_clean_magnet(fb_hash, torrent_name) - print(f"torapi-qbit fallback: got hash {fb_hash[:10]}... via extract_hash_from_result") - except Exception as fbe: - print(f"torapi-qbit fallback failed: {fbe}") - - # Если хэш всё ещё пустой — пропускаем этого провайдера - if not hash_value and not re.search(r'urn:btih:([a-fA-F0-9]{40}|[a-zA-Z0-9]{32})', magnet): - print(f"Skipping {provider_name}: no valid magnet hash") - continue - + # Парсим результат в стандартный формат torrent = { "title": torrent_name, @@ -496,48 +458,10 @@ async def search_torrent_by_id(torrent_id: str) -> dict: print(f"Error searching by ID on {provider_name}: {response.status_code} - {response.text}") except Exception as e: - print(f"Error searching on {provider_name}: {type(e).__name__}: {e}") - import traceback - traceback.print_exc() + print(f"Error searching on {provider_name}: {e}") continue - print(f"No results found for ID {torrent_id} on any provider via search API") - - # Fallback: пробуем локальный torapi-qbit (через qBittorrent) - try: - torapi_add_url = os.getenv("TORAPI_ADD_URL", "http://localhost:8444") - fallback_response = await client.get( - f"{torapi_add_url}/api/search/id/rutracker", - params={"query": torrent_id}, - timeout=30.0 - ) - if fallback_response.status_code == 200: - fallback_results = fallback_response.json() - if isinstance(fallback_results, list) and len(fallback_results) > 0: - result = fallback_results[0] - hash_value = result.get('Hash', '') - if not hash_value: - orig_magnet = result.get('Magnet', '') - hm = re.search(r'urn:btih:([a-fA-F0-9]{40})', orig_magnet) - if hm: - hash_value = hm.group(1) - if hash_value: - torrent_name = result.get('Name', '') or result.get('Original_Name', '') - magnet = generate_clean_magnet(hash_value, torrent_name) - print(f"Fallback: got magnet via torapi-qbit: {magnet[:60]}...") - return { - "title": torrent_name, - "url": result.get('Url', ''), - "hash": hash_value, - "magnet": magnet, - "torrent_url": result.get('Torrent', ''), - "provider": "torapi-qbit", - "id": torrent_id - } - except Exception as e: - print(f"Fallback to torapi-qbit failed: {e}") - - print(f"All fallbacks exhausted for ID {torrent_id}") + print(f"No results found for ID {torrent_id} on any provider") return None except Exception as e: @@ -680,69 +604,25 @@ def parse_size_to_bytes(size_str: str) -> int: return 0 -def extract_hash_from_result(result: dict) -> str: - """Извлекает хэш торрента из результата API — пробует все возможные поля""" - hash_value = "" - - # 1. Прямое поле Hash - hash_value = result.get('Hash', '') or result.get('hash', '') or '' - if hash_value and re.match(r'^[a-fA-F0-9]{40}$', hash_value): - return hash_value.upper() - - # 2. Поле InfoHash - if not hash_value: - hash_value = result.get('InfoHash', '') or result.get('info_hash', '') or '' - if hash_value and re.match(r'^[a-fA-F0-9]{40}$', hash_value): - return hash_value.upper() - - # 3. Из Magnet ссылки - magnet = result.get('Magnet', '') or result.get('magnet', '') or '' - if magnet: - hm = re.search(r'urn:btih:([a-fA-F0-9]{40})', magnet) - if hm: - return hm.group(1).upper() - hm32 = re.search(r'urn:btih:([a-zA-Z0-9]{32})', magnet) - if hm32: - return hm32.group(1) - - # 4. Из Torrent URL (rutracker: /dl.php?t=XXXXX) - torrent_url = result.get('Torrent', '') or result.get('torrent', '') or result.get('Url', '') or '' - # Некоторые провайдеры кодируют хэш в URL - if 'btih:' in torrent_url: - hm = re.search(r'btih:([a-fA-F0-9]{40})', torrent_url) - if hm: - return hm.group(1).upper() - - # 5. Из поля Id/ID (некоторые API возвращают хэш как ID) - tid = result.get('Id') or result.get('id') or result.get('ID') or '' - if tid and re.match(r'^[a-fA-F0-9]{40}$', tid): - return tid.upper() - - return hash_value # Может быть пустым - - def generate_clean_magnet(hash_value: str, title: str = None) -> str: """Генерирует чистую magnet-ссылку с публичными трекерами""" if not hash_value: return "" - # Рабочие публичные трекеры (2025+) + # Проверенные рабочие трекеры (минимальный набор) public_trackers = [ "udp://tracker.opentrackr.org:1337/announce", "udp://open.stealth.si:80/announce", "udp://tracker.openbittorrent.com:6969/announce", - "https://tracker.tamersunion.org:443/announce", - "udp://exodus.desync.com:6969/announce", - "udp://tracker.moeking.me:6969/announce", + "udp://tracker.coppersurfer.tk:6969/announce", + "udp://tracker.leechers-paradise.org:6969/announce" ] # Создаем базовую magnet-ссылку с хэшем magnet = f"magnet:?xt=urn:btih:{hash_value}" - # Добавляем название в кодировке RFC 3986 для удобства - if title: - from urllib.parse import quote - magnet += f"&dn={quote(title)}" + # НЕ добавляем название файла - это может вызывать проблемы с кириллицей + # DHT сам найдет название по хэшу # Добавляем публичные трекеры for tracker in public_trackers: @@ -1170,64 +1050,8 @@ async def torrents_page(request: Request, movie_title: str, year: str = None): } ) -@app.get("/api/proxy-torrent-download") -async def proxy_torrent_download(url: str = Query(...)): - """Прокси скачивание .torrent файла через NL-сервер (обходит DPI)""" - try: - proxy_base = os.getenv("TMDB_PROXY_URL", "http://localhost:8001") - print(f"Proxying .torrent download: {url} via {proxy_base}") - - async with httpx.AsyncClient(timeout=30.0) as client: - # Пробуем через NL прокси (tmdb-proxy имеет прямой выход в интернет) - try: - proxy_resp = await client.get( - f"{proxy_base}/proxy-torrent", - params={"url": url}, - timeout=30.0 - ) - if proxy_resp.status_code == 200 or (proxy_resp.status_code < 400 and len(proxy_resp.content) > 100): - content_type = proxy_resp.headers.get("content-type", "application/x-bittorrent") - return Response( - content=proxy_resp.content, - media_type=content_type, - headers={ - "Content-Disposition": f'attachment; filename="{os.path.basename(url)}.torrent"', - "Content-Length": str(len(proxy_resp.content)) - } - ) - print(f"NL proxy returned {proxy_resp.status_code}, trying direct download...") - except Exception as proxy_err: - print(f"NL proxy error: {proxy_err}, trying direct download...") - - # Fallback: пытаемся скачать напрямую из контейнера (может работать если DNS не блокируется) - try: - direct_resp = await client.get(url, timeout=15.0) - if direct_resp.status_code == 200 and len(direct_resp.content) > 100: - content_type = direct_resp.headers.get("content-type", "application/x-bittorrent") - return Response( - content=direct_resp.content, - media_type=content_type, - headers={ - "Content-Disposition": f'attachment; filename="{os.path.basename(url)}.torrent"', - "Content-Length": str(len(direct_resp.content)) - } - ) - except Exception as direct_err: - print(f"Direct download failed: {direct_err}") - - raise HTTPException( - status_code=502, - detail=f"Не удалось скачать .torrent файл. NL-прокси и прямой доступ недоступны." - ) - - except HTTPException: - raise - except Exception as e: - print(f"Error in proxy-torrent-download: {e}") - raise HTTPException(status_code=500, detail=f"Ошибка прокси: {str(e)}") - @app.post("/api/add-torrent") -async def add_torrent_to_client(torrent_id: str = Form(...), magnet: str = Form(""), torrent_title: str = Form("")): +async def add_torrent_to_client(torrent_id: str = Form(...)): """Добавление торрента в qBittorrent через прямое API""" try: print(f"Attempting to add torrent with ID: {torrent_id}") @@ -1235,21 +1059,10 @@ async def add_torrent_to_client(torrent_id: str = Form(...), magnet: str = Form( if not torrent_id or torrent_id.strip() == '': return {"status": "error", "message": "ID торрента не указан"} - # Если magnet передан напрямую (из результатов поиска), используем его - if magnet and magnet.startswith('magnet:'): - print(f"Using direct magnet link: {magnet[:100]}...") - torrent_info = { - "title": torrent_title or torrent_id, - "hash": "", - "magnet": magnet, - "torrent_url": "" - } - else: - # Fallback: ищем по ID через TorAPI - torrent_info = await search_torrent_by_id(torrent_id) - + # Получаем информацию о торренте по ID + torrent_info = await search_torrent_by_id(torrent_id) if not torrent_info: - print(f"Torrent info not found for ID: {torrent_id}") + print(f"Torrent info is None for ID: {torrent_id}") return {"status": "error", "message": f"Торрент с ID {torrent_id} не найден. Проверьте логи для деталей."} print(f"Found torrent info: title={torrent_info.get('title', 'Unknown')[:50]}, magnet={'present' if torrent_info.get('magnet') else 'missing'}, torrent_url={'present' if torrent_info.get('torrent_url') else 'missing'}") @@ -1298,42 +1111,27 @@ async def add_torrent_to_client(torrent_id: str = Form(...), magnet: str = Form( if hash_match: torrent_hash = hash_match.group(1).upper() - # Реальная верификация: проверяем, что торрент появился в qBittorrent - verified = False - actual_hash = torrent_hash - torrents_response = await client.get(f"{qb_url}/api/v2/torrents/info") - if torrents_response.status_code == 200: - qb_torrents = torrents_response.json() - if torrent_hash: - for qt in qb_torrents: - if qt.get('hash', '').upper() == torrent_hash: - verified = True - actual_hash = qt.get('hash', '') - print(f"Verified: torrent found by hash in qBittorrent") - break - if not verified: - # Пробуем найти по названию - t_title = torrent_info.get('title', '').lower() - for qt in qb_torrents: - qt_name = qt.get('name', '').lower() - if t_title and qt_name and (t_title[:30] in qt_name or qt_name[:30] in t_title): - verified = True - actual_hash = qt.get('hash', '') - print(f"Verified: torrent found by name in qBittorrent: {qt.get('name', '')}") - break + if torrent_hash: + torrents_response = await client.get(f"{qb_url}/api/v2/torrents/info") + if torrents_response.status_code == 200: + torrents = torrents_response.json() + # Ищем торрент по хэшу + for torrent in torrents: + if torrent.get('hash', '').upper() == torrent_hash: + return { + "status": "success", + "message": f"Торрент '{torrent_info.get('title', 'Unknown')[:50]}...' добавлен в qBittorrent через magnet-ссылку!", + "torrent_hash": torrent.get('hash'), + "torrent_name": torrent_info.get('title', 'Unknown') + } - if verified: - return { - "status": "success", - "message": f"Торрент '{torrent_info.get('title', 'Unknown')[:50]}...' добавлен в qBittorrent!", - "torrent_hash": actual_hash, - "torrent_name": torrent_info.get('title', 'Unknown') - } - else: - # qBittorrent сказал Ok., но торрент не появился — ложный успех - print(f"WARNING: qBittorrent returned Ok. but torrent was not added (hash={torrent_hash or 'empty'})") - print("Proceeding to .torrent file fallback...") - # Не возвращаем success, а пробуем .torrent fallback ниже + # Если не нашли по хэшу, но ответ был Ok, считаем успешным + return { + "status": "success", + "message": f"Торрент '{torrent_info.get('title', 'Unknown')[:50]}...' добавлен в qBittorrent через magnet-ссылку!", + "torrent_hash": torrent_hash, + "torrent_name": torrent_info.get('title', 'Unknown') + } else: print(f"Magnet link failed (status: {add_response.status_code}, response: {add_response.text}), trying .torrent file...") else: @@ -1347,42 +1145,13 @@ async def add_torrent_to_client(torrent_id: str = Form(...), magnet: str = Form( if torrent_url: print(f"Trying to add via .torrent file: {torrent_url}") + add_response = await client.post( + f"{qb_url}/api/v2/torrents/add", + data={"urls": torrent_url} + ) - # Сначала пробуем скачать .torrent через NL-прокси (обходит DPI) - try: - proxy_base = os.getenv("TMDB_PROXY_URL", "http://localhost:8001") - print(f"Downloading .torrent via NL proxy: {proxy_base}/proxy-torrent") - proxy_resp = await client.get( - f"{proxy_base}/proxy-torrent", - params={"url": torrent_url}, - timeout=30.0 - ) - if proxy_resp.status_code == 200: - torrent_content = proxy_resp.content - print(f"Downloaded .torrent via NL proxy: {len(torrent_content)} bytes") - - # Загружаем файл в qBittorrent - add_response = await client.post( - f"{qb_url}/api/v2/torrents/add", - files={"torrents": ("torrent.torrent", torrent_content, "application/x-bittorrent")} - ) - print(f"Add via .torrent file upload response: {add_response.status_code} - {add_response.text}") - else: - print(f"NL proxy failed: {proxy_resp.status_code}, sending URL directly to qBittorrent") - add_response = await client.post( - f"{qb_url}/api/v2/torrents/add", - data={"urls": torrent_url} - ) - print(f"Add via .torrent response status: {add_response.status_code}") - print(f"Add via .torrent response text: {add_response.text}") - except Exception as proxy_err: - print(f"NL proxy error: {proxy_err}, falling back to direct URL") - add_response = await client.post( - f"{qb_url}/api/v2/torrents/add", - data={"urls": torrent_url} - ) - print(f"Add via .torrent response status: {add_response.status_code}") - print(f"Add via .torrent response text: {add_response.text}") + print(f"Add via .torrent response status: {add_response.status_code}") + print(f"Add via .torrent response text: {add_response.text}") if add_response.status_code == 200 and add_response.text.strip() == "Ok.": # Проверяем, что торрент действительно добавился @@ -1394,12 +1163,13 @@ async def add_torrent_to_client(torrent_id: str = Form(...), magnet: str = Form( if torrents_response.status_code == 200: torrents = torrents_response.json() - # Ищем торрент по названию (первые 30 символов) + # Ищем торрент по названию (первые 20 символов для точного совпадения) added_torrent = None for torrent in torrents: torrent_name = torrent.get('name', '').lower() + # Проверяем совпадение по началу названия if torrent_title and torrent_name: - if torrent_title[:30] in torrent_name or torrent_name[:30] in torrent_title: + if torrent_title[:20] in torrent_name or torrent_name[:20] in torrent_title: added_torrent = torrent break @@ -1411,11 +1181,12 @@ async def add_torrent_to_client(torrent_id: str = Form(...), magnet: str = Form( "torrent_name": added_torrent.get('name', torrent_info.get('title', 'Unknown')) } - # Торрент не появился — честный error вместо ложного success - print(f"WARNING: qBittorrent returned Ok. for .torrent but torrent was not found in list (title={torrent_title or 'empty'})") + # Если не нашли по названию, но ответ был Ok, считаем успешным return { - "status": "error", - "message": f"qBittorrent не добавил торрент. Возможно, файл недоступен или DPI-блокировка. Попробуйте magnet-ссылку." + "status": "success", + "message": f"Торрент '{torrent_info.get('title', 'Unknown')[:50]}...' добавлен в qBittorrent через .torrent файл (проверьте список торрентов в qBittorrent)!", + "torrent_hash": torrent_info.get('hash', ''), + "torrent_name": torrent_info.get('title', 'Unknown') } else: error_msg = add_response.text.strip() diff --git a/app/.env b/app/.env deleted file mode 100644 index 2dc8d8a..0000000 --- a/app/.env +++ /dev/null @@ -1,6 +0,0 @@ -TELEGRAM_BOT_TOKEN=7662650066:AAFgsfYJNYgpcSHaSe6fspsjqmhMkOBT1s4 -NL_HOST=72.56.91.135 -QBITTORRENT_USERNAME=vrubelroman -QBITTORRENT_PASSWORD=VRKshtein07 -QBITTORRENT_HOST=192.168.8.177 -QBITTORRENT_PORT=8080 diff --git a/app/.env.example b/app/.env.example deleted file mode 100644 index 55d604b..0000000 --- a/app/.env.example +++ /dev/null @@ -1,12 +0,0 @@ -# 🔧 App Stack — переменные окружения (RU-хост) -# Скопируйте в .env и заполните своими данными - -# 🎬 NL-хост (голландский сервер) -# IP, на котором запущен search-стек (tmdb-proxy + torapi) -NL_HOST=72.56.91.135 - -# 🤖 Telegram Bot Token (от @BotFather) -TELEGRAM_BOT_TOKEN=ваш_то...n -# 🐳 qBittorrent (на этом же хосте, порт 8080) -QBITTORRENT_USERNAME=vrubelroman -QBITTORRENT_PASSWORD=ваш_па... diff --git a/app/docker-compose.yml b/app/docker-compose.yml deleted file mode 100644 index c117659..0000000 --- a/app/docker-compose.yml +++ /dev/null @@ -1,92 +0,0 @@ -# 🏠 App Stack — сервисы, запускаемые на хосте в России (192.168.8.173) -# Веб-интерфейс + Telegram бот для поиска и скачивания фильмов -# -# ⚡ Запуск: -# cd app && docker compose up -d --build -# -# 📋 Перед запуском создайте .env из .env.example - -services: - # ============================================================ - # 🌐 Веб-приложение + API - # ============================================================ - movie-search: - build: . - container_name: movie-search - env_file: - - .env - environment: - # NL-сервисы (поиск фильмов и торрентов, без блокировок) - - TMDB_PROXY_URL=http://${NL_HOST:-72.56.91.135}:8001 - - TORRENT_SEARCH_URL=http://${NL_HOST:-72.56.91.135}:8443 - - # Локальный torapi-qbit — резолвит magnet через qBittorrent - - TORRENT_ADD_URL=http://app-torapi-qbit:8443 - - TORAPI_ADD_URL=http://app-torapi-qbit:8443 - - # qBittorrent (на 192.168.8.177) - - QBITTORRENT_USERNAME=${QBITTORRENT_USERNAME:-vrubelroman} - - QBITTORRENT_PASSWORD=${QBITTORRENT_PASSWORD:-VRKshtein07} - - QBITTORRENT_HOST=${QBITTORRENT_HOST:-192.168.8.177} - - QBITTORRENT_PORT=${QBITTORRENT_PORT:-8080} - - - HOST=0.0.0.0 - - PORT=8000 - ports: - - "0.0.0.0:8089:8000" - restart: unless-stopped - networks: - - app-stack - depends_on: - - app-torapi-qbit - - # ============================================================ - # 🤖 Telegram бот - # ============================================================ - telegram-bot: - build: - context: . - dockerfile: Dockerfile.telegram - container_name: telegram-bot-findFilms - env_file: - - .env - environment: - # NL-сервисы (поиск) - - TMDB_PROXY_URL=http://${NL_HOST:-72.56.91.135}:8001 - - TORRENT_SEARCH_URL=http://${NL_HOST:-72.56.91.135}:8443 - - # Локальный torapi-qbit - - TORRENT_ADD_URL=http://app-torapi-qbit:8443 - - # qBittorrent (на 192.168.8.177) - - QBITTORRENT_USERNAME=${QBITTORRENT_USERNAME:-vrubelroman} - - QBITTORRENT_PASSWORD=${QBITTORRENT_PASSWORD:-VRKshtein07} - - QBITTORRENT_HOST=${QBITTORRENT_HOST:-192.168.8.177} - - QBITTORRENT_PORT=${QBITTORRENT_PORT:-8080} - restart: unless-stopped - networks: - - app-stack - depends_on: - - movie-search - - # ============================================================ - # 🔗 TorAPI → qBittorrent bridge — magnet ссылки - # Проксирует запросы к qBittorrent для получения magnet-хэшей - # ============================================================ - app-torapi-qbit: - image: lifailon/torapi:latest - container_name: app-torapi-qbit - environment: - - USERNAME=${QBITTORRENT_USERNAME:-vrubelroman} - - PASSWORD=${QBITTORRENT_PASSWORD:-VRKshtein07} - - PROXY_ADDRESS=${QBITTORRENT_HOST:-192.168.8.177} - - PROXY_PORT=${QBITTORRENT_PORT:-8080} - ports: - - "0.0.0.0:8088:8443" - restart: unless-stopped - networks: - - app-stack - -networks: - app-stack: - driver: bridge diff --git a/app/test_app.py b/app/test_app.py deleted file mode 100644 index b1ad387..0000000 --- a/app/test_app.py +++ /dev/null @@ -1,273 +0,0 @@ -#!/usr/bin/env python3 -""" -Тесты для app.py — изолированные, без внешних зависимостей. -Запуск: python3 -m unittest test_app -v -""" -import sys -import os -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -import unittest -from app import ( - extract_hash_from_result, - generate_clean_magnet, - parse_size_to_bytes, - parse_size, - extract_resolution, - extract_quality, - generate_search_variants, - score_torrent, - normalize_search_term, - is_movie_torrent, -) - - -class TestExtractHash(unittest.TestCase): - - def test_direct_hash_field(self): - result = {"Hash": "08ada5a7a6183aae1e09d831df6748d566095a10"} - self.assertEqual(extract_hash_from_result(result), "08ADA5A7A6183AAE1E09D831DF6748D566095A10") - - def test_hash_from_magnet(self): - result = { - "Hash": "", - "Magnet": "magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10&dn=test" - } - self.assertEqual(extract_hash_from_result(result), "08ADA5A7A6183AAE1E09D831DF6748D566095A10") - - def test_hash_from_info_hash(self): - result = {"InfoHash": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0"} - self.assertEqual( - extract_hash_from_result(result), - "A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8A9B0" - ) - - def test_empty_result(self): - self.assertEqual(extract_hash_from_result({}), "") - self.assertEqual(extract_hash_from_result({"Hash": ""}), "") - - def test_hash_from_torrent_url_no_btih(self): - result = {"Torrent": "https://rutracker.org/forum/dl.php?t=123456"} - self.assertEqual(extract_hash_from_result(result), "") - - def test_case_insensitive_hash(self): - result = {"Hash": "08ada5a7a6183aae1e09d831df6748d566095a10"} - self.assertEqual(extract_hash_from_result(result), "08ADA5A7A6183AAE1E09D831DF6748D566095A10") - - def test_id_field_as_hash(self): - result = {"Id": "08ada5a7a6183aae1e09d831df6748d566095a10"} - self.assertEqual(extract_hash_from_result(result), "08ADA5A7A6183AAE1E09D831DF6748D566095A10") - - def test_non_hash_id_field(self): - result = {"Id": "12345", "Hash": ""} - self.assertEqual(extract_hash_from_result(result), "") - - def test_hash_from_lowercase_field(self): - result = {"hash": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0"} - self.assertEqual( - extract_hash_from_result(result), - "A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8A9B0" - ) - - -class TestGenerateCleanMagnet(unittest.TestCase): - - def test_basic_magnet(self): - magnet = generate_clean_magnet("08ada5a7a6183aae1e09d831df6748d566095a10", "Test Movie") - self.assertTrue(magnet.startswith("magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10")) - self.assertIn("&dn=Test%20Movie", magnet) - self.assertIn("&tr=udp://tracker.opentrackr.org:1337/announce", magnet) - self.assertIn("&tr=udp://tracker.openbittorrent.com:6969/announce", magnet) - - def test_empty_hash(self): - self.assertEqual(generate_clean_magnet(""), "") - self.assertEqual(generate_clean_magnet(None), "") - - def test_no_title(self): - magnet = generate_clean_magnet("08ada5a7a6183aae1e09d831df6748d566095a10") - self.assertTrue(magnet.startswith("magnet:?xt=urn:btih:")) - self.assertNotIn("&dn=", magnet) - - def test_cyrillic_title(self): - magnet = generate_clean_magnet("a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0", "Терминатор 2") - self.assertIn("&dn=%D0%A2%D0%B5%D1%80%D0%BC%D0%B8%D0%BD%D0%B0%D1%82%D0%BE%D1%80%202", magnet) - - def test_has_trackers(self): - magnet = generate_clean_magnet("a" * 40) - expected_trackers = [ - "tracker.opentrackr.org", - "open.stealth.si", - "tracker.openbittorrent.com", - "tracker.tamersunion.org", - "exodus.desync.com", - "tracker.moeking.me", - ] - for tracker in expected_trackers: - self.assertIn(tracker, magnet, f"Missing tracker: {tracker}") - - def test_no_dead_trackers(self): - magnet = generate_clean_magnet("a" * 40) - self.assertNotIn("coppersurfer.tk", magnet) - self.assertNotIn("leechers-paradise.org", magnet) - - -class TestParseSize(unittest.TestCase): - - def test_gb(self): - bytes_size, readable = parse_size("25.3 GB") - self.assertEqual(bytes_size, int(25.3 * 1024**3)) - self.assertEqual(readable, "25.3 GB") - - def test_mb(self): - bytes_size, readable = parse_size("6.2 MB") - self.assertEqual(bytes_size, int(6.2 * 1024**2)) - - def test_kb(self): - bytes_size, readable = parse_size("512 KB") - self.assertEqual(bytes_size, 512 * 1024) - - def test_empty(self): - bytes_size, readable = parse_size("") - self.assertEqual(bytes_size, 0) - self.assertEqual(readable, "Неизвестно") - - def test_none(self): - bytes_size, readable = parse_size(None) - self.assertEqual(bytes_size, 0) - self.assertEqual(readable, "Неизвестно") - - -class TestParseSizeToBytes(unittest.TestCase): - - def test_gb(self): - result = parse_size_to_bytes("25.3 GB") - self.assertAlmostEqual(result, 25.3 * 1024**3, delta=1) - - def test_mb(self): - result = parse_size_to_bytes("500 MB") - self.assertEqual(result, 500 * 1024**2) - - def test_empty(self): - self.assertEqual(parse_size_to_bytes(""), 0) - - -class TestExtractResolution(unittest.TestCase): - - def test_2160p(self): - self.assertEqual(extract_resolution("Movie 2024 UHD 2160p BluRay"), "2160p") - - def test_4k(self): - self.assertEqual(extract_resolution("Movie 2024 4K HDR"), "2160p") - - def test_1080p(self): - self.assertEqual(extract_resolution("Movie 2024 1080p WEB-DL"), "1080p") - - def test_720p(self): - self.assertEqual(extract_resolution("Movie 2024 720p HDTV"), "720p") - - def test_unknown(self): - self.assertEqual(extract_resolution("Movie Unknown Quality"), "Неизвестно") - - -class TestExtractQuality(unittest.TestCase): - - def test_bluray(self): - self.assertEqual(extract_quality("Movie 2024 Bluray 1080p"), "BluRay") - - def test_bdrip(self): - self.assertEqual(extract_quality("Movie 2024 BDRip 720p"), "BluRay") - - def test_webdl(self): - self.assertEqual(extract_quality("Movie 2024 WEB-DL 1080p"), "WEB-DL") - - def test_webrip(self): - self.assertEqual(extract_quality("Movie 2024 WEBRip 720p"), "WEBRip") - - def test_hdtv(self): - self.assertEqual(extract_quality("Movie 2024 HDTV 720p"), "HDTV") - - def test_unknown(self): - self.assertEqual(extract_quality("Movie 2024 Some Quality"), "Unknown") - - -class TestNormalizeSearchTerm(unittest.TestCase): - - def test_basic(self): - self.assertEqual(normalize_search_term("The Terminator"), "Terminator") - - def test_with_articles(self): - self.assertEqual(normalize_search_term("The Matrix Revolutions"), "Matrix.Revolutions") - - def test_empty(self): - self.assertEqual(normalize_search_term(""), "") - self.assertEqual(normalize_search_term(None), "") - - def test_spaces_to_dots(self): - self.assertEqual(normalize_search_term("Harry Potter"), "Harry.Potter") - - -class TestGenerateSearchVariants(unittest.TestCase): - - def test_basic(self): - variants = generate_search_variants("Terminator", "The Terminator", "1984") - self.assertIn("Terminator", variants) - self.assertIn("The Terminator", variants) - self.assertIn("Terminator 1984", variants) - - def test_terminator_special(self): - variants = generate_search_variants("Терминатор 2", "Terminator 2", "1991") - self.assertIn("T2", variants) - self.assertIn("T2.Judgment.Day", variants) - - def test_no_duplicates(self): - variants = generate_search_variants("Terminator", "Terminator", "1984") - self.assertEqual(len(variants), len(set(variants))) - - -class TestScoreTorrent(unittest.TestCase): - - def test_high_score_exact_match(self): - torrent = { - "title": "The Terminator 1984 1080p BluRay", - "quality": "BluRay", - "resolution": "1080p", - "seeds": 150, - } - score = score_torrent(torrent, "Terminator", "The Terminator", "1984") - self.assertGreater(score, 0.8) - - def test_low_score_no_match(self): - torrent = { - "title": "Some Other Movie 2023 720p WEB-DL", - "quality": "WEB-DL", - "resolution": "720p", - "seeds": 5, - } - score = score_torrent(torrent, "Terminator", "The Terminator", "1984") - self.assertAlmostEqual(score, 0.3) - - def test_score_capped_at_one(self): - torrent = { - "title": "The Terminator 1984 2160p BluRay", - "quality": "BluRay", - "resolution": "2160p", - "seeds": 200, - } - score = score_torrent(torrent, "The Terminator", "The Terminator", "1984") - self.assertLessEqual(score, 1.0) - - -class TestIsMovieTorrent(unittest.TestCase): - - def test_is_movie(self): - self.assertTrue(is_movie_torrent("The Terminator 1984 BluRay", "Terminator", "The Terminator")) - - def test_not_movie_excluded_keyword(self): - self.assertFalse(is_movie_torrent("The Game 1997 1080p BluRay", "The Game", "")) - - def test_not_movie_unknown_title(self): - self.assertFalse(is_movie_torrent("Some Other Movie 2023 720p", "Terminator", "The Terminator")) - - -if __name__ == "__main__": - unittest.main() diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..d8b4fe4 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,263 @@ +#!/bin/bash + +# 🚀 Скрипт развертывания searchTorrentDownl на новом компьютере +# Автор: AI Assistant +# Версия: 1.0 + +set -e # Остановка при ошибке + +echo "🎬 searchTorrentDownl - Скрипт развертывания" +echo "==============================================" +echo "" + +# Цвета для вывода +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Функция для вывода сообщений +log_info() { + echo -e "${BLUE}ℹ️ $1${NC}" +} + +log_success() { + echo -e "${GREEN}✅ $1${NC}" +} + +log_warning() { + echo -e "${YELLOW}⚠️ $1${NC}" +} + +log_error() { + echo -e "${RED}❌ $1${NC}" +} + +# Проверка операционной системы +check_os() { + log_info "Проверка операционной системы..." + + if [[ "$OSTYPE" == "linux-gnu"* ]]; then + log_success "Linux обнаружен" + else + log_error "Этот скрипт предназначен для Linux. Обнаружена ОС: $OSTYPE" + exit 1 + fi +} + +# Проверка прав root +check_root() { + if [[ $EUID -eq 0 ]]; then + log_warning "Скрипт запущен от root. Рекомендуется запускать от обычного пользователя." + read -p "Продолжить? (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 + fi + fi +} + +# Обновление системы +update_system() { + log_info "Обновление системы..." + sudo apt update && sudo apt upgrade -y + log_success "Система обновлена" +} + +# Установка Docker +install_docker() { + log_info "Проверка Docker..." + + if command -v docker &> /dev/null; then + log_success "Docker уже установлен" + else + log_info "Установка Docker..." + curl -fsSL https://get.docker.com -o get-docker.sh + sudo sh get-docker.sh + sudo usermod -aG docker $USER + rm get-docker.sh + log_success "Docker установлен" + fi + + # Проверка Docker Compose + if command -v docker-compose &> /dev/null; then + log_success "Docker Compose уже установлен" + else + log_info "Установка Docker Compose..." + sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose + log_success "Docker Compose установлен" + fi +} + +# Установка qBittorrent +install_qbittorrent() { + log_info "Проверка qBittorrent..." + + if command -v qbittorrent-nox &> /dev/null; then + log_success "qBittorrent уже установлен" + else + log_info "Установка qBittorrent-nox..." + sudo apt install -y qbittorrent-nox + log_success "qBittorrent установлен" + fi +} + +# Настройка qBittorrent +setup_qbittorrent() { + log_info "Настройка qBittorrent..." + + # Создание пользователя qbittorrent если не существует + if ! id "qbittorrent" &>/dev/null; then + sudo useradd -r -s /bin/false qbittorrent + fi + + # Создание директории для конфигурации + sudo mkdir -p /home/qbittorrent/.config/qBittorrent + sudo chown -R qbittorrent:qbittorrent /home/qbittorrent + + # Создание systemd сервиса + sudo tee /etc/systemd/system/qbittorrent.service > /dev/null <" + log_info "cd searchTorrentDownl" + exit 1 + fi +} + +# Настройка переменных окружения +setup_environment() { + log_info "Настройка переменных окружения..." + + if [ ! -f ".env" ]; then + log_info "Создание файла .env..." + cat > .env </dev/null || true + + # Сборка и запуск + docker compose up -d --build + + log_success "Сервисы запущены" +} + +# Проверка статуса +check_status() { + log_info "Проверка статуса сервисов..." + + echo "" + echo "📊 Статус контейнеров:" + docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" + + echo "" + echo "🌐 Доступные сервисы:" + echo " • Веб-интерфейс: http://localhost:8089" + echo " • qBittorrent: http://localhost:8080 (admin/admin)" + echo " • Telegram Bot: @your_bot_username (команда /start)" + + echo "" + echo "📝 Следующие шаги:" + echo " 1. Откройте http://localhost:8080 и настройте qBittorrent" + echo " 2. Отредактируйте .env файл с вашими API ключами" + echo " 3. Перезапустите сервисы: docker compose restart" + echo " 4. Откройте http://localhost:8089 для тестирования" +} + +# Основная функция +main() { + echo "🚀 Начинаем развертывание searchTorrentDownl..." + echo "" + + check_os + check_root + update_system + install_docker + install_qbittorrent + setup_qbittorrent + create_docker_network + clone_repository + setup_environment + start_services + check_status + + echo "" + log_success "🎉 Развертывание завершено успешно!" + echo "" + log_info "Для остановки сервисов: docker compose down" + log_info "Для просмотра логов: docker compose logs -f" + log_info "Для перезапуска: docker compose restart" +} + +# Запуск +main "$@" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ee05a93 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,81 @@ +services: + movie-search: + build: . + container_name: movie-search + env_file: + - .env + environment: + - HOST=0.0.0.0 + - PORT=8000 + - TORAPI_URL=http://torrent-api:8000 + - TORRENT_ADD_URL=http://host.docker.internal:8088 + - QBITTORRENT_USERNAME=vrubelroman + - QBITTORRENT_PASSWORD=vrubel07 + - QBITTORRENT_HOST=host.docker.internal + - QBITTORRENT_PORT=8082 + ports: + - "0.0.0.0:8089:8000" + restart: unless-stopped + extra_hosts: + - "host.docker.internal:host-gateway" + networks: + - torrentvideo_default + - default + + torapi-search: + image: lifailon/torapi:latest + container_name: TorAPI-Search + environment: + - USERNAME= + - PASSWORD= + ports: + - "8443:8443" + restart: unless-stopped + networks: + - torrentvideo_default + - default + + torapi-qbittorrent: + image: lifailon/torapi:latest + container_name: TorAPI-qBittorrent + environment: + - USERNAME=vrubelroman + - PASSWORD=vrubel07 + - PROXY_ADDRESS=host.docker.internal + - PROXY_PORT=8082 + ports: + - "8444:8443" + restart: unless-stopped + extra_hosts: + - "host.docker.internal:host-gateway" + networks: + - torrentvideo_default + - default + + telegram-bot: + build: + context: . + dockerfile: Dockerfile.telegram + container_name: telegram-bot-findFilms + env_file: + - .env + environment: + - TORRENT_ADD_URL=http://host.docker.internal:8088 + - QBITTORRENT_USERNAME=vrubelroman + - QBITTORRENT_PASSWORD=vrubel07 + - QBITTORRENT_HOST=host.docker.internal + - QBITTORRENT_PORT=8082 + restart: unless-stopped + extra_hosts: + - "host.docker.internal:host-gateway" + networks: + - torrentvideo_default + - default + depends_on: + - movie-search + - torapi-search + - torapi-qbittorrent + +networks: + torrentvideo_default: + external: true diff --git a/env.example b/env.example new file mode 100644 index 0000000..1db7407 --- /dev/null +++ b/env.example @@ -0,0 +1,29 @@ +# 🔧 Конфигурация searchTorrentDownl +# Скопируйте этот файл в .env и заполните своими данными + +# 🎬 TMDB Proxy URL +# URL прокси-сервиса TMDB (работает на хосте без VPN) +# Если прокси на другом хосте, укажите его IP: http://:8001 +# Если прокси на том же хосте: http://localhost:8001 +TMDB_PROXY_URL=72.56.91.135:8001 + +# 🤖 Telegram Bot Token +# Получите у @BotFather в Telegram +TELEGRAM_BOT_TOKEN=7662650066:AAFgsfYJNYgpcSHaSe6fspsjqmhMkOBT1s4 + +# 🐳 qBittorrent настройки +QBITTORRENT_USERNAME=admin +QBITTORRENT_PASSWORD=admin +QBITTORRENT_HOST=host.docker.internal +QBITTORRENT_PORT=8082 + +# 🔍 TorAPI настройки +# URL TorAPI для поиска торрентов (работает на хосте с VPN) +# Если TorAPI на другом хосте, укажите его IP: http://:8443 +# Если TorAPI на том же хосте: http://localhost:8443 +TORRENT_SEARCH_URL=http://72.56.91.135:8443 +TORRENT_ADD_URL=http://host.docker.internal:8088 + +# 🌐 Основное приложение +HOST=0.0.0.0 +PORT=8000 diff --git a/app/requirements.txt b/requirements.txt similarity index 100% rename from app/requirements.txt rename to requirements.txt diff --git a/app/run_telegram_bot.py b/run_telegram_bot.py similarity index 100% rename from app/run_telegram_bot.py rename to run_telegram_bot.py diff --git a/searchFilms/.env b/searchFilms/.env deleted file mode 100644 index 0e80f30..0000000 --- a/searchFilms/.env +++ /dev/null @@ -1 +0,0 @@ -TMDB_API_KEY=6d58225585fb77af5945a964de41849f diff --git a/searchFilms/.env.example b/searchFilms/.env.example deleted file mode 100644 index df5222b..0000000 --- a/searchFilms/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -# Search Stack — NL-хост (72.56.91.135) - -# 🎬 TMDB API Key -# Получить: https://www.themoviedb.org/settings/api -TMDB_API_KEY=ваш_кл... diff --git a/searchFilms/docker-compose.yml b/searchFilms/docker-compose.yml deleted file mode 100644 index 6d7acee..0000000 --- a/searchFilms/docker-compose.yml +++ /dev/null @@ -1,31 +0,0 @@ -services: - tmdb-proxy: - build: ./tmdb-proxy - container_name: search-tmdb-proxy - environment: - - TMDB_API_KEY=$TMDB_API_KEY - - PORT=8001 - ports: - - "0.0.0.0:8001:8001" - restart: unless-stopped - dns: - - 8.8.8.8 - - 8.8.4.4 - networks: - - search-stack - - torapi-search: - image: lifailon/torapi:latest - container_name: search-torapi - environment: - - USERNAME= - - PASSWORD= - ports: - - "0.0.0.0:8443:8443" - restart: unless-stopped - networks: - - search-stack - -networks: - search-stack: - driver: bridge diff --git a/start_all.sh b/start_all.sh new file mode 100755 index 0000000..3aa257e --- /dev/null +++ b/start_all.sh @@ -0,0 +1,85 @@ +#!/bin/bash + +# Скрипт для запуска всего проекта searchTorrentDownl +# Включает веб-приложение и Telegram бота + +echo "🚀 Запуск проекта searchTorrentDownl..." + +# Проверяем, что мы в правильной директории +if [ ! -f "app.py" ]; then + echo "❌ Ошибка: Запустите скрипт из директории проекта" + exit 1 +fi + +# Создаем виртуальное окружение если его нет +if [ ! -d "venv" ]; then + echo "📦 Создание виртуального окружения..." + python3 -m venv venv +fi + +# Активируем виртуальное окружение +echo "🔧 Активация виртуального окружения..." +source venv/bin/activate + +# Устанавливаем зависимости +echo "📥 Установка зависимостей..." +pip install -r requirements.txt + +# Проверяем, что qBittorrent запущен +echo "🔍 Проверка qBittorrent..." +if ! curl -s http://localhost:8080/api/v2/app/version > /dev/null; then + echo "⚠️ qBittorrent не запущен. Запустите его командой:" + echo " sudo systemctl start qbittorrent" + echo " или" + echo " sudo -u qbittorrent /usr/bin/qbittorrent-nox --webui-port=8080" + echo "" + echo "🔧 Продолжаем без qBittorrent (поиск фильмов будет работать)..." +fi + +# Запускаем основное приложение в фоне +echo "🌐 Запуск веб-приложения..." +python3 app.py & +APP_PID=$! + +# Ждем немного, чтобы приложение запустилось +sleep 3 + +# Проверяем, что приложение запустилось +if ! curl -s http://localhost:8089/api/search/terminator > /dev/null; then + echo "❌ Ошибка запуска веб-приложения" + kill $APP_PID 2>/dev/null + exit 1 +fi + +echo "✅ Веб-приложение запущено на http://localhost:8089" + +# Запускаем Telegram бота +echo "🤖 Запуск Telegram бота..." +python3 run_telegram_bot.py & +BOT_PID=$! + +echo "" +echo "🎉 Проект успешно запущен!" +echo "" +echo "📱 Доступные интерфейсы:" +echo " • Веб-интерфейс: http://localhost:8089" +echo " • qBittorrent: http://localhost:8080 (admin/vrubel07)" +echo " • Telegram Bot: @your_bot_username (команда /start)" +echo "" +echo "🛑 Для остановки нажмите Ctrl+C" + +# Функция для корректного завершения +cleanup() { + echo "" + echo "🛑 Остановка сервисов..." + kill $APP_PID 2>/dev/null + kill $BOT_PID 2>/dev/null + echo "✅ Все сервисы остановлены" + exit 0 +} + +# Перехватываем сигнал завершения +trap cleanup SIGINT SIGTERM + +# Ждем завершения +wait diff --git a/start_all_services.sh b/start_all_services.sh new file mode 100755 index 0000000..64f0eef --- /dev/null +++ b/start_all_services.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +# Скрипт для запуска всех сервисов findFilms +echo "🚀 Запуск всех сервисов findFilms..." + +# Переходим в директорию проекта +cd /Users/admin/Documents/PROJECTS/TorrentFilm/findFilms + +# Запускаем qBittorrent локально (если не запущен) +if ! pgrep -f "qbittorrent.*--webui-port=8082" > /dev/null; then + echo "📱 Запуск qBittorrent..." + /Applications/qBittorrent.app/Contents/MacOS/qbittorrent --webui-port=8082 --no-splash --confirm-legal-notice & + sleep 5 +fi + +# Запускаем Docker сервисы +echo "🐳 Запуск Docker сервисов..." +docker compose up -d + +# Проверяем статус +echo "📊 Проверка статуса сервисов..." +sleep 5 + +echo "" +echo "🎉 Все сервисы запущены!" +echo "" +echo "📱 Доступные интерфейсы:" +echo " • Веб-интерфейс: http://localhost:8089" +echo " • qBittorrent: http://localhost:8082 (admin/vrubel07)" +echo " • Telegram Bot: @your_bot_username" +echo "" +echo "🔧 Управление:" +echo " • Остановить все: docker compose down" +echo " • Перезапустить: docker compose restart" +echo " • Логи: docker compose logs -f" + + diff --git a/start_ubuntu.sh b/start_ubuntu.sh new file mode 100755 index 0000000..0399e35 --- /dev/null +++ b/start_ubuntu.sh @@ -0,0 +1,177 @@ +#!/bin/bash + +# Скрипт для запуска всех сервисов findFilms на Ubuntu +# Устанавливает qBittorrent, настраивает systemd и запускает Docker контейнеры + +set -e # Остановка при ошибке + +echo "🚀 Запуск findFilms для Ubuntu" +echo "==============================================" +echo "" + +# Цвета для вывода +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +log_info() { + echo -e "${BLUE}ℹ️ $1${NC}" +} + +log_success() { + echo -e "${GREEN}✅ $1${NC}" +} + +log_warning() { + echo -e "${YELLOW}⚠️ $1${NC}" +} + +log_error() { + echo -e "${RED}❌ $1${NC}" +} + +# Проверка директории +if [ ! -f "app.py" ]; then + log_error "Запустите скрипт из директории проекта findFilms" + exit 1 +fi + +# Шаг 1: Проверка и установка Docker +log_info "Проверка Docker..." +if ! command -v docker &> /dev/null; then + log_warning "Docker не найден, установка Docker..." + curl -fsSL https://get.docker.com -o get-docker.sh + sudo sh get-docker.sh + sudo usermod -aG docker $USER + rm get-docker.sh + log_success "Docker установлен" + log_warning "Перезапустите терминал или выполните: newgrp docker" + exit 0 +else + log_success "Docker найден" +fi + +# Проверка Docker Compose +if ! command -v docker compose &> /dev/null; then + log_warning "Docker Compose не найден, установка..." + sudo apt-get update + sudo apt-get install -y docker-compose-plugin + log_success "Docker Compose установлен" +fi + +# Шаг 2: Установка qBittorrent +log_info "Проверка qBittorrent..." +if ! command -v qbittorrent-nox &> /dev/null; then + log_warning "qBittorrent не найден, установка..." + sudo apt-get update + sudo apt-get install -y qbittorrent-nox + log_success "qBittorrent установлен" +else + log_success "qBittorrent найден" +fi + +# Шаг 3: Настройка systemd сервиса для qBittorrent +log_info "Настройка qBittorrent systemd сервиса..." + +# Создание пользователя qbittorrent если не существует +if ! id "qbittorrent" &>/dev/null; then + sudo useradd -r -s /bin/false qbittorrent + log_info "Создан пользователь qbittorrent" +fi + +# Создание systemd сервиса +sudo tee /etc/systemd/system/qbittorrent.service > /dev/null <<'EOF' +[Unit] +Description=qBittorrent-nox +After=network.target + +[Service] +Type=simple +User=qbittorrent +Group=qbittorrent +ExecStart=/usr/bin/qbittorrent-nox --webui-port=8082 +Restart=always +RestartSec=5 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target +EOF + +log_success "qBittorrent systemd сервис настроен" + +# Перезагрузка systemd и запуск сервиса +sudo systemctl daemon-reload +sudo systemctl enable qbittorrent +sudo systemctl restart qbittorrent + +log_success "qBittorrent запущен и настроен на автозапуск" + +# Проверка: нужна ли настройка пароля в qBittorrent +sleep 3 +echo "" +log_warning "⚠️ ВАЖНО: Настройка qBittorrent" +echo "" +echo "🔐 qBittorrent может использовать временные credentials." +echo "Для корректной работы всех сервисов необходимо установить:" +echo "" +echo " 1. Откройте: http://localhost:8082" +echo " 2. Войдите (используйте admin/admin если это первый запуск)" +echo " 3. Перейдите в: Tools → Options → Web UI" +echo " 4. Установите:" +echo " • Username: admin" +echo " • Password: vrubel07" +echo " 5. Нажмите 'Save' внизу страницы" +echo "" +echo "📝 Эти credentials необходимы для корректной работы всех сервисов" +echo "" +echo -n "Нажмите Enter когда закончите настройку qBittorrent..." +read + +log_info "Веб-интерфейс: http://localhost:8082" +log_info "Логин: admin / vrubel07" + +# Шаг 4: Создание Docker сети +log_info "Создание Docker сети..." +if ! docker network ls | grep -q "torrentvideo_default"; then + docker network create torrentvideo_default + log_success "Сеть torrentvideo_default создана" +else + log_success "Сеть torrentvideo_default уже существует" +fi + +# Шаг 5: Остановка существующих контейнеров +log_info "Остановка существующих контейнеров..." +docker compose down 2>/dev/null || true + +# Шаг 6: Запуск Docker сервисов +log_info "Запуск Docker сервисов..." +docker compose up -d --build + +# Ждем немного для старта +sleep 5 + +# Проверка статуса +log_info "Проверка статуса сервисов..." +echo "" +docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "(NAMES|movie-search|TorAPI|telegram-bot)" || true + +echo "" +log_success "🎉 Все сервисы запущены!" +echo "" +echo "📱 Доступные интерфейсы:" +echo " • Веб-интерфейс: http://localhost:8089" +echo " • qBittorrent: http://localhost:8082 (admin/vrubel07)" +echo " • Telegram Bot: @your_bot_username" +echo "" +echo "🔧 Управление:" +echo " • Остановить Docker: docker compose down" +echo " • Остановить qBittorrent: sudo systemctl stop qbittorrent" +echo " • Перезапустить все: sudo systemctl restart qbittorrent && docker compose restart" +echo " • Логи Docker: docker compose logs -f" +echo " • Логи qBittorrent: sudo journalctl -u qbittorrent -f" +echo "" + diff --git a/stop_all_services.sh b/stop_all_services.sh new file mode 100755 index 0000000..a5a7da0 --- /dev/null +++ b/stop_all_services.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# Скрипт для остановки всех сервисов findFilms +echo "🛑 Остановка всех сервисов findFilms..." + +# Переходим в директорию проекта +cd /Users/admin/Documents/PROJECTS/TorrentFilm/findFilms + +# Останавливаем Docker сервисы +echo "🐳 Остановка Docker сервисов..." +docker compose down + +# Останавливаем qBittorrent +echo "📱 Остановка qBittorrent..." +pkill -f "qbittorrent.*--webui-port=8082" + +echo "" +echo "✅ Все сервисы остановлены!" +echo "" +echo "🔧 Для запуска используйте: ./start_all_services.sh" + + diff --git a/app/telegram_bot.py b/telegram_bot.py similarity index 100% rename from app/telegram_bot.py rename to telegram_bot.py diff --git a/app/templates/error.html b/templates/error.html similarity index 100% rename from app/templates/error.html rename to templates/error.html diff --git a/app/templates/index.html b/templates/index.html similarity index 100% rename from app/templates/index.html rename to templates/index.html diff --git a/app/templates/results.html b/templates/results.html similarity index 100% rename from app/templates/results.html rename to templates/results.html diff --git a/app/templates/torrents.html b/templates/torrents.html similarity index 53% rename from app/templates/torrents.html rename to templates/torrents.html index 6247203..d1afc0a 100644 --- a/app/templates/torrents.html +++ b/templates/torrents.html @@ -18,7 +18,10 @@ border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } - h1 { color: #333; margin-bottom: 20px; } + h1 { + color: #333; + margin-bottom: 20px; + } .back-link { display: inline-block; margin-bottom: 20px; @@ -28,14 +31,20 @@ border: 1px solid #007bff; border-radius: 5px; } - .back-link:hover { background-color: #007bff; color: white; } + .back-link:hover { + background-color: #007bff; + color: white; + } .movie-info { background-color: #e9ecef; padding: 15px; border-radius: 5px; margin-bottom: 20px; } - .torrents-list { display: grid; gap: 15px; } + .torrents-list { + display: grid; + gap: 15px; + } .torrent-item { border: 1px solid #ddd; border-radius: 8px; @@ -44,7 +53,9 @@ box-shadow: 0 2px 5px rgba(0,0,0,0.1); transition: box-shadow 0.3s ease; } - .torrent-item:hover { box-shadow: 0 4px 10px rgba(0,0,0,0.15); } + .torrent-item:hover { + box-shadow: 0 4px 10px rgba(0,0,0,0.15); + } .torrent-title { font-size: 18px; font-weight: bold; @@ -57,24 +68,30 @@ gap: 15px; margin-bottom: 15px; } - .detail-item { display: flex; flex-direction: column; } + .detail-item { + display: flex; + flex-direction: column; + } .detail-label { font-size: 12px; color: #666; text-transform: uppercase; margin-bottom: 4px; } - .detail-value { font-size: 14px; font-weight: bold; color: #333; } + .detail-value { + font-size: 14px; + font-weight: bold; + color: #333; + } .resolution-1080p { color: #28a745; } .resolution-720p { color: #ffc107; } .resolution-480p { color: #dc3545; } .resolution-2160p { color: #6f42c1; } - + .torrent-actions { display: flex; gap: 10px; margin-top: 15px; - flex-wrap: wrap; } .btn { padding: 8px 16px; @@ -82,25 +99,31 @@ border-radius: 5px; cursor: pointer; text-decoration: none; - display: inline-flex; - align-items: center; - gap: 6px; + display: inline-block; font-size: 14px; transition: background-color 0.3s ease; - white-space: nowrap; } - .btn-primary { background-color: #007bff; color: white; } - .btn-primary:hover { background-color: #0056b3; } - .btn-success { background-color: #28a745; color: white; } - .btn-success:hover { background-color: #1e7e34; } - .btn-secondary { background-color: #6c757d; color: white; } - .btn-secondary:hover { background-color: #545b62; } - .btn-warning { background-color: #ffc107; color: #333; } - .btn-warning:hover { background-color: #e0a800; } - .btn-info { background-color: #17a2b8; color: white; } - .btn-info:hover { background-color: #138496; } - .btn:disabled { opacity: 0.6; cursor: not-allowed; } - + .btn-primary { + background-color: #007bff; + color: white; + } + .btn-primary:hover { + background-color: #0056b3; + } + .btn-success { + background-color: #28a745; + color: white; + } + .btn-success:hover { + background-color: #1e7e34; + } + .btn-secondary { + background-color: #6c757d; + color: white; + } + .btn-secondary:hover { + background-color: #545b62; + } .no-torrents { text-align: center; color: #666; @@ -118,93 +141,69 @@ .quality-bluray { background-color: #007bff; color: white; } .quality-web-dl { background-color: #28a745; color: white; } .quality-hdtv { background-color: #ffc107; color: #333; } - - .toast { - position: fixed; - bottom: 20px; - left: 50%; - transform: translateX(-50%); - background: #333; - color: #fff; - padding: 12px 24px; - border-radius: 8px; - font-size: 14px; - z-index: 1000; - opacity: 0; - transition: opacity 0.3s ease; - pointer-events: none; - } - .toast.show { opacity: 1; } - .toast.success { background: #28a745; } - .toast.error { background: #dc3545; } - - .magnet-link { - word-break: break-all; - font-size: 11px; - color: #666; - margin-top: 8px; - padding: 6px 8px; - background: #f8f9fa; - border-radius: 4px; - display: none; - } - .magnet-link.visible { display: block; }
← Назад к поиску - +

🔍 Торренты для: "{{ movie_title }}"

- +
Фильм: {{ movie_title }} {% if year %}
Год: {{ year }} {% endif %}
- + {% if torrents %}
{% for torrent in torrents %}
{{ torrent.title }}
- +
Размер
{{ torrent.size_readable }}
+
Разрешение
{{ torrent.resolution }}
+
Качество
{{ torrent.quality }}
+
Сиды
{{ torrent.seeds }}
+
Пиры
{{ torrent.peers }}
+ {% if torrent.category %}
Категория
{{ torrent.category }}
{% endif %} + {% if torrent.date %}
Дата
{{ torrent.date }}
{% endif %} + {% if torrent.provider %}
Провайдер
@@ -212,26 +211,11 @@
{% endif %}
- +
- {% if torrent.magnet and torrent.magnet.startswith('magnet:') %} - 🧲 Magnet - - {% else %} - - {% endif %} - - {% if torrent.download_url and torrent.download_url.startswith('http') %} - 💾 Скачать .torrent - {% else %} - - {% endif %} - - {% if torrent.magnet and torrent.magnet.startswith('magnet:') %} - - {% else %} - - {% endif %} + Magnet + Скачать .torrent +
{% endfor %} @@ -243,68 +227,18 @@ {% endif %}
-
-