Skip to content
Daniel Mendler edited this page May 19, 2026 · 8 revisions

Handy helpers

Extract feed URL from website

From https://github.com/emacs-elfeed/elfeed/issues/249

I couldn't find a function to do this, so I came up with this. It uses the s, org-web-tools, and esxml packages. I don't know if it is something you'd want to include in Elfeed (the dependencies could be eliminated, of course...and I should have used Elfeed's XML library, haha), but in case you or users might find it useful, here it is:

(cl-defun ap/feed-for-url (url &key (prefer 'atom) (all nil))
    "Return feed URL for web page at URL.
PREFER may be `atom' (the default) or `rss'.  When ALL is
non-nil, all feed URLs of all types are returned; otherwise only
one feed URL of the preferred type is returned.  When called
interactively, insert the URL at point."
    ;; TODO: Add domain if URL is relative?
    (interactive (list (org-web-tools--get-first-url)))
    (cl-flet ((add-protocol (protocol url)
                            ;; A feed URL might elide the protocol, like "//domain/index.rss", so we add
                            ;; it if necessary.
                            (unless (s-starts-with? protocol url)
                              (setq url (concat protocol
                                                (if (s-starts-with? "//" url)
                                                    ":"
                                                  "://")
                                                url)))
                            url))
      (let* ((protocol (second (s-match (rx (group "http" (optional "s")) "://")
                                        url)))
             (preferred-type (format "application/%s+xml" (symbol-name prefer)))
             (html (org-web-tools--get-url url))
             (dom (with-temp-buffer
                    (insert html)
                    (libxml-parse-html-region (point-min) (point-max))))
             (potential-feeds (esxml-query-all "link[rel=alternate]" dom))
             (return (if all
                         ;; Return all URLs
                         (cl-loop for (tag attrs) in potential-feeds
                                  collect (add-protocol protocol (alist-get 'href attrs)))
                       ;; Return the first URL of preferred type
                       (cl-loop for (tag attrs) in potential-feeds
                                when (equal preferred-type (alist-get 'type attrs))
                                return (add-protocol protocol (alist-get 'href attrs))))))
        (if (called-interactively-p)
            (insert (if (listp return)
                        (s-join " " return)
                      return))
          return))))

;; Use like:
;; (ap/feed-for-url "http://soylentnews.org/")
;; (ap/feed-for-url "http://soylentnews.org/" :prefer 'rss)
;; (ap/feed-for-url "http://soylentnews.org/" :all t)

Fetch new feeds

(defun my/elfeed-fetch-new-feeds ()
  "Update all feeds with zero entries."
  (interactive)
  (when (= 0 (elfeed-queue-count-total))
    (let ((all-feeds (make-hash-table :test 'equal)))
      (dolist (feed (elfeed-feed-list))
	(setf (gethash feed all-feeds) t))
      (with-elfeed-db-visit (entry feed)
	(remhash (elfeed-feed-url feed) all-feeds))
      (maphash (lambda (url _value)
		 (when url
		   (elfeed-update-feed url t)))
	       all-feeds)
      (run-hooks 'elfeed-update-init-hooks)
      (elfeed-db-save))))

Navigate to next/previous entry in the same feed

If standard date sorting is used, you cannot easily move to the previous or next entry from the same feed. The commands given below serve this purpose. As an alternative you can group by feed by setting (setq elfeed-search-sort-function #'elfeed-search-group-by-feed).

Elfeed Search

(defun my/elfeed-search-previous-from-feed (entry)
  (interactive (list (elfeed-search-selected t))
	       elfeed-search-mode)
  (let ((feed (elfeed-entry-feed entry))
	(end-pos (point)))
    (save-excursion
      (forward-line -1)
      (while (and (not (equal feed (elfeed-entry-feed (elfeed-search-selected t))))
		  (not (bobp)))
	(forward-line -1))
      (if (equal (point) end-pos)
	  (user-error "Beginning of buffer"))
      (if (equal feed (elfeed-entry-feed (elfeed-search-selected t)))
	  (setf end-pos (point))
	(user-error "No previous entry from feed that matches filter")))
    (goto-char end-pos)))

(defun my/elfeed-search-next-from-feed (entry)
  (interactive (list (elfeed-search-selected t))
	       elfeed-search-mode)
  (let ((feed (elfeed-entry-feed entry))
	(end-pos (point)))
    (if (save-excursion (forward-line) (eobp))
	(user-error "End of buffer")
      (save-excursion
	(forward-line)
	(while (and (not (eobp))
		    (not (equal feed (elfeed-entry-feed (elfeed-search-selected t)))))
	  (forward-line))
	(if (and (elfeed-search-selected t)
		 (equal feed (elfeed-entry-feed (elfeed-search-selected t))))
	    (setf end-pos (point))
	  (user-error "No next entry from feed that matches filter")))
      (goto-char end-pos))))

Elfeed Show

(defun my/elfeed-show-previous-from-feed (entry)
   "Display the previous entry from the same feed as ENTRY."
   (interactive (list elfeed-show-entry)
		elfeed-show-mode)
   (let* ((current-entry-seen-p)
	  (entry-to-show (with-elfeed-db-visit (db-entry feed)
			   (when (equal feed (elfeed-entry-feed entry))
			     (if current-entry-seen-p
				 (elfeed-db-return db-entry)
			       (when (equal entry db-entry)
				 (setf current-entry-seen-p t)))))))
     (if entry-to-show
	 (elfeed-show-entry entry-to-show)
       (user-error "No previous entry from this feed"))))

 (defun my/elfeed-show-next-from-feed (entry)
   "Display the next entry from the same feed as ENTRY."
   (interactive (list elfeed-show-entry)
		elfeed-show-mode)
   (let* ((most-recent-entry)
	  (entry-to-show (with-elfeed-db-visit (db-entry feed)
			   (when (equal feed (elfeed-entry-feed entry))
			     (when (equal entry db-entry)
			       (elfeed-db-return most-recent-entry))
			     (setf most-recent-entry db-entry)))))
     (if entry-to-show
	 (elfeed-show-entry entry-to-show)
       (user-error "No next entry from this feed"))))