Spaces:
Build error
Build error
File size: 9,920 Bytes
0827183 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 |
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
from abc import ABC
from typing import List, Callable, Awaitable
from aiohttp.web_request import Request
from aiohttp.web_response import Response
from botframework.connector.auth import ClaimsIdentity
from botbuilder.core import conversation_reference_extension
from botbuilder.core import BotAdapter, TurnContext
from botbuilder.schema import (
Activity,
ResourceResponse,
ActivityTypes,
ConversationAccount,
ConversationReference,
)
from .activity_resourceresponse import ActivityResourceResponse
from .slack_client import SlackClient
from .slack_helper import SlackHelper
from .slack_adatper_options import SlackAdapterOptions
class SlackAdapter(BotAdapter, ABC):
"""
BotAdapter that can handle incoming Slack events. Incoming Slack events are deserialized to an Activity that is
dispatched through the middleware and bot pipeline.
"""
def __init__(
self,
client: SlackClient,
on_turn_error: Callable[[TurnContext, Exception], Awaitable] = None,
options: SlackAdapterOptions = None,
):
super().__init__(on_turn_error)
self.slack_client = client
self.slack_logged_in = False
self.options = options if options else SlackAdapterOptions()
async def send_activities(
self, context: TurnContext, activities: List[Activity]
) -> List[ResourceResponse]:
"""
Send a message from the bot to the messaging API.
:param context: A TurnContext representing the current incoming message and environment.
:type context: :class:`botbuilder.core.TurnContext`
:param activities: An array of outgoing activities to be sent back to the messaging API.
:type activities: :class:`typing.List[Activity]`
:return: An array of ResourceResponse objects containing the IDs that Slack assigned to the sent messages.
:rtype: :class:`typing.List[ResourceResponse]`
"""
if not context:
raise Exception("TurnContext is required")
if not activities:
raise Exception("List[Activity] is required")
responses = []
for activity in activities:
if activity.type == ActivityTypes.message:
message = SlackHelper.activity_to_slack(activity)
slack_response = await self.slack_client.post_message(message)
if slack_response and slack_response.status_code / 100 == 2:
resource_response = ActivityResourceResponse(
id=slack_response.data["ts"],
activity_id=slack_response.data["ts"],
conversation=ConversationAccount(
id=slack_response.data["channel"]
),
)
responses.append(resource_response)
return responses
async def update_activity(self, context: TurnContext, activity: Activity):
"""
Update a previous message with new content.
:param context: A TurnContext representing the current incoming message and environment.
:type context: :class:`botbuilder.core.TurnContext`
:param activity: The updated activity in the form '{id: `id of activity to update`, ...}'.
:type activity: :class:`botbuilder.schema.Activity`
:return: A resource response with the ID of the updated activity.
:rtype: :class:`botbuilder.schema.ResourceResponse`
"""
if not context:
raise Exception("TurnContext is required")
if not activity:
raise Exception("Activity is required")
if not activity.id:
raise Exception("Activity.id is required")
if not activity.conversation:
raise Exception("Activity.conversation is required")
message = SlackHelper.activity_to_slack(activity)
results = await self.slack_client.chat_update(
ts=message.ts,
channel=message.channel,
text=message.text,
)
if results.status_code / 100 != 2:
raise Exception(f"Error updating activity on slack: {results}")
return ResourceResponse(id=activity.id)
async def delete_activity(
self, context: TurnContext, reference: ConversationReference
):
"""
Delete a previous message.
:param context: A TurnContext representing the current incoming message and environment.
:type context: :class:`botbuilder.core.TurnContext`
:param reference: An object in the form "{activityId: `id of message to delete`,conversation: { id: `id of Slack
channel`}}".
:type reference: :class:`botbuilder.schema.ConversationReference`
"""
if not context:
raise Exception("TurnContext is required")
if not reference:
raise Exception("ConversationReference is required")
if not reference.channel_id:
raise Exception("ConversationReference.channel_id is required")
if not context.activity.timestamp:
raise Exception("Activity.timestamp is required")
await self.slack_client.chat_delete(
channel=reference.conversation.id, ts=reference.activity_id
)
async def continue_conversation(
self,
reference: ConversationReference,
callback: Callable,
bot_id: str = None, # pylint: disable=unused-argument
claims_identity: ClaimsIdentity = None,
audience: str = None, # pylint: disable=unused-argument
):
"""
Send a proactive message to a conversation.
.. remarks::
Most channels require a user to initiate a conversation with a bot before the bot can send activities to the
user.
:param reference: A reference to the conversation to continue.
:type reference: :class:`botbuilder.schema.ConversationReference`
:param callback: The method to call for the resulting bot turn.
:type callback: :class:`typing.Callable`
:param bot_id: Unused for this override.
:type bot_id: str
:param claims_identity: A ClaimsIdentity for the conversation.
:type claims_identity: :class:`botframework.connector.auth.ClaimsIdentity`
:param audience: Unused for this override.
:type audience: str
"""
if not reference:
raise Exception("ConversationReference is required")
if not callback:
raise Exception("callback is required")
if claims_identity:
request = conversation_reference_extension.get_continuation_activity(
reference
)
context = TurnContext(self, request)
context.turn_state[BotAdapter.BOT_IDENTITY_KEY] = claims_identity
context.turn_state[BotAdapter.BOT_CALLBACK_HANDLER_KEY] = callback
else:
request = TurnContext.apply_conversation_reference(
conversation_reference_extension.get_continuation_activity(reference),
reference,
)
context = TurnContext(self, request)
return await self.run_pipeline(context, callback)
async def process(self, req: Request, logic: Callable) -> Response:
"""
Accept an incoming webhook request and convert it into a TurnContext which can be processed by the bot's logic.
:param req: The aiohttp Request object.
:type req: :class:`aiohttp.web_request.Request`
:param logic: The method to call for the resulting bot turn.
:type logic: :class:`tying.Callable`
:return: The aiohttp Response.
:rtype: :class:`aiohttp.web_response.Response`
"""
if not req:
raise Exception("Request is required")
if not self.slack_logged_in:
await self.slack_client.login_with_slack()
self.slack_logged_in = True
body = await req.text()
if (
self.options.verify_incoming_requests
and not self.slack_client.verify_signature(req, body)
):
return SlackHelper.response(
req, 401, "Rejected due to mismatched header signature"
)
slack_body = SlackHelper.deserialize_body(req.content_type, body)
if slack_body.type == "url_verification":
return SlackHelper.response(req, 200, slack_body.challenge)
if (
not self.slack_client.options.slack_verification_token
and slack_body.token != self.slack_client.options.slack_verification_token
):
text = f"Rejected due to mismatched verificationToken:{body}"
return SlackHelper.response(req, 403, text)
if slack_body.payload:
# handle interactive_message callbacks and block_actions
activity = SlackHelper.payload_to_activity(slack_body.payload)
elif slack_body.type == "event_callback":
activity = await SlackHelper.event_to_activity(
slack_body.event, self.slack_client
)
elif slack_body.command:
activity = await SlackHelper.command_to_activity(
slack_body, self.slack_client
)
else:
return SlackHelper.response(
req, 200, f"Unknown Slack event type {slack_body.type}"
)
context = TurnContext(self, activity)
await self.run_pipeline(context, logic)
return SlackHelper.response(req, 200)
|