feat: Enhance search functionality in CalibreOPDSClient to support contextual search with start_href

This commit is contained in:
JB
2025-11-29 05:54:13 -08:00
parent 8a2bd7ec50
commit b0a3e1dbd9
2 changed files with 42 additions and 41 deletions
+36 -35
View File
@@ -198,10 +198,10 @@ class CalibreOPDSClient:
return self._parse_feed(response.text, base_url=target) return self._parse_feed(response.text, base_url=target)
def search(self, query: str) -> OPDSFeed: def search(self, query: str, start_href: Optional[str] = None) -> OPDSFeed:
cleaned = (query or "").strip() cleaned = (query or "").strip()
if not cleaned: if not cleaned:
return self.fetch_feed() return self.fetch_feed(start_href) if start_href else self.fetch_feed()
base_feed: Optional[OPDSFeed] = None base_feed: Optional[OPDSFeed] = None
try: try:
@@ -223,51 +223,42 @@ class CalibreOPDSClient:
last_error: Optional[Exception] = None last_error: Optional[Exception] = None
base_combined: Optional[OPDSFeed] = None base_combined: Optional[OPDSFeed] = None
# Try server-side search first (global search)
for path, params in candidates: for path, params in candidates:
try: try:
feed = self.fetch_feed(path, params=params) feed = self.fetch_feed(path, params=params)
except CalibreOPDSError as exc: except CalibreOPDSError as exc:
last_error = exc last_error = exc
continue continue
combined = self._merge_feed_entries(
self._collect_search_results(feed, cleaned), # If we got results from server search, use them
self._local_search(cleaned, seed_feed=feed), if feed.entries:
) return feed
if combined.entries:
return combined # Fallback to local search (crawling)
if base_feed is None: # If start_href is provided, we search from there (contextual search)
# Otherwise we search from root (which might be empty if root has no books)
seed_feed: Optional[OPDSFeed] = None
if start_href:
try: try:
base_feed = self.fetch_feed() seed_feed = self.fetch_feed(start_href)
except CalibreOPDSError: except CalibreOPDSError:
base_feed = None pass
if base_feed is not None and base_combined is None:
base_combined = self._merge_feed_entries( if not seed_feed and base_feed:
self._collect_search_results(base_feed, cleaned), seed_feed = base_feed
self._local_search(cleaned, seed_feed=base_feed),
) if not seed_feed:
if base_combined is not None and base_combined.entries:
return base_combined
if base_feed is None:
try: try:
base_feed = self.fetch_feed() seed_feed = self.fetch_feed()
except CalibreOPDSError as exc: except CalibreOPDSError as exc:
if last_error is not None: if last_error:
raise last_error raise last_error
raise exc raise exc
if base_feed is None:
if last_error is not None: return self._local_search(cleaned, seed_feed=seed_feed)
raise last_error
raise CalibreOPDSError("Calibre OPDS base feed is unavailable")
if base_combined is None:
base_combined = self._merge_feed_entries(
self._collect_search_results(base_feed, cleaned),
self._local_search(cleaned, seed_feed=base_feed),
)
if base_combined.entries:
return base_combined
if last_error is not None:
raise last_error
return base_combined
def _collect_search_results( def _collect_search_results(
self, self,
@@ -803,6 +794,16 @@ class CalibreOPDSClient:
absolute_href = urljoin(base_for_join, href) absolute_href = urljoin(base_for_join, href)
entry = OPDSLink(href=absolute_href, rel=rel, type=link_type, title=title) entry = OPDSLink(href=absolute_href, rel=rel, type=link_type, title=title)
key = rel or absolute_href key = rel or absolute_href
# Prioritize search links with template parameters
if key == "search" and key in results:
existing = results[key]
if "{searchTerms}" in (existing.href or ""):
continue
if "{searchTerms}" in absolute_href:
results[key] = entry
continue
results[key] = entry results[key] = entry
return results return results
+1 -1
View File
@@ -122,7 +122,7 @@ def api_calibre_opds_feed() -> ResponseReturnValue:
if letter: if letter:
feed = client.browse_letter(letter, start_href=href) feed = client.browse_letter(letter, start_href=href)
elif query: elif query:
feed = client.search(query) feed = client.search(query, start_href=href)
else: else:
feed = client.fetch_feed(href) feed = client.fetch_feed(href)
except CalibreOPDSError as exc: except CalibreOPDSError as exc: