def _create_dialog_skill_bot_activity( self, selected_option: str, turn_context: TurnContext) -> Activity: """ Helper method to create the activity to be sent to the DialogSkillBot using selected type and values. """ selected_option = selected_option.lower() # Note: in a real bot, the dialogArgs will be created dynamically based on the conversation # and what each action requires; here we hardcode the values to make things simpler. # Just forward the message activity to the skill with whatever the user said. if selected_option == self._skill_action_message.lower(): # Note message activities also support input parameters but we are not using them in this example. return turn_context.activity activity = None # Send an event activity to the skill with "BookFlight" in the name. if selected_option == self._skill_action_book_flight.lower(): activity = Activity(type=ActivityTypes.event) activity.name = self._skill_action_book_flight # Send an event activity to the skill with "BookFlight" in the name and some testing values. if (selected_option == self._skill_action_book_flight_with_input_parameters.lower()): activity = Activity(type=ActivityTypes.event) activity.name = self._skill_action_book_flight activity.value = {"origin": "New York", "destination": "Seattle"} # Send an event activity to the skill with "GetWeather" in the name and some testing values. if selected_option == self._skill_action_get_weather.lower(): activity = Activity(type=ActivityTypes.event) activity.name = self._skill_action_get_weather activity.value = {"latitude": 47.614891, "longitude": -122.195801} return activity if not activity: raise Exception( f"Unable to create dialogArgs for {selected_option}.") # We are manually creating the activity to send to the skill; ensure we add the ChannelData and Properties # from the original activity so the skill gets them. # Note: this is not necessary if we are just forwarding the current activity from context. activity.channel_data = turn_context.activity.channel_data activity.additional_properties = turn_context.activity.additional_properties return activity
async def test_should_handle_invoke_activities(self): activity_sent = None from_bot_id_sent = None to_bot_id_sent = None to_url_sent = None async def capture( from_bot_id: str, to_bot_id: str, to_url: str, service_url: str, # pylint: disable=unused-argument conversation_id: str, # pylint: disable=unused-argument activity: Activity, ): nonlocal from_bot_id_sent, to_bot_id_sent, to_url_sent, activity_sent from_bot_id_sent = from_bot_id to_bot_id_sent = to_bot_id to_url_sent = to_url activity_sent = activity mock_skill_client = self._create_mock_skill_client(capture) conversation_state = ConversationState(MemoryStorage()) dialog_options = SkillDialogTests.create_skill_dialog_options( conversation_state, mock_skill_client) sut = SkillDialog(dialog_options, "dialog_id") activity_to_send = Activity( type=ActivityTypes.invoke, name=str(uuid.uuid4()), ) client = DialogTestClient( "test", sut, BeginSkillDialogOptions(activity=activity_to_send), conversation_state=conversation_state, ) # Send something to the dialog to start it await client.send_activity(MessageFactory.text("irrelevant")) # Assert results and data sent to the SkillClient for fist turn assert dialog_options.bot_id == from_bot_id_sent assert dialog_options.skill.app_id == to_bot_id_sent assert dialog_options.skill.skill_endpoint == to_url_sent assert activity_to_send.text == activity_sent.text assert DialogTurnStatus.Waiting == client.dialog_turn_result.status # Send a second message to continue the dialog await client.send_activity(MessageFactory.text("Second message")) # Assert results for second turn assert activity_sent.text == "Second message" assert DialogTurnStatus.Waiting == client.dialog_turn_result.status # Send EndOfConversation to the dialog await client.send_activity( Activity(type=ActivityTypes.end_of_conversation)) # Assert we are done. assert DialogTurnStatus.Complete == client.dialog_turn_result.status
from botbuilder.schema import ( Activity, ActivityTypes, ChannelAccount, ConversationAccount, Entity, Mention, ResourceResponse, ) from botbuilder.core import BotAdapter, MessageFactory, TurnContext ACTIVITY = Activity( id="1234", type="message", text="test", from_property=ChannelAccount(id="user", name="User Name"), recipient=ChannelAccount(id="bot", name="Bot Name"), conversation=ConversationAccount(id="convo", name="Convo Name"), channel_id="UnitTest", service_url="https://example.org", ) class SimpleAdapter(BotAdapter): async def send_activities(self, context, activities) -> List[ResourceResponse]: responses = [] assert context is not None assert activities is not None assert isinstance(activities, list) assert activities for (idx, activity) in enumerate(activities): # pylint: disable=unused-variable assert isinstance(activity, Activity)
ClaimsIdentity, AuthenticationConstants, AppCredentials, CredentialProvider, ) REFERENCE = ConversationReference( activity_id="1234", channel_id="test", service_url="https://example.org/channel", user=ChannelAccount(id="user", name="User Name"), bot=ChannelAccount(id="bot", name="Bot Name"), conversation=ConversationAccount(id="convo1"), ) TEST_ACTIVITY = Activity(text="test", type=ActivityTypes.message) INCOMING_MESSAGE = TurnContext.apply_conversation_reference( copy(TEST_ACTIVITY), REFERENCE, True ) OUTGOING_MESSAGE = TurnContext.apply_conversation_reference( copy(TEST_ACTIVITY), REFERENCE ) INCOMING_INVOKE = TurnContext.apply_conversation_reference( Activity(type=ActivityTypes.invoke), REFERENCE, True ) class AdapterUnderTest(BotFrameworkAdapter): def __init__(self, settings=None): super().__init__(settings)
await step_context.context.send_activity("step1") return Dialog.end_of_turn async def Waterfall2_Step2(step_context: WaterfallStepContext) -> DialogTurnResult: await step_context.context.send_activity("step2") return Dialog.end_of_turn async def Waterfall2_Step3(step_context: WaterfallStepContext) -> DialogTurnResult: await step_context.context.send_activity("step3") return Dialog.end_of_turn self.add_step(Waterfall2_Step1) self.add_step(Waterfall2_Step2) self.add_step(Waterfall2_Step3) begin_message = Activity() begin_message.text = 'begin' begin_message.type = 'message' class WaterfallTests(aiounittest.AsyncTestCase): def test_waterfall_none_name(self): self.assertRaises(TypeError, (lambda:WaterfallDialog(None))) def test_watterfall_add_none_step(self): waterfall = WaterfallDialog("test") self.assertRaises(TypeError, (lambda:waterfall.add_step(None))) async def test_waterfall_with_set_instead_of_array(self): self.assertRaises(TypeError, lambda:WaterfallDialog('a', { 1, 2 }))
async def _fill_out_user_profile(self, flow: ConversationFlow, profile: UserProfile, turn_context: TurnContext): user_input = turn_context.activity.text.strip().lower() #begin a conversation and ask for a significant word if flow.last_question_asked == Question.NONE: await turn_context.send_activity( MessageFactory.text( "Let's get started. Please type a significant word !")) flow.last_question_asked = Question.WORD #validate word and ask for the response elif flow.last_question_asked == Question.WORD: validate_result = self._validate_word(user_input) if not validate_result.is_valid: await turn_context.send_activity( MessageFactory.text(validate_result.message)) else: profile.word = validate_result.value await turn_context.send_activity( MessageFactory.text( f"You choose: {turn_context.activity.text.strip()} \n\nPlease Wait." )) await turn_context.send_activities([ Activity(type=ActivityTypes.typing), Activity(type="delay", value=2000) ]) global intrus global liste #liste=closest_words(profile.word) most = model.most_similar(profile.word) liste = [] for i in range(5): liste.append(most[:5][i][0]) sim = 1 while sim > 0.5: intrus = vocab[random.randint(1, len(vocab) - 1)] if intrus in liste: continue meaning = dic.meaning(intrus) if not meaning: #wordnik api continue #sim=distance(profile.word,intrus) sim = model.similarity(profile.word, intrus) liste.insert(random.randrange(len(liste)), intrus) card = HeroCard( text= "Here is the list of the words.\n\nPlease choose the intruder one!", buttons=[ CardAction(type=ActionTypes.im_back, title=liste[0], value=liste[0]), CardAction(type=ActionTypes.im_back, title=liste[1], value=liste[1]), CardAction(type=ActionTypes.im_back, title=liste[2], value=liste[2]), CardAction(type=ActionTypes.im_back, title=liste[3], value=liste[3]), CardAction(type=ActionTypes.im_back, title=liste[4], value=liste[4]), CardAction(type=ActionTypes.im_back, title=liste[5], value=liste[5]), ], ) reply = MessageFactory.attachment(CardFactory.hero_card(card)) await turn_context.send_activity(reply) flow.last_question_asked = Question.RESPONSE # validate response and wrap it up elif flow.last_question_asked == Question.RESPONSE: if user_input == "exit": await turn_context.send_activity( MessageFactory.text( "Type anything to run the Intruder Bot again.")) flow.last_question_asked = Question.NONE else: validate_result = self._validate_result(user_input) if not validate_result.is_valid: card = HeroCard( text=validate_result.message, buttons=[ CardAction(type=ActionTypes.im_back, title="exit", value="exit"), ], ) reply = MessageFactory.attachment( CardFactory.hero_card(card)) await turn_context.send_activity(reply) profile.score -= 1 await turn_context.send_activity( MessageFactory.text(f"Your Score is: {profile.score}")) suggested = MessageFactory.text("Chase the Intruder!") suggested.suggested_actions = SuggestedActions(actions=[ CardAction(title=liste[0], type=ActionTypes.im_back, value=liste[0]), CardAction(title=liste[1], type=ActionTypes.im_back, value=liste[1]), CardAction(title=liste[2], type=ActionTypes.im_back, value=liste[2]), CardAction(title=liste[3], type=ActionTypes.im_back, value=liste[3]), CardAction(title=liste[4], type=ActionTypes.im_back, value=liste[4]), CardAction(title=liste[5], type=ActionTypes.im_back, value=liste[5]), ]) await turn_context.send_activity(suggested) else: profile.response = validate_result.value profile.score += 1 await turn_context.send_activity( MessageFactory.text( f"You have responded correctly.\n\nYour score is: {profile.score}\n\nThanks for completing the test." )) await turn_context.send_activity( MessageFactory.text( "Type anything to run the Intruder Bot again.")) flow.last_question_asked = Question.NONE
def append_choices( self, prompt: Activity, channel_id: str, choices: List[Choice], style: ListStyle, options: ChoiceFactoryOptions = None, ) -> Activity: """ Composes an output activity containing a set of choices. :param prompt: The prompt to append the user's choice to :type prompt: :param channel_id: Id of the channel the prompt is being sent to :type channel_id: str :param: choices: List of choices to append :type choices: :class:`List` :param: style: Configured style for the list of choices :type style: :class:`ListStyle` :param: options: Optional formatting options to use when presenting the choices :type style: :class:`ChoiceFactoryOptions` :return: A :class:Task representing the asynchronous operation :rtype: :class:Task .. remarks:: If the task is successful, the result contains the updated activity. When overridden in a derived class, appends choices to the activity when the user is prompted for input. This is an helper function to compose an output activity containing a set of choices. """ # Get base prompt text (if any) text = prompt.text if prompt is not None and prompt.text else "" # Create temporary msg # TODO: fix once ChoiceFactory complete def inline() -> Activity: return ChoiceFactory.inline(choices, text, None, options) def list_style() -> Activity: return ChoiceFactory.list_style(choices, text, None, options) def suggested_action() -> Activity: return ChoiceFactory.suggested_action(choices, text) def hero_card() -> Activity: return ChoiceFactory.hero_card(choices, text) def list_style_none() -> Activity: activity = Activity(type=ActivityTypes.message) activity.text = text return activity def default() -> Activity: return ChoiceFactory.for_channel(channel_id, choices, text, None, options) # Maps to values in ListStyle Enum switcher = { 0: list_style_none, 1: default, 2: inline, 3: list_style, 4: suggested_action, 5: hero_card, } msg = switcher.get(int(style.value), default)() # Update prompt with text, actions and attachments if prompt: # clone the prompt the set in the options (note ActivityEx has Properties so this is the safest mechanism) prompt = copy.copy(prompt) prompt.text = msg.text if (msg.suggested_actions is not None and msg.suggested_actions.actions is not None and msg.suggested_actions.actions): prompt.suggested_actions = msg.suggested_actions if msg.attachments: if prompt.attachments: prompt.attachments.extend(msg.attachments) else: prompt.attachments = msg.attachments return prompt # TODO: Update to InputHints.ExpectingInput; msg.input_hint = None return msg
from botbuilder.dialogs.choices import Choice, ChoiceFactoryOptions, ListStyle from botbuilder.dialogs.prompts import ( ChoicePrompt, PromptCultureModel, PromptOptions, PromptValidatorContext, ) from botbuilder.schema import Activity, ActivityTypes _color_choices: List[Choice] = [ Choice(value="red"), Choice(value="green"), Choice(value="blue"), ] _answer_message: Activity = Activity(text="red", type=ActivityTypes.message) _invalid_message: Activity = Activity(text="purple", type=ActivityTypes.message) class ChoicePromptTest(aiounittest.AsyncTestCase): def test_choice_prompt_with_empty_id_should_fail(self): empty_id = "" with self.assertRaises(TypeError): ChoicePrompt(empty_id) def test_choice_prompt_with_none_id_should_fail(self): none_id = None with self.assertRaises(TypeError):
async def exec_test_for_locale(valid_locale: str, locale_variations: List): # Hold the correct answer from when a valid locale is used expected_answer = None def inspector(activity: Activity, description: str): nonlocal expected_answer assert not description if valid_locale == test_locale: expected_answer = activity.text else: # Ensure we're actually testing a variation. assert activity.locale != valid_locale assert activity.text == expected_answer return True async def exec_test(turn_context: TurnContext): dialog_context = await dialogs.create_context(turn_context) results: DialogTurnResult = await dialog_context.continue_dialog( ) if results.status == DialogTurnStatus.Empty: options = PromptOptions( prompt=Activity(type=ActivityTypes.message, text="Please choose a color."), choices=_color_choices, ) await dialog_context.prompt("prompt", options) elif results.status == DialogTurnStatus.Complete: selected_choice = results.result await turn_context.send_activity(selected_choice.value) await convo_state.save_changes(turn_context) async def validator(prompt: PromptValidatorContext) -> bool: assert prompt if not prompt.recognized.succeeded: await prompt.context.send_activity("Bad input.") return prompt.recognized.succeeded test_locale = None for test_locale in locale_variations: adapter = TestAdapter(exec_test) convo_state = ConversationState(MemoryStorage()) dialog_state = convo_state.create_property("dialogState") dialogs = DialogSet(dialog_state) choice_prompt = ChoicePrompt("prompt", validator) dialogs.add(choice_prompt) step1 = await adapter.send( Activity(type=ActivityTypes.message, text="Hello", locale=test_locale)) await step1.assert_reply(inspector)
async def run_dialog(dialog: Dialog, turn_context: TurnContext, accessor: StatePropertyAccessor): """ Creates a dialog stack and starts a dialog, pushing it onto the stack. """ dialog_set = DialogSet(accessor) dialog_set.add(dialog) dialog_context = await dialog_set.create_context(turn_context) # Handle EoC and Reprompt event from a parent bot (can be root bot to skill or skill to skill) if DialogExtensions.__is_from_parent_to_skill(turn_context): # Handle remote cancellation request from parent. if turn_context.activity.type == ActivityTypes.end_of_conversation: if not dialog_context.stack: # No dialogs to cancel, just return. return remote_cancel_text = "Skill was canceled through an EndOfConversation activity from the parent." await turn_context.send_trace_activity( f"Extension {Dialog.__name__}.run_dialog", label=remote_cancel_text, ) # Send cancellation message to the dialog to ensure all the parents are canceled # in the right order. await dialog_context.cancel_all_dialogs() return # Handle a reprompt event sent from the parent. if (turn_context.activity.type == ActivityTypes.event and turn_context.activity.name == DialogEvents.reprompt_dialog): if not dialog_context.stack: # No dialogs to reprompt, just return. return await dialog_context.reprompt_dialog() return # Continue or start the dialog. result = await dialog_context.continue_dialog() if result.status == DialogTurnStatus.Empty: result = await dialog_context.begin_dialog(dialog.id) # Skills should send EoC when the dialog completes. if (result.status == DialogTurnStatus.Complete or result.status == DialogTurnStatus.Cancelled): if DialogExtensions.__send_eoc_to_parent(turn_context): end_message_text = ( f"Dialog {dialog.id} has **completed**. Sending EndOfConversation." ) await turn_context.send_trace_activity( f"Extension {Dialog.__name__}.run_dialog", label=end_message_text, value=result.result, ) activity = Activity( type=ActivityTypes.end_of_conversation, value=result.result, locale=turn_context.activity.locale, ) await turn_context.send_activity(activity)
async def _fill_out_user_profile(self, flow: ConversationFlow, profile: UserProfile, turn_context: TurnContext): # Begins flow process user_input = turn_context.activity.text.strip() # Ask for date; starts conversation flow if flow.last_question_asked == Question.NONE: await turn_context.send_activity( MessageFactory.text( "Let's get started. What date and time would you like to book your movie for?" )) flow.last_question_asked = Question.DATE # Validate date; ask for movie selection elif flow.last_question_asked == Question.DATE: # This is where date must be bound to profile.date validate_result = self._validate_date(user_input) if not validate_result.is_valid: await turn_context.send_activity( MessageFactory.text(validate_result.message)) else: profile.date = validate_result.value await turn_context.send_activity( MessageFactory.text( f"Great! We have a good selection of movies showing at {profile.date}" )) await turn_context.send_activity( MessageFactory.text( "Which movie would you like to watch? Please type the name of the movie as your reply." )) # movies attachment message = Activity(type=ActivityTypes.message) message.text = "Moana, Once Upon a Time in Hollywood, Ready Player One, Sicario, The Girl With the Dragon Tattoo." message.attachments = [self._get_inline_attachment()] await turn_context.send_activity(message) flow.last_question_asked = Question.MOVIE # Validate movie; ask for seat reservations elif flow.last_question_asked == Question.MOVIE: ## This is where movie must be bound to profile.movie validate_result = self._validate_movie(user_input) if not validate_result.is_valid: await turn_context.send_activity( MessageFactory.text(validate_result.message)) else: profile.movie = validate_result.value await turn_context.send_activity( MessageFactory.text( f"Sounds good! {profile.movie} is a great pick!")) await turn_context.send_activity( MessageFactory.text("How many seats are you reserving?")) flow.last_question_asked = Question.SEATS # Validate seats; ask about row preferences elif flow.last_question_asked == Question.SEATS: validate_result = self._validate_seats(user_input) if not validate_result.is_valid: await turn_context.send_activity( MessageFactory.text(validate_result.message)) else: profile.seats = validate_result.value await turn_context.send_activity( MessageFactory.text( f"You are booking {profile.seats} seats.")) await turn_context.send_activity( MessageFactory.text( "What is your row preference? There are 50 rows in our theater." )) flow.last_question_asked = Question.PREFERENCE # Validate preferences; ask about email elif flow.last_question_asked == Question.PREFERENCE: validate_result = self._validate_preference(user_input) if not validate_result.is_valid: await turn_context.send_activity( MessageFactory.text(validate_result.message)) else: profile.preference = validate_result.value await turn_context.send_activity( MessageFactory.text( f"Your row preference has been set as {profile.preference}" )) await turn_context.send_activity( MessageFactory.text( "Please enter your email for booking confirmation.")) flow.last_question_asked = Question.EMAIL # Validate email, confirm by displaying all info, wrap up w/ NONE elif flow.last_question_asked == Question.EMAIL: validate_result = self._validate_email(user_input) if not validate_result.is_valid: await turn_context.send_activity( MessageFactory.text(validate_result.message)) else: profile.email = validate_result.value await turn_context.send_activity( MessageFactory.text( f"You have now completed the booking process.")) await turn_context.send_activity( MessageFactory.text( f"Booking for {profile.email}: {profile.movie} on {profile.date}" )) await turn_context.send_activity( MessageFactory.text( f"You are reserving {profile.seats} seats in row {profile.preference}" )) await turn_context.send_activity( MessageFactory.text("Type anything to run the bot again.")) flow.last_question_asked = Question.NONE #End of flow, able to restart again
async def handle_skill_on_turn( self, dialog_context: DialogContext ) -> DialogTurnResult: # the bot is running as a skill. turn_context = dialog_context.context # Process remote cancellation if ( turn_context.activity.type == ActivityTypes.end_of_conversation and dialog_context.active_dialog is not None and self.is_from_parent_to_skill(turn_context) ): # Handle remote cancellation request from parent. active_dialog_context = self.get_active_dialog_context(dialog_context) remote_cancel_text = "Skill was canceled through an EndOfConversation activity from the parent." await turn_context.send_trace_activity( f"{self.__class__.__name__}.on_turn_async()", label=f"{remote_cancel_text}", ) # Send cancellation message to the top dialog in the stack to ensure all the parents are canceled in the # right order. return await active_dialog_context.cancel_all_dialogs(True) # Handle reprompt # Process a reprompt event sent from the parent. if ( turn_context.activity.type == ActivityTypes.event and turn_context.activity.name == DialogEvents.reprompt_dialog ): if not dialog_context.active_dialog: return DialogTurnResult(DialogTurnStatus.Empty) await dialog_context.reprompt_dialog() return DialogTurnResult(DialogTurnStatus.Waiting) # Continue execution # - This will apply any queued up interruptions and execute the current/next step(s). turn_result = await dialog_context.continue_dialog() if turn_result.status == DialogTurnStatus.Empty: # restart root dialog start_message_text = f"Starting {self._root_dialog_id}." await turn_context.send_trace_activity( f"{self.__class__.__name__}.handle_skill_on_turn_async()", label=f"{start_message_text}", ) turn_result = await dialog_context.begin_dialog(self._root_dialog_id) await DialogManager.send_state_snapshot_trace(dialog_context, "Skill State") if self.should_send_end_of_conversation_to_parent(turn_context, turn_result): end_message_text = f"Dialog {self._root_dialog_id} has **completed**. Sending EndOfConversation." await turn_context.send_trace_activity( f"{self.__class__.__name__}.handle_skill_on_turn_async()", label=f"{end_message_text}", value=turn_result.result, ) # Send End of conversation at the end. activity = Activity( type=ActivityTypes.end_of_conversation, value=turn_result.result, locale=turn_context.activity.locale, code=EndOfConversationCodes.completed_successfully if turn_result.status == DialogTurnStatus.Complete else EndOfConversationCodes.user_cancelled, ) await turn_context.send_activity(activity) return turn_result
# Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. import aiounittest from botbuilder.core import TurnContext from botbuilder.core.adapters import TestAdapter from botbuilder.schema import Activity, ConversationReference, ChannelAccount from botframework.connector.auth import MicrosoftAppCredentials RECEIVED_MESSAGE = Activity(type="message", text="received") UPDATED_ACTIVITY = Activity(type="message", text="update") DELETED_ACTIVITY_REFERENCE = ConversationReference(activity_id="1234") class TestTestAdapter(aiounittest.AsyncTestCase): async def test_should_call_bog_logic_when_receive_activity_is_called(self): async def logic(context: TurnContext): assert context assert context.activity assert context.activity.type == "message" assert context.activity.text == "test" assert context.activity.id assert context.activity.from_property assert context.activity.recipient assert context.activity.conversation assert context.activity.channel_id assert context.activity.service_url adapter = TestAdapter(logic) await adapter.receive_activity("test")
async def create_conversation( self, reference: ConversationReference, logic: Callable[[TurnContext], Awaitable] = None, conversation_parameters: ConversationParameters = None, channel_id: str = None, service_url: str = None, credentials: AppCredentials = None, ): """ Starts a new conversation with a user. Used to direct message to a member of a group. :param reference: The conversation reference that contains the tenant :type reference: :class:`botbuilder.schema.ConversationReference` :param logic: The logic to use for the creation of the conversation :type logic: :class:`typing.Callable` :param conversation_parameters: The information to use to create the conversation :type conversation_parameters: :param channel_id: The ID for the channel. :type channel_id: :class:`typing.str` :param service_url: The channel's service URL endpoint. :type service_url: :class:`typing.str` :param credentials: The application credentials for the bot. :type credentials: :class:`botframework.connector.auth.AppCredentials` :raises: It raises a generic exception error. :return: A task representing the work queued to execute. .. remarks:: To start a conversation, your bot must know its account information and the user's account information on that channel. Most channels only support initiating a direct message (non-group) conversation. The adapter attempts to create a new conversation on the channel, and then sends a conversation update activity through its middleware pipeline to the the callback method. If the conversation is established with the specified users, the ID of the activity will contain the ID of the new conversation. """ try: if not service_url: service_url = reference.service_url if not service_url: raise TypeError( "BotFrameworkAdapter.create_conversation(): service_url or reference.service_url is required." ) if not channel_id: channel_id = reference.channel_id if not channel_id: raise TypeError( "BotFrameworkAdapter.create_conversation(): channel_id or reference.channel_id is required." ) parameters = (conversation_parameters if conversation_parameters else ConversationParameters(bot=reference.bot, members=[reference.user], is_group=False)) # Mix in the tenant ID if specified. This is required for MS Teams. if reference.conversation and reference.conversation.tenant_id: # Putting tenant_id in channel_data is a temporary while we wait for the Teams API to be updated parameters.channel_data = { "tenant": { "id": reference.conversation.tenant_id } } # Permanent solution is to put tenant_id in parameters.tenant_id parameters.tenant_id = reference.conversation.tenant_id # This is different from C# where credentials are required in the method call. # Doing this for compatibility. app_credentials = (credentials if credentials else await self.__get_app_credentials( self.settings.app_id, self.__get_botframework_oauth_scope())) # Create conversation client = self._get_or_create_connector_client( service_url, app_credentials) resource_response = await client.conversations.create_conversation( parameters) event_activity = Activity( type=ActivityTypes.event, name="CreateConversation", channel_id=channel_id, service_url=service_url, id=resource_response.activity_id if resource_response.activity_id else str(uuid.uuid4()), conversation=ConversationAccount( id=resource_response.id, tenant_id=parameters.tenant_id, ), channel_data=parameters.channel_data, recipient=parameters.bot, ) context = self._create_context(event_activity) context.turn_state[BotAdapter.BOT_CONNECTOR_CLIENT_KEY] = client claims_identity = ClaimsIdentity( claims={ AuthenticationConstants.AUDIENCE_CLAIM: app_credentials.microsoft_app_id, AuthenticationConstants.APP_ID_CLAIM: app_credentials.microsoft_app_id, AuthenticationConstants.SERVICE_URL_CLAIM: service_url, }, is_authenticated=True, ) context.turn_state[BotAdapter.BOT_IDENTITY_KEY] = claims_identity return await self.run_pipeline(context, logic) except Exception as error: raise error
from unittest.mock import Mock from botbuilder.core import BotFrameworkAdapter, BotFrameworkAdapterSettings, TurnContext from botbuilder.schema import Activity, ActivityTypes, ConversationAccount, ConversationReference, ChannelAccount from botframework.connector import ConnectorClient from botframework.connector.auth import ClaimsIdentity reference = ConversationReference( activity_id='1234', channel_id='test', service_url='https://example.org/channel', user=ChannelAccount(id='user', name='User Name'), bot=ChannelAccount(id='bot', name='Bot Name'), conversation=ConversationAccount(id='convo1')) test_activity = Activity(text='test', type=ActivityTypes.message) incoming_message = TurnContext.apply_conversation_reference( copy(test_activity), reference, True) outgoing_message = TurnContext.apply_conversation_reference( copy(test_activity), reference) incoming_invoke = TurnContext.apply_conversation_reference( Activity(type=ActivityTypes.invoke), reference, True) class AdapterUnderTest(BotFrameworkAdapter): def __init__(self, settings=None): super().__init__(settings) self.tester = aiounittest.AsyncTestCase() self.fail_auth = False self.fail_operation = False
async def test_should_recognize_and_use_custom_locale_dict(self, ): async def exec_test(turn_context: TurnContext): dialog_context = await dialogs.create_context(turn_context) results: DialogTurnResult = await dialog_context.continue_dialog() if results.status == DialogTurnStatus.Empty: options = PromptOptions( prompt=Activity(type=ActivityTypes.message, text="Please choose a color."), choices=_color_choices, ) await dialog_context.prompt("prompt", options) elif results.status == DialogTurnStatus.Complete: selected_choice = results.result await turn_context.send_activity(selected_choice.value) await convo_state.save_changes(turn_context) async def validator(prompt: PromptValidatorContext) -> bool: assert prompt if not prompt.recognized.succeeded: await prompt.context.send_activity("Bad input.") return prompt.recognized.succeeded adapter = TestAdapter(exec_test) convo_state = ConversationState(MemoryStorage()) dialog_state = convo_state.create_property("dialogState") dialogs = DialogSet(dialog_state) culture = PromptCultureModel( locale="custom-locale", no_in_language="customNo", yes_in_language="customYes", separator="customSeparator", inline_or="customInlineOr", inline_or_more="customInlineOrMore", ) custom_dict = { culture.locale: ChoiceFactoryOptions( inline_or=culture.inline_or, inline_or_more=culture.inline_or_more, inline_separator=culture.separator, include_numbers=True, ) } choice_prompt = ChoicePrompt("prompt", validator, choice_defaults=custom_dict) dialogs.add(choice_prompt) step1 = await adapter.send( Activity(type=ActivityTypes.message, text="Hello", locale=culture.locale)) await step1.assert_reply( "Please choose a color. (1) redcustomSeparator(2) greencustomInlineOrMore(3) blue" )
async def _send_to_skill( self, context: TurnContext, activity: Activity, skill_conversation_id: str ) -> Activity: if activity.type == ActivityTypes.invoke: # Force ExpectReplies for invoke activities so we can get the replies right away and send # them back to the channel if needed. This makes sure that the dialog will receive the Invoke # response from the skill and any other activities sent, including EoC. activity.delivery_mode = DeliveryModes.expect_replies # Always save state before forwarding # (the dialog stack won't get updated with the skillDialog and things won't work if you don't) await self.dialog_options.conversation_state.save_changes(context, True) skill_info = self.dialog_options.skill response = await self.dialog_options.skill_client.post_activity( self.dialog_options.bot_id, skill_info.app_id, skill_info.skill_endpoint, self.dialog_options.skill_host_endpoint, skill_conversation_id, activity, ) # Inspect the skill response status if not 200 <= response.status <= 299: raise Exception( f'Error invoking the skill id: "{skill_info.id}" at "{skill_info.skill_endpoint}"' f" (status is {response.status}). \r\n {response.body}" ) eoc_activity: Activity = None if activity.delivery_mode == DeliveryModes.expect_replies and response.body: # Process replies in the response.Body. response.body: List[Activity] response.body = ExpectedReplies().deserialize(response.body).activities # Track sent invoke responses, so more than one is not sent. sent_invoke_response = False for from_skill_activity in response.body: if from_skill_activity.type == ActivityTypes.end_of_conversation: # Capture the EndOfConversation activity if it was sent from skill eoc_activity = from_skill_activity # The conversation has ended, so cleanup the conversation id await self.dialog_options.conversation_id_factory.delete_conversation_reference( skill_conversation_id ) elif not sent_invoke_response and await self._intercept_oauth_cards( context, from_skill_activity, self.dialog_options.connection_name ): # Token exchange succeeded, so no oauthcard needs to be shown to the user sent_invoke_response = True else: # If an invoke response has already been sent we should ignore future invoke responses as this # represents a bug in the skill. if from_skill_activity.type == ActivityTypes.invoke_response: if sent_invoke_response: continue sent_invoke_response = True # Send the response back to the channel. await context.send_activity(from_skill_activity) return eoc_activity
# Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. import aiounittest from botbuilder.core import TurnContext, MemoryStorage, UserState from botbuilder.core.adapters import TestAdapter from botbuilder.schema import Activity, ChannelAccount RECEIVED_MESSAGE = Activity( type="message", text="received", channel_id="test", from_property=ChannelAccount(id="user"), ) MISSING_CHANNEL_ID = Activity(type="message", text="received", from_property=ChannelAccount(id="user")) MISSING_FROM_PROPERTY = Activity(type="message", text="received", channel_id="test") class TestUserState(aiounittest.AsyncTestCase): storage = MemoryStorage() adapter = TestAdapter() context = TurnContext(adapter, RECEIVED_MESSAGE) user_state = UserState(storage) async def test_should_load_and_save_state_from_storage(self): await self.user_state.load(self.context)
async def on_message_activity(self, turn_context: TurnContext): # Get the state properties from the turn context. profile = await self.profile_accessor.get(turn_context, UserProfile) flow = await self.flow_accessor.get(turn_context, ConversationFlow) text = turn_context.activity.text.lower() if text == "get started" or text == "/start": return await turn_context.send_activities([ Activity(type=ActivityTypes.typing), Activity(type="delay", value=1000), Activity( type=ActivityTypes.message, text= "Welcome ! Thank you for subscribing.\n\nThis bot will present you a list of words similar to the word that you will type, and you should find the intruder one.\n\nYou can use the built-in dictionary at anytime to search the meanings of a word.\n\nIf you want any help just type 'help me' .\n\nIf you ever want to test your lexical skills, just type a significant word and Let's Start !" ) ]) elif text == "help me" or text == "/help": return await turn_context.send_activity( MessageFactory.text( "Intruder Bot\n\nUsage: \n\n _Def word_ You can use the built-in dictionary at anytime to search the meanings of a word.\n\nType anything to start the test, then when the conversation starts, you will be asked to type a word, and we will send you 5 most similar words to that word and an intruder one that you have to detect in order to pass the test. \n\n Your score will be incremented on each correct response, and will be decremented on each incorrect response.\n\n Let's Play !" )) elif "def" == text.split()[0]: if len(text.split()) < 2: return await turn_context.send_activity( MessageFactory.text("Please type a word to define !")) elif len(text.split()) < 3: word = text.split()[1] meaning = dic.meaning(word) if meaning: df = list(meaning.items()) for it in df: await turn_context.send_activity( MessageFactory.text(f"**{it[0]}** : \n\n* " + " \n\n* ".join(it[1]))) else: #wordnik_api return await turn_context.send_activity( MessageFactory.text("Please type a significant word !") ) else: return await turn_context.send_activity( MessageFactory.text("Please type just one word !")) elif text == "word of the day" or text == "/word_day": today = datetime.today().strftime('%Y-%m-%d') getwordofday = f"curl -X GET --header 'Accept: application/json' 'https://api.wordnik.com/v4/words.json/wordOfTheDay?date={today}&api_key={api_key}'" result = os.popen(getwordofday).read() res = json.loads(result) await turn_context.send_activity( MessageFactory.text( f"The word of today is : **{res['word']}**")) for it in res['definitions']: await turn_context.send_activity( MessageFactory.text(f"**{it['partOfSpeech']}** : \n\n* " + it['text'])) examples = [(example['title'], example['text']) for example in res['examples']] for it in range(len(examples)): await turn_context.send_activity( MessageFactory.text( f"_Example{it+1}_ \n\n**{examples[it][0]}** : \n\n* " + examples[it][1])) if res['note']: await turn_context.send_activity( MessageFactory.text("**Note**" + " : \n\n* " + res['note']) ) else: await self._fill_out_user_profile(flow, profile, turn_context) # Save changes to UserState and ConversationState await self.conversation_state.save_changes(turn_context) await self.user_state.save_changes(turn_context)
async def run_dialog( dialog: Dialog, turn_context: TurnContext, accessor: StatePropertyAccessor ): dialog_set = DialogSet(accessor) dialog_set.add(dialog) dialog_context = await dialog_set.create_context(turn_context) claims = turn_context.turn_state.get(BotAdapter.BOT_IDENTITY_KEY) if isinstance(claims, ClaimsIdentity) and SkillValidation.is_skill_claim( claims.claims ): # The bot is running as a skill. if ( turn_context.activity.type == ActivityTypes.end_of_conversation and dialog_context.stack and DialogExtensions.__is_eoc_coming_from_parent(turn_context) ): remote_cancel_text = "Skill was canceled through an EndOfConversation activity from the parent." await turn_context.send_trace_activity( f"Extension {Dialog.__name__}.run_dialog", label=remote_cancel_text, ) await dialog_context.cancel_all_dialogs() else: # Process a reprompt event sent from the parent. if ( turn_context.activity.type == ActivityTypes.event and turn_context.activity.name == DialogEvents.reprompt_dialog and dialog_context.stack ): await dialog_context.reprompt_dialog() return # Run the Dialog with the new message Activity and capture the results # so we can send end of conversation if needed. result = await dialog_context.continue_dialog() if result.status == DialogTurnStatus.Empty: start_message_text = f"Starting {dialog.id}" await turn_context.send_trace_activity( f"Extension {Dialog.__name__}.run_dialog", label=start_message_text, ) result = await dialog_context.begin_dialog(dialog.id) # Send end of conversation if it is completed or cancelled. if ( result.status == DialogTurnStatus.Complete or result.status == DialogTurnStatus.Cancelled ): end_message_text = f"Dialog {dialog.id} has **completed**. Sending EndOfConversation." await turn_context.send_trace_activity( f"Extension {Dialog.__name__}.run_dialog", label=end_message_text, value=result.result, ) activity = Activity( type=ActivityTypes.end_of_conversation, value=result.result ) await turn_context.send_activity(activity) else: # The bot is running as a standard bot. results = await dialog_context.continue_dialog() if results.status == DialogTurnStatus.Empty: await dialog_context.begin_dialog(dialog.id)
def list_style_none() -> Activity: activity = Activity(type=ActivityTypes.message) activity.text = text return activity
async def create_conversation( self, reference: ConversationReference, logic: Callable[[TurnContext], Awaitable] = None, conversation_parameters: ConversationParameters = None, ): """ Starts a new conversation with a user. Used to direct message to a member of a group. :param reference: The conversation reference that contains the tenant :type reference: :class:`botbuilder.schema.ConversationReference` :param logic: The logic to use for the creation of the conversation :type logic: :class:`typing.Callable` :param conversation_parameters: The information to use to create the conversation :type conversation_parameters: :raises: It raises a generic exception error. :return: A task representing the work queued to execute. .. remarks:: To start a conversation, your bot must know its account information and the user's account information on that channel. Most channels only support initiating a direct message (non-group) conversation. The adapter attempts to create a new conversation on the channel, and then sends a conversation update activity through its middleware pipeline to the the callback method. If the conversation is established with the specified users, the ID of the activity will contain the ID of the new conversation.</para> """ try: if reference.service_url is None: raise TypeError( "BotFrameworkAdapter.create_conversation(): reference.service_url cannot be None." ) # Create conversation parameters = (conversation_parameters if conversation_parameters else ConversationParameters(bot=reference.bot, members=[reference.user], is_group=False)) client = await self.create_connector_client(reference.service_url) # Mix in the tenant ID if specified. This is required for MS Teams. if reference.conversation is not None and reference.conversation.tenant_id: # Putting tenant_id in channel_data is a temporary while we wait for the Teams API to be updated parameters.channel_data = { "tenant": { "id": reference.conversation.tenant_id } } # Permanent solution is to put tenant_id in parameters.tenant_id parameters.tenant_id = reference.conversation.tenant_id resource_response = await client.conversations.create_conversation( parameters) request = TurnContext.apply_conversation_reference( Activity(type=ActivityTypes.event, name="CreateConversation"), reference, is_incoming=True, ) request.conversation = ConversationAccount( id=resource_response.id, tenant_id=parameters.tenant_id) request.channel_data = parameters.channel_data if resource_response.service_url: request.service_url = resource_response.service_url context = self.create_context(request) return await self.run_pipeline(context, logic) except Exception as error: raise error
async def process_request( self, request: ReceiveRequest, logger: Logger, # pylint: disable=unused-argument context: object, # pylint: disable=unused-argument ) -> StreamingResponse: # pylint: disable=pointless-string-statement response = StreamingResponse() # We accept all POSTs regardless of path, but anything else requires special treatment. if not request.verb == StreamingRequest.POST: return self._handle_custom_paths(request, response) # Convert the StreamingRequest into an activity the adapter can understand. try: body_str = await request.read_body_as_str() except Exception as error: traceback.print_exc() response.status_code = int(HTTPStatus.BAD_REQUEST) # TODO: log error return response try: # TODO: validate if should use deserialize or from_dict body_dict = loads(body_str) activity: Activity = Activity.deserialize(body_dict) # All activities received by this StreamingRequestHandler will originate from the same channel, but we won't # know what that channel is until we've received the first request. if not self.service_url: self._service_url = activity.service_url # If this is the first time the handler has seen this conversation it needs to be added to the dictionary so # the adapter is able to route requests to the correct handler. if not self.has_conversation(activity.conversation.id): self._conversations[activity.conversation.id] = datetime.now() """ Any content sent as part of a StreamingRequest, including the request body and inline attachments, appear as streams added to the same collection. The first stream of any request will be the body, which is parsed and passed into this method as the first argument, 'body'. Any additional streams are inline attachments that need to be iterated over and added to the Activity as attachments to be sent to the Bot. """ if len(request.streams) > 1: stream_attachments = [ Attachment(content_type=stream.content_type, content=stream.stream) for stream in request.streams ] if activity.attachments: activity.attachments += stream_attachments else: activity.attachments = stream_attachments # Now that the request has been converted into an activity we can send it to the adapter. adapter_response = await self._activity_processor.process_streaming_activity( activity, self._bot.on_turn) # Now we convert the invokeResponse returned by the adapter into a StreamingResponse we can send back # to the channel. if not adapter_response: response.status_code = int(HTTPStatus.OK) else: response.status_code = adapter_response.status if adapter_response.body: response.set_body(adapter_response.body) except Exception as error: traceback.print_exc() response.status_code = int(HTTPStatus.INTERNAL_SERVER_ERROR) response.set_body(str(error)) # TODO: log error return response
async def test_eoc_activity(self): activity = Activity(type=ActivityTypes.end_of_conversation) await self.__activity_callback_test(activity) assert (activity.caller_id == f"{CallerIdConstants.bot_to_bot_prefix}{self.skill_id}")
async def _recognize_token( self, dialog_context: DialogContext) -> PromptRecognizerResult: context = dialog_context.context token = None if OAuthPrompt._is_token_response_event(context): token = context.activity.value # fixup the turnContext's state context if this was received from a skill host caller state: CallerInfo = dialog_context.active_dialog.state[ OAuthPrompt.PERSISTED_CALLER] if state: # set the ServiceUrl to the skill host's Url dialog_context.context.activity.service_url = state.caller_service_url claims_identity = context.turn_state.get( BotAdapter.BOT_IDENTITY_KEY) connector_client = await _UserTokenAccess.create_connector_client( context, dialog_context.context.activity.service_url, claims_identity, state.scope, ) context.turn_state[ BotAdapter.BOT_CONNECTOR_CLIENT_KEY] = connector_client elif OAuthPrompt._is_teams_verification_invoke(context): code = context.activity.value["state"] try: token = await _UserTokenAccess.get_user_token( context, self._settings, code) if token is not None: await context.send_activity( Activity( type="invokeResponse", value=InvokeResponse(int(HTTPStatus.OK)), )) else: await context.send_activity( Activity( type="invokeResponse", value=InvokeResponse(int(HTTPStatus.NOT_FOUND)), )) except Exception: await context.send_activity( Activity( type="invokeResponse", value=InvokeResponse( int(HTTPStatus.INTERNAL_SERVER_ERROR)), )) elif self._is_token_exchange_request_invoke(context): if isinstance(context.activity.value, dict): context.activity.value = TokenExchangeInvokeRequest( ).from_dict(context.activity.value) if not (context.activity.value and self._is_token_exchange_request( context.activity.value)): # Received activity is not a token exchange request. await context.send_activity( self._get_token_exchange_invoke_response( int(HTTPStatus.BAD_REQUEST), "The bot received an InvokeActivity that is missing a TokenExchangeInvokeRequest value." " This is required to be sent with the InvokeActivity.", )) elif (context.activity.value.connection_name != self._settings.connection_name): # Connection name on activity does not match that of setting. await context.send_activity( self._get_token_exchange_invoke_response( int(HTTPStatus.BAD_REQUEST), "The bot received an InvokeActivity with a TokenExchangeInvokeRequest containing a" " ConnectionName that does not match the ConnectionName expected by the bots active" " OAuthPrompt. Ensure these names match when sending the InvokeActivityInvalid" " ConnectionName in the TokenExchangeInvokeRequest", )) elif not getattr(context.adapter, "exchange_token"): # Token Exchange not supported in the adapter. await context.send_activity( self._get_token_exchange_invoke_response( int(HTTPStatus.BAD_GATEWAY), "The bot's BotAdapter does not support token exchange operations." " Ensure the bot's Adapter supports the ExtendedUserTokenProvider interface.", )) raise AttributeError( "OAuthPrompt._recognize_token(): not supported by the current adapter." ) else: # No errors. Proceed with token exchange. token_exchange_response = None try: token_exchange_response = await _UserTokenAccess.exchange_token( context, self._settings, TokenExchangeRequest( token=context.activity.value.token), ) except: # Ignore Exceptions # If token exchange failed for any reason, tokenExchangeResponse above stays null, and # hence we send back a failure invoke response to the caller. pass if not token_exchange_response or not token_exchange_response.token: await context.send_activity( self._get_token_exchange_invoke_response( int(HTTPStatus.PRECONDITION_FAILED), "The bot is unable to exchange token. Proceed with regular login.", )) else: await context.send_activity( self._get_token_exchange_invoke_response( int(HTTPStatus.OK), None, context.activity.value.id)) token = TokenResponse( channel_id=token_exchange_response.channel_id, connection_name=token_exchange_response. connection_name, token=token_exchange_response.token, expiration=None, ) elif context.activity.type == ActivityTypes.message and context.activity.text: match = re.match(r"(?<!\d)\d{6}(?!\d)", context.activity.text) if match: token = await _UserTokenAccess.get_user_token( context, self._settings, match[0]) return (PromptRecognizerResult(True, token) if token is not None else PromptRecognizerResult())
# Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. import aiounittest from botbuilder.schema import Activity, ConversationReference from botbuilder.core import TurnContext from botbuilder.core.adapters import TestAdapter from datetime import datetime RECEIVED_MESSAGE = Activity(type='message', text='received') UPDATED_ACTIVITY = Activity(type='message', text='update') DELETED_ACTIVITY_REFERENCE = ConversationReference(activity_id='1234') class TestTestAdapter(aiounittest.AsyncTestCase): async def test_should_call_bog_logic_when_receive_activity_is_called(self): async def logic(context: TurnContext): assert context assert context.activity assert context.activity.type == 'message' assert context.activity.text == 'test' assert context.activity.id assert context.activity.from_property assert context.activity.recipient assert context.activity.conversation assert context.activity.channel_id assert context.activity.service_url adapter = TestAdapter(logic) await adapter.receive_activity('test')
def create_send_activity() -> Activity: return Activity( type=ActivityTypes.message, delivery_mode=DeliveryModes.expect_replies, text=str(uuid.uuid4()), )
async def logic(context: TurnContext): nonlocal counter counter += 1 await context.send_activity( Activity(type='message', text=str(counter)))
async def _recognize_token(self, context: TurnContext) -> PromptRecognizerResult: token = None if OAuthPrompt._is_token_response_event(context): token = context.activity.value elif OAuthPrompt._is_teams_verification_invoke(context): code = context.activity.value["state"] try: token = await self.get_user_token(context, code) if token is not None: await context.send_activity( Activity( type="invokeResponse", value=InvokeResponse(int(HTTPStatus.OK)), )) else: await context.send_activity( Activity( type="invokeResponse", value=InvokeResponse(int(HTTPStatus.NOT_FOUND)), )) except Exception: await context.send_activity( Activity( type="invokeResponse", value=InvokeResponse( int(HTTPStatus.INTERNAL_SERVER_ERROR)), )) elif self._is_token_exchange_request_invoke(context): if isinstance(context.activity.value, dict): context.activity.value = TokenExchangeInvokeRequest( ).from_dict(context.activity.value) if not (context.activity.value and self._is_token_exchange_request( context.activity.value)): # Received activity is not a token exchange request. await context.send_activity( self._get_token_exchange_invoke_response( int(HTTPStatus.BAD_REQUEST), "The bot received an InvokeActivity that is missing a TokenExchangeInvokeRequest value." " This is required to be sent with the InvokeActivity.", )) elif (context.activity.value.connection_name != self._settings.connection_name): # Connection name on activity does not match that of setting. await context.send_activity( self._get_token_exchange_invoke_response( int(HTTPStatus.BAD_REQUEST), "The bot received an InvokeActivity with a TokenExchangeInvokeRequest containing a" " ConnectionName that does not match the ConnectionName expected by the bots active" " OAuthPrompt. Ensure these names match when sending the InvokeActivityInvalid" " ConnectionName in the TokenExchangeInvokeRequest", )) elif not getattr(context.adapter, "exchange_token"): # Token Exchange not supported in the adapter. await context.send_activity( self._get_token_exchange_invoke_response( int(HTTPStatus.BAD_GATEWAY), "The bot's BotAdapter does not support token exchange operations." " Ensure the bot's Adapter supports the ITokenExchangeProvider interface.", )) raise AttributeError( "OAuthPrompt.recognize(): not supported by the current adapter." ) else: # No errors. Proceed with token exchange. extended_user_token_provider: ExtendedUserTokenProvider = context.adapter token_exchange_response = await extended_user_token_provider.exchange_token_from_credentials( context, self._settings.oath_app_credentials, self._settings.connection_name, context.activity.from_property.id, TokenExchangeRequest(token=context.activity.value.token), ) if not token_exchange_response or not token_exchange_response.token: await context.send_activity( self._get_token_exchange_invoke_response( int(HTTPStatus.CONFLICT), "The bot is unable to exchange token. Proceed with regular login.", )) else: await context.send_activity( self._get_token_exchange_invoke_response( int(HTTPStatus.OK), None, context.activity.value.id)) token = TokenResponse( channel_id=token_exchange_response.channel_id, connection_name=token_exchange_response. connection_name, token=token_exchange_response.token, expiration=None, ) elif context.activity.type == ActivityTypes.message and context.activity.text: match = re.match(r"(?<!\d)\d{6}(?!\d)", context.activity.text) if match: token = await self.get_user_token(context, match[0]) return (PromptRecognizerResult(True, token) if token is not None else PromptRecognizerResult())
# Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. import pytest from botbuilder.core import BotContext, BotState, MemoryStorage, TestAdapter from botbuilder.schema import Activity RECEIVED_MESSAGE = Activity(type='message', text='received') STORAGE_KEY = 'stateKey' def cached_state(context, state_key): cached = context.services.get(state_key) return cached['state'] if cached is not None else None def key_factory(context): assert context is not None return STORAGE_KEY class TestBotState: storage = MemoryStorage() adapter = TestAdapter() context = BotContext(adapter, RECEIVED_MESSAGE) middleware = BotState(storage, key_factory) @pytest.mark.asyncio async def test_should_return_undefined_from_get_if_nothing_cached(self): state = await self.middleware.get(self.context)