#+property: header-args :tangle ~/.emacs.d/gnus.el :comments yes :results silent
#+title: Gnus
#+auto_tangle: t

* Feature switching vars
  #+begin_src emacs-lisp
  (defvar jao-gnus-use-local-imap t)
  (defvar jao-gnus-use-leafnode t)
  (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)
  #+end_src
* Startup and kill
  #+begin_src emacs-lisp
    ;;;;; 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))
  #+end_src
* Directories
  #+begin_src emacs-lisp
    (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"))
  #+end_src
* Looks
*** Verbosity
    #+begin_src emacs-lisp
      (setq gnus-verbose 5)
    #+end_src
*** Geometry
    #+begin_src emacs-lisp
      ;;; geometry:
      (defvar jao-gnus-use-three-panes t)

      (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 190))
          (gnus-add-configuration
           `(article
             (horizontal 1.0
                         (vertical 60 (group 1.0))
                         (vertical 130
                                   (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 60 (group 1.0))
                         (vertical 130 (summary 1.0 point))
                         ,side-bar)))

          (gnus-add-configuration
           `(reply
             (horizontal 1.0
                         (message 90 point)
                         (article 100)
                         ,side-bar)))))
    #+end_src
*** No blue icon
    #+begin_src emacs-lisp
      ;; (defalias 'gnus-mode-line-buffer-identification 'identity)
      (advice-add 'gnus-mode-line-buffer-identification :override #'identity)
      (setq gnus-mode-line-image-cache nil)
    #+end_src
* Search
  [[info:gnus#Searching][info:gnus#Searching]]
  #+begin_src emacs-lisp
    (setq gnus-search-use-parsed-queries t
          jao-gnus-search-prefix (expand-file-name "~/var/mail/"))

    (defun jao-gnus-search-engine (engine)
      `(gnus-search-engine ,engine (remove-prefix ,jao-gnus-search-prefix)))
  #+end_src
* News server
  #+begin_src emacs-lisp
    (setq gnus-select-method
          (cond (jao-gnus-use-leafnode
                 `(nntp "localhost"
                        ,(jao-gnus-search-engine 'gnus-search-notmuch)))
                (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
  #+end_src
* IMAP servers
  #+begin_src emacs-lisp
    ;; archiving messages
    (setq gnus-message-archive-group nil
          nnimap-quirks nil)

    ;; imap
    (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"))))
  #+end_src
* Mailbox and maildir servers
  #+begin_src emacs-lisp
    (setq mail-sources '((file :path "/var/mail/jao")))

    (setq nnml-get-new-mail t
          nnmail-treat-duplicates 'delete
          nnmail-scan-directory-mail-source-once t
          nnmail-cache-accepted-message-ids t
          nnmail-message-id-cache-length 50000
          nnmail-cache-ignore-groups ".*\\(trove\\.\\|feeds\\.\\|spamish\\).*"
          nnmail-split-fancy-with-parent-ignore-groups nil
          nnmail-crosspost t)

    (setq nnmail-resplit-incoming t
          nnmail-mail-splitting-decodes t
          nnmail-split-methods 'nnmail-split-fancy)

    (when jao-gnus-use-nnml
      (add-to-list 'gnus-secondary-select-methods `(nnml "")))

    (when jao-gnus-use-maildirs
      (add-to-list 'gnus-secondary-select-methods
                   '(nnmaildir "bml" (directory "/home/jao/var/maildir/bigml/")))
      (add-to-list 'gnus-secondary-select-methods
                   '(nnmaildir "jao" (directory "/home/jao/var/maildir/jao/")))
      (add-to-list 'gnus-secondary-select-methods
                   '(nnmaildir "gmail" (directory "/home/jao/var/maildir/gmail/"))))

  #+end_src
* Demons and notifications
  #+begin_src emacs-lisp
    (setq mail-user-agent 'gnus-user-agent)

    (require 'gnus-demon)
    (gnus-demon-add-rescan)

    ;; 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
      '(("nnimap:bigml/inbox" "B" jao-themes-f00)
        ("nnimap:bigml/bugs" "b" jao-themes-error)
        ("nnimap:bigml/support" "S" default)
        ("nnimap:jao/inbox" "I" jao-themes-f01)
        ("nnimap:bigml/[^ibs]" "W" jao-themes-dimm)
        ("nnimap:jao" "J" jao-themes-dimm)
        ("nnimap:feeds/[^e]" "F" jao-themes-dimm)
        ("^gmane\\.emacs\\|nnimap:feeds/emacs" "E" jao-themes-dimm)
        ("^gmane\\.[^e]" "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))
      (save-window-excursion (jao-minibuffer-refresh)))

    (add-hook 'gnus-after-getting-new-news-hook #'jao-gnus--notify)
    (add-hook 'gnus-started-hook #'jao-gnus--notify)

    (defun jao-gnus--summary-done ()
        (let ((inhibit-message t)
              (message-log-max nil))
          (save-window-excursion
            (jao-gnus--notify)
            (org-agenda-list))))

    (add-hook 'gnus-summary-exit-hook #'jao-gnus--summary-done)

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

  #+end_src
* Delayed messages
  #+BEGIN_SRC emacs-lisp
    ;;; 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)))
  #+END_SRC
* Add-ons
*** icalendar
    #+begin_src emacs-lisp
      (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))
    #+end_src
*** bbdb
    #+begin_src emacs-lisp
      (with-eval-after-load "bbdb"
        (bbdb-initialize 'gnus 'message 'pgp 'mail)
        (bbdb-mua-auto-update-init 'gnus)
        (eval-after-load "gnus-sum"
          '(progn
             (define-key gnus-summary-mode-map ":" 'bbdb-mua-annotate-sender)
             (define-key gnus-summary-mode-map ";" 'bbdb-mua-annotate-recipients))))
    #+end_src
*** randomsig
    #+begin_src emacs-lisp
      (with-eval-after-load "randomsig"
        (with-eval-after-load "gnus-sum"
          (define-key gnus-summary-save-map "-"
            'gnus/randomsig-summary-read-sig)))
    #+end_src
*** notmuch -> gnus
    #+begin_src emacs-lisp
      (defun jao-notmuch-goto-message-in-gnus ()
        "Open a summary buffer containing the current notmuch article."
        (interactive)
        (let ((group (jao-maildir-file-to-group (notmuch-show-get-filename)))
              (message-id (replace-regexp-in-string "^id:"
                                                    ""
                                                    (notmuch-show-get-message-id))))
          (if (and group message-id)
              (org-gnus-follow-link group message-id)
            (message "Couldn't get relevant infos for switching to Gnus."))))

      (defalias 'jao-open-gnus-frame 'jao-afio--goto-mail)

      (eval-after-load "notmuch-show"
        '(define-key notmuch-show-mode-map (kbd "C-c C-c")
           #'jao-notmuch-goto-message-in-gnus))
    #+end_src
*** gnus-recent
    #+begin_src emacs-lisp :tangle no
      (use-package gnus-recent
        :ensure t
        :after gnus
        :bind (:map gnus-summary-mode-map
                    (("l" . #'gnus-recent-goto-previous))
                    :map gnus-group-mode-map
                    (("C-c l" . #'gnus-recent-goto-previous)
                     ("C-c r" . #'gnus-recent))))
    #+end_src
* Groups buffer
  #+begin_src emacs-lisp
    ;; (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 45)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))))
    ;; To limit expiration to the `g' count, `jao-gnus--get-count':
    ;; (remove-hook 'gnus-summary-prepare-exit-hook 'gnus-summary-expire-articles)
    ;; (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 "z" nil)
    (define-key gnus-group-mode-map "zg" #'notmuch)
    (define-key gnus-group-mode-map "zz" #'jao-consult-notmuch-folder)
    (define-key gnus-group-mode-map "zZ" #'consult-notmuch)

    (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))
    #+end_src
* Group parameters
  #+begin_src emacs-lisp
    (setq jao-gnus-expirable
          (format (concat "^nnimap:\\("
                          "\\(\\(bigml\\|bml\\)/%s\\)\\|"
                          "\\(jao/%s\\)\\|"
                          "\\(feeds/.+\\)\\|trash\\|spam"
                          "\\)")
                  (regexp-opt '("support" "reports" "deploys"
                                "lists" "drivel" "bugs"))
                  (regexp-opt '("books" "think" "local" "drivel"
                                "lists" "emacs" "bills" "gnu"))))

    (setq gnus-permanently-visible-groups "^nnselect")

    (setq gnus-parameters
          `(("^nnimap:jao/.*"
             (jao-gnus--trash-group "nnimap:jao/trash")
             (jao-gnus--spam-group "nnimap:jao/spam")
             (jao-gnus--archiving-group "nnimap:trove/jao"))
            ("^nnimap:\\(jao\\|pm\\|bigml\\)/\\(trash\\|spam\\)"
             (gcc-self . nil)
             (auto-expire . t)
             (total-expire . t)
             (expiry-wait . 1)
             (jao-gnus--trash-group nil)
             (expiry-target . delete))
            ("^nnimap:jao/inbox"
             (gcc-self . t))
            ("^nnimap:bigml/.*"
             (posting-style (address "jao@bigml.com"))
             (jao-gnus--archiving-group "nnimap:trove/bigml")
             (jao-gnus--spam-group "nnimap:bigml/spam"))
            ("^nnimap:bigml/inbox"
             (gcc-self . t)
             (auto-expire . t)
             (total-expire . t)
             (expiry-wait . 14)
             (jao-gnus--trash-group "nnimap:trash")
             (expiry-target . "nnimap:trove/bigml"))
            ("^nnimap:bigml/support"
             (posting-style (address "support@bigml.com")))
            (,jao-gnus-expirable
             (jao-gnus--trash-group nil)
             (gcc-self . nil)
             (auto-expire . t)
             (total-expire . t)
             (expiry-wait . 7)
             (expiry-target . delete))
            ("^nnimap:feeds/podcasts"
             (auto-expire . nil)
             (total-expire . nil))
            ("^nnimap:feeds/\\(papers\\|math\\|physics\\)$"
             (expiry-wait . 30)
             (jao-gnus--archiving-group "nnimap:trove/sci"))
            ("^nnimap:feeds/\\(programming\\)$"
             (expiry-wait . 30)
             (jao-gnus--archiving-group "nnimap:trove/tech")
             (posting-style (address "jao@gnu.org")))
            ("^nnimap:jao/hacking$"
             (jao-gnus--archiving-group "nnimap:trove/tech"))
            ("^nnimap:jao/gnu$"
             (expiry-target . "nnimap:trove/gnu")
             (jao-gnus--archiving-group "nnimap:trove/gnu"))
            ("^nnimap:jao/bills$"
             (expiry-target . "nnimap:trove/bills")
             (jao-gnus--archiving-group "nnimap:trove/bills"))
            ("\\(gmane\\|gwene\\)\\..*"
             (jao-gnus--archiving-group "nnimap:trove/tech")
             (posting-style (address "jao@gnu.org")))))
  #+end_src
* Summary buffer
*** Configuration, summary line
    #+BEGIN_SRC emacs-lisp
      (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)

      (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-face-1 'jao-gnus-face-tree)

      (setq gnus-not-empty-thread-mark ?·) ; ↓)
      (setq jao-gnus--summary-line-fmt
            (concat "%%U %%*%%R %%uj "
                    "[ %%~(max-right 23)~(pad-right 23)n "
                    " %%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 75 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 190)

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

      (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)))
                "¬" ;; "~"
              " "))))

      (setq gnus-summary-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")))

      ;; old name, for emacs 23
      (setq gnus-user-date-format-alist gnus-summary-user-date-format-alist)
    #+END_SRC
*** Moving messages around
    #+BEGIN_SRC emacs-lisp
      (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)
    #+END_SRC
*** Writing emails
    #+BEGIN_SRC emacs-lisp
      (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)
    #+END_SRC
*** arXiv capture
    #+begin_src emacs-lisp
      (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"))
    #+end_src
* Article buffer
*** Config, headers
    #+begin_src emacs-lisp
      (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-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:")))
    #+end_src
*** HTML email
    #+BEGIN_SRC emacs-lisp
      (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)
      (require 'shr)
      (defun jao-gnus-remove-anchors ()
        (save-excursion
          (goto-char (point-min))
          (when (re-search-forward " .+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 1000))
                  (shr-render-region begin (1- (point)))))))))

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

      ;; show images
      (defun jao-gnus-show-image (&optional external)
        (interactive "P")
        (when (eq major-mode 'gnus-summary-mode)
          (gnus-summary-select-article-buffer))
        (let ((pos (next-single-property-change (point) 'w3m-image)))
          (if (not pos)
              (gnus-article-show-images)
            (goto-char pos)
            (if external (w3m-view-image) (w3m-toggle-inline-image)))))

      (defun jao-gnus-show-images (&optional external)
        (interactive "P")
        (save-window-excursion
          (gnus-summary-select-article-buffer)
          (save-excursion
            (let ((pos (next-single-property-change (point) 'w3m-image)))
              (if (not pos)
                  (gnus-article-show-images)
                (goto-char pos)
                (w3m-toggle-inline-images))))))
    #+END_SRC
*** Follow links and enclosures
    #+begin_src emacs-lisp
      (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 (&optional playp)
        (interactive "P")
        (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)))
                (message "%s %s ..." (if playp "Playing" "Adding") url)
                (when playp (jao-mpc-clear))
                (jao-mpc-add-url url)
                (when playp (jao-mpc-play)))))))
    #+end_src
* Keyboard shortcuts
  #+BEGIN_SRC emacs-lisp
    (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)

    (major-mode-hydra-define gnus-summary-mode nil
        ("Browse"
         (("g" jao-gnus-follow-link "Follow link in emacs")
          ("G" (lambda () (interactive) (jao-gnus-follow-link t))
           "Follow link in external browser"))
         "Capture"
         (("x" jao-gnus-arXiv-capture "Capture arXiv entry")
          ("e" jao-gnus-open-enclosure "Add enclosure to playlist")
          ("E" (jao-gnus-open-enclosure t) "Play enclosure"))
         "Images"
         (("i" jao-gnus-show-images "Show images"))
         "Toot"
         (("t" jao-gnus-tweet-link "Tweet article")
          ("T" jao-gnus-toot-link "Toot article"))))

    (major-mode-hydra-define gnus-article-mode nil
      ("Browse"
       (("g" jao-gnus-follow-link "Follow link in emacs")
        ("G" (lambda () (interactive) (jao-gnus-follow-link t))
         "Follow link in external browser"))
       "Capture"
       (("x" jao-gnus-arXiv-capture "Capture arXiv entry")
        ("e" jao-gnus-open-enclosure "Add enclosure to playlist")
        ("E" (jao-gnus-open-enclosure t) "Play enclosure"))
       "Images"
       (("z" w3m-lnum-zoom-in-image "Zoom image at point")
        ("I" (if (fboundp 'w3m-view-image) (w3m-view-image) (eww-display-image))
         "View image at point")
        ("i" jao-gnus-show-images "Show images"))
       "Toot"
       (("t" jao-gnus-tweet-link "Tweet article")
        ("T" jao-gnus-toot-link "Toot article"))))
   #+END_SRC