2012-09-26 11:25:38 +02:00
|
|
|
|
;;; mu4e-message.el -- part of mu4e, the mu mail user agent
|
|
|
|
|
;;
|
|
|
|
|
;; Copyright (C) 2012 Dirk-Jan C. Binnema
|
|
|
|
|
|
|
|
|
|
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
|
|
|
|
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
|
|
|
|
|
|
|
|
|
;; This file is not part of GNU Emacs.
|
|
|
|
|
;;
|
|
|
|
|
;; GNU Emacs is free software: you can redistribute it and/or modify
|
|
|
|
|
;; it under the terms of the GNU General Public License as published by
|
|
|
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
;; (at your option) any later version.
|
|
|
|
|
|
|
|
|
|
;; GNU Emacs is distributed in the hope that it will be useful,
|
|
|
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
;; GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
|
|
|
;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
|
|
|
|
;; Functions to get data from mu4e-message plist structure
|
|
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
(eval-when-compile (byte-compile-disable-warning 'cl-functions))
|
2012-09-26 15:47:42 +02:00
|
|
|
|
|
|
|
|
|
(require 'mu4e-vars)
|
2012-09-26 16:28:30 +02:00
|
|
|
|
(require 'mu4e-utils)
|
2012-09-26 15:47:42 +02:00
|
|
|
|
|
2012-09-26 11:25:38 +02:00
|
|
|
|
(require 'cl)
|
|
|
|
|
(require 'html2text)
|
2012-09-26 16:28:30 +02:00
|
|
|
|
|
2012-09-26 11:25:38 +02:00
|
|
|
|
|
2013-10-19 10:45:30 +02:00
|
|
|
|
(defcustom mu4e-html2text-command 'html2text
|
|
|
|
|
"Either a shell command or a function that converts from html to plain text.
|
|
|
|
|
|
|
|
|
|
If it is a shell-command, the command has to read html from stdin
|
|
|
|
|
and output plain text on stdout. If this is not defined, the emacs
|
|
|
|
|
`html2text' tool will be used when faced with html-only
|
|
|
|
|
messages. If you use htmltext, it's recommended you use \"html2text
|
|
|
|
|
-utf8 -width 72\". Alternatives are the python-based html2markdown,
|
|
|
|
|
w3m and on MacOS you may want to use textutil.
|
|
|
|
|
|
|
|
|
|
It can also be a function, which takes the current buffer in html
|
|
|
|
|
as input, and transforms it into html (like the `html2text'
|
|
|
|
|
function).
|
|
|
|
|
|
|
|
|
|
In both cases, the output is expected to be in utf-8 encoding.
|
|
|
|
|
|
|
|
|
|
The default is emacs' built-in `html2text' function."
|
|
|
|
|
:type '(choice string function)
|
|
|
|
|
:group 'mu4e-view)
|
2012-09-26 11:25:38 +02:00
|
|
|
|
|
|
|
|
|
(defcustom mu4e-view-prefer-html nil
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"Whether to base the body display on the html-version.
|
|
|
|
|
If the e-mail message has no html-version the plain-text version
|
|
|
|
|
is always used."
|
2012-09-26 11:25:38 +02:00
|
|
|
|
:type 'boolean
|
|
|
|
|
:group 'mu4e-view)
|
|
|
|
|
|
2013-05-26 14:25:31 +02:00
|
|
|
|
(defcustom mu4e-view-html-plaintext-ratio-heuristic 10
|
|
|
|
|
"Ratio between the length of the html and the plain text part
|
2013-10-14 21:43:56 +02:00
|
|
|
|
below which mu4e will consider the plain text part to be 'This
|
|
|
|
|
messages requires html' text bodies."
|
2013-05-26 14:25:31 +02:00
|
|
|
|
:type 'integer
|
|
|
|
|
:group 'mu4e-view)
|
|
|
|
|
|
2012-09-26 11:25:38 +02:00
|
|
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
2012-10-16 09:56:46 +02:00
|
|
|
|
(defsubst mu4e-message-field-raw (msg field)
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"Retrieve FIELD from message plist MSG.
|
|
|
|
|
FIELD is one of :from, :to, :cc, :bcc, :subject, :data,
|
|
|
|
|
:message-id, :path, :maildir, :priority, :attachments,
|
|
|
|
|
:references, :in-reply-to, :body-txt, :body-html
|
2012-09-26 11:25:38 +02:00
|
|
|
|
|
2012-10-16 09:56:46 +02:00
|
|
|
|
Returns `nil' if the field does not exist.
|
|
|
|
|
|
2012-09-26 11:25:38 +02:00
|
|
|
|
A message plist looks something like:
|
|
|
|
|
\(:docid 32461
|
|
|
|
|
:from ((\"Nikola Tesla\" . \"niko@example.com\"))
|
|
|
|
|
:to ((\"Thomas Edison\" . \"tom@example.com\"))
|
|
|
|
|
:cc ((\"Rupert The Monkey\" . \"rupert@example.com\"))
|
|
|
|
|
:subject \"RE: what about the 50K?\"
|
|
|
|
|
:date (20369 17624 0)
|
|
|
|
|
:size 4337
|
|
|
|
|
:message-id \"6BDC23465F79238C8233AB82D81EE81AF0114E4E74@123213.mail.example.com\"
|
|
|
|
|
:path \"/home/tom/Maildir/INBOX/cur/133443243973_1.10027.atlas:2,S\"
|
|
|
|
|
:maildir \"/INBOX\"
|
|
|
|
|
:priority normal
|
|
|
|
|
:flags (seen)
|
|
|
|
|
:attachments
|
|
|
|
|
((:index 2 :name \"photo.jpg\" :mime-type \"image/jpeg\" :size 147331)
|
|
|
|
|
(:index 3 :name \"book.pdf\" :mime-type \"application/pdf\" :size 192220))
|
|
|
|
|
:references (\"6BDC23465F79238C8384574032D81EE81AF0114E4E74@123213.mail.example.com\"
|
|
|
|
|
\"6BDC23465F79238203498230942D81EE81AF0114E4E74@123213.mail.example.com\")
|
|
|
|
|
:in-reply-to \"6BDC23465F79238203498230942D81EE81AF0114E4E74@123213.mail.example.com\"
|
|
|
|
|
:body-txt \"Hi Tom, ...\"
|
|
|
|
|
\)).
|
2012-10-16 09:56:46 +02:00
|
|
|
|
Some notes on the format:
|
2012-09-26 11:25:38 +02:00
|
|
|
|
- The address fields are lists of pairs (NAME . EMAIL), where NAME can be nil.
|
|
|
|
|
- The date is in format emacs uses in `current-time'
|
|
|
|
|
- Attachments are a list of elements with fields :index (the number of
|
|
|
|
|
the MIME-part), :name (the file name, if any), :mime-type (the
|
|
|
|
|
MIME-type, if any) and :size (the size in bytes, if any).
|
|
|
|
|
- Messages in the Headers view come from the database and do not have
|
2012-10-16 09:56:46 +02:00
|
|
|
|
:attachments, :body-txt or :body-html fields. Message in the
|
2012-09-26 11:25:38 +02:00
|
|
|
|
Message view use the actual message file, and do include these fields."
|
|
|
|
|
;; after all this documentation, the spectacular implementation
|
2012-10-01 20:18:42 +02:00
|
|
|
|
(if msg
|
|
|
|
|
(plist-get msg field)
|
|
|
|
|
(mu4e-error "message must be non-nil")))
|
2012-10-16 09:56:46 +02:00
|
|
|
|
|
|
|
|
|
(defsubst mu4e-message-field (msg field)
|
|
|
|
|
"Retrieve FIELD from message plist MSG.
|
|
|
|
|
Like `mu4e-message-field-nil', but will sanitize `nil' values:
|
|
|
|
|
- all string field except body-txt/body-html: nil -> \"\"
|
|
|
|
|
- numeric fields + dates : nil -> 0
|
|
|
|
|
- all others : return the value
|
|
|
|
|
Thus, function will return nil for empty lists, non-existing body-txt or body-html."
|
|
|
|
|
(let ((val (mu4e-message-field-raw msg field)))
|
|
|
|
|
(cond
|
|
|
|
|
(val
|
|
|
|
|
val) ;; non-nil -> just return it
|
2012-10-20 12:06:49 +02:00
|
|
|
|
((member field '(:subject :message-id :path :maildir :in-reply-to))
|
2012-10-16 09:56:46 +02:00
|
|
|
|
"") ;; string fields except body-txt, body-html: nil -> ""
|
2012-10-28 18:31:13 +01:00
|
|
|
|
((member field '(:body-html :body-txt))
|
|
|
|
|
val)
|
2012-10-16 09:56:46 +02:00
|
|
|
|
((member field '(:docid :size))
|
|
|
|
|
0) ;; numeric type: nil -> 0
|
2013-10-14 21:43:56 +02:00
|
|
|
|
(t
|
2012-10-16 09:56:46 +02:00
|
|
|
|
val)))) ;; otherwise, just return nil
|
|
|
|
|
|
2012-10-28 18:31:13 +01:00
|
|
|
|
(defsubst mu4e-message-has-field (msg field)
|
|
|
|
|
"Return t if MSG contains FIELD, nil otherwise."
|
|
|
|
|
(plist-member msg field))
|
|
|
|
|
|
2012-09-26 16:28:30 +02:00
|
|
|
|
(defsubst mu4e-message-at-point (&optional noerror)
|
2012-09-26 15:47:42 +02:00
|
|
|
|
"Get the message s-expression for the message at point in either
|
|
|
|
|
the headers buffer or the view buffer, or nil if there is no such
|
2012-09-26 16:28:30 +02:00
|
|
|
|
message. If optional NOERROR is non-nil, do not raise an error when
|
2012-09-26 15:47:42 +02:00
|
|
|
|
there is no message at point."
|
2013-10-14 21:43:56 +02:00
|
|
|
|
(let ((msg (or (get-text-property (point) 'msg) mu4e~view-msg)))
|
2012-09-26 15:47:42 +02:00
|
|
|
|
(if msg
|
|
|
|
|
msg
|
2012-09-26 16:28:30 +02:00
|
|
|
|
(unless noerror (mu4e-warn "No message at point")))))
|
2012-10-28 18:31:13 +01:00
|
|
|
|
|
|
|
|
|
(defsubst mu4e-message-field-at-point (field)
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"Get the field FIELD from the message at point.
|
|
|
|
|
This is equivalent to:
|
|
|
|
|
(mu4e-message-field (mu4e-message-at-point) FIELD)."
|
2012-10-28 18:31:13 +01:00
|
|
|
|
(mu4e-message-field (mu4e-message-at-point) field))
|
|
|
|
|
|
2012-09-26 11:25:38 +02:00
|
|
|
|
(defun mu4e-message-body-text (msg)
|
2012-11-10 14:01:17 +01:00
|
|
|
|
"Get the body in text form for this message.
|
2013-10-19 10:45:30 +02:00
|
|
|
|
This is either :body-txt, or if not available, :body-html converted
|
|
|
|
|
to text, using `mu4e-html2text-command' is non-nil, it will use
|
|
|
|
|
that. Normally, thiss function prefers the text part, but this can
|
|
|
|
|
be changed by setting `mu4e-view-prefer-html'."
|
2012-09-26 11:25:38 +02:00
|
|
|
|
(let* ((txt (mu4e-message-field msg :body-txt))
|
|
|
|
|
(html (mu4e-message-field msg :body-html))
|
|
|
|
|
(body
|
|
|
|
|
(cond
|
2013-05-26 14:25:31 +02:00
|
|
|
|
;; does it look like some text? ie., if the text part is more than
|
|
|
|
|
;; mu4e-view-html-plaintext-ratio-heuristic times shorter than the
|
|
|
|
|
;; html part, it should't be used
|
|
|
|
|
;; This is an heuristic to guard against 'This messages requires
|
|
|
|
|
;; html' text bodies.
|
|
|
|
|
((and (> (* mu4e-view-html-plaintext-ratio-heuristic
|
|
|
|
|
(length txt)) (length html))
|
2012-09-26 11:25:38 +02:00
|
|
|
|
;; use html if it's prefered, unless there is no html
|
|
|
|
|
(or (not mu4e-view-prefer-html) (not html)))
|
|
|
|
|
txt)
|
|
|
|
|
;; otherwise, it there some html?
|
|
|
|
|
(html
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(insert html)
|
2013-10-19 10:45:30 +02:00
|
|
|
|
(cond
|
|
|
|
|
((stringp mu4e-html2text-command)
|
|
|
|
|
(shell-command-on-region (point-min) (point-max)
|
|
|
|
|
mu4e-html2text-command nil t))
|
|
|
|
|
((functionp mu4e-html2text-command)
|
|
|
|
|
(funcall mu4e-html2text-command))
|
|
|
|
|
(t (mu4e-error "Invalid `mu4e-html2text-command'")))
|
2012-09-26 11:25:38 +02:00
|
|
|
|
(buffer-string)))
|
|
|
|
|
(t ;; otherwise, an empty body
|
|
|
|
|
""))))
|
|
|
|
|
;; and finally, remove some crap from the remaining string; it seems
|
|
|
|
|
;; esp. outlook lies about its encoding (ie., it says 'iso-8859-1' but
|
|
|
|
|
;; really it's 'windows-1252'), thus giving us these funky chars. here, we
|
|
|
|
|
;; either remove them, or replace with 'what-was-meant' (heuristically)
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(insert body)
|
|
|
|
|
(goto-char (point-min))
|
2012-12-23 20:55:54 +01:00
|
|
|
|
(while (re-search-forward "[
<EFBFBD><EFBFBD>]" nil t)
|
2012-09-26 11:25:38 +02:00
|
|
|
|
(replace-match
|
|
|
|
|
(cond
|
2012-12-23 20:55:54 +01:00
|
|
|
|
((string= (match-string 0) "<EFBFBD>") "'")
|
2012-09-26 11:25:38 +02:00
|
|
|
|
(t ""))))
|
|
|
|
|
(buffer-string))))
|
|
|
|
|
|
2012-09-27 16:21:22 +02:00
|
|
|
|
(defun mu4e-message-contact-field-matches (msg cfield rx)
|
|
|
|
|
"Checks whether any of the of the contacts in field
|
|
|
|
|
CFIELD (either :to, :from, :cc or :bcc) of msg MSG matches (with
|
|
|
|
|
their name or e-mail address) regular expressions RX. If there is a
|
2012-10-20 15:07:01 +02:00
|
|
|
|
match, return non-nil; otherwise return nil. RX can also be a list
|
|
|
|
|
of regular expressions, in which case any of those are tried for a
|
|
|
|
|
match."
|
2012-09-27 16:21:22 +02:00
|
|
|
|
(unless (member cfield '(:to :from :bcc :cc))
|
|
|
|
|
(mu4e-error "Not a contacts field (%S)" cfield))
|
2012-10-20 15:07:01 +02:00
|
|
|
|
(if (listp rx)
|
|
|
|
|
;; if rx is a list, try each one of them for a match
|
|
|
|
|
(find-if
|
|
|
|
|
(lambda (a-rx) (mu4e-message-contact-field-matches msg cfield a-rx))
|
|
|
|
|
rx)
|
|
|
|
|
;; not a list, check the rx
|
|
|
|
|
(find-if
|
|
|
|
|
(lambda (ct)
|
|
|
|
|
(let ((name (car ct)) (email (cdr ct)))
|
|
|
|
|
(or
|
|
|
|
|
(and name (string-match rx name))
|
|
|
|
|
(and email (string-match rx email)))))
|
2013-10-14 21:43:56 +02:00
|
|
|
|
(mu4e-message-field msg cfield))))
|
2012-09-27 16:21:22 +02:00
|
|
|
|
|
2013-07-07 17:30:30 +02:00
|
|
|
|
(defun mu4e-message-contact-field-matches-me (msg cfield)
|
|
|
|
|
"Checks whether any of the of the contacts in field
|
|
|
|
|
CFIELD (either :to, :from, :cc or :bcc) of msg MSG matches *me*,
|
|
|
|
|
that is, any of the e-mail address in
|
|
|
|
|
`mu4e-user-mail-address-list'. Returns the contact cell that
|
|
|
|
|
matched, or nil."
|
|
|
|
|
(find-if
|
|
|
|
|
(lambda (cc-cell)
|
|
|
|
|
(member-if
|
|
|
|
|
(lambda (addr)
|
|
|
|
|
(string= (downcase addr) (downcase (cdr cc-cell))))
|
|
|
|
|
mu4e-user-mail-address-list))
|
|
|
|
|
(mu4e-message-field msg cfield)))
|
2012-09-26 11:25:38 +02:00
|
|
|
|
|
|
|
|
|
(defsubst mu4e-message-part-field (msgpart field)
|
|
|
|
|
"Get some field in a message part; a part would look something like:
|
2012-11-10 14:01:17 +01:00
|
|
|
|
(:index 2 :name \"photo.jpg\" :mime-type \"image/jpeg\" :size 147331)."
|
2012-09-26 11:25:38 +02:00
|
|
|
|
(plist-get msgpart field))
|
|
|
|
|
|
2012-09-26 16:28:30 +02:00
|
|
|
|
;; backward compatibility ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
(defalias 'mu4e-msg-field 'mu4e-message-field)
|
|
|
|
|
(defalias 'mu4e-body-text 'mu4e-message-body-text) ;; backward compatibility
|
|
|
|
|
|
|
|
|
|
(defun mu4e-field-at-point (field)
|
|
|
|
|
"Get FIELD (a symbol, see `mu4e-header-info') for the message at
|
|
|
|
|
point in eiter the headers buffer or the view buffer."
|
|
|
|
|
(plist-get (mu4e-message-at-point) field))
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
|
|
|
|
|
|
2012-09-26 11:25:38 +02:00
|
|
|
|
(provide 'mu4e-message)
|