2021-03-10 19:49:40 +01:00
|
|
|
|
;;; mu4e-icalendar.el --- reply to iCalendar meeting requests (part of mu4e) -*- lexical-binding: t; -*-
|
2020-02-11 12:23:40 +01:00
|
|
|
|
|
2023-01-24 19:50:15 +01:00
|
|
|
|
;; Copyright (C) 2019-2023 Christophe Troestler
|
2019-04-21 22:26:10 +02:00
|
|
|
|
|
|
|
|
|
;; 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.
|
2020-02-11 12:23:40 +01:00
|
|
|
|
|
2021-05-29 23:36:13 +02:00
|
|
|
|
;; mu4e is free software: you can redistribute it and/or modify
|
2019-04-21 22:26:10 +02:00
|
|
|
|
;; 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.
|
|
|
|
|
|
2021-05-29 23:36:13 +02:00
|
|
|
|
;; mu4e is distributed in the hope that it will be useful,
|
2019-04-21 22:26:10 +02:00
|
|
|
|
;; 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
|
2021-05-29 23:36:13 +02:00
|
|
|
|
;; along with mu4e. If not, see <http://www.gnu.org/licenses/>.
|
2019-04-13 00:53:36 +02:00
|
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
|
|
|
|
;; To install:
|
2019-04-18 11:57:39 +02:00
|
|
|
|
;; (require 'mu4e-icalendar)
|
|
|
|
|
;; (mu4e-icalendar-setup)
|
2019-04-29 09:52:28 +02:00
|
|
|
|
;; Optional
|
|
|
|
|
;; (setq mu4e-icalendar-trash-after-reply t)
|
2019-04-13 00:53:36 +02:00
|
|
|
|
|
2020-06-18 23:09:46 +02:00
|
|
|
|
;; 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').
|
|
|
|
|
|
2020-06-18 22:51:44 +02:00
|
|
|
|
;; 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.
|
|
|
|
|
|
2019-04-18 11:57:39 +02:00
|
|
|
|
;; To enable optional iCalendar->Org sync functionality
|
2019-04-13 00:53:36 +02:00
|
|
|
|
;; NOTE: both the capture file and the headline(s) inside must already exist
|
2019-04-18 23:11:57 +02:00
|
|
|
|
;; (require 'org-agenda)
|
2019-04-13 00:53:36 +02:00
|
|
|
|
;; (setq gnus-icalendar-org-capture-file "~/org/notes.org")
|
|
|
|
|
;; (setq gnus-icalendar-org-capture-headline '("Calendar"))
|
|
|
|
|
;; (gnus-icalendar-org-setup)
|
|
|
|
|
|
2019-04-21 22:26:10 +02:00
|
|
|
|
;;; Code:
|
|
|
|
|
|
2019-04-13 00:53:36 +02:00
|
|
|
|
(require 'gnus-icalendar)
|
2019-08-19 12:41:33 +02:00
|
|
|
|
(require 'cl-lib)
|
2019-04-13 00:53:36 +02:00
|
|
|
|
|
2021-03-10 19:49:40 +01:00
|
|
|
|
(require 'mu4e-mark)
|
2021-08-29 16:30:10 +02:00
|
|
|
|
(require 'mu4e-helpers)
|
|
|
|
|
(require 'mu4e-contacts)
|
2021-03-10 19:49:40 +01:00
|
|
|
|
(require 'mu4e-headers)
|
|
|
|
|
(require 'mu4e-view)
|
2020-02-06 19:28:24 +01:00
|
|
|
|
|
2021-08-29 16:30:10 +02:00
|
|
|
|
|
|
|
|
|
;;; Configuration
|
|
|
|
|
;;;; Calendar
|
2021-03-16 18:49:27 +01:00
|
|
|
|
|
2021-08-29 16:30:10 +02:00
|
|
|
|
(defgroup mu4e-icalendar nil
|
|
|
|
|
"Icalendar related settings."
|
|
|
|
|
:group 'mu4e)
|
|
|
|
|
|
|
|
|
|
(defcustom mu4e-icalendar-trash-after-reply nil
|
|
|
|
|
"If non-nil, trash the icalendar invitation after replying."
|
|
|
|
|
:type 'boolean
|
|
|
|
|
:group 'mu4e-icalendar)
|
|
|
|
|
|
|
|
|
|
(defcustom mu4e-icalendar-diary-file nil
|
|
|
|
|
"If non-nil, the file in which to add events upon reply."
|
|
|
|
|
:type '(choice (const :tag "Do not insert a diary entry" nil)
|
|
|
|
|
(string :tag "Insert a diary entry in this file"))
|
|
|
|
|
:group 'mu4e-icalendar)
|
|
|
|
|
|
|
|
|
|
|
2019-04-21 22:26:10 +02:00
|
|
|
|
;;;###autoload
|
2019-04-13 00:53:36 +02:00
|
|
|
|
(defun mu4e-icalendar-setup ()
|
2019-04-21 22:26:10 +02:00
|
|
|
|
"Perform the necessary initialization to use mu4e-icalendar."
|
2019-04-13 00:53:36 +02:00
|
|
|
|
(gnus-icalendar-setup)
|
|
|
|
|
(cl-defmethod gnus-icalendar-event:inline-reply-buttons :around
|
|
|
|
|
((event gnus-icalendar-event) handle)
|
2022-12-24 09:02:21 +01:00
|
|
|
|
(if (and (boundp 'mu4e--view-rendering)
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(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)))))
|
2019-04-13 00:53:36 +02:00
|
|
|
|
(cl-call-next-method event handle))))
|
|
|
|
|
|
2023-01-24 19:50:15 +01:00
|
|
|
|
(defun mu4e--icalendar-has-email (email list)
|
2021-04-06 13:26:49 +02:00
|
|
|
|
"Check that EMAIL is in LIST."
|
|
|
|
|
(let ((email (downcase email)))
|
2022-05-05 00:32:46 +02:00
|
|
|
|
(cl-find-if (lambda (c) (let ((e (mu4e-contact-email c)))
|
2021-04-06 13:26:49 +02:00
|
|
|
|
(and (stringp e) (string= email (downcase e)))))
|
|
|
|
|
list)))
|
|
|
|
|
|
2019-04-13 00:53:36 +02:00
|
|
|
|
(defun mu4e-icalendar-reply (data)
|
2019-04-21 22:26:10 +02:00
|
|
|
|
"Reply to the text/calendar event present in DATA."
|
2019-04-13 00:53:36 +02:00
|
|
|
|
;; Based on `gnus-icalendar-reply'.
|
|
|
|
|
(let* ((handle (car data))
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(status (cadr data))
|
|
|
|
|
(event (caddr data))
|
2023-01-24 19:50:15 +01:00
|
|
|
|
(gnus-icalendar-additional-identities
|
|
|
|
|
(mu4e-personal-addresses 'no-regexp))
|
2020-10-22 09:18:06 +02:00
|
|
|
|
(reply (gnus-icalendar-with-decoded-handle
|
2020-06-18 23:15:08 +02:00
|
|
|
|
handle
|
2021-03-16 21:24:59 +01:00
|
|
|
|
(gnus-icalendar-event-reply-from-buffer
|
|
|
|
|
(current-buffer) status (gnus-icalendar-identities))))
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(msg (mu4e-message-at-point 'noerror))
|
|
|
|
|
(charset (cdr (assoc 'charset (mm-handle-type handle)))))
|
2019-04-13 00:53:36 +02:00
|
|
|
|
(when reply
|
|
|
|
|
(cl-labels
|
2020-02-11 12:00:46 +01:00
|
|
|
|
((fold-icalendar-buffer
|
|
|
|
|
()
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(while (re-search-forward "^\\(.\\{72\\}\\)\\(.+\\)$" nil t)
|
|
|
|
|
(replace-match "\\1\n \\2")
|
|
|
|
|
(goto-char (line-beginning-position)))))
|
2019-04-13 00:53:36 +02:00
|
|
|
|
|
2021-04-06 13:26:49 +02:00
|
|
|
|
(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)
|
2023-01-24 19:50:15 +01:00
|
|
|
|
(mu4e-compose-cite-function #'mu4e--icalendar-delete-citation)
|
2021-04-06 13:26:49 +02:00
|
|
|
|
(mu4e-sent-messages-behavior 'delete)
|
|
|
|
|
(mu4e-compose-reply-recipients 'sender)
|
|
|
|
|
(ical-msg (cl-copy-list msg)))
|
2023-01-24 19:50:15 +01:00
|
|
|
|
;; Make sure the reply is sent to email of the organiser with
|
|
|
|
|
;; proper name.
|
2021-04-06 13:26:49 +02:00
|
|
|
|
(let* ((organizer (gnus-icalendar-event:organizer event))
|
2022-08-25 21:00:09 +02:00
|
|
|
|
(reply-to (car (plist-get msg :reply-to)))
|
|
|
|
|
(from (car (plist-get msg :from)))
|
|
|
|
|
(name (or (plist-get reply-to :name)
|
|
|
|
|
(plist-get from :name))))
|
|
|
|
|
;; Add :reply-to field when incomplete or absent
|
2021-04-06 13:26:49 +02:00
|
|
|
|
(unless (or (string= organizer "")
|
2023-01-24 19:50:15 +01:00
|
|
|
|
(mu4e--icalendar-has-email organizer reply-to))
|
|
|
|
|
(plist-put ical-msg :reply-to
|
|
|
|
|
`((:name ,name :email ,organizer))))
|
2022-08-25 21:00:09 +02:00
|
|
|
|
(plist-put ical-msg :subject
|
|
|
|
|
(concat (capitalize (symbol-name status))
|
|
|
|
|
": " (gnus-icalendar-event:summary event))))
|
2021-04-06 13:26:49 +02:00
|
|
|
|
(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
|
2023-01-24 19:50:15 +01:00
|
|
|
|
(mu4e--icalendar-trash-message-hook msg)
|
2021-04-06 13:26:49 +02:00
|
|
|
|
90 t)))))
|
2019-04-13 00:53:36 +02:00
|
|
|
|
|
2019-04-18 23:11:57 +02:00
|
|
|
|
;; Back in article buffer
|
|
|
|
|
(setq-local gnus-icalendar-reply-status status)
|
2020-08-03 10:33:09 +02:00
|
|
|
|
|
2021-03-10 19:49:40 +01:00
|
|
|
|
(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
|
2023-01-24 19:50:15 +01:00
|
|
|
|
(mu4e--icalendar-insert-diary event status
|
2020-02-11 12:00:46 +01:00
|
|
|
|
mu4e-icalendar-diary-file))))))
|
2019-04-13 00:53:36 +02:00
|
|
|
|
|
2023-01-24 19:50:15 +01:00
|
|
|
|
(defun mu4e--icalendar-delete-citation ()
|
2019-04-21 22:26:10 +02:00
|
|
|
|
"Function passed to `mu4e-compose-cite-function' to remove the citation."
|
2020-06-18 23:09:46 +02:00
|
|
|
|
(message-cite-original-without-signature)
|
|
|
|
|
(kill-region (point-min) (point-max)))
|
2019-04-13 00:53:36 +02:00
|
|
|
|
|
2023-01-24 19:50:15 +01:00
|
|
|
|
(defun mu4e--icalendar-trash-message (original-msg)
|
2019-04-29 20:17:27 +02:00
|
|
|
|
"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))
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(markdescr (assq 'trash mu4e-marks))
|
|
|
|
|
(action (plist-get (cdr markdescr) :action))
|
|
|
|
|
(target (mu4e-get-trash-folder original-msg)))
|
2019-04-29 20:17:27 +02:00
|
|
|
|
(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)
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(buffer-live-p (mu4e-get-view-buffer)))
|
2022-11-18 13:54:27 +01:00
|
|
|
|
(mu4e-display-buffer (mu4e-get-view-buffer))
|
2019-04-29 20:17:27 +02:00
|
|
|
|
(or (mu4e-view-headers-next)
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(kill-buffer-and-window))))))
|
2019-04-18 11:00:50 +02:00
|
|
|
|
|
2023-01-24 19:50:15 +01:00
|
|
|
|
(defun mu4e--icalendar-trash-message-hook (original-msg)
|
|
|
|
|
"Trash the icalender message ORIGINAL-MSG."
|
|
|
|
|
(lambda ()
|
|
|
|
|
(setq mu4e-sent-func
|
|
|
|
|
(mu4e--icalendar-trash-message original-msg))))
|
2021-03-17 00:34:04 +01:00
|
|
|
|
|
2023-01-24 19:50:15 +01:00
|
|
|
|
(defun mu4e--icalendar-insert-diary (event reply-status filename)
|
2019-04-21 22:26:10 +02:00
|
|
|
|
"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'."
|
2019-04-18 23:11:57 +02:00
|
|
|
|
;; FIXME: handle recurring events
|
|
|
|
|
(let* ((beg (gnus-icalendar-event:start-time event))
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(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))))
|
2019-04-18 23:11:57 +02:00
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(if (string= beg-date end-date)
|
2020-02-11 12:00:46 +01:00
|
|
|
|
(insert beg-date " " beg-time "-" end-time " " txt "\n")
|
2019-04-18 23:11:57 +02:00
|
|
|
|
(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))))
|
|
|
|
|
|
2023-01-24 19:50:15 +01:00
|
|
|
|
;;;
|
2019-04-13 00:53:36 +02:00
|
|
|
|
(provide 'mu4e-icalendar)
|
|
|
|
|
;;; mu4e-icalendar.el ends here
|