Last active
June 18, 2020 10:04
-
-
Save zwalchuk/73188198b386a996da25a6f091ff82b4 to your computer and use it in GitHub Desktop.
Watson Slackbot Google Calendar Integration
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{"name":"Calendar Bot","created":"2016-07-25T19:46:12.345Z","intents":[{"intent":"anything_else","created":"2016-08-24T15:02:45.649Z","examples":[{"text":"Can anyone figure this out?","created":"2016-08-24T15:03:21.421Z"},{"text":"eleven and a half","created":"2016-08-24T15:03:42.067Z"},{"text":"Fish","created":"2016-08-24T15:03:00.288Z"},{"text":"five","created":"2016-08-24T15:03:36.329Z"},{"text":"I'd like a taco","created":"2016-08-24T15:02:50.984Z"},{"text":"jump to it","created":"2016-08-24T15:03:47.126Z"},{"text":"sldkjflsdjk","created":"2016-08-24T15:02:56.602Z"},{"text":"The Berlin Wall","created":"2016-08-24T15:03:06.128Z"},{"text":"What in the world are you doing?","created":"2016-08-24T15:03:14.107Z"},{"text":"What's the meaning of life?","created":"2016-08-24T15:03:31.696Z"}],"description":null},{"intent":"free_time","created":"2016-08-24T14:53:44.239Z","examples":[{"text":"Am I free on Monday?","created":"2016-08-24T14:54:23.712Z"},{"text":"Can I schedule a meeting Thursday morning?","created":"2016-08-24T14:55:24.967Z"},{"text":"Do I have a break today?","created":"2016-08-24T15:12:27.464Z"},{"text":"Do I have any space Tuesday afternoon?","created":"2016-08-24T14:54:33.156Z"},{"text":"What times do I have available on Wednesday?","created":"2016-08-24T14:54:09.000Z"},{"text":"When am I next available?","created":"2016-08-24T14:54:58.339Z"},{"text":"When is my next open time?","created":"2016-08-24T14:54:46.235Z"}],"description":null},{"intent":"goodbyes","created":"2016-08-24T15:16:15.170Z","examples":[{"text":"adios","created":"2016-08-24T15:16:33.100Z"},{"text":"Bye!!","created":"2016-08-24T15:16:23.019Z"},{"text":"goodbye","created":"2016-08-24T15:16:46.062Z"},{"text":"hope to see you soon","created":"2016-08-24T17:10:42.148Z"},{"text":"later","created":"2016-08-24T15:16:18.133Z"},{"text":"see ya","created":"2016-08-24T15:16:26.359Z"},{"text":"talk to you later","created":"2016-08-24T15:16:30.886Z"},{"text":"ttyl","created":"2016-08-24T15:16:52.120Z"}],"description":null},{"intent":"greetings","created":"2016-08-24T15:15:32.450Z","examples":[{"text":"can someone help me?","created":"2016-08-24T17:22:21.893Z"},{"text":"hello","created":"2016-08-24T15:15:41.014Z"},{"text":"Hello?","created":"2016-08-24T15:16:08.871Z"},{"text":"Hey there!","created":"2016-08-24T15:15:49.071Z"},{"text":"hi","created":"2016-08-24T15:15:44.142Z"},{"text":"sup","created":"2016-08-24T15:15:56.597Z"},{"text":"'sup?","created":"2016-08-24T15:15:52.759Z"},{"text":"yo","created":"2016-08-24T15:15:59.875Z"}],"description":null},{"intent":"schedule","created":"2016-08-24T14:53:51.645Z","examples":[{"text":"Tell me about my day","created":"2016-08-24T14:56:02.340Z"},{"text":"What do I have going on Friday?","created":"2016-08-24T15:07:01.392Z"},{"text":"What meetings do I have this afternoon?","created":"2016-08-24T14:55:39.309Z"},{"text":"What's my schedule look like?","created":"2016-08-24T14:55:46.075Z"},{"text":"What's on my calendar today?","created":"2016-08-24T14:55:54.273Z"},{"text":"Where do I need to be next week?","created":"2016-08-24T14:56:52.622Z"},{"text":"Who am I meeting with tomorrow?","created":"2016-08-24T14:56:11.126Z"}],"description":null}],"updated":"2016-10-18T16:15:55.787Z","entities":[{"entity":"days","values":[{"value":"Friday","created":"2016-08-24T14:58:33.234Z","metadata":null,"synonyms":["F","Fri"]},{"value":"Monday","created":"2016-08-24T14:58:33.234Z","metadata":null,"synonyms":["M","Mon"]},{"value":"Saturday","created":"2016-08-24T14:58:33.234Z","metadata":null,"synonyms":["Sat"]},{"value":"Sunday","created":"2016-08-24T14:58:33.234Z","metadata":null,"synonyms":["Sun"]},{"value":"Thursday","created":"2016-08-24T14:58:33.234Z","metadata":null,"synonyms":["Th","Thurs"]},{"value":"Tuesday","created":"2016-08-24T14:58:33.234Z","metadata":null,"synonyms":["T","Tues"]},{"value":"Wednesday","created":"2016-08-24T14:58:33.234Z","metadata":null,"synonyms":["W","Weds"]}],"created":"2016-08-24T14:58:33.234Z","open_list":false,"description":null}],"language":"en","metadata":null,"description":"Integrate with Google Calendar","dialog_nodes":[{"go_to":null,"output":{"text":"How can I help?"},"parent":null,"context":null,"created":"2016-08-24T15:19:54.337Z","metadata":null,"conditions":"#greetings","description":null,"dialog_node":"node_4_1472051994101","previous_sibling":null},{"go_to":null,"output":{"text":"I'm sorry, I don't understand."},"parent":null,"context":null,"created":"2016-08-24T15:14:42.155Z","metadata":null,"conditions":"anything_else","description":null,"dialog_node":"node_2_1472051681967","previous_sibling":"node_3_1472051849506"},{"go_to":null,"output":{"text":"It's been a pleasure"},"parent":null,"context":null,"created":"2016-08-24T15:17:29.774Z","metadata":null,"conditions":"#goodbyes","description":null,"dialog_node":"node_3_1472051849506","previous_sibling":"node_4_1472051994101"}],"workspace_id":"96b041a3-95d8-4f99-80df-d17eea4d6167"} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#author niyatip | |
from __future__ import print_function | |
from apiclient import discovery | |
from slackclient import SlackClient | |
from watson_developer_cloud import ConversationV1 | |
import os | |
import time | |
import httplib2 | |
import json | |
import oauth2client | |
from oauth2client import client | |
from oauth2client import tools | |
import logging | |
logging.basicConfig() | |
import datetime | |
try: | |
import argparse | |
flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args() | |
except ImportError: | |
flags = None | |
# starterbot's ID as an environment variable | |
BOT_ID = os.environ.get("BOT_ID") | |
# constants | |
AT_BOT = "<@" + BOT_ID + ">" | |
# If modifying these scopes, delete your previously saved credentials | |
# at ~/.credentials/calendar-python-quickstart.json | |
SCOPES = 'https://www.googleapis.com/auth/calendar.readonly' | |
CLIENT_SECRET_FILE = 'client_secret.json' | |
APPLICATION_NAME = 'Google Calendar API Python Quickstart' | |
# instantiate Slack & Twilio clients | |
slack_client = SlackClient(os.environ.get('SLACK_BOT_TOKEN')) | |
#instantiate workspace and context for Conversation service | |
WORKSPACE_ID = <YOUR_WORKSPACE_ID> | |
USERNAME = <YOUR_USERNAME> | |
PASSWORD = <YOUR_PASSWORD> | |
context = {} | |
FLOW_MAP = {} | |
def get_credentials(user): | |
"""Gets valid user credentials from storage. | |
If nothing has been stored, or if the stored credentials are invalid, | |
the OAuth2 flow is completed to obtain the new credentials. | |
Returns: | |
Credentials, the obtained credential. | |
""" | |
home_dir = os.path.expanduser('~') | |
credential_dir = os.path.join(home_dir, '.credentials') | |
if not os.path.exists(credential_dir): | |
os.makedirs(credential_dir) | |
credential_path = os.path.join(credential_dir, | |
'calendar-python-quickstart-' + user + '.json') | |
store = oauth2client.file.Storage(credential_path) | |
credentials = store.get() | |
return credentials | |
def get_auth_url(user): | |
""" Creates a Flow Object from a clients_secrets.json which stores client parameters | |
like client ID, client secret and other JSON parameters. | |
Returns: | |
Authorization server URI. | |
""" | |
existing_flow = FLOW_MAP.get(user) | |
if existing_flow is None: | |
#urn:ietf:wg:oauth:2.0:oob to not redirect anywhere, but instead show the token on the auth_uri page | |
flow = client.flow_from_clientsecrets(filename = CLIENT_SECRET_FILE, scope = SCOPES, redirect_uri = "urn:ietf:wg:oauth:2.0:oob") | |
flow.user_agent = APPLICATION_NAME | |
auth_url = flow.step1_get_authorize_url() | |
print(auth_url) | |
FLOW_MAP[user] = flow | |
return auth_url | |
else: | |
return existing_flow.step1_get_authorize_url() | |
def set_auth_token(user, token): | |
""" Exchanges an authorization flow for a Credentials object. | |
Passes the token provided by authorization server redirection to this function. | |
Stores user credentials. | |
""" | |
flow = FLOW_MAP.get(user) | |
if flow is not None: | |
try: | |
credentials = flow.step2_exchange(token) | |
except oauth2client.client.FlowExchangeError: | |
return -1 | |
home_dir = os.path.expanduser('~') | |
credential_dir = os.path.join(home_dir, '.credentials') | |
if not os.path.exists(credential_dir): | |
os.makedirs(credential_dir) | |
credential_path = os.path.join(credential_dir, | |
'calendar-python-quickstart-' + user + '.json') | |
store = oauth2client.file.Storage(credential_path) | |
print("Storing credentials at " + credential_path) | |
store.put(credentials) | |
return 0 | |
else: | |
return None | |
def calendarUsage(user, intent): | |
"""Shows basic usage of the Google Calendar API. | |
Creates a Google Calendar API service object and outputs a list of the next | |
10 events on the user's calendar. | |
""" | |
responseFromCalendar = "" | |
credentials = get_credentials(user) | |
http = credentials.authorize(httplib2.Http()) | |
service = discovery.build('calendar', 'v3', http=http) | |
now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time | |
print('Getting the 10 upcoming events') | |
eventsResult = service.events().list( | |
calendarId='primary', timeMin=now, maxResults=10, singleEvents=True, | |
orderBy='startTime').execute() | |
events = eventsResult.get('items', []) | |
if intent == "schedule": | |
dataList = [] | |
if not events: | |
dataList = 'No upcoming events found.' | |
for event in events: | |
start = datetime.datetime.strptime(event['start']['dateTime'][:-6],"%Y-%m-%dT%H:%M:%S").strftime("%I:%M %p, %a %b %d") | |
attachmentObject = {} | |
attachmentObject['color'] = "#2952A3" | |
attachmentObject['title'] = event['summary'] | |
attachmentObject['text']= start | |
dataList.append(attachmentObject) | |
print(event['summary']) | |
return dataList | |
if intent == "free_time": | |
if not events: | |
response = "You are free all day." | |
else: | |
#grab the date of the calendar request | |
date, time = events[0]['start']['dateTime'].split('T') | |
#assume a starting time of 8 AM | |
checkTime = datetime.datetime.strptime(date+"T08:00:00","%Y-%m-%dT%H:%M:%S") | |
endTime = datetime.datetime.strptime(date+"T17:00:00","%Y-%m-%dT%H:%M:%S") | |
response = "You are free" | |
#loop over events, if they start before 5 PM check to see if there is space between the start of the event and the end of the previous | |
for event in events: | |
start = datetime.datetime.strptime(event['start']['dateTime'][:-6],"%Y-%m-%dT%H:%M:%S") | |
if start < endTime: | |
if start > checkTime: | |
response += " from " + checkTime.strftime("%I:%M %p") + " to " + start.strftime("%I:%M %p") + "," | |
checkTime = datetime.datetime.strptime(event['end']['dateTime'][:-6],"%Y-%m-%dT%H:%M:%S") | |
#if last event ends before 5 PM, set hard limit at 5. Otherwise, change sentence formatting appropriately | |
if checkTime < endTime: | |
response += " and from " + checkTime.strftime("%I:%M %p") + " to 05:00 PM" | |
else: | |
response = response[:-1] | |
r = response.rsplit(',',1) | |
if len(r)>1: | |
response = r[0] + ", and" + r[1] | |
if response == "You are fre": | |
response = "No free times" | |
return response | |
def handle_command(command, channel, user): | |
""" | |
Receives commands directed at the bot and determines if they | |
are valid commands. | |
If so, then acts on the commands. If not, | |
returns back what it needs for clarification. | |
""" | |
attachments = "" | |
response = "Not sure what you mean." | |
if command.startswith("token"): | |
store_status = set_auth_token(user, command[6:].strip()) | |
if store_status is None: | |
response = "You must first start the authorization process with @watson hello." | |
elif store_status == -1: | |
response = "The token you sent is wrong." | |
elif store_status == 0: | |
response = "Authentication successful!You can now communicate with Watson." | |
elif get_credentials(user) is None or command.startswith("reauth"): | |
response = "Visit the following URL in the browser: " + get_auth_url(user) \ | |
+ " \n Then send watson the authorization code like @watson token abc123." | |
else : | |
#Link to Watson Conversation as Auth is completed | |
# Replace with your own service credentials | |
conversation = ConversationV1( | |
username= USERNAME, | |
password= PASSWORD, | |
version='2016-09-20' | |
) | |
#Get response from Watson Conversation | |
responseFromWatson = conversation.message( | |
workspace_id=WORKSPACE_ID, | |
message_input={'text': command}, | |
context=context | |
) | |
#Get intent of the query | |
intent = responseFromWatson['intents'][0]['intent'] | |
#Render response on Bot | |
#Format Calendar output on the basis of intent of query | |
if intent == "schedule": | |
response = "Here are your upcoming events: " | |
attachments = calendarUsage(user, intent) | |
elif intent == "free_time": | |
response = calendarUsage(user, intent) | |
else: | |
response = responseFromWatson['output']['text'][0] | |
slack_client.api_call("chat.postMessage", as_user=True, channel=channel, text=response, | |
attachments=attachments) | |
def parse_slack_output(slack_rtm_output): | |
""" | |
The Slack Real Time Messaging API is an events firehose. | |
This parsing function returns None unless a message is | |
directed at the Bot, based on its ID. | |
""" | |
output_list = slack_rtm_output | |
if output_list and len(output_list) > 0: | |
for output in output_list: | |
if output and 'text' in output and AT_BOT in output['text']: | |
# return text after the @ mention, whitespace removed | |
return output['text'].split(AT_BOT)[1].strip(), \ | |
output['channel'], output['user'] | |
return None, None, None | |
if __name__ == "__main__": | |
READ_WEBSOCKET_DELAY = 1 # 1 second delay between reading from firehose | |
if slack_client.rtm_connect(): | |
print("StarterBot connected and running!") | |
while True: | |
command, channel, user = parse_slack_output(slack_client.rtm_read()) | |
if command and channel and user: | |
handle_command(command, channel, user) | |
time.sleep(READ_WEBSOCKET_DELAY) | |
else: | |
print("Connection failed. Invalid Slack token or bot ID?") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment