Emacs configuration and packages
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

29 KiB

Gnus

Feature switching vars

  (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)

Startup and kill

  ;;;;; close gnus when closing emacs, but ask when exiting
  (setq gnus-interactive-exit t)

  (defun jao-gnus-started-hook ()
    (add-hook 'before-kill-emacs-hook 'gnus-group-exit))

  (add-hook 'gnus-started-hook 'jao-gnus-started-hook)

  (defun jao-gnus-after-exiting-hook ()
    (remove-hook 'before-kill-emacs-hook 'gnus-group-exit))

  (add-hook 'gnus-after-exiting-gnus-hook 'jao-gnus-after-exiting-hook)

  ;; define a wrapper around the save-buffers-kill-emacs
  ;; to run the new hook before:
  (defadvice save-buffers-kill-emacs
      (before my-save-buffers-kill-emacs activate)
    "Install hook when emacs exits before emacs asks to save this and that."
    (run-hooks 'before-kill-emacs-hook))

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

  ;;; geometry:
  (defvar jao-gnus-use-three-panes window-system)
  (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
    (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 t
        gnus-search-notmuch-raw-queries-p nil
        gnus-permanently-visible-groups "^nnselect:.*"
        gnus-search-ignored-newsgroups "nndraft.*\\|nnselect.*")

  (with-eval-after-load "gnus-search"
    (add-to-list 'gnus-search-expandable-keys "list")

    (cl-defmethod gnus-search-transform-expression ((engine gnus-search-notmuch)
                                                    (expr (head list)))
      (format "List:%s" (gnus-search-transform-expression engine (cdr expr)))))

  (defun jao-gnus--notmuch-engine (prefix config)
    (let ((prefix (file-name-as-directory (expand-file-name prefix "~")))
          (config (expand-file-name config gnus-home-directory)))
      `(gnus-search-engine gnus-search-notmuch
                           (remove-prefix ,prefix)
                           (config-file ,config))))

  ;; (add-to-list 'gnus-parameters '("^nnselect:.*" (nnselect-rescan . t)))

News server

  (setq gnus-select-method
        (cond
         (jao-gnus-use-leafnode
          `(nntp "localhost"
                 ,(jao-gnus--notmuch-engine "var/news" "notmuch-news.config")))
         (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

Local mail

nnmail params

  (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 mail-sources
        (when jao-gnus-use-nnml
          (cons '(file :path "/var/mail/jao")
                (when (eq jao-afio-mail-function 'gnus)
                  (mapcar (lambda (d) `(maildir :path ,(concat d "/")))
                          (directory-files "~/var/mail" t "[^\\.]")))))
        gnus-message-archive-group nil
        nnml-get-new-mail t
        nnml-directory message-directory)

  (when jao-gnus-use-nnml
    (add-to-list
     'gnus-secondary-select-methods
     `(nnml "" ,(jao-gnus--notmuch-engine (jao-gnus-dir "Mail") "notmuch.config"))))

  (defvar jao-gnus-nnml-group-params
    `(("nnml:\\(local\\|trash\\|spam\\)"
       (auto-expire . t)
       (total-expire . t)
       (expiry-wait . 1)
       (expiry-target . delete))
      ("nnml:jao\\..*"
       (posting-style ("Bcc" "proton@jao.io")
                      ("Gcc" "nnml:jao.trove"))
       (jao-gnus--trash-group "nnml:trash")
       (jao-gnus--spam-group "nnml:spam")
       (jao-gnus--archiving-group "nnml:jao.trove"))
      ("nnml:bigml\\..*"
       (gcc-self . nil)
       (auto-expire . t)
       (total-expire . t)
       (expiry-wait . 3)
       (expiry-target . delete)
       (posting-style (address "jao@bigml.com"))
       (jao-gnus--trash-group "nnml:trash")
       (jao-gnus--spam-group "nnml:spam")
       (jao-gnus--archiving-group "nnml:bigml.trove"))
      ("nnml:bigml\\.\\(inbox\\|support\\)"
       (gcc-self . t)
       (auto-expire . t)
       (total-expire . t)
       (expiry-wait . 7)
       (expiry-target . "nnml:bigml.trove"))
      ("nnml:bigml\\.trove"
       (auto-expire . t)
       (total-expire . t)
       (expiry-target . delete)
       (expiry-wait . 365))
      ("nnml:jao\\.drivel"
       (auto-expire . t)
       (total-expire . t)
       (expiry-wait . 3)
       (expiry-target . delete))
      ("nnml:feeds\\.\\(.*\\)"
       (auto-expire . t)
       (total-expire . t)
       (expiry-wait . 7)
       (expiry-target . delete)
       (comment . "feeds.\\1")
       (jao-gnus--archiving-group "nnml:feeds.trove"))
      ("^nnml:feeds\\.\\(news\\)$" (expiry-wait . 2))
      ("nnml:feeds\\.\\(trove\\|lobsters\\|philosophy\\)"
       (auto-expire . nil)
       (total-expire . nil))
      ("nnml:feeds\\.fun"
       (mm-html-inhibit-images nil)
       (mm-html-blocked-images nil))))

  (when jao-gnus-use-nnml
    (dolist (p jao-gnus-nnml-group-params)
      (add-to-list 'gnus-parameters p t)))

leafnode

  (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)))

maildirs

  (when jao-gnus-use-maildirs
    (add-to-list
     'gnus-secondary-select-methods
     `(nnmaildir "mail"
                 (directory "~/.nnmaildirs")
                 ,(jao-gnus--notmuch-engine "~/var/mail/"
                                            "~/.notmuch-config"))))

IMAP servers

  (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"))))

Demon and notifications

  (setq mail-user-agent 'gnus-user-agent)

  ;; synchronicity
  (setq gnus-asynchronous t)
  ;;; prefetch as many articles as possible
  (setq gnus-use-article-prefetch nil)

  (setq gnus-save-killed-list nil)
  (setq gnus-check-new-newsgroups nil)

  (defvar jao-gnus-tracked-groups
    (let ((feeds (thread-first
                   (directory-files mail-source-directory nil "feeds")
                   (seq-difference '("feeds.trove")))))
      `(("nnml:bigml.inbox" "B" jao-themes-f00)
        ("nnml:bigml.bugs" "b" jao-themes-error)
        ("nnml:bigml.support" "S" default)
        ("nnml:jao.inbox" "I" jao-themes-f01)
        ("nnml:bigml.[^ibs]" "W" jao-themes-dimm)
        ("nnml:jao.[^ist]" "J" jao-themes-dimm)
        (,(format "^nnml:%s" (regexp-opt feeds)) "F" jao-themes-dimm)
        ("^gmane" "G" jao-themes-dimm)
        ("nnml:local" "l" 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))
                        (prog1 (cons (cons (car g) n) r)
                          (gnus-message 7 "%s in %s" n g))
                      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))
    (save-window-excursion (jao-minibuffer-refresh)))

  (defun jao-gnus-scan ()
    (interactive)
    (let ((inhibit-message t))
      (gnus-demon-scan-mail)
      (shell-command "index-mail.sh")
      (save-window-excursion )
      (jao-gnus--notify)))

  (require 'gnus-demon)
  (gnus-demon-add-handler 'gnus-demon-scan-news 5 1)
  ;; (gnus-demon-remove-handler 'jao-gnus-scan)

  (add-hook 'gnus-started-hook #'jao-gnus-scan)
  (add-hook 'gnus-summary-exit-hook #'jao-gnus--notify)
  (add-hook 'gnus-summary-exit-hook #'org-agenda-list)
  (add-hook 'gnus-after-getting-new-news-hook #'jao-gnus-scan)

  (with-eval-after-load "jao-minibuffer"
    (jao-minibuffer-add-variable 'jao-gnus--notify-strs -20))

Delayed messages

  ;;; delayed messages (C-cC-j in message buffer)
  (require 'gnus-util)
  (gnus-delay-initialize)
  (setq gnus-delay-default-delay "3h")
  ;;; so that the Date is set when the message is sent, not when it's
  ;;; delayed
  (eval-after-load "message"
    '(setq message-draft-headers (remove 'Date message-draft-headers)))

Groups buffer

  ;; (setq gnus-group-line-format " %m%S%p%P:%~(pad-right 35)c %3y %B\n")
  ;; (setq gnus-group-line-format " %m%S%p%3y%P%* %~(pad-right 30)C %B\n")
  (setq gnus-group-line-format " %m%S%p%3y%P%* %~(pad-right 30)G %B\n")
  (setq gnus-topic-line-format "%i[ %(%{%n%}%) -- %A ]%v\n")
  (setq gnus-group-uncollapsed-levels 2)
  (setq gnus-auto-select-subject 'unread)
  (setq-default gnus-large-newsgroup 2000)

  (add-hook 'gnus-select-group-hook 'gnus-group-set-timestamp)
  (add-hook 'gnus-group-mode-hook 'gnus-topic-mode)

  (defvar jao-gnus--expire-every 50)
  (defvar jao-gnus--get-count (1+ jao-gnus--expire-every))

  (defun jao-gnus-get-new-news (&optional arg)
    (interactive "p")
    (when (and jao-gnus--expire-every
               (> jao-gnus--get-count jao-gnus--expire-every))
      (when jao-gnus-use-pm-imap (gnus-group-catchup "nnimap:pm/spam" t))
      (gnus-group-expire-all-groups)
      (setq jao-gnus--get-count 0))
    (setq jao-gnus--get-count (1+ jao-gnus--get-count))
    (gnus-group-get-new-news (max (if (= 1 jao-gnus--get-count) 4 3)
                                  (or arg 0))))

  ;; (define-key gnus-group-mode-map "g" 'jao-gnus-get-new-news)

  (defun jao-gnus-restart-servers ()
    (interactive)
    (message "Restarting all servers...")
    (gnus-group-enter-server-mode)
    (gnus-server-close-all-servers)
    (gnus-server-open-all-servers)
    (gnus-server-exit)
    (message "Restarting all servers... done"))

  (define-key gnus-group-mode-map "\C-x\C-s" #'gnus-group-save-newsrc)

  (jao-transient-major-mode gnus-group
    ["Search"
     ("zc" "consult search" consult-notmuch)
     ("zf" "consult folder search" jao-consult-notmuch-folder)
     ("g" "gnus search" gnus-group-read-ephemeral-search-group)])

  (defun jao-gnus--first-group ()
    (when (derived-mode-p 'gnus-group-mode)
      (gnus-group-first-unread-group)))

  (with-eval-after-load "jao-afio"
    (add-hook 'jao-afio-switch-hook #'jao-gnus--first-group))

Summary buffer

Configuration

  (setq gnus-face-1 'jao-gnus-face-tree)

  (setq gnus-show-threads t
        gnus-thread-hide-subtree t
        gnus-summary-make-false-root 'adopt
        gnus-summary-gather-subject-limit 120
        gnus-sort-gathered-threads-function 'gnus-thread-sort-by-date
        gnus-thread-sort-functions '(gnus-thread-sort-by-date))

  (setq gnus-summary-ignore-duplicates t
        gnus-suppress-duplicates t
        ;; gnus-summary-ignored-from-addresses jao-mails-regexp
        gnus-process-mark-toggle t
        gnus-refer-thread-use-search t
        gnus-auto-select-next 'almost-quietly)

Search

  (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)
  (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: "
                          ": <author>"))
            "\\|unofficial mirror of [^:]+: "))

  (defun gnus-user-format-function-f (headers)
    (let* ((from (gnus-header-from headers))
           (from (gnus-summary-extract-address-component from)))
      (replace-regexp-in-string jao-gnus--news-rx "" 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")
                   "* %:subject\n  %i" :immediate-finish t)
                 t)
    (org-capture-upgrade-templates org-capture-templates))

  (defun jao-gnus-arXiv-capture ()
    (interactive)
    (gnus-summary-select-article-buffer)
    (gnus-article-goto-part 0)
    (forward-paragraph)
    (setq-local transient-mark-mode 'lambda)
    (set-mark (point))
    (goto-char (point-max))
    (org-capture nil "X"))

Article buffer

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 nil)
  (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)

  (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:")))

HTML email (washing, 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)

  ;; no html in From: (washing articles from arxiv feeds) and cleaning up
  ;; addresses
  (require 'shr)
  (defvar jao-gnus--from-rx
    (concat "From: \\\"?\\(  " jao-gnus--news-rx "\\)"))

  (defun jao-gnus-remove-anchors ()
    (save-excursion
      (goto-char (point-min))
      (cond ((re-search-forward jao-gnus--from-rx nil t)
             (replace-match "" nil nil nil 1))
            ((re-search-forward "[gq].+ updates on arXiv.org: " nil t)
             (replace-match "")
             (let ((begin (point)))
               (when (re-search-forward "^\\(To\\|Subject\\):" nil t)
                 (beginning-of-line)
                 (let ((shr-width 10000))
                   (shr-render-region begin (1- (point))))))))))

  (add-hook 'gnus-part-display-hook 'jao-gnus-remove-anchors)

  (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-show-images ()
    (interactive)
    (save-window-excursion
      (gnus-summary-select-article-buffer)
      (save-excursion
        (setq jao-gnus--images (not jao-gnus--images))
        (if jao-gnus--images
            (gnus-article-show-images)
          (gnus-article-remove-images)))))

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))))
        (if external
            (jao-browse-with-external-browser)
          (browse-url (jao-url-around-point))))))

  (defun jao-gnus-open-enclosure ()
    (interactive)
    (save-window-excursion
      (gnus-summary-select-article-buffer)
      (save-excursion
        (goto-char (point-min))
        (when (search-forward "Enclosure:")
          (forward-char 2)
          (when-let ((url (thing-at-point-url-at-point)))
            (jao-browse-add-url-to-mpc url))))))

Add-ons

notmuch integration

  (require 'jao-notmuch-gnus)

  (jao-notmuch-gnus-auto-tag)

  (defun jao-gnus-toggle-todo ()
    (interactive)
    (jao-notmuch-gnus-toggle-tags '("todo")))

  (define-key gnus-summary-mode-map (kbd "C-c T") #'jao-notmuch-gnus-tag-message)
  (define-key gnus-summary-mode-map (kbd "C-c t") #'jao-notmuch-gnus-show-tags)
  (define-key gnus-summary-mode-map (kbd "C-c C-t") #'jao-gnus-toggle-todo)

  (with-eval-after-load "notmuch-show"
    (define-key gnus-group-mode-map "z" #'jao-gnus-consult-notmuch)
    (define-key gnus-group-mode-map "Z" #'notmuch)
    (define-key notmuch-show-mode-map
                (kbd "C-c C-c")
                #'jao-notmuch-gnus-goto-message))

  (defun jao-gnus-notmuch-export (query)
    (notmuch-tree query nil nil "* consult-notmuch results *"))

  (setq consult-notmuch-export-function #'jao-gnus-notmuch-export)

  (with-eval-after-load "notmuch-tree"
    (define-key notmuch-tree-mode-map
                (kbd "C-<return>")
                #'jao-notmuch-gnus-goto-message))

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 'mail)
    (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)))

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)