🎨 Обновление веб-интерфейса и исправления

 Новые возможности:
- Красивый современный веб-интерфейс с градиентным дизайном
- Адаптивный дизайн для мобильных устройств
- Анимированные элементы и эффекты наведения
- Улучшенная типографика и цветовая схема

🔧 Технические улучшения:
- Исправлена проблема с внешним доступом (0.0.0.0:8089:8000)
- Улучшен поиск торрентов по ID на всех провайдерах
- Добавлено подробное логирование и обработка ошибок
- Оптимизирована производительность приложения

📁 Новые файлы:
- MANAGEMENT.md - инструкции по управлению сервисами
- start_all_services.sh - скрипт запуска всех сервисов
- stop_all_services.sh - скрипт остановки всех сервисов

🌐 Доступ:
- Локально: http://localhost:8089
- Внешний: http://84.22.132.114:8089
This commit is contained in:
vrubelroman 2025-10-13 21:11:19 +03:00
parent 90ad38bca7
commit a21654106b
6 changed files with 553 additions and 65 deletions

126
MANAGEMENT.md Normal file
View file

@ -0,0 +1,126 @@
# 🎬 Управление сервисами findFilms
## 🚀 Быстрый старт
### Запуск всех сервисов:
```bash
./start_all_services.sh
```
### Остановка всех сервисов:
```bash
./stop_all_services.sh
```
## 📊 Статус сервисов
### Проверка статуса:
```bash
docker ps | grep -E "(movie-search|TorAPI|telegram-bot)"
```
### Проверка qBittorrent:
```bash
ps aux | grep qbittorrent | grep -v grep
```
## 🔧 Управление Docker контейнерами
### Запуск:
```bash
docker compose up -d
```
### Остановка:
```bash
docker compose down
```
### Перезапуск:
```bash
docker compose restart
```
### Просмотр логов:
```bash
# Все сервисы
docker compose logs -f
# Конкретный сервис
docker logs -f movie-search
docker logs -f telegram-bot
```
## 🌐 Доступные интерфейсы
- **Веб-интерфейс**: http://localhost:8089
- **qBittorrent**: http://localhost:8082 (admin/vrubel07)
- **Telegram Bot**: @your_bot_username
## 🔄 Автозапуск
Все Docker контейнеры настроены на автозапуск при старте системы:
- `movie-search` - веб-приложение
- `TorAPI-Search` - поиск торрентов
- `TorAPI-qBittorrent` - получение magnet ссылок
- `telegram-bot` - Telegram бот
## 🛠️ Устранение неполадок
### Проблема: Сервис не запускается
```bash
# Проверьте логи
docker logs <container_name>
# Перезапустите
docker compose restart <service_name>
```
### Проблема: Конфликт портов
```bash
# Проверьте занятые порты
lsof -i :8089
lsof -i :8082
```
### Проблема: qBittorrent не отвечает
```bash
# Перезапустите qBittorrent
pkill qbittorrent
/Applications/qBittorrent.app/Contents/MacOS/qbittorrent --webui-port=8082 --no-splash --confirm-legal-notice &
```
## 📈 Мониторинг
### Использование ресурсов:
```bash
docker stats
```
### Проверка здоровья:
```bash
# Веб-интерфейс
curl http://localhost:8089/
# qBittorrent API
curl -X POST -d "username=admin&password=vrubel07" http://localhost:8082/api/v2/auth/login
```
## 🔒 Безопасность
- Все пароли настроены в переменных окружения
- qBittorrent доступен только локально
- Telegram боты используют разные токены
## 📝 Логи
Логи всех сервисов доступны через Docker:
```bash
# Последние 50 строк
docker logs --tail 50 <container_name>
# Следить за логами в реальном времени
docker logs -f <container_name>
```

329
app.py
View file

