fix: xREL progressive 5er-Batches, Badge-Coverage, Dirname zweizeilig nach German.
This commit is contained in:
@@ -253,33 +253,27 @@ router.get('/movie/:id', authenticate, async (req, res) => {
|
|||||||
} catch (e) { res.status(500).json({ error: e.message }); }
|
} catch (e) { res.status(500).json({ error: e.message }); }
|
||||||
});
|
});
|
||||||
|
|
||||||
// POST /api/tools/media/xrel-check – Cover-Badge Batch-Check
|
// POST /api/tools/media/xrel-check – Check ob deutsche Releases existieren
|
||||||
|
// Nimmt max 5 Filme pro Request (Frontend schickt mehrere kleine Batches)
|
||||||
router.post('/xrel-check', authenticate, async (req, res) => {
|
router.post('/xrel-check', authenticate, async (req, res) => {
|
||||||
const movies = (req.body.movies || []).slice(0, 20);
|
const movies = (req.body.movies || []).slice(0, 5);
|
||||||
if (!movies.length) return res.json({});
|
if (!movies.length) return res.json({});
|
||||||
|
|
||||||
const result = {};
|
const result = {};
|
||||||
// 3er-Batches
|
await Promise.all(movies.map(async m => {
|
||||||
for (let i = 0; i < movies.length; i += 3) {
|
|
||||||
const chunk = movies.slice(i, i + 3);
|
|
||||||
await Promise.all(chunk.map(async m => {
|
|
||||||
try {
|
try {
|
||||||
const extInfoId = await findExtInfoId(m.title, m.original_title);
|
const extInfoId = await findExtInfoId(m.title, m.original_title);
|
||||||
if (!extInfoId) { result[m.id] = false; return; }
|
if (!extInfoId) { result[m.id] = false; return; }
|
||||||
// Schnell-Check: nur P2P Seite 1 prüfen ob german vorhanden
|
// P2P zuerst – main_lang ist zuverlässig
|
||||||
const p2p = await xrelFetch('/p2p/releases.json', { ext_info_id: extInfoId, per_page: 25 });
|
const p2p = await xrelFetch('/p2p/releases.json', { ext_info_id: extInfoId, per_page: 25 }, 8000);
|
||||||
const p2pHit = (p2p.list || []).some(r => r.main_lang === 'german');
|
if ((p2p.list || []).some(r => r.main_lang === 'german')) { result[m.id] = true; return; }
|
||||||
if (p2pHit) { result[m.id] = true; return; }
|
// Scene als Fallback
|
||||||
// Auch Scene prüfen
|
const scene = await xrelFetch('/release/ext_info.json', { id: extInfoId, per_page: 25 }, 8000);
|
||||||
const scene = await xrelFetch('/release/ext_info.json', { id: extInfoId, per_page: 25 });
|
result[m.id] = (scene.list || []).some(r => /german|deutsch/i.test(r.dirname || ''));
|
||||||
const sceneHit = (scene.list || []).some(r => /german|deutsch/i.test(r.dirname || ''));
|
|
||||||
result[m.id] = sceneHit;
|
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
result[m.id] = false;
|
result[m.id] = false;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
if (i + 3 < movies.length) await new Promise(r => setTimeout(r, 250));
|
|
||||||
}
|
|
||||||
res.json(result);
|
res.json(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -261,13 +261,19 @@ function KinoGrid({ onOpenModal }) {
|
|||||||
api('/tools/media/now-playing')
|
api('/tools/media/now-playing')
|
||||||
.then(list => {
|
.then(list => {
|
||||||
setMovies(list);
|
setMovies(list);
|
||||||
// xREL-Batch-Check nach Laden
|
// xREL-Badge: progressive 5er-Batches
|
||||||
api('/tools/media/xrel-check', {
|
const moviesMeta = list.map(m => ({ id: m.id, title: m.title, original_title: m.original_title }));
|
||||||
method: 'POST',
|
for (let i = 0; i < moviesMeta.length; i += 5) {
|
||||||
body: { movies: list.map(m => ({ id: m.id, title: m.title, original_title: m.original_title, release_date: m.release_date })) }
|
const chunk = moviesMeta.slice(i, i + 5);
|
||||||
}).then(result => {
|
api('/tools/media/xrel-check', { method:'POST', body:{ movies: chunk } })
|
||||||
setXrelIds(new Set(Object.entries(result).filter(([,v]) => v).map(([k]) => Number(k))));
|
.then(result => {
|
||||||
|
setXrelIds(prev => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
Object.entries(result).forEach(([k,v]) => { if (v) next.add(Number(k)); });
|
||||||
|
return next;
|
||||||
|
});
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(e => setError(e.message))
|
.catch(e => setError(e.message))
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
@@ -320,12 +326,18 @@ function DemnächstGrid({ onOpenModal }) {
|
|||||||
.then(grps => {
|
.then(grps => {
|
||||||
setGroups(grps);
|
setGroups(grps);
|
||||||
const allMovies = Object.values(grps).flat();
|
const allMovies = Object.values(grps).flat();
|
||||||
api('/tools/media/xrel-check', {
|
const moviesMeta = allMovies.map(m => ({ id: m.id, title: m.title, original_title: m.original_title }));
|
||||||
method: 'POST',
|
for (let i = 0; i < moviesMeta.length; i += 5) {
|
||||||
body: { movies: allMovies.map(m => ({ id: m.id, title: m.title, original_title: m.original_title, release_date: m.release_date })) }
|
const chunk = moviesMeta.slice(i, i + 5);
|
||||||
}).then(result => {
|
api('/tools/media/xrel-check', { method:'POST', body:{ movies: chunk } })
|
||||||
setXrelIds(new Set(Object.entries(result).filter(([,v]) => v).map(([k]) => Number(k))));
|
.then(result => {
|
||||||
|
setXrelIds(prev => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
Object.entries(result).forEach(([k,v]) => { if (v) next.add(Number(k)); });
|
||||||
|
return next;
|
||||||
|
});
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(e => setError(e.message))
|
.catch(e => setError(e.message))
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
@@ -594,30 +606,23 @@ function MovieModal({ tmdbId, onClose, mobile }) {
|
|||||||
<div style={{ display:'flex', flexDirection:'column', gap:4 }}>
|
<div style={{ display:'flex', flexDirection:'column', gap:4 }}>
|
||||||
{detail.xrel.map((r, i) => (
|
{detail.xrel.map((r, i) => (
|
||||||
<a key={i} href={r.url} target="_blank" rel="noopener noreferrer" style={{
|
<a key={i} href={r.url} target="_blank" rel="noopener noreferrer" style={{
|
||||||
display:'flex', alignItems:'center', gap:8,
|
display:'flex', alignItems:'flex-start', gap:8,
|
||||||
background:'rgba(255,255,255,0.03)', border:'1px solid rgba(255,255,255,0.07)',
|
background:'rgba(255,255,255,0.03)', border:'1px solid rgba(255,255,255,0.07)',
|
||||||
borderRadius:6, padding:'6px 10px', textDecoration:'none',
|
borderRadius:6, padding:'6px 10px', textDecoration:'none',
|
||||||
}}>
|
}}>
|
||||||
<span style={{ fontSize:9, fontWeight:700, fontFamily:'monospace',
|
<span style={{ fontSize:9, fontWeight:700, fontFamily:'monospace',
|
||||||
color: r.p2p ? '#4ecdc4' : '#ff6b9d',
|
color: r.p2p ? '#4ecdc4' : '#ff6b9d',
|
||||||
background: r.p2p ? 'rgba(78,205,196,0.12)' : 'rgba(255,107,157,0.12)',
|
background: r.p2p ? 'rgba(78,205,196,0.12)' : 'rgba(255,107,157,0.12)',
|
||||||
borderRadius:3, padding:'1px 5px', flexShrink:0 }}>
|
borderRadius:3, padding:'1px 5px', flexShrink:0, marginTop:1 }}>
|
||||||
{r.p2p ? 'P2P' : 'SCN'}
|
{r.p2p ? 'P2P' : 'SCN'}
|
||||||
</span>
|
</span>
|
||||||
<span style={{ fontSize:10, color:'rgba(255,255,255,0.75)', fontFamily:'monospace',
|
<div style={{ flex:1, minWidth:0 }}>
|
||||||
flex:1, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
|
<DirnameWrapped dirname={r.dirname} />
|
||||||
{r.dirname}
|
<div style={{ display:'flex', gap:8, marginTop:2 }}>
|
||||||
</span>
|
{r.size && <span style={{ fontSize:9, color:'rgba(255,255,255,0.35)', fontFamily:'monospace' }}>{r.size}</span>}
|
||||||
{r.size && (
|
{r.date && <span style={{ fontSize:9, color:'rgba(255,255,255,0.25)', fontFamily:'monospace' }}>{r.date}</span>}
|
||||||
<span style={{ fontSize:9, color:'rgba(255,255,255,0.35)', fontFamily:'monospace', flexShrink:0 }}>
|
</div>
|
||||||
{r.size}
|
</div>
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{r.date && (
|
|
||||||
<span style={{ fontSize:9, color:'rgba(255,255,255,0.25)', fontFamily:'monospace', flexShrink:0 }}>
|
|
||||||
{r.date}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -639,6 +644,26 @@ function Badge({ children, bg, color }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dirname zweizeilig: vor "German." trennen
|
||||||
|
function DirnameWrapped({ dirname }) {
|
||||||
|
if (!dirname) return null;
|
||||||
|
const lower = dirname.toLowerCase();
|
||||||
|
const idx = lower.indexOf('german.');
|
||||||
|
if (idx < 0) {
|
||||||
|
// Kein "German." – einfach umbrechen nach 40 Zeichen Worttrennung
|
||||||
|
return <span style={{ fontSize:10, color:'rgba(255,255,255,0.75)', fontFamily:'monospace',
|
||||||
|
wordBreak:'break-all', lineHeight:1.4 }}>{dirname}</span>;
|
||||||
|
}
|
||||||
|
const before = dirname.slice(0, idx + 7); // inkl. "German."
|
||||||
|
const after = dirname.slice(idx + 7);
|
||||||
|
return (
|
||||||
|
<span style={{ fontSize:10, color:'rgba(255,255,255,0.75)', fontFamily:'monospace', lineHeight:1.4 }}>
|
||||||
|
{before}
|
||||||
|
{after && <><br/><span style={{ color:'rgba(255,255,255,0.5)' }}>{after}</span></>}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function StatusBox({ children }) {
|
function StatusBox({ children }) {
|
||||||
return (
|
return (
|
||||||
<div style={{ padding:'28px 20px', textAlign:'center',
|
<div style={{ padding:'28px 20px', textAlign:'center',
|
||||||
|
|||||||
Reference in New Issue
Block a user