from __future__ import annotations from abc import ABC, abstractmethod from dataclasses import dataclass, field from pathlib import Path from typing import Any, Dict, List, Optional, Tuple @dataclass class SearchResult: """Unified search result format across all search providers.""" table: str # Provider name: "libgen", "soulseek", "bandcamp", "youtube", etc. title: str # Display title/filename path: str # Download target (URL, path, magnet, identifier) detail: str = "" # Additional description annotations: List[str] = field(default_factory=list) # Tags: ["120MB", "flac", "ready"] media_kind: str = "other" # Type: "book", "audio", "video", "game", "magnet" size_bytes: Optional[int] = None tags: set[str] = field(default_factory=set) # Searchable tags columns: List[Tuple[str, str]] = field(default_factory=list) # Display columns full_metadata: Dict[str, Any] = field(default_factory=dict) # Extra metadata def to_dict(self) -> Dict[str, Any]: """Convert to dictionary for pipeline processing.""" return { "table": self.table, "title": self.title, "path": self.path, "detail": self.detail, "annotations": self.annotations, "media_kind": self.media_kind, "size_bytes": self.size_bytes, "tags": list(self.tags), "columns": list(self.columns), "full_metadata": self.full_metadata, } class SearchProvider(ABC): """Base class for search providers.""" def __init__(self, config: Optional[Dict[str, Any]] = None): self.config = config or {} self.name = self.__class__.__name__.lower() @abstractmethod def search( self, query: str, limit: int = 50, filters: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> List[SearchResult]: """Search for items matching the query.""" def download(self, result: SearchResult, output_dir: Path) -> Optional[Path]: """Download an item from a search result.""" return None def validate(self) -> bool: """Check if provider is available and properly configured.""" return True class FileProvider(ABC): """Base class for file upload providers.""" def __init__(self, config: Optional[Dict[str, Any]] = None): self.config = config or {} self.name = self.__class__.__name__.lower() @abstractmethod def upload(self, file_path: str, **kwargs: Any) -> str: """Upload a file and return the URL.""" def validate(self) -> bool: """Check if provider is available/configured.""" return True