feeds: convert to module

This commit is contained in:
2023-01-08 05:24:56 +00:00
parent 488036beb3
commit e8f778fecd
12 changed files with 158 additions and 105 deletions

View File

@@ -2,6 +2,7 @@
{
imports = [
./bluetooth.nix
./feeds.nix
./fs.nix
./hardware
./i2p.nix

View File

@@ -1,5 +1,4 @@
{ lib }:
{ ... }:
let
hourly = { freq = "hourly"; };
daily = { freq = "daily"; };
@@ -13,24 +12,15 @@ let
tech = { cat = "tech"; };
uncat = { cat = "uncat"; };
text = { format = "text"; };
image = { format = "image"; };
podcast = { format = "podcast"; };
mkRss = format: url: { inherit url format; } // uncat // infrequent;
# format-specific helpers
mkText = mkRss text;
mkImg = mkRss image;
mkPod = mkRss podcast;
mkText = mkRss "text";
mkImg = mkRss "image";
mkPod = mkRss "podcast";
# host-specific helpers
mkSubstack = subdomain: mkText "https://${subdomain}.substack.com/feed";
mkSubstack = subdomain: { substack = subdomain; };
# merge the attrs `new` into each value of the attrs `addTo`
addAttrs = new: addTo: builtins.mapAttrs (k: v: v // new) addTo;
# for each value in `attrs`, add a value to the child attrs which holds its key within the parent attrs.
withInverseMapping = key: attrs: builtins.mapAttrs (k: v: v // { "${key}" = k; }) attrs;
in rec {
podcasts = [
(mkPod "https://lexfridman.com/feed/podcast/" // rat // weekly)
## Astral Codex Ten
@@ -154,41 +144,7 @@ in rec {
# ART
(mkImg "https://miniature-calendar.com/feed" // art // daily)
];
all = texts ++ images ++ podcasts;
# return only the feed items which match this category (e.g. "tech")
filterCat = cat: feeds: builtins.filter (item: item.cat == cat) feeds;
# return only the feed items which match this format (e.g. "podcast")
filterFormat = format: feeds: builtins.filter (item: item.format == format) feeds;
# transform a list of feeds into an attrs mapping cat => [ feed0 feed1 ... ]
partitionByCat = feeds: builtins.groupBy (f: f.cat) feeds;
# represents a single RSS feed.
opmlTerminal = feed: ''<outline xmlUrl="${feed.url}" type="rss"/>'';
# a list of RSS feeds.
opmlTerminals = feeds: lib.strings.concatStringsSep "\n" (builtins.map opmlTerminal feeds);
# one node which packages some flat grouping of terminals.
opmlGroup = title: feeds: ''
<outline text="${title}" title="${title}">
${opmlTerminals feeds}
</outline>
'';
# a list of groups (`groupMap` is an attrs mapping groupName => [ feed0 feed1 ... ]).
opmlGroups = groupMap: lib.strings.concatStringsSep "\n" (
builtins.attrValues (builtins.mapAttrs opmlGroup groupMap)
);
# top-level OPML file which could be consumed by something else.
opmlTopLevel = body: ''
<?xml version="1.0" encoding="utf-8"?>
<opml version="2.0">
<body>
${body}
</body>
</opml>
'';
# **primary API**: generate a OPML file from the provided feeds
feedsToOpml = feeds: opmlTopLevel (opmlGroups (partitionByCat feeds));
in
{
sane.feeds = texts ++ images ++ podcasts;
}

View File

@@ -9,7 +9,7 @@
# $ sudo -u freshrss -g freshrss FRESHRSS_DATA_PATH=/var/lib/freshrss ./result/cli/export-opml-for-user.php --user admin
# ```
{ config, lib, pkgs, ... }:
{ config, lib, pkgs, sane-lib, ... }:
{
sops.secrets.freshrss_passwd = {
sopsFile = ../../../secrets/servo.yaml;
@@ -30,8 +30,8 @@
systemd.services.freshrss-import-feeds =
let
fresh = config.systemd.services.freshrss-config;
feeds = import ../../../modules/home-manager/feeds.nix { inherit lib; };
opml = pkgs.writeText "sane-freshrss.opml" (feeds.feedsToOpml feeds.all);
feeds = config.sane.feeds;
opml = pkgs.writeText "sane-freshrss.opml" (sane-lib.feeds.feedsToOpml feeds);
in {
inherit (fresh) wantedBy environment;
serviceConfig = {

View File

@@ -3,6 +3,7 @@
{
imports = [
./allocations.nix
./feeds.nix
./fs
./gui
./home-manager

50
modules/feeds.nix Normal file
View File

@@ -0,0 +1,50 @@
{ lib, ... }:
with lib;
let
feed = types.submodule ({ config, ... }: {
options = {
freq = mkOption {
type = types.enum [ "hourly" "daily" "weekly" "infrequent" ];
default = "infrequent";
};
cat = mkOption {
type = types.enum [ "art" "humor" "pol" "rat" "tech" "uncat" ];
default = "uncat";
};
format = mkOption {
type = types.enum [ "text" "image" "podcast" ];
};
url = mkOption {
type = types.str;
description = ''
url to a RSS feed
'';
};
substack = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
if the feed is a substack domain, just enter the subdomain here and the url/format field can be populated automatically
'';
};
};
config = lib.mkIf (config.substack != null) {
url = "https://${config.substack}.substack.com/feed";
format = "text";
};
});
in
{
# we don't explicitly generate anything from the feeds here.
# instead, config.sane.feeds is used by a variety of services at their definition site.
options = {
sane.feeds = mkOption {
type = types.listOf feed;
default = [];
description = ''
RSS feeds indexed by a human-readable name.
'';
};
};
}

View File

@@ -11,7 +11,6 @@ let
cfg = config.sane.home-manager;
# extract `pkg` from `sane.packages.enabledUserPkgs`
pkg-list = pkgspec: builtins.map (e: e.pkg) pkgspec;
feeds = import ./feeds.nix { inherit lib; };
in
{
imports = [

View File

@@ -1,40 +1,41 @@
# gnome feeds RSS viewer
{ lib, sane-lib, ... }:
{ config, lib, sane-lib, ... }:
let
feeds = import ./feeds.nix { inherit lib; };
feeds = sane-lib.feeds;
all-feeds = config.sane.feeds;
wanted-feeds = feeds.filterByFormat ["text" "image"] all-feeds;
in {
sane.fs."/home/colin/.config/org.gabmus.gfeeds.json" =
let
myFeeds = feeds.texts ++ feeds.images;
in sane-lib.fs.wantedText (builtins.toJSON {
# feed format is a map from URL to a dict,
# with dict["tags"] a list of string tags.
feeds = builtins.foldl' (acc: feed: acc // {
"${feed.url}".tags = [ feed.cat feed.freq ];
}) {} myFeeds;
dark_reader = false;
new_first = true;
# windowsize = {
# width = 350;
# height = 650;
# };
max_article_age_days = 90;
enable_js = false;
max_refresh_threads = 3;
# saved_items = {};
# read_items = [];
show_read_items = true;
full_article_title = true;
# views: "webview", "reader", "rsscont"
default_view = "rsscont";
open_links_externally = true;
full_feed_name = false;
refresh_on_startup = true;
tags = lib.lists.unique (
(builtins.catAttrs "cat" myFeeds) ++ (builtins.catAttrs "freq" myFeeds)
);
open_youtube_externally = false;
media_player = "vlc"; # default: mpv
});
sane.fs."/home/colin/.config/org.gabmus.gfeeds.json" = sane-lib.fs.wantedText (
builtins.toJSON {
# feed format is a map from URL to a dict,
# with dict["tags"] a list of string tags.
feeds = builtins.foldl' (acc: feed: acc // {
"${feed.url}".tags = [ feed.cat feed.freq ];
}) {} wanted-feeds;
dark_reader = false;
new_first = true;
# windowsize = {
# width = 350;
# height = 650;
# };
max_article_age_days = 90;
enable_js = false;
max_refresh_threads = 3;
# saved_items = {};
# read_items = [];
show_read_items = true;
full_article_title = true;
# views: "webview", "reader", "rsscont"
default_view = "rsscont";
open_links_externally = true;
full_feed_name = false;
refresh_on_startup = true;
tags = lib.unique (
(builtins.catAttrs "cat" wanted-feeds) ++ (builtins.catAttrs "freq" wanted-feeds)
);
open_youtube_externally = false;
media_player = "vlc"; # default: mpv
}
);
}

View File

@@ -1,10 +1,12 @@
# gnome feeds RSS viewer
{ lib, sane-lib, ... }:
{ config, sane-lib, ... }:
let
feeds = import ./feeds.nix { inherit lib; };
feeds = sane-lib.feeds;
all-feeds = config.sane.feeds;
wanted-feeds = feeds.filterByFormat ["podcast"] all-feeds;
in {
sane.fs."/home/colin/.config/gpodderFeeds.opml" = sane-lib.fs.wantedText (
feeds.feedsToOpml feeds.podcasts
feeds.feedsToOpml wanted-feeds
);
}

View File

@@ -1,10 +1,12 @@
# news-flash RSS viewer
{ lib, sane-lib, ... }:
{ config, sane-lib, ... }:
let
feeds = import ./feeds.nix { inherit lib; };
feeds = sane-lib.feeds;
all-feeds = config.sane.feeds;
wanted-feeds = feeds.filterByFormat ["text" "image"] all-feeds;
in {
sane.fs."/home/colin/.config/newsflashFeeds.opml" = sane-lib.fs.wantedText (
feeds.feedsToOpml (feeds.texts ++ feeds.images)
feeds.feedsToOpml wanted-feeds
);
}

View File

@@ -1,16 +1,18 @@
{ config, lib, sane-lib, ... }:
let
feeds = sane-lib.feeds;
all-feeds = config.sane.feeds;
wanted-feeds = feeds.filterByFormat ["podcast"] all-feeds;
podcast-urls = lib.concatStringsSep "|" (
builtins.map (feed: feed.url) wanted-feeds
);
in
lib.mkIf config.sane.home-manager.enable
{
sane.fs."/home/colin/.config/vlc/vlcrc" =
let
feeds = import ./feeds.nix { inherit lib; };
podcastUrls = lib.strings.concatStringsSep "|" (
builtins.map (feed: feed.url) feeds.podcasts
);
in sane-lib.fs.wantedText ''
sane.fs."/home/colin/.config/vlc/vlcrc" = sane-lib.fs.wantedText ''
[podcast]
podcast-urls=${podcastUrls}
podcast-urls=${podcast-urls}
[core]
metadata-network-access=0
[qt]

View File

@@ -1,6 +1,7 @@
{ lib, ... }@moduleArgs:
{
feeds = import ./feeds.nix moduleArgs;
fs = import ./fs.nix moduleArgs;
path = import ./path.nix moduleArgs;
types = import ./types.nix moduleArgs;

38
modules/lib/feeds.nix Normal file
View File

@@ -0,0 +1,38 @@
{ lib, ... }:
rec {
# PRIMARY API: generate a OPML file from a list of feeds
feedsToOpml = feeds: opmlTopLevel (opmlGroups (partitionByCat feeds));
# only keep feeds whose category is one of the provided
filterByFormat = fmts: builtins.filter (feed: builtins.elem feed.format fmts);
## INTERNAL APIS
# transform a list of feeds into an attrs mapping cat => [ feed0 feed1 ... ]
partitionByCat = feeds: builtins.groupBy (f: f.cat) feeds;
# represents a single RSS feed.
opmlTerminal = feed: ''<outline xmlUrl="${feed.url}" type="rss"/>'';
# a list of RSS feeds.
opmlTerminals = feeds: lib.concatStringsSep "\n" (builtins.map opmlTerminal feeds);
# one node which packages some flat grouping of terminals.
opmlGroup = title: feeds: ''
<outline text="${title}" title="${title}">
${opmlTerminals feeds}
</outline>
'';
# a list of groups (`groupMap` is an attrs mapping groupName => [ feed0 feed1 ... ]).
opmlGroups = groupMap: lib.concatStringsSep "\n" (
builtins.attrValues (builtins.mapAttrs opmlGroup groupMap)
);
# top-level OPML file which could be consumed by something else.
opmlTopLevel = body: ''
<?xml version="1.0" encoding="utf-8"?>
<opml version="2.0">
<body>
${body}
</body>
</opml>
'';
}