2016-04-07 09:03:22 -04:00
|
|
|
#!/usr/bin/env python
|
2016-03-10 11:15:34 -05:00
|
|
|
from __future__ import print_function
|
2012-10-21 16:18:51 -04:00
|
|
|
import datetime
|
2016-12-15 20:38:44 -05:00
|
|
|
import httplib2
|
|
|
|
import itertools
|
|
|
|
import logging
|
2012-10-21 16:18:51 -04:00
|
|
|
import optparse
|
2016-12-15 20:38:44 -05:00
|
|
|
import os
|
2016-01-23 21:39:44 -05:00
|
|
|
from six.moves import urllib
|
2016-12-15 20:38:44 -05:00
|
|
|
import sys
|
|
|
|
import time
|
2012-10-21 16:18:51 -04:00
|
|
|
import traceback
|
2016-12-15 20:38:44 -05:00
|
|
|
from typing import List, Set, Tuple, Iterable, Optional
|
2012-10-21 16:18:51 -04:00
|
|
|
|
2016-12-15 20:38:44 -05:00
|
|
|
from oauth2client import client, tools
|
|
|
|
from oauth2client.file import Storage
|
|
|
|
try:
|
|
|
|
from googleapiclient import discovery
|
|
|
|
except ImportError:
|
|
|
|
logging.exception('Install google-api-python-client')
|
|
|
|
|
|
|
|
sys.path.append(os.path.join(os.path.dirname(__file__), '../../'))
|
2013-08-07 11:51:03 -04:00
|
|
|
import zulip
|
2016-12-15 20:38:44 -05:00
|
|
|
|
|
|
|
SCOPES = 'https://www.googleapis.com/auth/calendar.readonly'
|
|
|
|
CLIENT_SECRET_FILE = 'client_secret.json'
|
|
|
|
APPLICATION_NAME = 'Zulip'
|
|
|
|
HOME_DIR = os.path.expanduser('~')
|
|
|
|
|
|
|
|
# Our cached view of the calendar, updated periodically.
|
|
|
|
events = [] # type: List[Tuple[int, datetime.datetime, str]]
|
|
|
|
|
|
|
|
# Unique keys for events we've already sent, so we don't remind twice.
|
|
|
|
sent = set() # type: Set[Tuple[int, datetime.datetime]]
|
|
|
|
|
|
|
|
sys.path.append(os.path.dirname(__file__))
|
2012-10-21 16:18:51 -04:00
|
|
|
|
|
|
|
parser = optparse.OptionParser(r"""
|
|
|
|
|
|
|
|
%prog \
|
2013-07-24 15:27:14 -04:00
|
|
|
--user foo@zulip.com \
|
2016-12-15 20:38:44 -05:00
|
|
|
--calendar calendarID@example.calendar.google.com
|
2012-10-21 16:18:51 -04:00
|
|
|
|
2016-12-15 20:38:44 -05:00
|
|
|
This integration can be used to send yourself reminders, on Zulip, of Google Calendar Events.
|
2012-10-21 16:18:51 -04:00
|
|
|
|
2016-12-15 20:38:44 -05:00
|
|
|
Before running this integration make sure you run the get-google-credentials file to give Zulip
|
|
|
|
access to certain aspects of your Google Account.
|
2012-10-21 16:18:51 -04:00
|
|
|
|
2016-12-15 20:38:44 -05:00
|
|
|
This integration should be run on your local machine. Your API key and other information are
|
|
|
|
revealed to local users through the command line.
|
2012-10-21 16:18:51 -04:00
|
|
|
|
2016-12-15 20:38:44 -05:00
|
|
|
Depends on: google-api-python-client
|
2012-10-21 16:18:51 -04:00
|
|
|
""")
|
|
|
|
|
2016-12-15 20:38:44 -05:00
|
|
|
|
2012-10-21 16:18:51 -04:00
|
|
|
parser.add_option('--interval',
|
2016-12-11 08:30:45 -05:00
|
|
|
dest='interval',
|
2016-12-15 20:38:44 -05:00
|
|
|
default=30,
|
2016-12-11 08:30:45 -05:00
|
|
|
type=int,
|
|
|
|
action='store',
|
2016-12-15 20:38:44 -05:00
|
|
|
help='Minutes before event for reminder [default: 30]',
|
2016-12-11 08:30:45 -05:00
|
|
|
metavar='MINUTES')
|
2016-12-15 20:38:44 -05:00
|
|
|
|
|
|
|
parser.add_option('--calendar',
|
|
|
|
dest = 'calendarID',
|
|
|
|
default = 'primary',
|
|
|
|
type = str,
|
|
|
|
action = 'store',
|
|
|
|
help = 'Calendar ID for the calendar you want to receive reminders from.')
|
|
|
|
|
2013-08-07 11:51:03 -04:00
|
|
|
parser.add_option_group(zulip.generate_option_group(parser))
|
2012-10-21 16:18:51 -04:00
|
|
|
|
|
|
|
(options, args) = parser.parse_args()
|
|
|
|
|
2016-12-15 20:38:44 -05:00
|
|
|
if not (options.zulip_email):
|
|
|
|
parser.error('You must specify --user')
|
2012-10-21 16:18:51 -04:00
|
|
|
|
2016-12-15 20:38:44 -05:00
|
|
|
zulip_client = zulip.init_from_options(options)
|
2012-10-21 16:18:51 -04:00
|
|
|
|
2016-12-15 20:38:44 -05:00
|
|
|
def get_credentials():
|
|
|
|
# type: () -> client.Credentials
|
|
|
|
"""Gets valid user credentials from storage.
|
2012-10-21 16:18:51 -04:00
|
|
|
|
2016-12-15 20:38:44 -05:00
|
|
|
If nothing has been stored, or if the stored credentials are invalid,
|
|
|
|
an exception is thrown and the user is informed to run the script in this directory to get
|
|
|
|
credentials.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Credentials, the obtained credential.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
credential_path = os.path.join(HOME_DIR,
|
|
|
|
'google-credentials.json')
|
|
|
|
|
|
|
|
store = Storage(credential_path)
|
|
|
|
credentials = store.get()
|
|
|
|
|
|
|
|
return credentials
|
|
|
|
except client.Error:
|
|
|
|
logging.exception('Error while trying to open the `google-credentials.json` file.')
|
|
|
|
except IOError:
|
|
|
|
logging.error("Run the get-google-credentials script from this directory first.")
|
2012-10-21 16:18:51 -04:00
|
|
|
|
|
|
|
|
|
|
|
def get_events():
|
2016-11-29 23:05:00 -05:00
|
|
|
# type: () -> Iterable[Tuple[int, datetime.datetime, str]]
|
2016-12-15 20:38:44 -05:00
|
|
|
credentials = get_credentials()
|
|
|
|
creds = credentials.authorize(httplib2.Http())
|
|
|
|
service = discovery.build('calendar', 'v3', http=creds)
|
|
|
|
|
|
|
|
now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time
|
|
|
|
feed = service.events().list(calendarId=options.calendarID, timeMin=now, maxResults=5,
|
|
|
|
singleEvents=True, orderBy='startTime').execute()
|
|
|
|
|
|
|
|
for event in feed["items"]:
|
|
|
|
try:
|
|
|
|
start = event["start"]["dateTime"]
|
|
|
|
except KeyError:
|
|
|
|
start = event["start"]["date"]
|
|
|
|
start = start[:19]
|
2013-01-16 13:51:32 -05:00
|
|
|
# All-day events can have only a date
|
|
|
|
fmt = '%Y-%m-%dT%H:%M:%S' if 'T' in start else '%Y-%m-%d'
|
|
|
|
start = datetime.datetime.strptime(start, fmt)
|
2016-12-15 20:38:44 -05:00
|
|
|
try:
|
|
|
|
yield (event["id"], start, event["summary"])
|
|
|
|
except KeyError:
|
|
|
|
yield (event["id"], start, "(No Title)")
|
2012-10-21 16:18:51 -04:00
|
|
|
|
|
|
|
|
|
|
|
def send_reminders():
|
2016-11-29 23:05:00 -05:00
|
|
|
# type: () -> Optional[None]
|
2012-10-21 16:18:51 -04:00
|
|
|
global sent
|
|
|
|
|
|
|
|
messages = []
|
|
|
|
keys = set()
|
|
|
|
now = datetime.datetime.now()
|
|
|
|
|
2016-12-15 20:38:44 -05:00
|
|
|
for id, start, summary in events:
|
2012-10-21 16:18:51 -04:00
|
|
|
dt = start - now
|
2016-12-15 20:38:44 -05:00
|
|
|
if dt.days == 0 and dt.seconds < 60 * options.interval:
|
2012-10-21 16:18:51 -04:00
|
|
|
# The unique key includes the start time, because of
|
|
|
|
# repeating events.
|
2016-12-15 20:38:44 -05:00
|
|
|
key = (id, start)
|
2012-10-21 16:18:51 -04:00
|
|
|
if key not in sent:
|
2016-12-15 20:38:44 -05:00
|
|
|
if start.hour == 0 and start.minute == 0:
|
2017-01-09 14:45:11 -05:00
|
|
|
line = '%s is today.' % (summary,)
|
2016-12-15 20:38:44 -05:00
|
|
|
else:
|
|
|
|
line = '%s starts at %s' % (summary, start.strftime('%H:%M'))
|
2016-03-10 11:15:34 -05:00
|
|
|
print('Sending reminder:', line)
|
2012-10-21 16:18:51 -04:00
|
|
|
messages.append(line)
|
|
|
|
keys.add(key)
|
|
|
|
|
|
|
|
if not messages:
|
|
|
|
return
|
|
|
|
|
|
|
|
if len(messages) == 1:
|
|
|
|
message = 'Reminder: ' + messages[0]
|
|
|
|
else:
|
|
|
|
message = 'Reminder:\n\n' + '\n'.join('* ' + m for m in messages)
|
|
|
|
|
2016-12-15 20:38:44 -05:00
|
|
|
zulip_client.send_message(dict(
|
2012-12-03 16:13:00 -05:00
|
|
|
type = 'private',
|
2016-12-15 20:38:44 -05:00
|
|
|
to = options.zulip_email,
|
|
|
|
sender = options.zulip_email,
|
2012-12-03 16:13:00 -05:00
|
|
|
content = message))
|
2012-10-21 16:18:51 -04:00
|
|
|
|
2016-12-15 20:38:44 -05:00
|
|
|
sent.update(keys)
|
2012-10-21 16:18:51 -04:00
|
|
|
|
|
|
|
# Loop forever
|
|
|
|
for i in itertools.count():
|
|
|
|
try:
|
|
|
|
# We check reminders every minute, but only
|
|
|
|
# download the calendar every 10 minutes.
|
|
|
|
if not i % 10:
|
|
|
|
events = list(get_events())
|
|
|
|
send_reminders()
|
|
|
|
except:
|
2016-12-15 20:38:44 -05:00
|
|
|
logging.exception("Couldn't download Google calendar and/or couldn't post to Zulip.")
|
2012-10-21 16:18:51 -04:00
|
|
|
time.sleep(60)
|