Skip to content

Instantly share code, notes, and snippets.

@cmj
Created July 2, 2026 15:33
Show Gist options
  • Select an option

  • Save cmj/f52b6ad5cd549d51321bdf3e3582029f to your computer and use it in GitHub Desktop.

Select an option

Save cmj/f52b6ad5cd549d51321bdf3e3582029f to your computer and use it in GitHub Desktop.
Trump's truthsocial.com RSS feed generator
#!/usr/bin/env python3
"""
RSS feed generator for @realDonaldTrump's Truth Social posts.
"""
import sys
import io
import re
import html
from datetime import datetime
from email.utils import format_datetime
from xml.sax.saxutils import escape as xml_escape
import requests
ACCOUNT_ID = "107780257626128497"
API_URL = (
f"https://truthsocial.com/api/v1/accounts/{ACCOUNT_ID}/statuses"
"?exclude_replies=true&with_muted=true"
)
MAX_LEN = 420
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", newline="")
def strip_html(raw: str) -> str:
text = raw
text = text.replace("<br/>", " ").replace("<br />", " ").replace("<br>", " ")
text = re.sub(r'</span><span class="ellipsis">', "", text)
text = re.sub(r'</span><span class="invisible">', "", text)
text = re.sub(r'</span><span class="">', "", text)
text = text.replace("</span>", " ")
text = re.sub(r"<[^>]*>", "", text) # strip any remaining tags
text = html.unescape(text) # &amp; -> &, &gt; -> >, etc.
text = re.sub(r"\s{2,}", " ", text).strip()
return text
def truncate(text: str, limit: int = MAX_LEN) -> str:
if len(text) > limit:
return text[: limit - 1] + "…"
return text
def format_pubdate(created_at: str) -> str:
try:
dt = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
return format_datetime(dt)
except ValueError:
return created_at
HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
),
"Accept": "application/json",
}
def fetch_statuses():
resp = requests.get(API_URL, headers=HEADERS, timeout=15)
resp.encoding = "utf-8"
resp.raise_for_status()
return resp.json()
def build_item(status: dict) -> str:
created = format_pubdate(status.get("created_at", ""))
url = status.get("url", "")
content_html = status.get("content", "")
if content_html and content_html != "<p></p>":
content = strip_html(content_html.replace("*", "٭"))
else:
media = status.get("media_attachments") or [{}]
content = media[0].get("url", "")
content = truncate(content)
title = xml_escape(content)
description = xml_escape(content)
link = xml_escape(url)
return (
" <item>\n"
f" <title>{title}</title>\n"
" <dc:creator>@realDonaldTrump</dc:creator>\n"
f" <description>{description}</description>\n"
f" <pubDate>{created}</pubDate>\n"
f" <link>{link}</link>\n"
" </item>"
)
def main():
print("Content-type: application/rss+xml; charset=utf-8")
print("Cache-control: public, max-age=4000")
print("Access-Control-Allow-Origin: *")
print()
print('<?xml version="1.0" encoding="UTF-8"?>')
print(
'<rss version="2.0" '
'xmlns:dc="http://purl.org/dc/elements/1.1/" '
'xmlns:webfeeds="http://webfeeds.org/rss/1.0" '
'xmlns:media="http://search.yahoo.com/mrss/">'
)
print(" <channel>")
print(" <title>realDonaldTrump</title>")
print(" <description>Public posts from @realDonaldTrump</description>")
print(" <link>https://truthsocial.com/@realDonaldTrump</link>")
print(" <language>en-us</language>")
try:
statuses = fetch_statuses()
except (requests.RequestException, ValueError) as exc:
sys.stderr.write(f"Failed to fetch/parse statuses: {exc}\n")
statuses = []
for status in statuses:
print(build_item(status))
print(" </channel>")
print("</rss>")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment