dotfiles/.mutt/scripts/mutt-ical.py

224 lines
7.1 KiB
Python
Executable File

#!/usr/bin/env python
# -*- coding: utf8 -*-
"""
This script is meant as a simple way to reply to ical invitations from mutt.
See README for instructions and LICENSE for licensing information.
"""
from __future__ import with_statement
__author__="Martin Sander"
__license__="MIT"
from tzlocal import get_localzone
import pytz
import vobject
import tempfile, time
import os, sys
import warnings
from datetime import date, datetime
from subprocess import Popen, PIPE
from getopt import gnu_getopt as getopt
timezone = get_localzone()
mutt="mutt"
usage="""
usage:
%s [OPTIONS] -e your@email.address filename.ics
OPTIONS:
-i interactive
-a accept
-d decline
-t tentatively accept
-c mutt_command
(accept is default, last one wins)
""" % sys.argv[0]
def del_if_present(dic, key):
if dic.has_key(key):
del dic[key]
def set_accept_state(attendees, state):
for attendee in attendees:
attendee.params['PARTSTAT'] = [unicode(state)]
for i in ["RSVP","ROLE","X-NUM-GUESTS","CUTYPE"]:
del_if_present(attendee.params,i)
return attendees
def get_accept_decline():
while True:
sys.stdout.write("\nAccept Invitation? [y/n/t/q]")
ans = sys.stdin.readline()
if ans.lower() == 'y\n':
return 'ACCEPTED'
elif ans.lower() == 'n\n':
return 'DECLINED'
elif ans.lower() == 't\n':
return 'TENTATIVE'
elif ans.lower() == 'q\n':
return ''
def get_answer(invitation):
# create
ans = vobject.newFromBehavior('vcalendar')
ans.add('method')
ans.method.value = "REPLY"
ans.add('vevent')
# just copy from invitation
for i in ["uid", "summary", "dtstart", "dtend", "organizer"]:
if invitation.vevent.contents.has_key(i):
ans.vevent.add( invitation.vevent.contents[i][0] )
# new timestamp
ans.vevent.add('dtstamp')
ans.vevent.dtstamp.value = datetime.utcnow().replace(
tzinfo = invitation.vevent.dtstamp.value.tzinfo)
return ans
def write_to_tempfile(ical):
tempdir = tempfile.mkdtemp()
icsfile = tempdir+"/event-reply.ics"
with open(icsfile,"w") as f:
f.write(ical.serialize())
return icsfile, tempdir
def get_mutt_command(ical, email_address, accept_decline, icsfile):
accept_decline = accept_decline.capitalize()
if ical.vevent.contents.has_key('organizer'):
if hasattr(ical.vevent.organizer,'EMAIL_param'):
sender = ical.vevent.organizer.EMAIL_param
else:
sender = ical.vevent.organizer.value.split(':')[1] #workaround for MS
else:
sender = "NO SENDER"
summary = ical.vevent.contents['summary'][0].value.encode()
command = [mutt, "-e", "my_hdr From: %s" % email_address, "-a", icsfile,
"-s", "%s: %s" % (accept_decline, summary), "--", sender]
#Uncomment the below line, and move it above the -s line to enable the wrapper
#"-e", 'set sendmail=\'ical_reply_sendmail_wrapper.sh\'',
return command
def execute(command, mailtext):
process = Popen(command, stdin=PIPE)
process.stdin.write(mailtext)
process.stdin.close()
result = None
while result is None:
result = process.poll()
time.sleep(.1)
if result != 0:
print "unable to send reply, subprocess exited with\
exit code %d\nPress return to continue" % result
sys.stdin.readline()
def openics(invitation_file):
with open(invitation_file) as f:
try:
with warnings.catch_warnings(): #vobject uses deprecated Exception stuff
warnings.simplefilter("ignore")
invitation = vobject.readOne(f, ignoreUnreadable=True)
except AttributeError:
invitation = vobject.readOne(f, ignoreUnreadable=True)
return invitation
def display(ical):
summary = ical.vevent.contents['summary'][0].value.encode()
if ical.vevent.contents.has_key('organizer'):
if hasattr(ical.vevent.organizer,'EMAIL_param'):
sender = ical.vevent.organizer.EMAIL_param
else:
sender = ical.vevent.organizer.value.split(':')[1] #workaround for MS
else:
sender = "NO SENDER"
if ical.vevent.contents.has_key('description'):
description = ical.vevent.contents['description'][0].value
else:
description = "NO DESCRIPTION"
if ical.vevent.contents.has_key('attendee'):
attendees = ical.vevent.contents['attendee']
else:
attendees = ""
sys.stdout.write("Start:\t" + ical.vevent.dtstart.value.astimezone(timezone).strftime('%Y-%m-%d %I:%M %p %Z') + "\n")
sys.stdout.write("End:\t" + ical.vevent.dtend.value.astimezone(timezone).strftime('%Y-%m-%d %I:%M %p %Z') + "\n")
sys.stdout.write("From:\t" + sender + "\n")
sys.stdout.write("Title:\t" + summary + "\n")
sys.stdout.write("To:\t")
for attendee in attendees:
if hasattr(attendee,'EMAIL_param'):
sys.stdout.write(attendee.CN_param + " <" + attendee.EMAIL_param + ">, ")
else:
sys.stdout.write(attendee.CN_param + " <" + attendee.value.split(':')[1] + ">, ") #workaround for MS
sys.stdout.write("\n\n")
sys.stdout.write(description + "\n")
if __name__=="__main__":
email_address = None
email_addresses = []
accept_decline = ''
opts, args=getopt(sys.argv[1:],"e:aidtc:")
if len(args) < 1:
sys.stderr.write(usage)
sys.exit(1)
invitation = openics(args[0])
#print(invitation)
display(invitation)
for opt,arg in opts:
if opt == '-e':
email_addresses = arg.split(',')
if opt == '-i':
accept_decline = get_accept_decline()
if opt == '-a':
accept_decline = 'ACCEPTED'
if opt == '-d':
accept_decline = 'DECLINED'
if opt == '-t':
accept_decline = 'TENTATIVE'
if opt == '-c':
mutt = arg
if accept_decline == '':
sys.exit(0)
ans = get_answer(invitation)
if invitation.vevent.contents.has_key('attendee'):
attendees = invitation.vevent.contents['attendee']
else:
attendees = ""
set_accept_state(attendees,accept_decline)
ans.vevent.add('attendee')
ans.vevent.attendee_list.pop()
flag = 1
for attendee in attendees:
if hasattr(attendee,'EMAIL_param'):
if attendee.EMAIL_param in email_addresses:
ans.vevent.attendee_list.append(attendee)
email_address = attendee.EMAIL_param
flag = 0
else:
if attendee.value.split(':')[1] in email_addresses:
ans.vevent.attendee_list.append(attendee)
email_address = attendee.value.split(':')[1]
flag = 0
if flag:
sys.stderr.write("Seems like you have not been invited to this event!\n")
sys.exit(1)
icsfile, tempdir = write_to_tempfile(ans)
mutt_command = get_mutt_command(ans, email_address, accept_decline, icsfile)
mailtext = "From: %s\n\n%s has %s" % (email_address, email_address, accept_decline.lower())
execute(mutt_command, mailtext)
os.remove(icsfile)
os.rmdir(tempdir)