8000 Added botbuilder-adapters-slack (#688) · TheCompuGuru/botbuilder-python@e6b6701 · GitHub
[go: up one dir, main page]

Skip to content

Commit e6b6701

Browse files
authored
Added botbuilder-adapters-slack (microsoft#688)
* Added botbuilder-adapters-slack * black fixes * setup.py black fixes * pylint fixes
1 parent 8696c83 commit e6b6701

File tree

15 files changed

+1296
-0
lines changed

15 files changed

+1296
-0
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
2+
=================================
3+
BotBuilder-Adapters SDK for Python
4+
=================================
5+
6+
.. image:: https://fuselabs.visualstudio.com/SDK_v4/_apis/build/status/Python/SDK_v4-Python-CI?branchName=master
7+
:target: https://fuselabs.visualstudio.com/SDK_v4/_apis/build/status/Python/SDK_v4-Python-CI
8+
:align: right
9+
:alt: Azure DevOps status for master branch
10+
.. image:: https://badge.fury.io/py/botbuilder-dialogs.svg
11+
:target: https://badge.fury.io/py/botbuilder-dialogs
12+
:alt: Latest PyPI package version
13+
14+
A dialog stack based conversation manager for Microsoft BotBuilder.
15+
16+
How to Install
17+
==============
18+
19+
.. code-block:: python
20+
21+
pip install botbuilder-dialogs
22+
23+
24+
Documentation/Wiki
25+
==================
26+
27+
You can find more information on the botbuilder-python project by visiting our `Wiki`_.
28+
29+
Requirements
30+
============
31+
32+
* `Python >= 3.7.0`_
33+
34+
35+
Source Code
36+
===========
37+
The latest developer version is available in a github repository:
38+
https://github.com/Microsoft/botbuilder-python/
39+
40+
41+
Contributing
42+
============
43+
44+
This project welcomes contributions and suggestions. Most contributions require you to agree to a
45+
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
46+
the rights to use your contribution. For details, visit https://cla.microsoft.com.
47+
48+
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
49+
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
50+
provided by the bot. You will only need to do this once across all repos using our CLA.
51+
52+
This project has adopted the `Microsoft Open Source Code of Conduct`_.
53+
For more information see the `Code of Conduct FAQ`_ or
54+
contact `opencode@microsoft.com`_ with any additional questions or comments.
55+
56+
Reporting Security Issues
57+
=========================
58+
59+
Security issues and bugs should be reported privately, via email, to the Microsoft Security
60+
Response Center (MSRC) at `secure@microsoft.com`_. You should
61+
receive a response within 24 hours. If for some reason you do not, please follow up via
62+
email to ensure we received your original message. Further information, including the
63+
`MSRC PGP`_ key, can be found in
64+
the `Security TechCenter`_.
65+
66+
License
67+
=======
68+
69+
Copyright (c) Microsoft Corporation. All rights reserved.
70+
71+
Licensed under the MIT_ License.
72+
73+
.. _Wiki: https://github.com/Microsoft/botbuilder-python/wiki
74+
.. _Python >= 3.7.0: https://www.python.org/downloads/
75+
.. _MIT: https://github.com/Microsoft/vscode/blob/master/LICENSE.txt
76+
.. _Microsoft Open Source Code of Conduct: https://opensource.microsoft.com/codeofconduct/
77+
.. _Code of Conduct FAQ: https://opensource.microsoft.com/codeofconduct/faq/
78+
.. _opencode@microsoft.com: mailto:opencode@microsoft.com
79+
.. _secure@microsoft.com: mailto:secure@microsoft.com
80+
.. _MSRC PGP: https://technet.microsoft.com/en-us/security/dn606155
81+
.. _Security TechCenter: https://github.com/Microsoft/vscode/blob/master/LICENSE.txt
82+
83+
.. <https://technet.microsoft.com/en-us/security/default>`_
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# coding=utf-8
2+
# --------------------------------------------------------------------------
3+
# Copyright (c) Microsoft Corporation. All rights reserved.
4+
# Licensed under the MIT License. See License.txt in the project root for
5+
# license information.
6+
# --------------------------------------------------------------------------
7+
8+
from .about import __version__
9+
from .slack_options import SlackAdapterOptions
10+
from .slack_client import SlackClient
11+
from .slack_adapter import SlackAdapter
12+
from .slack_payload import SlackPayload
13+
from .slack_message import SlackMessage
14+
from .slack_event import SlackEvent
15+
from .activity_resourceresponse import ActivityResourceResponse
16+
from .slack_request_body import SlackRequestBody
17+
from .slack_helper import SlackHelper
18+
19+
__all__ = [
20+
"__version__",
21+
"SlackAdapterOptions",
22+
"SlackClient",
23+
"SlackAdapter",
24+
"SlackPayload",
25+
"SlackMessage",
26+
"SlackEvent",
27+
"ActivityResourceResponse",
28+
"SlackRequestBody",
29+
"SlackHelper",
30+
]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import os
5+
6+
__title__ = "botbuilder-adapters-slack"
7+
__version__ = (
8+
os.environ["packageVersion"] if "packageVersion" in os.environ else "4.7.1"
9+
)
10+
__uri__ = "https://www.github.com/Microsoft/botbuilder-python"
11+
__author__ = "Microsoft"
12+
__description__ = "Microsoft Bot Framework Bot Builder"
13+
__summary__ = "Microsoft Bot Framework Bot Builder SDK for Python."
14+
__license__ = "MIT"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from botbuilder.schema import ResourceResponse, ConversationAccount
5+
6+
7+
class ActivityResourceResponse(ResourceResponse):
8+
def __init__(self, activity_id: str, conversation: ConversationAccount, **kwargs):
9+
super().__init__(**kwargs)
10+
self.activity_id = activity_id
11+
self.conversation = conversation
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from abc import ABC
5+
from typing import List, Callable, Awaitable
6+
7+
from aiohttp.web_request import Request
8+
from aiohttp.web_response import Response
9+
from botframework.connector.auth import ClaimsIdentity
10+
from botbuilder.core import conversation_reference_extension
11+
from botbuilder.core import BotAdapter, TurnContext
12+
from botbuilder.schema import (
13+
Activity,
14+
ResourceResponse,
15+
ActivityTypes,
16+
ConversationAccount,
17+
ConversationReference,
18+
)
19+
F987 20+
from .activity_resourceresponse import ActivityResourceResponse
21+
from .slack_client import SlackClient
22+
from .slack_helper import SlackHelper
23+
24+
25+
class SlackAdapter(BotAdapter, ABC):
26+
"""
27+
BotAdapter that can handle incoming slack events. Incoming slack events are deserialized to an Activity
28+
that is dispatch through the middleware and bot pipeline.
29+
"""
30+
31+
def __init__(
32+
self,
33+
client: SlackClient,
34+
on_turn_error: Callable[[TurnContext, Exception], Awaitable] = None,
35+
):
36+
super().__init__(on_turn_error)
37+
self.slack_client = client
38+
self.slack_logged_in = False
39+
40+
async def send_activities(
41+
self, context: TurnContext, activities: List[Activity]
42+
) -> List[ResourceResponse]:
43+
"""
44+
Standard BotBuilder adapter method to send a message from the bot to the messaging API.
45+
46+
:param context: A TurnContext representing the current incoming message and environment.
47+
:param activities: An array of outgoing activities to be sent back to the messaging API.
48+
:return: An array of ResourceResponse objects containing the IDs that Slack assigned to the sent messages.
49+
"""
50+
51+
if not context:
52+
raise Exception("TurnContext is required")
53+
if not activities:
54+
raise Exception("List[Activity] is required")
55+
56+
responses = []
57+
58+
for activity in activities:
59+
if activity.type == ActivityTypes.message:
60+
message = SlackHelper.activity_to_slack(activity)
61+
62+
slack_response = await self.slack_client.post_message_to_slack(message)
63+
64+
if slack_response and slack_response.status_code / 100 == 2:
65+
resource_response = ActivityResourceResponse(
66+
id=slack_response.data["ts"],
67+
activity_id=slack_response.data["ts"],
68+
conversation=ConversationAccount(
69+
id=slack_response.data["channel"]
70+
),
71+
)
72+
73+
responses.append(resource_response)
74+
75+
return responses
76+
77+
async def update_activity(self, context: TurnContext, activity: Activity):
78+
"""
79+
Standard BotBuilder adapter method to update a previous message with new content.
80+
81+
:param context: A TurnContext representing the current incoming message and environment.
82+
:param activity: The updated activity in the form '{id: `id of activity to update`, ...}'.
83+
:return: A resource response with the Id of the updated activity.
84+
"""
85+
86+
if not context:
87+
raise Exception("TurnContext is required")
88+
if not activity:
89+
raise Exception("Activity is required")
90+
if not activity.id:
91+
raise Exception("Activity.id is required")
92+
if not activity.conversation:
93+
raise Exception("Activity.conversation is required")
94+
95+
message = SlackHelper.activity_to_slack(activity)
96+
results = await self.slack_client.update(
97+
timestamp=message.ts, channel_id=message.channel, text=message.text,
98+
)
99+
100+
if results.status_code / 100 != 2:
101+
raise Exception(f"Error updating activity on slack: {results}")
102+
103+
return ResourceResponse(id=activity.id)
104+
105+
async def delete_activity(
106+
self, context: TurnContext, reference: ConversationReference
107+
):
108+
"""
109+
Standard BotBuilder adapter method to delete a previous message.
110+
111+
:param context: A TurnContext representing the current incoming message and environment.
112+
:param reference: An object in the form "{activityId: `id of message to delete`,
113+
conversation: { id: `id of slack channel`}}".
114+
"""
115+
116+
if not context:
117+
raise Exception("TurnContext is required")
118+
if not reference:
119+
raise Exception("ConversationReference is required")
120+
if not reference.channel_id:
121+
raise Exception("ConversationReference.channel_id is required")
122+
if not context.activity.timestamp:
123+
raise Exception("Activity.timestamp is required")
124+
125+
await self.slack_client.delete_message(
126+
channel_id=reference.channel_id, timestamp=context.activity.timestamp
127+
)
128+
129+
async def continue_conversation(
130+
self,
131+
reference: ConversationReference,
132+
callback: Callable,
133+
bot_id: str = None, # pylint: disable=unused-argument
134+
claims_identity: ClaimsIdentity = None, # pylint: disable=unused-argument
135+
):
136+
"""
137+
Sends a proactive message to a conversation. Call this method to proactively send a message to a conversation.
138+
Most _channels require a user to initiate a conversation with a bot before the bot can send activities
139+
to the user.
140+
:param bot_id: The application ID of the bot. This parameter is ignored in
141+
single tenant the Adpters (Console, Test, etc) but is critical to the BotFrameworkAdapter
142+
which is multi-tenant aware. </param>
143+
:param reference: A reference to the conversation to continue.</param>
144+
:param callback: The method to call for the resulting bot turn.</param>
145+
:param claims_identity:
146+
"""
147+
148+
if not reference:
149+
raise Exception("ConversationReference is required")
150+
if not callback:
151+
raise Exception("callback is required")
152+
153+
request = TurnContext.apply_conversation_reference(
154+
conversation_reference_extension.get_continuation_activity(reference),
155+
reference,
156+
)
157+
context = TurnContext(self, request)
158+
159+
return await self.run_pipeline(context, callback)
160+
161+
async def process(self, req: Request, logic: Callable) -> Response:
162+
"""
163+
Accept an incoming webhook request and convert it into a TurnContext which can be processed by the bot's logic.
164+
165+
:param req: The aoihttp Request object
166+
:param logic: The method to call for the resulting bot turn.</param>
167+
:return: The aoihttp Response
168+
"""
169+
if not req:
170+
raise Exception("Request is required")
171+
172+
if not self.slack_logged_in:
173+
await self.slack_client.login_with_slack()
174+
self.slack_logged_in = True
175+
176+
body = await req.text()
177+
slack_body = SlackHelper.deserialize_body(req.content_type, body)
178+
179+
if slack_body.type == "url_verification":
180+
return SlackHelper.response(req, 200, slack_body.challenge)
181+
182+
if not self.slack_client.verify_signature(req, body):
183+
text = "Rejected due to mismatched header signature"
184+
return SlackHelper.response(req, 401, text)
185+
186+
if (
187+
not self.slack_client.options.slack_verification_token
188+
and slack_body.token != self.slack_client.options.slack_verification_token
189+
):
190+
text = f"Rejected due to mismatched verificationToken:{body}"
191+
return SlackHelper.response(req, 403, text)
192+
193+
if slack_body.payload:
194+
# handle interactive_message callbacks and block_actions
195+
activity = SlackHelper.payload_to_activity(slack_body.payload)
196+
elif slack_body.type == "event_callback":
197+
activity = await SlackHelper.event_to_activity(
198+
slack_body.event, self.slack_client
199+
)
200+
elif slack_body.command:
201+
activity = await SlackHelper.command_to_activity(
202+
slack_body, self.slack_client
203+
)
204+
else:
205+
raise Exception(f"Unknown Slack event type {slack_body.type}")
206+
207+
context = TurnContext(self, activity)
208+
await self.run_pipeline(context, logic)
209+
210+
return SlackHelper.response(req, 200)

0 commit comments

Comments
 (0)
0