George Huebner

The Singularity Ate My News Reader

I decided to start using an RSS client, primarily because I kept coming back to these same few blogs with really nice content and wanted to stay updated, but also because the signal-to-noise ratio on e.g. Reddit is horrendous and mindlessly doomscrolling is not a good use of my time.

I’ve gone through a real Emacs ricing stint lately, and as the lines of elisp in my config continue to accrue, so too does the gravitational pull of my favorite text-based operating system gradually increase — email, IRC, and now RSS have fallen through the event horizon. I decided to pick up Elfeed and I wasn’t disappointed; you can tell that Chris Wellons, despite having turned to the dark side, has put a lot of thought into the UX and extensibility of it.

Elfeed Hacks

Here are a few lines from my config that I think might be of value to other people:

(use-package elfeed
    :config
    (defun elfeed-clean-trash (&optional now)
      (interactive "P")
      (with-elfeed-db-visit (entry _)
          (when (elfeed-tagged-p 'trash entry)
            (setf (elfeed-entry-content entry) nil)))
      (if now
          (elfeed-db-gc)
        (run-with-idle-timer 15 nil #'elfeed-db-gc)))

    (defface elfeed-search-trashed-title-face
        '((((class color)) (:foreground "red" :weight extra-bold)))
      "Face used in search mode for trashed entries.")
    (push '(trash elfeed-search-trashed-title-face) elfeed-search-face-alist)

So Elfeed doesn’t really have a concept of “deleting” entries, but you can delete the content of an entry if you’re worried about disk space; given that I subscribe to news aggregators, this is a legitimate concern.

    (setq-default elfeed-search-filter "-trash @6-months-ago +unread")
    (setq fysh/elfeed-filters '(("Blogs" . "-trash +blog") ("Comics" . "-trash +webcomic") ("News" . "-trash @1-week-ago +aggregator +unread")))
    (defun fysh/elfeed-pick-filter (filter)
      (interactive (list (alist-get (completing-read "Filter: " (mapcar (lambda (s) (car s)) fysh/elfeed-filters)) fysh/elfeed-filters nil nil 'equal)))
      (setq elfeed-search-filter filter)
      (elfeed-search-update--force)
      (goto-char (point-min)))
    :general-config
    ('normal 'elfeed-search-mode-map "S" #'elfeed-search-live-filter)
    ('normal 'elfeed-search-mode-map "s" #'fysh/elfeed-pick-filter)

I really like the interactive search feature, but having a few defaults for my most common tags is quite nice.

    :config
    (define-advice elfeed-search-show-entry (:override (entry &optional arg) quick-browse)
      (interactive (list (elfeed-search-selected :ignore-region) current-prefix-arg))
      (require 'elfeed-show)
      (when (elfeed-entry-p entry)
        (elfeed-untag entry 'unread)
        (elfeed-search-update-entry entry)
        (unless elfeed-search-remain-on-entry (forward-line))
        (pcase arg
          ('(4) (funcall browse-url-browser-function (elfeed-entry-link entry)))
          ('(16) (funcall browse-url-secondary-browser-function (elfeed-entry-link entry)))
          (_ (elfeed-show-entry entry))))))

Although I usually prefer using shr to view entries, entries from news aggregators just contain a link to the post and a link to the comments section; for these I can C-u RET to open the article directly in my browser (I’m using the surprisingly nice xwidget-webkit).

Speaking of news aggregators, it’s rather annoying to have the same blog post appear multiple times from Hacker News and Lobsters; to address that, I patched Elfeed to generate the unique id for an entry based on its link, causing “hash collisions” between identical articles. I haven’t upstreamed this because it’s certainly an antipattern (Elfeed fetches feeds asynchronously, so there’s a race condition between which feed you fetch a link from first), but I tend to filter posts using tags, not feeds, so I like it better this way: if you’re interested, you can get the patch here.

Finally, there’s an annoying issue where cached images are displayed at their original resolution, usually exceeding the dimensions of the window I’m viewing the post in. I originally thought this was a bug in shr, then realized it was a bug in Elfeed, but when fixing Elfeed I realized there was a related SVG bug in Emacs. If you’re impatient you can (setq shr-ignore-cache t) as a band-aid fix.

Bonus Round: Browser Config

(use-package emacs
    :requires xwidget-internal
    :init
    (defun fysh/search (start end)
      "Search selected string using a search engine."
      (interactive "r")
      (let* ((q (buffer-substring-no-properties start end))
             (query (if (use-region-p) q
                      (read-string (format-prompt "Search" (current-word)) nil nil (current-word)))))
        (browse-url (concat fysh/search-prefix (url-hexify-string query)))))
    :general
    (leader-def "S" #'fysh/search)

There’s gotta be a builtin keybinding for this…

    :hook (evil-collection-setup . (lambda (&rest _) (general-def 'normal 'xwidget-webkit-mode-map "J" (lambda () (interactive) (xwidget-webkit-scroll-up (/ (frame-pixel-height) 2))))))

I rarely need to scroll horizontally, and apparently no one else does either, because it was broken for quite a while ( ̄ω ̄;)

    :config
    (defun fysh/eww-browse-url-ephemeral (url &rest args)
      (eww-browse-url url args)
      (run-with-timer 5 nil (lambda () (kill-matching-buffers "eww-" nil t))))
    (setq browse-url-handlers '(
                                ("journals\\.aps\\.org" . (lambda (url &rest args)
                                                            (let* ((pdf (string-replace "/abstract/" "/pdf/" url)))
                                                              (if (string-equal url pdf) (browse-url-default-browser url) (eww-browse-url (concat fysh/browse-url-proxy-prefix pdf))))))
                                ("arxiv\\.org" . (lambda (url &rest args)
                                                   (cond ((string-match-p "/pdf/" url) (fysh/eww-browse-url-ephemeral url args))
                                                         ((string-match-p "/abs/" url) (fysh/eww-browse-url-ephemeral (string-replace "/abs/" "/pdf/" url) args))
                                                         (t (browse-url-default-browser url args)))))
							    ("pdf$" . fysh/eww-browse-url-ephemeral)
							    ("." . xwidget-webkit-browse-url)))

One really nice feature of eww is that PDFs and other non-text files will automatically open in their respective Emacs major mode; to further facilitate that, I mimicked Zotero’s connectors by redirecting arXiv abstracts to their PDFs.

fysh/browse-url-proxy-prefix was meant to view PDFs from paywalled journals through my university’s proxy; eww can’t auth because there’s no JS engine, so I considered reusing cookies from my primary browser, but that sounds like a lot more work than just Gib Lenning the papers instead 😉

    (define-advice xwidget-kill-buffer-query-function (:override () always-kill) t))

I don’t like being asked to kill buffers; (defalias 'yes-or-no-p 'y-or-n-p) usually does the trick, but take this as my parting advice for the reader.

Feeds I Read

Scott Aaronson cs, quantum

Andrew Kelley zig, systems

Xe Iaso nix

Artemis nix

Jade Lovelace nix

Ian Henry nix

Jakob Kreuze emacs, security

Jon Sangster nix, emacs

Chris Wellons emacs, systems

Evan Ovadia vale, systems

Karthinks emacs

XKCD webcomic

Leftover Salad webcomic

#emacs