/// <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);
        }
Beispiel #3
0
        /// <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);
        }
Beispiel #4
0
        /// <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);
        }
Beispiel #7
0
        /// <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);
        }
Beispiel #8
0
 /// <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))
     });
 }
Beispiel #9
0
 /// <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);
        }
Beispiel #11
0
        /// <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);
        }
Beispiel #12
0
        /// <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);
        }
Beispiel #13
0
 /// <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);
        }
Beispiel #16
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);
            }
        }
Beispiel #18
0
        /// <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);
        }
Beispiel #19
0
        /// <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);
        }
Beispiel #21
0
        /// <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);
        }
Beispiel #22
0
        /// <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);
        }
Beispiel #24
0
        /// <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);
        }
Beispiel #27
0
        /// <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);
        }
Beispiel #28
0
        /// <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);
        }
Beispiel #29
0
        /// <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);
 }