Add e-book sidecar integration and improve request handling

Introduces optional e-book sidecar downloads from Anna's Archive, including admin UI, settings API, FlareSolverr integration, and documentation. Enhances request creation logic to prevent duplicate downloads by checking for 'downloaded' and 'available' statuses, updates UI to reflect processing state, and adds SABnzbd support to download and cleanup flows. Also updates ranking algorithm documentation and improves cache invalidation for recent requests.
This commit is contained in:
kikootwo
2026-01-07 17:19:42 -05:00
parent 24ea53bd2f
commit 95c25ff73a
26 changed files with 1968 additions and 116 deletions
+51
View File
@@ -9,6 +9,7 @@ import axios from 'axios';
import { createJobLogger, JobLogger } from './job-logger';
import { tagMultipleFiles, checkFfmpegAvailable } from './metadata-tagger';
import { prisma } from '../db';
import { downloadEbook } from '../services/ebook-scraper';
export interface AudiobookMetadata {
title: string;
@@ -261,6 +262,56 @@ export class FileOrganizer {
}
}
// E-book sidecar: Download accompanying e-book if enabled
try {
const ebookConfig = await prisma.configuration.findUnique({
where: { key: 'ebook_sidecar_enabled' },
});
const ebookEnabled = ebookConfig?.value === 'true';
if (ebookEnabled) {
await logger?.info(`E-book sidecar enabled, searching for e-book...`);
// Get configuration
const [formatConfig, baseUrlConfig, flaresolverrConfig] = await Promise.all([
prisma.configuration.findUnique({ where: { key: 'ebook_sidecar_preferred_format' } }),
prisma.configuration.findUnique({ where: { key: 'ebook_sidecar_base_url' } }),
prisma.configuration.findUnique({ where: { key: 'ebook_sidecar_flaresolverr_url' } }),
]);
const preferredFormat = formatConfig?.value || 'epub';
const baseUrl = baseUrlConfig?.value || 'https://annas-archive.li';
const flaresolverrUrl = flaresolverrConfig?.value || undefined;
// Download e-book (will try ASIN first, then fall back to title+author)
const ebookResult = await downloadEbook(
audiobook.asin || '', // ASIN (optional - will fallback to title+author if empty)
audiobook.title,
audiobook.author,
targetPath, // Same directory as audiobook
preferredFormat,
baseUrl,
logger ?? undefined,
flaresolverrUrl
);
if (ebookResult.success && ebookResult.filePath) {
await logger?.info(`E-book downloaded: ${path.basename(ebookResult.filePath)}`);
result.filesMovedCount++;
} else {
await logger?.warn(`E-book download failed: ${ebookResult.error}`);
result.errors.push(`E-book sidecar: ${ebookResult.error}`);
}
}
} catch (error) {
await logger?.warn(
`E-book sidecar error: ${error instanceof Error ? error.message : 'Unknown error'}`
);
result.errors.push('E-book sidecar failed');
// Don't throw - audiobook organization continues
}
result.targetPath = targetPath;
result.success = true;