aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJan Tuomi <jans.tuomi@gmail.com>2025-04-27 20:38:19 +0300
committerJan Tuomi <jans.tuomi@gmail.com>2025-04-27 20:38:19 +0300
commitd3a32fa776b2d22d68adf68615984fd9755bb249 (patch)
tree51df42567924fbe61bfbc08d4367dd9c2cc72e38
parent21d0a44fb02b7b4c064f5cbc3a0fbfde8161426d (diff)
Add opml endpoint, more settings
-rw-r--r--app/server.py38
-rw-r--r--plugins/JsonApiSourcePlugin.py28
2 files changed, 55 insertions, 11 deletions
diff --git a/app/server.py b/app/server.py
index 8e90dea..0d1becc 100644
--- a/app/server.py
+++ b/app/server.py
@@ -1,7 +1,7 @@
+import os
from typing import Any, cast
import bottle
from tinydb import Query
-
from app.DatabaseManager import database_manager
@@ -17,6 +17,10 @@ def feed_to_tr(feed: dict[str, str]) -> str:
return f'<tr>{feed_id_to_td(feed["feed_id"])}{feed_last_build_date_to_td(feed.get("feed_last_build_date", ""))}</tr>'
+def opml_to_tr() -> str:
+ return '<tr><td colspan="2"><a href="/opml">All feeds (OPML)</a></td></tr>'
+
+
def feeds_to_table(feeds: list[dict[str, str]]) -> str:
if len(feeds) == 0:
return (
@@ -24,7 +28,7 @@ def feeds_to_table(feeds: list[dict[str, str]]) -> str:
+ "If you did that already, you might have to wait a bit for the data to propagate."
)
- trs: list[str] = [feed_to_tr(feed) for feed in feeds]
+ trs: list[str] = [feed_to_tr(feed) for feed in feeds] + [opml_to_tr()]
thead = f"<thead><tr><td>Feed</td><td>Last build date</td></tr></thead>"
tbody = f'<tbody>{"".join(trs)}</tbody>'
return "<table>" + thead + tbody + "</table>"
@@ -114,6 +118,36 @@ def feed(feed_id: str):
feed_xml: str = feed["feed_xml"]
return feed_xml
+@bottle.route("/opml")
+def opml():
+ if database_manager.db is None:
+ raise Exception("Database is not initialized")
+
+ base_url = os.environ.get("AGGRO_BASE_URL", "http://localhost:8080")
+
+ _feeds = database_manager.feeds.all()
+ feeds = cast(list[dict[str, str]], _feeds)
+
+ bottle.response.set_header("content-type", "text/xml")
+
+ outlines = "\n".join(
+ f'<outline text="{feed["feed_id"]}" title="{feed["feed_id"]}" type="rss" xmlUrl="{base_url}/{feed["feed_id"]}" />'
+ for feed in feeds
+ )
+
+ opml = f"""<?xml version="1.0" encoding="UTF-8"?>
+<opml version="2.0">
+ <head>
+ <title>Aggro Subscriptions</title>
+ </head>
+ <body>
+{outlines}
+ </body>
+</opml>
+"""
+ return opml
+
+
def run_web_server(host: str, port: int):
bottle.run(host=host, port=port)
diff --git a/plugins/JsonApiSourcePlugin.py b/plugins/JsonApiSourcePlugin.py
index c799a05..b202349 100644
--- a/plugins/JsonApiSourcePlugin.py
+++ b/plugins/JsonApiSourcePlugin.py
@@ -12,7 +12,9 @@ class Plugin(PluginInterface):
def __init__(self, id: str, params: Params) -> None:
super().__init__("JsonApiSourcePlugin", id, params)
self.url: str = get_config(params, "url").strip("/")
- self.method: str = get_config_or_default(params, "method", "get").strip().tolower()
+ self.method: str = get_config_or_default(params, "method", "get").strip().lower()
+ self.body: str | None = get_config_or_default(params, "body", None)
+ self.headers: dict = get_config_or_default(params, "headers", {})
self.selector_post: str = get_config(params, "selector_post")
self.selector_title: str | None = get_config_or_default(
params, "selector_title", None
@@ -55,14 +57,22 @@ class Plugin(PluginInterface):
result_items: list[Item] = []
with requests.session() as session:
fetch_fn = getattr(session, self.method)
- api_resp = fetch_fn(self.url, allow_redirects=True)
+ body = (
+ json.dumps(self.body)
+ if self.body and (type(self.body) is dict or type(self.body) is list)
+ else self.body
+ )
+ api_resp = fetch_fn(self.url, allow_redirects=True, data=body, headers=self.headers)
data = json.loads(api_resp.text)
- post_items = eval(self.selector_post, {"data": data})
+
+ ctx = {"json": json, "data": data}
+ post_items = eval(self.selector_post, ctx)
for post in post_items:
+ ctx = {"json": json, "post": post, "data": data}
if self.selector_link:
link = eval(
- self.selector_link, {"post": post, "data": data}
+ self.selector_link, ctx
).strip()
guid = ItemGUID(link, is_perma_link=True)
else:
@@ -71,28 +81,28 @@ class Plugin(PluginInterface):
if self.selector_title:
title = eval(
- self.selector_title, {"post": post, "data": data}
+ self.selector_title, ctx
).strip()
else:
title = None
if self.selector_description:
description = eval(
- self.selector_description, {"post": post, "data": data}
+ self.selector_description, ctx
).strip()
else:
description = None
if self.selector_date:
date = eval(
- self.selector_date, {"post": post, "data": data}
+ self.selector_date, ctx
).strip()
else:
date = None
if self.selector_author:
author = eval(
- self.selector_author, {"post": post, "data": data}
+ self.selector_author, ctx
).strip()
else:
author = None
@@ -100,7 +110,7 @@ class Plugin(PluginInterface):
if self.selector_image:
try:
image_src = eval(
- self.selector_image, {"post": post, "data": data}
+ self.selector_image, ctx
).strip()
except Exception:
print_exc()