# olex v0.1 # A tool to copy Outlook calendar entries to 30Boxes.com # User can specify start and end dates and whether to prompt for each entry # Events can then be imported with full information or just blocked off # Events are tagged for easier management # # Copyright Jonathan Wilkins # http://bitland.net/olex # # TODO: # - handle recurring appointments better # - handle reminders import win32com.client import cookielib, datetime, httplib, md5, os, sys, time, urllib, urllib2 from optparse import OptionParser def prompt(prompt): return raw_input(prompt).strip() def get_cookies(username, password): pwdhash = md5.new(password).hexdigest() cookiejar = cookielib.LWPCookieJar() opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookiejar)) urllib2.install_opener(opener) u = urllib.quote(username) postbody = 'action=login&pass='+pwdhash+'&doneUrl=%2F&email='+u+'&x=0&y=0' headers = {'Content-type': 'application/x-www-form-urlencoded', 'Accept': 'text/plain'} try: req = urllib2.Request('http://30boxes.com/login', postbody, headers) resp = urllib2.urlopen(req) except IOError, e: print "Error logging in, Server returned", if hasattr(e, 'code'): print e.code, if hasattr(e, 'reason'): print e.reason, print return '' except HTTPError, e: print "Server said: " + e.reason return '' except URLError, e: print "Couldn't contact server: " + e.reason return '' cookies = '' print "cookiejar: " for index, c in enumerate(cookiejar): cookies += c.name + '=' + c.value + '; ' return cookies def add_event(cookies, name, start, end, body, location, tag): # POST to 30boxes.com/ajax.php # Body looks like: # action=newEvent&input= <start time> <end time> # with optional specifiers: () for body, [] for location and tag <tag> eventlist = ['action=newEvent&input='] params = [name, ' ', start, '-', end, ' '] if body != '': params.extend(['(', body, ')', ' ']) if location != '': params.extend(['[', location, ']', ' ']) if tag != '': params.extend(['tag', ' ', tag]) for x in params: x = str(x) if isinstance(x, unicode): x = x.encode('utf-8') x = urllib.quote(x) x = x.replace('/', '%2F') x = x.replace('&', '%26') x = x.replace('=', '%3D') x = x.replace('.', '%2E') x = x.replace('-', '%2D') eventlist.append(x) eventstr = ''.join(eventlist) eventstr = eventstr.replace('+', '%20') eventstr = eventstr.replace(' ', '%20') headers = {'Content-type': 'application/x-www-form-urlencoded', 'Cookie': cookies, 'Accept': 'text/plain'} h = httplib.HTTPConnection('30boxes.com:80') h.request('POST', '/ajax.php', eventstr, headers) response = h.getresponse() if response.status != 200: print "Error creating event" def main(prog, *args): print 'Starting Outlook Export ...' optp = OptionParser(usage='Usage: %prog [options]', version='%prog 0.1') optp.add_option('-u', '--user', dest='username', default='', help='Specify your 30boxes.com username') optp.add_option('-p', '--pass', dest='password', default='', help='Specify your 30boxes.com password') optp.add_option('-s', '--start', dest='start', default='', help='Specify a start date (Default is today)') optp.add_option('-e', '--end', dest='end', default='', help='Specify an end data (Default is 1 year from now)') optp.add_option('-a', '--ask', dest='ask', action='store_true', help='Ask before each adding each entry') optp.add_option('-c', '--count', dest='count', help='Process no more than <COUNT> records') optp.add_option('-t', '--tag', dest='tag', default='fromoutlook', help='Tag all added events with this string ' '(Default is fromoutlook)') optp.add_option('-b', '--blockonly', dest='blockonly', action='store_true', help="Don't store details, just block off the time") optp.add_option('-m', '--blockmsg', dest='blockmsg', default="Refer to Outlook for details", help="Message to put on blocked off events") (opts, oargs) = optp.parse_args() # get login creds if opts.username == '': opts.username = prompt("Enter your 30boxes username (email): ") if opts.password == '': opts.password = prompt("Enter your password: ") cookies = get_cookies(opts.username, opts.password) if cookies == '': print "Couldn't login, can't continue" return if opts.count: if ((int(opts.count) < 1) or (int(opts.count) > 10000)): print "-c parameter must be a number between 1 and 10000" return outlook = win32com.client.Dispatch("Outlook.Application") ns = outlook.GetNamespace("MAPI") appts = ns.GetDefaultFolder(9).Items appts.Sort("[Start]") appts.IncludeRecurrences = "True" if opts.start == '': begin = datetime.datetime.now() else: t = time.strptime(opts.start, "%m/%d/%y") begin = datetime.datetime(t[0], t[1], t[2]) if opts.end == '': end = datetime.datetime(begin.year+1, begin.month, begin.day) else: t = time.strptime(opts.end, "%m/%d/%y") end = datetime.datetime(t[0], t[1], t[2]) print "Processing events between " + str(begin.strftime("%m/%d/%Y")), print "and " + str(end.strftime("%m/%d/%Y")) restrict = "[Start] >= '" + str(begin.strftime("%m/%d/%Y")) + " 12:00 am' AND [Start] <= '" + str(end.strftime("%m/%d/%y")) + " 11:59 pm'" appts = appts.Restrict(restrict) appt = appts.GetFirst() print "-" * 40 if opts.ask: print "You will be prompted before each entry is sent to 30Boxes" print "Your options are:" print "\tY - Yes, send this event and all of it's details" print "\tN - No, don't send this event" print "\tB - Send this event without details, just block off the time" print "-" * 40 processed = 0 while appt is not None: sendit = True blocked = opts.blockonly try: # if any of these is not in the result it's '' print 'Title: ' + appt.Subject print 'Location: ' + appt.Location if appt.AllDayEvent: print 'All Day\n' else: print 'Start: ' + str(appt.Start) print 'End: ' + str(appt.End) print 'Notes:\n' + appt.Body if appt.ReminderSet: pass # XXX - handle reminder stuff except AttributeError: print "AttributeError: ", appt print "You may have to restart Outlook to correct this" except: print "Unexpected error: ", sys.exc_info()[0] print "You may have to restart Outlook to correct this" if opts.ask: validres = False while not validres: res = prompt("Send this event? (Y/N/B): ") if res.upper() == 'Y': sendit = True blocked = False validres = True elif res.upper() == 'N': sendit = False validres = True elif res.upper() == 'B': sendit = True blocked = True validres = True # Send it to 30B if blocked and sendit: add_event(cookies, opts.blockmsg, appt.Start, appt.End, '', '', opts.tag) elif not blocked and sendit: add_event(cookies, appt.Subject, appt.Start, appt.End, appt.Body, appt.Location, opts.tag) print "-" * 40 processed += 1 appt = appts.GetNext() if opts.count: if processed >= int(opts.count): appt = None if __name__ == '__main__': main(sys.argv[0], *sys.argv[1:])