From 99d985e4c6f7862b09920b6371207a95afb21092 Mon Sep 17 00:00:00 2001 From: Michael Walker Date: Mon, 2 Sep 2024 20:46:51 +0100 Subject: [PATCH] [nyarlathotep] Add job to post KJP to the fediverse https://hacksrus.xyz/users/kjpbot https://kingjamesprogramming.tumblr.com/ --- hosts/nyarlathotep/configuration.nix | 31 +++++++++ hosts/nyarlathotep/jobs/rss-to-mastodon.py | 81 ++++++++++++++++++++++ hosts/nyarlathotep/secrets.yaml | 6 +- 3 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 hosts/nyarlathotep/jobs/rss-to-mastodon.py diff --git a/hosts/nyarlathotep/configuration.nix b/hosts/nyarlathotep/configuration.nix index 19ea6ce7..dc79d324 100644 --- a/hosts/nyarlathotep/configuration.nix +++ b/hosts/nyarlathotep/configuration.nix @@ -588,4 +588,35 @@ in ]; sops.secrets."users/remote_sync/ssh_private_key".owner = config.users.extraUsers.remote-sync.name; + + ############################################################################### + # RSS-to-Mastodon + ############################################################################### + + users.extraUsers.rss-to-mastodon = { + home = "/persist/var/lib/rss-to-mastodon"; + createHome = true; + isSystemUser = true; + group = "nogroup"; + }; + + systemd.services.rss-to-mastodon-kjp-hacksrus = { + description = "Publish King James Programming to hacksrus.xyz"; + startAt = "hourly"; + serviceConfig = { + ExecStart = + let python = pkgs.python3.withPackages (ps: [ ps.docopt ps.feedparser ps.requests ]); + in concatStringsSep " " [ + "${python}/bin/python3" + (pkgs.writeText "rss-to-mastodon.py" (fileContents ./jobs/rss-to-mastodon.py)) + "-d https://hacksrus.xyz/" + "-f https://kingjamesprogramming.tumblr.com/rss" + "-l /persist/var/lib/rss-to-mastodon/kjp-hacksrus.txt" + ]; + User = "rss-to-mastodon"; + EnvironmentFile = config.sops.secrets."users/rss_to_mastodon/kjp_hacksrus_env".path; + }; + }; + + sops.secrets."users/rss_to_mastodon/kjp_hacksrus_env" = { }; } diff --git a/hosts/nyarlathotep/jobs/rss-to-mastodon.py b/hosts/nyarlathotep/jobs/rss-to-mastodon.py new file mode 100644 index 00000000..bc86d57a --- /dev/null +++ b/hosts/nyarlathotep/jobs/rss-to-mastodon.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 + +"""RSS-to-Mastodon (or Pleroma) + +Requires the API_KEY environment variable to be set. + +Usage: + rss-to-mastodon -d -f -l [-e ] [-v ] + +Options: + -d api domain + -f rss feed URL + -l file to log feed item IDs to (to prevent double-posting) + -e maximum number of entries to post [default: 1] + -v visibility of entries [default: public] +""" + +import docopt +import feedparser +import http.client +import os +import pathlib +import requests +import sys +import time + +api_token = os.getenv("API_KEY") +if api_token is None: + raise Exception("missing API key") + +args = docopt.docopt(__doc__) +api_domain = args["-d"] +feed_url = args["-f"] +history_file = pathlib.Path(args["-l"]) +entries = int(args["-e"]) +visibility = args["-v"] + +attempts = 0 +feed = None +while attempts < 5: + # tumblr seems to often just drop connections with the default feedparser + # user agent, so let's pretend to be curl + try: + feed = feedparser.parse(feed_url, agent="curl/7.54.1") + break + except http.client.RemoteDisconnected: + print(f"failed to download feed - attempt {attempts}", file=sys.stderr) + attempts += 1 + time.sleep(2) + +if feed is None: + print("could not download feed", file=sys.stderr) + sys.exit(1) + +# will crash if the file doesn't exist - but that's probably a good failsafe to +# prevent the same post being spammed if the log file gets accidentally deleted +history = history_file.read_text().split() +items = [entry for entry in feed["items"][:entries] if entry["id"] not in history] + +# if there are multiple items, post the older ones first +for item in reversed(items): + print(item["id"]) + print(item["title"]) + print() + + requests.post( + f"{api_domain}/api/v1/statuses", + headers={ + "Authorization": f"Bearer {api_token}", + "Idempotency-Key": item["id"], + }, + json={ + "status": item["title"], + "visibility": visibility, + }, + ).raise_for_status() + + # yes, this is inefficient - but the file will have a few hundred entries in + # it at most + history.append(item["id"]) + history_file.write_text("\n".join(history)) diff --git a/hosts/nyarlathotep/secrets.yaml b/hosts/nyarlathotep/secrets.yaml index 405932e5..e08e3a13 100644 --- a/hosts/nyarlathotep/secrets.yaml +++ b/hosts/nyarlathotep/secrets.yaml @@ -1,6 +1,8 @@ users: barrucadu: ENC[AES256_GCM,data:H6q5mf0vurd5FRCPftLGXvGzDep5iYlSw4gJMVCWhB8+35A8wYyE8i5qW+pNEE+TtxrXpYa9MzjWpAUdsmoyHi1Ncficb8USvIWAeLgQljAG0mrV9Yj1TjmDH81UsXKqzpJw2duXymnQ,iv:ls1DMfK4Y0RZgEDPRhQC/jJPUOKRLRljV/yuIfAqnB4=,tag:x2FzpOuDDndWF7WMJqb4eQ==,type:str] notbarrucadu: ENC[AES256_GCM,data:Q7++CIUemGmLY2mCYQoF4ImyK9HNqbd0NTNY0PohQfQhuXZE6vvxRUypXppKUxpaLct4mvc+f9+uAY5MJB8a2D3YH51tqbEDQaJqxWn1Qw90Wxubvr/EdpzgKJW9BAZLIGxCTOM=,iv:QmoC1J+1FNiLzGrBJiWa83VuRZJx5CKflZTmhHFt6ZM=,tag:qGMfbc13j65/yDlzAvy3QQ==,type:str] + rss_to_mastodon: + kjp_hacksrus_env: ENC[AES256_GCM,data:+H7js3WDrDuuQQjuAs3mG87xHyhWXUOoH47OlhUhM96h6pyVP5pV34+gwDvO4cgCWmbeDg==,iv:ptGUl/MdXgxrrem0kMKeQcpUsHgPbBamaJehDjVwVAo=,tag:z4MXvOFHU1e/LTkjcBec0A==,type:str] remote_sync: ssh_private_key: ENC[AES256_GCM,data:lOfo8ALZTCq7GVEZ+2KCDch+mOkOrSm5oqJKUNP+JVqfxA3r2GV8071ESFme8oTmsLIl/u5W9UxK6GMobM5M4ynZUTr1H3/yLRMNrbymTqIrKeGejaIy4hZRbF4Pv4NgtRTbnBfP7HHHnhykARuROmmtLQVITrAi4j9BJ7smyzOd8rdeyOsHBioOd784DLUGXQaGAmEvBRGuZ/pq3hEV0kQof5I94V82A7zWjZpD4yO76e+LYCA0Eu2FxM88P5jkiTPWLxFciY0z++2QvPguuYeXeAFkDc7yR2hiWFWT+rtfZSGsBL+Czse9PZbatugHn59ZPEzvUlv+FTnx5Vc1o9fQYGWCJGerZY2Sg6AtPglEdrmeTtEeELGVmnK/i5etlRaOER4fGSIIoIQ/xAor/GK0laoMQtU/MDLcuvw8BtD91/yTwFYbrghe8BEbnO36OUFQFh4OIWo8pHnkfbPZtJF+fRjXiO5yEdZpx29pMrqkhWqvEeujGurLZvTcQLX2iJ0utmQ3zv2/d8c+mzNHPNxUvHAEkp3kUjyktq0aylyjTdw=,iv:rxxAph3k6JEFJaq1LbWfIj6ah1E9r6locEBDghf/7gE=,tag:oEp4/ILyn5YfEDgC7eMhiw==,type:str] nixfiles: @@ -33,8 +35,8 @@ sops: UFVZQkpPSUY1YWZlRlFCbDNBcHhzVmcKzBqO5fMoaNerJw3ovWXCPLQM0cDfte03 ZiXMnrIUfIf2AntjjnZKc84jsnubTyD2fQLGNVkAetmQ3PTb3OKXqQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2024-02-17T02:55:22Z" - mac: ENC[AES256_GCM,data:aSOVomfoEY8J9Wwie2YN8uRKodptwKQqMWvS7QNgcTrm8QzFG2E73tWPln+OQoRYmdZVeM948v7u0fl73G5bkC3SwYdizdxOG0MEyDyDQs0k2YLPyJpQB8KPf+xv4ME6C6LSYwGzQKUYN98/03Vyt6YWe+LTYOi6ZlPlX/fNo/I=,iv:Mj65qQay08OTFc3p6fAoVdaDEhoR2do/VkUf08m7/wA=,tag:jPLX2vhTTxJ6oHLYS9xDyA==,type:str] + lastmodified: "2024-09-02T19:42:11Z" + mac: ENC[AES256_GCM,data:Fvlv7VdlVJotJtwoRUTbif2Hu/ly9P7sHB3S39Q5h4JJuX19jO32VySS5dg0NKzm9VbjaD42E+5wGty128JQNs4vcS+znlgP1H5ZJdLd0tKTYtGjIdV8CoJAWRdLqcyJwNrGbnEaPkyk5jaIf7m2zTHOIhBrH0nT80QEaqpL0aQ=,iv:Ly3hnnpActejElcp1ubHa9wuMrrFEgHzof07Om9pr5g=,tag:EZm3A4pGldvT+spk45LWxA==,type:str] pgp: [] unencrypted_suffix: _unencrypted version: 3.8.1