async def begin_dialog(self, dialog_context: DialogContext, options: object = None): """ Method called when a new dialog has been pushed onto the stack and is being activated. :param dialog_context: The dialog context for the current turn of conversation. :param options: (Optional) additional argument(s) to pass to the dialog being started. """ dialog_args = self._validate_begin_dialog_args(options) await dialog_context.context.send_trace_activity( f"{SkillDialog.__name__}.BeginDialogAsync()", label=f"Using activity of type: {dialog_args.activity.type}", ) # Create deep clone of the original activity to avoid altering it before forwarding it. skill_activity: Activity = deepcopy(dialog_args.activity) # Apply conversation reference and common properties from incoming activity before sending. TurnContext.apply_conversation_reference( skill_activity, TurnContext.get_conversation_reference(dialog_context.context.activity), is_incoming=True, ) # Store delivery mode in dialog state for later use. dialog_context.active_dialog.state[ self._deliver_mode_state_key ] = dialog_args.activity.delivery_mode # Create the conversationId and store it in the dialog context state so we can use it later skill_conversation_id = await self._create_skill_conversation_id( dialog_context.context, dialog_context.context.activity ) dialog_context.active_dialog.state[ SkillDialog.SKILLCONVERSATIONIDSTATEKEY ] = skill_conversation_id # Send the activity to the skill. eoc_activity = await self._send_to_skill( dialog_context.context, skill_activity, skill_conversation_id ) if eoc_activity: return await dialog_context.end_dialog(eoc_activity.value) return self.end_of_turn
async def on_turn(self, turn_context: TurnContext, logic: Callable[[TurnContext], Awaitable]): """ Processes an incoming activity. :param turn_context: :param logic: :return: """ translate = await self._should_translate(turn_context) if translate and turn_context.activity.type == ActivityTypes.message: turn_context.activity.text = await self.translator.translate( turn_context.activity.text, TranslationSettings.default_language.value) async def aux_on_send(context: TurnContext, activities: List[Activity], next_send: Callable): user_language = await self.language_preference_accessor.get( context, TranslationSettings.default_language.value) should_translate = user_language != TranslationSettings.default_language.value # Translate messages sent to the user to user language if should_translate: for activity in activities: await self._translate_message_activity( activity, user_language) return await next_send() async def aux_on_update(context: TurnContext, activity: Activity, next_update: Callable): user_language = await self.language_preference_accessor.get( context, TranslationSettings.default_language.value) should_translate = user_language != TranslationSettings.default_language.value # Translate messages sent to the user to user language if should_translate and activity.type == ActivityTypes.message: await self._translate_message_activity(activity, user_language) return await next_update() turn_context.on_send_activities(aux_on_send) turn_context.on_update_activity(aux_on_update) await logic()
async def test_update_activity_should_apply_conversation_reference(self): activity_id = "activity ID" context = TurnContext(SimpleAdapter(), ACTIVITY) called = False async def update_handler(context, activity, next_handler_coroutine): nonlocal called called = True assert context is not None assert activity.id == activity_id assert activity.conversation.id == ACTIVITY.conversation.id await next_handler_coroutine() context.on_update_activity(update_handler) new_activity = MessageFactory.text("test text") new_activity.id = activity_id update_result = await context.update_activity(new_activity) assert called is True assert update_result.id == activity_id
async def test_send_message_to_teams(self): def create_conversation(): pass adapter = SimpleAdapterWithCreateConversation( call_create_conversation=create_conversation) turn_context = TurnContext(adapter, ACTIVITY) handler = TestTeamsActivityHandler() await handler.on_turn(turn_context)
def add_or_update_continuation_parameters(self, context: TurnContext): self.continuation_parameters_store[ context.activity.from_property.id ] = ContinuationParameters( claims_identity=context.turn_state.get(BotAdapter.BOT_IDENTITY_KEY), conversation_reference=TurnContext.get_conversation_reference( context.activity ), oauth_scope=context.turn_state.get(BotAdapter.BOT_OAUTH_SCOPE_KEY), )
def test_get_conversation_reference_should_return_valid_reference(self): reference = TurnContext.get_conversation_reference(ACTIVITY) assert reference.activity_id == ACTIVITY.id assert reference.user == ACTIVITY.from_property assert reference.bot == ACTIVITY.recipient assert reference.conversation == ACTIVITY.conversation assert reference.channel_id == ACTIVITY.channel_id assert reference.locale == ACTIVITY.locale assert reference.service_url == ACTIVITY.service_url
def create_empty_context(): b = TestAdapter() a = Activity( type = ActivityTypes.message, channel_id = "EmptyContext", conversation = ConversationAccount(id= 'test' ), from_property = ChannelAccount(id= '*****@*****.**') ) bc = TurnContext(b, a) return bc
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)
def _add_conversation_reference(self, activity: Activity): """ This populates the shared Dictionary that holds conversation references. In this sample, this dictionary is used to send a message to members when /api/notify is hit. :param activity: :return: """ conversation_reference = TurnContext.get_conversation_reference( activity) self.conversation_references[ conversation_reference.user.id] = conversation_reference
def on_post(self, req, resp): try: auth_header = req.headers['AUTHORIZATION'] except KeyError: resp.status = falcon.HTTP_403 return if req.content_length: activity = Activity().deserialize(json.load(req.stream)) sync_run(self.adapter.authenticate_request(activity, auth_header)) commandline = activity.text conversation = activity.conversation context = TurnContext(self.adapter, activity) commandline = commandline.replace("<at>{}</at>".format(self.name), "") sync_run( context.send_activity(self.bot.command_dispatcher(commandline)))
async def test_typing_activity(self): activity = Activity(type=ActivityTypes.typing) turn_context = TurnContext(SimpleAdapter(), activity) # Act bot = TestingTeamsActivityHandler() await bot.on_turn(turn_context) assert len(bot.record) == 1 assert bot.record[0] == "on_typing_activity"
def create_empty_context(): adapter = TestAdapter() activity = Activity( type=ActivityTypes.message, channel_id="EmptyContext", conversation=ConversationAccount(id="test"), from_property=ChannelAccount(id="*****@*****.**"), ) context = TurnContext(adapter, activity) return context
def _get_context(question: str, bot_adapter: BotAdapter) -> TurnContext: test_adapter = bot_adapter or TestAdapter() activity = Activity( type=ActivityTypes.message, text=question, conversation=ConversationAccount(), recipient=ChannelAccount(), from_property=ChannelAccount(), ) return TurnContext(test_adapter, activity)
async def logic(context: TurnContext): if test_case != FlowTestCase.root_bot_only: claims_identity = ClaimsIdentity( { AuthenticationConstants.VERSION_CLAIM: "2.0", AuthenticationConstants.AUDIENCE_CLAIM: self.skill_bot_id, AuthenticationConstants.AUTHORIZED_PARTY: self.parent_bot_id, }, True, ) context.turn_state[ BotAdapter.BOT_IDENTITY_KEY] = claims_identity if test_case == FlowTestCase.root_bot_consuming_skill: context.turn_state[ SkillHandler. SKILL_CONVERSATION_REFERENCE_KEY] = SkillConversationReference( None, AuthenticationConstants. TO_CHANNEL_FROM_BOT_OAUTH_SCOPE) if test_case == FlowTestCase.middle_skill: context.turn_state[ SkillHandler. SKILL_CONVERSATION_REFERENCE_KEY] = SkillConversationReference( None, self.parent_bot_id) async def capture_eoc(inner_context: TurnContext, activities: List[Activity], next): # pylint: disable=unused-argument for activity in activities: if activity.type == ActivityTypes.end_of_conversation: self.eoc_sent = activity break return await next() context.on_send_activities(capture_eoc) await DialogExtensions.run_dialog( dialog, context, convo_state.create_property("DialogState"))
async def test_should_add_and_call_save_all_changes_on_multiple_plugins( self): adapter = TestAdapter() context = TurnContext(adapter, Activity()) foo_state = BotStateMock({"foo": "bar"}) bar_state = BotStateMock({"bar": "foo"}) autosave_middleware = AutoSaveStateMiddleware([foo_state, bar_state]) await autosave_middleware.bot_state_set.save_all_changes(context) assert (foo_state.write_called or bar_state.write_called), "write not called for either plugin." assert foo_state.write_called, "write not called for 'foo_state' plugin." assert bar_state.write_called, "write not called for 'bar_state' plugin."
async def end_dialog(self, context: TurnContext, instance: DialogInstance, reason: DialogReason): # Send of of conversation to the skill if the dialog has been cancelled. if reason in (DialogReason.CancelCalled, DialogReason.ReplaceCalled): activity = Activity(type=ActivityTypes.end_of_conversation) # Apply conversation reference and common properties from incoming activity before sending. TurnContext.apply_conversation_reference( activity, TurnContext.get_conversation_reference(context.activity), is_incoming=True, ) activity.channel_data = context.activity.channel_data activity.additional_properties = context.activity.additional_properties # connection Name is not applicable for an EndDialog, as we don't expect as OAuthCard in response. skill_conversation_id = instance.state[ SkillDialog.SKILLCONVERSATIONIDSTATEKEY] await self._send_to_skill(context, activity, skill_conversation_id) await super().end_dialog(context, instance, reason)
async def test_on_installation_update(self): activity = Activity(type=ActivityTypes.installation_update) adapter = TestInvokeAdapter() turn_context = TurnContext(adapter, activity) # Act bot = TestingActivityHandler() await bot.on_turn(turn_context) assert len(bot.record) == 1 assert bot.record[0] == "on_installation_update"
async def process_command(self, context: TurnContext) -> Any: if context.activity.type == ActivityTypes.message and context.activity.text: original_text = context.activity.text TurnContext.remove_recipient_mention(context.activity) command = context.activity.text.strip().split(" ") if len(command ) > 1 and command[0] == InspectionMiddleware._COMMAND: if len(command) == 2 and command[1] == "open": await self._process_open_command(context) return True if len(command) == 3 and command[1] == "attach": await self.process_attach_command(context, command[2]) return True context.activity.text = original_text return False
async def test_on_end_of_conversation_activity(self): activity = Activity(type=ActivityTypes.end_of_conversation) adapter = TestInvokeAdapter() turn_context = TurnContext(adapter, activity) # Act bot = TestingActivityHandler() await bot.on_turn(turn_context) assert len(bot.record) == 1 assert bot.record[0] == "on_end_of_conversation_activity"
async def test_should_call_multiple_on_delete_activity_handlers_in_order( self): context = TurnContext(SimpleAdapter(), ACTIVITY) called_first = False called_second = False async def first_delete_handler(context, reference, next_handler_coroutine): nonlocal called_first, called_second assert called_first is False, 'called_first should not be True before first_delete_handler is called.' called_first = True assert called_second is False, 'Second on_delete_activity handler was called before first.' assert reference is not None assert context is not None assert reference.activity_id == '1234' await next_handler_coroutine() async def second_delete_handler(context, reference, next_handler_coroutine): nonlocal called_first, called_second assert called_first assert called_second is False, 'called_second was set to True before second handler was called.' called_second = True assert reference is not None assert context is not None assert reference.activity_id == '1234' await next_handler_coroutine() context.on_delete_activity(first_delete_handler) context.on_delete_activity(second_delete_handler) await context.delete_activity(ACTIVITY.id) assert called_first is True assert called_second is True
async def _end_skill_conversation( self, turn_context: TurnContext, error: Exception # pylint: disable=unused-argument ): if not self._skill_client or not self._skill_config: return try: # Inform the active skill that the conversation is ended so that it has a chance to clean up. # Note: the root bot manages the ActiveSkillPropertyName, which has a value while the root bot # has an active conversation with a skill. active_skill = await self._conversation_state.create_property( MainDialog.ACTIVE_SKILL_PROPERTY_NAME).get(turn_context) if active_skill: bot_id = self._config.APP_ID end_of_conversation = Activity( type=ActivityTypes.end_of_conversation) end_of_conversation.code = "RootSkillError" TurnContext.apply_conversation_reference( end_of_conversation, TurnContext.get_conversation_reference( turn_context.activity), True, ) await self._conversation_state.save_changes(turn_context, True) await self._skill_client.post_activity_to_skill( bot_id, active_skill, self._skill_config.SKILL_HOST_ENDPOINT, end_of_conversation, ) except Exception as exception: print( f"\n Exception caught on _end_skill_conversation : {exception}", file=sys.stderr, ) traceback.print_exc()
async def on_message_activity(self, turn_context: TurnContext): TurnContext.remove_recipient_mention(turn_context.activity) turn_context.activity.text = turn_context.activity.text.strip() if turn_context.activity.text == "MentionMe": await self._mention_activity(turn_context) return if turn_context.activity.text == "UpdateCardAction": await self._update_card_activity(turn_context) return if turn_context.activity.text == "MessageAllMembers": await self._message_all_members(turn_context) return if turn_context.activity.text == "Delete": await self._delete_card_activity(turn_context) return card = HeroCard( title="Welcome Card", text="Click the buttons to update this card", buttons=[ CardAction( type=ActionTypes.message_back, title="Update Card", text="UpdateCardAction", value={"count": 0}, ), CardAction( type=ActionTypes.message_back, title="Message all memebers", text="MessageAllMembers", ), ], ) await turn_context.send_activity( MessageFactory.attachment(CardFactory.hero_card(card))) return
async def continue_conversation( self, reference: ConversationReference, callback: Callable, bot_id: str = None, claims_identity: ClaimsIdentity = None, audience: str = None, ): """ Sends a proactive message to a conversation. Call this method to proactively send a message to a conversation. Most _channels require a user to initiate a conversation with a bot before the bot can send activities to the user. :param bot_id: Unused for this override. :param reference: A reference to the conversation to continue. :param callback: The method to call for the resulting bot turn. :param claims_identity: A ClaimsIdentity for the conversation. :param audience: Unused for this override. """ 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 test_pass_resource_responses_through(self): def validate_responses(activities: List[Activity]): pass # no need to do anything. adapter = SimpleAdapter(call_on_send=validate_responses) context = TurnContext(adapter, Activity()) activity_id = str(uuid.uuid1()) activity = TestMessage.message(activity_id) resource_response = await context.send_activity(activity) self.assertTrue(resource_response.id == activity_id, "Incorrect response Id returned")
async def test_should_reject_with_error_if_channel_id_is_missing(self): context = TurnContext(self.adapter, MISSING_CHANNEL_ID) async def next_middleware(): assert False, 'Should not have called next_middleware' try: await self.user_state.on_process_request(context, next_middleware) except AttributeError: pass else: raise AssertionError( 'Should not have completed and not raised AttributeError.')
async def end_dialog(self, context: TurnContext, instance: DialogInstance, reason: DialogReason): # Send of of conversation to the skill if the dialog has been cancelled. if reason in (DialogReason.CancelCalled, DialogReason.ReplaceCalled): await context.send_trace_activity( f"{SkillDialog.__name__}.end_dialog()", label=f"ActivityType: {context.activity.type}", ) activity = Activity(type=ActivityTypes.end_of_conversation) # Apply conversation reference and common properties from incoming activity before sending. TurnContext.apply_conversation_reference( activity, TurnContext.get_conversation_reference(context.activity), is_incoming=True, ) activity.channel_data = context.activity.channel_data activity.additional_properties = context.activity.additional_properties await self._send_to_skill(context, activity) await super().end_dialog(context, instance, reason)
async def on_message_activity(self, turn_context: TurnContext): await turn_context.send_activity("parent: before child") activity = MessageFactory.text("parent to child") TurnContext.apply_conversation_reference( activity, TurnContext.get_conversation_reference(turn_context.activity)) activity.delivery_mode = DeliveryModes.expect_replies activities = await self.client.post_buffered_activity( None, "toBotId", "http://localhost:3979/api/messages", "http://tempuri.org/whatever", str(uuid.uuid4()), activity, ) if activities: await turn_context.send_activities(activities) await turn_context.send_activity("parent: after child")
async def test_invoke(self): activity = Activity(type=ActivityTypes.invoke, name="some.random.invoke") adapter = TestInvokeAdapter() turn_context = TurnContext(adapter, activity) # Act bot = TestingActivityHandler() await bot.on_turn(turn_context) assert len(bot.record) == 1 assert bot.record[0] == "on_invoke_activity" assert adapter.activity.value.status == int(HTTPStatus.OK)
async def test_invoke_should_not_match(self): activity = Activity(type=ActivityTypes.invoke, name="should.not.match") adapter = TestInvokeAdapter() turn_context = TurnContext(adapter, activity) # Act bot = TestingActivityHandler() await bot.on_turn(turn_context) assert len(bot.record) == 1 assert bot.record[0] == "on_invoke_activity" assert adapter.activity.value.status == int(HTTPStatus.NOT_IMPLEMENTED)
async def test_should_reject_with_error_if_from_property_is_missing(self): context = TurnContext(self.adapter, MISSING_FROM_PROPERTY) async def next_middleware(): assert False, 'Should not have called next_middleware' try: await self.middleware.on_process_request(context, next_middleware) except AttributeError: pass else: raise AssertionError( 'Should not have completed and not raised AttributeError.')