Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 68 additions & 50 deletions ciscosparkbot/Spark.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ class SparkBot(Flask):
def __init__(self, spark_bot_name, spark_bot_token=None,
spark_api_url=None,
spark_bot_email=None, spark_bot_url=None,
default_action="/help", debug=False):
default_action="/help", debug=False, wh_resource="messages",
wh_event="created"):
"""
Initialize a new SparkBot

Expand Down Expand Up @@ -54,6 +55,8 @@ def __init__(self, spark_bot_name, spark_bot_token=None,
self.spark_bot_email = spark_bot_email
self.spark_bot_url = spark_bot_url
self.default_action = default_action
self.wh_resource = wh_resource
self.wh_event = wh_event

# Create Spark API Object for interacting with Spark
if (spark_api_url):
Expand Down Expand Up @@ -107,16 +110,20 @@ def spark_setup(self):
# Setup the Spark Connection
globals()["spark"] = CiscoSparkAPI(access_token=self.spark_bot_token)
globals()["webhook"] = self.setup_webhook(self.spark_bot_name,
self.spark_bot_url)
self.spark_bot_url,
self.wh_resource,
self.wh_event)
sys.stderr.write("Configuring Webhook. \n")
sys.stderr.write("Webhook ID: " + globals()["webhook"].id + "\n")

# noinspection PyMethodMayBeStatic
def setup_webhook(self, name, targeturl):
def setup_webhook(self, name, targeturl, wh_resource, wh_event):
"""
Setup Spark WebHook to send incoming messages to this bot.
:param name: Name of the WebHook
:param targeturl: Target URL for WebHook
:param wh_resource: WebHook 'resource'; messages, memberships, etc.
:param wh_event: WebHook 'event'; created, or all
:return: WebHook
"""
# Get a list of current webhooks
Expand All @@ -136,16 +143,19 @@ def setup_webhook(self, name, targeturl):
sys.stderr.write("Creating new webhook.\n")
wh = self.spark.webhooks.create(name=name,
targetUrl=targeturl,
resource="messages",
event="created")
resource=wh_resource,
event=wh_event)

# if we have an existing webhook update it
# if we have an existing webhook, delete and recreate
# (can't update resource/event)
else:
# Need try block because if there are NO webhooks it throws error
try:
wh = self.spark.webhooks.update(webhookId=wh.id,
name=name,
targetUrl=targeturl)
wh = self.spark.webhooks.delete(webhookId=wh.id)
wh = self.spark.webhooks.create(name=name,
targetUrl=targeturl,
resource=wh_resource,
event=wh_event)
# https://github.com/CiscoDevNet/ciscosparkapi/blob/master/ciscosparkapi/api/webhooks.py#L237
except Exception as e:
msg = "Encountered an error updating webhook: {}"
Expand Down Expand Up @@ -217,55 +227,63 @@ def process_incoming_message(self):
and determine reply.
:return:
"""
reply = None

# Get the webhook data
post_data = request.json

# Determine the Spark Room to send reply to
room_id = post_data["data"]["roomId"]

# Get the details about the message that was sent.
message_id = post_data["data"]["id"]
message = self.spark.messages.get(message_id)
if self.DEBUG:
sys.stderr.write("Message content:" + "\n")
sys.stderr.write(str(message) + "\n")

# First make sure not processing a message from the bots
# Needed to avoid the bot talking to itself
# We check using IDs instead of emails since the email
# of the bot could change while the bot is running
# for example from bot@sparkbot.io to bot@webex.bot
if message.personId in self.spark.people.me().id:
rsc = post_data["resource"]
if rsc != "messages":
if rsc in self.commands.keys():
reply = self.commands[rsc]["callback"](self, post_data)
else:
return ""
elif post_data["resource"] == "messages":
# Get the details about the message that was sent.
message_id = post_data["data"]["id"]
message = self.spark.messages.get(message_id)
if self.DEBUG:
sys.stderr.write("Ignoring message from our self" + "\n")
return ""

# Log details on message
sys.stderr.write("Message from: " + message.personEmail + "\n")

# Find the command that was sent, if any
command = ""
for c in self.commands.items():
if message.text.find(c[0]) != -1:
command = c[0]
sys.stderr.write("Found command: " + command + "\n")
# If a command was found, stop looking for others
break

# Build the reply to the user
reply = ""

# Take action based on command
# If no command found, send the default_action
if command in [""] and self.default_action:
# noinspection PyCallingNonCallable
reply = self.commands[self.default_action]["callback"](message)
elif command in self.commands.keys():
# noinspection PyCallingNonCallable
reply = self.commands[command]["callback"](message)
else:
pass
sys.stderr.write("Message content:" + "\n")
sys.stderr.write(str(message) + "\n")

# First make sure not processing a message from the bots
# Needed to avoid the bot talking to itself
# We check using IDs instead of emails since the email
# of the bot could change while the bot is running
# for example from bot@sparkbot.io to bot@webex.bot
if message.personId in self.spark.people.me().id:
if self.DEBUG:
sys.stderr.write("Ignoring message from our self" + "\n")
return ""

# Log details on message
sys.stderr.write("Message from: " + message.personEmail + "\n")

# Find the command that was sent, if any
command = ""
for c in self.commands.items():
if message.text.find(c[0]) != -1:
command = c[0]
sys.stderr.write("Found command: " + command + "\n")
# If a command was found, stop looking for others
break

# Build the reply to the user
reply = ""

# Take action based on command
# If no command found, send the default_action
if command in [""] and self.default_action:
# noinspection PyCallingNonCallable
reply = self.commands[self.default_action]["callback"](message)
elif command in self.commands.keys():
# noinspection PyCallingNonCallable
reply = self.commands[command]["callback"](message)
else:
pass

# allow command handlers to craft their own Spark message
if reply and isinstance(reply, Response):
Expand Down
58 changes: 58 additions & 0 deletions tests/spark_mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,64 @@ def incoming_msg(cls):
}
return json.dumps(data)

@classmethod
def incoming_membership_fail(cls):
data = {
'id': 'newwebhook',
'name': 'My Awesome Webhook',
'targetUrl': 'https://example.com/mywebhook',
'resource': 'memberships',
'event': 'created',
'orgId': 'OTZhYmMyYWEtM2RjYy0xMWU1LWExNTItZmUzNDgxOWNkYzlh',
'createdBy': 'fdsafdsf',
'appId': 'asdfasdfsadf',
'ownedBy': 'creator',
'status': 'active',
'created': '2018-09-13T19:35:51.248Z',
'actorId': 'OTZhYmMyYWEtM2RjYy0xMWU1LWExNTItZmUzNDgxOWNkYzlh',
'data': {
'id': 'incoming_membership_id',
'roomId': 'some_room_id',
'personId': 'some_person_id',
'personEmail': 'matt@example.com',
'personDisplayName': 'Matt',
'personOrgId': 'OTZhYmMyYWEtM2RjYy0xMWU1LWExNTItZmUzNDgxOWNk',
'isModerator': False,
'isMonitor': False,
'created': '2018-09-13T19:35:58.803Z'
}
}
return json.dumps(data)

@classmethod
def incoming_membership_pass(cls):
data = {
'id': 'newwebhook',
'name': 'My Awesome Webhook',
'targetUrl': 'https://example.com/mywebhook',
'resource': 'memberships',
'event': 'created',
'orgId': 'OTZhYmMyYWEtM2RjYy0xMWU1LWExNTItZmUzNDgxOWNkYzlh',
'createdBy': 'fdsafdsf',
'appId': 'asdfasdfsadf',
'ownedBy': 'creator',
'status': 'active',
'created': '2018-09-13T19:35:51.248Z',
'actorId': 'OTZhYmMyYWEtM2RjYy0xMWU1LWExNTItZmUzNDgxOWNkYzlh',
'data': {
'id': 'incoming_membership_id',
'roomId': 'some_room_id',
'personId': 'some_person_id',
'personEmail': 'matt@cisco.com',
'personDisplayName': 'Matt',
'personOrgId': 'OTZhYmMyYWEtM2RjYy0xMWU1LWExNTItZmUzNDgxOWNk',
'isModerator': False,
'isMonitor': False,
'created': '2018-09-13T19:35:58.803Z'
}
}
return json.dumps(data)

@classmethod
def get_message_help(cls):
data = {
Expand Down
85 changes: 85 additions & 0 deletions tests/sparkbot.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,91 @@ def test_process_incoming_message_send_help(self, m):
print(resp.data)
self.assertIn(b'I understand the following commands', resp.data)

@requests_mock.mock()
def test_process_incoming_membership_check_sender_fail(self, m):
m.get('https://api.ciscospark.com/v1/webhooks',
json=MockSparkAPI.list_webhooks())
m.post('https://api.ciscospark.com/v1/webhooks',
json=MockSparkAPI.create_webhook())
m.post('//api.ciscospark.com/v1/messages', json={})
bot_email = "test@test.com"
spark_token = "somefaketoken"
bot_url = "http://fakebot.com"
bot_app_name = "testbot"
# Create a new bot
bot = SparkBot(bot_app_name,
spark_bot_token=spark_token,
spark_bot_url=bot_url,
spark_bot_email=bot_email,
debug=True,
wh_resource="memberships",
wh_event="all")

# Add new command
bot.add_command('memberships',
'*',
self.check_membership)
bot.testing = True
self.app = bot.test_client()

resp = self.app.post('/',
data=MockSparkAPI.incoming_membership_fail(),
content_type="application/json")
self.assertEqual(resp.status_code, 200)
print(resp.data)
self.assertIn(b"failed", resp.data)

@requests_mock.mock()
def test_process_incoming_membership_check_sender_pass(self, m):
m.get('https://api.ciscospark.com/v1/webhooks',
json=MockSparkAPI.list_webhooks())
m.post('https://api.ciscospark.com/v1/webhooks',
json=MockSparkAPI.create_webhook())
m.post('//api.ciscospark.com/v1/messages', json={})
bot_email = "test@test.com"
spark_token = "somefaketoken"
bot_url = "http://fakebot.com"
bot_app_name = "testbot"
# Create a new bot
bot = SparkBot(bot_app_name,
spark_bot_token=spark_token,
spark_bot_url=bot_url,
spark_bot_email=bot_email,
debug=True,
wh_resource="memberships",
wh_event="all")

# Add new command
bot.add_command('memberships',
'*',
self.check_membership)
bot.testing = True
self.app = bot.test_client()

resp = self.app.post('/',
data=MockSparkAPI.incoming_membership_pass(),
content_type="application/json")
self.assertEqual(resp.status_code, 200)
print(resp.data)
self.assertIn(b"success", resp.data)

def check_membership(self, ob, incoming_msg):
"""
Sample function to do some action.
:param incoming_msg: The incoming message object from Spark
:param ob: Spark API object
:return: A text or markdown based reply
"""

whitelist = ["cisco.com"]
pemail = incoming_msg["data"]["personEmail"]
pdom = pemail.split("@")[1]

if pdom in whitelist:
return "success"
else:
return "failed"

@requests_mock.mock()
def test_process_incoming_message_default_command(self, m):
m.get('//api.ciscospark.com/v1/people/me', json=MockSparkAPI.me())
Expand Down