private async Task UpdateActivityAsync(ITurnContext turnContext, Activity activity, CancellationToken cancellationToken) { var ignoreUpdate = turnContext.TurnState.Get <CardManagerTurnState>()?.MiddlewareIgnoreUpdate; ignoreUpdate?.Add(activity); try { await turnContext.UpdateActivityAsync(activity, cancellationToken).ConfigureAwait(false); } catch { // TODO: Find out what exceptions I need to handle throw; } finally { ignoreUpdate?.Remove(activity); } if (!CardTree.GetIds(activity, TreeNodeType.Activity).Any()) { await RemoveActivityAsync(turnContext, activity, cancellationToken).ConfigureAwait(false); } }
// ---------------- // UPDATING METHODS // ---------------- public async Task SaveActivitiesAsync(ITurnContext turnContext, IEnumerable <Activity> activities, CancellationToken cancellationToken = default) { BotAssert.ContextNotNull(turnContext); if (activities is null) { throw new ArgumentNullException(nameof(activities)); } var state = await GetStateAsync(turnContext, cancellationToken).ConfigureAwait(false); foreach (var activity in activities) { if (activity.Id != null) { // Remove any activities with a matching ID so that a duplicate isn't saved when updating await UnsaveActivityAsync(turnContext, activity.Id, cancellationToken).ConfigureAwait(false); } if (CardTree.GetIds(activity, TreeNodeType.Activity).Any()) { state.SavedActivities.Add(activity); } } await StateAccessor.SetAsync(turnContext, state, cancellationToken).ConfigureAwait(false); }
public static ISet <DataId> GetIdsFromBatch(this IEnumerable <IMessageActivity> activities) { if (activities is null) { throw new ArgumentNullException(nameof(activities)); } return(CardTree.GetIds(activities, TreeNodeType.Batch)); }
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); }