support for extension sources on watchparties
This commit is contained in:
@@ -88,6 +88,7 @@ const RoomsApp = (function() {
|
|||||||
|
|
||||||
// Anime Search Modal
|
// Anime Search Modal
|
||||||
animeSearchModal: document.getElementById('anime-search-modal'),
|
animeSearchModal: document.getElementById('anime-search-modal'),
|
||||||
|
searchSourceSelect: document.getElementById('search-source-select'),
|
||||||
animeSearchInput: document.getElementById('anime-search-input'),
|
animeSearchInput: document.getElementById('anime-search-input'),
|
||||||
animeResults: document.getElementById('anime-results'),
|
animeResults: document.getElementById('anime-results'),
|
||||||
closeSearchBtn: document.getElementById('close-search-modal'),
|
closeSearchBtn: document.getElementById('close-search-modal'),
|
||||||
@@ -222,6 +223,24 @@ const RoomsApp = (function() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
extensionsReady = true;
|
extensionsReady = true;
|
||||||
|
|
||||||
|
// AÑADE ESTO AQUÍ: Llenar el dropdown de búsqueda inmediatamente
|
||||||
|
populateSearchDropdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
// AÑADE ESTA NUEVA FUNCIÓN FUERA (o dentro del scope de RoomsApp)
|
||||||
|
function populateSearchDropdown() {
|
||||||
|
if (!elements.searchSourceSelect) return;
|
||||||
|
|
||||||
|
// Reiniciar y poner AniList primero
|
||||||
|
elements.searchSourceSelect.innerHTML = '<option value="anilist">AniList</option>';
|
||||||
|
|
||||||
|
extensionsStore.list.forEach(ext => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = ext;
|
||||||
|
opt.textContent = ext[0].toUpperCase() + ext.slice(1);
|
||||||
|
elements.searchSourceSelect.appendChild(opt);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupEventListeners() {
|
function setupEventListeners() {
|
||||||
@@ -369,6 +388,16 @@ const RoomsApp = (function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
elements.roomExtSelect.value = selectedAnimeData.source || extensionsStore.list[0];
|
elements.roomExtSelect.value = selectedAnimeData.source || extensionsStore.list[0];
|
||||||
|
if (elements.searchSourceSelect) {
|
||||||
|
elements.searchSourceSelect.innerHTML = '<option value="anilist">AniList</option>';
|
||||||
|
|
||||||
|
extensionsStore.list.forEach(ext => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = ext;
|
||||||
|
opt.textContent = ext[0].toUpperCase() + ext.slice(1);
|
||||||
|
elements.searchSourceSelect.appendChild(opt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await onQuickExtensionChange(null, true);
|
await onQuickExtensionChange(null, true);
|
||||||
}
|
}
|
||||||
@@ -459,17 +488,28 @@ const RoomsApp = (function() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
let title, img, id;
|
let title, img, id, source;
|
||||||
|
|
||||||
const titleEl = itemLink.querySelector('.search-title');
|
// Try to get data from dataset (Extension Results)
|
||||||
const imgEl = itemLink.querySelector('.search-poster, img');
|
if (itemLink.dataset.source) {
|
||||||
|
id = itemLink.dataset.id;
|
||||||
|
source = itemLink.dataset.source;
|
||||||
|
title = itemLink.dataset.title;
|
||||||
|
img = itemLink.dataset.image;
|
||||||
|
}
|
||||||
|
// Fallback to DOM parsing (AniList/SearchManager Results)
|
||||||
|
else {
|
||||||
|
const titleEl = itemLink.querySelector('.search-title');
|
||||||
|
const imgEl = itemLink.querySelector('.search-poster, img');
|
||||||
|
|
||||||
title = titleEl ? titleEl.textContent : (itemLink.textContent.trim() || 'Unknown');
|
title = titleEl ? titleEl.textContent : (itemLink.textContent.trim() || 'Unknown');
|
||||||
img = imgEl ? (imgEl.src || imgEl.dataset.src || '/public/assets/placeholder.svg') : '/public/assets/placeholder.svg';
|
img = imgEl ? (imgEl.src || imgEl.dataset.src || '/public/assets/placeholder.svg') : '/public/assets/placeholder.svg';
|
||||||
|
|
||||||
const href = itemLink.getAttribute('href') || '';
|
const href = itemLink.getAttribute('href') || '';
|
||||||
const hrefParts = href.split('/').filter(p => p);
|
const hrefParts = href.split('/').filter(p => p);
|
||||||
id = hrefParts[hrefParts.length - 1] || itemLink.dataset.id;
|
id = hrefParts[hrefParts.length - 1] || itemLink.dataset.id;
|
||||||
|
source = 'anilist';
|
||||||
|
}
|
||||||
|
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
@@ -477,12 +517,14 @@ const RoomsApp = (function() {
|
|||||||
id: id,
|
id: id,
|
||||||
title: title,
|
title: title,
|
||||||
image: img,
|
image: img,
|
||||||
source: 'anilist'
|
source: source // Set the detected source
|
||||||
};
|
};
|
||||||
|
|
||||||
const animeResultObj = {
|
const animeResultObj = {
|
||||||
id: id,
|
id: id,
|
||||||
title: title,
|
title: title,
|
||||||
cover: img
|
cover: img,
|
||||||
|
source: source // Pass to next function
|
||||||
};
|
};
|
||||||
|
|
||||||
showConfigStep();
|
showConfigStep();
|
||||||
@@ -649,18 +691,34 @@ const RoomsApp = (function() {
|
|||||||
let episodeToPlay = activeContext.episode;
|
let episodeToPlay = activeContext.episode;
|
||||||
if (fromModal && elements.inpEpisode) episodeToPlay = elements.inpEpisode.value;
|
if (fromModal && elements.inpEpisode) episodeToPlay = elements.inpEpisode.value;
|
||||||
|
|
||||||
const ext = overrides.forceExtension ||
|
let ext = overrides.forceExtension ||
|
||||||
(fromModal ? (configState.extension || elements.selExtension?.value) : null) ||
|
(fromModal ? (configState.extension || elements.selExtension?.value) : null) ||
|
||||||
activeContext.extension ||
|
activeContext.extension ||
|
||||||
(elements.roomExtSelect ? elements.roomExtSelect.value : null);
|
(elements.roomExtSelect ? elements.roomExtSelect.value : null);
|
||||||
|
|
||||||
const server = overrides.forceServer ||
|
|
||||||
(fromModal ? (configState.server || elements.selServer?.value) : null) ||
|
|
||||||
activeContext.server ||
|
|
||||||
(elements.roomServerSelect ? elements.roomServerSelect.value : null);
|
|
||||||
|
|
||||||
const category = elements.roomSdToggle?.getAttribute('data-state') || activeContext.category || 'sub';
|
const category = elements.roomSdToggle?.getAttribute('data-state') || activeContext.category || 'sub';
|
||||||
|
|
||||||
|
let currentSource = selectedAnimeData.source || 'anilist';
|
||||||
|
|
||||||
|
if (currentSource !== 'anilist') {
|
||||||
|
ext = currentSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
let server = overrides.forceServer;
|
||||||
|
|
||||||
|
if (!server) {
|
||||||
|
if (fromModal) {
|
||||||
|
server = configState.server || elements.selServer?.value;
|
||||||
|
} else if (currentSource !== 'anilist' || ext === elements.roomExtSelect?.value) {
|
||||||
|
server = elements.roomServerSelect?.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!server && extensionsStore.settings[ext]) {
|
||||||
|
const extSettings = extensionsStore.settings[ext];
|
||||||
|
server = extSettings.episodeServers?.[0] || 'Default';
|
||||||
|
}
|
||||||
|
|
||||||
if (!ext || !server) {
|
if (!ext || !server) {
|
||||||
console.warn("Faltan datos (ext o server).", {ext, server});
|
console.warn("Faltan datos (ext o server).", {ext, server});
|
||||||
if (fromModal && elements.configError) {
|
if (fromModal && elements.configError) {
|
||||||
@@ -681,8 +739,8 @@ const RoomsApp = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const apiUrl = `/api/watch/stream?animeId=${selectedAnimeData.id}&episode=${episodeToPlay}&server=${encodeURIComponent(server)}&category=${category}&ext=${ext}&source=anilist`;
|
const currentSource = selectedAnimeData.source || 'anilist';
|
||||||
console.log('Fetching stream:', apiUrl);
|
const apiUrl = `/api/watch/stream?animeId=${selectedAnimeData.id}&episode=${episodeToPlay}&server=${encodeURIComponent(server)}&category=${category}&ext=${ext}&source=${currentSource}`; console.log('Fetching stream:', apiUrl);
|
||||||
|
|
||||||
const res = await fetch(apiUrl);
|
const res = await fetch(apiUrl);
|
||||||
if (!res.ok) throw new Error(`Error ${res.status}: Failed to fetch stream`);
|
if (!res.ok) throw new Error(`Error ${res.status}: Failed to fetch stream`);
|
||||||
@@ -699,8 +757,13 @@ const RoomsApp = (function() {
|
|||||||
|
|
||||||
let proxyUrl = `/api/proxy?url=${encodeURIComponent(source.url)}`;
|
let proxyUrl = `/api/proxy?url=${encodeURIComponent(source.url)}`;
|
||||||
const headers = data.headers || {};
|
const headers = data.headers || {};
|
||||||
if (headers['Referer']) proxyUrl += `&referer=${encodeURIComponent(headers['Referer'])}`;
|
if (headers['Referer']) {
|
||||||
|
proxyUrl += `&referer=${encodeURIComponent(headers['Referer'])}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers['User-Agent']) {
|
||||||
|
proxyUrl += `&userAgent=${encodeURIComponent(headers['User-Agent'])}`;
|
||||||
|
}
|
||||||
const subtitles = (source.subtitles || []).map(sub => ({
|
const subtitles = (source.subtitles || []).map(sub => ({
|
||||||
label: sub.language,
|
label: sub.language,
|
||||||
srclang: sub.id || sub.language.toLowerCase().slice(0, 2),
|
srclang: sub.id || sub.language.toLowerCase().slice(0, 2),
|
||||||
@@ -797,14 +860,22 @@ const RoomsApp = (function() {
|
|||||||
updateCountBtn();
|
updateCountBtn();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/anime/${animeResult.id}?source=anilist`);
|
// Use the source from the result (default to anilist)
|
||||||
|
const sourceParam = animeResult.source || 'anilist';
|
||||||
|
|
||||||
|
// Fetch details using the specific source
|
||||||
|
const response = await fetch(`/api/anime/${animeResult.id}?source=${sourceParam}`);
|
||||||
|
|
||||||
if (!response.ok) throw new Error("Failed to load anime details");
|
if (!response.ok) throw new Error("Failed to load anime details");
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
currentAnimeDetails = data;
|
currentAnimeDetails = data;
|
||||||
|
|
||||||
|
// Save metadata
|
||||||
if (selectedAnimeData) {
|
if (selectedAnimeData) {
|
||||||
selectedAnimeData.malId = data.idMal;
|
selectedAnimeData.malId = data.idMal; // Might be null for extensions, that's okay
|
||||||
|
// Ensure source is persisted
|
||||||
|
selectedAnimeData.source = sourceParam;
|
||||||
}
|
}
|
||||||
|
|
||||||
modalTotalEpisodes = data.episodes || 12;
|
modalTotalEpisodes = data.episodes || 12;
|
||||||
@@ -813,9 +884,19 @@ const RoomsApp = (function() {
|
|||||||
renderModalEpisodes();
|
renderModalEpisodes();
|
||||||
setupModalPaginationControls();
|
setupModalPaginationControls();
|
||||||
|
|
||||||
|
// Auto-select the extension in the config dropdown if it matches
|
||||||
|
if (extensionsReady && elements.selExtension && sourceParam !== 'anilist') {
|
||||||
|
// If the extension we searched with is in the list, select it
|
||||||
|
if (Array.from(elements.selExtension.options).some(o => o.value === sourceParam)) {
|
||||||
|
elements.selExtension.value = sourceParam;
|
||||||
|
handleModalExtensionChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching details", error);
|
console.error("Error fetching details", error);
|
||||||
gridContainer.innerHTML = '';
|
gridContainer.innerHTML = '';
|
||||||
|
// If details fail (common with strict scrapers), show manual input
|
||||||
document.querySelector('.manual-ep-input').style.display = 'block';
|
document.querySelector('.manual-ep-input').style.display = 'block';
|
||||||
document.getElementById('modal-pagination').style.display = 'none';
|
document.getElementById('modal-pagination').style.display = 'none';
|
||||||
}
|
}
|
||||||
@@ -1985,12 +2066,62 @@ const RoomsApp = (function() {
|
|||||||
async function searchAnime() {
|
async function searchAnime() {
|
||||||
const query = elements.animeSearchInput.value.trim();
|
const query = elements.animeSearchInput.value.trim();
|
||||||
if (!query) return;
|
if (!query) return;
|
||||||
elements.animeResults.innerHTML = '<div style="padding:20px;text-align:center;color:#888;">Searching...</div>';
|
|
||||||
if (window.SearchManager) {
|
const source = elements.searchSourceSelect ? elements.searchSourceSelect.value : 'anilist';
|
||||||
await window.SearchManager.search(query, 'anime', elements.animeResults);
|
elements.animeResults.innerHTML = '<div style="padding:20px;text-align:center;color:#888;"><div class="spinner" style="margin:0 auto 10px;"></div>Searching...</div>';
|
||||||
} else {
|
|
||||||
elements.animeResults.innerHTML = 'SearchManager not loaded';
|
// 1. ANILIST SEARCH (Legacy)
|
||||||
|
if (source === 'anilist') {
|
||||||
|
if (window.SearchManager) {
|
||||||
|
await window.SearchManager.search(query, 'anime', elements.animeResults);
|
||||||
|
} else {
|
||||||
|
elements.animeResults.innerHTML = 'SearchManager not loaded';
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. EXTENSION SEARCH
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/search/${source}?q=${encodeURIComponent(query)}`);
|
||||||
|
const data = await res.json();
|
||||||
|
renderExtensionResults(data.results || [], source);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Search error:", e);
|
||||||
|
elements.animeResults.innerHTML = '<div style="padding:20px;text-align:center;color:#ff6b6b;">Search failed</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderExtensionResults(results, sourceName) {
|
||||||
|
elements.animeResults.innerHTML = '';
|
||||||
|
|
||||||
|
if (results.length === 0) {
|
||||||
|
elements.animeResults.innerHTML = '<div style="padding:20px;text-align:center;color:#888;">No results found</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
results.forEach(item => {
|
||||||
|
const title = item.title.english || item.title.romaji || item.title.native || 'Unknown Title';
|
||||||
|
const image = item.coverImage?.large || item.coverImage?.medium || '/public/assets/placeholder.svg';
|
||||||
|
|
||||||
|
// We add data-source attribute to identify where it came from
|
||||||
|
const div = document.createElement('a');
|
||||||
|
div.className = 'anime-result-item';
|
||||||
|
div.href = '#'; // Prevent navigation
|
||||||
|
div.dataset.id = item.id;
|
||||||
|
div.dataset.source = sourceName; // Important: Store the extension name
|
||||||
|
div.dataset.title = title;
|
||||||
|
div.dataset.image = image;
|
||||||
|
|
||||||
|
div.innerHTML = `
|
||||||
|
<img src="${image}" class="search-poster" loading="lazy">
|
||||||
|
<div class="search-info">
|
||||||
|
<div class="search-title">${escapeHtml(title)}</div>
|
||||||
|
<div class="search-meta" style="color:var(--brand-color)">${sourceName}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
elements.animeResults.appendChild(div);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeHtml(text) {
|
function escapeHtml(text) {
|
||||||
|
|||||||
@@ -206,7 +206,14 @@
|
|||||||
<div id="step-search">
|
<div id="step-search">
|
||||||
<h2 class="modal-title">Select Anime</h2>
|
<h2 class="modal-title">Select Anime</h2>
|
||||||
<div class="search-bar">
|
<div class="search-bar">
|
||||||
<input type="text" id="anime-search-input" placeholder="Search anime..." />
|
<div class="quick-select-wrapper" style="min-width: 130px; background: rgba(255,255,255,0.05);">
|
||||||
|
<select id="search-source-select" class="quick-select">
|
||||||
|
<option value="anilist">AniList</option>
|
||||||
|
</select>
|
||||||
|
<div class="select-arrow">▼</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="text" id="anime-search-input" placeholder="Search anime..." autocomplete="off"/>
|
||||||
<button id="anime-search-btn">Search</button>
|
<button id="anime-search-btn">Search</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="anime-results" class="anime-results"></div>
|
<div id="anime-results" class="anime-results"></div>
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ const RoomsApp = (function() {
|
|||||||
|
|
||||||
// Anime Search Modal
|
// Anime Search Modal
|
||||||
animeSearchModal: document.getElementById('anime-search-modal'),
|
animeSearchModal: document.getElementById('anime-search-modal'),
|
||||||
|
searchSourceSelect: document.getElementById('search-source-select'),
|
||||||
animeSearchInput: document.getElementById('anime-search-input'),
|
animeSearchInput: document.getElementById('anime-search-input'),
|
||||||
animeResults: document.getElementById('anime-results'),
|
animeResults: document.getElementById('anime-results'),
|
||||||
closeSearchBtn: document.getElementById('close-search-modal'),
|
closeSearchBtn: document.getElementById('close-search-modal'),
|
||||||
@@ -222,6 +223,24 @@ const RoomsApp = (function() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
extensionsReady = true;
|
extensionsReady = true;
|
||||||
|
|
||||||
|
// AÑADE ESTO AQUÍ: Llenar el dropdown de búsqueda inmediatamente
|
||||||
|
populateSearchDropdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
// AÑADE ESTA NUEVA FUNCIÓN FUERA (o dentro del scope de RoomsApp)
|
||||||
|
function populateSearchDropdown() {
|
||||||
|
if (!elements.searchSourceSelect) return;
|
||||||
|
|
||||||
|
// Reiniciar y poner AniList primero
|
||||||
|
elements.searchSourceSelect.innerHTML = '<option value="anilist">AniList</option>';
|
||||||
|
|
||||||
|
extensionsStore.list.forEach(ext => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = ext;
|
||||||
|
opt.textContent = ext[0].toUpperCase() + ext.slice(1);
|
||||||
|
elements.searchSourceSelect.appendChild(opt);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupEventListeners() {
|
function setupEventListeners() {
|
||||||
@@ -369,6 +388,16 @@ const RoomsApp = (function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
elements.roomExtSelect.value = selectedAnimeData.source || extensionsStore.list[0];
|
elements.roomExtSelect.value = selectedAnimeData.source || extensionsStore.list[0];
|
||||||
|
if (elements.searchSourceSelect) {
|
||||||
|
elements.searchSourceSelect.innerHTML = '<option value="anilist">AniList</option>';
|
||||||
|
|
||||||
|
extensionsStore.list.forEach(ext => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = ext;
|
||||||
|
opt.textContent = ext[0].toUpperCase() + ext.slice(1);
|
||||||
|
elements.searchSourceSelect.appendChild(opt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await onQuickExtensionChange(null, true);
|
await onQuickExtensionChange(null, true);
|
||||||
}
|
}
|
||||||
@@ -459,17 +488,28 @@ const RoomsApp = (function() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
let title, img, id;
|
let title, img, id, source;
|
||||||
|
|
||||||
const titleEl = itemLink.querySelector('.search-title');
|
// Try to get data from dataset (Extension Results)
|
||||||
const imgEl = itemLink.querySelector('.search-poster, img');
|
if (itemLink.dataset.source) {
|
||||||
|
id = itemLink.dataset.id;
|
||||||
|
source = itemLink.dataset.source;
|
||||||
|
title = itemLink.dataset.title;
|
||||||
|
img = itemLink.dataset.image;
|
||||||
|
}
|
||||||
|
// Fallback to DOM parsing (AniList/SearchManager Results)
|
||||||
|
else {
|
||||||
|
const titleEl = itemLink.querySelector('.search-title');
|
||||||
|
const imgEl = itemLink.querySelector('.search-poster, img');
|
||||||
|
|
||||||
title = titleEl ? titleEl.textContent : (itemLink.textContent.trim() || 'Unknown');
|
title = titleEl ? titleEl.textContent : (itemLink.textContent.trim() || 'Unknown');
|
||||||
img = imgEl ? (imgEl.src || imgEl.dataset.src || '/public/assets/placeholder.svg') : '/public/assets/placeholder.svg';
|
img = imgEl ? (imgEl.src || imgEl.dataset.src || '/public/assets/placeholder.svg') : '/public/assets/placeholder.svg';
|
||||||
|
|
||||||
const href = itemLink.getAttribute('href') || '';
|
const href = itemLink.getAttribute('href') || '';
|
||||||
const hrefParts = href.split('/').filter(p => p);
|
const hrefParts = href.split('/').filter(p => p);
|
||||||
id = hrefParts[hrefParts.length - 1] || itemLink.dataset.id;
|
id = hrefParts[hrefParts.length - 1] || itemLink.dataset.id;
|
||||||
|
source = 'anilist';
|
||||||
|
}
|
||||||
|
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
@@ -477,12 +517,14 @@ const RoomsApp = (function() {
|
|||||||
id: id,
|
id: id,
|
||||||
title: title,
|
title: title,
|
||||||
image: img,
|
image: img,
|
||||||
source: 'anilist'
|
source: source // Set the detected source
|
||||||
};
|
};
|
||||||
|
|
||||||
const animeResultObj = {
|
const animeResultObj = {
|
||||||
id: id,
|
id: id,
|
||||||
title: title,
|
title: title,
|
||||||
cover: img
|
cover: img,
|
||||||
|
source: source // Pass to next function
|
||||||
};
|
};
|
||||||
|
|
||||||
showConfigStep();
|
showConfigStep();
|
||||||
@@ -649,18 +691,34 @@ const RoomsApp = (function() {
|
|||||||
let episodeToPlay = activeContext.episode;
|
let episodeToPlay = activeContext.episode;
|
||||||
if (fromModal && elements.inpEpisode) episodeToPlay = elements.inpEpisode.value;
|
if (fromModal && elements.inpEpisode) episodeToPlay = elements.inpEpisode.value;
|
||||||
|
|
||||||
const ext = overrides.forceExtension ||
|
let ext = overrides.forceExtension ||
|
||||||
(fromModal ? (configState.extension || elements.selExtension?.value) : null) ||
|
(fromModal ? (configState.extension || elements.selExtension?.value) : null) ||
|
||||||
activeContext.extension ||
|
activeContext.extension ||
|
||||||
(elements.roomExtSelect ? elements.roomExtSelect.value : null);
|
(elements.roomExtSelect ? elements.roomExtSelect.value : null);
|
||||||
|
|
||||||
const server = overrides.forceServer ||
|
|
||||||
(fromModal ? (configState.server || elements.selServer?.value) : null) ||
|
|
||||||
activeContext.server ||
|
|
||||||
(elements.roomServerSelect ? elements.roomServerSelect.value : null);
|
|
||||||
|
|
||||||
const category = elements.roomSdToggle?.getAttribute('data-state') || activeContext.category || 'sub';
|
const category = elements.roomSdToggle?.getAttribute('data-state') || activeContext.category || 'sub';
|
||||||
|
|
||||||
|
let currentSource = selectedAnimeData.source || 'anilist';
|
||||||
|
|
||||||
|
if (currentSource !== 'anilist') {
|
||||||
|
ext = currentSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
let server = overrides.forceServer;
|
||||||
|
|
||||||
|
if (!server) {
|
||||||
|
if (fromModal) {
|
||||||
|
server = configState.server || elements.selServer?.value;
|
||||||
|
} else if (currentSource !== 'anilist' || ext === elements.roomExtSelect?.value) {
|
||||||
|
server = elements.roomServerSelect?.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!server && extensionsStore.settings[ext]) {
|
||||||
|
const extSettings = extensionsStore.settings[ext];
|
||||||
|
server = extSettings.episodeServers?.[0] || 'Default';
|
||||||
|
}
|
||||||
|
|
||||||
if (!ext || !server) {
|
if (!ext || !server) {
|
||||||
console.warn("Faltan datos (ext o server).", {ext, server});
|
console.warn("Faltan datos (ext o server).", {ext, server});
|
||||||
if (fromModal && elements.configError) {
|
if (fromModal && elements.configError) {
|
||||||
@@ -681,8 +739,8 @@ const RoomsApp = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const apiUrl = `/api/watch/stream?animeId=${selectedAnimeData.id}&episode=${episodeToPlay}&server=${encodeURIComponent(server)}&category=${category}&ext=${ext}&source=anilist`;
|
const currentSource = selectedAnimeData.source || 'anilist';
|
||||||
console.log('Fetching stream:', apiUrl);
|
const apiUrl = `/api/watch/stream?animeId=${selectedAnimeData.id}&episode=${episodeToPlay}&server=${encodeURIComponent(server)}&category=${category}&ext=${ext}&source=${currentSource}`; console.log('Fetching stream:', apiUrl);
|
||||||
|
|
||||||
const res = await fetch(apiUrl);
|
const res = await fetch(apiUrl);
|
||||||
if (!res.ok) throw new Error(`Error ${res.status}: Failed to fetch stream`);
|
if (!res.ok) throw new Error(`Error ${res.status}: Failed to fetch stream`);
|
||||||
@@ -699,8 +757,13 @@ const RoomsApp = (function() {
|
|||||||
|
|
||||||
let proxyUrl = `/api/proxy?url=${encodeURIComponent(source.url)}`;
|
let proxyUrl = `/api/proxy?url=${encodeURIComponent(source.url)}`;
|
||||||
const headers = data.headers || {};
|
const headers = data.headers || {};
|
||||||
if (headers['Referer']) proxyUrl += `&referer=${encodeURIComponent(headers['Referer'])}`;
|
if (headers['Referer']) {
|
||||||
|
proxyUrl += `&referer=${encodeURIComponent(headers['Referer'])}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers['User-Agent']) {
|
||||||
|
proxyUrl += `&userAgent=${encodeURIComponent(headers['User-Agent'])}`;
|
||||||
|
}
|
||||||
const subtitles = (source.subtitles || []).map(sub => ({
|
const subtitles = (source.subtitles || []).map(sub => ({
|
||||||
label: sub.language,
|
label: sub.language,
|
||||||
srclang: sub.id || sub.language.toLowerCase().slice(0, 2),
|
srclang: sub.id || sub.language.toLowerCase().slice(0, 2),
|
||||||
@@ -797,14 +860,22 @@ const RoomsApp = (function() {
|
|||||||
updateCountBtn();
|
updateCountBtn();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/anime/${animeResult.id}?source=anilist`);
|
// Use the source from the result (default to anilist)
|
||||||
|
const sourceParam = animeResult.source || 'anilist';
|
||||||
|
|
||||||
|
// Fetch details using the specific source
|
||||||
|
const response = await fetch(`/api/anime/${animeResult.id}?source=${sourceParam}`);
|
||||||
|
|
||||||
if (!response.ok) throw new Error("Failed to load anime details");
|
if (!response.ok) throw new Error("Failed to load anime details");
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
currentAnimeDetails = data;
|
currentAnimeDetails = data;
|
||||||
|
|
||||||
|
// Save metadata
|
||||||
if (selectedAnimeData) {
|
if (selectedAnimeData) {
|
||||||
selectedAnimeData.malId = data.idMal;
|
selectedAnimeData.malId = data.idMal; // Might be null for extensions, that's okay
|
||||||
|
// Ensure source is persisted
|
||||||
|
selectedAnimeData.source = sourceParam;
|
||||||
}
|
}
|
||||||
|
|
||||||
modalTotalEpisodes = data.episodes || 12;
|
modalTotalEpisodes = data.episodes || 12;
|
||||||
@@ -813,9 +884,19 @@ const RoomsApp = (function() {
|
|||||||
renderModalEpisodes();
|
renderModalEpisodes();
|
||||||
setupModalPaginationControls();
|
setupModalPaginationControls();
|
||||||
|
|
||||||
|
// Auto-select the extension in the config dropdown if it matches
|
||||||
|
if (extensionsReady && elements.selExtension && sourceParam !== 'anilist') {
|
||||||
|
// If the extension we searched with is in the list, select it
|
||||||
|
if (Array.from(elements.selExtension.options).some(o => o.value === sourceParam)) {
|
||||||
|
elements.selExtension.value = sourceParam;
|
||||||
|
handleModalExtensionChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching details", error);
|
console.error("Error fetching details", error);
|
||||||
gridContainer.innerHTML = '';
|
gridContainer.innerHTML = '';
|
||||||
|
// If details fail (common with strict scrapers), show manual input
|
||||||
document.querySelector('.manual-ep-input').style.display = 'block';
|
document.querySelector('.manual-ep-input').style.display = 'block';
|
||||||
document.getElementById('modal-pagination').style.display = 'none';
|
document.getElementById('modal-pagination').style.display = 'none';
|
||||||
}
|
}
|
||||||
@@ -1985,12 +2066,62 @@ const RoomsApp = (function() {
|
|||||||
async function searchAnime() {
|
async function searchAnime() {
|
||||||
const query = elements.animeSearchInput.value.trim();
|
const query = elements.animeSearchInput.value.trim();
|
||||||
if (!query) return;
|
if (!query) return;
|
||||||
elements.animeResults.innerHTML = '<div style="padding:20px;text-align:center;color:#888;">Searching...</div>';
|
|
||||||
if (window.SearchManager) {
|
const source = elements.searchSourceSelect ? elements.searchSourceSelect.value : 'anilist';
|
||||||
await window.SearchManager.search(query, 'anime', elements.animeResults);
|
elements.animeResults.innerHTML = '<div style="padding:20px;text-align:center;color:#888;"><div class="spinner" style="margin:0 auto 10px;"></div>Searching...</div>';
|
||||||
} else {
|
|
||||||
elements.animeResults.innerHTML = 'SearchManager not loaded';
|
// 1. ANILIST SEARCH (Legacy)
|
||||||
|
if (source === 'anilist') {
|
||||||
|
if (window.SearchManager) {
|
||||||
|
await window.SearchManager.search(query, 'anime', elements.animeResults);
|
||||||
|
} else {
|
||||||
|
elements.animeResults.innerHTML = 'SearchManager not loaded';
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. EXTENSION SEARCH
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/search/${source}?q=${encodeURIComponent(query)}`);
|
||||||
|
const data = await res.json();
|
||||||
|
renderExtensionResults(data.results || [], source);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Search error:", e);
|
||||||
|
elements.animeResults.innerHTML = '<div style="padding:20px;text-align:center;color:#ff6b6b;">Search failed</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderExtensionResults(results, sourceName) {
|
||||||
|
elements.animeResults.innerHTML = '';
|
||||||
|
|
||||||
|
if (results.length === 0) {
|
||||||
|
elements.animeResults.innerHTML = '<div style="padding:20px;text-align:center;color:#888;">No results found</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
results.forEach(item => {
|
||||||
|
const title = item.title.english || item.title.romaji || item.title.native || 'Unknown Title';
|
||||||
|
const image = item.coverImage?.large || item.coverImage?.medium || '/public/assets/placeholder.svg';
|
||||||
|
|
||||||
|
// We add data-source attribute to identify where it came from
|
||||||
|
const div = document.createElement('a');
|
||||||
|
div.className = 'anime-result-item';
|
||||||
|
div.href = '#'; // Prevent navigation
|
||||||
|
div.dataset.id = item.id;
|
||||||
|
div.dataset.source = sourceName; // Important: Store the extension name
|
||||||
|
div.dataset.title = title;
|
||||||
|
div.dataset.image = image;
|
||||||
|
|
||||||
|
div.innerHTML = `
|
||||||
|
<img src="${image}" class="search-poster" loading="lazy">
|
||||||
|
<div class="search-info">
|
||||||
|
<div class="search-title">${escapeHtml(title)}</div>
|
||||||
|
<div class="search-meta" style="color:var(--brand-color)">${sourceName}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
elements.animeResults.appendChild(div);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeHtml(text) {
|
function escapeHtml(text) {
|
||||||
|
|||||||
@@ -204,7 +204,14 @@
|
|||||||
<div id="step-search">
|
<div id="step-search">
|
||||||
<h2 class="modal-title">Select Anime</h2>
|
<h2 class="modal-title">Select Anime</h2>
|
||||||
<div class="search-bar">
|
<div class="search-bar">
|
||||||
<input type="text" id="anime-search-input" placeholder="Search anime..." />
|
<div class="quick-select-wrapper" style="min-width: 130px; background: rgba(255,255,255,0.05);">
|
||||||
|
<select id="search-source-select" class="quick-select">
|
||||||
|
<option value="anilist">AniList</option>
|
||||||
|
</select>
|
||||||
|
<div class="select-arrow">▼</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="text" id="anime-search-input" placeholder="Search anime..." autocomplete="off"/>
|
||||||
<button id="anime-search-btn">Search</button>
|
<button id="anime-search-btn">Search</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="anime-results" class="anime-results"></div>
|
<div id="anime-results" class="anime-results"></div>
|
||||||
|
|||||||
Reference in New Issue
Block a user