392 lines
17 KiB
HTML
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 %} |