feat: Implement filtering of unsupported download formats in CalibreOPDSClient

This commit is contained in:
JB
2025-11-11 07:29:13 -08:00
parent 4491674fac
commit 67453ed17c
2 changed files with 63 additions and 7 deletions
+25 -7
View File
@@ -4,6 +4,7 @@ import dataclasses
import html
import re
from dataclasses import dataclass, field
from pathlib import PurePosixPath
from typing import Any, Dict, Iterable, List, Mapping, Optional
from urllib.parse import urljoin, urlparse
from xml.etree import ElementTree as ET
@@ -38,6 +39,8 @@ _EPUB_MIME_TYPES = {
"application/x-zip",
"application/x-zip-compressed",
}
_SUPPORTED_DOWNLOAD_MIME_TYPES = set(_EPUB_MIME_TYPES) | {"application/pdf"}
_SUPPORTED_DOWNLOAD_EXTENSIONS = {".epub", ".pdf"}
class CalibreOPDSError(RuntimeError):
@@ -270,7 +273,12 @@ class CalibreOPDSClient:
feed_id = root.findtext("atom:id", default=None, namespaces=NS)
feed_title = root.findtext("atom:title", default=None, namespaces=NS)
links = self._parse_links(root.findall("atom:link", NS), base_url)
entries = [self._parse_entry(node, base_url) for node in root.findall("atom:entry", NS)]
parsed_entries = [self._parse_entry(node, base_url) for node in root.findall("atom:entry", NS)]
entries = [
entry
for entry in parsed_entries
if entry.download and self._is_supported_download(entry.download)
]
return OPDSFeed(id=feed_id, title=feed_title, entries=entries, links=links)
def _parse_entry(self, node: ET.Element, base_url: str) -> OPDSEntry:
@@ -576,14 +584,27 @@ class CalibreOPDSClient:
results[key] = entry
return results
@staticmethod
def _is_supported_download(link: OPDSLink) -> bool:
mime = (link.type or "").split(";")[0].strip().lower()
if mime in _SUPPORTED_DOWNLOAD_MIME_TYPES:
return True
href = (link.href or "").strip()
if not href:
return False
parsed_path = urlparse(href).path or ""
extension = PurePosixPath(parsed_path).suffix.lower()
return extension in _SUPPORTED_DOWNLOAD_EXTENSIONS
@staticmethod
def _select_download_link(links: Mapping[str, OPDSLink] | Iterable[OPDSLink]) -> Optional[OPDSLink]:
if isinstance(links, Mapping):
iterable: List[OPDSLink] = list(links.values())
else:
iterable = list(links)
supported = [link for link in iterable if CalibreOPDSClient._is_supported_download(link)]
best: Optional[OPDSLink] = None
for link in iterable:
for link in supported:
rel = (link.rel or "").lower()
if "acquisition" not in rel:
continue
@@ -594,11 +615,8 @@ class CalibreOPDSClient:
best = link
if best:
return best
# Fallback to first epub-like link even without explicit acquisition rel
for link in iterable:
mime = (link.type or "").lower()
if mime in _EPUB_MIME_TYPES:
return link
if supported:
return supported[0]
# No valid acquisition-style link exposed
return None
+38
View File
@@ -104,3 +104,41 @@ def test_calibre_opds_relative_urls_keep_catalog_prefix() -> None:
assert client._make_url("books/sample.epub") == "http://example.com/opds/books/sample.epub"
assert client._make_url("/cover/1") == "http://example.com/cover/1"
assert client._make_url("?page=2") == "http://example.com/opds/?page=2"
def test_calibre_opds_filters_out_unsupported_formats() -> None:
client = CalibreOPDSClient("http://example.com/catalog")
xml_payload = """<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<feed xmlns=\"http://www.w3.org/2005/Atom\">
<id>catalog</id>
<title>Example Catalog</title>
<entry>
<id>audio-book</id>
<title>Unsupported Audio</title>
<link rel=\"http://opds-spec.org/acquisition\"
href=\"books/sample.mp3\"
type=\"audio/mpeg\" />
</entry>
<entry>
<id>pdf-book</id>
<title>Allowed PDF</title>
<link rel=\"http://opds-spec.org/acquisition\"
href=\"books/sample.pdf\"
type=\"application/pdf\" />
</entry>
<entry>
<id>epub-book</id>
<title>Allowed EPUB</title>
<link rel=\"http://opds-spec.org/acquisition\"
href=\"books/sample.epub\" />
</entry>
</feed>
"""
feed = client._parse_feed(xml_payload, base_url="http://example.com/catalog")
identifiers = {entry.id for entry in feed.entries}
assert identifiers == {"pdf-book", "epub-book"}
for entry in feed.entries:
assert entry.download is not None
assert entry.download.href.endswith((".pdf", ".epub"))