async def _login_step( self, step_context: WaterfallStepContext) -> DialogTurnResult: user_profile: UserProfile = await self.user_profile_accessor.get( step_context.context, UserProfile) user_bot_id = TurnContext.get_conversation_reference( step_context.context.activity).user.id if user_profile.is_auth: self.logged_users[user_bot_id] = user_profile.id return await step_context.continue_dialog() if step_context.result: if isinstance(step_context.result, dict): result_token = step_context.result['token'] else: result_token = step_context.result.token try: id = await GitHubClient(token=result_token).get_id() except: return await self._logout(step_context) self.logged_users[user_bot_id] = id user_profile.id = id user_profile.is_auth = True user_profile.random = str(uuid.uuid4()) await step_context.context.send_activity( f"Accesso effettuato correttamente.") return await step_context.end_dialog() else: await step_context.context.send_activity( "Non è stato possibile effettuare l'accesso.") return await step_context.cancel_all_dialogs()
async def _send_proactive_non_threaded_message(self, turn_context: TurnContext): conversation_reference = TurnContext.get_conversation_reference( turn_context.activity) conversation_parameters = ConversationParameters( is_group=False, bot=turn_context.activity.recipient, members=[turn_context.activity.from_property], tenant_id=turn_context.activity.conversation.tenant_id, ) proactive_message = MessageFactory.text("This is a proactive message") proactive_message.label = turn_context.activity.id async def get_ref(tc1): conversation_reference_inner = TurnContext.get_conversation_reference( tc1.activity) return await tc1.adapter.continue_conversation( conversation_reference_inner, send_message, self._app_id) async def send_message(tc2: TurnContext): return await tc2.send_activity(proactive_message) await turn_context.adapter.create_conversation( conversation_reference, get_ref, conversation_parameters)
async def _message_all_members(self, turn_context: TurnContext): team_members = await TeamsInfo.get_members(turn_context) for member in team_members: conversation_reference = TurnContext.get_conversation_reference( turn_context.activity) conversation_parameters = ConversationParameters( is_group=False, bot=turn_context.activity.recipient, members=[member], tenant_id=turn_context.activity.conversation.tenant_id, ) async def get_ref(tc1): conversation_reference_inner = TurnContext.get_conversation_reference( tc1.activity) return await tc1.adapter.continue_conversation( conversation_reference_inner, send_message, self._app_id) async def send_message(tc2: TurnContext): return await tc2.send_activity( f"Hello {member.name}. I'm a Teams conversation bot.") # pylint: disable=cell-var-from-loop await turn_context.adapter.create_conversation( conversation_reference, get_ref, conversation_parameters) await turn_context.send_activity( MessageFactory.text("All messages have been sent"))
async def send_private_message(turn_context: TurnContext, user_id: str, activity: Activity): """ Send proactive message to the user (Does not broke the chat conversation) """ conversation_reference = TurnContext.get_conversation_reference( turn_context.activity ) conversation_parameters = ConversationParameters( is_group=False, bot=turn_context.activity.recipient, members=[await TeamsInfo.get_member(turn_context, user_id)], tenant_id=turn_context.activity.conversation.tenant_id, ) async def get_reference(temp_turn_context_1: TurnContext): conversation_reference_inner = TurnContext.get_conversation_reference( temp_turn_context_1.activity ) return await temp_turn_context_1.adapter.continue_conversation( conversation_reference_inner, send_message, temp_turn_context_1.adapter.settings.app_id ) async def send_message(temp_turn_context_2: TurnContext): return await temp_turn_context_2.send_activity(activity) await turn_context.adapter.create_conversation( conversation_reference, get_reference, conversation_parameters )
async def get_reference(temp_turn_context_1: TurnContext): conversation_reference_inner = TurnContext.get_conversation_reference( temp_turn_context_1.activity ) return await temp_turn_context_1.adapter.continue_conversation( conversation_reference_inner, send_message, temp_turn_context_1.adapter.settings.app_id )
async def create_skill_conversation_id( # pylint: disable=arguments-differ self, options: SkillConversationIdFactoryOptions) -> str: """ Creates a new `SkillConversationReference`. :param options: Creation options to use when creating the `SkillConversationReference`. :type options: :class:`botbuilder.core.skills.SkillConversationIdFactoryOptions` :return: ID of the created `SkillConversationReference`. """ if not options: raise TypeError("options can't be None") conversation_reference = TurnContext.get_conversation_reference( options.activity) skill_conversation_id = str(uuid()) # Create the SkillConversationReference instance. skill_conversation_reference = SkillConversationReference( conversation_reference=conversation_reference, oauth_scope=options.from_bot_oauth_scope, ) # Store the SkillConversationReference using the skill_conversation_id as a key. skill_conversation_info = { skill_conversation_id: skill_conversation_reference } await self._storage.write(skill_conversation_info) # Return the generated skill_conversation_id (that will be also used as the conversation ID to call the skill). return skill_conversation_id
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 # 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 get_ref(tc1): conversation_reference_inner = TurnContext.get_conversation_reference( tc1.activity ) return await tc1.adapter.continue_conversation( conversation_reference_inner, send_message, self._app_id )
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.service_url == ACTIVITY.service_url
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
async def _process_open_command(self, context: TurnContext) -> Any: sessions = await self.inspection_state_accessor.get( context, DEFAULT_INSPECTION_SESSIONS_BY_STATUS) session_id = self._open_command( sessions, TurnContext.get_conversation_reference(context.activity)) await context.send_activity( make_command_activity( f"{InspectionMiddleware._COMMAND} attach {session_id}")) await self.inspection_state.save_changes(context, False)
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_apply_conversation_reference_when_is_incoming_is_true_should_not_prepare_a_reply( self): reference = TurnContext.get_conversation_reference(ACTIVITY) reply = TurnContext.apply_conversation_reference( Activity(type="message", text="reply"), reference, True) assert reply.recipient == ACTIVITY.recipient assert reply.from_property == ACTIVITY.from_property assert reply.conversation == ACTIVITY.conversation assert reply.service_url == ACTIVITY.service_url assert reply.channel_id == ACTIVITY.channel_id
def test_apply_conversation_reference_should_return_prepare_reply_when_is_incoming_is_False( self): reference = TurnContext.get_conversation_reference(ACTIVITY) reply = TurnContext.apply_conversation_reference( Activity(type='message', text='reply'), reference) assert reply.recipient == ACTIVITY.from_property assert reply.from_property == ACTIVITY.recipient assert reply.conversation == ACTIVITY.conversation assert reply.service_url == ACTIVITY.service_url assert reply.channel_id == ACTIVITY.channel_id
async def on_message_activity(self, turn_context: TurnContext): # If there is an active skill active_skill_id: str = await self._active_skill_property.get( turn_context) skill_conversation_id = await self._conversation_id_factory.create_skill_conversation_id( TurnContext.get_conversation_reference(turn_context.activity)) if active_skill_id: # NOTE: Always SaveChanges() before calling a skill so that any activity generated by the skill # will have access to current accurate state. await self._conversation_state.save_changes(turn_context, force=True) # route activity to the skill await self._skill_client.post_activity( self._bot_id, self._skills_config.SKILLS[active_skill_id].app_id, self._skills_config.SKILLS[active_skill_id].skill_endpoint, self._skills_config.SKILL_HOST_ENDPOINT, skill_conversation_id, turn_context.activity, ) else: if "skill" in turn_context.activity.text: await turn_context.send_activity( MessageFactory.text( "Got it, connecting you to the skill...")) # save ConversationReferene for skill await self._active_skill_property.set(turn_context, "SkillBot") # NOTE: Always SaveChanges() before calling a skill so that any activity generated by the # skill will have access to current accurate state. await self._conversation_state.save_changes(turn_context, force=True) await self._skill_client.post_activity( self._bot_id, self._skills_config.SKILLS["SkillBot"].app_id, self._skills_config.SKILLS["SkillBot"].skill_endpoint, self._skills_config.SKILL_HOST_ENDPOINT, skill_conversation_id, turn_context.activity, ) else: # just respond await turn_context.send_activity( MessageFactory.text( "Me no nothin'. Say \"skill\" and I'll patch you through" ))
async def reprompt_dialog( # pylint: disable=unused-argument self, context: TurnContext, instance: DialogInstance): # Create and send an event to the skill so it can resume the dialog. reprompt_event = Activity(type=ActivityTypes.event, name=DialogEvents.reprompt_dialog) # Apply conversation reference and common properties from incoming activity before sending. TurnContext.apply_conversation_reference( reprompt_event, TurnContext.get_conversation_reference(context.activity), is_incoming=True, ) await self._send_to_skill(context, reprompt_event)
async def _send_proactive_threaded_message(self, turn_context: TurnContext): activity = MessageFactory.text( "I will send two messages to this thread") result = await turn_context.send_activity(activity) team_id = TeamsInfo.get_team_id(turn_context) for i in range(2): proactive_activity = MessageFactory.text( f"This is message {i+1}/2 that will be sent.") TurnContext.apply_conversation_reference( proactive_activity, TurnContext.get_conversation_reference(activity)) await turn_context.send_activity(proactive_activity)
async def on_error(context: TurnContext, error: Exception): # This check writes out errors to console log .vs. app insights. # NOTE: In production environment, you should consider logging this to Azure # application insights. print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr) traceback.print_exc() # Send a message to the user await context.send_activity("The bot encountered an error or bug.") await context.send_activity( "To continue to run this bot, please fix the bot source code.") # Send a trace activity if we're talking to the Bot Framework Emulator if context.activity.channel_id == "emulator": # Create a trace activity that contains the error object trace_activity = Activity( label="TurnError", name="on_turn_error Trace", timestamp=datetime.utcnow(), type=ActivityTypes.trace, value=f"{error}", value_type="https://www.botframework.com/schemas/error", ) await context.send_activity(trace_activity) # Inform the active skill that the conversation is ended so that it has # a chance to clean up. # Note: ActiveSkillPropertyName is set by the RooBot while messages are being # forwarded to a Skill. active_skill_id = await CONVERSATION_STATE.create_property( "activeSkillProperty").get(context) if active_skill_id: end_of_conversation = Activity(type=ActivityTypes.end_of_conversation, code="RootSkillError") TurnContext.apply_conversation_reference( end_of_conversation, TurnContext.get_conversation_reference(context.activity), is_incoming=True) await CONVERSATION_STATE.save_changes(context, True) await CLIENT.post_activity( CONFIG.APP_ID, SKILL_CONFIG.SKILLS[active_skill_id], SKILL_CONFIG.SKILL_HOST_ENDPOINT, end_of_conversation, ) # Clear out state await CONVERSATION_STATE.delete(context)
async def create_skill_conversation_id( self, options_or_conversation_reference: Union[ SkillConversationIdFactoryOptions, ConversationReference], ) -> str: key = (options_or_conversation_reference.activity.conversation.id + options_or_conversation_reference.activity.service_url) if key not in self.conversation_refs: self.conversation_refs[key] = SkillConversationReference( conversation_reference=TurnContext.get_conversation_reference( options_or_conversation_reference.activity), oauth_scope=options_or_conversation_reference. from_bot_oauth_scope, ) return key
async def reprompt_dialog( # pylint: disable=unused-argument self, context: TurnContext, instance: DialogInstance ): # Create and send an event to the skill so it can resume the dialog. reprompt_event = Activity( type=ActivityTypes.event, name=DialogEvents.reprompt_dialog ) # Apply conversation reference and common properties from incoming activity before sending. TurnContext.apply_conversation_reference( reprompt_event, TurnContext.get_conversation_reference(context.activity), is_incoming=True, ) # connection Name is not applicable for a RePrompt, as we don't expect as OAuthCard in response. skill_conversation_id = instance.state[SkillDialog.SKILLCONVERSATIONIDSTATEKEY] await self._send_to_skill(context, reprompt_event, skill_conversation_id)
async def post_activity( self, from_bot_id: str, to_skill: BotFrameworkSkill, service_url: str, activity: Activity, ) -> InvokeResponse: skill_conversation_id = await self._skill_conversation_id_factory.create_skill_conversation_id( TurnContext.get_conversation_reference(activity)) return await super().post_activity( from_bot_id, to_skill.app_id, to_skill.skill_endpoint, service_url, skill_conversation_id, activity, )
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 teams_create_conversation(self, turn_context: TurnContext, teams_channel_id: str, message): params = ConversationParameters( is_group=True, channel_data={"channel": { "id": teams_channel_id }}, activity=message) connector_client = await turn_context.adapter.create_connector_client( turn_context.activity.service_url) conversation_resource_response = await connector_client.conversations.create_conversation( params) conversation_reference = TurnContext.get_conversation_reference( turn_context.activity) conversation_reference.conversation.id = conversation_resource_response.id return [ conversation_reference, conversation_resource_response.activity_id ]
async def create_skill_conversation_id( # pylint: disable=W0221 self, options: SkillConversationIdFactoryOptions ) -> str: conversation_reference = TurnContext.get_conversation_reference( options.activity ) key = hashlib.md5( f"{conversation_reference.conversation.id}{conversation_reference.service_url}".encode() ).hexdigest() skill_conversation_reference = SkillConversationReference( conversation_reference=conversation_reference, oauth_scope=options.from_bot_oauth_scope, ) self._conversation_refs[key] = skill_conversation_reference return key
async def create_skill_conversation_id( self, options_or_conversation_reference: Union[ SkillConversationIdFactoryOptions, ConversationReference ], ) -> str: if not options_or_conversation_reference: raise TypeError("Need options or conversation reference") if not isinstance( options_or_conversation_reference, SkillConversationIdFactoryOptions ): raise TypeError( "This SkillConversationIdFactory can only handle SkillConversationIdFactoryOptions" ) options = options_or_conversation_reference # Create the storage key based on the SkillConversationIdFactoryOptions. conversation_reference = TurnContext.get_conversation_reference( options.activity ) skill_conversation_id = ( f"{conversation_reference.conversation.id}" f"-{options.bot_framework_skill.id}" f"-{conversation_reference.channel_id}" f"-skillconvo" ) # Create the SkillConversationReference instance. skill_conversation_reference = SkillConversationReference( conversation_reference=conversation_reference, oauth_scope=options.from_bot_oauth_scope, ) # Store the SkillConversationReference using the skill_conversation_id as a key. skill_conversation_info = {skill_conversation_id: skill_conversation_reference} await self._storage.write(skill_conversation_info) # Return the generated skill_conversation_id (that will be also used as the conversation ID to call the skill). return skill_conversation_id
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 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 on_message_activity(self, turn_context: TurnContext): # for signin, just use an oauth prompt to get the exchangeable token # also ensure that the channelId is not emulator if turn_context.activity.type != "emulator": if (turn_context.activity.text == "login" or turn_context.activity.text.isdigit()): await self._conversation_state.load(turn_context, True) await self._user_state.load(turn_context, True) await DialogHelper.run_dialog( self._dialog, turn_context, self._conversation_state.create_property("DialogState"), ) elif turn_context.activity.text == "logout": bot_adapter = turn_context.adapter await bot_adapter.sign_out_user(turn_context, self._connection_name) await turn_context.send_activity( MessageFactory.text("You have been signed out.")) elif turn_context.activity.text in ("skill login", "skill logout"): # incoming activity needs to be cloned for buffered replies clone_activity = MessageFactory.text( turn_context.activity.text) TurnContext.apply_conversation_reference( clone_activity, TurnContext.get_conversation_reference( turn_context.activity), True, ) clone_activity.delivery_mode = DeliveryModes.expect_replies activities = await self._client.post_buffered_activity( self._from_bot_id, self._to_bot_id, "http://localhost:3979/api/messages", "http://tempuri.org/whatever", turn_context.activity.conversation.id, clone_activity, ) if activities: if not await self._intercept_oauth_cards( activities, turn_context): await turn_context.send_activities(activities) return await turn_context.send_activity( MessageFactory.text("parent: before child")) activity = MessageFactory.text("parent: before child") TurnContext.apply_conversation_reference( activity, TurnContext.get_conversation_reference(turn_context.activity), True, ) activity.delivery_mode = DeliveryModes.expect_replies activities = await self._client.post_buffered_activity( self._from_bot_id, self._to_bot_id, "http://localhost:3979/api/messages", "http://tempuri.org/whatever", str(uuid4()), activity, ) await turn_context.send_activities(activities) await turn_context.send_activity( MessageFactory.text("parent: after child"))
def _rm_conversation_reference(self, activity: Activity): conversation_reference = \ TurnContext.get_conversation_reference(activity) if conversation_reference in self.conversation_references: del self.conversation_references[conversation_reference.user.id]