bf52bc3079
build-and-push / docker (push) Has been cancelled
Features: feed CRUD, per-feed ntfy target (incl. private servers), Telegram/webhook channels, keyword filters, image attachments, per-feed intervals, OPML import/export, notification history & stats, users with roles, admin alerts, RU/EN i18n, light/dark theme, notification preview, history search, activity chart. Dockerized. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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
|