;;; jao-r2e.el --- List of rss2email subscriptions -*- lexical-binding: t; -*- ;; Copyright (C) 2025 Jose Antonio Ortega Ruiz ;; Author: Jose Antonio Ortega Ruiz ;; Keywords: news ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . (require 'jao-url) (autoload 'View-quit "view") (autoload 'eww-view-source "eww") (autoload 'jao-notmuch-subtags "jao-notmuch") (defvar jao-r2e-confirm-toggle nil) (defconst jao-r2e--buffer "*r2e*") (defun jao-r2e--buffer () (with-current-buffer (get-buffer-create jao-r2e--buffer) (unless (derived-mode-p 'jao-r2e-mode) (jao-r2e-mode)) (current-buffer))) (defvar jao-r2e-mode-map (let ((map (make-keymap))) (suppress-keymap map) (define-key map [?q] 'bury-buffer) (define-key map [?n] 'next-line) (define-key map [?p] 'previous-line) (define-key map [?N] 'jao-r2e-next) (define-key map [?P] 'jao-r2e-prev) (define-key map [?g] 'jao-r2e-list) (define-key map [?s] 'jao-r2e-subscribe) (define-key map [?t] 'jao-r2e-toggle) (define-key map [?D] 'jao-r2e-delete) (define-key map [?u] 'jao-r2e-recover) map)) ;;;###autoload (defun jao-r2e-mode () "A very simple mode to show the output of r2e commands." (interactive) (kill-all-local-variables) (buffer-disable-undo) (use-local-map jao-r2e-mode-map) ;; (setq-local font-lock-defaults '(jao-r2e-font-lock-keywords)) (setq-local truncate-lines t) (setq-local next-line-add-newlines nil) (setq major-mode 'jao-r2e-mode) (setq mode-name "r2e") (read-only-mode 1)) (defun jao-r2e--do (things &optional buffer) "Execute a r2e command THINGS in the given BUFFER." (let ((b (or buffer (pop-to-buffer (jao-r2e--buffer))))) (let ((inhibit-read-only t) (cmd (format "r2e %s" things))) (unless buffer (with-current-buffer b (delete-region (point-min) (point-max)))) (with-temp-message (format "Running: %s ...." cmd) (shell-command cmd b)) (unless buffer (read-only-mode 1))))) (defconst jao-r2e--feed-rx "^\\([0-9]+\\): \\[\\( \\|\\*\\)\\] \\([^ ]+\\) (\\(.+\\) -> \\(.+\\))") (defun jao-r2e--feed-at-point () (beginning-of-line) (when-let* ((m (looking-at jao-r2e--feed-rx))) (list (match-string 1) (match-string 3) (string= "*" (match-string 2)) (match-string 4) (match-string 5)))) (defun jao-r2e () (interactive) (pop-to-buffer (jao-r2e--buffer)) (when (looking-at-p "^$") (jao-r2e-list))) (defun jao-r2e-list () (interactive) (jao-r2e--do "list")) (defun jao-r2e--srx (opp) (when-let* ((f (jao-r2e--feed-at-point))) (let ((a (if opp (not (caddr f)) (caddr f)))) (format "^[0-9]+: \\[%s\\] " (if a "\\*" " "))))) (defun jao-r2e-next (opp) "Next feed with the same (or opposite) status." (interactive "P") (when-let* ((rx (jao-r2e--srx opp))) (next-line) (when (re-search-forward rx nil t) (beginning-of-line)))) (defun jao-r2e-prev (opp) "Previous feed with the same (or opposite) status." (interactive "P") (when-let* ((rx (jao-r2e--srx opp))) (when (re-search-backward rx nil t) (beginning-of-line)))) (defun jao-r2e-toggle () (interactive) (let ((f (jao-r2e--feed-at-point))) (unless f (error "No feed at point")) (let ((p (point)) (no (car f)) (name (cadr f)) (act (if (caddr f) "pause" "unpause"))) (when (or (not jao-r2e-confirm-toggle) (yes-or-no-p (format "%s '%s'? " act name))) (with-temp-buffer (jao-r2e--do (format "%s %s" act no) (current-buffer))) (jao-r2e-list) (goto-char p))))) (persist-defvar jao-r2e-deleted-feeds '() "List of rss2email feeds deleted at some point.") (defun jao-r2e-delete () "Delete feed at point. Use `jao-r2e-recover' to undelete." (interactive) (let ((f (jao-r2e--feed-at-point))) (unless f (error "No feed at point")) (let ((p (point)) (no (car f)) (name (cadr f))) (when (yes-or-no-p (format "Delete feed '%s'" name)) (add-to-list 'jao-r2e-deleted-feeds (cdr f)) (with-temp-buffer (jao-r2e--do (format "delete %s" no) (current-buffer))) (jao-r2e-list) (goto-char p))))) (defun jao-r2e-recover () (interactive) (when (seq-empty-p jao-r2e-deleted-feeds) (error "No feeds recoverable at this point.")) (let ((feed (completing-read "Recover feed: " jao-r2e-deleted-feeds))) (when-let* ((ps (assoc feed jao-r2e-deleted-feeds)) (url (caddr ps)) (mail (car (last ps))) (cat (when (string-match "feeds\\.\\(.+\\)@localhost" mail) (match-string 1 mail)))) (jao-r2e-subscribe (list url feed) cat t) (setq jao-r2e-deleted-feeds (remove ps jao-r2e-deleted-feeds))))) (defun jao-r2e--find-url () (save-excursion (when (derived-mode-p 'w3m-mode 'eww-mode) (if (fboundp 'w3m-view-source) (w3m-view-source) (eww-view-source))) (goto-char (point-min)) (when (re-search-forward "type=\"application/\\(?:atom\\|rss\\)\\+xml\" +" nil t) (let ((url (save-excursion (when (re-search-forward "href=\"\\([^\n\"]+\\)\"" nil t) (match-string-no-properties 1)))) (title (when (re-search-forward "\\(?:title=\"\\([^\n\"]+\\)\" +\\)" nil t) (match-string-no-properties 1)))) (cond ((derived-mode-p 'w3m-view-mode) (w3m-view-source)) ((string-match-p ".*\\*eww-source\\b.*" (buffer-name)) (View-quit))) (when url (cons url (or title ""))))))) (defun jao-r2e-subscribe (url &optional cat relist) "Subscribe to a given RSS URL. If URL not given, look for it." (interactive (list (or (jao-url-around-point) (jao-r2e--find-url) (read-string "Feed URL: ")))) (let* ((url+title (ensure-list url)) (url (car url+title)) (title (cdr url+title))) (unless url (error "No feeds found")) (let ((url (if (string-match "^feed:" url) (substring url 5) url))) (when (y-or-n-p (format "Subscribe to <%s>? " url)) (let* ((name (read-string "Feed name: " title)) (cats (cons "prog" (jao-notmuch-subtags "feeds"))) (cat (completing-read "Category: " cats nil t cat)) (subs (format "r2e add %s '%s' feeds.%s@localhost" name url cat))) (with-temp-message "Subscribing..." (shell-command-to-string subs)) (with-temp-message "Retrieving feed..." (shell-command (format "r2e run %s" name))) (when relist (jao-r2e-list))))))) (provide 'jao-r2e) ;;; jao-r2e.el ends here