/// <summary> /// Tries to reject the pending engagement request of the given party. /// </summary> /// <param name="partyToReject">The party whose request to reject.</param> /// <param name="rejecterParty">The party rejecting the request (optional).</param> /// <returns>The result of the operation.</returns> public MessageRouterResult RejectPendingRequest(Party partyToReject, Party rejecterParty = null) { if (partyToReject == null) { throw new ArgumentNullException($"The party to reject ({nameof(partyToReject)} cannot be null"); } MessageRouterResult result = new MessageRouterResult() { ConversationOwnerParty = rejecterParty, ConversationClientParty = partyToReject }; if (RoutingDataManager.RemovePendingRequest(partyToReject)) { result.Type = MessageRouterResultType.EngagementRejected; } else { result.Type = MessageRouterResultType.Error; result.ErrorMessage = $"Failed to remove the pending request of user \"{partyToReject.ChannelAccount?.Name}\""; } return(result); }
/// <summary> /// Ends the engagement where the given party is the conversation owner /// (e.g. a customer service agent). /// </summary> /// <param name="conversationOwnerParty">The owner of the engagement (conversation).</param> /// <returns>The results. If the number of results is more than 0, the operation was successful.</returns> public List <MessageRouterResult> EndEngagement(Party conversationOwnerParty) { List <MessageRouterResult> messageRouterResults = new List <MessageRouterResult>(); Party ownerInConversation = RoutingDataManager.FindEngagedPartyByChannel( conversationOwnerParty.ChannelId, conversationOwnerParty.ChannelAccount); if (ownerInConversation != null && RoutingDataManager.IsEngaged(ownerInConversation, EngagementProfile.Owner)) { Party otherParty = RoutingDataManager.GetEngagedCounterpart(ownerInConversation); messageRouterResults.AddRange( RoutingDataManager.RemoveEngagement(ownerInConversation, EngagementProfile.Owner)); } else { messageRouterResults.Add(new MessageRouterResult() { Type = MessageRouterResultType.Error, ConversationOwnerParty = conversationOwnerParty, ErrorMessage = "No conversation to close found" }); } return(messageRouterResults); }
/// <summary> /// Creates a new message activity and populates it based on the given arguments. /// </summary> /// <param name="sender">The channel account of the sender.</param> /// <param name="recipient">The conversation reference of the recipient.</param> /// <param name="message">The message content.</param> /// <returns>A newly created message activity.</returns> public static IMessageActivity CreateMessageActivity( ChannelAccount sender, ConversationReference recipient, string message) { IMessageActivity messageActivity = Activity.CreateMessageActivity(); if (sender != null) { messageActivity.From = sender; } if (recipient != null) { if (recipient.Conversation != null) { messageActivity.Conversation = recipient.Conversation; } ChannelAccount recipientChannelAccount = RoutingDataManager.GetChannelAccount(recipient); if (recipientChannelAccount != null) { messageActivity.Recipient = recipientChannelAccount; } } messageActivity.Text = message; return(messageActivity); }
/// <summary> /// Tries to initiate a connection (1:1 conversation) by creating a request on behalf of /// the given requestor. This method does nothing, if a request for the same user already exists. /// </summary> /// <param name="requestor">The requestor conversation reference.</param> /// <param name="rejectConnectionRequestIfNoAggregationChannel"> /// If true, will reject all requests, if there is no aggregation channel.</param> /// <returns>The result of the operation: /// - ConnectionRequestResultType.Created, /// - ConnectionRequestResultType.AlreadyExists, /// - ConnectionRequestResultType.NotSetup or /// - ConnectionRequestResultType.Error (see the error message for more details). /// </returns> public virtual ConnectionRequestResult CreateConnectionRequest( ConversationReference requestor, bool rejectConnectionRequestIfNoAggregationChannel = false) { if (requestor == null) { throw new ArgumentNullException("Requestor missing"); } ConnectionRequestResult createConnectionRequestResult = null; RoutingDataManager.AddConversationReference(requestor); ConnectionRequest connectionRequest = new ConnectionRequest(requestor); if (RoutingDataManager.IsAssociatedWithAggregation(requestor)) { createConnectionRequestResult = new ConnectionRequestResult() { Type = ConnectionRequestResultType.Error, ErrorMessage = $"The given ConversationReference ({RoutingDataManager.GetChannelAccount(requestor)?.Name}) is associated with aggregation and hence invalid to request a connection" }; } else { createConnectionRequestResult = RoutingDataManager.AddConnectionRequest( connectionRequest, rejectConnectionRequestIfNoAggregationChannel); } return(createConnectionRequestResult); }
/// <summary> /// Sends the given message to all the aggregation channels, if any exist. /// </summary> /// <param name="messageText">The message to broadcast.</param> /// <returns></returns> public async Task BroadcastMessageToAggregationChannelsAsync(string messageText) { foreach (Party aggregationChannel in RoutingDataManager.GetAggregationParties()) { await SendMessageToPartyByBotAsync(aggregationChannel, messageText); } }
/// <summary> /// Tries to send the given message activity to the given party using this bot on the same /// channel as the party who the message is sent to. /// </summary> /// <param name="partyToMessage">The party to send the message to.</param> /// <param name="messageActivity">The message activity to send (message content).</param> /// <returns>The ResourceResponse instance or null in case of an error.</returns> public async Task <ResourceResponse> SendMessageToPartyByBotAsync(Party partyToMessage, IMessageActivity messageActivity) { Party botParty = null; if (partyToMessage != null) { // We need the channel account of the bot in the SAME CHANNEL as the RECIPIENT. // The identity of the bot in the channel of the sender is most likely a different one and // thus unusable since it will not be recognized on the recipient's channel. botParty = RoutingDataManager.FindBotPartyByChannelAndConversation( partyToMessage.ChannelId, partyToMessage.ConversationAccount); } if (botParty != null) { messageActivity.From = botParty.ChannelAccount; MessagingUtils.ConnectorClientAndMessageBundle bundle = MessagingUtils.CreateConnectorClientAndMessageActivity( partyToMessage.ServiceUrl, messageActivity); return(await bundle.connectorClient.Conversations.SendToConversationAsync( (Activity)bundle.messageActivity)); } return(null); }
/// <summary> /// Tries to establish 1:1 chat between the two given parties. /// Note that the conversation owner will have a new separate party in the created engagement. /// </summary> /// <param name="conversationOwnerParty">The party who owns the conversation (e.g. customer service agent).</param> /// <param name="conversationClientParty">The other party in the conversation.</param> /// <returns>The result of the operation.</returns> public async Task <MessageRouterResult> AddEngagementAsync( Party conversationOwnerParty, Party conversationClientParty) { if (conversationOwnerParty == null || conversationClientParty == null) { throw new ArgumentNullException( $"Neither of the arguments ({nameof(conversationOwnerParty)}, {nameof(conversationClientParty)}) can be null"); } MessageRouterResult result = new MessageRouterResult() { ConversationOwnerParty = conversationOwnerParty, ConversationClientParty = conversationClientParty }; Party botParty = RoutingDataManager.FindBotPartyByChannelAndConversation( conversationOwnerParty.ChannelId, conversationOwnerParty.ConversationAccount); if (botParty != null) { ConnectorClient connectorClient = new ConnectorClient(new Uri(conversationOwnerParty.ServiceUrl)); ConversationResourceResponse response = await connectorClient.Conversations.CreateDirectConversationAsync( botParty.ChannelAccount, conversationOwnerParty.ChannelAccount); if (response != null && !string.IsNullOrEmpty(response.Id)) { // The conversation account of the conversation owner for this 1:1 chat is different - // thus, we need to create a new party instance ConversationAccount directConversationAccount = new ConversationAccount(id: response.Id); Party acceptorPartyEngaged = new Party( conversationOwnerParty.ServiceUrl, conversationOwnerParty.ChannelId, conversationOwnerParty.ChannelAccount, directConversationAccount); RoutingDataManager.AddParty(acceptorPartyEngaged); RoutingDataManager.AddParty( new Party(botParty.ServiceUrl, botParty.ChannelId, botParty.ChannelAccount, directConversationAccount), false); result = RoutingDataManager.AddEngagementAndClearPendingRequest(acceptorPartyEngaged, conversationClientParty); result.ConversationResourceResponse = response; } else { result.Type = MessageRouterResultType.Error; result.ErrorMessage = "Failed to create a direct conversation"; } } else { result.Type = MessageRouterResultType.Error; result.ErrorMessage = "Failed to find the bot instance"; } await HandleAndLogMessageRouterResultAsync(result); return(result); }
/// <summary> /// Stores the conversation reference instances (sender and recipient) in the given activity. /// </summary> /// <param name="activity">The activity.</param> /// <returns>The list of two results, where the first element is for the sender and the last for the recipient.</returns> public IList <ModifyRoutingDataResult> StoreConversationReferences(IActivity activity) { return(new List <ModifyRoutingDataResult>() { RoutingDataManager.AddConversationReference(CreateSenderConversationReference(activity)), RoutingDataManager.AddConversationReference(CreateRecipientConversationReference(activity)) }); }
/// <summary> /// Checks if the given connection matches this one. /// </summary> /// <param name="other">The other connection.</param> /// <returns>True, if the connections are match. False otherwise.</returns> public bool Equals(Connection other) { return(other != null && ((RoutingDataManager.Match(ConversationReference1, other.ConversationReference1) && RoutingDataManager.Match(ConversationReference2, other.ConversationReference2)) || (RoutingDataManager.Match(ConversationReference1, other.ConversationReference2) && RoutingDataManager.Match(ConversationReference2, other.ConversationReference1)))); }
/// <summary> /// Tries to initiates the engagement by creating a request on behalf of the sender in the /// given activity. This method does nothing, if a request for the same user already exists. /// </summary> /// <param name="activity">The activity.</param> /// <returns>The result of the operation.</returns> public MessageRouterResult InitiateEngagement(Activity activity) { MessageRouterResult messageRouterResult = RoutingDataManager.AddPendingRequest(MessagingUtils.CreateSenderParty(activity)); messageRouterResult.Activity = activity; return(messageRouterResult); }
/// <summary> /// Routes the message in the given activity, if the sender is connected in a conversation. /// </summary> /// <param name="activity">The activity to handle.</param> /// <param name="addNameToMessage">If true, will add the name of the sender to the beginning of the message.</param> /// <returns>The result of the operation: /// - MessageRouterResultType.NoActionTaken, if no routing rule for the sender is found OR /// - MessageRouterResultType.OK, if the message was routed successfully OR /// - MessageRouterResultType.FailedToForwardMessage in case of an error (see the error message). /// </returns> public virtual async Task <MessageRoutingResult> RouteMessageIfSenderIsConnectedAsync( IMessageActivity activity, bool addNameToMessage = true) { ConversationReference sender = CreateSenderConversationReference(activity); Connection connection = RoutingDataManager.FindConnection(sender); MessageRoutingResult messageRoutingResult = new MessageRoutingResult() { Type = MessageRoutingResultType.NoActionTaken, Connection = connection }; if (connection != null) { ConversationReference recipient = RoutingDataManager.Match(sender, connection.ConversationReference1) ? connection.ConversationReference2 : connection.ConversationReference1; if (recipient != null) { string message = activity.Text; if (addNameToMessage) { string senderName = RoutingDataManager.GetChannelAccount(sender).Name; if (!string.IsNullOrWhiteSpace(senderName)) { message = $"{senderName}: {message}"; } } ResourceResponse resourceResponse = await SendMessageAsync(recipient, message); if (resourceResponse != null) { messageRoutingResult.Type = MessageRoutingResultType.MessageRouted; if (!RoutingDataManager.UpdateTimeSinceLastActivity(connection)) { Logger.Log("Failed to update the time since the last activity property of the connection"); } } else { messageRoutingResult.Type = MessageRoutingResultType.FailedToRouteMessage; messageRoutingResult.ErrorMessage = $"Failed to forward the message to the recipient"; } } else { messageRoutingResult.Type = MessageRoutingResultType.Error; messageRoutingResult.ErrorMessage = "Failed to find the recipient to forward the message to"; } } return(messageRoutingResult); }
/// <summary> /// Creates a compact card for accepting/rejecting multiple requests. /// </summary> /// <param name="connectionRequests">The connection requests.</param> /// <param name="doAccept">If true, will create an accept card. If false, will create a reject card.</param> /// <param name="botName">The name of the bot (optional).</param> /// <returns>The newly created card.</returns> public static HeroCard CreateMultiConnectionRequestCard( IList <ConnectionRequest> connectionRequests, bool doAccept, string botName = null) { HeroCard card = new HeroCard() { Title = (doAccept ? Strings.AcceptConnectionRequestsCardTitle : Strings.RejectConnectionRequestCardTitle), Subtitle = (doAccept ? Strings.AcceptConnectionRequestsCardInstructions : Strings.RejectConnectionRequestsCardInstructions), }; card.Buttons = new List <CardAction>(); if (!doAccept && connectionRequests.Count > 1) { card.Buttons.Add(new CardAction() { Title = Strings.RejectAll, Type = ActionTypes.ImBack, Value = new Command(Commands.RejectRequest, new string[] { Command.CommandParameterAll }, botName).ToString() }); } foreach (ConnectionRequest connectionRequest in connectionRequests) { ChannelAccount requestorChannelAccount = RoutingDataManager.GetChannelAccount(connectionRequest.Requestor, out bool isBot); if (requestorChannelAccount == null) { throw new ArgumentNullException("The channel account of the requestor is null"); } string requestorChannelAccountName = string.IsNullOrEmpty(requestorChannelAccount.Name) ? StringConstants.NoUserNamePlaceholder : requestorChannelAccount.Name; string requestorChannelId = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(connectionRequest.Requestor.ChannelId); string requestorChannelAccountId = requestorChannelAccount.Id; Command command = Command.CreateAcceptOrRejectConnectionRequestCommand(connectionRequest, doAccept, botName); card.Buttons.Add(new CardAction() { Title = string.Format( Strings.RequestorDetailsItem, requestorChannelAccountName, requestorChannelId, requestorChannelAccountId), Type = ActionTypes.ImBack, Value = command.ToString() }); } return(card); }
/// <summary> /// Constructor. /// </summary> /// <param name="routingDataStore">The routing data store implementation.</param> /// <param name="microsoftAppCredentials">The bot application credentials. /// May be required, depending on the setup of your app, for sending messages.</param> /// <param name="globalTimeProvider">The global time provider for providing the current /// <param name="ILogger">Logger to use. Defaults to DebugLogger.</param> /// time for various events such as when a connection is requested.</param> public MessageRouter( IRoutingDataStore routingDataStore, MicrosoftAppCredentials microsoftAppCredentials, GlobalTimeProvider globalTimeProvider = null, ILogger logger = null) { Logger = logger ?? new DebugLogger(); RoutingDataManager = new RoutingDataManager(routingDataStore, globalTimeProvider, Logger); _microsoftAppCredentials = microsoftAppCredentials; }
/// <summary> /// Tries to initiates the engagement by creating a request on behalf of the sender in the /// given activity. This method does nothing, if a request for the same user already exists. /// </summary> /// <param name="activity">The activity.</param> /// <returns>The result of the operation.</returns> public async Task <MessageRouterResult> InitiateEngagementAsync(Activity activity) { MessageRouterResult messageRouterResult = RoutingDataManager.AddPendingRequest(MessagingUtils.CreateSenderParty(activity)); messageRouterResult.Activity = activity; await HandleAndLogMessageRouterResultAsync(messageRouterResult); return(messageRouterResult); }
/// <summary> /// Removes the given party from the routing data. /// </summary> /// <param name="partyToRemove">The party to remove.</param> /// <returns>True, if the party was removed. False otherwise.</returns> public async Task <bool> RemovePartyAsync(Party partyToRemove) { IList <MessageRouterResult> messageRouterResults = RoutingDataManager.RemoveParty(partyToRemove); foreach (MessageRouterResult messageRouterResult in messageRouterResults) { await HandleAndLogMessageRouterResultAsync(messageRouterResult); } return(messageRouterResults.Count > 0); }
public static Command CreateAcceptOrRejectConnectionRequestCommand( ConnectionRequest connectionRequest, bool doAccept, string botName = null) { ChannelAccount requestorChannelAccount = RoutingDataManager.GetChannelAccount(connectionRequest.Requestor); return(new Command( doAccept ? Commands.AcceptRequest : Commands.RejectRequest, new string[] { requestorChannelAccount?.Id, connectionRequest.Requestor.Conversation?.Id }, botName)); }
/// <summary> /// Checks the given parties and adds them to the collection, if not already there. /// /// Note that this method expects that the recipient is the bot. The sender could also be /// the bot, but that case is checked before adding the sender to the container. /// </summary> /// <param name="senderParty">The sender party (from).</param> /// <param name="recipientParty">The recipient party.</param> public void MakeSurePartiesAreTracked(Party senderParty, Party recipientParty) { // Store the bot identity, if not already stored RoutingDataManager.AddParty(recipientParty, false); // Check that the party who sent the message is not the bot if (!RoutingDataManager.GetBotParties().Contains(senderParty)) { // Store the user party, if not already stored RoutingDataManager.AddParty(senderParty); } }
/// <summary> /// Sends the given message to the given recipient. /// </summary> /// <param name="recipient">The conversation reference of the recipient.</param> /// <param name="messageActivity">The message activity to send.</param> /// <returns>A valid resource response instance, if successful. Null in case of an error.</returns> public virtual async Task <ResourceResponse> SendMessageAsync( ConversationReference recipient, IMessageActivity messageActivity) { if (recipient == null) { Logger.Log("The conversation reference is null"); return(null); } // We need the bot identity in the SAME CHANNEL/CONVERSATION as the RECIPIENT - // Otherwise, the platform (e.g. Slack) will reject the incoming message as it does not // recognize the sender ConversationReference botInstance = RoutingDataManager.FindBotInstanceForRecipient(recipient); if (botInstance == null || botInstance.Bot == null) { Logger.Log("Failed to find the bot instance"); return(null); } messageActivity.From = botInstance.Bot; messageActivity.Recipient = RoutingDataManager.GetChannelAccount(recipient); // Make sure the message activity contains a valid conversation ID if (messageActivity.Conversation == null) { messageActivity.Conversation = recipient.Conversation; } ConnectorClientMessageBundle bundle = new ConnectorClientMessageBundle( recipient.ServiceUrl, messageActivity, _microsoftAppCredentials); ResourceResponse resourceResponse = null; try { resourceResponse = await bundle.ConnectorClient.Conversations.SendToConversationAsync( (Activity)bundle.MessageActivity); } catch (UnauthorizedAccessException e) { Logger.Log($"Failed to send message: {e.Message}"); } catch (Exception e) { Logger.Log($"Failed to send message: {e.Message}"); } return(resourceResponse); }
/// <summary> /// Finds the message log associated with the given user. /// </summary> /// <param name="user">The user whose message log to find.</param> /// <returns>The message log of the user or null, if not found.</returns> public MessageLog GetMessageLog(ConversationReference user) { var messageLogs = GetMessageLogs(); foreach (MessageLog messageLog in messageLogs) { if (RoutingDataManager.Match(user, messageLog.User)) { return(messageLog); } } return(null); }
/// <summary> /// Checks the given activity for back channel messages and handles them, if detected. /// Currently the only back channel message supported is for creating connections /// (establishing 1:1 conversations). /// </summary> /// <param name="activity">The activity to check for back channel messages.</param> /// <returns> /// The result: /// * MessageRouterResultType.Connected: A connection (1:1 conversation) was created /// * MessageRouterResultType.NoActionTaken: No back channel message detected /// * MessageRouterResultType.Error: See the error message for details /// </returns> public MessageRouterResult HandleBackChannelMessage(Activity activity) { MessageRouterResult messageRouterResult = new MessageRouterResult(); if (activity == null || string.IsNullOrEmpty(activity.Text)) { messageRouterResult.Type = MessageRouterResultType.Error; messageRouterResult.ErrorMessage = $"The given activity ({nameof(activity)}) is either null or the message is missing"; } else if (activity.Text.Equals(BackChannelId)) { if (activity.ChannelData == null) { messageRouterResult.Type = MessageRouterResultType.Error; messageRouterResult.ErrorMessage = "No channel data"; } else { // Handle accepted request and start 1:1 conversation Party conversationClientParty = null; try { conversationClientParty = ParsePartyFromChannelData(activity.ChannelData); } catch (Exception e) { messageRouterResult.Type = MessageRouterResultType.Error; messageRouterResult.ErrorMessage = $"Failed to parse the party information from the back channel message: {e.Message}"; } if (conversationClientParty != null) { Party conversationOwnerParty = MessagingUtils.CreateSenderParty(activity); messageRouterResult = RoutingDataManager.ConnectAndClearPendingRequest( conversationOwnerParty, conversationClientParty); messageRouterResult.Activity = activity; } } } else { // No back channel message detected messageRouterResult.Type = MessageRouterResultType.NoActionTaken; } return(messageRouterResult); }
/// <summary> /// Creates a large connection request card. /// </summary> /// <param name="connectionRequest">The connection request.</param> /// <param name="botName">The name of the bot (optional).</param> /// <returns>A newly created request card.</returns> public static HeroCard CreateConnectionRequestCard( ConnectionRequest connectionRequest, string botName = null) { if (connectionRequest == null || connectionRequest.Requestor == null) { throw new ArgumentNullException("The connection request or the conversation reference of the requestor is null"); } ChannelAccount requestorChannelAccount = RoutingDataManager.GetChannelAccount(connectionRequest.Requestor); if (requestorChannelAccount == null) { throw new ArgumentNullException("The channel account of the requestor is null"); } string requestorChannelAccountName = string.IsNullOrEmpty(requestorChannelAccount.Name) ? StringConstants.NoUserNamePlaceholder : requestorChannelAccount.Name; string requestorChannelId = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(connectionRequest.Requestor.ChannelId); Command acceptCommand = Command.CreateAcceptOrRejectConnectionRequestCommand(connectionRequest, true, botName); Command rejectCommand = Command.CreateAcceptOrRejectConnectionRequestCommand(connectionRequest, false, botName); HeroCard card = new HeroCard() { Title = Strings.ConnectionRequestTitle, Subtitle = string.Format(Strings.RequestorDetailsTitle, requestorChannelAccountName, requestorChannelId), Text = string.Format(Strings.AcceptRejectConnectionHint, acceptCommand.ToString(), rejectCommand.ToString()), Buttons = new List <CardAction>() { new CardAction() { Title = Strings.AcceptButtonTitle, Type = ActionTypes.ImBack, Value = acceptCommand.ToString() }, new CardAction() { Title = Strings.RejectButtonTitle, Type = ActionTypes.ImBack, Value = rejectCommand.ToString() } } }; return(card); }
/// <summary> /// Checks the given activity and determines whether the message was addressed directly to /// the bot or not. /// /// Note: Only mentions are inspected at the moment. /// </summary> /// <param name="messageActivity">The message activity.</param> /// <param name="strict">Use false for channels that do not properly support mentions.</param> /// <returns>True, if the message was address directly to the bot. False otherwise.</returns> public bool WasBotAddressedDirectly(IMessageActivity messageActivity, bool strict = true) { bool botWasMentioned = false; if (strict) { Mention[] mentions = messageActivity.GetMentions(); foreach (Mention mention in mentions) { foreach (ConversationReference bot in _messageRouter.RoutingDataManager.GetBotInstances()) { if (mention.Mentioned.Id.Equals(RoutingDataManager.GetChannelAccount(bot).Id)) { botWasMentioned = true; break; } } } } else { // Here we assume the message starts with the bot name, for instance: // // * "@<BOT NAME>..." // * "<BOT NAME>: ..." string botName = messageActivity.Recipient?.Name; string message = messageActivity.Text?.Trim(); if (!string.IsNullOrEmpty(botName) && !string.IsNullOrEmpty(message) && message.Length > botName.Length) { try { message = message.Remove(botName.Length + 1, message.Length - botName.Length - 1); botWasMentioned = message.Contains(botName); } catch (ArgumentOutOfRangeException e) { System.Diagnostics.Debug.WriteLine($"Failed to check if bot was mentioned: {e.Message}"); } } } return(botWasMentioned); }
public bool RemoveConversationReference(ConversationReference conversationReferenceToRemove) { if (conversationReferenceToRemove.User != null) { return((Users as List <ConversationReference>) .RemoveAll(conversationReference => RoutingDataManager.Match(conversationReference, conversationReferenceToRemove)) > 0); } if (conversationReferenceToRemove.Bot != null) { return((BotInstances as List <ConversationReference>) .RemoveAll(conversationReference => RoutingDataManager.Match(conversationReference, conversationReferenceToRemove)) > 0); } return(false); }
/// <summary> /// Tries to reject the connection request of the associated with the given conversation reference. /// </summary> /// <param name="requestorToReject">The conversation reference of the party whose request to reject.</param> /// <param name="rejecter">The conversation reference of the party rejecting the request (optional).</param> /// <returns>The result of the operation: /// - ConnectionRequestResultType.Rejected or /// - ConnectionRequestResultType.Error (see the error message for more details). /// </returns> public virtual ConnectionRequestResult RejectConnectionRequest( ConversationReference requestorToReject, ConversationReference rejecter = null) { if (requestorToReject == null) { throw new ArgumentNullException("The conversation reference instance of the party whose request to reject cannot be null"); } ConnectionRequestResult rejectConnectionRequestResult = null; ConnectionRequest connectionRequest = RoutingDataManager.FindConnectionRequest(requestorToReject); if (connectionRequest != null) { rejectConnectionRequestResult = RoutingDataManager.RemoveConnectionRequest(connectionRequest); rejectConnectionRequestResult.Rejecter = rejecter; } return(rejectConnectionRequestResult); }
/// <summary> /// Tries to send the given message to the given party using this bot on the same channel /// as the party who the message is sent to. /// </summary> /// <param name="partyToMessage">The party to send the message to.</param> /// <param name="messageText">The message content.</param> /// <returns>The ResourceResponse instance or null in case of an error.</returns> public async Task <ResourceResponse> SendMessageToPartyByBotAsync(Party partyToMessage, string messageText) { Party botParty = null; if (partyToMessage != null) { botParty = RoutingDataManager.FindBotPartyByChannelAndConversation( partyToMessage.ChannelId, partyToMessage.ConversationAccount); } if (botParty != null) { MessagingUtils.ConnectorClientAndMessageBundle bundle = MessagingUtils.CreateConnectorClientAndMessageActivity( partyToMessage, messageText, botParty?.ChannelAccount); return(await bundle.connectorClient.Conversations.SendToConversationAsync( (Activity)bundle.messageActivity)); } return(null); }
/// <summary> /// Checks the given activity for back channel messages and handles them, if detected. /// Currently the only back channel message supported is for adding engagements /// (establishing 1:1 conversations). /// </summary> /// <param name="activity">The activity to check for back channel messages.</param> /// <returns>True, if a back channel message was detected and handled. False otherwise.</returns> public async Task <bool> HandleBackChannelMessageAsync(Activity activity) { MessageRouterResult messageRouterResult = new MessageRouterResult(); if (activity == null || string.IsNullOrEmpty(activity.Text)) { messageRouterResult.Type = MessageRouterResultType.Error; messageRouterResult.ErrorMessage = $"The given activity ({nameof(activity)}) is either null or the message is missing"; } else if (activity.Text.StartsWith(BackChannelId)) { if (activity.ChannelData == null) { messageRouterResult.Type = MessageRouterResultType.Error; messageRouterResult.ErrorMessage = "No channel data"; } else { // Handle accepted request and start 1:1 conversation string partyId = ((JObject)activity.ChannelData)[BackChannelId][PartyIdPropertyId].ToString(); Party conversationClientParty = Party.FromIdString(partyId); Party conversationOwnerParty = MessagingUtils.CreateSenderParty(activity); messageRouterResult = RoutingDataManager.AddEngagementAndClearPendingRequest( conversationOwnerParty, conversationClientParty); messageRouterResult.Activity = activity; } } else { // No back channel message detected messageRouterResult.Type = MessageRouterResultType.NoActionTaken; } await HandleAndLogMessageRouterResultAsync(messageRouterResult); return(messageRouterResult.Type == MessageRouterResultType.EngagementAdded); }
/// <summary> /// Tries to resolve the name of the given user/bot instance. /// Will fallback to ID, if no name specified. /// </summary> /// <param name="conversationReference">The conversation reference, whose details to resolve.</param> /// <returns>The name or the ID of the given user/bot instance.</returns> protected virtual string GetNameOrId(ConversationReference conversationReference) { if (conversationReference != null) { ChannelAccount channelAccount = RoutingDataManager.GetChannelAccount(conversationReference); if (channelAccount != null) { if (!string.IsNullOrWhiteSpace(channelAccount.Name)) { return(channelAccount.Name); } if (!string.IsNullOrWhiteSpace(channelAccount.Id)) { return(channelAccount.Id); } } } return(StringConstants.NoNameOrId); }
/// <summary> /// Disconnects all connections associated with the given conversation reference. /// </summary> /// <param name="conversationReference">The conversation reference connected in a conversation.</param> /// <returns>The results: /// - ConnectionResultType.Disconnected, /// - ConnectionResultType.Error (see the error message for more details). /// </returns> public virtual IList <ConnectionResult> Disconnect(ConversationReference conversationReference) { IList <ConnectionResult> disconnectResults = new List <ConnectionResult>(); bool wasDisconnected = true; while (wasDisconnected) { wasDisconnected = false; Connection connection = RoutingDataManager.FindConnection(conversationReference); if (connection != null) { ConnectionResult disconnectResult = RoutingDataManager.Disconnect(connection); disconnectResults.Add(disconnectResult); if (disconnectResult.Type == ConnectionResultType.Disconnected) { wasDisconnected = true; } } } return(disconnectResults); }
/// <summary> /// Handles the given connection request result. /// </summary> /// <param name="connectionRequestResult">The result to handle.</param> /// <returns>True, if the result was handled. False, if no action was taken.</returns> protected virtual async Task <bool> HandleConnectionRequestResultAsync( ConnectionRequestResult connectionRequestResult) { ConnectionRequest connectionRequest = connectionRequestResult?.ConnectionRequest; if (connectionRequest == null || connectionRequest.Requestor == null) { System.Diagnostics.Debug.WriteLine("No client to inform about the connection request result"); return(false); } switch (connectionRequestResult.Type) { case ConnectionRequestResultType.Created: foreach (ConversationReference aggregationChannel in _messageRouter.RoutingDataManager.GetAggregationChannels()) { ConversationReference botConversationReference = _messageRouter.RoutingDataManager.FindConversationReference( aggregationChannel.ChannelId, aggregationChannel.Conversation.Id, null, true); if (botConversationReference != null) { IMessageActivity messageActivity = Activity.CreateMessageActivity(); messageActivity.Conversation = aggregationChannel.Conversation; messageActivity.Recipient = RoutingDataManager.GetChannelAccount(aggregationChannel); messageActivity.Attachments = new List <Attachment> { CommandCardFactory.CreateConnectionRequestCard( connectionRequest, RoutingDataManager.GetChannelAccount( botConversationReference)?.Name).ToAttachment() }; await _messageRouter.SendMessageAsync(aggregationChannel, messageActivity); } } await _messageRouter.SendMessageAsync( connectionRequest.Requestor, Strings.NotifyClientWaitForRequestHandling); return(true); case ConnectionRequestResultType.AlreadyExists: await _messageRouter.SendMessageAsync( connectionRequest.Requestor, Strings.NotifyClientDuplicateRequest); return(true); case ConnectionRequestResultType.Rejected: if (connectionRequestResult.Rejecter != null) { await _messageRouter.SendMessageAsync( connectionRequestResult.Rejecter, string.Format(Strings.NotifyOwnerRequestRejected, GetNameOrId(connectionRequest.Requestor))); } await _messageRouter.SendMessageAsync( connectionRequest.Requestor, Strings.NotifyClientRequestRejected); return(true); case ConnectionRequestResultType.NotSetup: await _messageRouter.SendMessageAsync( connectionRequest.Requestor, Strings.NoAgentsAvailable); return(true); case ConnectionRequestResultType.Error: if (connectionRequestResult.Rejecter != null) { await _messageRouter.SendMessageAsync( connectionRequestResult.Rejecter, string.Format(Strings.ConnectionRequestResultErrorWithResult, connectionRequestResult.ErrorMessage)); } return(true); default: break; } return(false); }
public bool RemoveAggregationChannel(ConversationReference aggregationConversationReferenceToRemove) { return((AggregationChannels as List <ConversationReference>) .RemoveAll(conversationReference => RoutingDataManager.Match(conversationReference, aggregationConversationReferenceToRemove)) > 0); }