legado初步支持
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { opdsClient } from '@/lib/opds.client';
|
||||
import { bookProvider } from '@/lib/book-provider';
|
||||
|
||||
import { getAuthorizedBooksUsername } from '../_utils';
|
||||
|
||||
@@ -17,7 +17,7 @@ export async function GET(request: NextRequest) {
|
||||
if (!sourceId) {
|
||||
return NextResponse.json({ error: '缺少 sourceId' }, { status: 400 });
|
||||
}
|
||||
const result = await opdsClient.getCatalog(sourceId, href);
|
||||
const result = await bookProvider.getCatalog(sourceId, href);
|
||||
return NextResponse.json(result);
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: (error as Error).message }, { status: 500 });
|
||||
|
||||
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { BookAcquisitionLink } from '@/lib/book.types';
|
||||
import { db } from '@/lib/db';
|
||||
import { opdsClient } from '@/lib/opds.client';
|
||||
import { bookProvider } from '@/lib/book-provider';
|
||||
|
||||
import { getAuthorizedBooksUsername } from '../_utils';
|
||||
|
||||
@@ -49,7 +49,7 @@ async function resolveDetail(username: string, payload: DetailPayload) {
|
||||
}]
|
||||
: undefined;
|
||||
|
||||
const detail = await opdsClient.getBookDetail(sourceId, href, {
|
||||
const detail = await bookProvider.getBookDetail(sourceId, href, {
|
||||
id: bookId,
|
||||
title: payload.title || shelfItem?.title || readRecord?.title || undefined,
|
||||
author: payload.author || shelfItem?.author || readRecord?.author || undefined,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { db } from '@/lib/db';
|
||||
import { opdsClient } from '@/lib/opds.client';
|
||||
import { bookProvider } from '@/lib/book-provider';
|
||||
|
||||
import { getAuthorizedBooksUsername } from '../_utils';
|
||||
|
||||
@@ -11,12 +11,13 @@ type FilePayload = {
|
||||
sourceId?: string;
|
||||
bookId?: string;
|
||||
href?: string;
|
||||
format?: 'epub' | 'pdf' | null;
|
||||
format?: 'epub' | 'pdf' | 'chapters' | null;
|
||||
};
|
||||
|
||||
async function resolveFileHref(username: string, payload: FilePayload): Promise<{ sourceId: string; href: string }> {
|
||||
const sourceId = payload.sourceId?.trim();
|
||||
if (!sourceId) throw new Error('缺少 sourceId');
|
||||
if (payload.format === 'chapters') throw new Error('章节型书源不提供文件下载');
|
||||
|
||||
if (payload.href?.trim()) {
|
||||
return { sourceId, href: payload.href.trim() };
|
||||
@@ -35,9 +36,10 @@ async function resolveFileHref(username: string, payload: FilePayload): Promise<
|
||||
const detailHref = shelfItem?.detailHref || readRecord?.detailHref;
|
||||
if (!detailHref) throw new Error('找不到可下载文件');
|
||||
|
||||
const preferred = await opdsClient.getPreferredAcquisition(sourceId, detailHref);
|
||||
const preferred = await bookProvider.getPreferredAcquisition(sourceId, detailHref);
|
||||
if (preferred.format === 'chapters') throw new Error('章节型书源不提供文件下载');
|
||||
if (payload.format && preferred.format !== payload.format) {
|
||||
const detail = await opdsClient.getBookDetail(sourceId, detailHref);
|
||||
const detail = await bookProvider.getBookDetail(sourceId, detailHref);
|
||||
const matched = detail.acquisitionLinks.find((item) => (payload.format === 'pdf' ? item.type.toLowerCase().includes('pdf') : item.type.toLowerCase().includes('epub')));
|
||||
if (!matched?.href) throw new Error('找不到对应格式文件');
|
||||
return { sourceId, href: matched.href };
|
||||
@@ -47,7 +49,7 @@ async function resolveFileHref(username: string, payload: FilePayload): Promise<
|
||||
}
|
||||
|
||||
async function proxyFile(request: NextRequest, sourceId: string, href: string) {
|
||||
const source = await opdsClient.getSourceById(sourceId);
|
||||
const source = await bookProvider.getSourceById(sourceId);
|
||||
const headers = new Headers();
|
||||
if (source.authMode === 'basic' && source.username) {
|
||||
headers.set('Authorization', `Basic ${Buffer.from(`${source.username}:${source.password || ''}`).toString('base64')}`);
|
||||
@@ -98,7 +100,7 @@ export async function GET(request: NextRequest) {
|
||||
sourceId: searchParams.get('sourceId') || undefined,
|
||||
bookId: searchParams.get('bookId') || undefined,
|
||||
href: searchParams.get('href') || undefined,
|
||||
format: (searchParams.get('format')?.trim() as 'epub' | 'pdf' | null) || null,
|
||||
format: (searchParams.get('format')?.trim() as 'epub' | 'pdf' | 'chapters' | null) || null,
|
||||
});
|
||||
return await proxyFile(request, resolved.sourceId, resolved.href);
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { legadoClient } from '@/lib/legado.client';
|
||||
|
||||
import { getAuthorizedBooksUsername } from '../../_utils';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const username = await getAuthorizedBooksUsername(request);
|
||||
if (username instanceof NextResponse) return username;
|
||||
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const sourceId = searchParams.get('sourceId')?.trim();
|
||||
const href = searchParams.get('href')?.trim();
|
||||
const tocHref = searchParams.get('tocHref')?.trim() || undefined;
|
||||
if (!sourceId || !href) return NextResponse.json({ error: '缺少 sourceId 或 href' }, { status: 400 });
|
||||
const chapter = await legadoClient.getChapterContent(sourceId, href, tocHref);
|
||||
return NextResponse.json(chapter);
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: (error as Error).message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { legadoClient } from '@/lib/legado.client';
|
||||
|
||||
import { getAuthorizedBooksUsername } from '../../_utils';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const username = await getAuthorizedBooksUsername(request);
|
||||
if (username instanceof NextResponse) return username;
|
||||
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const sourceId = searchParams.get('sourceId')?.trim();
|
||||
const bookId = searchParams.get('bookId')?.trim();
|
||||
const href = searchParams.get('href')?.trim() || '';
|
||||
|
||||
if (!sourceId) return NextResponse.json({ error: '缺少 sourceId' }, { status: 400 });
|
||||
|
||||
const chapters = bookId
|
||||
? await legadoClient.getChaptersByBookId(sourceId, bookId)
|
||||
: href
|
||||
? await legadoClient.getChapters(sourceId, href)
|
||||
: null;
|
||||
if (!chapters) return NextResponse.json({ error: '缺少 bookId 或 href,无法定位章节目录' }, { status: 400 });
|
||||
return NextResponse.json({ chapters }, { headers: { 'Cache-Control': 'no-store' } });
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: (error as Error).message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { BookAcquisitionLink } from '@/lib/book.types';
|
||||
import { db } from '@/lib/db';
|
||||
import { opdsClient } from '@/lib/opds.client';
|
||||
import { bookProvider } from '@/lib/book-provider';
|
||||
|
||||
import { getAuthorizedBooksUsername } from '../../_utils';
|
||||
|
||||
@@ -13,7 +13,7 @@ type ManifestPayload = {
|
||||
bookId?: string;
|
||||
href?: string;
|
||||
acquisitionHref?: string;
|
||||
format?: 'epub' | 'pdf' | null;
|
||||
format?: 'epub' | 'pdf' | 'chapters' | null;
|
||||
title?: string;
|
||||
author?: string;
|
||||
cover?: string;
|
||||
@@ -45,12 +45,12 @@ async function resolveManifest(username: string, payload: ManifestPayload) {
|
||||
const fallbackAcquisitionLinks: BookAcquisitionLink[] = resolvedAcquisitionHref
|
||||
? [{
|
||||
rel: 'http://opds-spec.org/acquisition',
|
||||
type: resolvedFormat === 'pdf' ? 'application/pdf' : 'application/epub+zip',
|
||||
type: resolvedFormat === 'chapters' ? 'application/x-legado-chapters+json' : resolvedFormat === 'pdf' ? 'application/pdf' : 'application/epub+zip',
|
||||
href: resolvedAcquisitionHref,
|
||||
}]
|
||||
: [];
|
||||
|
||||
const detail = await opdsClient.getBookDetail(sourceId, resolvedHref || '', {
|
||||
const detail = await bookProvider.getBookDetail(sourceId, resolvedHref || '', {
|
||||
id: bookId || resolvedAcquisitionHref || undefined,
|
||||
title: payload.title || existingRecord?.title || shelfItem?.title || undefined,
|
||||
author: payload.author || existingRecord?.author || shelfItem?.author || undefined,
|
||||
@@ -60,9 +60,9 @@ async function resolveManifest(username: string, payload: ManifestPayload) {
|
||||
acquisitionLinks: fallbackAcquisitionLinks,
|
||||
});
|
||||
const preferred = resolvedHref
|
||||
? await opdsClient.getPreferredAcquisition(sourceId, resolvedHref)
|
||||
? await bookProvider.getPreferredAcquisition(sourceId, resolvedHref)
|
||||
: {
|
||||
format: resolvedFormat === 'pdf' ? 'pdf' : 'epub',
|
||||
format: resolvedFormat === 'chapters' ? 'chapters' : resolvedFormat === 'pdf' ? 'pdf' : 'epub',
|
||||
href: resolvedAcquisitionHref || '',
|
||||
};
|
||||
const lastRecord = await db.getBookReadRecord(username, sourceId, detail.id);
|
||||
@@ -70,7 +70,8 @@ async function resolveManifest(username: string, payload: ManifestPayload) {
|
||||
return NextResponse.json({
|
||||
book: detail,
|
||||
format: preferred.format,
|
||||
fileUrl: `/api/books/file?sourceId=${encodeURIComponent(sourceId)}&bookId=${encodeURIComponent(detail.id)}&format=${encodeURIComponent(preferred.format)}`,
|
||||
fileUrl: preferred.format === 'chapters' ? undefined : `/api/books/file?sourceId=${encodeURIComponent(sourceId)}&bookId=${encodeURIComponent(detail.id)}&format=${encodeURIComponent(preferred.format)}`,
|
||||
chaptersUrl: preferred.format === 'chapters' ? `/api/books/read/chapters?sourceId=${encodeURIComponent(sourceId)}&bookId=${encodeURIComponent(detail.id)}` : undefined,
|
||||
acquisitionHref: preferred.href,
|
||||
cacheKey: `${sourceId}::${detail.id}::${preferred.format}`,
|
||||
coverUrl: detail.cover,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { opdsClient } from '@/lib/opds.client';
|
||||
import { bookProvider } from '@/lib/book-provider';
|
||||
|
||||
import { getAuthorizedBooksUsername } from '../_utils';
|
||||
|
||||
@@ -17,7 +17,7 @@ export async function GET(request: NextRequest) {
|
||||
if (!q) {
|
||||
return NextResponse.json({ results: [], failedSources: [] });
|
||||
}
|
||||
const result = await opdsClient.searchBooks(q, sourceId);
|
||||
const result = await bookProvider.searchBooks(q, sourceId);
|
||||
return NextResponse.json(result);
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: (error as Error).message }, { status: 500 });
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { opdsClient } from '@/lib/opds.client';
|
||||
import { bookProvider } from '@/lib/book-provider';
|
||||
|
||||
import { getAuthorizedBooksUsername } from '../../_utils';
|
||||
|
||||
@@ -37,7 +37,7 @@ export async function GET(request: NextRequest) {
|
||||
};
|
||||
|
||||
try {
|
||||
const sources = await opdsClient.getSearchSources(sourceId);
|
||||
const sources = await bookProvider.getSearchSources(sourceId);
|
||||
let completedSources = 0;
|
||||
let totalResults = 0;
|
||||
const failedSources: Array<{ sourceId: string; sourceName: string; error: string }> = [];
|
||||
@@ -47,7 +47,7 @@ export async function GET(request: NextRequest) {
|
||||
await Promise.all(
|
||||
sources.map(async (source) => {
|
||||
try {
|
||||
const result = await opdsClient.searchBooksSource(q, source);
|
||||
const result = await bookProvider.searchBooksSource(q, source);
|
||||
completedSources += 1;
|
||||
totalResults += result.results.length;
|
||||
send({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { opdsClient } from '@/lib/opds.client';
|
||||
import { bookProvider } from '@/lib/book-provider';
|
||||
|
||||
import { getAuthorizedBooksUsername } from '../_utils';
|
||||
|
||||
@@ -11,7 +11,7 @@ export async function GET(request: NextRequest) {
|
||||
if (username instanceof NextResponse) return username;
|
||||
|
||||
try {
|
||||
const sources = await opdsClient.getSources();
|
||||
const sources = await bookProvider.getSources();
|
||||
return NextResponse.json({ sources });
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: (error as Error).message }, { status: 500 });
|
||||
|
||||
Reference in New Issue
Block a user