enhanced book backend

This commit is contained in:
2025-12-03 21:43:19 +01:00
parent 920ce19cc2
commit bf27d173e9
5 changed files with 53 additions and 37 deletions

View File

@@ -3,29 +3,27 @@ import * as booksService from './books.service';
import {getExtension} from '../../shared/extensions'; import {getExtension} from '../../shared/extensions';
import {BookRequest, ChapterRequest, SearchRequest} from '../types'; import {BookRequest, ChapterRequest, SearchRequest} from '../types';
export async function getBook(req: BookRequest, reply: FastifyReply) { export async function getBook(req: any, reply: FastifyReply) {
try { try {
const { id } = req.params; const { id } = req.params;
const source = req.query.ext || 'anilist'; const source = req.query.source;
let book; let book;
if (source === 'anilist') { if (source === 'anilist') {
book = await booksService.getBookById(id); book = await booksService.getBookById(id);
} else { } else {
const ext = getExtension(source); const ext = getExtension(source);
const result = await booksService.getBookInfoExtension(ext, id); const result = await booksService.getBookInfoExtension(ext, id);
book = result || null; book = result || null;
} }
return book; return book;
} catch (err) { } catch (err) {
const error = err as Error; return { error: (err as Error).message };
return { error: error.toString() };
} }
} }
export async function getTrending(req: FastifyRequest, reply: FastifyReply) { export async function getTrending(req: FastifyRequest, reply: FastifyReply) {
try { try {
const results = await booksService.getTrendingBooks(); const results = await booksService.getTrendingBooks();
@@ -88,30 +86,31 @@ export async function searchBooksInExtension(req: any, reply: FastifyReply) {
export async function getChapters(req: any, reply: FastifyReply) { export async function getChapters(req: any, reply: FastifyReply) {
try { try {
const { id } = req.params; const { id } = req.params;
const { ext } = req.query; const source = req.query.source || 'anilist';
return await booksService.getChaptersForBook(id, Boolean(ext));
} catch (err) { const isExternal = source !== 'anilist';
return await booksService.getChaptersForBook(id, isExternal);
} catch {
return { chapters: [] }; return { chapters: [] };
} }
} }
export async function getChapterContent(req: any, reply: FastifyReply) { export async function getChapterContent(req: any, reply: FastifyReply) {
try { try {
const { bookId, chapter, provider } = req.params; const { bookId, chapter, provider } = req.params;
const { ext } = req.query; const source = req.query.source || 'anilist';
const content = await booksService.getChapterContent( const content = await booksService.getChapterContent(
bookId, bookId,
chapter, chapter,
provider, provider,
ext source
); );
return reply.send(content); return reply.send(content);
} catch (err) { } catch (err) {
const error = err as Error; console.error("getChapterContent error:", (err as Error).message);
console.error("getChapterContent error:", error.message);
return reply.code(500).send({ error: "Error loading chapter" }); return reply.code(500).send({ error: "Error loading chapter" });
} }
} }

View File

@@ -255,7 +255,8 @@ async function searchChaptersInExtension(ext: Extension, name: string, searchTit
number: parseFloat(ch.number.toString()), number: parseFloat(ch.number.toString()),
title: ch.title, title: ch.title,
date: ch.releaseDate, date: ch.releaseDate,
provider: name provider: name,
index: ch.index
})); }));
await setCache(cacheKey, result, CACHE_TTL_MS); await setCache(cacheKey, result, CACHE_TTL_MS);
@@ -311,17 +312,15 @@ export async function getChaptersForBook(id: string, ext: Boolean): Promise<{ ch
}; };
} }
export async function getChapterContent(bookId: string, chapterIndex: string, providerName: string, name: string): Promise<ChapterContent> { export async function getChapterContent(bookId: string, chapterIndex: string, providerName: string, source: string): Promise<ChapterContent> {
const extensions = getAllExtensions(); const extensions = getAllExtensions();
const ext = extensions.get(providerName); const ext = extensions.get(providerName);
if (!ext) { if (!ext) {
throw new Error("Provider not found"); throw new Error("Provider not found");
} }
let exts = "anilist";
if (name) exts = "ext";
const contentCacheKey = `content:${providerName}:${exts}:${bookId}:${chapterIndex}`; const contentCacheKey = `content:${providerName}:${source}:${bookId}:${chapterIndex}`;
const cachedContent = await getCache(contentCacheKey); const cachedContent = await getCache(contentCacheKey);
if (cachedContent) { if (cachedContent) {
@@ -340,7 +339,8 @@ export async function getChapterContent(bookId: string, chapterIndex: string, pr
} }
} }
const chapterList = await getChaptersForBook(bookId, Boolean(name)); const isExternal = source !== 'anilist';
const chapterList = await getChaptersForBook(bookId, isExternal);
if (!chapterList?.chapters || chapterList.chapters.length === 0) { if (!chapterList?.chapters || chapterList.chapters.length === 0) {
throw new Error("Chapters not found"); throw new Error("Chapters not found");

View File

@@ -78,6 +78,7 @@ export interface Episode {
} }
export interface Chapter { export interface Chapter {
index: number;
id: string; id: string;
number: string | number; number: string | number;
title?: string; title?: string;

View File

@@ -6,6 +6,14 @@ const itemsPerPage = 12;
let extensionName = null; let extensionName = null;
let bookSlug = null; let bookSlug = null;
function getBookUrl(id, source = 'anilist') {
return `/api/book/${id}?source=${source}`;
}
function getChaptersUrl(id, source = 'anilist') {
return `/api/book/${id}/chapters?source=${source}`;
}
async function init() { async function init() {
try { try {
const path = window.location.pathname; const path = window.location.pathname;
@@ -24,9 +32,10 @@ async function init() {
const idForFetch = currentBookId; const idForFetch = currentBookId;
const fetchUrl = extensionName const fetchUrl = getBookUrl(
? `/api/book/${idForFetch}?ext=${extensionName}` idForFetch,
: `/api/book/${idForFetch}`; extensionName || 'anilist'
);
const res = await fetch(fetchUrl); const res = await fetch(fetchUrl);
const data = await res.json(); const data = await res.json();
@@ -129,9 +138,10 @@ async function loadChapters(idForFetch) {
try { try {
const fetchUrl = extensionName const fetchUrl = getChaptersUrl(
? `/api/book/${idForFetch}/chapters?ext=${extensionName}` idForFetch,
: `/api/book/${idForFetch}/chapters`; extensionName || 'anilist'
);
const res = await fetch(fetchUrl); const res = await fetch(fetchUrl);
const data = await res.json(); const data = await res.json();
@@ -174,7 +184,7 @@ function populateProviderFilter() {
if (providers.length > 0) { if (providers.length > 0) {
select.style.display = 'inline-block'; select.style.display = 'inline-block';
select.innerHTML = ''; select.innerHTML = '<option value="all">All Providers</option>';
providers.forEach(prov => { providers.forEach(prov => {
const opt = document.createElement('option'); const opt = document.createElement('option');
@@ -236,7 +246,7 @@ function renderTable(idForFetch) {
<td>${ch.title || 'Chapter ' + ch.number}</td> <td>${ch.title || 'Chapter ' + ch.number}</td>
<td><span class="pill" style="font-size:0.75rem;">${ch.provider}</span></td> <td><span class="pill" style="font-size:0.75rem;">${ch.provider}</span></td>
<td> <td>
<button class="read-btn-small" onclick="openReader('${bookId}', '${realIndex}', '${ch.provider}')"> <button class="read-btn-small" onclick="openReader('${bookId}', '${ch.index}', '${ch.provider}')">
Read Read
</button> </button>
</td> </td>
@@ -276,8 +286,8 @@ function updatePagination() {
function openReader(bookId, chapterId, provider) { function openReader(bookId, chapterId, provider) {
const c = encodeURIComponent(chapterId); const c = encodeURIComponent(chapterId);
const p = encodeURIComponent(provider); const p = encodeURIComponent(provider);
let extension = ""; let extension = "?source=anilist";
if (extensionName) extension = "?" + extensionName; if (extensionName) extension = "?source=" + extensionName;
window.location.href = `/read/${p}/${c}/${bookId}${extension}`; window.location.href = `/read/${p}/${c}/${bookId}${extension}`;
} }

View File

@@ -126,10 +126,15 @@ async function loadChapter() {
</div> </div>
`; `;
const urlParams = new URLSearchParams(window.location.search);
let source = urlParams.get('source');
if (!source) {
source = 'anilist';
}
const newEndpoint = `/api/book/${bookId}/${chapter}/${provider}?source=${source}`;
try { try {
let ext = "" const res = await fetch(newEndpoint);
if(hasQuery) ext = "?ext=yes"
const res = await fetch(`/api/book/${bookId}/${chapter}/${provider}${ext}`);
const data = await res.json(); const data = await res.json();
if (data.title) { if (data.title) {
@@ -480,12 +485,13 @@ document.getElementById('back-btn').addEventListener('click', () => {
const provider = parts[2]; const provider = parts[2];
const mangaId = parts[4]; const mangaId = parts[4];
const isInt = Number.isInteger(Number(mangaId)); const urlParams = new URLSearchParams(window.location.search);
let source = urlParams.get('source');
if (isInt) { if (!source) {
window.location.href = `/book/${mangaId}`; window.location.href = `/book/${mangaId}`;
} else { } else {
window.location.href = `/book/${provider}/${mangaId}`; window.location.href = `/book/${source}/${mangaId}`;
} }
}); });