public string RefreshAgent(int id) { string response = ResponseNone; MessageRouterManager messageRouterManager = WebApiConfig.MessageRouterManager; IRoutingDataManager routingDataManager = messageRouterManager.RoutingDataManager; var Settings = new Settings.BotSettings(); Manager manager = new Manager(Settings[BotSettings.KeyRoutingDataStorageConnectionString]); try { Dictionary <Party, Party> connectedParties = routingDataManager.GetConnectedParties(); Dictionary <Party, Party> waitingConnectedParties = manager.GetWaitingConnectedParties(); foreach (var connectedPartie in connectedParties) { clientsParties.Add(connectedPartie.Value); } foreach (var waitingConnectedPartie in waitingConnectedParties) { clientsParties.Add(waitingConnectedPartie.Value); } response = JsonConvert.SerializeObject(clientsParties); } catch (InvalidOperationException e) { Debug.WriteLine($"{e.Message}"); } Debug.WriteLine("refresh"); return(response); }
/// <summary> /// Constructor. /// </summary> /// <param name="routingDataManager">The routing data manager instance.</param> /// <param name="backchannelId">The ID for back channel messages. If null, the default value is used.</param> /// <param name="partyKey">The key identifying the serialized party data. If null, the default value is used.</param> public BackChannelMessageHandler(IRoutingDataManager routingDataManager, string backChannelId = null, string partyKey = null) { _routingDataManager = routingDataManager ?? throw new ArgumentNullException("Routing data manager instance must be given"); BackChannelId = string.IsNullOrEmpty(backChannelId) ? DefaultBackChannelId : backChannelId; PartyKey = string.IsNullOrEmpty(partyKey) ? DefaultPartyKey : partyKey; }
/// <summary> /// Creates and sets up the instances required for message routing. /// </summary> public static void InitializeMessageRouting() { Settings = new BotSettings(); string connectionString = Settings[BotSettings.KeyRoutingDataStorageConnectionString]; IRoutingDataManager routingDataManager = null; if (string.IsNullOrEmpty(connectionString)) { System.Diagnostics.Debug.WriteLine($"WARNING!!! No connection string found - using {nameof(LocalRoutingDataManager)}"); routingDataManager = new LocalRoutingDataManager(); } else { System.Diagnostics.Debug.WriteLine($"Found a connection string - using {nameof(AzureTableStorageRoutingDataManager)}"); routingDataManager = new AzureTableStorageRoutingDataManager(connectionString); } MessageRouterManager = new MessageRouterManager(routingDataManager); MessageRouterResultHandler = new MessageRouterResultHandler(MessageRouterManager); CommandMessageHandler = new CommandMessageHandler(MessageRouterManager, MessageRouterResultHandler); BackChannelMessageHandler = new BackChannelMessageHandler(MessageRouterManager.RoutingDataManager); }
public string GetAgentById(int id) { string response = ResponseNone; MessageRouterManager messageRouterManager = WebApiConfig.MessageRouterManager; IRoutingDataManager routingDataManager = messageRouterManager.RoutingDataManager; if (routingDataManager.GetAggregationParties().Count == 0 && routingDataManager.GetPendingRequests().Count > 0) { try { Party conversationClientParty = messageRouterManager.RoutingDataManager.GetPendingRequests().First(); messageRouterManager.RoutingDataManager.RemovePendingRequest(conversationClientParty); response = conversationClientParty.ToJsonString(); } catch (InvalidOperationException e) { System.Diagnostics.Debug.WriteLine($"Failed to handle a pending request: {e.Message}"); } } return(response); }
/// <summary> /// Checks the given activity for a possible command. /// /// All messages that start with a specific command keyword or contain a mention of the bot /// ("@<bot name>") are checked for possible commands. /// </summary> /// <param name="activity">An Activity instance containing a possible command.</param> /// <returns>True, if a command was detected and handled. False otherwise.</returns> public async virtual Task <bool> HandleCommandAsync(Activity activity) { bool wasHandled = false; Activity replyActivity = null; Command command = ExtractCommand(activity); if (command != null) { Party senderParty = MessagingUtils.CreateSenderParty(activity); switch (command.BaseCommand.ToLower()) { case string baseCommand when(baseCommand.Equals(Commands.CommandListOptions)): // Present all command options in a card replyActivity = CommandCardFactory.AddCardToActivity( activity.CreateReply(), CommandCardFactory.CreateCommandOptionsCard(activity.Recipient?.Name)); wasHandled = true; break; case string baseCommand when(baseCommand.Equals(Commands.CommandAddAggregationChannel)): // Establish the sender's channel/conversation as an aggreated one if not already exists Party aggregationPartyToAdd = new Party(activity.ServiceUrl, activity.ChannelId, null, activity.Conversation); if (_messageRouterManager.RoutingDataManager.AddAggregationParty(aggregationPartyToAdd)) { replyActivity = activity.CreateReply(ConversationText.AggregationChannelSet); } else { replyActivity = activity.CreateReply(ConversationText.AggregationChannelAlreadySet); } wasHandled = true; break; case string baseCommand when(baseCommand.Equals(Commands.CommandRemoveAggregationChannel)): // Remove the sender's channel/conversation from the list of aggregation channels if (_messageRouterManager.RoutingDataManager.IsAssociatedWithAggregation(senderParty)) { Party aggregationPartyToRemove = new Party(activity.ServiceUrl, activity.ChannelId, null, activity.Conversation); if (_messageRouterManager.RoutingDataManager.RemoveAggregationParty(aggregationPartyToRemove)) { replyActivity = activity.CreateReply(ConversationText.AggregationChannelRemoved); } else { replyActivity = activity.CreateReply(ConversationText.FailedToRemoveAggregationChannel); } wasHandled = true; } break; case string baseCommand when(baseCommand.Equals(Commands.CommandAcceptRequest) || baseCommand.Equals(Commands.CommandRejectRequest)): // Accept/reject conversation request bool doAccept = baseCommand.Equals(Commands.CommandAcceptRequest); if (_messageRouterManager.RoutingDataManager.IsAssociatedWithAggregation(senderParty)) { // The party is associated with the aggregation and has the right to accept/reject if (command.Parameters.Count == 0) { replyActivity = activity.CreateReply(); IList <Party> pendingRequests = _messageRouterManager.RoutingDataManager.GetPendingRequests(); if (pendingRequests.Count == 0) { replyActivity.Text = ConversationText.NoPendingRequests; } else { replyActivity = CommandCardFactory.AddCardToActivity( replyActivity, CommandCardFactory.CreateAcceptOrRejectCardForMultipleRequests( pendingRequests, doAccept, activity.Recipient?.Name)); } } else if (!doAccept && command.Parameters[0].Equals(Commands.CommandParameterAll)) { if (!await new MessageRoutingUtils().RejectAllPendingRequestsAsync( _messageRouterManager, _messageRouterResultHandler)) { replyActivity = activity.CreateReply(); replyActivity.Text = ConversationText.FailedToRejectPendingRequests; } } else { string errorMessage = await new MessageRoutingUtils().AcceptOrRejectRequestAsync( _messageRouterManager, _messageRouterResultHandler, senderParty, doAccept, command.Parameters[0]); if (!string.IsNullOrEmpty(errorMessage)) { replyActivity = activity.CreateReply(); replyActivity.Text = errorMessage; } } } #if DEBUG // We shouldn't respond to command attempts by regular users, but I guess // it's okay when debugging else { replyActivity = activity.CreateReply(ConversationText.ConnectionRequestResponseNotAllowed); } #endif wasHandled = true; break; case string baseCommand when(baseCommand.Equals(Commands.CommandDisconnect)): // End the 1:1 conversation IList <MessageRouterResult> messageRouterResults = _messageRouterManager.Disconnect(senderParty); foreach (MessageRouterResult messageRouterResult in messageRouterResults) { await _messageRouterResultHandler.HandleResultAsync(messageRouterResult); } wasHandled = true; break; #region Implementation of debugging commands #if DEBUG case string baseCommand when(baseCommand.Equals(Commands.CommandDeleteAllRoutingData)): // DELETE ALL ROUTING DATA replyActivity = activity.CreateReply(ConversationText.DeletingAllData); _messageRouterManager.RoutingDataManager.DeleteAll(); wasHandled = true; break; case string baseCommand when(baseCommand.Equals(Commands.CommandList)): bool listAll = command.Parameters.Contains(Commands.CommandParameterAll); replyActivity = activity.CreateReply(); string replyMessageText = string.Empty; if (listAll || command.Parameters.Contains(Commands.CommandParameterParties)) { // List user and bot parties IRoutingDataManager routingDataManager = _messageRouterManager.RoutingDataManager; string partiesAsString = PartyListToString(routingDataManager.GetUserParties()); replyMessageText += string.IsNullOrEmpty(partiesAsString) ? $"{ConversationText.NoUsersStored}{StringAndCharConstants.LineBreak}" : $"{ConversationText.Users}:{StringAndCharConstants.LineBreak}{partiesAsString}{StringAndCharConstants.LineBreak}"; partiesAsString = PartyListToString(routingDataManager.GetBotParties()); replyMessageText += string.IsNullOrEmpty(partiesAsString) ? $"{ConversationText.NoBotsStored}{StringAndCharConstants.LineBreak}" : $"{ConversationText.Bots}:{StringAndCharConstants.LineBreak}{partiesAsString}{StringAndCharConstants.LineBreak}"; wasHandled = true; } if (listAll || command.Parameters.Contains(Commands.CommandParameterRequests)) { // List all pending requests IList <Attachment> attachments = CommandCardFactory.CreateMultipleRequestCards( _messageRouterManager.RoutingDataManager.GetPendingRequests(), activity.Recipient?.Name); if (attachments.Count > 0) { replyMessageText += string.Format(ConversationText.PendingRequestsFoundWithCount, attachments.Count); replyMessageText += StringAndCharConstants.LineBreak; replyActivity.AttachmentLayout = AttachmentLayoutTypes.Carousel; replyActivity.Attachments = attachments; } else { replyMessageText += $"{ConversationText.NoPendingRequests}{StringAndCharConstants.LineBreak}"; } wasHandled = true; } if (listAll || command.Parameters.Contains(Commands.CommandParameterConnections)) { // List all connections (conversations) string connectionsAsString = _messageRouterManager.RoutingDataManager.ConnectionsToString(); replyMessageText += string.IsNullOrEmpty(connectionsAsString) ? $"{ConversationText.NoConversations}{StringAndCharConstants.LineBreak}" : $"{connectionsAsString}{StringAndCharConstants.LineBreak}"; wasHandled = true; } if (listAll || command.Parameters.Contains(Commands.CommandParameterResults)) { // List all logged message router results string resultsAsString = _messageRouterManager.RoutingDataManager.GetLastMessageRouterResults(); replyMessageText += string.IsNullOrEmpty(resultsAsString) ? $"{ConversationText.NoResults}{StringAndCharConstants.LineBreak}" : $"{resultsAsString}{StringAndCharConstants.LineBreak}"; wasHandled = true; } if (!wasHandled) { replyMessageText = ConversationText.InvalidOrMissingCommandParameter; } replyActivity.Text = replyMessageText; break; #endif #endregion default: replyActivity = activity.CreateReply(string.Format(ConversationText.CommandNotRecognized, command.BaseCommand)); break; } if (replyActivity != null) { ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl)); await connector.Conversations.ReplyToActivityAsync(replyActivity); } } return(wasHandled); }
/// <summary> /// Constructor. /// </summary> /// <param name="routingDataManager">The routing data manager.</param> public MessageRouterManager(IRoutingDataManager routingDataManager) { RoutingDataManager = routingDataManager; BackChannelId = DefaultBackChannelId; PartyPropertyId = DefaultPartyPropertyId; }
/// <summary> /// Constructor. /// </summary> /// <param name="routingDataManager">The routing data manager.</param> public MessageRouterManager(IRoutingDataManager routingDataManager) { RoutingDataManager = routingDataManager; }
/// <summary> /// Constructor. /// </summary> /// <param name="routingDataManager">The routing data manager.</param> public DefaultBotCommandHandler(IRoutingDataManager routingDataManager) { _routingDataManager = routingDataManager; }
/// <summary> /// Notifies both the conversation owner (agent) and the conversation client (customer) /// about the connection status change. /// </summary> /// <param name="messageRouterResult">The result to handle.</param> protected virtual async Task HandleConnectionChangedResultAsync(MessageRouterResult messageRouterResult) { IRoutingDataManager routingDataManager = _messageRouterManager.RoutingDataManager; Party conversationOwnerParty = messageRouterResult.ConversationOwnerParty; Party conversationClientParty = messageRouterResult.ConversationClientParty; string conversationOwnerName = string.IsNullOrEmpty(conversationOwnerParty?.ChannelAccount.Name) ? StringAndCharConstants.NoUserNamePlaceholder : conversationOwnerParty?.ChannelAccount.Name; string conversationClientName = string.IsNullOrEmpty(conversationClientParty?.ChannelAccount.Name) ? StringAndCharConstants.NoUserNamePlaceholder : conversationClientParty?.ChannelAccount.Name; string messageToConversationOwner = string.Empty; string messageToConversationClient = string.Empty; if (messageRouterResult.Type == MessageRouterResultType.ConnectionRequested) { bool conversationClientPartyMissing = (conversationClientParty == null || conversationClientParty.ChannelAccount == null); foreach (Party aggregationParty in _messageRouterManager.RoutingDataManager.GetAggregationParties()) { Party botParty = routingDataManager.FindBotPartyByChannelAndConversation( aggregationParty.ChannelId, aggregationParty.ConversationAccount); if (botParty != null) { if (conversationClientPartyMissing) { await _messageRouterManager.SendMessageToPartyByBotAsync( aggregationParty, ConversationText.RequestorDetailsMissing); } else { IMessageActivity messageActivity = Activity.CreateMessageActivity(); messageActivity.Conversation = aggregationParty.ConversationAccount; messageActivity.Recipient = aggregationParty.ChannelAccount; messageActivity.Attachments = new List <Attachment> { CommandCardFactory.CreateRequestCard( conversationClientParty, botParty.ChannelAccount?.Name).ToAttachment() }; await _messageRouterManager.SendMessageToPartyByBotAsync( aggregationParty, messageActivity); } } } if (!conversationClientPartyMissing) { messageToConversationClient = ConversationText.NotifyClientWaitForRequestHandling; } } else if (messageRouterResult.Type == MessageRouterResultType.ConnectionAlreadyRequested) { messageToConversationClient = ConversationText.NotifyClientDuplicateRequest; } else if (messageRouterResult.Type == MessageRouterResultType.ConnectionRejected) { messageToConversationOwner = string.Format(ConversationText.NotifyOwnerRequestRejected, conversationClientName); messageToConversationClient = ConversationText.NotifyClientRequestRejected; } else if (messageRouterResult.Type == MessageRouterResultType.Connected) { messageToConversationOwner = string.Format(ConversationText.NotifyOwnerConnected, conversationClientName); messageToConversationClient = string.Format(ConversationText.NotifyClientConnected, conversationOwnerName); } else if (messageRouterResult.Type == MessageRouterResultType.Disconnected) { messageToConversationOwner = string.Format(ConversationText.NotifyOwnerDisconnected, conversationClientName); messageToConversationClient = string.Format(ConversationText.NotifyClientDisconnected, conversationOwnerName); } if (conversationOwnerParty != null && !string.IsNullOrEmpty(messageToConversationOwner)) { await _messageRouterManager.SendMessageToPartyByBotAsync( conversationOwnerParty, messageToConversationOwner); } if (conversationClientParty != null && !string.IsNullOrEmpty(messageToConversationClient)) { await _messageRouterManager.SendMessageToPartyByBotAsync( conversationClientParty, messageToConversationClient); } }
/// <summary> /// Tries to accept/reject a pending request. /// </summary> /// <param name="messageRouterManager">The message router manager.</param> /// <param name="messageRouterResultHandler">The message router result handler.</param> /// <param name="senderParty">The sender party (accepter/rejecter).</param> /// <param name="doAccept">If true, will try to accept the request. If false, will reject.</param> /// <param name="channelAccountIdOfPartyToAcceptOrReject">The channel account ID of the party whose request to accep/reject.</param> /// <returns>Null, if an accept/reject operation was executed successfully. /// A user friendly error message otherwise.</returns> public async Task <string> AcceptOrRejectRequestAsync( MessageRouterManager messageRouterManager, MessageRouterResultHandler messageRouterResultHandler, Party senderParty, bool doAccept, string channelAccountIdOfPartyToAcceptOrReject) { string errorMessage = null; IRoutingDataManager routingDataManager = messageRouterManager.RoutingDataManager; Party partyToAcceptOrReject = null; if (routingDataManager.GetPendingRequests().Count > 0) { try { partyToAcceptOrReject = routingDataManager.GetPendingRequests().Single( party => (party.ChannelAccount != null && !string.IsNullOrEmpty(party.ChannelAccount.Id) && party.ChannelAccount.Id.Equals(channelAccountIdOfPartyToAcceptOrReject))); } catch (InvalidOperationException e) { errorMessage = string.Format( ConversationText.FailedToFindPendingRequestForUserWithErrorMessage, channelAccountIdOfPartyToAcceptOrReject, e.Message); } } if (partyToAcceptOrReject != null) { Party connectedSenderParty = routingDataManager.FindConnectedPartyByChannel( senderParty.ChannelId, senderParty.ChannelAccount); bool senderIsConnected = (connectedSenderParty != null && routingDataManager.IsConnected(connectedSenderParty, ConnectionProfile.Owner)); MessageRouterResult messageRouterResult = null; if (doAccept) { if (senderIsConnected) { // The sender (accepter/rejecter) is ALREADY connected with another party Party otherParty = routingDataManager.GetConnectedCounterpart(connectedSenderParty); if (otherParty != null) { errorMessage = string.Format( ConversationText.AlreadyConnectedWithUser, otherParty.ChannelAccount?.Name); } else { errorMessage = ConversationText.ErrorOccured; } } else { bool createNewDirectConversation = !(NoDirectConversationsWithChannels.Contains(senderParty.ChannelId.ToLower())); // Try to accept messageRouterResult = await messageRouterManager.ConnectAsync( senderParty, partyToAcceptOrReject, createNewDirectConversation); } } else { // Note: Rejecting is OK even if the sender is alreay connected messageRouterResult = messageRouterManager.RejectPendingRequest(partyToAcceptOrReject, senderParty); } if (messageRouterResult != null) { await messageRouterResultHandler.HandleResultAsync(messageRouterResult); } } else { errorMessage = ConversationText.FailedToFindPendingRequest; } return(errorMessage); }
/// <summary> /// Constructor. /// </summary> private MessageRouterManager() { // TODO: Get this instance from a database instead of keeping a local copy! RoutingDataManager = new LocalRoutingDataManager(); }
/// <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 virtual MessageRouterResult HandleBackChannelMessage(Activity activity) { MessageRouterResult messageRouterResultNoAction = new MessageRouterResult { Type = MessageRouterResultType.NoActionTaken }; MessageRouterResult messageRouterResult = new MessageRouterResult(); var Settings = new BotSettings(); Manager manager = new Manager(Settings[BotSettings.KeyRoutingDataStorageConnectionString]); 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); Debug.WriteLine($"Client : {JsonConvert.SerializeObject(conversationClientParty)}"); } 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); Debug.WriteLine($"Owner : {JsonConvert.SerializeObject(conversationOwnerParty)}"); MessageRouterManager messageRouterManager = WebApiConfig.MessageRouterManager; IRoutingDataManager routingDataManager = messageRouterManager.RoutingDataManager; bool isConnected = false; Dictionary <Party, Party> connectedParties = routingDataManager.GetConnectedParties(); foreach (var connectedPartie in connectedParties) { if (connectedPartie.Value.ConversationAccount.Id == conversationClientParty.ConversationAccount.Id) { isConnected = true; break; } } if (isConnected) { bool deleteConnexion = manager.ExecuteRemoveConnexionByConversationClientId(conversationClientParty.ConversationAccount.Id); messageRouterResult = deleteConnexion ? manager.Connect(conversationOwnerParty, conversationClientParty) : messageRouterResultNoAction; } else { Dictionary <Party, Party> waitingConnectedParties = manager.GetWaitingConnectedParties(); bool isWaitingConnected = false; foreach (var waitingConnectedPartie in waitingConnectedParties) { if (waitingConnectedPartie.Value.ConversationAccount.Id == conversationClientParty.ConversationAccount.Id) { isWaitingConnected = true; break; } } if (isWaitingConnected) { bool deleteWaitingConnection = manager.ExecuteRemoveWaitingConnexionByConversationClientId(conversationClientParty.ConversationAccount.Id); messageRouterResult = deleteWaitingConnection ? manager.WaitingConnectAndClearPendingRequest(conversationOwnerParty, conversationClientParty) : messageRouterResultNoAction; messageRouterResult.Activity = activity; } else { messageRouterResult = manager.WaitingConnectAndClearPendingRequest(conversationOwnerParty, conversationClientParty); //messageRouterResult = _routingDataManager.ConnectAndClearPendingRequest( // conversationOwnerParty, conversationClientParty); messageRouterResult.Activity = activity; } } } } } else { // No back channel message detected //messageRouterResult.Type = MessageRouterResultType.NoActionTaken; ConnectionEntity waitingConnection = manager.RetrieveWaitingConnectionByConversationIdOwner(activity.Conversation.Id); if (waitingConnection != null) { Party conversationOwnerParty = JsonConvert.DeserializeObject <PartyEntity>(waitingConnection.Owner).ToParty(); Party conversationClientParty = JsonConvert.DeserializeObject <PartyEntity>(waitingConnection.Client).ToParty(); if (activity.Text == $"@{conversationOwnerParty.ChannelAccount.Name} accept {conversationClientParty.ChannelAccount.Id}" || activity.Text == $"@{conversationOwnerParty.ChannelAccount.Name} reject {conversationClientParty.ChannelAccount.Id}") { if (activity.Text.Contains("accept")) { messageRouterResult = manager.Connect(conversationOwnerParty, conversationClientParty); } else { messageRouterResult.Type = MessageRouterResultType.ConnectionRejected; messageRouterResult.ConversationClientParty = conversationClientParty; messageRouterResult.ConversationOwnerParty = conversationOwnerParty; } manager.RemoveWaitingConnection(conversationOwnerParty, conversationClientParty); } else { messageRouterResult.Type = MessageRouterResultType.ConnectionAlreadyRequested; if (activity.Conversation.Id == conversationOwnerParty.ConversationAccount.Id) { messageRouterResult.ConversationOwnerParty = conversationOwnerParty; messageRouterResult.ConversationClientParty = conversationClientParty; } else { messageRouterResult.ConversationClientParty = conversationClientParty; } } } else { // No back channel message detected messageRouterResult.Type = MessageRouterResultType.NoActionTaken; } } return(messageRouterResult); }
/// <summary> /// Tries to accept/reject a pending request. /// </summary> /// <param name="senderParty">The sender party (accepter/rejecter).</param> /// <param name="commandMessage">The command message. Required for resolving the party to accept/reject.</param> /// <param name="doAccept">If true, will try to accept the request. If false, will reject.</param> /// <returns>Null, if successful. A user friendly error message otherwise.</returns> private async Task <string> AcceptOrRejectRequestAsync(Party senderParty, string commandMessage, bool doAccept) { string errorMessage = null; IRoutingDataManager routingDataManager = _messageRouterManager.RoutingDataManager; Party connectedSenderParty = routingDataManager.FindConnectedPartyByChannel(senderParty.ChannelId, senderParty.ChannelAccount); if (connectedSenderParty == null || !routingDataManager.IsConnected(senderParty, ConnectionProfile.Owner)) { // The sender (accepter/rejecter) is NOT connected with another party if (routingDataManager.GetPendingRequests().Count > 0) { // The name of the user to accept should be the second word string[] splitMessage = commandMessage.Split(' '); if (splitMessage.Count() > 1 && !string.IsNullOrEmpty(splitMessage[1])) { Party partyToAcceptOrReject = null; try { partyToAcceptOrReject = routingDataManager.GetPendingRequests().Single( party => (party.ChannelAccount != null && !string.IsNullOrEmpty(party.ChannelAccount.Id) && party.ChannelAccount.Id.Equals(splitMessage[1]))); } catch (InvalidOperationException e) { errorMessage = $"Failed to find a pending request for user \"{splitMessage[1]}\": {e.Message}"; } if (partyToAcceptOrReject != null) { MessageRouterResult messageRouterResult = null; if (doAccept) { messageRouterResult = await _messageRouterManager.ConnectAsync( senderParty, partyToAcceptOrReject, !partyToAcceptOrReject.ChannelId.Contains("skype")); } else { messageRouterResult = _messageRouterManager.RejectPendingRequest(partyToAcceptOrReject, senderParty); } await _messageRouterResultHandler.HandleResultAsync(messageRouterResult); } } else { errorMessage = "User name missing"; } } else { errorMessage = "No pending requests"; } } else { // The sender (accepter/rejecter) is ALREADY connected with another party Party otherParty = routingDataManager.GetConnectedCounterpart(connectedSenderParty); if (otherParty != null) { errorMessage = $"You are already connected with user \"{otherParty.ChannelAccount.Name}\""; } else { errorMessage = "An error occured"; } } return(errorMessage); }
/// <summary> /// Checks the given activity for a possible command. /// /// All messages that start with a specific command keyword or contain a mention of the bot /// ("@<bot name>") are checked for possible commands. /// </summary> /// <param name="activity">An Activity instance containing a possible command.</param> /// <returns>True, if a command was detected and handled. False otherwise.</returns> public async virtual Task <bool> HandleCommandAsync(Activity activity) { bool wasHandled = false; Activity replyActivity = null; if ((!string.IsNullOrEmpty(activity.Text) && activity.Text.StartsWith($"{Commands.CommandKeyword} ")) || WasBotAddressedDirectly(activity, false)) { string commandMessage = ExtractCleanCommandMessage(activity); Party senderParty = MessagingUtils.CreateSenderParty(activity); switch (commandMessage.ToLower()) { case string command when(command.StartsWith(Commands.CommandListOptions)): // Present all command options in a card replyActivity = CreateCommandOptionsCard(activity); wasHandled = true; break; case string command when(command.StartsWith(Commands.CommandAddAggregationChannel)): // Establish the sender's channel/conversation as an aggreated one if not already exists Party aggregationParty = new Party(activity.ServiceUrl, activity.ChannelId, null, activity.Conversation); if (_messageRouterManager.RoutingDataManager.AddAggregationParty(aggregationParty)) { replyActivity = activity.CreateReply("This channel/conversation is now where the requests are aggregated"); } else { replyActivity = activity.CreateReply("This channel/conversation is already receiving requests"); } wasHandled = true; break; case string command when(command.StartsWith(Commands.CommandAcceptRequest) || command.StartsWith(Commands.CommandRejectRequest)): // Accept/reject conversation request bool doAccept = command.StartsWith(Commands.CommandAcceptRequest); if (_messageRouterManager.RoutingDataManager.IsAssociatedWithAggregation(senderParty)) { // The party is associated with the aggregation and has the right to accept/reject string errorMessage = await AcceptOrRejectRequestAsync(senderParty, commandMessage, doAccept); if (!string.IsNullOrEmpty(errorMessage)) { replyActivity = activity.CreateReply(errorMessage); } } else { replyActivity = activity.CreateReply("Sorry, you are not allowed to accept/reject requests"); } wasHandled = true; break; case string command when(command.StartsWith(Commands.CommandDisconnect)): // End the 1:1 conversation IList <MessageRouterResult> messageRouterResults = _messageRouterManager.Disconnect(senderParty); foreach (MessageRouterResult messageRouterResult in messageRouterResults) { await _messageRouterResultHandler.HandleResultAsync(messageRouterResult); } wasHandled = true; break; #region Implementation of debugging commands #if DEBUG case string command when(command.StartsWith(Commands.CommandDeleteAllRoutingData)): // DELETE ALL ROUTING DATA await _messageRouterManager.BroadcastMessageToAggregationChannelsAsync( $"Deleting all data as requested by \"{senderParty.ChannelAccount?.Name}\"..."); replyActivity = activity.CreateReply("Deleting all data..."); _messageRouterManager.RoutingDataManager.DeleteAll(); wasHandled = true; break; case string command when(command.StartsWith(Commands.CommandListAllParties)): // List user and bot parties IRoutingDataManager routingDataManager = _messageRouterManager.RoutingDataManager; string replyMessage = string.Empty; string partiesAsString = PartyListToString(routingDataManager.GetUserParties()); if (string.IsNullOrEmpty(partiesAsString)) { replyMessage = $"No user parties{LineBreak}"; } else { replyMessage = $"Users:{LineBreak}{partiesAsString}"; } partiesAsString = PartyListToString(routingDataManager.GetBotParties()); if (string.IsNullOrEmpty(partiesAsString)) { replyMessage += "No bot parties"; } else { replyMessage += $"Bot:{LineBreak}{partiesAsString}"; } replyActivity = activity.CreateReply(replyMessage); wasHandled = true; break; case string command when(command.StartsWith(Commands.CommandListPendingRequests)): // List all pending requests var attachments = new List <Attachment>(); foreach (Party party in _messageRouterManager.RoutingDataManager.GetPendingRequests()) { attachments.Add(CreateRequestCard(party, activity.Recipient.Name)); } if (attachments.Count > 0) { replyActivity = activity.CreateReply($"{attachments.Count} pending request(s) found:"); replyActivity.AttachmentLayout = AttachmentLayoutTypes.Carousel; replyActivity.Attachments = attachments; } else { replyActivity = activity.CreateReply("No pending requests"); } wasHandled = true; break; case string command when(command.StartsWith(Commands.CommandListConnections)): // List all connections (conversations) string parties = _messageRouterManager.RoutingDataManager.ConnectionsToString(); if (string.IsNullOrEmpty(parties)) { replyActivity = activity.CreateReply("No conversations"); } else { replyActivity = activity.CreateReply(parties); } wasHandled = true; break; case string command when(command.StartsWith(Commands.CommandListLastMessageRouterResults)): // List all logged message router results string resultsAsString = _messageRouterManager.RoutingDataManager.GetLastMessageRouterResults(); replyActivity = activity.CreateReply($"{(string.IsNullOrEmpty(resultsAsString) ? "No results" : resultsAsString)}"); wasHandled = true; break; #endif #endregion default: replyActivity = activity.CreateReply($"Command \"{commandMessage}\" not recognized"); break; } if (replyActivity != null) { ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl)); await connector.Conversations.ReplyToActivityAsync(replyActivity); } } return(wasHandled); }
/// <summary> /// Notifies both the conversation owner (agent) and the conversation client (customer) /// about the connection status change. /// </summary> /// <param name="messageRouterResult">The result to handle.</param> protected virtual async Task HandleConnectionChangedResultAsync(MessageRouterResult messageRouterResult) { IRoutingDataManager routingDataManager = _messageRouterManager.RoutingDataManager; Party conversationOwnerParty = messageRouterResult.ConversationOwnerParty; Party conversationClientParty = messageRouterResult.ConversationClientParty; string conversationOwnerName = conversationOwnerParty?.ChannelAccount.Name; string conversationClientName = conversationClientParty?.ChannelAccount.Name; string messageToConversationOwner = string.Empty; string messageToConversationClient = string.Empty; if (messageRouterResult.Type == MessageRouterResultType.ConnectionRequested) { if (conversationClientParty == null || conversationClientParty.ChannelAccount == null) { await _messageRouterManager.BroadcastMessageToAggregationChannelsAsync( ConversationText.ConnectionRequestMadeButRequestorIsNull); throw new NullReferenceException(ConversationText.ConnectionRequestMadeButRequestorIsNull); } foreach (Party aggregationParty in _messageRouterManager.RoutingDataManager.GetAggregationParties()) { Party botParty = routingDataManager .FindBotPartyByChannelAndConversation(aggregationParty.ChannelId, aggregationParty.ConversationAccount); if (botParty != null) { IMessageActivity messageActivity = Activity.CreateMessageActivity(); messageActivity.Conversation = aggregationParty.ConversationAccount; messageActivity.Recipient = aggregationParty.ChannelAccount; messageActivity.Attachments = new List <Attachment> { CommandCardFactory.CreateRequestCard( conversationClientParty, botParty.ChannelAccount?.Name).ToAttachment() }; try { await _messageRouterManager.SendMessageToPartyByBotAsync(aggregationParty, messageActivity); } catch (UnauthorizedAccessException e) { System.Diagnostics.Debug.WriteLine($"Failed to broadcast message: {e.Message}"); } } else { try { await _messageRouterManager.BroadcastMessageToAggregationChannelsAsync( string.Format( ConversationText.FailedToFindBotOnAggregationChannel, aggregationParty.ConversationAccount.Name)); } catch (UnauthorizedAccessException e) { System.Diagnostics.Debug.WriteLine($"Failed to send message: {e.Message}"); } } } messageToConversationClient = ConversationText.NotifyClientWaitForRequestHandling; } else if (messageRouterResult.Type == MessageRouterResultType.ConnectionAlreadyRequested) { messageToConversationClient = ConversationText.NotifyClientDuplicateRequest; } else if (messageRouterResult.Type == MessageRouterResultType.ConnectionRejected) { messageToConversationOwner = string.Format(ConversationText.NotifyOwnerRequestRejected, conversationClientName); messageToConversationClient = ConversationText.NotifyClientRequestRejected; } else if (messageRouterResult.Type == MessageRouterResultType.Connected) { messageToConversationOwner = string.Format(ConversationText.NotifyOwnerConnected, conversationClientName); messageToConversationClient = string.Format(ConversationText.NotifyClientConnected, conversationOwnerName); } else if (messageRouterResult.Type == MessageRouterResultType.Disconnected) { messageToConversationOwner = string.Format(ConversationText.NotifyOwnerDisconnected, conversationClientName); messageToConversationClient = string.Format(ConversationText.NotifyClientDisconnected, conversationOwnerName); } if (!string.IsNullOrEmpty(messageToConversationOwner) && conversationOwnerParty != null) { try { await _messageRouterManager.SendMessageToPartyByBotAsync( conversationOwnerParty, messageToConversationOwner); } catch (UnauthorizedAccessException e) { System.Diagnostics.Debug.WriteLine($"Failed to send message: {e.Message}"); } } if (!string.IsNullOrEmpty(messageToConversationClient) && conversationClientParty != null) { try { await _messageRouterManager.SendMessageToPartyByBotAsync( conversationClientParty, messageToConversationClient); } catch (UnauthorizedAccessException e) { System.Diagnostics.Debug.WriteLine($"Failed to send message: {e.Message}"); } } }