ndpian · 2022-05-30 17:41
I'm trying to find a working solution which allows me to export any email or the entire thread to a nicely formatted pdf file. Is there any package which does this? Currently, in mu4e-action I see the following options: [x] widget view, [V] view in browser, [c] capture message, [v] view in browser, [s] show this thread. Thanks!!
aimebertrand · 2022-05-30 19:21
Hey,
I got this in my config:
(defun timu-func-mu4e-action-save-to-pdf (msg)
"Save mu4e MSG as a pdf.
The `wkhtmltopdf' cli command is a dependency.
Credit: https://github.com/malb/emacs.d/blob/master/malb.org."
(let* ((date (mu4e-message-field msg :date))
(infile (mu4e~write-body-to-html msg))
(outfile (format-time-string "%Y-%m-%d%H%M%S.pdf" date)))
(with-temp-buffer
(shell-command
(format "wkhtmltopdf %s ~/Desktop/%s" infile outfile) t))))
(add-to-list 'mu4e-view-actions
'("pdf" . timu-func-mu4e-action-save-to-pdf) t)
ndpian · 2022-05-30 19:53
Thanks for the snippet! However for some reason I get : "Symbol's function definition is void: mu4e~write-body-to-html"
7890yuiop · 2022-05-30 21:16
Probably a different version of `mu4e`. Search in `mu4e-actions.el` for something similar.
aimebertrand · 2022-05-30 21:55
Right. Sorry. The particular Xwidget support in mu/mu4e had been removed in some versions.
The following PR tries to reimplement it:
[https://github.com/djcb/mu/pull/2077](https://github.com/djcb/mu/pull/2077)
What I did is add the file in the PR to my `load-path.` This provides the missing function `mu4e~write-body-to-html` among other needed.
Michaelmrose · 2022-05-31 01:02
Neat work.
ndpian · 2022-05-31 13:57
amazing thanks! yes after loading the files in the PR your script works perfectly! :)
abougouffa · 2022-12-17 16:08
I have a hack in my config which addresses this, the problem with the majority of the examples I found is that they only work with mails with `text/html` part, and for plain-text mails. And most of them do not work properly with Unicode (or non ASCII) mails.
I have a hack in [my config](https://github.com/abougouffa/minemacs/blob/ce63b87105e4d144dad5241e2bb048868b40945b/modules/extras/me-mu4e-extras.el#L186-L226) which works for both cases, it depends on `wkhtmltopdf` and `txt2html`.
Here it is:
```elisp
(defun +clean-file-name (filename &optional downcase-p)
"Clean file name."
;; Clean slashes, backslashes, ":", ";", spaces, and tabs
(replace-regexp-in-string
"[:;\t\n\r /\\_]+" "-"
(replace-regexp-in-string
"[‘’‚’“”„”\"'()]+" ""
(if downcase-p (downcase filename) filename))))
(defun +html2pdf (infile outfile)
"Convert HTML file INFILE to PDF and save it to OUTFILE."
(call-process "wkhtmltopdf" nil nil nil
"--images" "--disable-javascript" "--enable-local-file-access"
"--encoding" "utf-8"
infile outfile))
(defun +txt2html (infile outfile &optional mail-mode-p)
"Convert plain-text file INFILE to HTML and save it to OUTFILE.
When MAIL-MODE-P is non-nil, --mailmode is passed to \"txt2html\"."
(apply
#'call-process
(append '("txt2html" nil nil nil "-8")
(when mail-mode-p '("--mailmode"))
(list "--outfile" outfile infile))))
(defvar +save-as-pdf-filename nil
"File name to use, if non-nil, for the output file.")
(defun +save-as-pdf (infile &optional _)
"Save URL as PDF.
This function's signature is compatible with `browse-url-browser-function'
so it can be used to save HTML pages or emails to PDF."
(let ((outfile (or +save-as-pdf-filename
(expand-file-name
(file-name-with-extension (file-name-base infile) ".pdf")
(file-name-directory infile)))))
(if (zerop
;; For HTML files, just call `+html2pdf'
(if (string= "html" (file-name-extension infile))
(+html2pdf infile outfile)
;; For non-HTML (plain-text) files, convert them to HTML then call `+html2pdf'
(let ((tmp-html (make-temp-file "txt2html-" nil ".html")))
(+txt2html infile tmp-html t)
(+html2pdf tmp-html outfile))))
(message "Exported PDF to %s" (abbreviate-file-name outfile))
(user-error
(if (file-exists-p outfile)
"PDF created but with some errors!"
"An error occured, cannot create the PDF!")))))
;; Based on: mu4e-action-view-in-browser
(defun +mu4e-view-save-mail-as-pdf (&optional msg skip-headers)
"Save current MSG as PDF.
If SKIP-HEADERS is set, do not show include message headers."
(interactive)
(when-let ((msg (or msg (mu4e-message-at-point))))
(with-temp-buffer
(insert-file-contents-literally
(mu4e-message-readable-path msg) nil nil nil t)
(run-hooks 'gnus-article-decode-hook)
(let ((header (unless skip-headers
(cl-loop for field in '("from" "to" "cc" "date" "subject")
when (message-fetch-field field)
concat (format "%s: %s\n" (capitalize field) it))))
(parts (mm-dissect-buffer t t)))
;; If singlepart, enforce a list.
(when (and (bufferp (car parts))
(stringp (car (mm-handle-type parts))))
(setq parts (list parts)))
;; Process the list
;; First, `+save-as-pdf' is set as browse-url function,
;; and the appropriate file output file name is bound to `+save-as-pdf-filename'
(let ((browse-url-browser-function #'+save-as-pdf)
(+save-as-pdf-filename
(expand-file-name
(format "%s_%s.pdf"
(format-time-string
"%F" (mu4e-message-field msg :date))
(+clean-file-name
(or (mu4e-message-field msg :subject) "No subject") t))
mu4e-attachment-dir)))
;; `gnus-article-browse-html-parts' will try to display the text/html part
;; of the message, but as `+save-as-pdf' is used as browse-url function,
;; it will be called with the proper argumets.
(unless (gnus-article-browse-html-parts parts header)
;; If the mail doesn't contain a text/html part, we save the plain-text message
;; and then we explicitly use `+save-as-pdf' to save it.
(let ((outfile (make-temp-file "plaintext-mail-" nil ".txt")))
(with-temp-file outfile
(insert (mu4e-view-message-text msg)))
(+save-as-pdf outfile))))
(mm-destroy-parts parts)))))
(add-to-list 'mu4e-view-actions '("pdf" . +mu4e-view-save-mail-as-pdf))
```