Add Stream/Download buttons to global search results

This commit is contained in:
2026-07-07 22:00:35 +02:00
parent bfb7002c4d
commit 041b60c771
2 changed files with 66 additions and 5 deletions
+17
View File
@@ -1153,6 +1153,23 @@ def api_download_file(did):
return send_file(p, as_attachment=True, download_name=download_name)
@app.route('/api/downloads/<did>/save')
def api_download_save(did):
"""Speichern: Datei senden und aus Liste entfernen"""
d = DM.get(did)
if not d or d.get('status') != 'complete':
return jsonify({'error': 'not ready'}), 404
p = d.get('path')
if not p or not os.path.exists(p):
return jsonify({'error': 'file missing'}), 404
# Sauberer Titel als Dateiname
safe_title = re.sub(r'[^\w\-\. ]', '_', d.get('title', 'video'))[:80]
download_name = f"{safe_title}.mp4"
# Aus Liste entfernen
DM.delete(did)
return send_file(p, as_attachment=True, download_name=download_name)
@app.route('/api/downloads/<did>', methods=['DELETE'])
def api_download_delete(did):
if DM.delete(did):
+49 -5
View File
@@ -31,6 +31,12 @@ header button { padding: 8px 16px; background: #e94560; border: none; border-rad
/* Suchergebnisse Liste */
.search-results { max-width: 700px; margin: 0 auto; }
.entry-list { display: flex; flex-direction: column; gap: 6px; }
.entry-actions { display: flex; gap: 4px; margin-left: auto; }
.btn-stream-sm, .btn-download-sm { width: 28px; height: 28px; border-radius: 4px; border: none; cursor: pointer; font-size: 14px; display: flex; align-items: center; justify-content: center; }
.btn-stream-sm { background: #0f3460; color: #0f0; }
.btn-download-sm { background: #0f3460; color: #ff0; }
.btn-stream-sm:hover { background: #0f0; color: #000; }
.btn-download-sm:hover { background: #ff0; color: #000; }
.entry-item { display: flex; align-items: center; gap: 12px; padding: 10px 12px; background: #16213e; border-radius: 8px; cursor: pointer; transition: background 0.15s; }
.entry-item:hover { background: #1a2744; }
.entry-item .entry-thumb { width: 50px; height: 70px; object-fit: cover; border-radius: 4px; background: #0f3460; flex-shrink: 0; }
@@ -161,16 +167,19 @@ function renderEntries() {
<h2 class="section-title">Suchergebnisse (${state.entries.length})</h2>
<div class="entry-list">
${state.entries.map((e, i) => `
<div class="entry-item" onclick="openSearchResult(${i})">
<div class="entry-item">
${e.thumb ? `<img src="${e.thumb}" class="entry-thumb" onerror="this.style.display='none'">` : '<div class="entry-thumb placeholder">🎬</div>'}
<div class="entry-info">
<div class="entry-info" onclick="openSearchResult(${i})">
<div class="entry-title">${e.title || 'Unnamed'}</div>
<div class="entry-meta">
${e.year ? `<span class="entry-year">${e.year}</span>` : ''}
${e._site ? `<span class="entry-site">${e._site.name}</span>` : ''}
</div>
</div>
<div class="entry-arrow">▶</div>
<div class="entry-actions">
<button class="btn-stream-sm" onclick="openSearchStream(${i})" title="Stream">▶</button>
<button class="btn-download-sm" onclick="addSearchDownload(${i})" title="Download">⬇</button>
</div>
</div>`).join('')}
</div>
</div>`;
@@ -313,6 +322,41 @@ async function openSearchResult(idx) {
}
}
async function openSearchStream(idx) {
// Stream in neuem Tab - bleibt auf der Suchseite
await openSearchResult(idx);
}
async function addSearchDownload(idx) {
// Download starten
const entry = state.entries[idx];
if (!entry) return;
const site = entry._site;
if (!site) {
alert('Site nicht gefunden');
return;
}
const params = new URLSearchParams({ url: entry.url || '' });
try {
const data = await api(`/sites/${site.id}/hosters?${params}`);
const hosters = (data.hosters || []).filter(h => typeof h === 'object');
if (hosters.length > 0) {
const link = hosters[0].link || hosters[0].url;
const did = await api(`/download?url=${encodeURIComponent(link)}&title=${encodeURIComponent(entry.title)}`);
alert('Download gestartet: ' + entry.title);
showDownloads();
} else {
alert('Keine Streams gefunden für "' + entry.title + '"');
}
} catch(e) {
alert('Fehler beim Starten des Downloads: ' + e.message);
}
}
async function pollDownload(did, idx, title) {
const btn = document.getElementById(`dlbtn${idx}`);
const maxWait = 120;
@@ -463,10 +507,10 @@ async function deleteDownload(id) {
}
function saveDownload(id, btn) {
// Download starten und Button deaktivieren
// Download starten - Backend löscht danach aus Liste
btn.disabled = true;
btn.style.opacity = '0.5';
location.href = `/api/downloads/${id}/file`;
location.href = `/api/downloads/${id}/save`;
}
function setBC(parts) {