;;; mu4e-message.el -- part of mu4e -*- lexical-binding: t -*- ;; Copyright (C) 2012-2022 Dirk-Jan C. Binnema ;; Author: Dirk-Jan C. Binnema ;; Maintainer: Dirk-Jan C. Binnema ;; This file is not part of GNU Emacs. ;; mu4e 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. ;; mu4e 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 mu4e. If not, see . ;;; Commentary: ;; Functions to get data from mu4e-message plist structure ;;; Code: (require 'mu4e-vars) (require 'mu4e-contacts) (require 'flow-fill) (require 'shr) (declare-function mu4e-error "mu4e-helpers") (declare-function mu4e-warn "mu4e-helpers") (declare-function mu4e-personal-address-p "mu4e-contacts") (declare-function mu4e-make-temp-file "mu4e-helpers") (make-obsolete-variable 'mu4e-html2text-command "No longer in use" "1.7.0") (make-obsolete-variable 'mu4e-view-prefer-html "No longer in use" "1.7.0") (make-obsolete-variable 'mu4e-view-html-plaintext-ratio-heuristic "No longer in use" "1.7.0") (make-obsolete-variable 'mu4e-message-body-rewrite-functions "No longer in use" "1.7.0") ;;; Message fields (defsubst mu4e-message-field-raw (msg field) "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 Returns nil if the field does not exist. A message plist looks something like: \(:docid 32461 :from ((:name \"Nikola Tesla\" :email \"niko@example.com\")) :to ((:name \"Thomas Edison\" :email \"tom@example.com\")) :cc ((:name \"Rupert The Monkey\" :email \"rupert@example.com\")) :subject \"RE: what about the 50K?\" :date (20369 17624 0) :size 4337 :message-id \"238C8233AB82D81EE81AF0114E4E74@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 (\"238C8384574032D81EE81AF0114E4E74@123213.mail.example.com\" \"6BDC23465F79238203498230942D81EE81AF0114E4E74@123213.mail.example.com\") :in-reply-to \"238203498230942D81EE81AF0114E4E74@123213.mail.example.com\" :body-txt \"Hi Tom, ...\" \)). Some notes on the format: - The address fields are lists of plist (:name NAME :email EMAIL), where the :name part can be absent. The `mu4e-contact-name' and `mu4e-contact-email' accessors can be useful for this. - 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 :attachments, :body-txt or :body-html fields. Message in the Message view use the actual message file, and do include these fields." ;; after all this documentation, the spectacular implementation (if msg (plist-get msg field) (mu4e-error "Message must be non-nil"))) (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 ((member field '(:subject :message-id :path :maildir :in-reply-to)) "") ;; string fields except body-txt, body-html: nil -> "" ((member field '(:body-html :body-txt)) val) ((member field '(:docid :size)) 0) ;; numeric type: nil -> 0 (t val)))) ;; otherwise, just return nil (defsubst mu4e-message-has-field (msg field) "If MSG has a FIELD return t, nil otherwise." (plist-member msg field)) (defsubst mu4e-message-at-point (&optional noerror) "Get the message s-expression for the message at point. Either the headers buffer or the view buffer, or nil if there is no such message. If optional NOERROR is non-nil, do not raise an error when there is no message at point." (let ((msg (or (get-text-property (point) 'msg) mu4e~view-message))) (if msg msg (unless noerror (mu4e-warn "No message at point"))))) (defsubst mu4e-message-field-at-point (field) "Get the field FIELD from the message at point. This is equivalent to: (mu4e-message-field (mu4e-message-at-point) FIELD)." (mu4e-message-field (mu4e-message-at-point) field)) (defun mu4e-message-body-text (_msg &optional _prefer-html) "Get the body in text form for message MSG." "" ;; not implemented for Gnus mode. ) (defun mu4e-message-contact-field-matches (msg cfield rx) "Does MSG's contact-field CFIELD match regexp RX? Check if any of the of the CFIELD in MSG matches RX. I.e. anything in field CFIELD (either :to, :from, :cc or :bcc, or a list of those) of msg MSG matches (with their name or e-mail address) regular expressions RX. If there is a 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." (if (and cfield (listp cfield)) (or (mu4e-message-contact-field-matches msg (mu4e-contact-name cfield) rx) (mu4e-message-contact-field-matches msg (mu4e-contact-email cfield) rx)) (when cfield (if (listp rx) ;; if rx is a list, try each one of them for a match (seq-find (lambda (a-rx) (mu4e-message-contact-field-matches msg cfield a-rx)) rx) ;; not a list, check the rx (seq-find (lambda (ct) (let ((name (mu4e-contact-name ct)) (email (mu4e-contact-email ct)) ;; the 'rx' may be some `/rx/` from mu4e-personal-addresses; ;; so let's detect and extract in that case. (rx (if (string-match-p "^\\(.*\\)/$" rx) (substring rx 1 -1) rx))) (or (and name (string-match rx name)) (and email (string-match rx email))))) (mu4e-message-field msg cfield)))))) (defun mu4e-message-contact-field-matches-me (msg cfield) "Does contact-field CFIELD in MSG match me? 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 addresses for which `mu4e-personal-address-p' return t. Returns the contact cell that matched, or nil." (seq-find (lambda (cell) (mu4e-personal-address-p (mu4e-contact-email cell))) (mu4e-message-field msg cfield))) (defun mu4e-message-sent-by-me (msg) "Is this MSG (to be) sent by me? Checks if the from field matches user's personal addresses." (mu4e-message-contact-field-matches-me msg :from)) (defun mu4e-message-personal-p (msg) "Does MSG have user's personal address? In any of the contact fields?" (seq-some (lambda (field) (mu4e-message-contact-field-matches-me msg field)) '(:from :to :cc :bcc))) (defsubst mu4e-message-part-field (msgpart field) "Get some FIELD from MSGPART. A part would look something like: (:index 2 :name \"photo.jpg\" :mime-type \"image/jpeg\" :size 147331)." (plist-get msgpart field)) ;; 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 for the message at point. Either in the headers buffer or the view buffer. Field is a symbol, see `mu4e-header-info'." (plist-get (mu4e-message-at-point) field)) ;;; Html2Text (make-obsolete 'mu4e-shr2text "No longer in use" "1.7.0") (defun mu4e-message-readable-path (&optional msg) "Get a readable path to MSG or raise an error. If MSG is nil, use `mu4e-message-at-point'." (let ((path (plist-get (or msg (mu4e-message-at-point)) :path))) (unless (file-readable-p path) (mu4e-error "No readable message at %s; database outdated?" path)) path)) (defun mu4e-copy-message-path () "Copy the message-path of message at point to the kill ring." (interactive) (let ((path (mu4e-message-field-at-point :path))) (kill-new path) (mu4e-message "Saved '%s' to kill-ring" path))) ;;; (provide 'mu4e-message) ;;; mu4e-message.el ends here