58 lines
1.8 KiB
Python
58 lines
1.8 KiB
Python
|
|
"""OPML import/export for feed subscriptions."""
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from xml.etree import ElementTree as ET
|
||
|
|
|
||
|
|
from .models import Feed
|
||
|
|
|
||
|
|
|
||
|
|
def export_opml(feeds: list[Feed]) -> str:
|
||
|
|
"""Render feeds as an OPML 2.0 document."""
|
||
|
|
opml = ET.Element("opml", version="2.0")
|
||
|
|
head = ET.SubElement(opml, "head")
|
||
|
|
ET.SubElement(head, "title").text = "RSS → ntfy subscriptions"
|
||
|
|
body = ET.SubElement(opml, "body")
|
||
|
|
for feed in feeds:
|
||
|
|
attrs = {
|
||
|
|
"type": "rss",
|
||
|
|
"text": feed.title or feed.url,
|
||
|
|
"title": feed.title or feed.url,
|
||
|
|
"xmlUrl": feed.url,
|
||
|
|
}
|
||
|
|
# Stash the ntfy topic so a re-import keeps the routing.
|
||
|
|
if feed.ntfy_topic:
|
||
|
|
attrs["ntfyTopic"] = feed.ntfy_topic
|
||
|
|
if feed.ntfy_server:
|
||
|
|
attrs["ntfyServer"] = feed.ntfy_server
|
||
|
|
ET.SubElement(body, "outline", attrs)
|
||
|
|
return '<?xml version="1.0" encoding="UTF-8"?>\n' + ET.tostring(
|
||
|
|
opml, encoding="unicode"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def parse_opml(content: str) -> list[dict]:
|
||
|
|
"""Extract feed definitions from an OPML document.
|
||
|
|
|
||
|
|
Returns a list of dicts ready to build Feed rows. Raises ValueError on
|
||
|
|
malformed XML.
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
root = ET.fromstring(content)
|
||
|
|
except ET.ParseError as exc:
|
||
|
|
raise ValueError(f"Некорректный OPML: {exc}") from exc
|
||
|
|
|
||
|
|
feeds: list[dict] = []
|
||
|
|
for outline in root.iter("outline"):
|
||
|
|
url = outline.get("xmlUrl") or outline.get("xmlurl")
|
||
|
|
if not url:
|
||
|
|
continue
|
||
|
|
feeds.append(
|
||
|
|
{
|
||
|
|
"url": url.strip(),
|
||
|
|
"title": (outline.get("title") or outline.get("text") or "").strip(),
|
||
|
|
"ntfy_topic": (outline.get("ntfyTopic") or "").strip(),
|
||
|
|
"ntfy_server": (outline.get("ntfyServer") or "").strip(),
|
||
|
|
}
|
||
|
|
)
|
||
|
|
return feeds
|