🎨 Обновление веб-интерфейса и исправления
✨ Новые возможности: - Красивый современный веб-интерфейс с градиентным дизайном - Адаптивный дизайн для мобильных устройств - Анимированные элементы и эффекты наведения - Улучшенная типографика и цветовая схема 🔧 Технические улучшения: - Исправлена проблема с внешним доступом (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
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:
|
||||
print(f"Searching torrent by ID: {torrent_id}")
|
||||
|
||||
# Используем правильный эндпоинт для поиска по ID
|
||||
response = await client.get(
|
||||
f"{TORRENT_SEARCH_URL}/api/search/id/rutracker",
|
||||
params={"query": torrent_id}
|
||||
)
|
||||
|
||||
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}")
|
||||
# Получаем список доступных провайдеров
|
||||
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/{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:
|
||||
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)
|
||||
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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue