Skip to content

Instantly share code, notes, and snippets.

@zwalchuk
Last active June 18, 2020 10:04
  • Star 7 You must be signed in to star a gist
  • Fork 10 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save zwalchuk/73188198b386a996da25a6f091ff82b4 to your computer and use it in GitHub Desktop.
Watson Slackbot Google Calendar Integration
{"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"}
#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