enhanced book backend
This commit is contained in:
@@ -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" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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");
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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}`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user