|
# using xclip and xdotool to simulate keyboard event |
|
import argparse |
|
import datetime |
|
import os |
|
import re |
|
import shlex |
|
import time |
|
import subprocess |
|
from pprint import pprint |
|
|
|
from bs4 import BeautifulSoup |
|
from icalendar import Calendar, Event, vText |
|
|
|
|
|
def argparser(): |
|
parser = argparse.ArgumentParser(description='Outlook .ics file to gcal') |
|
parser.add_argument('--ics', dest='ics', required=True, help='input file') |
|
parser.add_argument( |
|
'--cal', |
|
dest='cal', |
|
default='primary', |
|
help='execute to which calendar') |
|
parser.add_argument('--sleep', dest='sleep', type=int, default=1) |
|
parser.add_argument('--delay', dest='delay', type=int, default=150) |
|
return parser |
|
|
|
def parse_ics(ics): |
|
events = [] |
|
with open(ics, 'r') as rf: |
|
ical = Calendar().from_ical(rf.read()) |
|
ical_config = dict(ical.sorted_items()) |
|
for i, comp in enumerate(ical.walk()): |
|
if comp.name == 'VEVENT': |
|
event = {} |
|
for name, prop in comp.property_items(): |
|
|
|
if name in ['SUMMARY', 'LOCATION']: |
|
event[name.lower()] = prop.to_ical().decode('utf-8') |
|
|
|
elif name == 'DTSTART': |
|
event['start'] = { |
|
# 'dateTime': prop.dt.isoformat(), |
|
# 'timeZone': str(prop.dt.tzinfo), |
|
'dateTime': prop.dt |
|
} |
|
|
|
elif name == 'DTEND': |
|
event['end'] = { |
|
# 'dateTime': prop.dt.isoformat(), |
|
# 'timeZone': str(prop.dt.tzinfo), |
|
'dateTime': prop.dt |
|
} |
|
|
|
elif name == 'SEQUENCE': |
|
event[name.lower()] = prop |
|
|
|
elif name == 'TRANSP': |
|
event['transparency'] = prop.lower() |
|
|
|
elif name == 'CLASS': |
|
event['visibility'] = prop.lower() |
|
|
|
elif name == 'ORGANIZER': |
|
event['organizer'] = { |
|
'displayName': prop.params.get('CN') or '', |
|
'email': re.match('mailto:(.*)', prop).group(1) |
|
or '' |
|
} |
|
|
|
elif name == 'DESCRIPTION': |
|
desc = prop.to_ical().decode('utf-8') |
|
desc = desc.replace(u'\xa0', u' ') |
|
if name.lower() in event: |
|
event[name.lower( |
|
)] = desc + '\r\n' + event[name.lower()] |
|
else: |
|
event[name.lower()] = desc |
|
|
|
elif name == 'X-ALT-DESC' and 'description' not in event: |
|
soup = BeautifulSoup(prop, 'lxml') |
|
desc = soup.body.text.replace(u'\xa0', u' ') |
|
if 'description' in event: |
|
event['description'] += '\r\n' + desc |
|
else: |
|
event['description'] = desc |
|
|
|
elif name == 'ATTENDEE': |
|
RSVP = prop.params.get('RSVP') or '' |
|
RSVP = 'RSVP={}'.format('TRUE:{}'.format(prop) if RSVP == 'TRUE' else RSVP) |
|
ROLE = prop.params.get('ROLE') or '' |
|
email = re.match('mailto:(.*)', prop).group(1) or '' |
|
|
|
if 'attendees' not in event: |
|
event['attendees'] = [] |
|
event['attendees'].append({ |
|
'displayName': prop.params.get('CN') or '', |
|
'email': email, |
|
'comment': ROLE |
|
}) |
|
|
|
# VALARM: only remind by UI popup |
|
elif name == 'ACTION': |
|
event['reminders'] = {'useDefault': True} |
|
|
|
else: |
|
# print(name) |
|
pass |
|
|
|
events.append(event) |
|
|
|
return events |
|
|
|
def syscall(instruction): |
|
args = shlex.split(instruction) |
|
complete_process = subprocess.run( |
|
args=args, check=True, stdout=subprocess.PIPE) |
|
return complete_process |
|
|
|
def ctrl_v(msg, sleep=1): |
|
os.system('echo "{}" | xclip -selection clipboard'.format(msg)) |
|
time.sleep(sleep) |
|
os.system('xdotool key ctrl+v') |
|
|
|
def delay_type(msg, delay=150): |
|
os.system('xdotool type --delay {} {}'.format(delay, msg)) |
|
|
|
def main(args): |
|
events = parse_ics(args.ics) |
|
windows_id = syscall('xdotool search --name "Google Chrome"') |
|
windows_id = windows_id.stdout.decode('utf-8').strip('\n') |
|
syscall('xdotool windowfocus {}'.format(windows_id)) |
|
|
|
for i, event in enumerate(events): |
|
# gcal hotkey c - create event |
|
os.system('xdotool key c') |
|
|
|
# if not in the editevent page, reload and try next event |
|
os.system('xdotool key ctrl+l') |
|
time.sleep(1) |
|
os.system('xdotool key ctrl+c') |
|
url = syscall('xclip -selection clipboard -o') |
|
url = url.stdout.decode('utf-8') |
|
if not re.match(r'https://calendar.google.com/calendar/.*/eventedit', url): |
|
os.system('xdotool key F5') |
|
time.sleep(10) |
|
break |
|
else: |
|
os.system('xdotool key{}'.format(' Tab'*8)) |
|
|
|
# paste summary from clipboard |
|
ctrl_v(event['summary'], sleep=args.sleep) |
|
|
|
# move to date |
|
if isinstance(event['start']['dateTime'], datetime.datetime): |
|
os.system('xdotool key Tab Tab ctrl+a') |
|
delay_type(event['start']['dateTime'].strftime('%Y/%m/%d'), delay=args.delay) |
|
os.system('xdotool key Tab ctrl+a') |
|
delay_type(event['start']['dateTime'].strftime('%H:%M'), delay=args.delay) |
|
os.system('xdotool key Tab ctrl+a') |
|
delay_type(event['end']['dateTime'].strftime('%H:%M'), delay=args.delay) |
|
os.system('xdotool key Tab ctrl+a') |
|
delay_type(event['end']['dateTime'].strftime('%Y/%m/%d'), delay=args.delay) |
|
|
|
# move to location |
|
os.system('xdotool key{}'.format(' Tab' * 10)) |
|
elif isinstance(event['start']['dateTime'], datetime.date): |
|
os.system('xdotool key{} space'.format(' Tab'*7)) |
|
os.system('xdotool key{}'.format(' Shift+Tab'*2)) |
|
delay_type(event['start']['dateTime'].strftime('%Y/%m/%d'), delay=args.delay) |
|
os.system('xdotool key Tab') |
|
delay_type(event['end']['dateTime'].strftime('%Y/%m/%d'), delay=args.delay) |
|
|
|
# move to location |
|
os.system('xdotool key{}'.format(' Tab' * 9)) |
|
|
|
# move to location |
|
if 'location' in event: |
|
ctrl_v(event['location'], sleep=args.sleep) |
|
|
|
# type description |
|
os.system('xdotool key{}'.format(' Tab'*8)) |
|
if 'description' in event: |
|
ctrl_v(event['description'], sleep=args.sleep) |
|
|
|
# move to attendees |
|
os.system('xdotool key{}'.format(' Shift+Tab'*13)) |
|
if 'attendees' in event: |
|
for attendee in event['attendees']: |
|
if 'email' not in attendee: |
|
continue |
|
os.system('xdotool type --delay 150 {}'.format(attendee['email'])) |
|
os.system('xdotool key Return') |
|
|
|
# save eunbank.com.twvent without sending invitation |
|
os.system('xdotool key ctrl+s') |
|
time.sleep(args.sleep) |
|
os.system('xdotool key Shift+Tab Return') |
|
print('({}/{}) - Insert {}'.format(i+1, len(events), event['summary'])) |
|
time.sleep(10) |
|
|
|
|
|
if __name__ == '__main__': |
|
parser = argparser() |
|
main(parser.parse_args()) |