conf-mu4e.el 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. ;;; Code:
  2. (require 'mu4e)
  3. (require 'mu4e-contrib)
  4. (require 'mu4e-icalendar)
  5. (require 'message-view-patch)
  6. (require 'smtpmail-async)
  7. (require 'buffer-defuns)
  8. (require 'mml-sec)
  9. (require 'shr-color)
  10. ;; Custom vars
  11. (defcustom distopico:mu4e-inbox-update-modeline-interval 300
  12. "Inbox update interval."
  13. :type 'integer
  14. :group 'mu4e)
  15. (defcustom distopico:message-attachment-reminder
  16. "Are you sure you want to send this message without any attachment? "
  17. "Message to display and ask when there is a missing attachment.
  18. The default question asked when trying to send a message
  19. if contain `distopico:message-attachment-intent-re' without an
  20. actual attachment."
  21. :type 'string
  22. :group 'mu4e)
  23. (defcustom distopico:message-attachment-intent-re
  24. (regexp-opt '("I attach"
  25. "I have attached"
  26. "I've attached"
  27. "I have included"
  28. "I've included"
  29. "see the attached"
  30. "see the attachment"
  31. "attached file"
  32. "archivo incluido"
  33. "adjunto archivo"
  34. "ver adjunto"
  35. "adjunto va"
  36. "archivo adjunto"))
  37. "Regex of words that if are found will show warning and prevent send the message.
  38. If any of those are found in the message and if there is no attachment - should
  39. launch the no-attachment warning message defined in
  40. `distopico:message-attachment-reminder'."
  41. :type 'list
  42. :group 'mu4e)
  43. (defvar distopico:mu4e-get-mail-command
  44. (concat "flock -E 0 /tmp/offlineimap.lock python "
  45. (in-emacs-d "scripts/offlineimap-notify/offlineimap_notify.py"))
  46. "The default mu4e command to get emails.")
  47. (defvar distopico:mu4e-new-mail nil
  48. "Boolean to represent if there is new mail.")
  49. (defvar distopico:mu4e-mode-line-format nil
  50. "String to display in the mode line.")
  51. (defvar distopico:mu4e-update-timer nil
  52. "Interval timer object.")
  53. (defvar distopico:mu4e-contexts)
  54. (defvar distopico:mu4e-account-alist)
  55. ;; General mu4e config
  56. (setq mu4e-mu-home (concat (getenv "HOME") "/.mu")
  57. mu4e-get-mail-command distopico:mu4e-get-mail-command
  58. mu4e-confirm-quit nil
  59. mu4e-compose-keep-self-cc nil
  60. mu4e-compose-complete-addresses t
  61. mu4e-compose-dont-reply-to-self t
  62. ;; mu4e-change-filenames-when-moving - enable after migrate to mbsync
  63. mu4e-hide-index-messages t
  64. mu4e-headers-auto-update t
  65. mu4e-notification-support t
  66. mu4e-use-fancy-chars t
  67. mu4e-split-view 'horizontal
  68. mu4e-update-interval 300
  69. mu4e-context-policy 'pick-first
  70. mu4e-headers-visible-lines 20
  71. mu4e-headers-leave-behavior 'ask
  72. mu4e-headers-fields '((:human-date . 12)
  73. (:flags . 10)
  74. (:mailing-list . 10)
  75. (:from . 22)
  76. (:subject . nil)))
  77. ;; Set mu4e as default email emacs email composing client
  78. (setq mail-user-agent #'mu4e-user-agent
  79. message-mail-user-agent t)
  80. ;; Compose mail with gnus.
  81. (setq read-mail-command 'gnus
  82. mail-user-agent 'gnus-user-agent);;gnus-user-agent mu4e-user-agent
  83. ;; Bookmarks and shortcuts
  84. (setq mu4e-maildir-shortcuts
  85. '(("/1-Distopico/INBOX" . ?d)
  86. ("/2-vXcamiloXv/INBOX" . ?c)
  87. ("/3-AccionVisual/INBOX" . ?a)
  88. ("/4-RadioLiberacion/INBOX" . ?r)
  89. ("/5-TienditaVegan/INBOX" . ?t)))
  90. (setq mu4e-bookmarks
  91. '(("maildir:/\/*\/INBOX/" "All Inbox" ?i)
  92. ("maildir:/1-Distopico/INBOX" "[Distopico] All" ?D)
  93. ("flag:unread AND maildir:/1-Distopico/INBOX" "[Distopico] Unread Inbox" ?d)
  94. ("maildir:/2-vXcamiloXv/INBOX" "[vXcamiloXv] All" ?C)
  95. ("flag:unread AND maildir:/2-vXcamiloXv/INBOX" "[vXcamiloXv] Unread Inbox" ?c)
  96. ("flag:unread AND NOT flag:trashed AND NOT maildir:/\/*\/Spam*/ AND NOT maildir:/\/*\/Trash*/" "Unread All Inbox" ?u)
  97. ("flag:unread" "Unread All" ?U)
  98. ("date:today..now AND NOT maildir:/\/*\/Spam*/" "Today's messages" ?t)
  99. ("date:7d..now AND NOT maildir:/\/*\/Spam*/" "Last 7 days" ?w)
  100. ("maildir:/\/*\/Sent*/" "Sent messages" ?s)
  101. ("maildir:/\/*\/Trash/ OR maildir:/\/*\/Junk/" "All Trash" ?T)
  102. ("mime:image/* AND NOT maildir:/\/*\/Spam*/" "Messages with images" ?p)
  103. ("flag:unread AND maildir:/\/*\/Spam*/" "Unread spam" ?S)))
  104. ;; Custom marks
  105. (setq mu4e-headers-draft-mark '("D" . "⚒ ")
  106. mu4e-headers-flagged-mark '("F" . "✚ ")
  107. mu4e-headers-new-mark '("N" . "✱ ")
  108. mu4e-headers-passed-mark '("P" . "❯ ")
  109. mu4e-headers-replied-mark '("R" . "❮ ")
  110. mu4e-headers-seen-mark '("S" . "✔ ")
  111. mu4e-headers-trashed-mark '("T" . "⏚ ")
  112. mu4e-headers-attach-mark '("a" . "✉ ")
  113. mu4e-headers-encrypted-mark '("x" . "⚴ ")
  114. mu4e-headers-signed-mark '("s" . "☡ ")
  115. mu4e-headers-unread-mark '("u" . "⚐ "))
  116. ;; Config Messages
  117. (setq message-kill-buffer-on-exit t
  118. message-signature-insert-empty-line t
  119. message-citation-line-function 'message-insert-formatted-citation-line
  120. ;; Other format: message-citation-line-format "%N @ %Y-%m-%d %H:%M %Z:\n" "On %Y-%m-%d, %f wrote:" "On %Y-%m-%d %a at %H:%M %Z, %f wrote:\n"
  121. message-citation-line-format "On %Y-%m-%d, %f wrote:\n")
  122. (when (fboundp 'org-mime-htmlize)
  123. (add-hook 'message-mode-hook (lambda () (local-set-key "\C-c\M-o" 'org-mime-htmlize))))
  124. ;; use imagemagick, if available
  125. (when (fboundp 'imagemagick-register-types)
  126. (imagemagick-register-types))
  127. ;;Org config
  128. (defalias 'org-mail 'org-mu4e-compose-org-mode)
  129. (setq mu4e-org-contacts-file "~/Documents/org/contacts.org")
  130. ;; Actions
  131. (add-to-list 'mu4e-view-actions
  132. '("View in browser" . mu4e-action-view-in-browser) t)
  133. (add-to-list 'mu4e-view-actions
  134. '("add contact org" . distopico:mu4e-action-add-org-contact) t)
  135. (add-to-list 'mu4e-headers-actions
  136. '("add contact org" . distopico:mu4e-action-add-org-contact) t)
  137. ;; Default dir attachment
  138. (setq mu4e-attachment-dir "~/Downloads")
  139. ;; Send async
  140. (setq message-send-mail-function 'smtpmail-send-it) ;; allow: async-smtpmail-send-it or smtpmail-send-it
  141. ;; Not start in queuing mode
  142. (setq smtpmail-queue-mail nil
  143. smtpmail-smtp-user t)
  144. ;; Config by mu4e Account
  145. (ignore-errors
  146. (load (expand-file-name ".conf-private.gpg" "~/") t))
  147. ;; Default Account
  148. (setq mu4e-sent-folder "/1-Distopico/Sent"
  149. mu4e-drafts-folder "/1-Distopico/Drafts"
  150. mu4e-trash-folder "/1-Distopico/Trash"
  151. mu4e-refile-folder "/1-Distopico/Archive")
  152. ;; Sign/Encrypt
  153. (setq mml-secure-openpgp-encrypt-to-self t
  154. mml-secure-openpgp-sign-with-sender t)
  155. ;; Calendar support
  156. (mu4e-icalendar-setup)
  157. (gnus-icalendar-org-setup)
  158. ;; Custom keymap
  159. (define-key mu4e-main-mode-map "r" 'distopico:mu4e-maildirs-force-update)
  160. (define-key mu4e-main-mode-map (kbd "q") 'distopico:mu4e-close)
  161. (define-key mu4e-main-mode-map (kbd "C-q") 'distopico:mu4e-close)
  162. (define-key mu4e-headers-mode-map (kbd "C-q") 'distopico:mu4e-kill-close)
  163. (define-key mu4e-headers-mode-map (kbd "C-x k") 'distopico:mu4e-kill-close)
  164. (define-key mu4e-headers-mode-map (kbd "C-c o c") 'mu4e-org-store-and-capture)
  165. (define-key mu4e-headers-mode-map "o" 'distopico:mu4e-toggle-headers-include-related)
  166. (define-key mu4e-view-mode-map (kbd "C-q") 'distopico:mu4e-kill-close)
  167. (define-key mu4e-view-mode-map (kbd "C-x k") 'distopico:mu4e-kill-close)
  168. (define-key mu4e-view-mode-map (kbd "C-c o c") 'mu4e-org-store-and-capture)
  169. ;;------------------
  170. ;; Functions
  171. ;;------------------
  172. (defun distopico:mu4e--default-folders (acc-path settings)
  173. "Get default folders based in `ACC-PATH' and merge it with `SETTINGS'."
  174. (append `((mu4e-sent-folder . ,(format "/%s/Sent" acc-path))
  175. (mu4e-drafts-folder . ,(format "/%s/Drafts" acc-path))
  176. (mu4e-trash-folder . ,(format "/%s/Trash" acc-path))
  177. (mu4e-refile-folder . ,(format "/%s/Archive" acc-path)))
  178. settings))
  179. (defun distopico:mu4e--maildir-matches-p (maildir-rx)
  180. "Determine if the `maildir' match with `MAILDIR-RX' regexp."
  181. (apply-partially
  182. (lambda (maildir-rx msg)
  183. (when msg
  184. (string-match-p maildir-rx (mu4e-message-field msg :maildir))))
  185. maildir-rx))
  186. (defun mu4e-in-new-frame ()
  187. "Start mu4e in new frame."
  188. (interactive)
  189. (select-frame (make-frame))
  190. (mu4e))
  191. (defun distopico:mu4e-open ()
  192. "Open mu4e and remove other windows, save the state."
  193. (interactive)
  194. (open-buffer-delete-others mu4e-main-buffer-name :mu4e-fullscreen 'mu4e))
  195. (defun distopico:mu4e-close ()
  196. "Restore the previous window configuration and burry buffer."
  197. (interactive)
  198. (bury-buffer-restore-prev :mu4e-fullscreen))
  199. (defun distopico:mu4e-kill-close ()
  200. "Kill buffer and reload maildirs if headers."
  201. (interactive)
  202. (if (equal (buffer-name) "*mu4e-headers*")
  203. (progn
  204. (unless (eq major-mode 'mu4e-headers-mode)
  205. (mu4e-error "Must be in mu4e-headers-mode (%S)" major-mode))
  206. (mu4e-mark-handle-when-leaving)
  207. (let ((curbuf (current-buffer)) (curwin (selected-window))
  208. (headers-visible))
  209. (walk-windows
  210. (lambda (win)
  211. (with-selected-window win
  212. ;; if we the view window connected to this one, kill it
  213. (when (and (not (one-window-p win)) (eq mu4e~headers-view-win win))
  214. (delete-window win)
  215. (setq mu4e~headers-view-win nil)))
  216. ;; and kill any _other_ (non-selected) window that shows the current
  217. ;; buffer
  218. (when (and
  219. (eq curbuf (window-buffer win)) ;; does win show curbuf?
  220. (not (eq curwin win)) ;; it's not the curwin?
  221. (not (one-window-p))) ;; and not the last one?
  222. (delete-window win))))
  223. (kill-buffer (current-buffer))
  224. (mu4e~main-view))
  225. (distopico:mu4e-maildirs-force-update))
  226. (if (equal (buffer-name) "*mu4e-view*")
  227. (progn
  228. (kill-buffer (current-buffer))
  229. (delete-other-windows))
  230. (kill-buffer (current-buffer)))))
  231. (defun distopico:mu4e-toggle-headers-include-related ()
  232. "Toggle related `mu4e-headers-include-related' and refresh."
  233. (interactive)
  234. (setq mu4e-search-include-related
  235. (not mu4e-search-include-related))
  236. (mu4e-search-rerun))
  237. (defun distopico:mu4e-action-add-org-contact (msg)
  238. "Add an org-contact entry based address.
  239. From: address of the \
  240. current message `MSG' (in headers or view), You need to set
  241. `mu4e-org-contacts-file' to the full path to the file where you
  242. store your org-contacts."
  243. (unless (require 'org-capture nil 'noerror)
  244. (mu4e-error "The org-capture is not available"))
  245. (unless mu4e-org-contacts-file
  246. (mu4e-error "`mu4e-org-contacts-file' is not defined"))
  247. (let* ((sender (car-safe (mu4e-message-field msg :from)))
  248. (name (mu4e-contact-name sender))
  249. (email (mu4e-contact-email sender))
  250. (blurb
  251. (format
  252. (concat
  253. "** %s%%? \n"
  254. ":PROPERTIES:\n"
  255. ":EMAIL: %s\n"
  256. ":NICK: \n"
  257. ":END:\n\n")
  258. (or name email "")
  259. (or email "")))
  260. (key "mu4e-add-org-contact-key")
  261. (head "General")
  262. (org-capture-templates
  263. (append org-capture-templates
  264. (list (list key "contacts" 'entry
  265. (list 'file+headline mu4e-org-contacts-file head) blurb)))))
  266. (message "%S" org-capture-templates)
  267. (when (fboundp 'org-capture)
  268. (org-capture nil key))))
  269. (defun distopico:mu4e-new-mail-p ()
  270. "Predicate for if there is new mail or not in Boolean."
  271. (not (eq 0 (string-to-number
  272. (distopico:mu4e-get-unread-command)))))
  273. (defun distopico:mu4e-get-unread-command ()
  274. "Get unread messages fro mu4e binary."
  275. (replace-regexp-in-string
  276. "[\t\n\r]" ""
  277. (shell-command-to-string
  278. (concat "echo -n $( " mu4e-mu-binary " find "
  279. "--muhome " mu4e-mu-home " "
  280. "flag:unread AND maildir:'/\/*\/INBOX*/'"
  281. " 2>/dev/null | wc -l )"))))
  282. (defun distopico:mu4e-inbox-update ()
  283. "Print tooltip help and icon for unread messages."
  284. (interactive)
  285. (setq distopico:mu4e-mode-line-format
  286. (let ((unread (distopico:mu4e-get-unread-command)))
  287. (let ((unread-string
  288. (if (string= "0" unread) ""
  289. (format "[✉ %s]" unread))))
  290. (propertize
  291. unread-string
  292. 'display (if (boundp 'img:tron-email)
  293. img:tron-email
  294. nil)
  295. 'local-map (make-mode-line-mouse-map 'mouse-1 #'distopico:mu4e-open)
  296. 'help-echo (format "mu4e :: %s unread messages" unread)))))
  297. (force-mode-line-update)
  298. (sit-for 0))
  299. (defun distopico:mu4e-maildirs-force-update()
  300. "Clear cache and insert maildirs summary and reload."
  301. (interactive)
  302. (mu4e-message "Updating index & cache...")
  303. (mu4e-update-index)
  304. (distopico:mu4e-inbox-update))
  305. (defun distopico:message-attachment-present-p ()
  306. "Return t if an attachment is found in the current message."
  307. (save-excursion
  308. (save-restriction
  309. (widen)
  310. (goto-char (point-min))
  311. (when (search-forward "<#part" nil t) t))))
  312. (defun distopico:message-warn-if-no-attachments ()
  313. "Ask the user if wants to send the message even though \
  314. there are no attachments.
  315. from: http://mbork.pl/2016-02-06_An_attachment_reminder_in_mu4e"
  316. (when (and (save-excursion
  317. (save-restriction
  318. (widen)
  319. (goto-char (point-min))
  320. (re-search-forward distopico:message-attachment-intent-re nil t)))
  321. (not (distopico:message-attachment-present-p)))
  322. (unless (y-or-n-p distopico:message-attachment-reminder)
  323. (keyboard-quit))))
  324. (defun distopico:mu4e-index-updated-hook ()
  325. "Hook when it get new message."
  326. (let ((updated (plist-get mu4e-index-update-status :updated)))
  327. (when (> updated 0)
  328. ;; Sync command to show notification
  329. ;; (call-process "/bin/bash" nil 0 nil (in-emacs-d "/scripts/notify_mail.sh") (number-to-string mu4e-update-interval))
  330. ;; Async command to show notification
  331. ;;(start-process "mu4e-update" nil (in-emacs-d "/scripts/notify_mail.sh") (number-to-string mu4e-update-interval))
  332. ;; Sync command to show notification
  333. (shell-command-to-string (concat (in-emacs-d "/scripts/notify_mail.sh") " " (number-to-string mu4e-update-interval)))
  334. (distopico:mu4e-inbox-update))))
  335. (defun distopico:mu4e-view-mode-hook ()
  336. "Enable/disable some mode in `mu4e-view-mode'."
  337. ;; to easier read html in dark themes
  338. (setq shr-color-visible-luminance-min 80)
  339. (tabbar-local-mode +1)
  340. (footnote-mode +1)
  341. (visual-line-mode +1))
  342. (defun distopico:mu4e-compose-mode-hook ()
  343. "Enable/disable some mode in `mu4e-compose-mode'."
  344. (distopico:mu4e-view-mode-hook)
  345. (mml-secure-message-sign-pgpmime))
  346. (defun distopico:mu4e-init-load-hook ()
  347. "Run mu4e in startup."
  348. (setq-default mu4e-contexts (eval distopico:mu4e-contexts))
  349. (mu4e t)
  350. (distopico:mu4e-mode-line t))
  351. ;; Custom modes
  352. (define-minor-mode distopico:mu4e-mode-line
  353. "Minor mode Toggle inbox status display in mode line."
  354. :global t :group 'hardware
  355. (setq distopico:mu4e-mode-line-format "")
  356. (and distopico:mu4e-update-timer (cancel-timer distopico:mu4e-update-timer))
  357. (if (not distopico:mu4e-mode-line-format)
  358. (message "Disabled mu4e mode line..")
  359. (setq distopico:mu4e-update-timer
  360. (run-at-time nil distopico:mu4e-inbox-update-modeline-interval #'distopico:mu4e-inbox-update))
  361. (distopico:mu4e-inbox-update)))
  362. ;;-------------------
  363. ;; Hooks
  364. ;;-------------------
  365. (add-hook 'mu4e-index-updated-hook #'distopico:mu4e-index-updated-hook)
  366. (add-hook 'mu4e-view-mode-hook #'distopico:mu4e-view-mode-hook)
  367. (add-hook 'mu4e-compose-mode-hook #'distopico:mu4e-compose-mode-hook)
  368. (add-hook 'message-send-hook #'distopico:message-warn-if-no-attachments)
  369. (add-hook 'gnus-part-display-hook 'message-view-patch-highlight)
  370. (add-hook 'mu4e--update-mail-and-index-real 'distopico:inhibit-message-advisor)
  371. (add-hook 'distopico:after-init-load-hook #'distopico:mu4e-init-load-hook)
  372. (provide 'conf-mu4e)
  373. ;;; conf-mu4e.el ends here