@ -340,22 +340,39 @@ async def search_torrent_by_id(torrent_id: str) -> dict:
async with httpx.AsyncClient() as client:
print(f"Searching torrent by ID: {torrent_id}")
# Используем правильный эндпоинт для поиска по ID
# Получаем список доступных провайдеров
providers_response = await client.get(f"{TORRENT_SEARCH_URL}/api/provider/list")
if providers_response.status_code != 200:
print(f"Failed to get providers: {providers_response.status_code}")
return None
providers = providers_response.json()
print(f"Available providers: {[p['Provider'] for p in providers]}")
# Пробуем найти торрент на всех доступных провайдерах
for provider in providers:
provider_name = provider['Provider'].lower()
try:
print(f"Searching ID {torrent_id} on {provider_name}")
response = await client.get(
f"{TORRENT_SEARCH_URL}/api/search/id/rutracker",
f"{TORRENT_SEARCH_URL}/api/search/id/{provider_name}",
params={"query": torrent_id}
)
if response.status_code == 200:
results = response.json()
if results and len(results) > 0:
print(f"Response from {provider_name}: {type(results)} - {len(results) if isinstance(results, list) else 'not a list'}")
if results and isinstance(results, list) and len(results) > 0:
# Берем первый результат
result = results[0]
print(f"Found torrent by ID: {result.get('Name', 'Unknown')[:100]}...")
# Используем Original_Name если Name пустое
torrent_name = result.get('Name', '') or result.get('Original_Name', '')
print(f"Found torrent by ID on {provider_name}: {torrent_name[:100]}...")
# Получаем хэш и создаем чистую magnet-ссылку с публичными трекерами
hash_value = result.get('Hash', '')
torrent_title = result.get('Name', '')
torrent_title = torrent_name
if hash_value:
# Генерируем чистую magnet-ссылку с публичными трекерами
@ -369,7 +386,7 @@ async def search_torrent_by_id(torrent_id: str) -> dict:
# Парсим результат в стандартный формат
torrent = {
"title": result.get('Name', ''),
"title": torrent_name,
"url": result.get('Url', ''),
"hash": result.get('Hash', ''),
"magnet": magnet,
@ -388,15 +405,20 @@ async def search_torrent_by_id(torrent_id: str) -> dict:
"quality": result.get('Quality', ''),
"video": result.get('Video', ''),
"files": result.get('Files', []),
"provider": "rutracker",
"provider": provider_name,
"id": torrent_id
}
return torrent
else:
print(f"No results found for ID: {torrent_id}")
return None
print(f"No results found for ID {torrent_id} on {provider_name}")
else:
print(f"Error searching by ID: {response.status_code} - {response.text}")
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}: {e}")
continue
print(f"No results found for ID {torrent_id} on any provider")
return None
except Exception as e:
@ -549,7 +571,287 @@ def generate_clean_magnet(hash_value: str, title: str = None) -> str:
@app.get("/", response_class=HTMLResponse)
async def home(request: Request):
"""Главная страница с формой поиска"""
return templates.TemplateResponse("index.html", {"request": request})
try:
print(f"Home page requested from {request.client.host}")
return HTMLResponse("""
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🎬 Поиск фильмов и сериалов</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
padding: 40px;
max-width: 600px;
width: 100%;
text-align: center;
}
.logo {
font-size: 4rem;
margin-bottom: 20px;
background: linear-gradient(45deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
h1 {
color: #333;
font-size: 2.5rem;
margin-bottom: 10px;
font-weight: 700;
}
.subtitle {
color: #666;
font-size: 1.2rem;
margin-bottom: 40px;
line-height: 1.6;
}
.search-form {
display: flex;
flex-direction: column;
gap: 20px;
margin-bottom: 30px;
}
.search-input {
padding: 15px 20px;
border: 2px solid #e1e5e9;
border-radius: 50px;
font-size: 1.1rem;
outline: none;
transition: all 0.3s ease;
background: #fff;
}
.search-input:focus {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.search-button {
padding: 15px 30px;
background: linear-gradient(45deg, #667eea, #764ba2);
color: white;
border: none;
border-radius: 50px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 1px;
}
.search-button:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
}
.search-button:active {
transform: translateY(0);
}
.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 20px;
margin-top: 40px;
}
.feature {
padding: 20px;
background: rgba(102, 126, 234, 0.1);
border-radius: 15px;
transition: all 0.3s ease;
}
.feature:hover {
transform: translateY(-5px);
background: rgba(102, 126, 234, 0.2);
}
.feature-icon {
font-size: 2rem;
margin-bottom: 10px;
}
.feature-title {
font-weight: 600;
color: #333;
margin-bottom: 5px;
}
.feature-desc {
font-size: 0.9rem;
color: #666;
}
.loading {
display: none;
margin-top: 20px;
}
.spinner {
border: 3px solid #f3f3f3;
border-top: 3px solid #667eea;
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
margin: 0 auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error {
color: #e74c3c;
background: #fdf2f2;
padding: 10px 15px;
border-radius: 10px;
margin-top: 20px;
display: none;
}
.success {
color: #27ae60;
background: #f0f9f0;
padding: 10px 15px;
border-radius: 10px;
margin-top: 20px;
display: none;
}
@media (max-width: 768px) {
.container {
padding: 30px 20px;
}
h1 {
font-size: 2rem;
}
.logo {
font-size: 3rem;
}
}
</style>
</head>
<body>
<div class="container">
<div class="logo">🎬</div>
<h1>Поиск фильмов и сериалов</h1>
<p class="subtitle">
Найдите любой фильм или сериал и скачайте его через торрент.<br>
Быстро, удобно и бесплатно!
</p>
<form class="search-form" action="/search" method="post" id="searchForm">
<input
type="text"
name="movie_title"
class="search-input"
placeholder="Введите название фильма или сериала..."
required
autocomplete="off"
>
<button type="submit" class="search-button">
🔍 Найти
</button>
</form>
<div class="loading" id="loading">
<div class="spinner"></div>
<p>Ищем фильмы...</p>
</div>
<div class="error" id="error"></div>
<div class="success" id="success"></div>
<div class="features">
<div class="feature">
<div class="feature-icon">🎯</div>
<div class="feature-title">Точный поиск</div>
<div class="feature-desc">Находим именно то, что вы ищете</div>
</div>
<div class="feature">
<div class="feature-icon"></div>
<div class="feature-title">Быстро</div>
<div class="feature-desc">Результаты за секунды</div>
</div>
<div class="feature">
<div class="feature-icon">🆓</div>
<div class="feature-title">Бесплатно</div>
<div class="feature-desc">Полностью бесплатный сервис</div>
</div>
<div class="feature">
<div class="feature-icon">📱</div>
<div class="feature-title">Удобно</div>
<div class="feature-desc">Работает на всех устройствах</div>
</div>
</div>
</div>
<script>
document.getElementById('searchForm').addEventListener('submit', function(e) {
const loading = document.getElementById('loading');
const error = document.getElementById('error');
const success = document.getElementById('success');
// Скрываем предыдущие сообщения
error.style.display = 'none';
success.style.display = 'none';
// Показываем загрузку
loading.style.display = 'block';
// Проверяем, что поле не пустое
const input = document.querySelector('input[name="movie_title"]');
if (!input.value.trim()) {
e.preventDefault();
loading.style.display = 'none';
error.textContent = 'Пожалуйста, введите название фильма';
error.style.display = 'block';
return;
}
});
// Автофокус на поле ввода
document.querySelector('input[name="movie_title"]').focus();
</script>
</body>
</html>
""")
except Exception as e:
print(f"Error in home page: {e}")
raise HTTPException(status_code=500, detail=f"Error loading home page: {str(e)}")
@app.post("/search", response_class=HTMLResponse)
async def search(request: Request, movie_title: str = Form(...)):
@ -647,7 +949,7 @@ async def add_torrent_to_client(torrent_id: str = Form(...)):
qb_username = os.getenv("QBITTORRENT_USERNAME", "admin")
qb_password = os.getenv("QBITTORRENT_PASSWORD", "vrubel07")
qb_host = os.getenv("QBITTORRENT_HOST", "localhost")
qb_port = os.getenv("QBITTORRENT_PORT", "8080")
qb_port = os.getenv("QBITTORRENT_PORT", "8082")
qb_url = f"http://{qb_host}:{qb_port}"
async with httpx.AsyncClient(timeout=60.0) as client:
@ -733,4 +1035,5 @@ async def add_torrent_to_client(torrent_id: str = Form(...)):
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
print("Starting server on 0.0.0.0:8000")
uvicorn.run(app, host="0.0.0.0", port=8000, log_level="debug", access_log=True)

