lordpretzel / mu4e-views

GNU General Public License v3.0
108 stars 6 forks source link

License: GPL 3 MELPA Stable MELPA

mu4e-views

mu4e is great, but viewing of html emails is suboptimal. This packages enables the user to choose how to view emails. It's main use case is to view html emails using an xwidgets window, but custom viewing methods are also supported.

viewing-html-emails

Also provides methods to access content extracted from an email, e.g., urls or attachments. This makes it easier to build user defined viewing methods.

Warning

HTML emails may contain malicious content and tracking images. While mu4e-views by default applies a filter to remove things like external images that are used for tracking users, the current filtering is rather naive and probably misses many types of tracking.

With mu4e-views you can customize the filtering rules (mu4e-views-html-dom-filter-chain). Also you may want to setup customized rules for which view method and filtering is used based on the senders email address are coming from (see the discussion of the dispatcher view method and customized views below).

Installation

MELPA

mu4e-views is available from MELPA (both stable and unstable). Assuming your package-archives lists MELPA, just type

M-x package-install RET mu4e-views RET

to install it.

Prerequisites

If you are not installing from melpa you have to install the dependency xwidgets-reuse first. Also this uses xwidget, so you can only use this package if your emacs has been compiled with support for xwidget. If you are unsure whether this is the case try running: (xwidget-webkit-browse-url "https://www.gnu.org/").

Quelpa

Using use-package with quelpa.

(use-package
  :quelpa ((mu4e-views
    :fetcher github
    :repo "lordpretzel/mu4e-views")
  :upgrade t))

straight

Using use-package with straight.el

(use-package mu4e-views
  :straight (mu4e-views :type git :host github :repo "lordpretzel/mu4e-views"))

Source

Alternatively, install from source. First, clone the source code:

cd MY-PATH
git clone https://github.com/lordpretzel/mu4e-views.git

Now, from Emacs execute:

M-x package-install-file RET MY-PATH/mu4e-views

Alternatively to the second step, add this to your Symbol’s value as variable is void: .emacs file:

