jao-custom-gnus.el 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786
  1. ;; gnus configuration -*- lexical-binding: t -*-
  2. ;;; features
  3. (defvar jao-gnus-use-local-imap nil)
  4. (defvar jao-gnus-use-leafnode nil)
  5. (defvar jao-gnus-use-gandi-imap nil)
  6. (defvar jao-gnus-use-pm-imap nil)
  7. (defvar jao-gnus-use-gmane nil)
  8. (defvar jao-gnus-use-nnml nil)
  9. (defvar jao-gnus-use-maildirs nil)
  10. (defvar jao-notmuch-enabled nil)
  11. (defvar jao-gnus-nnml-group-params nil)
  12. ;;; directories
  13. (defun jao-gnus-dir (dir)
  14. (expand-file-name dir gnus-home-directory))
  15. (setq smtpmail-queue-dir (jao-gnus-dir "Mail/queued-mail/"))
  16. (setq mail-source-directory (jao-gnus-dir "Mail/")
  17. message-directory (jao-gnus-dir "Mail/"))
  18. (setq gnus-default-directory (expand-file-name "~")
  19. gnus-startup-file (jao-gnus-dir "newsrc")
  20. gnus-agent-directory (jao-gnus-dir "News/agent")
  21. gnus-home-score-file (jao-gnus-dir "scores")
  22. gnus-article-save-directory (jao-gnus-dir "saved/")
  23. nntp-authinfo-file (jao-gnus-dir "authinfo")
  24. nnmail-message-id-cache-file (jao-gnus-dir "nnmail-cache")
  25. nndraft-directory (jao-gnus-dir "drafts")
  26. nnrss-directory (jao-gnus-dir "rss"))
  27. ;;; looks
  28. ;;;; verbosity
  29. (setq gnus-verbose 4)
  30. ;;;; geometry
  31. (defvar jao-gnus-use-three-panes t)
  32. (defvar jao-gnus-groups-width 50)
  33. (defvar jao-gnus-wide-width 190)
  34. (setq gnus-use-trees nil
  35. gnus-generate-tree-function 'gnus-generate-horizontal-tree
  36. gnus-tree-minimize-window nil)
  37. (when jao-gnus-use-three-panes
  38. ;; (dolist (m '(calendar-mode org-agenda-mode gnus-group-mode))
  39. ;; (add-to-list 'display-buffer-alist `((major-mode . ,m) (dedicated t))))
  40. (setq calendar-left-margin 6)
  41. (let ((side-bar '(vertical 1.0
  42. ("inbox.org" 0.4)
  43. ("*Org Agenda*" 1.0)
  44. ("*Calendar*" 8)))
  45. (wide-len jao-gnus-wide-width)
  46. (groups-len jao-gnus-groups-width)
  47. (summary-len (- jao-gnus-wide-width jao-gnus-groups-width)))
  48. (gnus-add-configuration
  49. `(article
  50. (horizontal 1.0
  51. (vertical ,groups-len (group 1.0))
  52. (vertical ,summary-len
  53. (summary 0.25 point)
  54. (article 1.0))
  55. ,side-bar)))
  56. (gnus-add-configuration
  57. `(group (horizontal 1.0 (group ,wide-len point) ,side-bar)))
  58. (gnus-add-configuration
  59. `(message (horizontal 1.0 (message ,wide-len point) ,side-bar)))
  60. (gnus-add-configuration
  61. `(reply-yank (horizontal 1.0 (message ,wide-len point) ,side-bar)))
  62. (gnus-add-configuration
  63. `(summary
  64. (horizontal 1.0
  65. (vertical ,groups-len (group 1.0))
  66. (vertical ,summary-len (summary 1.0 point))
  67. ,side-bar)))
  68. (gnus-add-configuration
  69. `(reply
  70. (horizontal 1.0
  71. (message ,(- wide-len 100) point)
  72. (article 100)
  73. ,side-bar)))))
  74. ;;;; no blue icon
  75. (advice-add 'gnus-mode-line-buffer-identification :override #'identity)
  76. (setq gnus-mode-line-image-cache nil)
  77. ;;; search
  78. (setq gnus-search-use-parsed-queries nil
  79. gnus-search-notmuch-raw-queries-p nil
  80. gnus-permanently-visible-groups "^nnselect:.*"
  81. gnus-search-ignored-newsgroups "nndraft.*\\|nnselect.*")
  82. (with-eval-after-load "gnus-search"
  83. (defclass gnus-search-recoll (gnus-search-indexed)
  84. ((separator :type string :initform ".")
  85. (program :initform "recoll")
  86. (raw-queries-p :initform t)))
  87. (cl-defmethod gnus-search-indexed-extract ((_engine gnus-search-recoll))
  88. (prog1 (and (looking-at "^file://\\(.+\\)$") (list (match-string 1) 100))
  89. (forward-line 1)))
  90. (cl-defmethod gnus-search-transform-expression ((_engine gnus-search-recoll)
  91. expr)
  92. expr)
  93. (cl-defmethod gnus-search-indexed-search-command ((engine gnus-search-recoll)
  94. (qstring string)
  95. query
  96. &optional groups)
  97. (let* ((subdir (slot-value engine 'remove-prefix))
  98. (sep (slot-value engine 'separator))
  99. (gdirs (mapcar (lambda (g)
  100. (let ((g (gnus-group-short-name g)))
  101. (replace-regexp-in-string "\\." sep g)))
  102. (or groups
  103. (and (not (string= "" subdir)) (list subdir)))))
  104. (dirsq (and gdirs
  105. (concat "("
  106. (mapconcat (lambda (d) (format "dir:%s" d))
  107. gdirs " OR ")
  108. ")")))
  109. (qstring (if (string-prefix-p "id:" qstring)
  110. (replace-regexp-in-string "<\\|>" "\"" qstring)
  111. qstring))
  112. (qstring (if (cdr (assoc 'thread query))
  113. (concat qstring " OR "
  114. (replace-regexp-in-string "id:\"" "ref:\""
  115. qstring))
  116. qstring))
  117. (qstring (replace-regexp-in-string " or " " OR " qstring))
  118. (qstring (replace-regexp-in-string " and " " AND " qstring))
  119. (q (format "mime:message %s (%s)" dirsq qstring)))
  120. ;; (message "query is: %s -- %S" q query)
  121. `("-b" "-t" "-q" ,q))))
  122. ;; (add-to-list 'gnus-parameters '("^nnselect:.*" (nnselect-rescan . t)))
  123. ;;; news
  124. (defvar jao-gnus-leafnode-spool "/var/spool/news/")
  125. (setq gnus-select-method
  126. (cond
  127. (jao-gnus-use-leafnode
  128. `(nntp "localhost"
  129. (gnus-search-engine gnus-search-recoll
  130. (remove-prefix ,jao-gnus-leafnode-spool)
  131. (separator "/"))))
  132. (jao-gnus-use-gmane '(nntp "news.gmane.io"))
  133. (t '(nnnil ""))))
  134. (setq gnus-secondary-select-methods '())
  135. (setq nnheader-read-timeout 0.02
  136. gnus-save-newsrc-file nil) ; .newsrc only needed by other newsreaders
  137. ;; leafnode articles group parameters
  138. (defvar jao-gnus-image-groups '("xkcd"))
  139. (defvar jao-gnus-leafnode-group-params
  140. `((,(format "gwene\\..*%s.*" (regexp-opt jao-gnus-image-groups))
  141. (mm-html-inhibit-images nil)
  142. (mm-html-blocked-images nil))
  143. ("\\(gmane\\|gwene\\)\\..*"
  144. (jao-gnus--archiving-group "nnml:feeds.trove")
  145. (posting-style (address "jao@gnu.org")))))
  146. (when jao-gnus-use-leafnode
  147. (dolist (p jao-gnus-leafnode-group-params)
  148. (add-to-list 'gnus-parameters p t)))
  149. ;;; mail
  150. ;;;; nnmail
  151. (setq nnmail-treat-duplicates 'delete
  152. nnmail-scan-directory-mail-source-once nil
  153. nnmail-cache-accepted-message-ids t
  154. nnmail-message-id-cache-length 100000
  155. nnmail-split-fancy-with-parent-ignore-groups nil
  156. nnmail-use-long-file-names t
  157. nnmail-crosspost t
  158. nnmail-resplit-incoming t
  159. nnmail-mail-splitting-decodes t
  160. nnmail-split-methods 'nnmail-split-fancy)
  161. ;;;; nnml
  162. (setq gnus-message-archive-group nil
  163. nnml-get-new-mail t
  164. nnml-directory message-directory)
  165. (setq mail-sources
  166. (let* ((pwd (auth-source-pick-first-password :host "proton-bridge"))
  167. (mds (mapcar (lambda (f)
  168. `(maildir :path ,(expand-file-name f "~/var/mail/")))
  169. '("local/" "feeds/")))
  170. (ims (mapcar (lambda (b)
  171. `(imap :server "127.0.0.1" :port 1143
  172. :user "mail@jao.io" :password ,pwd
  173. :stream starttls :predicate "1:*"
  174. :fetchflag "\\Deleted \\Seen"
  175. :mailbox ,(concat "Labels/#" b)))
  176. '("inbox" "drivel" "hacking" "bills"
  177. "bigml" "prog" "words"))))
  178. (append mds ims)))
  179. (when jao-gnus-use-nnml
  180. (add-to-list
  181. 'gnus-secondary-select-methods
  182. `(nnml "" (gnus-search-engine gnus-search-recoll
  183. (remove-prefix ,(jao-gnus-dir "Mail/"))))))
  184. (when jao-gnus-use-nnml
  185. (dolist (p jao-gnus-nnml-group-params)
  186. (add-to-list 'gnus-parameters p t)))
  187. ;;;; imap
  188. (setq nnimap-quirks nil)
  189. (when jao-gnus-use-local-imap
  190. (add-to-list 'gnus-secondary-select-methods
  191. `(nnimap "" (nnimap-address "localhost"))))
  192. (when jao-gnus-use-pm-imap
  193. (add-to-list 'gnus-secondary-select-methods
  194. '(nnimap "pm"
  195. (nnimap-address "127.0.0.1")
  196. (nnimap-stream network)
  197. (nnimap-server-port 1143))))
  198. (when jao-gnus-use-gandi-imap
  199. (add-to-list 'gnus-secondary-select-methods
  200. '(nnimap "gandi" (nnimap-address "mail.gandi.net"))))
  201. ;;; groups
  202. (setq gnus-group-line-format
  203. " %m%S%p%3y%P%* %~(pad-right 30)G %B\n"
  204. ;; " %m%S%p%P:%~(pad-right 35)c %3y %B\n"
  205. ;; " %m%S%p%3y%P%* %~(pad-right 30)C %B\n"
  206. gnus-topic-line-format "%i[ %(%{%n%}%) -- %A ]%v\n"
  207. gnus-group-uncollapsed-levels 2
  208. gnus-auto-select-subject 'unread
  209. gnus-large-newsgroup 2000)
  210. (add-hook 'gnus-select-group-hook 'gnus-group-set-timestamp)
  211. (add-hook 'gnus-group-mode-hook 'gnus-topic-mode)
  212. ;;; rss
  213. (setq nnrss-use-local t ;; M-x nnrss-generate-download-script
  214. nnrss-ignore-article-fields '(category
  215. dc:creator
  216. dc:date
  217. enclosure
  218. guid
  219. link
  220. media:content
  221. media:thumbnail
  222. media:title
  223. post-id
  224. pubDate
  225. slash:comments))
  226. (add-to-list 'gnus-parameters `(,(format "nnrss:%s.*"
  227. (regexp-opt jao-gnus-image-groups t))
  228. (mm-html-inhibit-images nil)
  229. (mm-html-blocked-images nil)))
  230. ;;; summary
  231. ;;;; configuration
  232. (setq gnus-summary-ignore-duplicates t
  233. gnus-suppress-duplicates t
  234. ;; gnus-summary-ignored-from-addresses jao-mails-regexp
  235. gnus-process-mark-toggle t
  236. gnus-auto-select-next 'almost-quietly)
  237. ;;;; threading
  238. (setq gnus-face-1 'jao-gnus-face-tree
  239. gnus-show-threads t
  240. gnus-thread-hide-subtree t
  241. gnus-build-sparse-threads nil
  242. gnus-refer-thread-use-search t
  243. gnus-summary-make-false-root 'adopt
  244. gnus-summary-gather-subject-limit nil ;; 120
  245. gnus-summary-thread-gathering-function #'gnus-gather-threads-by-subject
  246. gnus-sort-gathered-threads-function 'gnus-thread-sort-by-date
  247. gnus-thread-sort-functions '(gnus-thread-sort-by-date))
  248. (defun jao-fix-protonmail-references (header)
  249. (let ((references (mail-header-references header)))
  250. (setf (mail-header-references header)
  251. (mapconcat #'(lambda (x)
  252. (if (string-match "protonmail.internalid" x) "" x))
  253. (gnus-split-references references)
  254. " "))
  255. header))
  256. (setq gnus-alter-header-function 'jao-fix-protonmail-references)
  257. ;;;; search on enter nnselect
  258. (defun jao-gnus--maybe-reselect (&rest _i)
  259. (when (string-match-p "^nnselect" (or (gnus-group-name-at-point) ""))
  260. (save-excursion (gnus-group-get-new-news-this-group))))
  261. (advice-add 'gnus-group-select-group :before #'jao-gnus--maybe-reselect)
  262. ;;;; summary line
  263. (setq gnus-not-empty-thread-mark ?↓) ; ↓) ?·
  264. (setq jao-gnus--summary-line-fmt
  265. (concat "%%U %%*%%R %%uj "
  266. "[ %%~(max-right 23)~(pad-right 23)uf "
  267. " %%I%%~(pad-left 2)t ] %%s"
  268. "%%-%s="
  269. "%%~(max-right 8)~(pad-left 8)&user-date;"
  270. "\n"))
  271. (defun jao-gnus--set-summary-line (&optional w)
  272. (let* ((d (if jao-gnus-use-three-panes (+ jao-gnus-groups-width 11) 12))
  273. (w (- (or w (window-width)) d)))
  274. (setq gnus-summary-line-format (format jao-gnus--summary-line-fmt w))))
  275. (add-hook 'gnus-select-group-hook 'jao-gnus--set-summary-line)
  276. ;; (jao-gnus--set-summary-line 187)
  277. (add-to-list 'nnmail-extra-headers 'Cc)
  278. (add-to-list 'nnmail-extra-headers 'BCc)
  279. (use-package gnus-sum
  280. :config
  281. (add-to-list 'gnus-extra-headers 'Cc)
  282. (add-to-list 'gnus-extra-headers 'BCc))
  283. (defun gnus-user-format-function-j (headers)
  284. (let ((to (gnus-extra-header 'To headers)))
  285. (if (string-match jao-mails-regexp to)
  286. (if (string-match "," to) "¬" "»") ;; "~" "=")
  287. (if (or (string-match jao-mails-regexp
  288. (gnus-extra-header 'Cc headers))
  289. (string-match jao-mails-regexp
  290. (gnus-extra-header 'BCc headers)))
  291. "¬" ;; "~"
  292. " "))))
  293. (defconst jao-gnus--news-rx
  294. (concat (regexp-opt '("ElDiaro.es "
  295. "ElDiario.es - ElDiario.es: "
  296. "The Guardian: "
  297. "Aeon | a world of ideas: "
  298. "Planet Debian: "))
  299. "\\|The Conversation – Articles (.+): "
  300. "\\|unofficial mirror of [^:]+: "
  301. "\\|[gq].+ updates on arXiv.org: "))
  302. (defun gnus-user-format-function-f (headers)
  303. (let* ((from (gnus-header-from headers))
  304. (from (gnus-summary-extract-address-component from))
  305. (from (replace-regexp-in-string jao-gnus--news-rx "" from)))
  306. from))
  307. (setq gnus-user-date-format-alist
  308. '(((gnus-seconds-today) . "%H:%M")
  309. ((+ 86400 (gnus-seconds-today)) . "'%H:%M")
  310. ;; (604800 . "%a %H:%M") ;; that's one week
  311. ((gnus-seconds-month) . "%a %d")
  312. ((gnus-seconds-year) . "%b %d")
  313. (t . "%b '%y")))
  314. ;;;; moving messages around
  315. (defvar-local jao-gnus--spam-group nil)
  316. (defvar-local jao-gnus--archiving-group nil)
  317. (defvar-local jao-gnus--archive-as-copy-p nil)
  318. (defvar jao-gnus--last-move nil)
  319. (defun jao-gnus-move-hook (a headers c to d)
  320. (setq jao-gnus--last-move (cons to (mail-header-id headers))))
  321. (defun jao-gnus-goto-last-moved ()
  322. (interactive)
  323. (when jao-gnus--last-move
  324. (when (eq major-mode 'gnus-summary-mode) (gnus-summary-exit))
  325. (gnus-group-goto-group (car jao-gnus--last-move))
  326. (gnus-group-select-group)
  327. (gnus-summary-goto-article (cdr jao-gnus--last-move) nil t)))
  328. (add-hook 'gnus-summary-article-move-hook 'jao-gnus-move-hook)
  329. (defun jao-gnus-archive (follow)
  330. (interactive "P")
  331. (if jao-gnus--archiving-group
  332. (progn
  333. (if (or jao-gnus--archive-as-copy-p
  334. (not (gnus-check-backend-function
  335. 'request-move-article gnus-newsgroup-name)))
  336. (gnus-summary-copy-article nil jao-gnus--archiving-group)
  337. (gnus-summary-move-article nil jao-gnus--archiving-group))
  338. (when follow (jao-gnus-goto-last-moved)))
  339. (gnus-summary-mark-as-read)
  340. (gnus-summary-delete-article)))
  341. (defun jao-gnus-archive-tickingly ()
  342. (interactive)
  343. (gnus-summary-tick-article)
  344. (jao-gnus-archive)
  345. (when jao-gnus--archive-as-copy-p
  346. (gnus-summary-mark-as-read)))
  347. (defun jao-gnus-show-tickled ()
  348. (interactive)
  349. (gnus-summary-limit-to-marks "!"))
  350. (make-variable-buffer-local
  351. (defvar jao-gnus--trash-group nil))
  352. (defun jao-gnus-trash ()
  353. (interactive)
  354. (gnus-summary-mark-as-read)
  355. (if jao-gnus--trash-group
  356. (gnus-summary-move-article nil jao-gnus--trash-group)
  357. (gnus-summary-delete-article)))
  358. (defun jao-gnus-move-to-spam ()
  359. (interactive)
  360. (gnus-summary-mark-as-read)
  361. (gnus-summary-move-article nil jao-gnus--spam-group))
  362. (define-key gnus-summary-mode-map "Ba" 'jao-gnus-archive)
  363. (define-key gnus-summary-mode-map "BA" 'jao-gnus-archive-tickingly)
  364. (define-key gnus-summary-mode-map "Bl" 'jao-gnus-goto-last-moved)
  365. (define-key gnus-summary-mode-map (kbd "B DEL") 'jao-gnus-trash)
  366. (define-key gnus-summary-mode-map (kbd "B <backspace>") 'jao-gnus-trash)
  367. (define-key gnus-summary-mode-map "Bs" 'jao-gnus-move-to-spam)
  368. (define-key gnus-summary-mode-map "/!" 'jao-gnus-show-tickled)
  369. (define-key gnus-summary-mode-map [f7] 'gnus-summary-force-verify-and-decrypt)
  370. ;;;; saving emails
  371. (setq gnus-default-article-saver 'gnus-summary-save-article-mail)
  372. (defvar jao-gnus-file-save-directory (expand-file-name "~/tmp"))
  373. (defun jao-gnus-file-save (newsgroup headers &optional last-file)
  374. (expand-file-name (format "%s.eml" (mail-header-subject headers))
  375. jao-gnus-file-save-directory))
  376. (setq gnus-mail-save-name 'jao-gnus-file-save)
  377. ;;;; arXiv capture
  378. (use-package org-capture
  379. :config
  380. (add-to-list 'org-capture-templates
  381. '("X" "arXiv" entry (file "notes/physics/arxiv.org")
  382. "* %:subject\n\n(jao-gnus-org-paragraph \"%i\")"
  383. :immediate-finish t)
  384. t)
  385. (org-capture-upgrade-templates org-capture-templates))
  386. (defvar jao-gnus-org-url nil)
  387. (defun jao-gnus-org-paragraph (x)
  388. (with-temp-buffer
  389. (insert " " (string-trim (or x "")) "\n ")
  390. (goto-char 0)
  391. (fill-paragraph)
  392. (goto-char (point-max))
  393. (open-rectangle 0 (point))
  394. (concat (buffer-string) "\n " (or jao-gnus-org-url ""))))
  395. (defun jao-gnus-arXiv-capture ()
  396. (interactive)
  397. (unless (derived-mode-p '(gnus-summary-mode)) (gnus-article-show-summary))
  398. (setq jao-subject (gnus-summary-article-subject))
  399. (gnus-summary-select-article-buffer)
  400. (gnus-article-goto-part 0)
  401. (setq-local transient-mark-mode 'lambda)
  402. (set-mark (point))
  403. (forward-paragraph)
  404. (save-excursion
  405. (when (re-search-forward "^Link" nil t)
  406. (beginning-of-line)
  407. (setq jao-gnus-org-url (org-eww-url-below-point))))
  408. (org-capture nil "X")
  409. (set-mark (point))
  410. (gnus-article-show-summary))
  411. ;;; article
  412. ;;;; config, headers
  413. (setq mail-source-delete-incoming t)
  414. (setq gnus-gcc-mark-as-read t)
  415. (setq gnus-treat-display-smileys nil)
  416. (setq gnus-treat-fill-long-lines nil)
  417. (setq gnus-treat-fill-article 120)
  418. (setq gnus-treat-fold-headers nil)
  419. (setq gnus-treat-strip-leading-blank-lines t)
  420. (setq gnus-article-auto-eval-lisp-snippets nil)
  421. (setq gnus-posting-styles '((".*" (name "Jose A. Ortega Ruiz"))))
  422. (setq gnus-single-article-buffer nil)
  423. (setq gnus-article-update-lapsed-header 60)
  424. (setq gnus-article-update-date-headers 60)
  425. (with-eval-after-load "gnus-art"
  426. (setq gnus-visible-headers
  427. (concat
  428. gnus-visible-headers
  429. "\\|^List-[iI][Dd]:\\|^X-Newsreader:\\|^X-Mailer:"
  430. "\\|^User-Agent:\\|^X-User-Agent:\\|^X-RSS-Feed:")))
  431. ;;;; html and images
  432. (setq gnus-button-url 'browse-url-generic
  433. gnus-inhibit-images t
  434. mm-discouraged-alternatives nil ;; '("text/html" "text/richtext")
  435. mm-inline-large-images 'resize)
  436. (defvar-local jao-gnus--images nil)
  437. (defun jao-gnus--init-images ()
  438. (with-current-buffer gnus-article-buffer
  439. (setq jao-gnus--images nil)))
  440. (add-hook 'gnus-select-article-hook #'jao-gnus--init-images)
  441. (defun jao-gnus-browse-html ()
  442. (interactive)
  443. (let ((browse-url-browser-function jao-browse-url-external-function)
  444. (browse-url-handlers nil)
  445. (browse-url-default-handlers nil))
  446. (gnus-article-browse-html-article)))
  447. (defun jao-gnus-show-images ()
  448. (interactive)
  449. (if window-system
  450. (save-window-excursion
  451. (gnus-summary-select-article-buffer)
  452. (save-excursion
  453. (if (and jao-afio-use-w3m (fboundp 'w3m-toggle-inline-images))
  454. (w3m-toggle-inline-images)
  455. (setq jao-gnus--images (not jao-gnus--images))
  456. (if jao-gnus--images
  457. (gnus-article-show-images)
  458. (gnus-article-remove-images)))))
  459. (jao-gnus-browse-html)))
  460. ;;;; format from:
  461. (defvar jao-gnus--from-rx
  462. (concat "From: \\\"?\\( *" jao-gnus--news-rx "\\)"))
  463. (defun jao-gnus-format-from ()
  464. (save-excursion
  465. (goto-char (point-min))
  466. (when (re-search-forward jao-gnus--from-rx nil t)
  467. (replace-match "" nil nil nil 1))))
  468. (add-hook 'gnus-part-display-hook 'jao-gnus-format-from)
  469. ;;;; follow links and enclosures
  470. (defun jao-gnus-follow-link (&optional external)
  471. (interactive "P")
  472. (when (eq major-mode 'gnus-summary-mode)
  473. (gnus-summary-select-article-buffer))
  474. (save-excursion
  475. (goto-char (point-min))
  476. (when (or (search-forward-regexp "^Via: h" nil t)
  477. (search-forward-regexp "^URL: h" nil t)
  478. (and (search-forward-regexp "^Link$" nil t)
  479. (not (beginning-of-line))))
  480. (cond (external (jao-browse-with-external-browser))
  481. ((featurep 'jao-custom-eww) (eww (jao-url-around-point)))
  482. (t (browse-url (jao-url-around-point)))))))
  483. (defun jao-gnus-from-eww (keep-eww-buffer)
  484. (interactive "P")
  485. (unless keep-eww-buffer (jao-eww-close))
  486. (jao-afio-goto-mail)
  487. (gnus-article-show-summary))
  488. (with-eval-after-load 'eww
  489. (define-key eww-mode-map (kbd "h") #'jao-gnus-from-eww))
  490. (defun jao-gnus-open-enclosure ()
  491. (interactive)
  492. (save-window-excursion
  493. (gnus-summary-select-article-buffer)
  494. (save-excursion
  495. (goto-char (point-min))
  496. (let ((offset (or (and (search-forward-regexp "^Enclosure: " nil t) 2)
  497. (and (search-forward-regexp "^Enclosure$" nil t) -2))))
  498. (when offset (forward-char offset))
  499. (if-let ((url (jao-url-around-point)))
  500. (jao-mpc-add-or-play-url url)
  501. (error "No enclosure found"))))))
  502. ;;;; delayed messages
  503. (require 'gnus-util)
  504. (gnus-delay-initialize)
  505. (setq gnus-delay-default-delay "3h")
  506. (eval-after-load "message"
  507. '(setq message-draft-headers (remove 'Date message-draft-headers)))
  508. ;;; daemon and exit
  509. (setq gnus-interactive-exit t)
  510. (defun jao-quit-gnus () (gnus-group-exit) t)
  511. (add-hook 'kill-emacs-query-functions #'jao-quit-gnus)
  512. ;; daemon config
  513. (setq mail-user-agent 'gnus-user-agent)
  514. (setq gnus-asynchronous t)
  515. (setq gnus-use-article-prefetch nil)
  516. (setq gnus-save-killed-list nil)
  517. (setq gnus-check-new-newsgroups nil)
  518. (require 'gnus-demon)
  519. (defun jao-gnus--scan ()
  520. (let ((inhibit-message t))
  521. (gnus-demon-scan-news)
  522. (jao-gnus--notify)))
  523. (defun jao-gnus-add-demon ()
  524. (interactive)
  525. (gnus-demon-add-handler 'jao-gnus--scan 5 1))
  526. (jao-gnus-add-demon)
  527. (gnus-demon-init)
  528. ;; this is, in theory, not needed; but at some point in the way to emacs
  529. ;; version 31 this idle timers have ceased to work after a sleep/awake cycle
  530. (add-to-list 'jao-sleep-awake-functions #'jao-gnus-add-demon)
  531. ;;; add-ons
  532. ;;;; notifications
  533. ;;;;; minibuffer
  534. (defvar jao-gnus-tracked-groups
  535. (let ((feeds (thread-first
  536. (directory-files mail-source-directory nil "feeds\\.[^e]")
  537. (seq-difference '("feeds.trove")))))
  538. `(("nnml:bigml\\.inbox" "B" jao-themes-f00)
  539. ("nnml:bigml\\.alba" "A" jao-themes-f00)
  540. ("nnml:bigml\\.bugs" "b" jao-themes-error)
  541. ("nnml:bigml\\.support" "S" default)
  542. ("nnml:jao\\.\\(inbox\\|trove\\)" "I" jao-themes-f01)
  543. ("nnml:bigml\\.[^aibs]" "W" jao-themes-dimm)
  544. ("nnml:jao.hacking" "H" jao-themes-dimm)
  545. ("nnml:jao.write" "W" jao-themes-warning)
  546. ("nnml:jao.[^isthw]" "J" jao-themes-dimm)
  547. (,(format "^nnml:%s" (regexp-opt feeds)) "F" jao-themes-dimm)
  548. ("feeds\\.e" "E" jao-themes-dimm)
  549. ("nnml:local" "l" jao-themes-dimm)
  550. ("nnrss:.*" "R" jao-themes-dimm)
  551. ("^\\(gwene\\|gmane\\)\\." "N" jao-themes-dimm))))
  552. (defun jao-gnus--unread-counts ()
  553. (seq-reduce (lambda (r g)
  554. (let ((n (gnus-group-unread (car g))))
  555. (if (and (numberp n) (> n 0)) (cons (cons (car g) n) r) r)))
  556. gnus-newsrc-alist
  557. ()))
  558. (defun jao-gnus--unread-label (counts rx label face)
  559. (let ((n (seq-reduce (lambda (n c)
  560. (if (string-match-p rx (car c)) (+ n (cdr c)) n))
  561. counts
  562. 0)))
  563. (when (> n 0) `(:propertize ,(format "%s%d " label n) face ,face))))
  564. (defvar jao-gnus--notify-strs ())
  565. (defun jao-gnus--notify-strs ()
  566. (let ((counts (jao-gnus--unread-counts)))
  567. (seq-filter #'identity
  568. (seq-map (lambda (args)
  569. (apply 'jao-gnus--unread-label counts args))
  570. jao-gnus-tracked-groups))))
  571. (defun jao-gnus--notify ()
  572. (setq jao-gnus--notify-strs (jao-gnus--notify-strs))
  573. (jao-minibuffer-refresh))
  574. (with-eval-after-load "jao-minibuffer"
  575. (jao-minibuffer-add-variable 'jao-gnus--notify-strs -20))
  576. (add-hook 'gnus-started-hook #'jao-gnus--notify)
  577. ;; (add-hook 'gnus-summary-exit-hook #'jao-gnus--notify)
  578. (add-hook 'gnus-after-getting-new-news-hook #'jao-gnus--notify)
  579. ;;;;; agenda and other updates on summary exit
  580. (let ((exit-count 0))
  581. (defun jao-gnus--on-summary-exit ()
  582. (when (> (setq exit-count (+ 1 exit-count)) 20)
  583. (setq exit-count 0)
  584. (jao-org-agenda))
  585. (jao-gnus--notify)))
  586. (add-hook 'gnus-summary-exit-hook #'jao-gnus--on-summary-exit)
  587. ;;;; open mail file in gnus
  588. (defun jao-gnus-file-to-group (file &optional maildir newsdir m-server n-server)
  589. "Compute the Gnus group name from the given file name.
  590. IN: /home/jao/.emacs.d/gnus/Mail/jao.trove/32, /home/jao/.emacs.d/gnus/Mail/
  591. OUT: nnml:jao.trove "
  592. (let* ((maildir (or maildir message-directory))
  593. (newsdir (or newsdir jao-gnus-leafnode-spool))
  594. (m-server (or m-server "nnml"))
  595. (n-server (or n-server "nntp+localhost"))
  596. (nntp (and newsdir (string-match-p newsdir file)))
  597. (g (directory-file-name (file-name-directory file)))
  598. (g (replace-regexp-in-string (file-name-as-directory maildir) "" g))
  599. (g (replace-regexp-in-string (file-name-as-directory newsdir) "" g))
  600. (g (cond (nntp (concat n-server ":" g))
  601. ((file-name-directory g)
  602. (replace-regexp-in-string "^\\([^/]+\\)/"
  603. (concat m-server ":\\1/")
  604. (file-name-directory g) t))
  605. (t (concat m-server ":" g))))
  606. (g (replace-regexp-in-string "/" "." g))
  607. (g (replace-regexp-in-string "[/.]$" "" g)))
  608. (cond ((string-match ":$" g) (concat g "inbox"))
  609. (nntp g)
  610. (t (replace-regexp-in-string ":\\." ":" g)))))
  611. (defun jao-gnus-goto-file (filename &optional _page)
  612. (let ((group (jao-gnus-file-to-group filename))
  613. (id (file-name-nondirectory filename)))
  614. (if (and group id)
  615. (org-gnus-follow-link group id)
  616. (message "Couldn't get relevant info for switching to Gnus."))))
  617. ;;;; afio
  618. (defun jao-gnus--on-afio-switch ()
  619. (when (derived-mode-p 'gnus-group-mode)
  620. (let ((no (or (gnus-group-unread (gnus-group-group-name)) 0)))
  621. (unless (> no 0) (gnus-group-first-unread-group)))))
  622. (add-hook 'jao-afio-switch-hook #'jao-gnus--on-afio-switch)
  623. (defun jao-gnus-refresh-workspace ()
  624. (interactive)
  625. (save-window-excursion (calendar) (jao-org-agenda)))
  626. ;;;; gnus-icalendar
  627. (require 'ol-gnus)
  628. (use-package gnus-icalendar
  629. :demand t
  630. :init (setq gnus-icalendar-org-capture-file
  631. (expand-file-name "inbox.org" org-directory)
  632. gnus-icalendar-org-capture-headline '("Appointments"))
  633. :config (gnus-icalendar-org-setup))
  634. ;;;; bbdb
  635. (with-eval-after-load "bbdb"
  636. ;; (bbdb-initialize 'gnus 'message 'pgp)
  637. (bbdb-mua-auto-update-init 'gnus)
  638. (with-eval-after-load "gnus-sum"
  639. (define-key gnus-summary-mode-map ":" 'bbdb-mua-annotate-sender)
  640. (define-key gnus-summary-mode-map ";" 'bbdb-mua-annotate-recipients)))
  641. ;;;; randomsig
  642. (with-eval-after-load "randomsig"
  643. (with-eval-after-load "gnus-sum"
  644. (define-key gnus-summary-save-map "-" 'gnus/randomsig-summary-read-sig)))
  645. ;;;; recoll
  646. (unless jao-notmuch-enabled
  647. (with-eval-after-load "org"
  648. (org-link-set-parameters "message" :follow #'jao-gnus-goto-file))
  649. (with-eval-after-load "consult-recoll"
  650. (add-to-list 'consult-recoll-open-fns
  651. '("message/rfc822" . jao-gnus-goto-file))))
  652. ;;;; notmuch
  653. (use-package jao-notmuch-gnus
  654. :demand t)
  655. (jao-load-path "consult-notmuch")
  656. (use-package consult-notmuch
  657. :bind (:map gnus-group-mode-map ("S" . #'jao-gnus-consult-notmuch)))
  658. ;;; keyboard shortcuts
  659. (define-key gnus-article-mode-map "i" 'jao-gnus-show-images)
  660. (define-key gnus-summary-mode-map "i" 'jao-gnus-show-images)
  661. (define-key gnus-article-mode-map "\M-g" 'jao-gnus-follow-link)
  662. (define-key gnus-summary-mode-map "\M-g" 'jao-gnus-follow-link)
  663. (define-key gnus-summary-mode-map "v" 'scroll-other-window)
  664. (define-key gnus-summary-mode-map "V" 'scroll-other-window-down)
  665. (define-key gnus-summary-mode-map "X" 'jao-gnus-arXiv-capture)
  666. (define-key gnus-summary-mode-map "e" 'jao-gnus-open-enclosure)
  667. (define-key gnus-summary-mode-map "\C-l" nil)
  668. (define-key gnus-group-mode-map "a" 'jao-gnus-refresh-workspace)