Aniworld-Downloder-by-phoen.../templates/index.html
2025-08-08 10:56:22 +00:00

392 lines
17 KiB
HTML

{% extends "base.html" %}
{% block content %}
<div class="row">
<div class="col-12 mb-4 text-center">
<h1 class="display-4">AniWorld Downloader</h1>
<p class="lead">Willkommen, {{ current_user.jellyfin_username }}!</p>
</div>
</div>
<div class="row">
<div class="col-md-8">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="bi bi-link-45deg"></i> Link eingeben</h5>
</div>
<div class="card-body">
<div class="input-group mb-3">
<input type="url" class="form-control" id="animeLink"
placeholder="https://aniworld.to/anime/stream/...">
<button class="btn btn-primary" type="button" id="analyzeButton">
<i class="bi bi-search"></i> Analysieren
</button>
</div>
<div id="linkAnalysis" class="d-none">
<h5 class="mt-4 mb-3" id="contentTitle">Titel wird geladen...</h5>
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">Typ</label>
<select class="form-select" id="contentType">
<option value="episodes">Episoden</option>
<option value="movies">Filme</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Provider</label>
<select class="form-select" id="provider">
<option value="Vidmoly">Vidmoly</option>
<option value="VOE">VOE</option>
<option value="SpeedFiles">SpeedFiles</option>
<option value="Vidoza">Vidoza</option>
<option value="Doodstream">Doodstream</option>
<option value="Streamtape">Streamtape</option>
</select>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">Sprache (Prioritätsreihenfolge)</label>
<select class="form-select" id="language">
<option value="German">Deutsch</option>
<option value="Ger-Sub">Deutsch Sub</option>
<option value="Eng-Sub">English Sub</option>
<option value="English">English</option>
</select>
</div>
<div class="col-md-6" id="seasonContainer">
<label class="form-label">Staffel</label>
<div class="d-flex">
<select class="form-select me-2" id="seasonStart">
<option value="1">1</option>
</select>
<span class="align-self-center">bis</span>
<select class="form-select ms-2" id="seasonEnd">
<option value="1">1</option>
</select>
</div>
</div>
<div class="col-md-6 d-none" id="movieContainer">
<label class="form-label">Film</label>
<div class="d-flex">
<select class="form-select me-2" id="movieStart">
<option value="1">1</option>
</select>
<span class="align-self-center">bis</span>
<select class="form-select ms-2" id="movieEnd">
<option value="1">1</option>
</select>
</div>
</div>
</div>
<div class="row mb-3" id="episodeContainer">
<div class="col-md-6">
<label class="form-label">Episode</label>
<div class="d-flex">
<select class="form-select me-2" id="episodeStart">
<option value="1">1</option>
</select>
<span class="align-self-center">bis</span>
<select class="form-select ms-2" id="episodeEnd">
<option value="1">1</option>
</select>
</div>
</div>
<div class="col-md-6">
<label class="form-label">Nach Download</label>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="reconnectVpn" checked>
<label class="form-check-label" for="reconnectVpn">
VPN neu verbinden
</label>
</div>
</div>
</div>
<div class="d-grid mt-4">
<button class="btn btn-success" id="startDownloadBtn">
<i class="bi bi-cloud-download"></i> Download starten
</button>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="bi bi-arrow-down-circle"></i> Aktuelle Downloads</h5>
<button class="btn btn-sm btn-outline-secondary" id="refreshDownloads">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
<div class="card-body" id="currentDownloads">
<div class="text-center py-4 text-muted">
<i class="bi bi-cloud-slash display-4"></i>
<p class="mt-2">Keine aktiven Downloads</p>
</div>
</div>
</div>
<div class="card mt-3">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-info-circle"></i> Schnellsuche</h5>
</div>
<div class="card-body">
<form action="{{ url_for('search') }}" method="get">
<div class="input-group">
<input type="text" class="form-control" name="q" placeholder="Suchbegriff...">
<button class="btn btn-primary" type="submit">
<i class="bi bi-search"></i>
</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize variables
let contentData = null;
// Event listeners
document.getElementById('analyzeButton').addEventListener('click', analyzeLink);
document.getElementById('startDownloadBtn').addEventListener('click', startDownload);
document.getElementById('refreshDownloads').addEventListener('click', updateCurrentDownloads);
document.getElementById('contentType').addEventListener('change', toggleContentType);
// Initialize content type view
function toggleContentType() {
const contentType = document.getElementById('contentType').value;
if (contentType === 'episodes') {
document.getElementById('seasonContainer').classList.remove('d-none');
document.getElementById('episodeContainer').classList.remove('d-none');
document.getElementById('movieContainer').classList.add('d-none');
} else {
document.getElementById('seasonContainer').classList.add('d-none');
document.getElementById('episodeContainer').classList.add('d-none');
document.getElementById('movieContainer').classList.remove('d-none');
}
}
// Load data from server
async function analyzeLink() {
const link = document.getElementById('animeLink').value;
if (!link) {
alert('Bitte gib einen Link ein.');
return;
}
try {
const response = await fetch('/api/analyze', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
link: link,
content_type: document.getElementById('contentType').value
})
});
if (!response.ok) {
throw new Error('Fehler beim Analysieren des Links');
}
contentData = await response.json();
// Update UI
document.getElementById('contentTitle').textContent = contentData.title;
document.getElementById('linkAnalysis').classList.remove('d-none');
if (contentData.content_type === 'episodes') {
// Update season options
updateSelectOptions('seasonStart', 1, contentData.seasons);
updateSelectOptions('seasonEnd', 1, contentData.seasons);
// Update episode options based on first season
updateSelectOptions('episodeStart', 1, contentData.episodes[0]);
updateSelectOptions('episodeEnd', 1, contentData.episodes[0]);
// Set event listeners for season changes
document.getElementById('seasonStart').addEventListener('change', updateEpisodeStartOptions);
document.getElementById('seasonEnd').addEventListener('change', updateEpisodeEndOptions);
} else {
// Update movie options
updateSelectOptions('movieStart', 1, contentData.movies);
updateSelectOptions('movieEnd', 1, contentData.movies);
}
toggleContentType();
} catch (error) {
console.error('Error:', error);
alert('Fehler beim Analysieren: ' + error.message);
}
}
function updateSelectOptions(elementId, start, end) {
const select = document.getElementById(elementId);
select.innerHTML = '';
for (let i = start; i <= end; i++) {
const option = document.createElement('option');
option.value = i;
option.textContent = i;
select.appendChild(option);
}
// If end options, select the last one
if (elementId.includes('End')) {
select.value = end;
}
}
function updateEpisodeStartOptions() {
const seasonIndex = parseInt(document.getElementById('seasonStart').value) - 1;
if (contentData && contentData.episodes && contentData.episodes[seasonIndex]) {
updateSelectOptions('episodeStart', 1, contentData.episodes[seasonIndex]);
}
}
function updateEpisodeEndOptions() {
const seasonIndex = parseInt(document.getElementById('seasonEnd').value) - 1;
if (contentData && contentData.episodes && contentData.episodes[seasonIndex]) {
updateSelectOptions('episodeEnd', 1, contentData.episodes[seasonIndex]);
document.getElementById('episodeEnd').value = contentData.episodes[seasonIndex];
}
}
async function startDownload() {
if (!contentData) {
alert('Bitte analysiere zuerst einen Link.');
return;
}
const contentType = document.getElementById('contentType').value;
const requestData = {
link: contentData.link,
provider: document.getElementById('provider').value,
language: document.getElementById('language').value,
content_type: contentType
};
if (contentType === 'episodes') {
requestData.season_start = parseInt(document.getElementById('seasonStart').value);
requestData.season_end = parseInt(document.getElementById('seasonEnd').value);
requestData.episode_start = parseInt(document.getElementById('episodeStart').value);
requestData.episode_end = parseInt(document.getElementById('episodeEnd').value);
} else {
requestData.movie_start = parseInt(document.getElementById('movieStart').value);
requestData.movie_end = parseInt(document.getElementById('movieEnd').value);
}
try {
const response = await fetch('/api/download/start', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestData)
});
if (!response.ok) {
throw new Error('Fehler beim Starten des Downloads');
}
const result = await response.json();
if (result.success) {
alert(`${result.downloads_added} Downloads wurden zur Warteschlange hinzugefügt!`);
updateCurrentDownloads();
} else {
alert('Fehler beim Hinzufügen des Downloads.');
}
} catch (error) {
console.error('Error:', error);
alert('Fehler beim Starten des Downloads: ' + error.message);
}
}
async function updateCurrentDownloads() {
try {
const response = await fetch('/api/downloads');
if (!response.ok) {
throw new Error('Fehler beim Laden der Downloads');
}
const downloads = await response.json();
const container = document.getElementById('currentDownloads');
const activeDownloads = downloads.filter(d =>
d.status === 'downloading' || d.status === 'queued'
);
if (activeDownloads.length === 0) {
container.innerHTML = `
<div class="text-center py-4 text-muted">
<i class="bi bi-cloud-slash display-4"></i>
<p class="mt-2">Keine aktiven Downloads</p>
</div>
`;
} else {
container.innerHTML = '';
activeDownloads.forEach(download => {
const card = document.createElement('div');
card.className = 'download-item mb-3 p-2 border-bottom';
const title = download.season
? `${download.title} S${download.season}E${download.episode}`
: `${download.title} Film ${download.episode}`;
const statusClass = download.status === 'downloading'
? 'text-primary'
: 'text-secondary';
card.innerHTML = `
<div class="d-flex justify-content-between">
<small title="${title}">${truncateText(title, 28)}</small>
<small class="${statusClass}">${capitalizeFirst(download.status)}</small>
</div>
<div class="progress mt-1" style="height: 6px;">
<div class="progress-bar" role="progressbar" style="width: ${download.progress}%"
aria-valuenow="${download.progress}" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<div class="d-flex justify-content-between mt-1">
<small class="text-muted">${download.language}</small>
<small class="text-muted">${download.provider}</small>
</div>
`;
container.appendChild(card);
});
}
} catch (error) {
console.error('Error:', error);
}
}
function truncateText(text, maxLength) {
return text.length > maxLength ? text.substring(0, maxLength) + '...' : text;
}
function capitalizeFirst(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
// Initial load of current downloads
updateCurrentDownloads();
// Auto-update downloads every 5 seconds
setInterval(updateCurrentDownloads, 5000);
});
</script>
{% endblock %}