1

I'm using wanderlust in emacs-25.1.x

I'm looking for a way to select a group of messages and have all of their MIME attachments extracted and saved into a specified folder.

I assume that I can mark the messages with the target mark ('*'), and use 'wl-summary-target-mark-set-action' to specify an action to run on each of these marked messages. However, I don't know what action will save all MIME attachments in any given message.

I have searched through the wanderlust docs, but I couldn't find anything. If this info is documented somewhere, could someone point me to it?

Thank you very much in advance.

  • I don't think this is possible without writing up custom code. There may be many attachments in each message of all different types. The leading source that everyone seems to copy for the wl-init has (I think) an example for multiple attachments in one message. I have written my own single message which has the attachment regexp that I commonly deal with -- pdf, jpeg, etc. Sometimes people attach signature images, so you'd end up snaring those as well. Some are in-line text, so you'd need to deal with that. I'd say start with one message and then expand your answer to multiple messages. – lawlist Apr 6 '17 at 1:28
  • Thank you. I'm glad to write my own code and to make use of a regex for selecting the attachments. I don't know what that leading source for wl-init is. Would you be willing to point me to a reference to it? – HippoMan Apr 6 '17 at 2:37
1

I briefly Googled, but did not find an example of a multiple file extract function that I thought I had once read somewhere. I only found a multiple attach example. Here is a simplified version of what I use in my own-setup -- I like to check for an existing file with the same name as I extract all files from a single message, and if there is none, then I just keep hitting the return key -- the additional advantage is that I can change the file name if I so choose. I would recommend perfecting a custom solution for single messages, and then expand the answer to include multiple messages:

(defun multi-extract ()
"Doc-string"
(interactive)
  (let* (
      (original-buffer (current-buffer))
      (multi-extract-regexp (concat
        "^\\[[*].*\\]$"
        "\\|<application/.* (base64)>"
        "\\|<image/x-png (base64)>"
        "\\|<image/png (base64)>"
        "\\|<image/jpeg (base64)>"
        "\\|<image/bmp (base64)>"
        "\\|<image/pjpeg (base64)>"
        "\\|<audio/wav (base64)>"
        "\\|<application/octet-stream (7bit)>"
        "\\|<application/octet-stream (quoted-printable)>"
        "\\|<text/plain; windows-1252 (7bit)>"
        "\\|<application/pdf (quoted-printable)>"))
      entity basename filename dired-basename-maybe
      mime-extract-dir prospective-filename)
    (save-excursion
      (unless (re-search-forward multi-extract-regexp nil t)
        (message "`multi-extract' could not locate any attachments.")))
    (save-excursion
      (goto-char (point-min))
      (while (re-search-forward multi-extract-regexp nil t)
        (setq entity (get-text-property (match-beginning 0) 'mime-view-entity))
        (setq prospective-filename
          (read-file-name "file-name:  "
            (if mime-extract-dir mime-extract-dir default-directory)))
        (setq mime-extract-dir (file-name-directory prospective-filename))
        (setq dired-basename-maybe (if (not (file-directory-p prospective-filename)) (file-name-nondirectory prospective-filename)))
        (setq basename (mime-entity-safe-filename entity))
        (setq filename (read-string "Save-As:  " (concat mime-extract-dir (if dired-basename-maybe dired-basename-maybe basename) )))
        (if (file-exists-p filename)
          (or (y-or-n-p (format "File %s exists.  Save anyway? " filename))
            (let ((debug-on-quit nil))
              (signal 'quit '("You chose to quit -- a file with that name already exists.")))))
        (mime-write-entity-content entity filename)))
    (when (and filename (buffer-live-p original-buffer))
      (with-current-buffer original-buffer
        (when (eq major-mode 'mime-view-mode)
          (setq default-directory (file-name-directory filename)))))))
  • Yes, I couldn't find anything via googling, either. Thank you for this! It's much more than I expected. Also, I thought of another way to approach this. It's a variation of what I did under mu4e for the same purpose: just write a function to save the text of each marked message in a temporary directory, and then fire off a python script to read the message files to extract the attachments I'm interested in and then delete the temp directory. I actually have a python script already written which does most of that. So now, I have two possible approaches to explore. – HippoMan Apr 6 '17 at 2:53
0

I managed to do this by means of my second proposed method: copying the marked emails to files in a temp directory, and then processing these files via an external python program: "/usr/local/bin/wl-image-process", in this case.

The python program opens each message file and parses it using python's "email" package. It traverses the list of message parts for the multipart message, and if any of the items are of MIME types that I'm interested in (images), it processes them.

Here's my elisp code.

(defvar my-wl-save-dir-pattern (expand-file-name ".wlsave-%Y%m%d%H%M%S%N" wl-temporary-file-directory)
  "*Temporary save directory pattern.")

;; Save the message if it contains attachments.
(defun my-wl-summary-save-if-has-attachments (&optional wl-save-dir)
  "Save current message to disk if it contains one or more attachments."
  (interactive)
  (let ((filename)
        (pathname)
        (num (wl-summary-message-number)))
    (unless wl-save-dir
      (setq wl-save-dir wl-temporary-file-directory))
    (if num
        (save-excursion
          (setq filename (concat (number-to-string num) wl-summary-save-file-suffix))
          (setq pathname (expand-file-name filename wl-save-dir))
          (wl-summary-set-message-buffer-or-redisplay)
          (save-excursion
            ;; This determines whether the message contains
            ;; any MIME parts.
            (if (get-text-property (point-min) 'mime-view-entity)
                (progn
                  (set-buffer (wl-message-get-original-buffer))
                  (write-region-as-binary (point-min) (point-max) pathname)
                  filename)
              nil)))
      nil)))

;; Helper routine.
(defun my-wl-summary-display-subr ()
  (let ((wl-save-dir (format-time-string my-wl-save-dir-pattern))
        number
        item
        msg-list)
    (message "processing ...")
    (unless (file-exists-p wl-save-dir)
      (make-directory wl-save-dir))
    (while (setq number (car wl-summary-buffer-target-mark-list))
      (wl-thread-jump-to-msg number)
      (let ((inhibit-message t))
        (setq item (my-wl-summary-save-if-has-attachments wl-save-dir)))
      (when item
        ;; '-snoc' is a 3rd-party routine
        ;; which does the opposite of 'cons'.
        (setq msg-list (-snoc msg-list item)))
      (wl-summary-unmark))
    (if msg-list
        (let* ((arg-list (append (list "-d" wl-save-dir) msg-list))
               (msg-count (length msg-list))
               (maybe-plural (if (= 1 msg-count) "" "s"))
               (program "/usr/local/bin/wl-image-process"))
          (with-temp-buffer
            (apply 'start-process "process" nil program arg-list))
          (message "%d message%s processed" msg-count maybe-plural))
      (message "no messages processed"))
    ))

;; Process all attachments for all marked messages.
;; If no messages are marked, mark them all.
(defun my-wl-summary-marked-process-attachments ()
  (interactive)
  (save-excursion
    (save-restriction
      (unless wl-summary-buffer-target-mark-list
        (wl-summary-target-mark-all))
      (my-wl-summary-process-subr)
      (wl-summary-toggle-disp-msg 'off))))

;; Process all attachments for the current message only.
(defun my-wl-summary-process-attachments ()
  (interactive)
  (wl-summary-target-mark)
  (my-wl-summary-process-subr))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.