feeds: convert to module
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./bluetooth.nix
|
./bluetooth.nix
|
||||||
|
./feeds.nix
|
||||||
./fs.nix
|
./fs.nix
|
||||||
./hardware
|
./hardware
|
||||||
./i2p.nix
|
./i2p.nix
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
{ lib }:
|
{ ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
hourly = { freq = "hourly"; };
|
hourly = { freq = "hourly"; };
|
||||||
daily = { freq = "daily"; };
|
daily = { freq = "daily"; };
|
||||||
@@ -13,24 +12,15 @@ let
|
|||||||
tech = { cat = "tech"; };
|
tech = { cat = "tech"; };
|
||||||
uncat = { cat = "uncat"; };
|
uncat = { cat = "uncat"; };
|
||||||
|
|
||||||
text = { format = "text"; };
|
|
||||||
image = { format = "image"; };
|
|
||||||
podcast = { format = "podcast"; };
|
|
||||||
|
|
||||||
mkRss = format: url: { inherit url format; } // uncat // infrequent;
|
mkRss = format: url: { inherit url format; } // uncat // infrequent;
|
||||||
# format-specific helpers
|
# format-specific helpers
|
||||||
mkText = mkRss text;
|
mkText = mkRss "text";
|
||||||
mkImg = mkRss image;
|
mkImg = mkRss "image";
|
||||||
mkPod = mkRss podcast;
|
mkPod = mkRss "podcast";
|
||||||
|
|
||||||
# host-specific helpers
|
# 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 = [
|
podcasts = [
|
||||||
(mkPod "https://lexfridman.com/feed/podcast/" // rat // weekly)
|
(mkPod "https://lexfridman.com/feed/podcast/" // rat // weekly)
|
||||||
## Astral Codex Ten
|
## Astral Codex Ten
|
||||||
@@ -154,41 +144,7 @@ in rec {
|
|||||||
# ART
|
# ART
|
||||||
(mkImg "https://miniature-calendar.com/feed" // art // daily)
|
(mkImg "https://miniature-calendar.com/feed" // art // daily)
|
||||||
];
|
];
|
||||||
|
in
|
||||||
all = texts ++ images ++ podcasts;
|
{
|
||||||
|
sane.feeds = 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));
|
|
||||||
}
|
}
|
@@ -9,7 +9,7 @@
|
|||||||
# $ sudo -u freshrss -g freshrss FRESHRSS_DATA_PATH=/var/lib/freshrss ./result/cli/export-opml-for-user.php --user admin
|
# $ 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 = {
|
sops.secrets.freshrss_passwd = {
|
||||||
sopsFile = ../../../secrets/servo.yaml;
|
sopsFile = ../../../secrets/servo.yaml;
|
||||||
@@ -30,8 +30,8 @@
|
|||||||
systemd.services.freshrss-import-feeds =
|
systemd.services.freshrss-import-feeds =
|
||||||
let
|
let
|
||||||
fresh = config.systemd.services.freshrss-config;
|
fresh = config.systemd.services.freshrss-config;
|
||||||
feeds = import ../../../modules/home-manager/feeds.nix { inherit lib; };
|
feeds = config.sane.feeds;
|
||||||
opml = pkgs.writeText "sane-freshrss.opml" (feeds.feedsToOpml feeds.all);
|
opml = pkgs.writeText "sane-freshrss.opml" (sane-lib.feeds.feedsToOpml feeds);
|
||||||
in {
|
in {
|
||||||
inherit (fresh) wantedBy environment;
|
inherit (fresh) wantedBy environment;
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./allocations.nix
|
./allocations.nix
|
||||||
|
./feeds.nix
|
||||||
./fs
|
./fs
|
||||||
./gui
|
./gui
|
||||||
./home-manager
|
./home-manager
|
||||||
|
50
modules/feeds.nix
Normal file
50
modules/feeds.nix
Normal 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.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
@@ -11,7 +11,6 @@ let
|
|||||||
cfg = config.sane.home-manager;
|
cfg = config.sane.home-manager;
|
||||||
# extract `pkg` from `sane.packages.enabledUserPkgs`
|
# extract `pkg` from `sane.packages.enabledUserPkgs`
|
||||||
pkg-list = pkgspec: builtins.map (e: e.pkg) pkgspec;
|
pkg-list = pkgspec: builtins.map (e: e.pkg) pkgspec;
|
||||||
feeds = import ./feeds.nix { inherit lib; };
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
|
@@ -1,40 +1,41 @@
|
|||||||
# gnome feeds RSS viewer
|
# gnome feeds RSS viewer
|
||||||
{ lib, sane-lib, ... }:
|
{ config, lib, sane-lib, ... }:
|
||||||
|
|
||||||
let
|
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 {
|
in {
|
||||||
sane.fs."/home/colin/.config/org.gabmus.gfeeds.json" =
|
sane.fs."/home/colin/.config/org.gabmus.gfeeds.json" = sane-lib.fs.wantedText (
|
||||||
let
|
builtins.toJSON {
|
||||||
myFeeds = feeds.texts ++ feeds.images;
|
# feed format is a map from URL to a dict,
|
||||||
in sane-lib.fs.wantedText (builtins.toJSON {
|
# with dict["tags"] a list of string tags.
|
||||||
# feed format is a map from URL to a dict,
|
feeds = builtins.foldl' (acc: feed: acc // {
|
||||||
# with dict["tags"] a list of string tags.
|
"${feed.url}".tags = [ feed.cat feed.freq ];
|
||||||
feeds = builtins.foldl' (acc: feed: acc // {
|
}) {} wanted-feeds;
|
||||||
"${feed.url}".tags = [ feed.cat feed.freq ];
|
dark_reader = false;
|
||||||
}) {} myFeeds;
|
new_first = true;
|
||||||
dark_reader = false;
|
# windowsize = {
|
||||||
new_first = true;
|
# width = 350;
|
||||||
# windowsize = {
|
# height = 650;
|
||||||
# width = 350;
|
# };
|
||||||
# height = 650;
|
max_article_age_days = 90;
|
||||||
# };
|
enable_js = false;
|
||||||
max_article_age_days = 90;
|
max_refresh_threads = 3;
|
||||||
enable_js = false;
|
# saved_items = {};
|
||||||
max_refresh_threads = 3;
|
# read_items = [];
|
||||||
# saved_items = {};
|
show_read_items = true;
|
||||||
# read_items = [];
|
full_article_title = true;
|
||||||
show_read_items = true;
|
# views: "webview", "reader", "rsscont"
|
||||||
full_article_title = true;
|
default_view = "rsscont";
|
||||||
# views: "webview", "reader", "rsscont"
|
open_links_externally = true;
|
||||||
default_view = "rsscont";
|
full_feed_name = false;
|
||||||
open_links_externally = true;
|
refresh_on_startup = true;
|
||||||
full_feed_name = false;
|
tags = lib.unique (
|
||||||
refresh_on_startup = true;
|
(builtins.catAttrs "cat" wanted-feeds) ++ (builtins.catAttrs "freq" wanted-feeds)
|
||||||
tags = lib.lists.unique (
|
);
|
||||||
(builtins.catAttrs "cat" myFeeds) ++ (builtins.catAttrs "freq" myFeeds)
|
open_youtube_externally = false;
|
||||||
);
|
media_player = "vlc"; # default: mpv
|
||||||
open_youtube_externally = false;
|
}
|
||||||
media_player = "vlc"; # default: mpv
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,12 @@
|
|||||||
# gnome feeds RSS viewer
|
# gnome feeds RSS viewer
|
||||||
{ lib, sane-lib, ... }:
|
{ config, sane-lib, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
feeds = import ./feeds.nix { inherit lib; };
|
feeds = sane-lib.feeds;
|
||||||
|
all-feeds = config.sane.feeds;
|
||||||
|
wanted-feeds = feeds.filterByFormat ["podcast"] all-feeds;
|
||||||
in {
|
in {
|
||||||
sane.fs."/home/colin/.config/gpodderFeeds.opml" = sane-lib.fs.wantedText (
|
sane.fs."/home/colin/.config/gpodderFeeds.opml" = sane-lib.fs.wantedText (
|
||||||
feeds.feedsToOpml feeds.podcasts
|
feeds.feedsToOpml wanted-feeds
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,12 @@
|
|||||||
# news-flash RSS viewer
|
# news-flash RSS viewer
|
||||||
{ lib, sane-lib, ... }:
|
{ config, sane-lib, ... }:
|
||||||
|
|
||||||
let
|
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 {
|
in {
|
||||||
sane.fs."/home/colin/.config/newsflashFeeds.opml" = sane-lib.fs.wantedText (
|
sane.fs."/home/colin/.config/newsflashFeeds.opml" = sane-lib.fs.wantedText (
|
||||||
feeds.feedsToOpml (feeds.texts ++ feeds.images)
|
feeds.feedsToOpml wanted-feeds
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,16 +1,18 @@
|
|||||||
{ config, lib, sane-lib, ... }:
|
{ 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
|
lib.mkIf config.sane.home-manager.enable
|
||||||
{
|
{
|
||||||
sane.fs."/home/colin/.config/vlc/vlcrc" =
|
sane.fs."/home/colin/.config/vlc/vlcrc" = sane-lib.fs.wantedText ''
|
||||||
let
|
|
||||||
feeds = import ./feeds.nix { inherit lib; };
|
|
||||||
podcastUrls = lib.strings.concatStringsSep "|" (
|
|
||||||
builtins.map (feed: feed.url) feeds.podcasts
|
|
||||||
);
|
|
||||||
in sane-lib.fs.wantedText ''
|
|
||||||
[podcast]
|
[podcast]
|
||||||
podcast-urls=${podcastUrls}
|
podcast-urls=${podcast-urls}
|
||||||
[core]
|
[core]
|
||||||
metadata-network-access=0
|
metadata-network-access=0
|
||||||
[qt]
|
[qt]
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
{ lib, ... }@moduleArgs:
|
{ lib, ... }@moduleArgs:
|
||||||
|
|
||||||
{
|
{
|
||||||
|
feeds = import ./feeds.nix moduleArgs;
|
||||||
fs = import ./fs.nix moduleArgs;
|
fs = import ./fs.nix moduleArgs;
|
||||||
path = import ./path.nix moduleArgs;
|
path = import ./path.nix moduleArgs;
|
||||||
types = import ./types.nix moduleArgs;
|
types = import ./types.nix moduleArgs;
|
||||||
|
38
modules/lib/feeds.nix
Normal file
38
modules/lib/feeds.nix
Normal 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>
|
||||||
|
'';
|
||||||
|
}
|
Reference in New Issue
Block a user