diff --git a/desktop/src/api/books/books.controller.ts b/desktop/src/api/books/books.controller.ts
index 5d07549..d8f7a8a 100644
--- a/desktop/src/api/books/books.controller.ts
+++ b/desktop/src/api/books/books.controller.ts
@@ -87,15 +87,16 @@ export async function getChapters(req: any, reply: FastifyReply) {
try {
const { id } = req.params;
const source = req.query.source || 'anilist';
+ const provider = req.query.provider;
const isExternal = source !== 'anilist';
- return await booksService.getChaptersForBook(id, isExternal);
- } catch {
+ return await booksService.getChaptersForBook(id, isExternal, provider);
+ } catch (err) {
+ console.error(err);
return { chapters: [] };
}
}
-
export async function getChapterContent(req: any, reply: FastifyReply) {
try {
const { bookId, chapter, provider } = req.params;
diff --git a/desktop/src/scripts/books/book.js b/desktop/src/scripts/books/book.js
index c649e84..22f2cb6 100644
--- a/desktop/src/scripts/books/book.js
+++ b/desktop/src/scripts/books/book.js
@@ -6,6 +6,8 @@ let bookSlug = null;
let allChapters = [];
let filteredChapters = [];
+let availableExtensions = [];
+
const chapterPagination = Object.create(PaginationManager);
chapterPagination.init(12, () => renderChapterTable());
@@ -16,7 +18,6 @@ document.addEventListener('DOMContentLoaded', () => {
async function init() {
try {
-
const urlData = URLUtils.parseEntityPath('book');
if (!urlData) {
showError("Book Not Found");
@@ -29,6 +30,7 @@ async function init() {
await loadBookMetadata();
+ await loadAvailableExtensions();
await loadChapters();
await setupAddToListButton();
@@ -39,11 +41,23 @@ async function init() {
}
}
+async function loadAvailableExtensions() {
+ try {
+ const res = await fetch('/api/extensions/book');
+ const data = await res.json();
+ availableExtensions = data.extensions || [];
+
+ setupProviderFilter();
+ } catch (err) {
+ console.error("Error fetching extensions:", err);
+ }
+}
+
async function loadBookMetadata() {
const source = extensionName || 'anilist';
const fetchUrl = `/api/book/${bookId}?source=${source}`;
- const res = await fetch(fetchUrl, { headers: AuthUtils.getSimpleAuthHeaders() });
+ const res = await fetch(fetchUrl);
const data = await res.json();
if (data.error || !data) {
@@ -154,17 +168,27 @@ function updateCustomAddButton() {
}
}
-async function loadChapters() {
+async function loadChapters(targetProvider = null) {
const tbody = document.getElementById('chapters-body');
if (!tbody) return;
- tbody.innerHTML = '
Searching extensions for chapters... ';
+ // Si no se pasa provider, intentamos pillar el del select o el primero disponible
+ if (!targetProvider) {
+ const select = document.getElementById('provider-filter');
+ targetProvider = select ? select.value : (availableExtensions[0] || 'all');
+ }
+
+ tbody.innerHTML = 'Searching extension for chapters... ';
try {
const source = extensionName || 'anilist';
- const fetchUrl = `/api/book/${bookId}/chapters?source=${source}`;
+ // AƱadimos el query param 'provider' para que el backend filtre
+ let fetchUrl = `/api/book/${bookId}/chapters?source=${source}`;
+ if (targetProvider !== 'all') {
+ fetchUrl += `&provider=${targetProvider}`;
+ }
- const res = await fetch(fetchUrl, { headers: AuthUtils.getSimpleAuthHeaders() });
+ const res = await fetch(fetchUrl);
const data = await res.json();
allChapters = data.chapters || [];
@@ -175,18 +199,17 @@ async function loadChapters() {
const totalEl = document.getElementById('total-chapters');
if (allChapters.length === 0) {
- tbody.innerHTML = 'No chapters found on loaded extensions. ';
+ tbody.innerHTML = 'No chapters found. ';
if (totalEl) totalEl.innerText = "0 Found";
return;
}
if (totalEl) totalEl.innerText = `${allChapters.length} Found`;
- setupProviderFilter();
-
setupReadButton();
chapterPagination.setTotalItems(filteredChapters.length);
+ chapterPagination.reset();
renderChapterTable();
} catch (err) {
@@ -211,44 +234,31 @@ function applyChapterFilter() {
function setupProviderFilter() {
const select = document.getElementById('provider-filter');
- if (!select) return;
-
- const providers = [...new Set(allChapters.map(ch => ch.provider))];
-
- if (providers.length === 0) return;
+ if (!select || availableExtensions.length === 0) return;
select.style.display = 'inline-block';
- select.innerHTML = 'All Providers ';
+ select.innerHTML = '';
- providers.forEach(prov => {
+ const allOpt = document.createElement('option');
+ allOpt.value = 'all';
+ allOpt.innerText = 'Load All (Slower)';
+ select.appendChild(allOpt);
+
+ availableExtensions.forEach(ext => {
const opt = document.createElement('option');
- opt.value = prov;
- opt.innerText = prov;
+ opt.value = ext;
+ opt.innerText = ext.charAt(0).toUpperCase() + ext.slice(1);
select.appendChild(opt);
});
- if (extensionName) {
- const extensionProvider = providers.find(
- p => p.toLowerCase() === extensionName.toLowerCase()
- );
-
- if (extensionProvider) {
- select.value = extensionProvider;
- filteredChapters = allChapters.filter(ch => ch.provider === extensionProvider);
- }
+ if (extensionName && availableExtensions.includes(extensionName)) {
+ select.value = extensionName;
+ } else if (availableExtensions.length > 0) {
+ select.value = availableExtensions[0];
}
- select.onchange = (e) => {
- const selected = e.target.value;
- if (selected === 'all') {
- filteredChapters = [...allChapters];
- } else {
- filteredChapters = allChapters.filter(ch => ch.provider === selected);
- }
-
- chapterPagination.reset();
- chapterPagination.setTotalItems(filteredChapters.length);
- renderChapterTable();
+ select.onchange = () => {
+ loadChapters(select.value);
};
}
diff --git a/docker/src/api/books/books.controller.ts b/docker/src/api/books/books.controller.ts
index 5d07549..d8f7a8a 100644
--- a/docker/src/api/books/books.controller.ts
+++ b/docker/src/api/books/books.controller.ts
@@ -87,15 +87,16 @@ export async function getChapters(req: any, reply: FastifyReply) {
try {
const { id } = req.params;
const source = req.query.source || 'anilist';
+ const provider = req.query.provider;
const isExternal = source !== 'anilist';
- return await booksService.getChaptersForBook(id, isExternal);
- } catch {
+ return await booksService.getChaptersForBook(id, isExternal, provider);
+ } catch (err) {
+ console.error(err);
return { chapters: [] };
}
}
-
export async function getChapterContent(req: any, reply: FastifyReply) {
try {
const { bookId, chapter, provider } = req.params;
diff --git a/docker/src/scripts/books/book.js b/docker/src/scripts/books/book.js
index c649e84..22f2cb6 100644
--- a/docker/src/scripts/books/book.js
+++ b/docker/src/scripts/books/book.js
@@ -6,6 +6,8 @@ let bookSlug = null;
let allChapters = [];
let filteredChapters = [];
+let availableExtensions = [];
+
const chapterPagination = Object.create(PaginationManager);
chapterPagination.init(12, () => renderChapterTable());
@@ -16,7 +18,6 @@ document.addEventListener('DOMContentLoaded', () => {
async function init() {
try {
-
const urlData = URLUtils.parseEntityPath('book');
if (!urlData) {
showError("Book Not Found");
@@ -29,6 +30,7 @@ async function init() {
await loadBookMetadata();
+ await loadAvailableExtensions();
await loadChapters();
await setupAddToListButton();
@@ -39,11 +41,23 @@ async function init() {
}
}
+async function loadAvailableExtensions() {
+ try {
+ const res = await fetch('/api/extensions/book');
+ const data = await res.json();
+ availableExtensions = data.extensions || [];
+
+ setupProviderFilter();
+ } catch (err) {
+ console.error("Error fetching extensions:", err);
+ }
+}
+
async function loadBookMetadata() {
const source = extensionName || 'anilist';
const fetchUrl = `/api/book/${bookId}?source=${source}`;
- const res = await fetch(fetchUrl, { headers: AuthUtils.getSimpleAuthHeaders() });
+ const res = await fetch(fetchUrl);
const data = await res.json();
if (data.error || !data) {
@@ -154,17 +168,27 @@ function updateCustomAddButton() {
}
}
-async function loadChapters() {
+async function loadChapters(targetProvider = null) {
const tbody = document.getElementById('chapters-body');
if (!tbody) return;
- tbody.innerHTML = 'Searching extensions for chapters... ';
+ // Si no se pasa provider, intentamos pillar el del select o el primero disponible
+ if (!targetProvider) {
+ const select = document.getElementById('provider-filter');
+ targetProvider = select ? select.value : (availableExtensions[0] || 'all');
+ }
+
+ tbody.innerHTML = 'Searching extension for chapters... ';
try {
const source = extensionName || 'anilist';
- const fetchUrl = `/api/book/${bookId}/chapters?source=${source}`;
+ // AƱadimos el query param 'provider' para que el backend filtre
+ let fetchUrl = `/api/book/${bookId}/chapters?source=${source}`;
+ if (targetProvider !== 'all') {
+ fetchUrl += `&provider=${targetProvider}`;
+ }
- const res = await fetch(fetchUrl, { headers: AuthUtils.getSimpleAuthHeaders() });
+ const res = await fetch(fetchUrl);
const data = await res.json();
allChapters = data.chapters || [];
@@ -175,18 +199,17 @@ async function loadChapters() {
const totalEl = document.getElementById('total-chapters');
if (allChapters.length === 0) {
- tbody.innerHTML = 'No chapters found on loaded extensions. ';
+ tbody.innerHTML = 'No chapters found. ';
if (totalEl) totalEl.innerText = "0 Found";
return;
}
if (totalEl) totalEl.innerText = `${allChapters.length} Found`;
- setupProviderFilter();
-
setupReadButton();
chapterPagination.setTotalItems(filteredChapters.length);
+ chapterPagination.reset();
renderChapterTable();
} catch (err) {
@@ -211,44 +234,31 @@ function applyChapterFilter() {
function setupProviderFilter() {
const select = document.getElementById('provider-filter');
- if (!select) return;
-
- const providers = [...new Set(allChapters.map(ch => ch.provider))];
-
- if (providers.length === 0) return;
+ if (!select || availableExtensions.length === 0) return;
select.style.display = 'inline-block';
- select.innerHTML = 'All Providers ';
+ select.innerHTML = '';
- providers.forEach(prov => {
+ const allOpt = document.createElement('option');
+ allOpt.value = 'all';
+ allOpt.innerText = 'Load All (Slower)';
+ select.appendChild(allOpt);
+
+ availableExtensions.forEach(ext => {
const opt = document.createElement('option');
- opt.value = prov;
- opt.innerText = prov;
+ opt.value = ext;
+ opt.innerText = ext.charAt(0).toUpperCase() + ext.slice(1);
select.appendChild(opt);
});
- if (extensionName) {
- const extensionProvider = providers.find(
- p => p.toLowerCase() === extensionName.toLowerCase()
- );
-
- if (extensionProvider) {
- select.value = extensionProvider;
- filteredChapters = allChapters.filter(ch => ch.provider === extensionProvider);
- }
+ if (extensionName && availableExtensions.includes(extensionName)) {
+ select.value = extensionName;
+ } else if (availableExtensions.length > 0) {
+ select.value = availableExtensions[0];
}
- select.onchange = (e) => {
- const selected = e.target.value;
- if (selected === 'all') {
- filteredChapters = [...allChapters];
- } else {
- filteredChapters = allChapters.filter(ch => ch.provider === selected);
- }
-
- chapterPagination.reset();
- chapterPagination.setTotalItems(filteredChapters.length);
- renderChapterTable();
+ select.onchange = () => {
+ loadChapters(select.value);
};
}