/elfeed-summary

Feed summary interface for elfeed

Primary LanguageEmacs LispGNU General Public License v3.0GPL-3.0

elfeed-summary

https://melpa.org/packages/elfeed-summary-badge.svg

The package provides a tree-based feed summary interface for elfeed. The tree can include individual feeds, searches, and groups. It mainly serves as an easier “jumping point” for elfeed, so to make querying a subset of the elfeed database one action away.

Inspired by newsboat.

./img/screenshot.png

Installation

The package is available on MELPA, so install it however you normally install packages. My preferred way is use-package with straight:

(use-package elfeed-summary
  :straight t)

Of course, you have to have elfeed configured.

Usage

Running M-x elfeed-summary opens up the summary buffer, as shown on the screenshot.

The tree consists of:

  • feeds;
  • searches;
  • groups, that can include other groups, feeds, and searches.

Available keybindings in the summary mode:

KeybindingCommandDescription
RETelfeed-summary--actionOpen thing under the cursor (a feed, search, or a group). If there is at least one unread item, it will show only unread items.
M-RETelfeed-summary--action-show-readOpen thing under the cursor, but always include read items
qQuit the summary buffer
relfeed-summary--refreshRefresh the summary buffer
Relfeed-summary-updateRun update for elfeed feeds
uelfeed-summary-toggle-only-unreadToggle showing only unread entries
Uelfeed-summary--action-mark-readMark everything in the entry under the cursor as read

The standard keybindings from magit-section are also available, for instance TAB toggles the visibility of the current group. evil-mode is also supported.

Configuration

Tree configuration

The structure of the tree is determined by the elfeed-summary-settings variable.

This is a list of these possible items:

  • Group (group . <group-params>) Groups are used to group elements under collapsible sections.
  • Query (query . <query-params>) Query extracts a subset of elfeed feeds based on the given criteria. Each found feed will be represented as a line.
  • Search (search . <search-params>) Elfeed search, as defined by elfeed-search-set-filter.
  • a few special forms

<group-params> is an alist with the following keys:

  • :title (mandatory)
  • :elements (mandatory) - elements of the group. The structure is the same as in the root definition.
  • :face - group face. The default face is elfeed-summary-group-face.
  • :hide - if non-nil, the group is collapsed by default.

<query-params> can be:

  • A symbol of a tag. A feed will be matched if it has that tag.
  • :all. Will match anything.
  • (title . "string") or (title . <form>) Match feed title with string-match-p. <form> makes sense if you want to pass something like rx.
  • (author . "string") or (author . <form>)
  • (url . "string") or (url . <form>)
  • (and <q-1> <q-2> ... <q-n>) Match if all the conditions 1, 2, …, n match.
  • (or <q-1> <q-2> ... <q-n>) or (<q-1> <q-2> ... <q-n>) Match if any of the conditions 1, 2, …, n match.
  • (not <query>)

Feed tags for the query are determined by the elfeed-feeds variable.

Query examples:

  • (emacs lisp) Return all feeds that have either “emacs” or “lisp” tags.
  • (and emacs lisp) Return all feeds that have both “emacs” and “lisp” tags.
  • (and (title . "Emacs") (not planets)) Return all feeds that have “Emacs” in their title and don’t have the “planets” tag.

<search-params> is an alist with the following keys:

  • :filter (mandatory) filter string, as defined by elfeed-search-set-filter
  • :title (mandatory) title.
  • :tags - list of tags to get the face of the entry.

Available special forms:

  • :misc - print out feeds, not found by any query above.

Also keep in mind that '(key . ((values))) is the same as '(key (values)). This helps to shorten the form in many cases.

Also, this variable is not validated by any means, so wrong values can produce somewhat cryptic errors. Sorry about that.

Example

Here is an excerpt from my configuration that was used to produce this screenshot:

