1
0
mirror of https://github.com/djcb/mu.git synced 2024-06-26 07:29:17 +02:00
mu/mu4e/mu4e-icalendar.el
Christophe Troestler f673911277 mu4e/ical: Compose the response attaching the calendar
This composes the response before any hook is run which is important
so that, if those hooks modify the message, they are aware that some
MML is present.  In particular, this is needed to have the
compatibility with the org-msg package; see
https://github.com/djcb/mu/issues/1956
2021-04-06 15:29:20 +02:00

212 lines
8.9 KiB
EmacsLisp

;;; mu4e-icalendar.el --- reply to iCalendar meeting requests (part of mu4e) -*- lexical-binding: t; -*-
;; Copyright (C) 2019- Christophe Troestler
;; Author: Christophe Troestler <Christophe.Troestler@umons.ac.be>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Keywords: email icalendar
;; Version: 0.0
;; 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:
;; To install:
;; (require 'mu4e-icalendar)
;; (mu4e-icalendar-setup)
;; Optional
;; (setq mu4e-icalendar-trash-after-reply t)
;; By default, the original message is not cited. However, if you
;; would like to reply to it, the citation is in the kill-ring (paste
;; it with `yank').
;; To add the event to a diary file of your choice:
;; (setq mu4e-icalendar-diary-file "/path/to/your/diary")
;; If the file specified is not your main diary file, add
;; #include "/path/to/your/diary"
;; to you main diary file to display the events.
;; To enable optional iCalendar->Org sync functionality
;; NOTE: both the capture file and the headline(s) inside must already exist
;; (require 'org-agenda)
;; (setq gnus-icalendar-org-capture-file "~/org/notes.org")
;; (setq gnus-icalendar-org-capture-headline '("Calendar"))
;; (gnus-icalendar-org-setup)
;;; Code:
(require 'gnus-icalendar)
(require 'cl-lib)
(require 'mu4e-mark)
(require 'mu4e-utils)
(require 'mu4e-headers)
(require 'mu4e-view)
(require 'mu4e-vars)
(when mu4e-view-use-old
(mu4e-error "iCalender support is not available with the old viewer"))
;;;###autoload
(defun mu4e-icalendar-setup ()
"Perform the necessary initialization to use mu4e-icalendar."
(gnus-icalendar-setup)
(cl-defmethod gnus-icalendar-event:inline-reply-buttons :around
((event gnus-icalendar-event) handle)
(if (and (boundp 'mu4e~view-rendering)
(gnus-icalendar-event:rsvp event))
(let ((method (gnus-icalendar-event:method event)))
(when (or (string= method "REQUEST") (string= method "PUBLISH"))
`(("Accept" mu4e-icalendar-reply (,handle accepted ,event))
("Tentative" mu4e-icalendar-reply (,handle tentative ,event))
("Decline" mu4e-icalendar-reply (,handle declined ,event)))))
(cl-call-next-method event handle))))
(defun mu4e~icalendar-has-email (email list)
"Check that EMAIL is in LIST."
(let ((email (downcase email)))
(cl-find-if (lambda (c) (let ((e (cdr c)))
(and (stringp e) (string= email (downcase e)))))
list)))
(defun mu4e-icalendar-reply (data)
"Reply to the text/calendar event present in DATA."
;; Based on `gnus-icalendar-reply'.
(let* ((handle (car data))
(status (cadr data))
(event (caddr data))
(gnus-icalendar-additional-identities (mu4e-personal-addresses 'no-regexp))
(reply (gnus-icalendar-with-decoded-handle
handle
(gnus-icalendar-event-reply-from-buffer
(current-buffer) status (gnus-icalendar-identities))))
(msg (mu4e-message-at-point 'noerror))
(charset (cdr (assoc 'charset (mm-handle-type handle)))))
(when reply
(cl-labels
((fold-icalendar-buffer
()
(goto-char (point-min))
(while (re-search-forward "^\\(.\\{72\\}\\)\\(.+\\)$" nil t)
(replace-match "\\1\n \\2")
(goto-char (line-beginning-position)))))
(let ((ical-name gnus-icalendar-reply-bufname))
(with-current-buffer (get-buffer-create ical-name)
(delete-region (point-min) (point-max))
(insert reply)
(fold-icalendar-buffer)
(when (and charset (string= (downcase charset) "utf-8"))
(decode-coding-region (point-min) (point-max) 'utf-8)))
;; Compose the reply message.
(save-excursion
(let ((message-signature nil)
(mu4e-compose-cite-function #'mu4e~icalendar-delete-citation)
(mu4e-sent-messages-behavior 'delete)
(mu4e-compose-reply-recipients 'sender)
(ical-msg (cl-copy-list msg)))
;; Make sure the reply is sent to the organiser.
(let* ((organizer (gnus-icalendar-event:organizer event))
(reply-to (plist-get msg :reply-to))
(name (or (caar reply-to)
(caar (plist-get msg :from))))
(email (cons name organizer)))
(unless (or (string= organizer "")
(mu4e~icalendar-has-email organizer reply-to))
(plist-put ical-msg :reply-to (cons email reply-to))))
(plist-put ical-msg :subject
(concat (capitalize (symbol-name status))
": " (gnus-icalendar-event:summary event)))
(mu4e~compose-handler
'reply ical-msg
`((:buffer-name ,ical-name
:mime-type "text/calendar; method=REPLY; charset=utf-8")))
(message-goto-body)
(set-buffer-modified-p nil); not yet modified by user
(when mu4e-icalendar-trash-after-reply
;; Override `mu4e-sent-handler' set by `mu4e-compose-mode' to
;; also trash the message (thus must be appended to hooks).
(add-hook 'message-sent-hook
(mu4e~icalendar-trash-message-hook msg)
90 t)))))
;; Back in article buffer
(setq-local gnus-icalendar-reply-status status)
(when gnus-icalendar-org-enabled-p
(if (gnus-icalendar-find-org-event-file event)
(gnus-icalendar--update-org-event event status)
(gnus-icalendar:org-event-save event status)))
(when mu4e-icalendar-diary-file
(mu4e~icalendar-insert-diary event status
mu4e-icalendar-diary-file))))))
(defun mu4e~icalendar-delete-citation ()
"Function passed to `mu4e-compose-cite-function' to remove the citation."
(message-cite-original-without-signature)
(kill-region (point-min) (point-max)))
(defun mu4e~icalendar-trash-message (original-msg)
"Trash the message ORIGINAL-MSG and move to the next one."
(lambda (docid path)
"See `mu4e-sent-handler' for DOCID and PATH."
(mu4e-sent-handler docid path)
(let* ((docid (mu4e-message-field original-msg :docid))
(markdescr (assq 'trash mu4e-marks))
(action (plist-get (cdr markdescr) :action))
(target (mu4e-get-trash-folder original-msg)))
(with-current-buffer (mu4e-get-headers-buffer)
(run-hook-with-args 'mu4e-mark-execute-pre-hook 'trash original-msg)
(funcall action docid original-msg target))
(when (and (mu4e~headers-view-this-message-p docid)
(buffer-live-p (mu4e-get-view-buffer)))
(switch-to-buffer (mu4e-get-view-buffer))
(or (mu4e-view-headers-next)
(kill-buffer-and-window))))))
(defun mu4e~icalendar-trash-message-hook (original-msg)
(lambda () (setq mu4e-sent-func
(mu4e~icalendar-trash-message original-msg))))
(defun mu4e~icalendar-insert-diary (event reply-status filename)
"Insert a diary entry for the EVENT in file named FILENAME.
REPLY-STATUS is the status of the reply. The possible values are
given in the doc of `gnus-icalendar-event-reply-from-buffer'."
;; FIXME: handle recurring events
(let* ((beg (gnus-icalendar-event:start-time event))
(beg-date (format-time-string "%d/%m/%Y" beg))
(beg-time (format-time-string "%H:%M" beg))
(end (gnus-icalendar-event:end-time event))
(end-date (format-time-string "%d/%m/%Y" end))
(end-time (format-time-string "%H:%M" end))
(summary (gnus-icalendar-event:summary event))
(location (gnus-icalendar-event:location event))
(status (capitalize (symbol-name reply-status)))
(txt (if location
(format "%s (%s)\n %s " summary status location)
(format "%s (%s)" summary status))))
(with-temp-buffer
(if (string= beg-date end-date)
(insert beg-date " " beg-time "-" end-time " " txt "\n")
(insert beg-date " " beg-time " Start of: " txt "\n")
(insert beg-date " " end-time " End of: " txt "\n"))
(write-region (point-min) (point-max) filename t))))
;;; _
(provide 'mu4e-icalendar)
;;; mu4e-icalendar.el ends here