179 lines
		
	
	
	
		
			5.6 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			179 lines
		
	
	
	
		
			5.6 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
#!/usr/bin/env python
 | 
						|
from __future__ import print_function
 | 
						|
import datetime
 | 
						|
import httplib2
 | 
						|
import itertools
 | 
						|
import logging
 | 
						|
import optparse
 | 
						|
import os
 | 
						|
from six.moves import urllib
 | 
						|
import sys
 | 
						|
import time
 | 
						|
import traceback
 | 
						|
from typing import List, Set, Tuple, Iterable, Optional
 | 
						|
 | 
						|
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__), '../../'))
 | 
						|
import zulip
 | 
						|
 | 
						|
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__))
 | 
						|
 | 
						|
parser = optparse.OptionParser(r"""
 | 
						|
 | 
						|
%prog \
 | 
						|
    --user foo@zulip.com \
 | 
						|
    --calendar calendarID@example.calendar.google.com
 | 
						|
 | 
						|
    This integration can be used to send yourself reminders, on Zulip, of Google Calendar Events.
 | 
						|
 | 
						|
    Before running this integration make sure you run the get-google-credentials file to give Zulip
 | 
						|
    access to certain aspects of your Google Account.
 | 
						|
 | 
						|
    This integration should be run on your local machine. Your API key and other information are
 | 
						|
    revealed to local users through the command line.
 | 
						|
 | 
						|
    Depends on: google-api-python-client
 | 
						|
""")
 | 
						|
 | 
						|
 | 
						|
parser.add_option('--interval',
 | 
						|
                  dest='interval',
 | 
						|
                  default=30,
 | 
						|
                  type=int,
 | 
						|
                  action='store',
 | 
						|
                  help='Minutes before event for reminder [default: 30]',
 | 
						|
                  metavar='MINUTES')
 | 
						|
 | 
						|
parser.add_option('--calendar',
 | 
						|
                  dest = 'calendarID',
 | 
						|
                  default = 'primary',
 | 
						|
                  type = str,
 | 
						|
                  action = 'store',
 | 
						|
                  help = 'Calendar ID for the calendar you want to receive reminders from.')
 | 
						|
 | 
						|
parser.add_option_group(zulip.generate_option_group(parser))
 | 
						|
 | 
						|
(options, args) = parser.parse_args()
 | 
						|
 | 
						|
if not (options.zulip_email):
 | 
						|
    parser.error('You must specify --user')
 | 
						|
 | 
						|
zulip_client = zulip.init_from_options(options)
 | 
						|
 | 
						|
def get_credentials():
 | 
						|
    # type: () -> client.Credentials
 | 
						|
    """Gets valid user credentials from storage.
 | 
						|
 | 
						|
    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.")
 | 
						|
 | 
						|
 | 
						|
def get_events():
 | 
						|
    # type: () -> Iterable[Tuple[int, datetime.datetime, str]]
 | 
						|
    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]
 | 
						|
        # 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)
 | 
						|
        try:
 | 
						|
            yield (event["id"], start, event["summary"])
 | 
						|
        except KeyError:
 | 
						|
            yield (event["id"], start, "(No Title)")
 | 
						|
 | 
						|
 | 
						|
def send_reminders():
 | 
						|
    # type: () -> Optional[None]
 | 
						|
    global sent
 | 
						|
 | 
						|
    messages = []
 | 
						|
    keys = set()
 | 
						|
    now = datetime.datetime.now()
 | 
						|
 | 
						|
    for id, start, summary in events:
 | 
						|
        dt = start - now
 | 
						|
        if dt.days == 0 and dt.seconds < 60 * options.interval:
 | 
						|
            # The unique key includes the start time, because of
 | 
						|
            # repeating events.
 | 
						|
            key = (id, start)
 | 
						|
            if key not in sent:
 | 
						|
                if start.hour == 0 and start.minute == 0:
 | 
						|
                    line = '%s is today.' % (summary,)
 | 
						|
                else:
 | 
						|
                    line = '%s starts at %s' % (summary, start.strftime('%H:%M'))
 | 
						|
                print('Sending reminder:', line)
 | 
						|
                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)
 | 
						|
 | 
						|
    zulip_client.send_message(dict(
 | 
						|
        type    = 'private',
 | 
						|
        to      = options.zulip_email,
 | 
						|
        sender  = options.zulip_email,
 | 
						|
        content = message))
 | 
						|
 | 
						|
    sent.update(keys)
 | 
						|
 | 
						|
# 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:
 | 
						|
        logging.exception("Couldn't download Google calendar and/or couldn't post to Zulip.")
 | 
						|
    time.sleep(60)
 |