Beispiel #1
0
        public async Task DisableIdAsync(
            ITurnContext turnContext,
            DataId dataId,
            TrackingStyle style = TrackingStyle.TrackEnabled,
            CancellationToken cancellationToken = default)
        {
            BotAssert.ContextNotNull(turnContext);

            if (dataId is null)
            {
                throw new ArgumentNullException(nameof(dataId));
            }

            switch (style)
            {
            case TrackingStyle.TrackEnabled:
                await ForgetIdAsync(turnContext, dataId, cancellationToken).ConfigureAwait(false);

                break;

            case TrackingStyle.TrackDisabled:
                await TrackIdAsync(turnContext, dataId, cancellationToken).ConfigureAwait(false);

                break;
            }
        }
        // This will be called by the Bot Builder SDK and all three of these parameters are guaranteed to not be null
        private async Task <ResourceResponse[]> OnSendActivities(ITurnContext turnContext, List <Activity> activities, Func <Task <ResourceResponse[]> > next)
        {
            var options = GetOptionsForChannel(turnContext.Activity.ChannelId);

            // This method should be executed even if the bot is only sending text
            if (options.AutoClearEnabledOnSend &&
                options.IdTrackingStyle == TrackingStyle.TrackEnabled &&
                activities.Any(activity => activity.Type == ActivityTypes.Message))
            {
                // TODO: Add an equivalent of this for channels with updates
                await Manager.ClearTrackedIdsAsync(turnContext).ConfigureAwait(false);
            }

            // The other methods will only be effective if the bot is sending attachments
            if (activities.Any(activity => activity.Attachments?.Any() != true))
            {
                return(await next().ConfigureAwait(false));
            }

            if (options.AutoConvertAdaptiveCards)
            {
                activities.ConvertAdaptiveCards();
            }

            if (options.AutoSeparateAttachmentsOnSend)
            {
                activities.SeparateAttachments();
            }

            if (options.AutoAdaptOutgoingCardActions)
            {
                activities.AdaptOutgoingCardActions(turnContext.Activity.ChannelId);
            }

            if (options.AutoApplyIds)
            {
                DataId.SetInBatch(activities, options.IdOptions);
            }

            // The resource response ID's will be automatically applied to the activities
            // so this return value is only passed along as the outer return value
            // and is not used for tracking/management.
            // The needed activity ID's can be extracted from the activities directly.
            var resourceResponses = await next().ConfigureAwait(false);

            if (options.AutoEnableOnSend && options.IdTrackingStyle == TrackingStyle.TrackEnabled)
            {
                foreach (var dataId in activities.GetIdsFromBatch())
                {
                    await Manager.EnableIdAsync(turnContext, dataId, options.IdTrackingStyle).ConfigureAwait(false);
                }
            }

            if (options.AutoSaveActivitiesOnSend)
            {
                await Manager.SaveActivitiesAsync(turnContext, activities).ConfigureAwait(false);
            }

            return(resourceResponses);
        }
        // This will be called by the Bot Builder SDK and all three of these parameters are guaranteed to not be null
        private async Task <ResourceResponse> OnUpdateActivity(ITurnContext turnContext, Activity activity, Func <Task <ResourceResponse> > next)
        {
            var ignoreUpdate = turnContext.TurnState.Get <CardManagerTurnState>()?.MiddlewareIgnoreUpdate;

            // Removing the item also checks if it was present
            if (ignoreUpdate.Remove(activity))
            {
                return(await next().ConfigureAwait(false));
            }

            var options    = GetOptionsForChannel(turnContext.Activity.ChannelId);
            var activities = new[] { activity };

            // Some functionality that is in OnSendActivities is intentionally omitted here.
            // We don't clear enabled ID's because a new activity isn't being sent.
            // We don't separate attachments because it's impossible to update an activity with multiple activities.

            if (options.AutoConvertAdaptiveCards)
            {
                activities.ConvertAdaptiveCards();
            }

            if (options.AutoAdaptOutgoingCardActions)
            {
                activities.AdaptOutgoingCardActions(turnContext.Activity.ChannelId);
            }

            if (options.AutoApplyIds)
            {
                DataId.SetInBatch(activities, options.IdOptions);
            }

            // The resource response ID will already be the ID of the activity
            // so this return value is only passed along as the outer return value
            // and is not used for tracking/management.
            // The needed activity ID can be extracted from the activity directly.
            var resourceResponse = await next().ConfigureAwait(false);

            if (options.AutoEnableOnSend && options.IdTrackingStyle == TrackingStyle.TrackEnabled)
            {
                foreach (var dataId in activities.GetIdsFromBatch())
                {
                    await Manager.EnableIdAsync(turnContext, dataId, options.IdTrackingStyle).ConfigureAwait(false);
                }
            }

            var state = await Manager.GetStateAsync(turnContext).ConfigureAwait(false);

            var savedActivity = state.SavedActivities.FirstOrDefault(a => a.Id == activity.Id);

            if (options.AutoSaveActivitiesOnSend || savedActivity != null)
            {
                await Manager.SaveActivitiesAsync(turnContext, activities).ConfigureAwait(false);
            }

            return(resourceResponse);
        }
Beispiel #4
0
        public async Task TrackIdAsync(ITurnContext turnContext, DataId dataId, CancellationToken cancellationToken = default)
        {
            BotAssert.ContextNotNull(turnContext);

            if (dataId is null)
            {
                throw new ArgumentNullException(nameof(dataId));
            }

            var state = await GetStateAsync(turnContext, cancellationToken).ConfigureAwait(false);

            state.DataIdsByScope.InitializeKey(dataId.Key, new HashSet <string>()).Add(dataId.Value);

            await StateAccessor.SetAsync(turnContext, state, cancellationToken).ConfigureAwait(false);
        }
Beispiel #5
0
        public async Task ForgetIdAsync(ITurnContext turnContext, DataId dataId, CancellationToken cancellationToken = default)
        {
            BotAssert.ContextNotNull(turnContext);

            if (dataId is null)
            {
                throw new ArgumentNullException(nameof(dataId));
            }

            var state = await GetStateAsync(turnContext, cancellationToken).ConfigureAwait(false);

            if (state.DataIdsByScope.TryGetValue(dataId.Key, out var ids))
            {
                ids?.Remove(dataId.Value);
            }

            await StateAccessor.SetAsync(turnContext, state, cancellationToken).ConfigureAwait(false);
        }
Beispiel #6
0
        public async Task DeleteActionSourceAsync(ITurnContext turnContext, string dataIdScope, CancellationToken cancellationToken = default)
        {
            // TODO: Provide a way to delete elements by specifying an ID that's not in the incoming action data

            BotAssert.ContextNotNull(turnContext);

            if (string.IsNullOrEmpty(dataIdScope))
            {
                throw new ArgumentNullException(nameof(dataIdScope));
            }

            var state = await GetStateAsync(turnContext, cancellationToken).ConfigureAwait(false);

            if (dataIdScope == DataIdScopes.Batch)
            {
                if (turnContext.GetIncomingActionData().ToJObject().GetIdFromActionData(DataIdScopes.Batch) is string batchId)
                {
                    var toDelete = new DataId(DataIdScopes.Batch, batchId);

                    // Iterate over a copy of the set so the original can be modified
                    foreach (var activity in state.SavedActivities.ToList())
                    {
                        // Delete any activity that contains the specified batch ID (data items are compared by value)
                        if (CardTree.GetIds(activity, TreeNodeType.Activity).Any(toDelete.Equals))
                        {
                            await DeleteActivityAsync(turnContext, activity, cancellationToken).ConfigureAwait(false);
                        }
                    }
                }
            }
            else
            {
                var matchResult = await GetDataMatchAsync(turnContext, cancellationToken).ConfigureAwait(false);

                var matchedActivity      = matchResult.SavedActivity;
                var matchedAttachment    = matchResult.SavedAttachment;
                var matchedAction        = matchResult.SavedAction;
                var shouldUpdateActivity = false;

                // TODO: Provide options for how to determine emptiness when cascading deletion
                // (e.g. when a card has no more actions rather than only when the card is completely empty)

                if (dataIdScope == DataIdScopes.Action && matchedActivity != null && matchedAttachment != null && matchedAction != null)
                {
                    if (matchedAttachment.ContentType.EqualsCI(ContentTypes.AdaptiveCard))
                    {
                        // For Adaptive Cards
                        if (matchedAction is JObject savedSubmitAction)
                        {
                            var adaptiveCard = (JObject)savedSubmitAction.Root;

                            // Remove the submit action from the Adaptive Card.
                            // SafeRemove will work whether the action is in an array
                            // or is the value of a select action property.
                            savedSubmitAction.SafeRemove();

                            matchedAttachment.Content = matchedAttachment.Content.FromJObject(adaptiveCard);
                            shouldUpdateActivity      = true;

                            // Check if the Adaptive Card is now empty
                            if (adaptiveCard.GetValue(AdaptiveProperties.Body).IsNullishOrEmpty() &&
                                adaptiveCard.GetValue(AdaptiveProperties.Actions).IsNullishOrEmpty())
                            {
                                // If the card is now empty, execute the next if block to delete it
                                dataIdScope = DataIdScopes.Card;
                            }
                        }
                    }
                    else
                    {
                        // For Bot Framework rich cards
                        if (matchedAction is CardAction cardAction)
                        {
                            // Remove the card action from the card
                            CardTree.Recurse(
                                matchedAttachment,
                                (IList <CardAction> actions) =>
                            {
                                actions.Remove(cardAction);
                            },
                                TreeNodeType.Attachment,
                                TreeNodeType.CardActionList);

                            shouldUpdateActivity = true;

                            // Check if the card is now empty.
                            // We are assuming that if a developer wants to make a rich card
                            // with only postBack/messageBack buttons then they will use a hero card
                            // and any other card would have more content than just postBack/messageBack buttons
                            // so only a hero card should potentially be empty at this point.
                            // We aren't checking if Buttons is null because it can't be at this point.
                            if (matchedAttachment.Content is HeroCard heroCard &&
                                !heroCard.Buttons.Any() &&
                                heroCard.Images?.Any() != true &&
                                string.IsNullOrWhiteSpace(heroCard.Title) &&
                                string.IsNullOrWhiteSpace(heroCard.Subtitle) &&
                                string.IsNullOrWhiteSpace(heroCard.Text))
                            {
                                // If the card is now empty, execute the next if block to delete it
                                dataIdScope = DataIdScopes.Card;
                            }
                        }
                    }
                }

                if (dataIdScope == DataIdScopes.Card && matchedActivity != null && matchedAttachment != null)
                {
                    matchedActivity.Attachments.Remove(matchedAttachment);

                    shouldUpdateActivity = true;

                    // Check if the activity is now empty
                    if (string.IsNullOrWhiteSpace(matchedActivity.Text) && !matchedActivity.Attachments.Any())
                    {
                        // If the activity is now empty, execute the next if block to delete it
                        dataIdScope = DataIdScopes.Carousel;
                    }
                }

                if (dataIdScope == DataIdScopes.Carousel && matchedActivity != null)
                {
                    await DeleteActivityAsync(turnContext, matchedActivity, cancellationToken).ConfigureAwait(false);
                }
                else if (shouldUpdateActivity)
                {
                    await UpdateActivityAsync(turnContext, matchedActivity, cancellationToken).ConfigureAwait(false);
                }
            }

            await StateAccessor.SetAsync(turnContext, state, cancellationToken).ConfigureAwait(false);
        }