Add multi-source ebook support and per-indexer categories

Introduces granular toggles for Anna's Archive and Indexer Search as ebook sources, updates settings UI to a three-section layout, and documents the new configuration. Adds per-indexer category configuration with separate tabs for audiobooks and ebooks, updates API routes and types for new settings, and ensures legacy config migration. Indexer grouping and file organization logic now support the new category structure and ebook source toggles.
This commit is contained in:
kikootwo
2026-01-30 22:12:24 -05:00
parent 590f089733
commit 5a0cce7985
19 changed files with 563 additions and 212 deletions
+47 -10
View File
@@ -4,13 +4,18 @@
*
* Groups indexers by their category configuration to minimize API calls.
* Indexers with identical categories are grouped together for a single search.
* Supports separate audiobook and ebook category configurations per indexer.
*/
export type CategoryType = 'audiobook' | 'ebook';
export interface IndexerConfig {
id: number;
name: string;
priority?: number;
categories?: number[];
audiobookCategories?: number[]; // Categories for audiobook searches
ebookCategories?: number[]; // Categories for ebook searches
categories?: number[]; // Legacy field for backwards compatibility
[key: string]: any; // Allow other properties
}
@@ -20,38 +25,70 @@ export interface IndexerGroup {
indexers: IndexerConfig[];
}
/**
* Gets the appropriate categories from an indexer based on the category type.
*
* @param indexer - The indexer configuration
* @param type - The category type ('audiobook' or 'ebook')
* @returns Array of category IDs
*/
export function getCategoriesForType(indexer: IndexerConfig, type: CategoryType): number[] {
if (type === 'ebook') {
return indexer.ebookCategories && indexer.ebookCategories.length > 0
? indexer.ebookCategories
: [7020]; // Default ebook category
}
// Audiobook - check new field first, then legacy field
if (indexer.audiobookCategories && indexer.audiobookCategories.length > 0) {
return indexer.audiobookCategories;
}
if (indexer.categories && indexer.categories.length > 0) {
return indexer.categories; // Legacy fallback
}
return [3030]; // Default audiobook category
}
/**
* Groups indexers by their category configuration.
* Indexers with identical category arrays are grouped together.
*
* @param indexers - Array of indexer configurations
* @param type - The category type to group by ('audiobook' or 'ebook')
* @returns Array of groups, each containing indexers with matching categories
*
* @example
* const indexers = [
* { id: 1, categories: [3030] },
* { id: 2, categories: [3030] },
* { id: 3, categories: [3030, 3010] },
* { id: 1, audiobookCategories: [3030], ebookCategories: [7020] },
* { id: 2, audiobookCategories: [3030], ebookCategories: [7020] },
* { id: 3, audiobookCategories: [3030, 3010], ebookCategories: [7020] },
* ];
*
* const groups = groupIndexersByCategories(indexers);
* const audiobookGroups = groupIndexersByCategories(indexers, 'audiobook');
* // Result:
* // [
* // { categories: [3030], indexerIds: [1, 2], indexers: [...] },
* // { categories: [3030, 3010], indexerIds: [3], indexers: [...] }
* // ]
*
* const ebookGroups = groupIndexersByCategories(indexers, 'ebook');
* // Result:
* // [
* // { categories: [7020], indexerIds: [1, 2, 3], indexers: [...] }
* // ]
*/
export function groupIndexersByCategories(indexers: IndexerConfig[]): IndexerGroup[] {
export function groupIndexersByCategories(
indexers: IndexerConfig[],
type: CategoryType = 'audiobook'
): IndexerGroup[] {
// Map to track unique category combinations
// Key: sorted category IDs as string (e.g., "3030,3010")
// Value: array of indexers with those categories
const groupMap = new Map<string, IndexerConfig[]>();
for (const indexer of indexers) {
// Get categories, default to [3030] (audiobooks) if not specified
const categories = indexer.categories && indexer.categories.length > 0
? indexer.categories
: [3030];
// Get categories for the specified type
const categories = getCategoriesForType(indexer, type);
// Sort categories to ensure consistent grouping
// [3030, 3010] and [3010, 3030] should be the same group
+5 -1
View File
@@ -36,7 +36,11 @@ export const TORRENT_CATEGORIES: TorrentCategory[] = [
},
];
export const DEFAULT_CATEGORIES = [3030]; // Audio/Audiobook
export const DEFAULT_AUDIOBOOK_CATEGORIES = [3030]; // Audio/Audiobook
export const DEFAULT_EBOOK_CATEGORIES = [7020]; // Books/EBook
// Legacy alias for backwards compatibility
export const DEFAULT_CATEGORIES = DEFAULT_AUDIOBOOK_CATEGORIES;
/**
* Get all child IDs for a parent category