Do not auto-download enclosures that are not audio or video

Blog feeds put each article's header image in an <enclosure>, so a text
feed read as a podcast full of episodes: 149 images, 74 MB across 11
feeds. media_types defaults to audio and video, with a per-feed override.

Such enclosures stay listed and stay downloadable by hand; the row names
what it is rather than saying "skipped". An unknown type is allowed, since
the real type is only known after downloading, and a torrent is allowed as
a container judged once unpacked.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdXho5tTkjFLeUXKbEjKBh
This commit is contained in:
2026-09-10 17:13:59 +00:00
parent 8b21d19365
commit a83f62ccd3
7 changed files with 131 additions and 7 deletions

View File

@@ -606,7 +606,8 @@ function epEl(e){
${num?`<span>${num}</span><span class="dot"></span>`:''}
<span>${dateOf(e.published)}</span>
${left?'<span class="dot"></span><span>'+left+'</span>':''}
${enc?`<span class="dot"></span><span class="chip ${esc(enc.state)}">${has?'downloaded':esc(enc.state)}</span>`:''}
${enc?`<span class="dot"></span><span class="chip ${esc(enc.state)}">${
has?'downloaded':(enc.state==='skipped'?kindOf(enc):esc(enc.state))}</span>`:''}
${enc&&enc.length?`<span>${mb(enc.length)}</span>`:''}
${e.flagged?'<span class="dot"></span><span>★ kept</span>':''}
</div>
@@ -615,7 +616,8 @@ function epEl(e){
</div>
<div class="rowacts">
${has?`<button class="iconbtn" data-a="play" title="Play">▶</button>`:
(enc?`<button class="iconbtn" data-a="get" title="Download">⤓</button>`:'')}
(enc?`<button class="iconbtn" data-a="get" title="Download this ${
enc.state==='skipped'?kindOf(enc):'file'}">⤓</button>`:'')}
<button class="iconbtn" data-a="flag" title="${e.flagged?'Stop keeping':'Keep (never auto-delete)'}">${e.flagged?'★':'☆'}</button>
<button class="iconbtn" data-a="read" title="Mark ${e.read?'unread':'read'}">${e.read?'○':'●'}</button>
${has?`<button class="iconbtn" data-a="del" title="Delete file">🗑</button>`:''}
@@ -630,6 +632,18 @@ function epEl(e){
if(S.open.has(e.guid)) showNotes(e,el);
return el;
}
/// What an enclosure is, for a row that is not an episode: "image", "pdf", "document".
function kindOf(enc){
const m=(enc.mime||'').toLowerCase();
if(m.startsWith('image/')) return 'image';
if(m.startsWith('video/')) return 'video';
if(m.startsWith('audio/')) return 'audio';
if(m.includes('pdf')) return 'pdf';
if(m.includes('torrent')) return 'torrent';
const ext=(enc.url||'').split('?')[0].split('.').pop();
return (ext && ext.length<=5) ? ext.toLowerCase() : 'file';
}
function feedArt(){ const f=S.feeds.find(x=>x.id===S.feed); return f&&f.image; }
function toggleNotes(e,el){
@@ -937,6 +951,11 @@ async function prefsModal(){
<span class="hint">Applies to any feed that does not set its own — including every feed
inside an OPML subscription. <b>0 means unlimited</b>, which will pull a whole back
catalogue the first time a feed is scanned.</span></div>
<div class="field"><label>Download these media types automatically</label>
<input type="text" id="gtypes" value="${esc((g.media_types||[]).join(', '))}"
placeholder="audio, video">
<span class="hint">Anything else is still listed and can be downloaded by hand — blog feeds
put article images in enclosures, and those are not episodes. Empty takes everything.</span></div>
<div class="field"><label>Disk quota (GB, 0 = unlimited)</label>
<input type="number" id="gquota" min="0" step="0.5" value="${g.max_total_gb}">
<span class="hint">Over this, the oldest played episodes are deleted first. Starred
@@ -952,6 +971,7 @@ async function prefsModal(){
await api('/api/settings',{method:'PATCH',body:JSON.stringify({
schedule:`every ${Math.max(1,Number($('#gnum').value)||1)}${$('#gunit').value}`,
max_new_per_check:Math.max(0,Number($('#gmax').value)||0),
media_types:$('#gtypes').value.split(',').map(t=>t.trim()).filter(Boolean),
max_total_gb:Number($('#gquota').value)||0,
max_age_days:Number($('#gage').value)||0})});
closeModal(); toast('Settings saved');