Feed2toot automatically parses rss feeds, identifies new posts and posts them on the
MastodonFediverse social network.
I'll probably end up writing something similar myself one of these days since (somewhat critically) this does not support multiple (feeds->account) mappings right now and thus I will have to run one for each of my Arcology Sites' feeds.
feed2toot
rixpkgs entry
We love a Python package that is pretty easy to get running:
{ lib,
buildPythonPackage,
fetchPypi,
beautifulsoup4,
feedparser,
mastodon-py,
python,
}:
rec {
buildPythonPackage pname = "feed2toot";
version = "0.17";
src = fetchPypi {
inherit pname version;
sha256 = "sha256-VKj0Wu5KdsfsywO1g4z3mFsQT9Svrt6wQHZSeNxbRAw=";
};
propagatedBuildInputs = [
beautifulsoup4
feedparser
mastodon-py
python.pkgs.influxdb];
meta = with lib; {
homepage = "https://feed2toot.readthedocs.io/en/latest/";
description = "Feed2toot parses a RSS feed, extracts the last entries and sends them to Mastodon.";
license = licenses.gpl3;
maintainers = with maintainers; [ rrix ];
};
}
shell:nix-build '<arroyo>' -A feed2toot -> /nix/store/fvw5m7q8y2kh0mkr2y52wv55lvv2hwym-python3.10-feed2toot-0.17
Generating the Feed URI Lists Programmatically
So I have a few Arcology
Sites, each of these needs its own configuration file passed to the
feed2toot
invocation. This queries Arroyo Arcology Generator's arcology.arroyo.Feed to generate a
file with each group of (site, visibility)
of feeds in the database.
setq arcology-sites '(("lionsrear" . "https://thelionsrear.com/")
("garden" . "https://arcology.garden/")
("cce" . "https://cce.whatthefuck.computer/")
("arcology" . "https://engine.arcology.garden/")))
(
defun arcology-arroyo-feeds-to-urls (group rows)
(let* ((file-name (format "~/arroyo-nix/files/feed2toot-uris-%s-%s.txt" (first group) (second group))))
(list file-name
(lambda (row)
(-map (let* ((feed-prefix (alist-get (nth 3 row) arcology-sites nil nil #'equal))
(nth 3 row))
(site (nth 1 row))
(key ("/") feed-prefix key)))
(feed-url (replace-regexp-in-string (concat site
feed-url
)
)
rows))))
defun arcology-arroyo-write-feed-list (file lines)
(
(with-temp-buffer"\n" lines))
(insert (s-join
(write-file file)
file))
defun arcology-arroyo-make-feed-lists ()
(* :from arcology-feeds])
(->> (arroyo-db-query [:select lambda (row)
(-group-by (list (nth 3 row) (nth 4 row))))
(lambda (partitioned)
(-map (let* ((partition (first partitioned))
(cdr partitioned)))
(rows (
(arcology-arroyo-feeds-to-urls partition rows))))lambda (group)
(-map (apply #'arcology-arroyo-write-feed-list group)))))
(
"\n" (arcology-arroyo-make-feed-lists)) (s-join
~/arroyo-nix/files/feed2toot-uris-garden-private.txt ~/arroyo-nix/files/feed2toot-uris-arcology-public.txt ~/arroyo-nix/files/feed2toot-uris-garden-public.txt ~/arroyo-nix/files/feed2toot-uris-lionsrear-unlisted.txt ~/arroyo-nix/files/feed2toot-uris-lionsrear-public.txt ~/arroyo-nix/files/feed2toot-uris-cce-public.txt
INPROGRESS deploy to The Wobserver
feed2toot
operates with INI
configuration files and luckily enough nixpkgs
includes a way to convert attrsets
to ini
files in lib.generators.toINI
; this is
wrapped in a function which takes the basic per-instance configuration
details including the uri-list which are generated above and then
creates a SystemD service manifest and
timer to poll the site and post the toots. Easy peazy.
,#+ARROYONIXOSMODULE: nixos/feed2toot.nix ,#+ARROYONIXOSROLE: server
{ pkgs, lib, config, ... }:
let
mkFeed2TootIni = { instance,
uriList,
visibility ? "public",
credLoc ? config.users.users.feed2toot.home,
tagListFile ? pkgs.writeText "empty-taglist" ""}:
"feed2toot-${instance}"
pkgs.writeText (lib.generators.toINI {}
{
mastodon = {
instance_url = "https://notes.whatthefuck.computer";
user_credentials = "${credLoc}/${instance}_feed2toot_usercred.txt";
client_credentials = "${credLoc}/${instance}_feed2toot_clientcred.txt";
toot_visibility = visibility;
};
cache.cachefile = "/var/cache/feed2toot/${instance}.db";
lock.lock_file = "/run/feed2toot/${instance}.lock";
rss.uri_list = builtins.path { path = uriList; };
rss.toot = "NEW by @rrix@notes.whatthefuck.computer: {link} \\n {summary}";
rss.toot_max_len = 10000;
hashtaglist.several_words_hashtags_list = tagListFile;
feedparser.accept_bozo_exceptions = true;
});
feeds = [
(mkFeed2TootIni {
instance = "garden";
visibility = "public";
uriList = ../files/feed2toot-uris-cce-public.txt;
})
(mkFeed2TootIni {
instance = "garden";
visibility = "public";
uriList = ../files/feed2toot-uris-garden-public.txt;
})
(mkFeed2TootIni {
instance = "lionsrear";
visibility = "public";
uriList = ../files/feed2toot-uris-lionsrear-public.txt;
})
(mkFeed2TootIni {
instance = "garden";
visibility = "unlisted";
uriList = ../files/feed2toot-uris-arcology-public.txt;
})
(mkFeed2TootIni {
instance = "lionsrear";
visibility = "unlisted";
uriList = ../files/feed2toot-uris-lionsrear-unlisted.txt;
})
(mkFeed2TootIni {
instance = "garden";
visibility = "private";
uriList = ../files/feed2toot-uris-garden-private.txt;
})
];
in {
ids.uids.feed2toot = 902;
ids.gids.bots = 902;
users.groups.bots = {
gid = config.ids.gids.bots;
};
users.users."feed2toot" = {
home = "/srv/feed2toot/";
group = "bots";
uid = config.ids.uids.feed2toot;
isSystemUser = true;
};
systemd.tmpfiles.rules = [
"d /run/feed2toot 1777 feed2toot bots" # lock file directory
"d /var/cache/feed2toot 1777 feed2toot bots" # cache file directory
"d /srv/feed2toot 1777 feed2toot bots" # working directory
];
systemd.services.feed2toot = {
description = "Feeds to Toots";
after = ["pleroma.service"];
wantedBy = ["default.target"];
script =
lib.concatMapStrings(feed: "\n${pkgs.feed2toot}/bin/feed2toot -c " + feed)
;
feedsserviceConfig = {
User = "feed2toot";
WorkingDirectory = "/srv/feed2toot";
};
};
systemd.timers.feed2toot = {
description = "Start feed2toot on the quarter-hour";
timerConfig = {
OnUnitActiveSec = "15 minutes";
OnStartupSec = "15 minutes";
};
wantedBy = [ "default.target" ];
};
}
NEXT remove bs4 html filtering
pleroma supports embedding HTML so we should use that.