Fix: Prowlarr categorisation and search fallback (#381)

This commit is contained in:
Alex
2025-12-31 13:14:51 +00:00
committed by GitHub
parent 875b705ed3
commit c609c0b2bb
4 changed files with 48 additions and 53 deletions
+20 -10
View File
@@ -111,35 +111,44 @@ def stage_file(source_path: Path, task_id: str, copy: bool = False) -> Path:
return staged_path
def _get_supported_formats() -> List[str]:
"""Get current supported formats from config singleton."""
formats = config.get("SUPPORTED_FORMATS", ["epub", "mobi", "azw3", "fb2", "djvu", "cbz", "cbr"])
def _get_supported_formats(content_type: str = None) -> List[str]:
"""Get current supported formats from config singleton based on content type."""
if content_type and content_type.lower() == "audiobook":
formats = config.get("SUPPORTED_AUDIOBOOK_FORMATS", ["m4b", "mp3"])
else:
formats = config.get("SUPPORTED_FORMATS", ["epub", "mobi", "azw3", "fb2", "djvu", "cbz", "cbr"])
# Handle both list (from MultiSelectField) and comma-separated string (legacy/env)
if isinstance(formats, str):
return [fmt.strip().lower() for fmt in formats.split(",") if fmt.strip()]
return [fmt.lower() for fmt in formats]
def _find_book_files_in_directory(directory: Path) -> Tuple[List[Path], List[Path]]:
"""Find all book files in a directory matching SUPPORTED_FORMATS.
def _find_book_files_in_directory(directory: Path, content_type: str = None) -> Tuple[List[Path], List[Path]]:
"""Find all book files in a directory matching supported formats.
Args:
directory: Directory to search recursively
content_type: Content type to determine format list (e.g., "audiobook")
Returns:
Tuple of (matching book files, rejected files with unsupported extensions)
"""
book_files = []
rejected_files = []
supported_formats = _get_supported_formats()
supported_formats = _get_supported_formats(content_type)
supported_exts = {f".{fmt}" for fmt in supported_formats}
is_audiobook = content_type and content_type.lower() == "audiobook"
if is_audiobook:
trackable_exts = {'.m4b', '.mp3', '.m4a', '.flac', '.ogg', '.wma', '.aac', '.wav'}
else:
trackable_exts = {'.pdf', '.epub', '.mobi', '.azw', '.azw3', '.fb2', '.djvu', '.cbz', '.cbr', '.doc', '.docx', '.rtf', '.txt'}
for file_path in directory.rglob("*"):
if file_path.is_file():
if file_path.suffix.lower() in supported_exts:
book_files.append(file_path)
elif file_path.suffix.lower() in {'.pdf', '.epub', '.mobi', '.azw', '.azw3', '.fb2', '.djvu', '.cbz', '.cbr', '.doc', '.docx', '.rtf', '.txt'}:
# Track ebook-like files that were rejected due to format settings
elif file_path.suffix.lower() in trackable_exts:
rejected_files.append(file_path)
return book_files, rejected_files
@@ -164,7 +173,8 @@ def process_directory(
Tuple of (list of final paths, error message if failed)
"""
try:
book_files, rejected_files = _find_book_files_in_directory(directory)
content_type = task.content_type
book_files, rejected_files = _find_book_files_in_directory(directory, content_type)
# Find archives in directory (ZIP/RAR)
archive_files = [f for f in directory.rglob("*") if f.is_file() and is_archive(f)]
@@ -206,7 +216,7 @@ def process_directory(
# Files were found but didn't match supported formats
rejected_exts = sorted(set(f.suffix.lower() for f in rejected_files))
rejected_list = ", ".join(rejected_exts)
supported_formats = _get_supported_formats()
supported_formats = _get_supported_formats(content_type)
logger.warning(
f"Found {len(rejected_files)} file(s) but none match supported formats. "
f"Rejected formats: {rejected_list}. Supported: {', '.join(sorted(supported_formats))}"
@@ -117,16 +117,17 @@ class ProwlarrHandler(DownloadHandler):
# No existing download - add new
status_callback("resolving", f"Sending to {client.name}")
try:
release_name = prowlarr_result.get("title") or task.title or "Unknown"
download_id = client.add_download(
url=download_url,
name=task.title or "Unknown",
name=release_name,
)
except Exception as e:
logger.error(f"Failed to add to {client.name}: {e}")
status_callback("error", f"Failed to add to {client.name}: {e}")
return None
logger.info(f"Added to {client.name}: {download_id} for '{task.title}'")
logger.info(f"Added to {client.name}: {download_id} for '{release_name}'")
# Poll for progress
return self._poll_and_complete(
@@ -323,22 +323,11 @@ def prowlarr_config_settings():
default=[],
show_when={"field": "PROWLARR_ENABLED", "value": True},
),
MultiSelectField(
key="PROWLARR_SEARCH_CATEGORIES",
label="Search Categories",
description="Categories to include in search. Select multiple to broaden results. Leave empty to search all categories.",
options=[
{"value": "7000", "label": "Books (All)"},
{"value": "7010", "label": "Mags", "childOf": "7000"},
{"value": "7020", "label": "Ebook", "childOf": "7000"},
{"value": "7030", "label": "Comics", "childOf": "7000"},
{"value": "7040", "label": "Technical", "childOf": "7000"},
{"value": "7050", "label": "Other", "childOf": "7000"},
{"value": "7060", "label": "Foreign", "childOf": "7000"},
{"value": "3030", "label": "Audio/Audiobook"},
],
default=["7000"],
variant="dropdown",
CheckboxField(
key="PROWLARR_AUTO_EXPAND",
label="Auto-expand search on no results",
default=False,
description="Automatically retry search without category filtering if no results are found",
show_when={"field": "PROWLARR_ENABLED", "value": True},
),
]
@@ -199,7 +199,6 @@ class ProwlarrSource(ReleaseSource):
def __init__(self):
self.last_search_type: Optional[str] = None
self._category_filtered_indexers: List[int] = []
@classmethod
def get_column_config(cls) -> ReleaseColumnConfig:
@@ -348,48 +347,44 @@ class ProwlarrSource(ReleaseSource):
return []
# Get search categories based on content type
# Audiobooks always use 3030 (Audio/Audiobook), ebooks use configured categories
# Audiobooks use 3030 (Audio/Audiobook), ebooks use 7000 (Books)
if content_type == "audiobook":
search_categories = ["3030"] # Audio/Audiobook category
search_categories = [3030] # Audio/Audiobook category
else:
search_categories = config.get("PROWLARR_SEARCH_CATEGORIES", ["7000"])
# Handle both list and comma-separated string formats
if isinstance(search_categories, str):
search_categories = [c.strip() for c in search_categories.split(",") if c.strip()]
search_categories = [7000] # Books category
if expand_search:
if not self._category_filtered_indexers:
logger.debug("No category-filtered indexers to expand")
return []
indexers_to_search = self._category_filtered_indexers
# Expand search: search all indexers without category filtering
categories = None
self.last_search_type = "expanded"
else:
indexers_to_search = indexer_ids
# Use configured categories, or None if empty (all categories)
categories = [int(c) for c in search_categories] if search_categories else None
self._category_filtered_indexers = []
self.last_search_type = "expanded" if not search_categories else "categories"
categories = search_categories
self.last_search_type = "categories"
logger.debug(f"Searching Prowlarr: query='{query}', indexers={indexers_to_search}, categories={categories}")
logger.debug(f"Searching Prowlarr: query='{query}', indexers={indexer_ids}, categories={categories}")
all_results = []
try:
for indexer_id in indexers_to_search:
for indexer_id in indexer_ids:
try:
raw_results = client.search(query=query, indexer_ids=[indexer_id], categories=categories)
# Track indexers that returned no results with category filter
# so "Expand search" can retry them without the filter
if categories and not raw_results:
self._category_filtered_indexers.append(indexer_id)
if raw_results:
all_results.extend(raw_results)
except Exception as e:
logger.warning(f"Search failed for indexer {indexer_id}: {e}")
if not expand_search and not self._category_filtered_indexers:
# Auto-expand: if no results with categories and auto-expand enabled, retry without categories
auto_expand_enabled = config.get("PROWLARR_AUTO_EXPAND", False)
logger.debug(f"Auto-expand check: no_results={not all_results}, has_categories={bool(categories)}, auto_expand_enabled={auto_expand_enabled}")
if not all_results and categories and auto_expand_enabled:
logger.info("Prowlarr: no results with category filter, auto-expanding search")
for indexer_id in indexer_ids:
try:
raw_results = client.search(query=query, indexer_ids=[indexer_id], categories=None)
if raw_results:
all_results.extend(raw_results)
except Exception as e:
logger.warning(f"Expanded search failed for indexer {indexer_id}: {e}")
self.last_search_type = "expanded"
results = [_prowlarr_result_to_release(r) for r in all_results]