···
+
"""OPML generation for thicket."""
+
import xml.etree.ElementTree as ET
+
from datetime import datetime
+
from pathlib import Path
+
from typing import Optional
+
from xml.dom import minidom
+
from ..models import UserMetadata
+
"""Generates OPML files from feed collections."""
+
def __init__(self) -> None:
+
"""Initialize the OPML generator."""
+
users: dict[str, UserMetadata],
+
title: str = "Thicket Feeds",
+
output_path: Optional[Path] = None,
+
"""Generate OPML XML content from user metadata.
+
users: Dictionary of username -> UserMetadata
+
title: Title for the OPML file
+
output_path: Optional path to write the OPML file
+
OPML XML content as string
+
# Create root OPML element
+
opml = ET.Element("opml", version="2.0")
+
head = ET.SubElement(opml, "head")
+
title_elem = ET.SubElement(head, "title")
+
title_elem.text = title
+
date_created = ET.SubElement(head, "dateCreated")
+
date_created.text = datetime.now().strftime("%a, %d %b %Y %H:%M:%S %z")
+
date_modified = ET.SubElement(head, "dateModified")
+
date_modified.text = datetime.now().strftime("%a, %d %b %Y %H:%M:%S %z")
+
body = ET.SubElement(opml, "body")
+
# Add each user as an outline with their feeds as sub-outlines
+
for username, user_metadata in sorted(users.items()):
+
user_outline = ET.SubElement(body, "outline")
+
user_outline.set("text", user_metadata.display_name or username)
+
user_outline.set("title", user_metadata.display_name or username)
+
# Add user metadata as attributes if available
+
if user_metadata.homepage:
+
user_outline.set("htmlUrl", user_metadata.homepage)
+
if user_metadata.email:
+
user_outline.set("email", user_metadata.email)
+
# Add each feed as a sub-outline
+
for feed_url in sorted(user_metadata.feeds):
+
feed_outline = ET.SubElement(user_outline, "outline")
+
feed_outline.set("type", "rss")
+
feed_outline.set("text", feed_url)
+
feed_outline.set("title", feed_url)
+
feed_outline.set("xmlUrl", feed_url)
+
feed_outline.set("htmlUrl", feed_url)
+
# Convert to pretty-printed XML string
+
xml_str = self._prettify_xml(opml)
+
# Write to file if path provided
+
output_path.write_text(xml_str, encoding="utf-8")
+
def _prettify_xml(self, elem: ET.Element) -> str:
+
"""Return a pretty-printed XML string for the Element."""
+
rough_string = ET.tostring(elem, encoding="unicode")
+
reparsed = minidom.parseString(rough_string)
+
return reparsed.toprettyxml(indent=" ")
+
def generate_flat_opml(
+
users: dict[str, UserMetadata],
+
title: str = "Thicket Feeds (Flat)",
+
output_path: Optional[Path] = None,
+
"""Generate a flat OPML file with all feeds at the top level.
+
This format may be more compatible with some feed readers.
+
users: Dictionary of username -> UserMetadata
+
title: Title for the OPML file
+
output_path: Optional path to write the OPML file
+
OPML XML content as string
+
# Create root OPML element
+
opml = ET.Element("opml", version="2.0")
+
head = ET.SubElement(opml, "head")
+
title_elem = ET.SubElement(head, "title")
+
title_elem.text = title
+
date_created = ET.SubElement(head, "dateCreated")
+
date_created.text = datetime.now().strftime("%a, %d %b %Y %H:%M:%S %z")
+
date_modified = ET.SubElement(head, "dateModified")
+
date_modified.text = datetime.now().strftime("%a, %d %b %Y %H:%M:%S %z")
+
body = ET.SubElement(opml, "body")
+
# Collect all feeds with their associated user info
+
for username, user_metadata in users.items():
+
for feed_url in user_metadata.feeds:
+
"display_name": user_metadata.display_name or username,
+
"homepage": user_metadata.homepage,
+
# Sort feeds by URL for consistency
+
all_feeds.sort(key=lambda f: f["url"] or "")
+
# Add each feed as a top-level outline
+
for feed_info in all_feeds:
+
feed_outline = ET.SubElement(body, "outline")
+
feed_outline.set("type", "rss")
+
# Create a descriptive title that includes the user
+
title_text = f"{feed_info['display_name']}: {feed_info['url']}"
+
feed_outline.set("text", title_text)
+
feed_outline.set("title", title_text)
+
url = feed_info["url"] or ""
+
feed_outline.set("xmlUrl", url)
+
homepage_url = feed_info.get("homepage") or url
+
feed_outline.set("htmlUrl", homepage_url or "")
+
# Add custom attributes for user info
+
feed_outline.set("thicketUser", feed_info["username"] or "")
+
homepage = feed_info.get("homepage")
+
feed_outline.set("thicketHomepage", homepage)
+
# Convert to pretty-printed XML string
+
xml_str = self._prettify_xml(opml)
+
# Write to file if path provided
+
output_path.write_text(xml_str, encoding="utf-8")