Add Stream/Download buttons to global search results
This commit is contained in:
@@ -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
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user