diff --git a/pkgs/gpodder-configured/default.nix b/pkgs/gpodder-configured/default.nix index 4f896882..e241f4be 100644 --- a/pkgs/gpodder-configured/default.nix +++ b/pkgs/gpodder-configured/default.nix @@ -1,14 +1,17 @@ { makeWrapper , gpodder , symlinkJoin -, writeShellScript +, writeScriptBin , config }: +let + remove-extra = writeScriptBin "remove_extra.py" (builtins.readFile ./remove_extra.py); +in # we use a symlinkJoin so that we can inherit the .desktop and icon files from the original gPodder (symlinkJoin { name = "gpodder-configured"; - paths = [ gpodder ]; + paths = [ gpodder remove-extra ]; buildInputs = [ makeWrapper ]; # gpodder keeps all its feeds in a sqlite3 database. @@ -17,7 +20,8 @@ # repeat imports are deduplicated by url, even when offline. postBuild = '' makeWrapper $out/bin/gpodder $out/bin/gpodder-configured \ - --run "$out/bin/gpo import ~/.config/gpodderFeeds.opml" + --run "$out/bin/remove_extra.py ~/.config/gpodderFeeds.opml" \ + --run "$out/bin/gpo import ~/.config/gpodderFeeds.opml" \ # fix up the .desktop file to invoke our wrapped application orig_desktop=$(readlink $out/share/applications/gpodder.desktop) diff --git a/pkgs/gpodder-configured/remove_extra.py b/pkgs/gpodder-configured/remove_extra.py new file mode 100755 index 00000000..e25033b0 --- /dev/null +++ b/pkgs/gpodder-configured/remove_extra.py @@ -0,0 +1,79 @@ +#!/usr/bin/env nix-shell +#!nix-shell -i python3 -p "python3.withPackages (ps: [gnome-feeds.listparser])" -p gpodder + +from dataclasses import dataclass, field +import listparser +import subprocess +import sys + +@dataclass(repr=True) +class Feed: + url: str + title: str # Optional + def __init__(self, url: str, title: str): + self.url = url + self.title = title if title else None + + def __eq__(self, other: 'Feed') -> bool: + return self.url == other.url and \ + (self.title == other.title or None in [self.title, other.title]) + +@dataclass(init=True) +class Partitioned: + has_not_wanted: list[Feed] = field(default_factory=list) + wanted_not_has: list[Feed] = field(default_factory=list) + intersection: list[Feed] = field(default_factory=list) + + +def wanted_feeds(opml_file: str): + parsed = listparser.parse(open(opml_file).read()) + return [Feed(url=p['url'], title=p['title']) for p in parsed.feeds] + +def has_feeds(): + listing = subprocess.check_output(["gpo", "list"]).decode() + feeds = [] + + title = None + for line in listing.split("\n"): + if line.startswith("# "): # title + title = line[2:].strip() + elif line.startswith("http"): # feed URL: + feeds.append(Feed(url=line, title=title)) + title = None + + return feeds + +def partition_feeds(wanted: list[Feed], has: list[Feed]) -> Partitioned: + p = Partitioned() + for f in wanted + has: + w, h = f in wanted, f in has + if h and not w: + p.has_not_wanted.append(f) + elif w and not h: + p.wanted_not_has.append(f) + else: + assert w and h + p.intersection.append(f) + + return p + +def remove_feed(feed: Feed): + subprocess.check_output(['gpo', 'unsubscribe', feed.url]) + +def rationalize_feeds(opml_file: str): + wanted = wanted_feeds(opml_file) + has = has_feeds() + partitioned = partition_feeds(wanted, has) + + print("extra feeds:", "" if partitioned.has_not_wanted else "(none)") + for f in partitioned.has_not_wanted: + print(" ", f) + print() + + for f in partitioned.has_not_wanted: + remove_feed(f) + +if __name__ == "__main__": + wanted_opml, = sys.argv[1:] + rationalize_feeds(wanted_opml) +