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 }); }
|
||||
});
|
||||
|
||||
// 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) => {
|
||||
const movies = (req.body.movies || []).slice(0, 20);
|
||||
const movies = (req.body.movies || []).slice(0, 5);
|
||||
if (!movies.length) return res.json({});
|
||||
|
||||
const result = {};
|
||||
// 3er-Batches
|
||||
for (let i = 0; i < movies.length; i += 3) {
|
||||
const chunk = movies.slice(i, i + 3);
|
||||
await Promise.all(chunk.map(async m => {
|
||||
try {
|
||||
const extInfoId = await findExtInfoId(m.title, m.original_title);
|
||||
if (!extInfoId) { result[m.id] = false; return; }
|
||||
// Schnell-Check: nur P2P Seite 1 prüfen ob german vorhanden
|
||||
const p2p = await xrelFetch('/p2p/releases.json', { ext_info_id: extInfoId, per_page: 25 });
|
||||
const p2pHit = (p2p.list || []).some(r => r.main_lang === 'german');
|
||||
if (p2pHit) { result[m.id] = true; return; }
|
||||
// Auch Scene prüfen
|
||||
const scene = await xrelFetch('/release/ext_info.json', { id: extInfoId, per_page: 25 });
|
||||
const sceneHit = (scene.list || []).some(r => /german|deutsch/i.test(r.dirname || ''));
|
||||
result[m.id] = sceneHit;
|
||||
} catch (_) {
|
||||
result[m.id] = false;
|
||||
}
|
||||
}));
|
||||
if (i + 3 < movies.length) await new Promise(r => setTimeout(r, 250));
|
||||
}
|
||||
await Promise.all(movies.map(async m => {
|
||||
try {
|
||||
const extInfoId = await findExtInfoId(m.title, m.original_title);
|
||||
if (!extInfoId) { result[m.id] = false; return; }
|
||||
// P2P zuerst – main_lang ist zuverlässig
|
||||
const p2p = await xrelFetch('/p2p/releases.json', { ext_info_id: extInfoId, per_page: 25 }, 8000);
|
||||
if ((p2p.list || []).some(r => r.main_lang === 'german')) { result[m.id] = true; return; }
|
||||
// Scene als Fallback
|
||||
const scene = await xrelFetch('/release/ext_info.json', { id: extInfoId, per_page: 25 }, 8000);
|
||||
result[m.id] = (scene.list || []).some(r => /german|deutsch/i.test(r.dirname || ''));
|
||||
} catch (_) {
|
||||
result[m.id] = false;
|
||||
}
|
||||
}));
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
|
||||
@@ -261,13 +261,19 @@ function KinoGrid({ onOpenModal }) {
|
||||
api('/tools/media/now-playing')
|
||||
.then(list => {
|
||||
setMovies(list);
|
||||
// xREL-Batch-Check nach Laden
|
||||
api('/tools/media/xrel-check', {
|
||||
method: 'POST',
|
||||
body: { movies: list.map(m => ({ id: m.id, title: m.title, original_title: m.original_title, release_date: m.release_date })) }
|
||||
}).then(result => {
|
||||
setXrelIds(new Set(Object.entries(result).filter(([,v]) => v).map(([k]) => Number(k))));
|
||||
}).catch(() => {});
|
||||
// xREL-Badge: progressive 5er-Batches
|
||||
const moviesMeta = list.map(m => ({ id: m.id, title: m.title, original_title: m.original_title }));
|
||||
for (let i = 0; i < moviesMeta.length; i += 5) {
|
||||
const chunk = moviesMeta.slice(i, i + 5);
|
||||
api('/tools/media/xrel-check', { method:'POST', body:{ movies: chunk } })
|
||||
.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(e => setError(e.message))
|
||||
.finally(() => setLoading(false));
|
||||
@@ -320,12 +326,18 @@ function DemnächstGrid({ onOpenModal }) {
|
||||
.then(grps => {
|
||||
setGroups(grps);
|
||||
const allMovies = Object.values(grps).flat();
|
||||
api('/tools/media/xrel-check', {
|
||||
method: 'POST',
|
||||
body: { movies: allMovies.map(m => ({ id: m.id, title: m.title, original_title: m.original_title, release_date: m.release_date })) }
|
||||
}).then(result => {
|
||||
setXrelIds(new Set(Object.entries(result).filter(([,v]) => v).map(([k]) => Number(k))));
|
||||
}).catch(() => {});
|
||||
const moviesMeta = allMovies.map(m => ({ id: m.id, title: m.title, original_title: m.original_title }));
|
||||
for (let i = 0; i < moviesMeta.length; i += 5) {
|
||||
const chunk = moviesMeta.slice(i, i + 5);
|
||||
api('/tools/media/xrel-check', { method:'POST', body:{ movies: chunk } })
|
||||
.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(e => setError(e.message))
|
||||
.finally(() => setLoading(false));
|
||||
@@ -594,30 +606,23 @@ function MovieModal({ tmdbId, onClose, mobile }) {
|
||||
<div style={{ display:'flex', flexDirection:'column', gap:4 }}>
|
||||
{detail.xrel.map((r, i) => (
|
||||
<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)',
|
||||
borderRadius:6, padding:'6px 10px', textDecoration:'none',
|
||||
}}>
|
||||
<span style={{ fontSize:9, fontWeight:700, fontFamily:'monospace',
|
||||
color: r.p2p ? '#4ecdc4' : '#ff6b9d',
|
||||
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'}
|
||||
</span>
|
||||
<span style={{ fontSize:10, color:'rgba(255,255,255,0.75)', fontFamily:'monospace',
|
||||
flex:1, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
|
||||
{r.dirname}
|
||||
</span>
|
||||
{r.size && (
|
||||
<span style={{ fontSize:9, color:'rgba(255,255,255,0.35)', fontFamily:'monospace', flexShrink:0 }}>
|
||||
{r.size}
|
||||
</span>
|
||||
)}
|
||||
{r.date && (
|
||||
<span style={{ fontSize:9, color:'rgba(255,255,255,0.25)', fontFamily:'monospace', flexShrink:0 }}>
|
||||
{r.date}
|
||||
</span>
|
||||
)}
|
||||
<div style={{ flex:1, minWidth:0 }}>
|
||||
<DirnameWrapped dirname={r.dirname} />
|
||||
<div style={{ display:'flex', gap:8, marginTop:2 }}>
|
||||
{r.size && <span style={{ fontSize:9, color:'rgba(255,255,255,0.35)', fontFamily:'monospace' }}>{r.size}</span>}
|
||||
{r.date && <span style={{ fontSize:9, color:'rgba(255,255,255,0.25)', fontFamily:'monospace' }}>{r.date}</span>}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</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 }) {
|
||||
return (
|
||||
<div style={{ padding:'28px 20px', textAlign:'center',
|
||||
|
||||
Reference in New Issue
Block a user