;; gnus configuration -*- lexical-binding: t -*- ;;; features (defvar jao-gnus-use-local-imap nil) (defvar jao-gnus-use-leafnode nil) (defvar jao-gnus-use-gandi-imap nil) (defvar jao-gnus-use-pm-imap nil) (defvar jao-gnus-use-gmane nil) (defvar jao-gnus-use-nnml nil) (defvar jao-gnus-use-maildirs nil) (defvar jao-notmuch-enabled nil) (defvar jao-gnus-nnml-group-params nil) ;;; directories (defun jao-gnus-dir (dir) (expand-file-name dir gnus-home-directory)) (setq smtpmail-queue-dir (jao-gnus-dir "Mail/queued-mail/")) (setq mail-source-directory (jao-gnus-dir "Mail/") message-directory (jao-gnus-dir "Mail/")) (setq gnus-default-directory (expand-file-name "~") gnus-startup-file (jao-gnus-dir "newsrc") gnus-agent-directory (jao-gnus-dir "News/agent") gnus-home-score-file (jao-gnus-dir "scores") gnus-article-save-directory (jao-gnus-dir "saved/") nntp-authinfo-file (jao-gnus-dir "authinfo") nnmail-message-id-cache-file (jao-gnus-dir "nnmail-cache") nndraft-directory (jao-gnus-dir "drafts") nnrss-directory (jao-gnus-dir "rss")) ;;; looks ;;;; verbosity (setq gnus-verbose 4) ;;;; geometry (defvar jao-gnus-use-three-panes t) (defvar jao-gnus-groups-width 50) (defvar jao-gnus-wide-width 190) (setq gnus-use-trees nil gnus-generate-tree-function 'gnus-generate-horizontal-tree gnus-tree-minimize-window nil) (when jao-gnus-use-three-panes ;; (dolist (m '(calendar-mode org-agenda-mode gnus-group-mode)) ;; (add-to-list 'display-buffer-alist `((major-mode . ,m) (dedicated t)))) (setq calendar-left-margin 6) (let ((side-bar '(vertical 1.0 ("inbox.org" 0.4) ("*Org Agenda*" 1.0) ("*Calendar*" 8))) (wide-len jao-gnus-wide-width) (groups-len jao-gnus-groups-width) (summary-len (- jao-gnus-wide-width jao-gnus-groups-width))) (gnus-add-configuration `(article (horizontal 1.0 (vertical ,groups-len (group 1.0)) (vertical ,summary-len (summary 0.25 point) (article 1.0)) ,side-bar))) (gnus-add-configuration `(group (horizontal 1.0 (group ,wide-len point) ,side-bar))) (gnus-add-configuration `(message (horizontal 1.0 (message ,wide-len point) ,side-bar))) (gnus-add-configuration `(reply-yank (horizontal 1.0 (message ,wide-len point) ,side-bar))) (gnus-add-configuration `(summary (horizontal 1.0 (vertical ,groups-len (group 1.0)) (vertical ,summary-len (summary 1.0 point)) ,side-bar))) (gnus-add-configuration `(reply (horizontal 1.0 (message ,(- wide-len 100) point) (article 100) ,side-bar))))) ;;;; no blue icon (advice-add 'gnus-mode-line-buffer-identification :override #'identity) (setq gnus-mode-line-image-cache nil) ;;; search (setq gnus-search-use-parsed-queries nil gnus-search-notmuch-raw-queries-p nil gnus-permanently-visible-groups "^nnselect:.*" gnus-search-ignored-newsgroups "nndraft.*\\|nnselect.*") (with-eval-after-load "gnus-search" (defclass gnus-search-recoll (gnus-search-indexed) ((separator :type string :initform ".") (program :initform "recoll") (raw-queries-p :initform t))) (cl-defmethod gnus-search-indexed-extract ((_engine gnus-search-recoll)) (prog1 (and (looking-at "^file://\\(.+\\)$") (list (match-string 1) 100)) (forward-line 1))) (cl-defmethod gnus-search-transform-expression ((_engine gnus-search-recoll) expr) expr) (cl-defmethod gnus-search-indexed-search-command ((engine gnus-search-recoll) (qstring string) query &optional groups) (let* ((subdir (slot-value engine 'remove-prefix)) (sep (slot-value engine 'separator)) (gdirs (mapcar (lambda (g) (let ((g (gnus-group-short-name g))) (replace-regexp-in-string "\\." sep g))) (or groups (and (not (string= "" subdir)) (list subdir))))) (dirsq (and gdirs (concat "(" (mapconcat (lambda (d) (format "dir:%s" d)) gdirs " OR ") ")"))) (qstring (if (string-prefix-p "id:" qstring) (replace-regexp-in-string "<\\|>" "\"" qstring) qstring)) (qstring (if (cdr (assoc 'thread query)) (concat qstring " OR " (replace-regexp-in-string "id:\"" "ref:\"" qstring)) qstring)) (qstring (replace-regexp-in-string " or " " OR " qstring)) (qstring (replace-regexp-in-string " and " " AND " qstring)) (q (format "mime:message %s (%s)" dirsq qstring))) ;; (message "query is: %s -- %S" q query) `("-b" "-t" "-q" ,q)))) ;; (add-to-list 'gnus-parameters '("^nnselect:.*" (nnselect-rescan . t))) ;;; news (defvar jao-gnus-leafnode-spool "/var/spool/news/") (setq gnus-select-method (cond (jao-gnus-use-leafnode `(nntp "localhost" (gnus-search-engine gnus-search-recoll (remove-prefix ,jao-gnus-leafnode-spool) (separator "/")))) (jao-gnus-use-gmane '(nntp "news.gmane.io")) (t '(nnnil "")))) (setq gnus-secondary-select-methods '()) (setq nnheader-read-timeout 0.02 gnus-save-newsrc-file nil) ; .newsrc only needed by other newsreaders ;; leafnode articles group parameters (defvar jao-gnus-image-groups '("xkcd")) (defvar jao-gnus-leafnode-group-params `((,(format "gwene\\..*%s.*" (regexp-opt jao-gnus-image-groups)) (mm-html-inhibit-images nil) (mm-html-blocked-images nil)) ("\\(gmane\\|gwene\\)\\..*" (jao-gnus--archiving-group "nnml:feeds.trove") (posting-style (address "jao@gnu.org"))))) (when jao-gnus-use-leafnode (dolist (p jao-gnus-leafnode-group-params) (add-to-list 'gnus-parameters p t))) ;;; mail ;;;; nnmail (setq nnmail-treat-duplicates 'delete nnmail-scan-directory-mail-source-once nil nnmail-cache-accepted-message-ids t nnmail-message-id-cache-length 100000 nnmail-split-fancy-with-parent-ignore-groups nil nnmail-use-long-file-names t nnmail-crosspost t nnmail-resplit-incoming t nnmail-mail-splitting-decodes t nnmail-split-methods 'nnmail-split-fancy) ;;;; nnml (setq gnus-message-archive-group nil nnml-get-new-mail t nnml-directory message-directory) (setq mail-sources (let* ((pwd (auth-source-pick-first-password :host "proton-bridge")) (mds (mapcar (lambda (f) `(maildir :path ,(expand-file-name f "~/var/mail/"))) '("local/" "feeds/"))) (ims (mapcar (lambda (b) `(imap :server "127.0.0.1" :port 1143 :user "mail@jao.io" :password ,pwd :stream starttls :predicate "1:*" :fetchflag "\\Deleted \\Seen" :mailbox ,(concat "Labels/#" b))) '("inbox" "drivel" "hacking" "bills" "bigml" "prog" "words")))) (append mds ims))) (when jao-gnus-use-nnml (add-to-list 'gnus-secondary-select-methods `(nnml "" (gnus-search-engine gnus-search-recoll (remove-prefix ,(jao-gnus-dir "Mail/")))))) (when jao-gnus-use-nnml (dolist (p jao-gnus-nnml-group-params) (add-to-list 'gnus-parameters p t))) ;;;; imap (setq nnimap-quirks nil) (when jao-gnus-use-local-imap (add-to-list 'gnus-secondary-select-methods `(nnimap "" (nnimap-address "localhost")))) (when jao-gnus-use-pm-imap (add-to-list 'gnus-secondary-select-methods '(nnimap "pm" (nnimap-address "127.0.0.1") (nnimap-stream network) (nnimap-server-port 1143)))) (when jao-gnus-use-gandi-imap (add-to-list 'gnus-secondary-select-methods '(nnimap "gandi" (nnimap-address "mail.gandi.net")))) ;;; groups (setq gnus-group-line-format " %m%S%p%3y%P%* %~(pad-right 30)G %B\n" ;; " %m%S%p%P:%~(pad-right 35)c %3y %B\n" ;; " %m%S%p%3y%P%* %~(pad-right 30)C %B\n" gnus-topic-line-format "%i[ %(%{%n%}%) -- %A ]%v\n" gnus-group-uncollapsed-levels 2 gnus-auto-select-subject 'unread gnus-large-newsgroup 2000) (add-hook 'gnus-select-group-hook 'gnus-group-set-timestamp) (add-hook 'gnus-group-mode-hook 'gnus-topic-mode) ;;; rss (setq nnrss-use-local t ;; M-x nnrss-generate-download-script nnrss-ignore-article-fields '(category dc:creator dc:date enclosure guid link media:content media:thumbnail media:title post-id pubDate slash:comments)) (add-to-list 'gnus-parameters `(,(format "nnrss:%s.*" (regexp-opt jao-gnus-image-groups t)) (mm-html-inhibit-images nil) (mm-html-blocked-images nil))) ;;; summary ;;;; configuration (setq gnus-summary-ignore-duplicates t gnus-suppress-duplicates t ;; gnus-summary-ignored-from-addresses jao-mails-regexp gnus-process-mark-toggle t gnus-auto-select-next 'almost-quietly) ;;;; threading (setq gnus-face-1 'jao-gnus-face-tree gnus-show-threads t gnus-thread-hide-subtree t gnus-build-sparse-threads nil gnus-refer-thread-use-search t gnus-summary-make-false-root 'adopt gnus-summary-gather-subject-limit nil ;; 120 gnus-summary-thread-gathering-function #'gnus-gather-threads-by-subject gnus-sort-gathered-threads-function 'gnus-thread-sort-by-date gnus-thread-sort-functions '(gnus-thread-sort-by-date)) (defun jao-fix-protonmail-references (header) (let ((references (mail-header-references header))) (setf (mail-header-references header) (mapconcat #'(lambda (x) (if (string-match "protonmail.internalid" x) "" x)) (gnus-split-references references) " ")) header)) (setq gnus-alter-header-function 'jao-fix-protonmail-references) ;;;; search on enter nnselect (defun jao-gnus--maybe-reselect (&rest _i) (when (string-match-p "^nnselect" (or (gnus-group-name-at-point) "")) (save-excursion (gnus-group-get-new-news-this-group)))) (advice-add 'gnus-group-select-group :before #'jao-gnus--maybe-reselect) ;;;; summary line (setq gnus-not-empty-thread-mark ?↓) ; ↓) ?· (setq jao-gnus--summary-line-fmt (concat "%%U %%*%%R %%uj " "[ %%~(max-right 23)~(pad-right 23)uf " " %%I%%~(pad-left 2)t ] %%s" "%%-%s=" "%%~(max-right 8)~(pad-left 8)&user-date;" "\n")) (defun jao-gnus--set-summary-line (&optional w) (let* ((d (if jao-gnus-use-three-panes (+ jao-gnus-groups-width 11) 12)) (w (- (or w (window-width)) d))) (setq gnus-summary-line-format (format jao-gnus--summary-line-fmt w)))) (add-hook 'gnus-select-group-hook 'jao-gnus--set-summary-line) ;; (jao-gnus--set-summary-line 187) (add-to-list 'nnmail-extra-headers 'Cc) (add-to-list 'nnmail-extra-headers 'BCc) (use-package gnus-sum :config (add-to-list 'gnus-extra-headers 'Cc) (add-to-list 'gnus-extra-headers 'BCc)) (defun gnus-user-format-function-j (headers) (let ((to (gnus-extra-header 'To headers))) (if (string-match jao-mails-regexp to) (if (string-match "," to) "¬" "»") ;; "~" "=") (if (or (string-match jao-mails-regexp (gnus-extra-header 'Cc headers)) (string-match jao-mails-regexp (gnus-extra-header 'BCc headers))) "¬" ;; "~" " ")))) (defconst jao-gnus--news-rx (concat (regexp-opt '("ElDiaro.es " "ElDiario.es - ElDiario.es: " "The Guardian: " "Aeon | a world of ideas: " "Planet Debian: ")) "\\|The Conversation – Articles (.+): " "\\|unofficial mirror of [^:]+: " "\\|[gq].+ updates on arXiv.org: ")) (defun gnus-user-format-function-f (headers) (let* ((from (gnus-header-from headers)) (from (gnus-summary-extract-address-component from)) (from (replace-regexp-in-string jao-gnus--news-rx "" from))) from)) (setq gnus-user-date-format-alist '(((gnus-seconds-today) . "%H:%M") ((+ 86400 (gnus-seconds-today)) . "'%H:%M") ;; (604800 . "%a %H:%M") ;; that's one week ((gnus-seconds-month) . "%a %d") ((gnus-seconds-year) . "%b %d") (t . "%b '%y"))) ;;;; moving messages around (defvar-local jao-gnus--spam-group nil) (defvar-local jao-gnus--archiving-group nil) (defvar-local jao-gnus--archive-as-copy-p nil) (defvar jao-gnus--last-move nil) (defun jao-gnus-move-hook (a headers c to d) (setq jao-gnus--last-move (cons to (mail-header-id headers)))) (defun jao-gnus-goto-last-moved () (interactive) (when jao-gnus--last-move (when (eq major-mode 'gnus-summary-mode) (gnus-summary-exit)) (gnus-group-goto-group (car jao-gnus--last-move)) (gnus-group-select-group) (gnus-summary-goto-article (cdr jao-gnus--last-move) nil t))) (add-hook 'gnus-summary-article-move-hook 'jao-gnus-move-hook) (defun jao-gnus-archive (follow) (interactive "P") (if jao-gnus--archiving-group (progn (if (or jao-gnus--archive-as-copy-p (not (gnus-check-backend-function 'request-move-article gnus-newsgroup-name))) (gnus-summary-copy-article nil jao-gnus--archiving-group) (gnus-summary-move-article nil jao-gnus--archiving-group)) (when follow (jao-gnus-goto-last-moved))) (gnus-summary-mark-as-read) (gnus-summary-delete-article))) (defun jao-gnus-archive-tickingly () (interactive) (gnus-summary-tick-article) (jao-gnus-archive) (when jao-gnus--archive-as-copy-p (gnus-summary-mark-as-read))) (defun jao-gnus-show-tickled () (interactive) (gnus-summary-limit-to-marks "!")) (make-variable-buffer-local (defvar jao-gnus--trash-group nil)) (defun jao-gnus-trash () (interactive) (gnus-summary-mark-as-read) (if jao-gnus--trash-group (gnus-summary-move-article nil jao-gnus--trash-group) (gnus-summary-delete-article))) (defun jao-gnus-move-to-spam () (interactive) (gnus-summary-mark-as-read) (gnus-summary-move-article nil jao-gnus--spam-group)) (define-key gnus-summary-mode-map "Ba" 'jao-gnus-archive) (define-key gnus-summary-mode-map "BA" 'jao-gnus-archive-tickingly) (define-key gnus-summary-mode-map "Bl" 'jao-gnus-goto-last-moved) (define-key gnus-summary-mode-map (kbd "B DEL") 'jao-gnus-trash) (define-key gnus-summary-mode-map (kbd "B ") 'jao-gnus-trash) (define-key gnus-summary-mode-map "Bs" 'jao-gnus-move-to-spam) (define-key gnus-summary-mode-map "/!" 'jao-gnus-show-tickled) (define-key gnus-summary-mode-map [f7] 'gnus-summary-force-verify-and-decrypt) ;;;; saving emails (setq gnus-default-article-saver 'gnus-summary-save-article-mail) (defvar jao-gnus-file-save-directory (expand-file-name "~/tmp")) (defun jao-gnus-file-save (newsgroup headers &optional last-file) (expand-file-name (format "%s.eml" (mail-header-subject headers)) jao-gnus-file-save-directory)) (setq gnus-mail-save-name 'jao-gnus-file-save) ;;;; arXiv capture (use-package org-capture :config (add-to-list 'org-capture-templates '("X" "arXiv" entry (file "notes/physics/arxiv.org") "* %:subject\n\n(jao-gnus-org-paragraph \"%i\")" :immediate-finish t) t) (org-capture-upgrade-templates org-capture-templates)) (defvar jao-gnus-org-url nil) (defun jao-gnus-org-paragraph (x) (with-temp-buffer (insert " " (string-trim (or x "")) "\n ") (goto-char 0) (fill-paragraph) (goto-char (point-max)) (open-rectangle 0 (point)) (concat (buffer-string) "\n " (or jao-gnus-org-url "")))) (defun jao-gnus-arXiv-capture () (interactive) (unless (derived-mode-p '(gnus-summary-mode)) (gnus-article-show-summary)) (setq jao-subject (gnus-summary-article-subject)) (gnus-summary-select-article-buffer) (gnus-article-goto-part 0) (setq-local transient-mark-mode 'lambda) (set-mark (point)) (forward-paragraph) (save-excursion (when (re-search-forward "^Link" nil t) (beginning-of-line) (setq jao-gnus-org-url (org-eww-url-below-point)))) (org-capture nil "X") (set-mark (point)) (gnus-article-show-summary)) ;;; article ;;;; config, headers (setq mail-source-delete-incoming t) (setq gnus-gcc-mark-as-read t) (setq gnus-treat-display-smileys nil) (setq gnus-treat-fill-long-lines nil) (setq gnus-treat-fill-article 120) (setq gnus-treat-fold-headers nil) (setq gnus-treat-strip-leading-blank-lines t) (setq gnus-article-auto-eval-lisp-snippets nil) (setq gnus-posting-styles '((".*" (name "Jose A. Ortega Ruiz")))) (setq gnus-single-article-buffer nil) (setq gnus-article-update-lapsed-header 60) (setq gnus-article-update-date-headers 60) (with-eval-after-load "gnus-art" (setq gnus-visible-headers (concat gnus-visible-headers "\\|^List-[iI][Dd]:\\|^X-Newsreader:\\|^X-Mailer:" "\\|^User-Agent:\\|^X-User-Agent:\\|^X-RSS-Feed:"))) ;;;; html and images (setq gnus-button-url 'browse-url-generic gnus-inhibit-images t mm-discouraged-alternatives nil ;; '("text/html" "text/richtext") mm-inline-large-images 'resize) (defvar-local jao-gnus--images nil) (defun jao-gnus--init-images () (with-current-buffer gnus-article-buffer (setq jao-gnus--images nil))) (add-hook 'gnus-select-article-hook #'jao-gnus--init-images) (defun jao-gnus-browse-html () (interactive) (let ((browse-url-browser-function jao-browse-url-external-function) (browse-url-handlers nil) (browse-url-default-handlers nil)) (gnus-article-browse-html-article))) (defun jao-gnus-show-images () (interactive) (if window-system (save-window-excursion (gnus-summary-select-article-buffer) (save-excursion (if (and jao-afio-use-w3m (fboundp 'w3m-toggle-inline-images)) (w3m-toggle-inline-images) (setq jao-gnus--images (not jao-gnus--images)) (if jao-gnus--images (gnus-article-show-images) (gnus-article-remove-images))))) (jao-gnus-browse-html))) ;;;; format from: (defvar jao-gnus--from-rx (concat "From: \\\"?\\( *" jao-gnus--news-rx "\\)")) (defun jao-gnus-format-from () (save-excursion (goto-char (point-min)) (when (re-search-forward jao-gnus--from-rx nil t) (replace-match "" nil nil nil 1)))) (add-hook 'gnus-part-display-hook 'jao-gnus-format-from) ;;;; follow links and enclosures (defun jao-gnus-follow-link (&optional external) (interactive "P") (when (eq major-mode 'gnus-summary-mode) (gnus-summary-select-article-buffer)) (save-excursion (goto-char (point-min)) (when (or (search-forward-regexp "^Via: h" nil t) (search-forward-regexp "^URL: h" nil t) (and (search-forward-regexp "^Link$" nil t) (not (beginning-of-line)))) (cond (external (jao-browse-with-external-browser)) ((featurep 'jao-custom-eww) (eww (jao-url-around-point))) (t (browse-url (jao-url-around-point))))))) (defun jao-gnus-from-eww (keep-eww-buffer) (interactive "P") (unless keep-eww-buffer (jao-eww-close)) (jao-afio-goto-mail) (gnus-article-show-summary)) (with-eval-after-load 'eww (define-key eww-mode-map (kbd "h") #'jao-gnus-from-eww)) (defun jao-gnus-open-enclosure () (interactive) (save-window-excursion (gnus-summary-select-article-buffer) (save-excursion (goto-char (point-min)) (let ((offset (or (and (search-forward-regexp "^Enclosure: " nil t) 2) (and (search-forward-regexp "^Enclosure$" nil t) -2)))) (when offset (forward-char offset)) (if-let ((url (jao-url-around-point))) (jao-mpc-add-or-play-url url) (error "No enclosure found")))))) ;;;; delayed messages (require 'gnus-util) (gnus-delay-initialize) (setq gnus-delay-default-delay "3h") (eval-after-load "message" '(setq message-draft-headers (remove 'Date message-draft-headers))) ;;; daemon and exit (setq gnus-interactive-exit t) (defun jao-quit-gnus () (gnus-group-exit) t) (add-hook 'kill-emacs-query-functions #'jao-quit-gnus) ;; daemon config (setq mail-user-agent 'gnus-user-agent) (setq gnus-asynchronous t) (setq gnus-use-article-prefetch nil) (setq gnus-save-killed-list nil) (setq gnus-check-new-newsgroups nil) (require 'gnus-demon) (defun jao-gnus--scan () (let ((inhibit-message t)) (gnus-demon-scan-news) (jao-gnus--notify))) (defun jao-gnus-add-demon () (gnus-demon-add-handler 'jao-gnus--scan 5 1)) (jao-gnus-add-demon) (gnus-demon-init) ;; this is, in theory, not needed; but at some point in the way to emacs ;; version 31 this idle timers have ceased to work after a sleep/awake cycle (add-to-list 'jao-sleep-awake-functions #'jao-gnus-add-demon) ;;; add-ons ;;;; notifications ;;;;; minibuffer (defvar jao-gnus-tracked-groups (let ((feeds (thread-first (directory-files mail-source-directory nil "feeds\\.[^e]") (seq-difference '("feeds.trove"))))) `(("nnml:bigml\\.inbox" "B" jao-themes-f00) ("nnml:bigml\\.alba" "A" jao-themes-f00) ("nnml:bigml\\.bugs" "b" jao-themes-error) ("nnml:bigml\\.support" "S" default) ("nnml:jao\\.\\(inbox\\|trove\\)" "I" jao-themes-f01) ("nnml:bigml\\.[^aibs]" "W" jao-themes-dimm) ("nnml:jao.hacking" "H" jao-themes-dimm) ("nnml:jao.write" "W" jao-themes-error) ("nnml:jao.[^isthw]" "J" jao-themes-dimm) (,(format "^nnml:%s" (regexp-opt feeds)) "F" jao-themes-dimm) ("feeds\\.e" "E" jao-themes-dimm) ("nnml:local" "l" jao-themes-dimm) ("nnrss:.*" "R" jao-themes-dimm) ("^\\(gwene\\|gmane\\)\\." "N" jao-themes-dimm)))) (defun jao-gnus--unread-counts () (seq-reduce (lambda (r g) (let ((n (gnus-group-unread (car g)))) (if (and (numberp n) (> n 0)) (cons (cons (car g) n) r) r))) gnus-newsrc-alist ())) (defun jao-gnus--unread-label (counts rx label face) (let ((n (seq-reduce (lambda (n c) (if (string-match-p rx (car c)) (+ n (cdr c)) n)) counts 0))) (when (> n 0) `(:propertize ,(format "%s%d " label n) face ,face)))) (defvar jao-gnus--notify-strs ()) (defun jao-gnus--notify-strs () (let ((counts (jao-gnus--unread-counts))) (seq-filter #'identity (seq-map (lambda (args) (apply 'jao-gnus--unread-label counts args)) jao-gnus-tracked-groups)))) (defun jao-gnus--notify () (setq jao-gnus--notify-strs (jao-gnus--notify-strs)) (jao-minibuffer-refresh)) (with-eval-after-load "jao-minibuffer" (jao-minibuffer-add-variable 'jao-gnus--notify-strs -20)) (add-hook 'gnus-started-hook #'jao-gnus--notify) ;; (add-hook 'gnus-summary-exit-hook #'jao-gnus--notify) (add-hook 'gnus-after-getting-new-news-hook #'jao-gnus--notify) ;;;;; agenda and other updates on summary exit (let ((exit-count 0)) (defun jao-gnus--on-summary-exit () (when (> (setq exit-count (+ 1 exit-count)) 20) (setq exit-count 0) (jao-org-agenda)) (jao-gnus--notify))) (add-hook 'gnus-summary-exit-hook #'jao-gnus--on-summary-exit) ;;;; open mail file in gnus (defun jao-gnus-file-to-group (file &optional maildir newsdir m-server n-server) "Compute the Gnus group name from the given file name. IN: /home/jao/.emacs.d/gnus/Mail/jao.trove/32, /home/jao/.emacs.d/gnus/Mail/ OUT: nnml:jao.trove " (let* ((maildir (or maildir message-directory)) (newsdir (or newsdir jao-gnus-leafnode-spool)) (m-server (or m-server "nnml")) (n-server (or n-server "nntp+localhost")) (nntp (and newsdir (string-match-p newsdir file))) (g (directory-file-name (file-name-directory file))) (g (replace-regexp-in-string (file-name-as-directory maildir) "" g)) (g (replace-regexp-in-string (file-name-as-directory newsdir) "" g)) (g (cond (nntp (concat n-server ":" g)) ((file-name-directory g) (replace-regexp-in-string "^\\([^/]+\\)/" (concat m-server ":\\1/") (file-name-directory g) t)) (t (concat m-server ":" g)))) (g (replace-regexp-in-string "/" "." g)) (g (replace-regexp-in-string "[/.]$" "" g))) (cond ((string-match ":$" g) (concat g "inbox")) (nntp g) (t (replace-regexp-in-string ":\\." ":" g))))) (defun jao-gnus-goto-file (filename &optional _page) (let ((group (jao-gnus-file-to-group filename)) (id (file-name-nondirectory filename))) (if (and group id) (org-gnus-follow-link group id) (message "Couldn't get relevant info for switching to Gnus.")))) ;;;; afio (defun jao-gnus--on-afio-switch () (when (derived-mode-p 'gnus-group-mode) (let ((no (or (gnus-group-unread (gnus-group-group-name)) 0))) (unless (> no 0) (gnus-group-first-unread-group))))) (add-hook 'jao-afio-switch-hook #'jao-gnus--on-afio-switch) (defun jao-gnus-refresh-workspace () (interactive) (save-window-excursion (calendar) (jao-org-agenda))) ;;;; gnus-icalendar (require 'ol-gnus) (use-package gnus-icalendar :demand t :init (setq gnus-icalendar-org-capture-file (expand-file-name "inbox.org" org-directory) gnus-icalendar-org-capture-headline '("Appointments")) :config (gnus-icalendar-org-setup)) ;;;; bbdb (with-eval-after-load "bbdb" ;; (bbdb-initialize 'gnus 'message 'pgp) (bbdb-mua-auto-update-init 'gnus) (with-eval-after-load "gnus-sum" (define-key gnus-summary-mode-map ":" 'bbdb-mua-annotate-sender) (define-key gnus-summary-mode-map ";" 'bbdb-mua-annotate-recipients))) ;;;; randomsig (with-eval-after-load "randomsig" (with-eval-after-load "gnus-sum" (define-key gnus-summary-save-map "-" 'gnus/randomsig-summary-read-sig))) ;;;; recoll (unless jao-notmuch-enabled (with-eval-after-load "org" (org-link-set-parameters "message" :follow #'jao-gnus-goto-file)) (with-eval-after-load "consult-recoll" (add-to-list 'consult-recoll-open-fns '("message/rfc822" . jao-gnus-goto-file)))) ;;; keyboard shortcuts (define-key gnus-article-mode-map "i" 'jao-gnus-show-images) (define-key gnus-summary-mode-map "i" 'jao-gnus-show-images) (define-key gnus-article-mode-map "\M-g" 'jao-gnus-follow-link) (define-key gnus-summary-mode-map "\M-g" 'jao-gnus-follow-link) (define-key gnus-summary-mode-map "v" 'scroll-other-window) (define-key gnus-summary-mode-map "V" 'scroll-other-window-down) (define-key gnus-summary-mode-map "X" 'jao-gnus-arXiv-capture) (define-key gnus-summary-mode-map "e" 'jao-gnus-open-enclosure) (define-key gnus-summary-mode-map "\C-l" nil) (define-key gnus-group-mode-map "a" 'jao-gnus-refresh-workspace)