summaryrefslogtreecommitdiffhomepage
path: root/custom/jao-custom-gnus.el
diff options
context:
space:
mode:
Diffstat (limited to 'custom/jao-custom-gnus.el')
-rw-r--r--custom/jao-custom-gnus.el830
1 files changed, 830 insertions, 0 deletions
diff --git a/custom/jao-custom-gnus.el b/custom/jao-custom-gnus.el
new file mode 100644
index 0000000..b3e1cb6
--- /dev/null
+++ b/custom/jao-custom-gnus.el
@@ -0,0 +1,830 @@
+;; 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"))
+
+(setq gnus-uncacheable-groups "^nnml")
+
+;;; private config
+(require 'jao-gnus-private nil t)
+
+;;; looks
+;;;; verbosity
+(setq gnus-verbose 4)
+;;;; geometry
+(defvar jao-gnus-use-three-panes (not jao-notmuch-enabled))
+(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)
+
+(setq calendar-left-margin 6)
+
+(defun jao-gnus-use-three-panes ()
+ (let ((side-bar `(vertical 1.0
+ ("inbox.org" 0.4)
+ ("*Org Agenda*" 1.0)
+ ("*Calendar*" ,(jao-d-l 9 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)))))
+
+(defun jao-gnus-use-two-panes ()
+ (let ((groups-len jao-gnus-groups-width)
+ (summary-len (- jao-gnus-wide-width jao-gnus-groups-width))
+ (msg-edit '(horizontal 1.0
+ (message 1.0 point)
+ (vertical 0.5
+ ("*Org Agenda*" 0.5)
+ ("inbox.org" 1.0)))))
+ (gnus-add-configuration
+ `(article
+ (horizontal 1.0
+ (vertical ,groups-len (group 1.0))
+ (vertical 1.0
+ (summary 0.25 point)
+ (article 1.0)))))
+
+ (gnus-add-configuration
+ `(group (horizontal 1.0
+ (group 0.5 point)
+ (vertical 1.0
+ ("*Org Agenda*" 1.0)
+ ("*Calendar*" 9)))))
+
+ (gnus-add-configuration
+ `(summary
+ (horizontal 1.0
+ (vertical ,groups-len (group 1.0))
+ (vertical 1.0 (summary 1.0 point)))))
+
+ (gnus-add-configuration `(message ,msg-edit))
+
+ (gnus-add-configuration `(forward ,msg-edit))
+
+ (gnus-add-configuration `(reply-yank ,msg-edit))
+
+ (gnus-add-configuration
+ `(reply (horizontal 1.0 (message 0.5 point) (article 1.0))))))
+
+(if jao-gnus-use-three-panes
+ (jao-gnus-use-three-panes)
+ (jao-gnus-use-two-panes))
+
+;;;; 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 t
+ gnus-permanently-visible-groups "^nnselect:.*"
+ gnus-search-ignored-newsgroups "nndraft.*\\|nnselect.*")
+
+(use-package jao-recoll
+ :if (jao-is-linux))
+
+;; (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)
+
+(defvar jao-local-mail-sources
+ (append (mapcar (lambda (f)
+ `(maildir :path ,(expand-file-name f jao-maildir)))
+ '("local/" "feeds/"))
+ (jao-when-darwin '((file :path "/var/mail/jao")))))
+
+(defun jao-pm-label-mail-sources (pwd &rest labels)
+ (mapcar (lambda (b)
+ `(imap :server "127.0.0.1" :port 1143
+ :user "jaor@pm.me" :password ,pwd
+ :stream starttls :predicate "1:*"
+ :fetchflag "\\Deleted \\Seen"
+ :mailbox ,(concat "Labels/#" b)))
+ (or labels '("inbox" "drivel" "hacking" "bills" "prog" "words"))))
+
+(defun jao-pm-folder-mail-sources (pwd &rest folders)
+ (mapcar (lambda (b)
+ `(imap :server "127.0.0.1" :port 1143
+ :user "jaor@pm.me" :password ,pwd
+ :stream starttls :predicate ""
+ :fetchflag ""
+ :mailbox ,(if b (concat "Folders/" b) "INBOX")))
+ (or folders '(nil "drivel" "hacking" "bills" "prog" "words"))))
+
+(setq mail-sources
+ (let* ((pwd (auth-source-pick-first-password :host "proton-bridge"))
+ (ims (jao-pm-label-mail-sources pwd)))
+ (append jao-local-mail-sources ims)))
+
+(when jao-gnus-use-nnml
+ (let ((prefix (expand-file-name "gnus/" jao-maildir)))
+ (add-to-list
+ ;; `(nnml "" ,(jao-recoll-gnus-search-engine (jao-gnus-dir "Mail/")))
+ 'gnus-secondary-select-methods
+ `(nnml "" (gnus-search-engine gnus-search-jao-notmuch
+ (remove-prefix ,prefix))))))
+
+(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 25)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-face-2 'jao-themes-f11
+ gnus-topic-line-format "%i %2{%~(pad-right 8)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-references
+ 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-p "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)
+ (+ jao-gnus-groups-width 12)))
+ (w (or w (if jao-gnus-use-three-panes (window-width) (frame-width))))
+ (w (- w d)))
+ (setq gnus-summary-line-format (format jao-gnus--summary-line-fmt w))))
+
+(add-hook 'gnus-select-group-hook 'jao-gnus--set-summary-line)
+
+(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: "
+ " via The Rust Programming Language Forum"))
+ "\\|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 <backspace>") '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")
+ "* %(jao-gnus-subject)\n\n %i\n\n %(jao-gnus-org-url)"
+ :immediate-finish t)
+ t)
+ (add-to-list 'org-capture-templates
+ '("X" "arXiv" entry (file "notes/physics/arxiv.org")
+ "* %(jao-gnus-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-url () jao-gnus-org-url)
+(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 ""))))
+(defvar jao-gnus-subject nil)
+(defun jao-gnus-subject () jao-gnus-subject)
+
+(defun jao-gnus-arXiv-capture ()
+ (interactive)
+ (unless (derived-mode-p '(gnus-summary-mode)) (gnus-article-show-summary))
+ (setq jao-gnus-subject (gnus-summary-article-subject))
+ (gnus-summary-select-article-buffer)
+ (gnus-article-goto-part 0)
+ (let ((transient-mark-mode t))
+ (set-mark (point))
+ (forward-paragraph)
+ (or (and (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"))
+ (and (save-excursion
+ (when (re-search-forward "^URL: " nil t)
+ (setq jao-gnus-org-url (thing-at-point-url-at-point))))
+ (org-capture nil "x"))))
+ (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)
+(setq gnus-article-truncate-lines t)
+
+(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:[\n ]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-shell-exec "notmuch-gnus-tags.sh")
+ (when-let* ((a (get-buffer "*Org Agenda*")))
+ (with-current-buffer a (org-agenda-redo-all)))
+ (jao-gnus--notify)))
+
+(defun jao-gnus--scan-local-mail ()
+ (let ((inhibit-message nil))
+ (message "Scanning local news in demon...")
+ (let ((mail-sources jao-local-mail-sources))
+ (gnus-demon-scan-news))
+ (jao-gnus--notify)))
+
+(defun jao-gnus-add-demon ()
+ (interactive)
+ (message "Adding scan demon for Gnus...")
+ (gnus-demon-add-handler 'jao-gnus--scan 5 1))
+
+(defun jao-gnus-remove-demon ()
+ (interactive)
+ (message "Removing scan demon for Gnus...")
+ (gnus-demon-remove-handler 'jao-gnus--scan))
+
+(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
+(jao-when-linux (add-to-list 'jao-sleep-awake-functions #'jao-gnus-add-demon))
+
+;;; add-ons
+;;;; notifications
+;;;;; minibuffer
+(defvar jao-gnus--notify-strs ())
+
+(defun jao-gnus--xbar-echo ()
+ (let* ((total (cdr (assoc "Gnus" gnus-topic-unreads)))
+ (jao (cdr (assoc "jao" gnus-topic-unreads)))
+ (str (concat (when (> total 0) (format "%d" total))
+ " "
+ (when (> jao 0) (format "J%d" jao)))))
+ (jao-shell-exec
+ (format "echo '%s | color=#8b3626 | size=11' >/tmp/xbar" str))))
+
+(defvar jao-gnus-group-notifications
+ '(("Gnus" "" jao-themes-dimm)
+ ("jao" "J" jao-themes-warning)
+ ("news" "N" jao-themes-dimm)
+ ("prog" "P" jao-themes-dimm)
+ ("sci" "S" jao-themes-dimm)))
+
+(defun jao-gnus--notify-group-str (p)
+ (let* ((n (cdr p))
+ (f (cdr (assoc (car p) jao-gnus-group-notifications))))
+ (when (and f (> n 0))
+ `(:propertize ,(format "%s%d " (car f) n) face ,(cadr f)))))
+
+(defun jao-gnus--notify ()
+ (setq jao-gnus--notify-strs
+ (seq-keep 'jao-gnus--notify-group-str gnus-topic-unreads))
+ (jao-when-darwin (jao-gnus--xbar-echo))
+ (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)
+(add-hook 'gnus-exit-group-hook #'jao-gnus--notify)
+
+;;;; 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)
+ (jao-gnus--notify)
+ (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))))
+;;;; notmuch
+(use-package jao-notmuch-gnus
+ :demand t
+ :init
+ (jao-when-darwin
+ (setq jao-notmuch-gnus-mail-directory
+ (expand-file-name "gnus" jao-maildir))))
+
+(jao-load-path "consult-notmuch")
+
+(use-package consult-notmuch
+ :ensure t
+ :bind (:map gnus-group-mode-map ("/" . #'jao-gnus-consult-notmuch)))
+
+;;; 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)
+
+(jao-when-darwin
+ (define-key gnus-group-mode-map "O" 'jao-mac-open-nnw))