"""Domain-level paywall hints. We never fetch article pages, so a paywall can only be inferred from the host. This is a curated, conservative list of hard/soft paywalls — enough to label a card "subscription may be required" and to prefer readable stories for the lead and for replacements. It will never be perfect; it's an honest hint, not a gate. """ from __future__ import annotations from urllib.parse import urlsplit # Host suffixes considered paywalled. Subdomains match (news.nature.com → nature.com). PAYWALL_DOMAINS = { "newscientist.com", "nature.com", "nytimes.com", "wsj.com", "ft.com", "economist.com", "wired.com", "theatlantic.com", "washingtonpost.com", "bloomberg.com", "technologyreview.com", "newyorker.com", "scientificamerican.com", "nationalgeographic.com", "thetimes.co.uk", "telegraph.co.uk", "foreignpolicy.com", "hbr.org", "harpers.org", } def is_paywalled(url: str | None) -> bool: host = urlsplit(url or "").netloc.lower() if host.startswith("www."): host = host[4:] return any(host == d or host.endswith("." + d) for d in PAYWALL_DOMAINS)