relay/app/public/views.py
Andros Fenollosa 349a5e1376 Add ActivityPub and RSS bridges serving virtual social.org feeds
Expose external accounts as virtual Org Social feeds that any client
can follow with a plain #+FOLLOW: line:

- /bridge/activitypub/@{user}@{instance}/ bridges a Mastodon or any
  ActivityPub account (WebFinger, actor and paginated outbox; only
  public top-level notes, with CW, hashtags, language and attachments)
- /bridge/rss/?url={feed} bridges any RSS/Atom feed (entry title as
  *** sub-heading, converted body and link to the original article)

Registration is implicit on first GET. Bridged data is stored in the
existing Profile/Post tables so /profile/, /search/ and the rest of
the API work on bridged feeds. Active bridges are refreshed every 15
minutes; bridges unrequested for 90 days are cleaned up.

Remote HTML is converted to Org text, escaping headline-like lines so
external content cannot inject posts. Fetches enforce the Webmention
SSRF protections, a 10s timeout and a 5MB size cap. Bridged posts
never queue Webmentions nor publish notifications.
2026-07-17 15:26:41 +02:00

115 lines
4.3 KiB
Python

from django.http import JsonResponse
def root_view(request):
"""Root endpoint with HATEOAS links"""
return JsonResponse(
{
"type": "Success",
"errors": [],
"data": {
"name": "Org Social Relay",
"description": "P2P system for Org Social files",
},
"_links": {
"self": {"href": "/", "method": "GET"},
"feeds": {"href": "/feeds/", "method": "GET"},
"add-feed": {"href": "/feeds/", "method": "POST"},
"feed-content": {
"href": "/feed-content/?feed={feed_url}",
"method": "GET",
"templated": True,
},
"mentions": {
"href": "/mentions/?feed={feed_url}",
"method": "GET",
"templated": True,
},
"replies": {
"href": "/replies/?post={post_url}",
"method": "GET",
"templated": True,
},
"notifications": {
"href": "/notifications/?feed={feed_url}",
"method": "GET",
"templated": True,
},
"sse-notifications": {
"href": "/sse/notifications/?feed={feed_url}",
"method": "GET",
"templated": True,
},
"sse-notifications-all": {
"href": "/sse/notifications/",
"method": "GET",
},
"reactions": {
"href": "/reactions/?feed={feed_url}",
"method": "GET",
"templated": True,
},
"replies-to": {
"href": "/replies-to/?feed={feed_url}",
"method": "GET",
"templated": True,
},
"boosts": {
"href": "/boosts/?post={post_url}",
"method": "GET",
"templated": True,
},
"interactions": {
"href": "/interactions/?post={post_url}",
"method": "GET",
"templated": True,
},
"search": {
"href": "/search/?q={query}",
"method": "GET",
"templated": True,
},
"groups": {"href": "/groups/", "method": "GET"},
"group-messages": {
"href": "/groups/{group_name}/",
"method": "GET",
"templated": True,
},
"join-group": {
"href": "/groups/{group_name}/members/?feed={feed_url}",
"method": "POST",
"templated": True,
},
"polls": {"href": "/polls/", "method": "GET"},
"poll-votes": {
"href": "/polls/votes/?post={post_url}",
"method": "GET",
"templated": True,
},
"profile": {
"href": "/profile/?feed={feed_url}",
"method": "GET",
"templated": True,
},
"rss": {
"href": "/rss.xml",
"method": "GET",
"description": "RSS feed of latest posts (supports ?tag={tag} and ?feed={feed_url} filters)",
},
"stats": {"href": "/stats/", "method": "GET"},
"bridge": {"href": "/bridge/", "method": "GET"},
"bridge-activitypub": {
"href": "/bridge/activitypub/@{user}@{instance}/",
"method": "GET",
"templated": True,
"description": "ActivityPub account as a virtual social.org feed",
},
"bridge-rss": {
"href": "/bridge/rss/?url={feed_url}",
"method": "GET",
"templated": True,
"description": "RSS/Atom feed as a virtual social.org feed",
},
},
}
)