ambevar-dotfiles/.emacs.d/lisp/init-message.el

184 lines
8.3 KiB
EmacsLisp
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

;; Message mode
;; This is common to Gnus, mu4e, notmuch, etc.
;;; TODO: Is it possible to mbsync without attachments?
(require 'init-smtpmail)
(setq user-full-name "Pierre Neidhardt"
mm-default-directory "~/temp" ; Where to save attachments.
;; Don't keep sent e-mail buffer. (Also see mu4e-conversation-kill-buffer-on-exit.)
message-kill-buffer-on-exit t
message-send-mail-function 'smtpmail-send-it
;; The following is only useful for sending mail with msmtp?
mail-specify-envelope-from t
mail-envelope-from 'header)
;; TODO: Use the following to automatically set the From: field when replying.
;; Might not be necessary.
;; `message-alternative-emails'
;; Also see the `gnus-alias' and `smtpmail-multi' packages.
;; https://old.reddit.com/r/emacs/comments/5iievm/nice_email_configuration_using_emacs_mbsync/
(defun message-recipients (&optional include-from)
"Return a list of all recipients in the message, looking at TO, CC and BCC.
Each recipient is in the format of `mail-extract-address-components'."
(mapcan (lambda (header)
(let ((header-value (message-fetch-field header)))
(and
header-value
(mail-extract-address-components header-value t))))
`(,@(when include-from '("From")) "To" "Cc" "Bcc")))
;; Sign messages by default. TODO: Which method?
(add-hook 'message-setup-hook 'mml-secure-sign-pgpmime)
;; (add-hook 'message-setup-hook 'mml-secure-message-sign-pgpmime)
;; Also crypt to self so that we can read sent e-mails.
(setq mml-secure-openpgp-encrypt-to-self t)
(defvar ambrevar/trust-threshold '(marignal full ultimate))
(defun ambrevar/trusted-addresses ()
"Return the list of trusted email addresses in the PGP keyring.
Trust is defined as per `ambrevar/trust-threshold'."
(let (valid-addresses)
(dolist (key (epg-list-keys (epg-make-context epa-protocol)) valid-addresses)
(dolist (user-id (epg-key-user-id-list key))
(when (memq (epg-user-id-validity user-id) '(marginal full ultimate))
(push (cadr (mail-extract-address-components (epg-user-id-string user-id)))
valid-addresses))))))
(defun message-sign-encrypt-if-all-keys-trusted ()
"Add MML tag to encrypt message when there is a key for each recipient.
Consider adding this function to `message-send-hook' to
systematically send encrypted emails when possible."
(let* ((recipients (message-recipients))
(untrusted-recipients (seq-difference recipients (ambrevar/trusted-addresses))))
(unless untrusted-recipients
(mml-secure-message-sign-encrypt)
;; (warn "Not encrypting because of untrusted %s" untrusted-recipients)
)))
;; TODO: Test and report upstream (Emacs + Notmuch).
(add-hook 'message-send-hook #'message-sign-encrypt-if-all-keys-trusted)
;; Fix replying to GitHub. TODO: Does not work.
(defun ambrevar/message-alter-bogus-github-recipients (addrcell)
"Discard @reply.github.com mail addresses.
ADDRCELL is a cons cell where the car is the mail address and the
cdr is the complete address (full name and mail address)."
(unless (string-match (rx "@noreply.github.com" string-end)
(cdr addrcell))
addrcell))
(setq message-alter-recipients-function 'ambrevar/message-alter-bogus-github-recipients)
(defvar ambrevar/message-compose-fortune-p nil
"Whether or not to include a fortune in the signature.")
(defun ambrevar/message-add-signature-and-maybe-fortune ()
"Insert signature using `user-full-name'.
A fortune is appended if `ambrevar/message-compose-fortune-p' is non-nil."
(require 'functions) ; For `call-process-to-string'.
;; Return the signature and set it for mu4e.
(setq mu4e-compose-signature
(concat
user-full-name "\n"
"https://ambrevar.xyz/"
(when (and ambrevar/message-compose-fortune-p
(executable-find "fortune"))
(concat "\n\n"
(ambrevar/call-process-to-string "fortune" "-s"))))))
;; (add-hook 'message-setup-hook 'ambrevar/message-add-signature-and-maybe-fortune)
(setq message-signature 'ambrevar/message-add-signature-and-maybe-fortune)
(when (require 'org-contacts nil t)
(defun ambrevar/message-select-dictionary ()
"Set dictionary according to the LANGUAGE property of the first
\"To:\" recipient found in the Org contacts file."
(interactive)
(let ((addresses (mapcar 'cadr (message-recipients)))
address-lang-map)
(setq address-lang-map
(cl-loop for contact in (org-contacts-filter)
;; The contact name is always the car of the assoc-list
;; returned by `org-contacts-filter'.
for language = (cdr (assoc-string "LANGUAGE" (nth 2 contact)))
;; Build the list of the user email addresses.
for email-list = (org-contacts-split-property
(or (cdr (assoc-string org-contacts-email-property
(nth 2 contact))) ""))
if (and email-list language)
;; Build an alist of (EMAIL . LANGUAGE).
nconc (cl-loop for email in email-list
collect (cons (downcase email) language))))
(while addresses
(if (not (assoc (car addresses) address-lang-map))
(setq addresses (cdr addresses))
(ispell-change-dictionary (cdr (assoc (car addresses) address-lang-map)))
(setq addresses nil)))))
(add-hook 'message-setup-hook 'ambrevar/message-select-dictionary))
;; Because it's to tempting to send an e-mail riddled with typos...
(add-hook 'message-setup-hook 'flyspell-mode)
;; Org capture for emails in org-contacts
(when (require 'org-contacts nil 'noerror)
;; TODO: Don't duplicate contacts.
(defun ambrevar/message-complete-address ()
(require 'subr-x)
;; Need to get last message buffer since Org capture happens in a different
;; buffer.
(let ((last-buffer
(cl-loop for buffer in (buffer-list)
when (with-current-buffer buffer
(or (derived-mode-p 'notmuch-show-mode)
(derived-mode-p 'message-mode)))
return buffer)))
(save-window-excursion
(with-current-buffer last-buffer
(let* ((recipients (message-recipients 'include-from))
(addresses-names (mapcar
(lambda (s)
(concat (cadr s) " " (car s)))
recipients))
(email-at-point (let ((email (thing-at-point 'email)))
(when email
(string-trim email "<" ">"))))
(default (when email-at-point (seq-find (lambda (addr)
(string-prefix-p email-at-point
addr))
addresses-names)))
(address-name (completing-read "Address: " addresses-names
nil nil nil nil default))
(idx (string-match " " address-name))
(address (substring address-name 0 idx))
(name (substring address-name idx)))
(list address name))))))
(defun ambrevar/org-capture-contact-format (address name)
(format "%s
:PROPERTIES:
:EMAIL: %s
:END:" name address))
(add-to-list 'org-capture-templates
`("C" "Add e-mail address to contacts" entry (file+headline ,(car org-contacts-files) "Contacts")
"* %(apply 'ambrevar/org-capture-contact-format (ambrevar/message-complete-address))")))
;; The following is an alternative using the template format string. It has some missing features though:
;; - Can't use (thing-at-point 'email) as a default.
;; - Need to manually match Name and Address.
;; (add-to-list 'org-capture-templates
;; `("c" "Add e-mail address to contacts" entry (file+headline ,(car org-contacts-files) "Contacts")
;; "* %^{Name|%:fromname|%:to-names|%:cc-names}
;; :PROPERTIES:
;; :EMAIL: %^{Address|%:fromaddress|%:to-addresses|%:cc-addresses}
;; :END:"))
(provide 'init-message)