added manual matching on books

This commit is contained in:
2026-01-02 16:54:40 +01:00
parent f5cfa29b64
commit 59fdadd288
18 changed files with 1235 additions and 494 deletions

View File

@@ -23,7 +23,6 @@ const AnimePlayer = (function() {
let hlsInstance = null;
let _manualExtensionId = null;
let _searchTimeout = null;
const els = {
wrapper: null,
@@ -46,10 +45,6 @@ const AnimePlayer = (function() {
dlConfirmBtn: null,
dlCancelBtn: null,
manualMatchBtn: null,
matchModal: null,
matchInput: null,
matchList: null,
closeMatchModalBtn: null
};
function init(animeId, initialSource, isLocal, animeData) {
@@ -83,31 +78,6 @@ const AnimePlayer = (function() {
els.dlConfirmBtn = document.getElementById('confirm-dl-btn');
els.dlCancelBtn = document.getElementById('cancel-dl-btn');
els.manualMatchBtn = document.getElementById('manual-match-btn');
els.matchModal = document.getElementById('match-modal');
els.matchInput = document.getElementById('match-search-input');
els.matchList = document.getElementById('match-results-list');
els.closeMatchModalBtn = document.getElementById('close-match-modal');
// Event Listeners para Manual Match
if (els.manualMatchBtn) els.manualMatchBtn.addEventListener('click', openMatchModal);
if (els.closeMatchModalBtn) els.closeMatchModalBtn.addEventListener('click', closeMatchModal);
// Cerrar modal al hacer click fuera
if (els.matchModal) {
els.matchModal.addEventListener('click', (e) => {
if (e.target === els.matchModal) closeMatchModal();
});
}
// Input de búsqueda con Debounce
if (els.matchInput) {
els.matchInput.addEventListener('input', (e) => {
clearTimeout(_searchTimeout);
_searchTimeout = setTimeout(() => {
executeMatchSearch(e.target.value);
}, 500); // Esperar 500ms tras dejar de escribir
});
}
const closeDlModalBtn = document.getElementById('close-download-modal');
@@ -168,112 +138,33 @@ const AnimePlayer = (function() {
if(els.subDubToggle) els.subDubToggle.addEventListener('click', toggleAudioMode);
if(els.serverSelect) els.serverSelect.addEventListener('change', () => loadStream());
if(els.extSelect) els.extSelect.addEventListener('change', () => handleExtensionChange(true));
if (els.manualMatchBtn) {
els.manualMatchBtn.addEventListener('click', openMatchModal);
}
loadExtensionsList();
}
function openMatchModal() {
if (!els.matchModal) return;
const currentExt = els.extSelect.value;
if (!currentExt || currentExt === 'local') return;
// Limpiar contenido previo
els.matchInput.value = '';
els.matchList.innerHTML = `<div style="padding:20px; text-align:center; color:#777;">Type to search in ${els.extSelect.value}...</div>`;
// 1. Mostrar el contenedor (para que el navegador calcule el layout)
els.matchModal.style.display = 'flex';
// 2. Pequeño delay o forzar reflow para que la transición de opacidad funcione
requestAnimationFrame(() => {
els.matchModal.classList.add('show');
});
setTimeout(() => els.matchInput.focus(), 100);
}
function closeMatchModal() {
if (!els.matchModal) return;
els.matchModal.classList.remove('show');
setTimeout(() => {
if (!els.matchModal.classList.contains('show')) {
els.matchModal.style.display = 'none';
MatchModal.open({
provider: currentExt,
initialQuery: _animeTitle, // Variable existente en player.js
onSearch: async (query, prov) => {
const res = await fetch(`/api/search/${prov}?q=${encodeURIComponent(query)}`);
const data = await res.json();
return data.results || [];
},
onSelect: (item) => {
console.log("Selected Anime ID:", item.id);
_manualExtensionId = item.id;
loadStream();
}
}, 300);
}
async function executeMatchSearch(query) {
if (!query || query.trim().length < 2) return;
const ext = els.extSelect.value;
if (!ext || ext === 'local') return;
els.matchList.innerHTML = '<div class="spinner" style="margin: 20px auto;"></div>';
try {
const res = await fetch(`/api/search/${ext}?q=${encodeURIComponent(query)}`);
const data = await res.json();
renderMatchResults(data.results || []);
} catch (e) {
console.error("Match Search Error:", e);
els.matchList.innerHTML = '<p style="color:#ef4444; text-align:center;">Error searching extension.</p>';
}
}
function renderMatchResults(results) {
els.matchList.innerHTML = '';
if (results.length === 0) {
els.matchList.innerHTML = '<p style="text-align:center; color:#999;">No results found.</p>';
return;
}
results.forEach(item => {
const div = document.createElement('div');
div.className = 'match-item dl-item';
const img = (item.coverImage && item.coverImage.large) ? item.coverImage.large : "/public/assets/placeholder.svg";
const title = item.title.english || item.title.romaji || item.title || 'Unknown';
const externalUrl = item.url || '#'; // El parámetro URL del JSON
div.innerHTML = `
<img src="${img}" alt="cover">
<div class="match-info">
<span class="match-title">${title}</span>
<span class="match-meta">${item.releaseDate || item.year || ''}</span>
</div>
${item.url ? `
<a href="${externalUrl}" target="_blank" class="btn-view-source" title="View Source">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
<polyline points="15 3 21 3 21 9"></polyline>
<line x1="10" y1="14" x2="21" y2="3"></line>
</svg>
</a>
` : ''}
`;
div.onclick = (e) => {
if (e.target.closest('.btn-view-source')) return;
selectManualMatch(item);
};
els.matchList.appendChild(div);
});
}
function selectManualMatch(item) {
// 1. Guardar el ID de la extensión
_manualExtensionId = item.id;
console.log("Manual Match Selected:", _manualExtensionId, "for extension:", els.extSelect.value);
// 2. Cerrar modal
closeMatchModal();
// 3. Recargar el stream con el nuevo ID
loadStream();
}
async function openInMPV() {
if (!_rawVideoData) {
alert("No video loaded yet.");