View file

@ -12,9 +12,9 @@ services:
- QBITTORRENT_USERNAME=admin
- QBITTORRENT_PASSWORD=vrubel07
- QBITTORRENT_HOST=host.docker.internal
- QBITTORRENT_PORT=8080
- QBITTORRENT_PORT=8082
ports:
- "8089:8000"
- "0.0.0.0:8089:8000"
restart: unless-stopped
extra_hosts:
- "host.docker.internal:host-gateway"
@ -62,7 +62,7 @@ services:
- QBITTORRENT_USERNAME=admin
- QBITTORRENT_PASSWORD=vrubel07
- QBITTORRENT_HOST=host.docker.internal
- QBITTORRENT_PORT=8080
- QBITTORRENT_PORT=8082
restart: unless-stopped
extra_hosts:
- "host.docker.internal:host-gateway"

37
start_all_services.sh Executable file
View file

@ -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"

22
stop_all_services.sh Executable file
View file

@ -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"

View file

@ -520,7 +520,7 @@ class MovieSearchBot:
try:
logger.info(f"Searching torrents for movie: {movie.title}")
# Используем существующий API endpoint
# Используем правильный API endpoint через movie-search сервис
async with httpx.AsyncClient(timeout=60.0) as client:
# URL-кодируем название фильма
import urllib.parse
@ -578,7 +578,7 @@ class MovieSearchBot:
# Показываем индикатор загрузки
await update.callback_query.edit_message_text("⬇️ Добавляю торрент в qBittorrent...")
# Используем существующий API endpoint
# Используем правильный API endpoint через movie-search сервис
async with httpx.AsyncClient(timeout=60.0) as client:
response = await client.post(
"http://movie-search:8000/api/add-torrent",