(setq elfeed-summary-settings
      '((group (:title . "GitHub")
               (:elements
                (query . (url . "SqrtMinusOne.private.atom"))
                (group . ((:title . "Guix packages")
                          (:elements
                           (query . (and github guix_packages)))
                          (:hide t)))))
        (group (:title . "Blogs [Software]")
               (:elements
                (query . software_blogs)))
        (group (:title . "Blogs [People]")
               (:elements
                (query . (and blogs people (not emacs)))
                (group (:title . "Emacs")
                       (:elements
                        (query . (and blogs people emacs))))))
        (group (:title . "Podcasts")
               (:elements
                (query . podcasts)))
        (group (:title . "Videos")
               (:elements
                (group
                 (:title . "Music")
                 (:elements
                  (query . (and videos music))))
                (group
                 (:title . "Tech")
                 (:elements
                  (query . (and videos tech))))
                (group
                 (:title . "History")
                 (:elements
                  (query . (and videos history))))
                ;; ...
                ))
        ;; ...
        (group (:title . "Miscellaneous")
               (:elements
                (group
                 (:title . "Searches")
                 (:elements
                  (search
                   (:filter . "@6-months-ago sqrtminusone")
                   (:title . "About me"))
                  (search
                   (:filter . "+later")
                   (:title . "Check later"))))
                (group
                 (:title . "Ungrouped")
                 (:elements :misc))))))

Faces

Faces for groups by default use the elfeed-summary-group-faces variable, which serves as a list of faces for each level of the tree. Individual group faces can be overridden with the :face attribute.

Faces for feeds by default reuse the existing elfeed mechanism. The tags for feeds are taken from the elfeed-feeds variable; if a feed has at least one unread entry, the unread tag is added to the list. This can be overridden by setting the elfeed-summary-feed-face-fn variable.

Searches are mostly the same as feeds, but tags for the search are taken from the :tags attribute. This also can be overridden with elfeed-summary-search-face-fn variable.

Opening elfeed-search in other window

If you set:

(setq elfeed-summary-other-window t)

Then RET and M-RET in the elfeed-summary buffer will open the search buffer in other window.

elfeed-summary-width regulates the width of the remaining summary window in this case. It is useful because the data in the search buffer is generally wider than in the summary buffer. The variable can also be set to nil to disable this behavior.

Other options

Also take a look at M-x customize-group elfeed-summary for the rest of available options.

Known problems

Usage with RSS-bridge

RSS-Bridge is a popular project to generate feeds, which unfortunately generates URLs like <instance-of-rss-bridge>?action…=

Which I don’t know how to filter in elfeed, because filtering with = does not seem to account for the URL params. To resolve this, I’ve configured nginx the following way:

location /rss-bridge/ {
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        rewrite ^/rss-bridge/rewrite/([0-9]+)/(.*)$ /$2 break;
        proxy_pass http://localhost:8085/;
}

This allows using URLs like: <my-rss-bridge>/rewrite/1234/?action.... Just don’t forget the trailing slash.

Setting a unique URL for every feed resolves the problem.

Ideas and alternatives

The default interface of elfeed is just a list of all entries. Naturally, it gets hard to navigate when there are a lot of sources with varying frequencies of posts.

Elfeed itself provides one solution, which is using bookmarks to save individual searches. This can work, but it can be somewhat cumbersome.

elfeed-score is another solution, which introduces scoring rules for entries. Thus, with proper rules set, the most important entries should be on the top of the list. You can take a look at this video by John Kitchin to see how this can work.

However, I mostly had elfeed-score to group entries to sets with equal scores, and I then processed one such set or the other. This is why I decided this package is a better fit for my workflow.

Another idea I used often before that is this function:

(defun my/elfeed-search-filter-source (entry)
  "Filter elfeed search buffer by the feed under the cursor."
  (interactive (list (elfeed-search-selected :ignore-region)))
  (when (elfeed-entry-p entry)
    (elfeed-search-set-filter
     (concat
      "@6-months-ago "
      "+unread "
      "="
      (replace-regexp-in-string
       (rx "?" (* not-newline) eos)
       ""
       (elfeed-feed-url (elfeed-entry-feed entry)))))))

I’ve bound it to o, so I would open elfeed, press o, and only see unread entries from a particular feed. Then I cleaned the filter and switched to the next feed. Once again, a tree with feeds is obviously a better tool for such a workflow.

The last solution I want to mention is elfeed-dashboard, although I didn’t test this one. It looks similar to this package but seems to require much more fine-tuning, for instance, it doesn’t allow to list all the feeds with a certain tag in a group.