'use strict';
// ---- Search: Seafile files + libraries and Plex music, all best-effort (a missing backend never blocks the others)
// Seafile: use the search index when the server has one; if it is missing OR comes back empty, scan file names library by library.
async function searchFiles(term, libs) {
const enc = encodeURIComponent, lower = term.toLowerCase();
let results = [], via = 'index';
try { results = (await sf.req(`/api2/search/?q=${enc(term)}&per_page=20`)).results || []; } catch {}
if (!results.length) { results = await walkSearch(libs, lower); via = 'scan'; }
return { results, via, scanned: libs.filter(l => !l.encrypted).length };
}
// Plex: the server-wide search first; if that finds nothing, filter the artists and albums of the chosen music library directly.
async function searchMusic(term) {
const enc = encodeURIComponent, lower = term.toLowerCase(), items = [];
try {
const mc = await px.get(`/hubs/search?query=${enc(term)}&limit=10`);
for (const hub of mc.Hub || []) for (const m of hub.Metadata || []) if (['artist', 'album', 'track'].includes(m.type)) items.push(m);
} catch {}
if (!items.length) {
const sec = await px.section(), big = '&X-Plex-Container-Start=0&X-Plex-Container-Size=3000&sort=titleSort';
const [artists, albums] = await Promise.all([px.get(`/library/sections/${sec}/all?type=8${big}`), px.get(`/library/sections/${sec}/all?type=9${big}`)]);
for (const a of artists.Metadata || []) if ((a.title || '').toLowerCase().includes(lower)) items.push({ ...a, type: 'artist' });
for (const a of albums.Metadata || []) if (((a.title || '') + ' ' + (a.parentTitle || '')).toLowerCase().includes(lower)) items.push({ ...a, type: 'album' });
}
return items.slice(0, 16);
}
// Plex movies and shows (and episodes) from the server-wide search.
async function searchVideo(term) {
const mc = await px.get(`/hubs/search?query=${encodeURIComponent(term)}&limit=12`), items = [];
for (const hub of mc.Hub || []) for (const m of hub.Metadata || []) if (['movie', 'show', 'episode'].includes(m.type)) items.push(m);
return items.slice(0, 24);
}
// Podgrab: the podcasts you follow, matched on title and author.
async function searchPodcasts(term) {
const list = await jfetch('/api/podcast/podcasts'), lower = term.toLowerCase();
return (Array.isArray(list) ? list : []).filter(s => ((pdF(s, 'title') || '') + ' ' + (pdF(s, 'author') || '')).toLowerCase().includes(lower)).slice(0, 12);
}
// The audiobook library, matched on title, author and series.
async function searchBooks(term) {
const [data] = await Promise.all([jfetch('/api/ab/books'), abLoadProg()]), all = Array.isArray(data) ? data : (data.books || Object.values(data)), lower = term.toLowerCase();
return all.filter(b => (abTitle(b) + ' ' + abAuthor(b) + ' ' + abSeries(b)).toLowerCase().includes(lower)).slice(0, 10);
}
function viewSearch() {
$('#view').innerHTML = `