(add-to-list 'load-path "MY-PATH/mu4e-views")
(require 'mu4e-views)

Usage

This package changes the way how mu4e shows emails when selecting an email from the mu4e-headers view. The main purpose of this package is to enable viewing of html emails in xwidgets-webkit, but is also possible for a user to define new custom views. Once a view is selected, you just use mu4e as usual and emails selected in the headers view are shown using the currently active view method.

Setup

After the package is loaded, you can call mu4e-views-mu4e-select-view-msg-method from the mu4e-headers view to select the method to use for viewing. Per default mu4e-view supports:

You may want to bind this to a key in mu4e-headers-mode-map and set the default method by customizing mu4e-views-default-view-method.

(define-key mu4e-headers-mode-map (kbd "v") #'mu4e-views-mu4e-select-view-msg-method)

Here is an example setup:

(use-package mu4e-views
  :after mu4e
  :defer nil
  :bind (:map mu4e-headers-mode-map
        ("v" . mu4e-views-mu4e-select-view-msg-method) ;; select viewing method
        ("M-n" . mu4e-views-cursor-msg-view-window-down) ;; from headers window scroll the email view
        ("M-p" . mu4e-views-cursor-msg-view-window-up) ;; from headers window scroll the email view
        ("f" . mu4e-views-toggle-auto-view-selected-message) ;; toggle opening messages automatically when moving in the headers view
        ("i" . mu4e-views-mu4e-view-as-nonblocked-html) ;; show currently selected email with all remote content
        )
  :config
  (setq mu4e-views-completion-method 'ivy) ;; use ivy for completion
  (setq mu4e-views-default-view-method "html") ;; make xwidgets default
  (mu4e-views-mu4e-use-view-msg-method "html") ;; select the default
  (setq mu4e-views-next-previous-message-behaviour 'stick-to-current-window) ;; when pressing n and p stay in the current window
  (setq mu4e-views-auto-view-selected-message t)) ;; automatically open messages when moving in the headers view

Temporarily switching view methods

Sometimes it is useful to be able to view the currently selected email message using a different view method without changing the view method. For instance, you may want to switch from the default html view method ("html") that blocks remote content to one that shows remote content to show images in the current email ("html-nonblock").

mu4e-views now provides a function for that: (mu4e-views-view-current-msg-with-method NAME). This function causes mu4e-views to redisplay the current email message using the view method named NAME. When selecting a different email message afterwards, this email will be shown using your normal viewing method.

Since showing remote content is a common use case, there is convenience function for that: mu4e-views-mu4e-view-as-nonblocked-html. You may want to bind this to a key in mu4e-headers-view. This is already bound to "i" in the mu4e-views-view-actions-mode-map keymap used by the html view methods.

Settings

xwidgets view (view method "html")

Several keys are bound in this view to store attachments, open attachments, go to urls in the email similar to the regular mu4e view window.

Synergy with xwwp

To use your keyboard to click on links in an email shown in xwidgets, you can use the excellent xwwp package.

Filtering html content

mu4e-views now supports filtering of html content to combat email tracking. However, the default filters of mu4e-views is quite naive and probably misses many types of tracking content (pull requests for improvement are appreciated). You can set mu4e-views-html-filter-external-content to control whether filters are applied or not.

You can customize mu4e-views-html-dom-filter-chain to define a list of functions that are applied in sequence to filter the email message's HTML dom. A filter function f should take as input ta message plist msg a dom node dom and should return the filtered DOM at node dom. To recursively apply f to the children of a node, your function f needs to explicitly call (mu4e-views-apply-dom-filter-to-children msg node f). While inconvenient this is necessary so that your function can change the DOM structure (e.g., remove a subtree). A good starting point is to have a look at the implementation of mu4e-views-default-dom-filter.

Dispatcher view method

If you do not want to implement a full view method yourself, you can let mu4e-views dispatch to a view method based on what email you are looking at. You need to customize mu4e-views-dispatcher-predicate-view-map which is an alist of (predicate . viewmethod-name) pairs. Each of these predicates is a function that takes as input a message plist (mu4e's internal representation of a message). The dispatcher view method applies the predicates in sequence and selects the view method for the first predicate that evaluates to non-nil. For example, this is the default setting for mu4e-views-dispatcher-predicate-view-map:

`((,(lambda (msg) (mu4e-message-field msg :body-html)) . "html")
  (,(lambda (msg) (ignore msg) t) . "text")

If you use this setting, then emails with html content are viewed using xwidgets (the "html" view method) and all other emails are viewed using mu4e's default view (called "text" in mu4e-views). As another example, consider the usecase of blocking remote content from emails unless the sender is in a whitelist (this overrides the mu4e-views-html-filter-external-content setting by using view methods "html-nonblock" and "html-block"). Note that this would require you to setup the email-whitelist variable yourself.

`((,(lambda (msg) (-contains-p email-whitelist (cdr (mu4e-message-field msg :from))))
   . "html-nonblock")
  (,(lambda (msg) (ignore msg) t) . "html-block")

Define custom views

To define a new view, you need to write a function (my-view-func html msg win) that uses window win to show the message. html is the name of a file storing the html content of the message. If mu4e-views-inject-email-information-into-html is t then mu4e-views injects a header into the html code to show some basic information about the email (e.g., sender, attachments, ...). msg is a mu4e internal message plist. You can use it to extract additional information about the email to be shown. Please refer to the mu4e and mu4e-views source code to see how this works.

To make mu4e-views aware of your new view method add it to mu4e-views-view-commands giving it a user-facing name. The format is (cons name plist). The supported keys for plist are:

As an example view method consider the usecase of showing the html source of an email:

(defun mu4e-views-view-raw-html (html msg win)
  (let ((buf (find-file-noselect html)))
    (with-current-buffer buf
      (read-only-mode)
      (select-window win)
      (switch-to-buffer buf t t))))

(defun mu4e-views-view-raw-html-is-view-p (win)
  (let ((winbuf (window-buffer win)))
    (with-current-buffer winbuf
      (eq major-mode 'html-mode))))

(add-to-list 'mu4e-views-view-commands
             '("rawhtml" .
               (:viewfunc mu4e-views-view-raw-html
                          :is-view-window-p mu4e-views-view-raw-html-is-view-p)))

mu4e-views provides several helper functions for typical operations with emails such as storing attachments as described above. These functions can be used in custom views too.

Exporting email

mu4e-views now has some basic functionality for exporting emails. Out of the box, export to pdf and html files is supported. This functionality is provided through a dispatcher function mu4e-views-export-msg-action. Per default this action is not added to the header and view actions. To bind this function as an action under key e, add the following to your configuration:

(add-to-list 'mu4e-headers-actions
             '("e: export message" . mu4e-views-export-msg-action) t)
(add-to-list 'mu4e-view-actions
             '("e: export message" . mu4e-views-export-msg-action) t)

The export action's export formats are defined in mu4e-views-export-alist which is an alist mapping symbols (file extensions) to functions that are called for exporting to this type of file. For example, (pdf . mu4e-views-export-to-pdf) means that the function mu4e-views-export-to-pdf will be called for exporting to pdf. An export function should take two arguments msg and file. file is the file to which we should export to and msg is the email (a mu4e plist).

Development

If you want to hack on mu4e-view, it maybe usefult to build docker containers for testing with different mu4e versions. There are two dockerfiles: Dockerfile builds a non-gui version and Dockerfile-gui builds a gui version for testing with xwidgets that you can connect to via VNC.

Selecting which mu4e versions to include

To change which versions are build change the VERSIONS variable in ./dockerfiles/build-mu.sh. Note that versions are git tags, e.g., master or 1.4.13.

Building the docker images

For both docker images you need some valid Maildir to be included into the image which should be stored in YOUR_MU4E_SRC_ROOT_DIR/Maildir. The docker images will contain multiple mu versions. Then run from the root mu4e source directory:

docker build -t mu4e-views .
docker build -f ./Dockerfile-gui -t mu4e-views-gui .

Using the images

To run the non-gui image and expose your version of mu4e. Run the following from the mu4e-views directory.

docker run -ti --rm -v $(pwd):/mu4e-views mu4e-views /bin/bash

For the the gui version you would want to run it in daemon mode and expose the VNC port:

docker run -d --rm -p 5900:5900 -v $(pwd):/mu4e-views mu4e-views-gui

Then use your favorite VNC viewer to connect to the container.

Running emacs with a particular mu version

The images include scripts to start emacs with a particular mu version, e.g., /emu-master.sh or /emu-1.4.13.sh. The build will create one script for each version in VERSIONS (see above).

This docker container is meant for debugging mu4e-views with different mu versions in gui environment A typical use case is to start a container with your local development version of mu4e-views E.g., from your local mu4e-views git repo:

To build the docker image copy some valid Maildir to ./Maildir and run docker build -t mu4e-views-test . If you need to test with a different version, then add it to VERSIONS="1.3.10 1.4.13 master in