From ec3929a1ebdbc893567232e9027888bb27aa5d41 Mon Sep 17 00:00:00 2001 From: Jens Luedicke Date: Fri, 5 Sep 2025 14:42:17 +0200 Subject: [PATCH] Add Elfeed configuration --- elfeed-config.el | 371 +++++++++++++++++++++++++++++++++++++++++++++++ elfeed.org | 116 +++++++++++++++ init.el | 21 ++- 3 files changed, 507 insertions(+), 1 deletion(-) create mode 100644 elfeed-config.el create mode 100644 elfeed.org diff --git a/elfeed-config.el b/elfeed-config.el new file mode 100644 index 0000000..40a8a2b --- /dev/null +++ b/elfeed-config.el @@ -0,0 +1,371 @@ +;;; elfeed-config.el --- Elfeed RSS reader configuration + +;;; Code: + +;; Install required packages if not already installed +(use-package elfeed + :ensure t + :bind (("C-x w" . elfeed)) + :config + ;; Set default search filter to show entries from last 2 weeks + (setq elfeed-search-filter "@2-weeks-ago +unread") + + ;; Store database in .emacs.d + (setq elfeed-db-directory (expand-file-name "elfeed" user-emacs-directory)) + + ;; Refresh feeds on startup + (setq elfeed-search-remain-on-entry t) + + ;; Set update interval + (setq elfeed-search-title-max-width 100) + (setq elfeed-search-title-min-width 30) + + ;; Sorting configuration + (setq elfeed-sort-order 'descending) + (setq elfeed-search-clipboard-type 'CLIPBOARD) + (setq elfeed-search-date-format '("%F %R" 16 :left)) + + ;; Face customization for different tags + (defface elfeed-face-tag-news + '((t :foreground "#8B4513")) + "Face for news-tagged entries") + + (defface elfeed-face-tag-tech + '((t :foreground "#4682B4")) + "Face for tech-tagged entries") + + (defface elfeed-face-tag-security + '((t :foreground "#DC143C")) + "Face for security-tagged entries") + + (defface elfeed-face-tag-programming + '((t :foreground "#228B22")) + "Face for programming-tagged entries") + + (defface elfeed-face-tag-opensource + '((t :foreground "#FF8C00")) + "Face for opensource-tagged entries") + + ;; Apply faces to tags + (setq elfeed-search-face-alist + '((news elfeed-face-tag-news) + (tech elfeed-face-tag-tech) + (security elfeed-face-tag-security) + (programming elfeed-face-tag-programming) + (opensource elfeed-face-tag-opensource)))) + +(use-package elfeed-org + :ensure t + :after elfeed + :config + ;; Load feeds from org file + (setq rmh-elfeed-org-files (list (expand-file-name "elfeed.org" user-emacs-directory))) + (elfeed-org)) + +;; Optional: Web browser for opening links +(setq browse-url-browser-function 'browse-url-firefox) + +;; Keybindings for elfeed +(with-eval-after-load 'elfeed + (define-key elfeed-search-mode-map (kbd "j") 'next-line) + (define-key elfeed-search-mode-map (kbd "k") 'previous-line) + (define-key elfeed-search-mode-map (kbd "m") 'elfeed-search-toggle-all-star) + (define-key elfeed-search-mode-map (kbd "u") 'elfeed-search-toggle-all-unread) + (define-key elfeed-search-mode-map (kbd "U") 'elfeed-update) + (define-key elfeed-search-mode-map (kbd "f") 'elfeed-search-live-filter)) + +;; Update feeds every hour +(run-at-time 0 (* 60 60) 'elfeed-update) + +;; Sorting functions +(defun elfeed-sort-by-date-ascending () + "Sort elfeed entries by date ascending." + (interactive) + (setq elfeed-sort-order 'ascending) + (elfeed-search-update :force)) + +(defun elfeed-sort-by-date-descending () + "Sort elfeed entries by date descending." + (interactive) + (setq elfeed-sort-order 'descending) + (elfeed-search-update :force)) + +(defun elfeed-sort-by-title () + "Sort elfeed entries by title." + (interactive) + (setf elfeed-search-sort-function + (lambda (a b) + (string< (elfeed-entry-title a) + (elfeed-entry-title b)))) + (elfeed-search-update :force)) + +(defun elfeed-sort-by-feed () + "Sort elfeed entries by feed source." + (interactive) + (setf elfeed-search-sort-function + (lambda (a b) + (string< (elfeed-feed-title (elfeed-entry-feed a)) + (elfeed-feed-title (elfeed-entry-feed b))))) + (elfeed-search-update :force)) + +;; Helper function to show only specific categories +(defun elfeed-show-news () + "Show only news entries." + (interactive) + (elfeed-search-set-filter "@2-weeks-ago +unread +news")) + +(defun elfeed-show-tech () + "Show only tech entries." + (interactive) + (elfeed-search-set-filter "@2-weeks-ago +unread +tech")) + +(defun elfeed-show-security () + "Show only security entries." + (interactive) + (elfeed-search-set-filter "@2-weeks-ago +unread +security")) + +(defun elfeed-show-programming () + "Show only programming entries." + (interactive) + (elfeed-search-set-filter "@2-weeks-ago +unread +programming")) + +(defun elfeed-show-opensource () + "Show only open source entries." + (interactive) + (elfeed-search-set-filter "@2-weeks-ago +unread +opensource")) + +;; Location-based filters +(defun elfeed-show-munich () + "Show only Munich/Bavaria news." + (interactive) + (elfeed-search-set-filter "@2-weeks-ago +unread +munich")) + +(defun elfeed-show-bavaria () + "Show only Bavaria news." + (interactive) + (elfeed-search-set-filter "@2-weeks-ago +unread +bavaria")) + +(defun elfeed-show-germany () + "Show only German news." + (interactive) + (elfeed-search-set-filter "@2-weeks-ago +unread +germany")) + +(defun elfeed-show-europe () + "Show only European/EU news." + (interactive) + (elfeed-search-set-filter "@2-weeks-ago +unread +eu")) + +(defun elfeed-show-us () + "Show only US news." + (interactive) + (elfeed-search-set-filter "@2-weeks-ago +unread +us")) + +(defun elfeed-show-world () + "Show only world news." + (interactive) + (elfeed-search-set-filter "@2-weeks-ago +unread +world")) + +;; Language filters +(defun elfeed-show-german-language () + "Show only German language feeds." + (interactive) + (elfeed-search-set-filter "@2-weeks-ago +unread +de")) + +;; Programming language filters +(defun elfeed-show-cpp () + "Show only C++ entries." + (interactive) + (elfeed-search-set-filter "@2-weeks-ago +unread +cpp")) + +(defun elfeed-show-python () + "Show only Python entries." + (interactive) + (elfeed-search-set-filter "@2-weeks-ago +unread +python")) + +(defun elfeed-show-qt () + "Show only Qt entries." + (interactive) + (elfeed-search-set-filter "@2-weeks-ago +unread +qt")) + +;; Combined filters +(defun elfeed-show-today () + "Show entries from today only." + (interactive) + (elfeed-search-set-filter "@1-day-ago +unread")) + +(defun elfeed-show-starred () + "Show starred entries." + (interactive) + (elfeed-search-set-filter "+star")) + +(defun elfeed-show-all () + "Show all unread entries." + (interactive) + (elfeed-search-set-filter "@2-weeks-ago +unread")) + +(defun elfeed-mark-all-read () + "Mark all entries as read." + (interactive) + (mark-whole-buffer) + (elfeed-search-untag-all-unread)) + +;; Advanced filter prompt +(defun elfeed-filter-by-source () + "Filter by specific feed source." + (interactive) + (let* ((feeds (elfeed-feed-list)) + (titles (mapcar (lambda (url) + (or (plist-get (elfeed-db-get-feed url) :title) + url)) + feeds)) + (selected (completing-read "Select feed: " titles))) + (elfeed-search-set-filter + (format "@2-weeks-ago +unread =%s" selected)))) + +;; Custom search function +(defun elfeed-search-custom () + "Prompt for a custom search filter." + (interactive) + (let ((filter (read-string "Enter filter: " elfeed-search-filter))) + (elfeed-search-set-filter filter))) + +;; Article reading functions +(defun elfeed-show-entry-in-eww () + "Open the current elfeed entry in eww browser." + (interactive) + (let ((entry (elfeed-search-selected :single))) + (when entry + (eww (elfeed-entry-link entry)) + (add-hook 'eww-after-render-hook 'eww-readable nil t)))) + +(defun elfeed-search-eww-open (&optional use-generic-p) + "Open the current elfeed entry in eww. +If USE-GENERIC-P is non-nil, use eww-readable after loading." + (interactive "P") + (let ((entries (elfeed-search-selected))) + (cl-loop for entry in entries + do (elfeed-untag entry 'unread) + when (elfeed-entry-link entry) + do (eww (elfeed-entry-link entry))) + (when use-generic-p + (add-hook 'eww-after-render-hook 'eww-readable nil t)) + (unless (use-region-p) (forward-line)) + (elfeed-search-update-entry))) + +(defun elfeed-show-eww-open (&optional use-generic-p) + "Open the current elfeed show entry in eww. +If USE-GENERIC-P is non-nil, use eww-readable after loading." + (interactive "P") + (let ((link (elfeed-entry-link elfeed-show-entry))) + (when link + (eww link) + (when use-generic-p + (add-hook 'eww-after-render-hook 'eww-readable nil t))))) + +;; Fetch and display article content in elfeed-show buffer +(defun elfeed-show-refresh-mail-style () + "Refresh the current elfeed entry, fetching full content and displaying it." + (interactive) + (let ((link (elfeed-entry-link elfeed-show-entry))) + (when link + (message "Fetching full article content...") + (url-retrieve + link + (lambda (status) + (if (plist-get status :error) + (message "Error fetching article: %s" (plist-get status :error)) + (let ((html (buffer-string)) + (inhibit-read-only t)) + (with-current-buffer (get-buffer "*elfeed-entry*") + (erase-buffer) + (insert (format "Title: %s\n" (elfeed-entry-title elfeed-show-entry))) + (insert (format "Feed: %s\n" (elfeed-feed-title (elfeed-entry-feed elfeed-show-entry)))) + (insert (format "Date: %s\n" (format-time-string "%Y-%m-%d %H:%M" (elfeed-entry-date elfeed-show-entry)))) + (insert (format "Link: %s\n\n" link)) + (insert "--- Full Article ---\n\n") + (let ((shr-width (- (window-width) 5)) + (shr-max-image-proportion 0.7)) + (shr-render-region (point) (point-max))) + (goto-char (point-min)))))))))) + +;; Enhanced readable mode for elfeed +(defun elfeed-show-readable () + "Make the current elfeed entry more readable by extracting main content." + (interactive) + (let ((inhibit-read-only t)) + (save-excursion + (goto-char (point-min)) + (when (search-forward "\n\n" nil t) + (let ((shr-width (min 80 (- (window-width) 5))) + (shr-max-image-proportion 0.6) + (shr-use-fonts nil)) + (shr-render-region (point) (point-max))))) + (text-scale-increase 1) + (olivetti-mode 1))) + +;; Toggle between summary and full article +(defvar-local elfeed-show-full-article-p nil + "Whether the full article is currently displayed.") + +(defun elfeed-show-toggle-full-article () + "Toggle between entry summary and full article content." + (interactive) + (if elfeed-show-full-article-p + (progn + (elfeed-show-refresh) + (setq-local elfeed-show-full-article-p nil) + (message "Showing summary")) + (let ((link (elfeed-entry-link elfeed-show-entry))) + (when link + (message "Fetching full article...") + (url-retrieve + link + (lambda (status) + (if (plist-get status :error) + (message "Error fetching article: %s" (plist-get status :error)) + (let ((html (buffer-substring (point) (point-max))) + (inhibit-read-only t)) + (with-current-buffer (get-buffer "*elfeed-entry*") + (let ((pos (point))) + (erase-buffer) + (elfeed-show-refresh) + (goto-char (point-max)) + (insert "\n\n--- Full Article ---\n\n") + (let ((start (point))) + (insert html) + (shr-render-region start (point-max)) + (goto-char pos)) + (setq-local elfeed-show-full-article-p t) + (message "Showing full article"))))))))))) + +;; Open in external browser as fallback +(defun elfeed-open-in-browser () + "Open current entry in external browser." + (interactive) + (let ((entry (if (eq major-mode 'elfeed-show-mode) + elfeed-show-entry + (elfeed-search-selected :single)))) + (when entry + (browse-url (elfeed-entry-link entry))))) + +;; Bind keys for article viewing +(with-eval-after-load 'elfeed + ;; In search mode + (define-key elfeed-search-mode-map (kbd "E") 'elfeed-search-eww-open) + (define-key elfeed-search-mode-map (kbd "B") 'elfeed-open-in-browser) + + ;; In show mode + (define-key elfeed-show-mode-map (kbd "E") 'elfeed-show-eww-open) + (define-key elfeed-show-mode-map (kbd "R") 'elfeed-show-readable) + (define-key elfeed-show-mode-map (kbd "F") 'elfeed-show-toggle-full-article) + (define-key elfeed-show-mode-map (kbd "B") 'elfeed-open-in-browser)) + +;; Disable line numbers in elfeed buffers +(add-hook 'elfeed-show-mode-hook (lambda () (display-line-numbers-mode 0))) +(add-hook 'elfeed-search-mode-hook (lambda () (display-line-numbers-mode 0))) + +;; Disable line numbers in EWW +(add-hook 'eww-mode-hook (lambda () (display-line-numbers-mode 0))) + +(provide 'elfeed-config) +;;; elfeed-config.el ends here \ No newline at end of file diff --git a/elfeed.org b/elfeed.org new file mode 100644 index 0000000..a22a3d6 --- /dev/null +++ b/elfeed.org @@ -0,0 +1,116 @@ +#+TITLE: Elfeed Feed Configuration +#+STARTUP: overview + +* Feeds :elfeed: + +** News :news: + +*** München/Bavaria :munich:bavaria:de: +**** [[https://www.sueddeutsche.de/muenchen/rss][Süddeutsche Zeitung - München]] +**** [[https://www.merkur.de/lokales/muenchen/rssfeed.rdf][Merkur - München]] +**** [[https://www.tz.de/muenchen/rssfeed.rdf][tz - München]] +**** [[https://www.br.de/nachrichten/bayern/rss.xml][BR24 - Bayern]] +**** [[https://www.abendzeitung-muenchen.de/storage/rss/rss/muenchen.xml][Abendzeitung München]] + +*** Germany :germany:de: +**** [[https://www.tagesschau.de/index~rss2.xml][Tagesschau]] +**** [[https://www.spiegel.de/schlagzeilen/index.rss][Der Spiegel]] +**** [[https://www.zeit.de/index/feed][Die Zeit]] +**** [[https://www.faz.net/rss/aktuell/][FAZ - Aktuell]] +**** [[https://www.sueddeutsche.de/rss][Süddeutsche Zeitung]] +**** [[https://www.heise.de/rss/heise.rdf][Heise News]] + +*** Europe/EU :eu:europe: +**** [[https://www.politico.eu/feed/][Politico EU]] +**** [[https://www.euronews.com/rss][Euronews]] +**** [[https://www.dw.com/rss/en/european-union/rss-17498][Deutsche Welle - EU]] +**** [[https://www.theguardian.com/world/europe-news/rss][The Guardian - Europe]] +**** [[https://feeds.bbci.co.uk/news/world/europe/rss.xml][BBC News - Europe]] + +*** United States :us: +**** [[https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml][New York Times]] +**** [[https://feeds.washingtonpost.com/rss/national][Washington Post - National]] +**** [[https://feeds.npr.org/1001/rss.xml][NPR News]] +**** [[https://feeds.bbci.co.uk/news/world/us_and_canada/rss.xml][BBC News - US & Canada]] +**** [[https://www.theguardian.com/us-news/rss][The Guardian - US]] + +*** World :world: +**** [[https://feeds.bbci.co.uk/news/world/rss.xml][BBC World News]] +**** [[https://www.theguardian.com/world/rss][The Guardian - World]] +**** [[https://rss.nytimes.com/services/xml/rss/nyt/World.xml][New York Times - World]] +**** [[https://feeds.washingtonpost.com/rss/world][Washington Post - World]] +**** [[https://www.aljazeera.com/xml/rss/all.xml][Al Jazeera English]] +**** [[https://www.dw.com/rss/en/top-stories/rss-691][Deutsche Welle - World]] + +** Technology :tech: + +*** General Tech News :technews: +**** [[https://feeds.arstechnica.com/arstechnica/index][Ars Technica]] +**** [[https://www.theverge.com/rss/index.xml][The Verge]] +**** [[https://techcrunch.com/feed/][TechCrunch]] +**** [[https://www.wired.com/feed/rss][Wired]] +**** [[https://www.anandtech.com/rss][AnandTech]] +**** [[https://www.heise.de/developer/rss/news-atom.xml][Heise Developer]] +**** [[https://www.golem.de/rss.php?feed=RSS2.0][Golem.de]] +**** [[https://news.ycombinator.com/rss][Hacker News]] +**** [[https://lobste.rs/rss][Lobsters]] + +*** Open Source :opensource: +**** [[https://opensource.com/feed][Opensource.com]] +**** [[https://lwn.net/headlines/rss][LWN.net]] +**** [[https://www.linux.com/feed/][Linux.com]] +**** [[https://www.linuxtoday.com/feed][Linux Today]] +**** [[https://itsfoss.com/feed/][It's FOSS]] +**** [[https://www.phoronix.com/rss.php][Phoronix]] +**** [[https://www.omgubuntu.co.uk/feed][OMG! Ubuntu!]] +**** [[https://planet.kde.org/global/atom.xml][Planet KDE]] +**** [[https://planet.gnome.org/atom.xml][Planet GNOME]] + +** Programming :programming: + +*** C++ :cpp: +**** [[https://isocpp.org/blog/rss][ISO C++ Blog]] +**** [[https://www.fluentcpp.com/feed/][Fluent C++]] +**** [[https://www.cppstories.com/index.xml][C++ Stories]] +**** [[https://herbsutter.com/feed/][Herb Sutter]] +**** [[https://blog.tartanllama.xyz/feed.xml][Tartanllama]] +**** [[https://devblogs.microsoft.com/cppblog/feed/][Microsoft C++ Team Blog]] +**** [[https://www.reddit.com/r/cpp/.rss][Reddit - r/cpp]] + +*** Python :python: +**** [[https://realpython.com/atom.xml][Real Python]] +**** [[https://planet.python.org/rss20.xml][Planet Python]] +**** [[https://www.python.org/jobs/feed/rss/][Python.org News]] +**** [[https://pycoders.com/feed][PyCoder's Weekly]] +**** [[https://www.pythonweekly.com/feed][Python Weekly]] +**** [[https://www.reddit.com/r/Python/.rss][Reddit - r/Python]] +**** [[https://talkpython.fm/episodes/rss][Talk Python To Me]] + +*** Qt :qt: +**** [[https://www.qt.io/blog/rss.xml][Qt Blog]] +**** [[https://planet.qt.io/rss20.xml][Planet Qt]] +**** [[https://woboq.com/blog/feed][Woboq Blog]] +**** [[https://www.kdab.com/category/blogs/feed/][KDAB Blogs]] +**** [[https://www.ics.com/blog/feed][ICS Qt Blog]] + +*** General Programming :general: +**** [[https://stackoverflow.blog/feed/][Stack Overflow Blog]] +**** [[https://dev.to/feed][DEV Community]] +**** [[https://news.ycombinator.com/rss][Hacker News]] +**** [[https://www.infoq.com/feed][InfoQ]] +**** [[https://martinfowler.com/feed.atom][Martin Fowler]] +**** [[https://www.joelonsoftware.com/feed/][Joel on Software]] + +** IT Security :security: +**** [[https://krebsonsecurity.com/feed/][Krebs on Security]] +**** [[https://www.schneier.com/feed/atom/][Schneier on Security]] +**** [[https://threatpost.com/feed/][Threatpost]] +**** [[https://www.darkreading.com/rss.xml][Dark Reading]] +**** [[https://feeds.feedburner.com/TheHackersNews][The Hacker News]] +**** [[https://www.bleepingcomputer.com/feed/][BleepingComputer]] +**** [[https://www.csoonline.com/index.rss][CSO Online]] +**** [[https://nakedsecurity.sophos.com/feed/][Naked Security]] +**** [[https://www.heise.de/security/rss/news-atom.xml][Heise Security]] +**** [[https://www.golem.de/rss.php?feed=RSS2.0&ms=security][Golem Security]] +**** [[https://www.bsi.bund.de/SiteGlobals/Functions/RSSFeed/RSSNewsfeed/RSSNewsfeed.xml][BSI News]] +**** [[https://www.reddit.com/r/netsec/.rss][Reddit - r/netsec]] \ No newline at end of file diff --git a/init.el b/init.el index 18d9db1..e1b0606 100644 --- a/init.el +++ b/init.el @@ -39,7 +39,13 @@ ibuffer-sidebar ibuffer-projectile ;; Required for some functionality - org dash s f ht spinner lv hydra avy)) + org dash s f ht spinner lv hydra avy + + ;; RSS/News reader + elfeed elfeed-org + + ;; Email (mu4e installed separately) + )) ;; Auto-install missing packages (when (cl-find-if-not #'package-installed-p package-selected-packages) @@ -697,12 +703,25 @@ (when (file-exists-p dev-config) (load-file dev-config))) + ;; Reload elfeed config if it exists + (let ((elfeed-config (expand-file-name "elfeed-config.el" user-emacs-directory))) + (when (file-exists-p elfeed-config) + (load-file elfeed-config))) + + + (message "Emacs configuration fully reloaded!")) ;;; Final Keybindings (global-set-key (kbd "C-x k") 'kill-current-buffer-no-confirm) (global-set-key (kbd "C-c C-r") 'reload-emacs-config) (global-set-key (kbd "C-x C-b") 'helm-buffers-list) +;;; RSS Reader Configuration (Elfeed) +(let ((elfeed-config (expand-file-name "elfeed-config.el" user-emacs-directory))) + (when (file-exists-p elfeed-config) + (load-file elfeed-config) + (message "Elfeed RSS reader configuration loaded."))) + ;;; Development Mode Information (defun show-dev-mode-info () "Show information about development mode."