🎨 Обновление веб-интерфейса и исправления
✨ Новые возможности: - Красивый современный веб-интерфейс с градиентным дизайном - Адаптивный дизайн для мобильных устройств - Анимированные элементы и эффекты наведения - Улучшенная типографика и цветовая схема 🔧 Технические улучшения: - Исправлена проблема с внешним доступом (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:
parent
90ad38bca7
commit
a21654106b
6 changed files with 553 additions and 65 deletions
126
MANAGEMENT.md
Normal file
126
MANAGEMENT.md
Normal 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>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
423
app.py
423
app.py
|
|
@ -340,64 +340,86 @@ async def search_torrent_by_id(torrent_id: str) -> dict:
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
print(f"Searching torrent by ID: {torrent_id}")
|
print(f"Searching torrent by ID: {torrent_id}")
|
||||||
|
|
||||||
# Используем правильный эндпоинт для поиска по ID
|
# Получаем список доступных провайдеров
|
||||||
response = await client.get(
|
providers_response = await client.get(f"{TORRENT_SEARCH_URL}/api/provider/list")
|
||||||
f"{TORRENT_SEARCH_URL}/api/search/id/rutracker",
|
if providers_response.status_code != 200:
|
||||||
params={"query": torrent_id}
|
print(f"Failed to get providers: {providers_response.status_code}")
|
||||||
)
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
results = response.json()
|
|
||||||
if results and len(results) > 0:
|
|
||||||
# Берем первый результат
|
|
||||||
result = results[0]
|
|
||||||
print(f"Found torrent by ID: {result.get('Name', 'Unknown')[:100]}...")
|
|
||||||
|
|
||||||
# Получаем хэш и создаем чистую magnet-ссылку с публичными трекерами
|
|
||||||
hash_value = result.get('Hash', '')
|
|
||||||
torrent_title = result.get('Name', '')
|
|
||||||
|
|
||||||
if hash_value:
|
|
||||||
# Генерируем чистую magnet-ссылку с публичными трекерами
|
|
||||||
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 magnet and not magnet.startswith('magnet:'):
|
|
||||||
magnet = f"magnet:?xt=urn:btih:{hash_value}"
|
|
||||||
|
|
||||||
# Парсим результат в стандартный формат
|
|
||||||
torrent = {
|
|
||||||
"title": result.get('Name', ''),
|
|
||||||
"url": result.get('Url', ''),
|
|
||||||
"hash": result.get('Hash', ''),
|
|
||||||
"magnet": magnet,
|
|
||||||
"torrent_url": result.get('Torrent', ''),
|
|
||||||
"imdb_url": result.get('IMDb_link', ''),
|
|
||||||
"kinopoisk_url": result.get('Kinopoisk_link', ''),
|
|
||||||
"poster": result.get('Poster', ''),
|
|
||||||
"year": result.get('Year', ''),
|
|
||||||
"release": result.get('Release', ''),
|
|
||||||
"type": result.get('Type', ''),
|
|
||||||
"duration": result.get('Duration', ''),
|
|
||||||
"audio": result.get('Audio', ''),
|
|
||||||
"director": result.get('Directer', ''),
|
|
||||||
"actors": result.get('Actors', ''),
|
|
||||||
"description": result.get('Description', ''),
|
|
||||||
"quality": result.get('Quality', ''),
|
|
||||||
"video": result.get('Video', ''),
|
|
||||||
"files": result.get('Files', []),
|
|
||||||
"provider": "rutracker",
|
|
||||||
"id": torrent_id
|
|
||||||
}
|
|
||||||
return torrent
|
|
||||||
else:
|
|
||||||
print(f"No results found for ID: {torrent_id}")
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
print(f"Error searching by ID: {response.status_code} - {response.text}")
|
|
||||||
return None
|
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/{provider_name}",
|
||||||
|
params={"query": torrent_id}
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
results = response.json()
|
||||||
|
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]
|
||||||
|
# Используем 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 = torrent_name
|
||||||
|
|
||||||
|
if hash_value:
|
||||||
|
# Генерируем чистую magnet-ссылку с публичными трекерами
|
||||||
|
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 magnet and not magnet.startswith('magnet:'):
|
||||||
|
magnet = f"magnet:?xt=urn:btih:{hash_value}"
|
||||||
|
|
||||||
|
# Парсим результат в стандартный формат
|
||||||
|
torrent = {
|
||||||
|
"title": torrent_name,
|
||||||
|
"url": result.get('Url', ''),
|
||||||
|
"hash": result.get('Hash', ''),
|
||||||
|
"magnet": magnet,
|
||||||
|
"torrent_url": result.get('Torrent', ''),
|
||||||
|
"imdb_url": result.get('IMDb_link', ''),
|
||||||
|
"kinopoisk_url": result.get('Kinopoisk_link', ''),
|
||||||
|
"poster": result.get('Poster', ''),
|
||||||
|
"year": result.get('Year', ''),
|
||||||
|
"release": result.get('Release', ''),
|
||||||
|
"type": result.get('Type', ''),
|
||||||
|
"duration": result.get('Duration', ''),
|
||||||
|
"audio": result.get('Audio', ''),
|
||||||
|
"director": result.get('Directer', ''),
|
||||||
|
"actors": result.get('Actors', ''),
|
||||||
|
"description": result.get('Description', ''),
|
||||||
|
"quality": result.get('Quality', ''),
|
||||||
|
"video": result.get('Video', ''),
|
||||||
|
"files": result.get('Files', []),
|
||||||
|
"provider": provider_name,
|
||||||
|
"id": torrent_id
|
||||||
|
}
|
||||||
|
return torrent
|
||||||
|
else:
|
||||||
|
print(f"No results found for ID {torrent_id} on {provider_name}")
|
||||||
|
else:
|
||||||
|
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:
|
except Exception as e:
|
||||||
print(f"Error searching torrent by ID: {e}")
|
print(f"Error searching torrent by ID: {e}")
|
||||||
|
|
@ -549,7 +571,287 @@ def generate_clean_magnet(hash_value: str, title: str = None) -> str:
|
||||||
@app.get("/", response_class=HTMLResponse)
|
@app.get("/", response_class=HTMLResponse)
|
||||||
async def home(request: Request):
|
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)
|
@app.post("/search", response_class=HTMLResponse)
|
||||||
async def search(request: Request, movie_title: str = Form(...)):
|
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_username = os.getenv("QBITTORRENT_USERNAME", "admin")
|
||||||
qb_password = os.getenv("QBITTORRENT_PASSWORD", "vrubel07")
|
qb_password = os.getenv("QBITTORRENT_PASSWORD", "vrubel07")
|
||||||
qb_host = os.getenv("QBITTORRENT_HOST", "localhost")
|
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}"
|
qb_url = f"http://{qb_host}:{qb_port}"
|
||||||
|
|
||||||
async with httpx.AsyncClient(timeout=60.0) as client:
|
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__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
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)
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ services:
|
||||||
- QBITTORRENT_USERNAME=admin
|
- QBITTORRENT_USERNAME=admin
|
||||||
- QBITTORRENT_PASSWORD=vrubel07
|
- QBITTORRENT_PASSWORD=vrubel07
|
||||||
- QBITTORRENT_HOST=host.docker.internal
|
- QBITTORRENT_HOST=host.docker.internal
|
||||||
- QBITTORRENT_PORT=8080
|
- QBITTORRENT_PORT=8082
|
||||||
ports:
|
ports:
|
||||||
- "8089:8000"
|
- "0.0.0.0:8089:8000"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- "host.docker.internal:host-gateway"
|
- "host.docker.internal:host-gateway"
|
||||||
|
|
@ -62,7 +62,7 @@ services:
|
||||||
- QBITTORRENT_USERNAME=admin
|
- QBITTORRENT_USERNAME=admin
|
||||||
- QBITTORRENT_PASSWORD=vrubel07
|
- QBITTORRENT_PASSWORD=vrubel07
|
||||||
- QBITTORRENT_HOST=host.docker.internal
|
- QBITTORRENT_HOST=host.docker.internal
|
||||||
- QBITTORRENT_PORT=8080
|
- QBITTORRENT_PORT=8082
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- "host.docker.internal:host-gateway"
|
- "host.docker.internal:host-gateway"
|
||||||
|
|
|
||||||
37
start_all_services.sh
Executable file
37
start_all_services.sh
Executable 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
22
stop_all_services.sh
Executable 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"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -520,7 +520,7 @@ class MovieSearchBot:
|
||||||
try:
|
try:
|
||||||
logger.info(f"Searching torrents for movie: {movie.title}")
|
logger.info(f"Searching torrents for movie: {movie.title}")
|
||||||
|
|
||||||
# Используем существующий API endpoint
|
# Используем правильный API endpoint через movie-search сервис
|
||||||
async with httpx.AsyncClient(timeout=60.0) as client:
|
async with httpx.AsyncClient(timeout=60.0) as client:
|
||||||
# URL-кодируем название фильма
|
# URL-кодируем название фильма
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
@ -578,7 +578,7 @@ class MovieSearchBot:
|
||||||
# Показываем индикатор загрузки
|
# Показываем индикатор загрузки
|
||||||
await update.callback_query.edit_message_text("⬇️ Добавляю торрент в qBittorrent...")
|
await update.callback_query.edit_message_text("⬇️ Добавляю торрент в qBittorrent...")
|
||||||
|
|
||||||
# Используем существующий API endpoint
|
# Используем правильный API endpoint через movie-search сервис
|
||||||
async with httpx.AsyncClient(timeout=60.0) as client:
|
async with httpx.AsyncClient(timeout=60.0) as client:
|
||||||
response = await client.post(
|
response = await client.post(
|
||||||
"http://movie-search:8000/api/add-torrent",
|
"http://movie-search:8000/api/add-torrent",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue