分享一些提升 elfeed 使用体验的方法

我在 Emacs 里用 skeeto/elfeed: An Emacs web feeds client 结合 remyhonig/elfeed-org: Configure the Elfeed RSS reader with an Orgmode file 来阅读订阅流。


elfeed-org 用一个 org 文件管理所有的订阅流,但是记录的订阅流链接往往都是 XML 页面链接, 我更希望点击的时候是跳转到博客主页而不是 XML 页面,所以我写了个方法, 从 XML 链接中解析域名后再跳转。

效果:

(如果 gif 图看不清,可以右键在新 tab 打开)

elfeed-link


elfeed 本身有丰富的过滤条件,我最常用的是过滤订阅流的名字,但现在我有 300 多个订阅流,我记不住那么名字,所以结合 consult,写个了 consult-elfeed 方法,读取所有订阅流的名字作为选项,然后将它们应用为过滤条件,同时更新 elfeed,这样我就可以选一个订阅流并预览它们的文章列表了。

效果: consult-elfeed


完整代码
;;; init-elfeed.el --- elfeed config -*- lexical-binding: t -*-
;;; Commentary:
;;; Code:



(defconst spike-leung/elfeed-org-files "~/.emacs.d/elfeed.org"
  "My elfeed org files path.")



(use-package elfeed
  :custom
  (elfeed-search-filter "@3-months-ago +unread +default"))

(use-package elfeed-org
  :hook ((after-init . elfeed-org))
  :init (setq rmh-elfeed-org-files (list spike-leung/elfeed-org-files)))

(use-package elfeed-autotag
  :hook (after-init . elfeed-autotag)
  :init
  (setq elfeed-autotag-files (list spike-leung/elfeed-org-files)))



(defun spike-leung/org-open-rss-feed-as-site-in-elfeed-org-files (orig-fun &rest args)
  "Advice for `org-open-at-point' to redirect RSS links only in a specific file."
  (let* ((element (org-element-context))
         (link (and (eq (org-element-type element) 'link)
                    (org-element-property :raw-link element))))
    (if (and buffer-file-name
             (string-equal (expand-file-name (buffer-file-name))
                           (expand-file-name spike-leung/elfeed-org-files))
             link
             (string-match-p (rx (or "rss" "feed" "atom" "xml")) link))
        (let* ((url-parts (url-generic-parse-url link))
               (scheme (url-type url-parts))
               (host (url-host url-parts))
               (site-url (concat scheme "://" host)))
          (message "Opening site for feed: %s" site-url)
          (browse-url site-url))
      (apply orig-fun args))))

(advice-add 'org-open-at-point :around #'spike-leung/org-open-rss-feed-as-site-in-elfeed-org-files)


(defconst spike-leung/elfeed-search-filter "@3-months-ago +unread"
  "Query string filtering shown entries.")

(defun spike-leung/get-feed-candidates (&optional level)
  "Extract headings title from `rmh-elfeed-org-files' as consult candidates.
If LEVEL exist, filter heading which level is greater or equal LEVEL."
  (mapcan
   (lambda (elfeed-org-file)
     (with-current-buffer (or (find-buffer-visiting elfeed-org-file)
                              (find-file-noselect elfeed-org-file))
       (delq nil
             (org-element-map (org-element-parse-buffer 'headline) 'headline
               (lambda (hl)
                 ;; property 的值可以在这里找: https://orgmode.org/worg/dev/org-element-api.html
                 (when (or (null level) (>= (org-element-property :level hl) level))
                   (let* ((raw-title (org-element-property :raw-value hl))
                          (title (org-link-display-format raw-title))
                          (annotation (org-entry-get hl "description"))
                          (feed-url (when (string-match org-link-bracket-re raw-title)
                                      (match-string 1 raw-title))))
                     (list :items (list title) :feed-url feed-url :annotation annotation))))
               nil))))
   rmh-elfeed-org-files))

(defun spike-leung/elfeed-preview-state (state candidate)
  "Return consult state function for live `elfeed' preview.
See `consult--with-preview' about STATE and CANDIDATE."
  (let* ((cand (car candidate))
         (metadata (cdr candidate))
         (feed-url (plist-get metadata :feed-url)))
    (pcase state
      ('setup
       (unless (get-buffer "*elfeed-search*")
         (elfeed-apply-hooks-now)
         (elfeed-org)
         (elfeed)
         (elfeed-search-clear-filter))
       (display-buffer "*elfeed-search*" '(display-buffer-reuse-window)))
      ('preview
       (elfeed-search-clear-filter)
       (when (and cand (get-buffer "*elfeed-search*"))
         (unless (or (string-empty-p cand) (null cand))
           (elfeed-search-set-filter (concat spike-leung/elfeed-search-filter " =" (string-replace " " "." cand))))))
      ('return
       (unless (or (string-empty-p cand) (null cand))
         (elfeed-search-set-filter (concat spike-leung/elfeed-search-filter " =" (string-replace " " "." cand)))
         (elfeed-update-feed feed-url))))))

(defun spike-leung/consult-elfeed ()
  "Select feed from `rmh-elfeed-org-files' with live preview in `elfeed'."
  (interactive)
  (let* ((candidates (spike-leung/get-feed-candidates 3)))
    (consult--multi candidates
                    :prompt "Feed: "
                    :state #'spike-leung/elfeed-preview-state
                    :history 'spike-leung/consult-elfeed-history
                    :annotate (lambda (cand)
                                (let* ((match-cand (seq-find
                                                    (lambda (v)
                                                      (string-match-p (car (plist-get v :items)) cand))
                                                    candidates))
                                       (annotation (and match-cand (plist-get match-cand :annotation))))
                                  (when annotation
                                    (concat (make-string 25 ?\s) annotation)))))
    (when (get-buffer "*elfeed-search*")
      (pop-to-buffer "*elfeed-search*"))))



(provide 'init-elfeed)
;;; init-elfeed.el ends here

代码逻辑的解释记录在博客里: 在 Emacs 中用 Elfeed 阅读订阅流

更新(具体请看博客 :backhand_index_pointing_up:):

  • 添加了一个切换已读/未读的方法
  • 添加了一个利用 translate.kagi.com 翻译的方法

如果你也用 elfeed,希望对你有帮助啦~

elfeed在多设备之间使用起来很难受,所以我还是自建了miniflux

1 个赞

确实,尤其是移动端= =

300多个?可以分享下feeds吗

在这:Spikemacs/elfeed.org

都是看到一些文章之后添加的订阅,但常看的也就几个,300 多个订阅几千条文章信息实在看不过来。有空要清理掉一些。

如果你打算从里头挑一些的话,慎重一些添加订阅,不然可能订阅了太多而产生阅读焦虑,或者订阅了基本不看。

1 个赞

我想减少屏幕使用时间,所以直接在墨水屏设备上面看了。

elfeed 我之前用来订阅 ins 来看非常的舒服,视频和图片都是原尺寸展现,非常的震撼,而且还没有ads,可惜的是ins的反爬非常的严格。

karthink 的 elfeed-tude 之前用来看字幕以及同步视频也很不错,可惜后来架构变化拉不下来了,我还没看什么问题。

1 个赞

墨水屏看着应该很舒服。

用过几天的 elfeed, 不错

可惜当时 immersive-translate 或者 gt 翻译都没法做到直接在每行下面显示翻译结果

于是我就放弃了,转而找了一个 miniflux 来用

miniflux 也有一个相关的扩展:

1 个赞

gt.el 可以按照不同层级翻译的,很好用,能达到翻译显示在下方 overlay。