added book entries from extensions
This commit is contained in:
@@ -1,12 +1,26 @@
|
||||
const booksService = require('./books.service');
|
||||
const {getExtension} = require("../shared/extensions");
|
||||
|
||||
async function getBook(req, reply) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const book = await booksService.getBookById(id);
|
||||
const source = req.query.ext || 'anilist';
|
||||
|
||||
let book;
|
||||
if (source === 'anilist') {
|
||||
book = await booksService.getBookById(id);
|
||||
} else {
|
||||
const extensionName = source;
|
||||
const ext = getExtension(extensionName);
|
||||
|
||||
const results = await booksService.searchBooksInExtension(ext, extensionName, id.replaceAll("-", " "));
|
||||
book = results[0] || null;
|
||||
}
|
||||
|
||||
return book;
|
||||
|
||||
} catch (err) {
|
||||
return { error: "Fetch error" };
|
||||
return { error: err.toString() };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +69,7 @@ async function searchBooks(req, reply) {
|
||||
async function getChapters(req, reply) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const chapters = await booksService.getChaptersForBook(id);
|
||||
return chapters;
|
||||
return await booksService.getChaptersForBook(id);
|
||||
} catch (err) {
|
||||
return { chapters: [] };
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const controller = require('./books.controller');
|
||||
|
||||
async function booksRoutes(fastify, options) {
|
||||
|
||||
fastify.get('/book/:id', controller.getBook);
|
||||
fastify.get('/books/trending', controller.getTrending);
|
||||
fastify.get('/books/popular', controller.getPopular);
|
||||
|
||||
@@ -107,10 +107,9 @@ async function searchBooksAniList(query) {
|
||||
return [];
|
||||
}
|
||||
|
||||
async function searchBooksExtensions(query) {
|
||||
const extensions = getAllExtensions();
|
||||
async function searchBooksInExtension(ext, name, query) {
|
||||
if (!ext) return [];
|
||||
|
||||
for (const [name, ext] of extensions) {
|
||||
if ((ext.type === 'book-board' || ext.type === 'manga-board') && ext.search) {
|
||||
try {
|
||||
console.log(`[${name}] Searching for book: ${query}`);
|
||||
@@ -126,6 +125,7 @@ async function searchBooksExtensions(query) {
|
||||
if (matches && matches.length > 0) {
|
||||
return matches.map(m => ({
|
||||
id: m.id,
|
||||
extensionName: name,
|
||||
title: { romaji: m.title, english: m.title },
|
||||
coverImage: { large: m.image || '' },
|
||||
averageScore: m.rating || m.score || null,
|
||||
@@ -138,24 +138,47 @@ async function searchBooksExtensions(query) {
|
||||
console.error(`Extension search failed for ${name}:`, e);
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
async function searchBooksExtensions(query) {
|
||||
const extensions = getAllExtensions();
|
||||
|
||||
for (const [name, ext] of extensions) {
|
||||
const results = await searchBooksInExtension(ext, name, query);
|
||||
if (results.length > 0) return results;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
async function getChaptersForBook(id) {
|
||||
let bookData = await queryOne("SELECT full_data FROM books WHERE id = ?", [id])
|
||||
let bookData = null;
|
||||
let searchTitle = null;
|
||||
|
||||
if (typeof id === "string" && isNaN(Number(id))) {
|
||||
searchTitle = id.replaceAll("-", " ");
|
||||
} else {
|
||||
bookData = await queryOne("SELECT full_data FROM books WHERE id = ?", [id])
|
||||
.then(row => row ? JSON.parse(row.full_data) : null)
|
||||
.catch(() => null);
|
||||
|
||||
if (!bookData) {
|
||||
try {
|
||||
const query = `query ($id: Int) { Media(id: $id, type: MANGA) { title { romaji english } startDate { year month day } } }`;
|
||||
const query = `query ($id: Int) {
|
||||
Media(id: $id, type: MANGA) {
|
||||
title { romaji english }
|
||||
startDate { year month day }
|
||||
}
|
||||
}`;
|
||||
|
||||
const res = await fetch('https://graphql.anilist.co', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ query, variables: { id: parseInt(id) } })
|
||||
});
|
||||
|
||||
const d = await res.json();
|
||||
if (d.data?.Media) bookData = d.data.Media;
|
||||
} catch (e) {}
|
||||
@@ -163,38 +186,42 @@ async function getChaptersForBook(id) {
|
||||
|
||||
if (!bookData) return { chapters: [] };
|
||||
|
||||
const titles = [bookData.title.english, bookData.title.romaji].filter(t => t);
|
||||
const searchTitle = titles[0];
|
||||
const titles = [bookData.title.english, bookData.title.romaji].filter(Boolean);
|
||||
searchTitle = titles[0];
|
||||
}
|
||||
|
||||
|
||||
const allChapters = [];
|
||||
const extensions = getAllExtensions();
|
||||
|
||||
const searchPromises = Array.from(extensions.entries())
|
||||
.filter(([name, ext]) => (ext.type === 'book-board' || ext.type === 'manga-board') && ext.search && ext.findChapters)
|
||||
.filter(([_, ext]) =>
|
||||
(ext.type === 'book-board' || ext.type === 'manga-board') &&
|
||||
ext.search && ext.findChapters
|
||||
)
|
||||
.map(async ([name, ext]) => {
|
||||
try {
|
||||
console.log(`[${name}] Searching chapters for: ${searchTitle}`);
|
||||
|
||||
const matches = await ext.search({
|
||||
query: searchTitle,
|
||||
media: {
|
||||
media: bookData ? {
|
||||
romajiTitle: bookData.title.romaji,
|
||||
englishTitle: bookData.title.english,
|
||||
startDate: bookData.startDate
|
||||
}
|
||||
} : {}
|
||||
});
|
||||
|
||||
if (matches && matches.length > 0) {
|
||||
if (matches?.length) {
|
||||
const best = matches[0];
|
||||
const chaps = await ext.findChapters(best.id);
|
||||
|
||||
if (chaps && chaps.length > 0) {
|
||||
if (chaps?.length) {
|
||||
console.log(`[${name}] Found ${chaps.length} chapters.`);
|
||||
chaps.forEach(ch => {
|
||||
const num = parseFloat(ch.number);
|
||||
allChapters.push({
|
||||
id: ch.id,
|
||||
number: num,
|
||||
number: parseFloat(ch.number),
|
||||
title: ch.title,
|
||||
date: ch.releaseDate,
|
||||
provider: name
|
||||
@@ -284,6 +311,7 @@ module.exports = {
|
||||
searchBooksLocal,
|
||||
searchBooksAniList,
|
||||
searchBooksExtensions,
|
||||
searchBooksInExtension,
|
||||
getChaptersForBook,
|
||||
getChapterContent
|
||||
};
|
||||
@@ -3,12 +3,28 @@ let allChapters = [];
|
||||
let filteredChapters = [];
|
||||
let currentPage = 1;
|
||||
const itemsPerPage = 12;
|
||||
let extensionName = null;
|
||||
|
||||
async function init() {
|
||||
try {
|
||||
const res = await fetch(`/api/book/${bookId}`);
|
||||
const path = window.location.pathname;
|
||||
const parts = path.split("/").filter(Boolean);
|
||||
let bookId;
|
||||
|
||||
if (parts.length === 3) {
|
||||
extensionName = parts[1];
|
||||
bookId = parts[2];
|
||||
} else {
|
||||
bookId = parts[1];
|
||||
}
|
||||
|
||||
const fetchUrl = extensionName
|
||||
? `/api/book/${bookId.slice(0,40)}?ext=${extensionName}`
|
||||
: `/api/book/${bookId}`;
|
||||
|
||||
const res = await fetch(fetchUrl);
|
||||
const data = await res.json();
|
||||
console.log(data)
|
||||
console.log(data);
|
||||
|
||||
if (data.error) {
|
||||
const titleEl = document.getElementById('title');
|
||||
@@ -22,6 +38,14 @@ async function init() {
|
||||
const titleEl = document.getElementById('title');
|
||||
if (titleEl) titleEl.innerText = title;
|
||||
|
||||
const extensionPill = document.getElementById('extension-pill');
|
||||
if (extensionName && extensionPill) {
|
||||
extensionPill.textContent = `${extensionName.charAt(0).toUpperCase() + extensionName.slice(1).toLowerCase()}`;
|
||||
extensionPill.style.display = 'inline-flex';
|
||||
} else if (extensionPill) {
|
||||
extensionPill.style.display = 'none';
|
||||
}
|
||||
|
||||
const descEl = document.getElementById('description');
|
||||
if (descEl) descEl.innerHTML = data.description || "No description available.";
|
||||
|
||||
@@ -76,7 +100,12 @@ async function loadChapters() {
|
||||
tbody.innerHTML = '<tr><td colspan="4" style="text-align:center; padding: 2rem;">Searching extensions for chapters...</td></tr>';
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/book/${bookId}/chapters`);
|
||||
const fetchUrl = extensionName
|
||||
? `/api/book/${bookId.slice(0, 40)}/chapters`
|
||||
: `/api/book/${bookId}/chapters`;
|
||||
|
||||
console.log(fetchUrl)
|
||||
const res = await fetch(fetchUrl);
|
||||
const data = await res.json();
|
||||
|
||||
allChapters = data.chapters || [];
|
||||
@@ -202,7 +231,7 @@ function updatePagination() {
|
||||
function openReader(bookId, chapterId, provider) {
|
||||
const c = encodeURIComponent(chapterId);
|
||||
const p = encodeURIComponent(provider);
|
||||
window.location.href = `/read/${bookId}/${c}/${p}`;
|
||||
window.location.href = `/read/${p}/${c}/${bookId}`;
|
||||
}
|
||||
|
||||
init();
|
||||
@@ -46,6 +46,16 @@ async function fetchBookSearch(query) {
|
||||
}
|
||||
}
|
||||
|
||||
function createSlug(text) {
|
||||
if (!text) return '';
|
||||
return text
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[^a-z0-9\s-]/g, '')
|
||||
.replace(/[\s-]+/g, '-');
|
||||
}
|
||||
|
||||
function renderSearchResults(results) {
|
||||
searchResults.innerHTML = '';
|
||||
|
||||
@@ -58,10 +68,23 @@ function renderSearchResults(results) {
|
||||
const rating = book.averageScore ? `${book.averageScore}%` : 'N/A';
|
||||
const year = book.seasonYear || (book.startDate ? book.startDate.year : '') || '????';
|
||||
const format = book.format || 'MANGA';
|
||||
let href;
|
||||
|
||||
if (book.isExtensionResult) {
|
||||
const titleSlug = createSlug(title);
|
||||
href = `/book/${book.extensionName}/${titleSlug}`;
|
||||
} else {
|
||||
href = `/book/${book.id}`;
|
||||
}
|
||||
|
||||
const extName = book.extensionName.charAt(0).toUpperCase() + book.extensionName.slice(1);
|
||||
const extPill = book.isExtensionResult
|
||||
? `<span>${extName}</span>`
|
||||
: '';
|
||||
|
||||
const item = document.createElement('a');
|
||||
item.className = 'search-item';
|
||||
item.href = `/book/${book.id}`;
|
||||
item.href = href;
|
||||
|
||||
item.innerHTML = `
|
||||
<img src="${img}" class="search-poster" alt="${title}">
|
||||
@@ -71,6 +94,7 @@ function renderSearchResults(results) {
|
||||
<span class="rating-pill">${rating}</span>
|
||||
<span>• ${year}</span>
|
||||
<span>• ${format}</span>
|
||||
${extPill}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -35,9 +35,9 @@ let observer = null;
|
||||
|
||||
const parts = window.location.pathname.split('/');
|
||||
|
||||
const bookId = parts[2];
|
||||
const bookId = parts[4];
|
||||
let chapter = parts[3];
|
||||
let provider = parts[4];
|
||||
let provider = parts[2];
|
||||
|
||||
function loadConfig() {
|
||||
try {
|
||||
@@ -125,7 +125,7 @@ async function loadChapter() {
|
||||
`;
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/book/${bookId}/${chapter}/${provider}`);
|
||||
const res = await fetch(`/api/book/${bookId.slice(0,40)}/${chapter}/${provider}`);
|
||||
const data = await res.json();
|
||||
|
||||
if (data.title) {
|
||||
@@ -467,15 +467,22 @@ nextBtn.addEventListener('click', () => {
|
||||
|
||||
function updateURL(newChapter) {
|
||||
chapter = newChapter;
|
||||
const newUrl = `/reader/${bookId}/${chapter}/${provider}`;
|
||||
const newUrl = `/reader/${provider}/${chapter}/${bookId}`;
|
||||
window.history.pushState({}, '', newUrl);
|
||||
}
|
||||
|
||||
document.getElementById('back-btn').addEventListener('click', () => {
|
||||
const parts = window.location.pathname.split('/');
|
||||
const provider = parts[2];
|
||||
const mangaId = parts[4];
|
||||
|
||||
const mangaId = parts[2];
|
||||
const isInt = Number.isInteger(Number(mangaId));
|
||||
|
||||
if (isInt) {
|
||||
window.location.href = `/book/${mangaId}`;
|
||||
} else {
|
||||
window.location.href = `/book/${provider}/${mangaId}`;
|
||||
}
|
||||
});
|
||||
|
||||
settingsBtn.addEventListener('click', () => {
|
||||
|
||||
@@ -28,7 +28,12 @@ async function viewsRoutes(fastify, options) {
|
||||
reply.type('text/html').send(stream);
|
||||
});
|
||||
|
||||
fastify.get('/read/:id/:chapter/:provider', (req, reply) => {
|
||||
fastify.get('/book/:extension/*', (req, reply) => {
|
||||
const stream = fs.createReadStream(path.join(__dirname, '..', '..', 'views', 'book.html'));
|
||||
reply.type('text/html').send(stream);
|
||||
});
|
||||
|
||||
fastify.get('/read/:provider/:chapter/*', (req, reply) => {
|
||||
const stream = fs.createReadStream(path.join(__dirname, '..', '..', 'views', 'read.html'));
|
||||
reply.type('text/html').send(stream);
|
||||
});
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
<h1 class="book-title" id="title">Loading...</h1>
|
||||
|
||||
<div class="meta-row">
|
||||
<div class="pill extension-pill" id="extension-pill" style="display: none; background: #8b5cf6;"></div>
|
||||
<div class="pill score" id="score">--% Score</div>
|
||||
<div class="pill" id="genres">Action</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user