/// <summary> /// Send event card /// </summary> /// <param name="eventMessages">List of EventMessage</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> private async Task SendEventCard(List <EventMessage> eventMessages) { var tasks = Task.Run(() => { Parallel.ForEach(eventMessages, new ParallelOptions { MaxDegreeOfParallelism = MaxParallelism }, async(eventMessage) => { var activity = eventMessage.Activity; HeroCard card = null; switch (eventMessage.MessageType) { case MessageType.Event: card = CelebrationCard.GetEventCard(activity); break; case MessageType.Preview: card = CelebrationCard.GetPreviewCard(activity); break; } var task = this.connectorServiceHelper.SendPersonalMessageAsync(string.Empty, new List <Attachment> { card.ToAttachment() }, activity.ConversationId); RetryWithExponentialBackoff retryBackOff = new RetryWithExponentialBackoff(); await retryBackOff.RunAsync(task, eventMessage, this.SuccessCallback, this.FailureCallback); }); }); await tasks; }
// Handle conversation update event (in personal scope) private async Task HandlePersonalConversationUpdateAsync(Activity activity) { this.logProvider.LogInfo($"Handling personal conversationUpdate in thread {activity.Conversation.Id}"); // In personal scope we only handle events with MembersAdded var isBotAdded = activity.MembersAdded?.Any(member => member.Id == activity.Recipient.Id); if (isBotAdded == true) { var user = activity.From; this.logProvider.LogInfo($"Conversation was created with user {user.Id}"); var objectId = user.Properties["aadObjectId"].ToString(); var userInfo = await this.userManagementHelper.GetUserByAadObjectIdAsync(objectId); if (userInfo?.InstallationMethod != BotScope.Team) { // Only send a welcome message to the users who were not previously seen in team scope var reply = activity.CreateReply(); reply.Attachments.Add(CelebrationCard.GetWelcomeCardForInstaller().ToAttachment()); await this.connectorClient.Conversations.SendToConversationWithRetriesAsync(reply); } else { this.logProvider.LogInfo($"Welcome message for {user.Id} was already sent in team scope"); } } }
// Process the new members added to the team private async Task ProcessNewTeamMembersAsync(IEnumerable <TeamsChannelAccount> teamMembers, Team teamInfo) { this.logProvider.LogInfo($"Processing new members in team {teamInfo.Id}"); var welcomeCard = CelebrationCard.GetWelcomeMessageForGeneralChannelAndTeamMembers(teamInfo.InstallerName, teamInfo.Name).ToAttachment(); await Task.WhenAll(teamMembers.Select(member => this.ProcessNewTeamMemberAsync(member, teamInfo, welcomeCard))); }
/// <summary> /// Receives message from user and reply to it /// </summary> /// <param name="activity">Activity object</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation</returns> public async Task <HttpResponseMessage> Post([FromBody] Activity activity) { try { UserTelemetryInitializer.SetTelemetryUserId(HttpContext.Current, activity.From.Id); this.LogUserActivity(activity); using (var dialogScope = DialogModule.BeginLifetimeScope(Conversation.Container, activity)) { this.connectorClient = dialogScope.Resolve <IConnectorClient>(); if (activity.Type == ActivityTypes.Message) { if (activity.Value != null) { // Process messageBack events using the dialog framework var dialog = dialogScope.Resolve <RootDialog>(); await Conversation.SendAsync(activity, () => dialog); } else { // Send welcome card if user send any message to bot var reply = activity.CreateReply(); reply.Attachments.Add(CelebrationCard.GetWelcomeCardInResponseToUserMessage().ToAttachment()); await this.connectorClient.Conversations.SendToConversationWithRetriesAsync(reply); } } else if (activity.Type == ActivityTypes.ConversationUpdate) { this.logProvider.LogInfo("Processing conversationUpdate activity"); switch (activity.Conversation.ConversationType) { case "personal": await this.HandlePersonalConversationUpdateAsync(activity); break; case "channel": await this.HandleTeamConversationUpdateAsync(activity); break; default: this.logProvider.LogWarning($"Received unexpected conversationUpdate activity with conversationType {activity.Conversation.ConversationType}"); break; } } } } catch (Exception ex) { this.logProvider.LogError($"Failed to process activity {activity.Type}", ex); throw; } return(this.Request.CreateResponse(HttpStatusCode.OK)); }
// Process the new member added to the team // This means recording the team membership, user information, and sending the user a welcome card private async Task ProcessNewTeamMemberAsync(TeamsChannelAccount member, Team teamInfo, Attachment welcomeCard) { this.logProvider.LogInfo($"Processing member {member.Id} in team {teamInfo.Id}"); try { var attachments = new List <Attachment> { welcomeCard }; var aadObjectId = (member.ObjectId ?? member.Properties["aadObjectId"]).ToString(); // Record the user's membership in the team this.logProvider.LogInfo($"Recording team membership"); var userTeamMembership = new UserTeamMembership { TeamId = teamInfo.Id, UserTeamsId = member.Id, }; await this.userManagementHelper.AddUserTeamMembershipAsync(userTeamMembership); // See if the user has events to share var events = await this.eventDataProvider.GetEventsByOwnerObjectIdAsync(aadObjectId); if (events.Count > 0) { this.logProvider.LogInfo($"User has {events.Count} existing events, will send invitation to share"); attachments.Add(CelebrationCard.GetShareEventAttachment(teamInfo.Id, teamInfo.Name, aadObjectId)); } // Get the user record var userInfo = await this.userManagementHelper.GetUserByAadObjectIdAsync(aadObjectId); // Create conversation if needed var conversationId = userInfo?.ConversationId; if (conversationId == null) { conversationId = await this.connectorClient.Conversations.CreateOrGetDirectConversationAsync(teamInfo.TenantId, member.Id); } // Send the personal welcome message this.logProvider.LogInfo($"Sending personal welcome message"); await this.connectorClient.Conversations.SendCardListAsync(conversationId, attachments); this.logProvider.LogInfo("Saving member details"); await this.StoreUserDetailsIfNeededAsync(member, teamInfo, conversationId); } catch (Exception ex) { this.logProvider.LogError($"Failed to process new member {member.Id} in {teamInfo.Id}", ex); throw; } }
// Handles event skip action private async Task HandleSkipEventAsync(IDialogContext context, IMessageActivity message) { var payload = ((JObject)message?.Value)?.ToObject <PreviewCardPayload>(); if (payload == null) { this.logProvider.LogInfo("Received message with no payload"); return; } // Get the event var celebrationEvent = await this.eventDataProvider.GetEventByIdAsync(payload.EventId, payload.OwnerAadObjectId); if (celebrationEvent == null) { this.logProvider.LogInfo($"Could not find event {payload.EventId} for user {payload.OwnerAadObjectId}"); await context.PostAsync(Strings.EventDoesNotExistMessage); return; } // Get the occurrence var occurrence = await this.eventDataProvider.GetEventOccurrenceByIdAsync(payload.OccurrenceId, payload.EventId); if (occurrence == null) { this.logProvider.LogInfo($"Could not find occurrence {payload.OccurrenceId} of event {payload.EventId} for user {payload.OwnerAadObjectId} (likely out of date card)"); await context.PostAsync(Strings.SkippedStaleEventMessage); return; } // Check that the occurrence is still in the future if (occurrence.DateTime < DateTimeOffset.UtcNow) { await context.PostAsync(Strings.EventPassedMessage); return; } // Mark the occurrence as skipped occurrence.Status = EventStatus.Skipped; await this.eventDataProvider.UpdateEventOccurrenceAsync(occurrence); // Update the card var updatedMessage = context.MakeMessage(); updatedMessage.Attachments.Add(CelebrationCard.GetPreviewCard(celebrationEvent, payload.OccurrenceId, payload.OwnerName, false).ToAttachment()); await this.connectorClient.Conversations.UpdateActivityAsync(message.Conversation.Id, message.ReplyToId, (Activity)updatedMessage); await context.PostAsync(string.Format(Strings.EventSkippedMessage, celebrationEvent.Title)); }
/// <summary> /// Handles event skip action. /// </summary> /// <param name="context">IDialogContext object.</param> /// <param name="activity">IAwaitable message activity.</param> /// <returns>Task.</returns> public async Task HandleEventSkipActions(IDialogContext context, IAwaitable <IMessageActivity> activity) { var message = (Activity)await activity; if (message.Value != null) { var replyMessage = string.Empty; var previewCardPayload = ((JObject)message.Value).ToObject <PreviewCardPayload>(); // Get event by eventId to check if it exist or not. CelebrationEvent celebrationEvent = await this.eventHelper.GetEventByEventIdAsync(previewCardPayload.EventId, previewCardPayload.OwnerAadObjectId); if (celebrationEvent != null) { if (previewCardPayload.UpcomingEventDate > DateTime.UtcNow.Date) { await this.eventHelper.UpdateRecurringEventAsync(previewCardPayload.EventId, EventStatus.Skipped); EventMessageActivity eventMessageActivity = new EventMessageActivity { Id = celebrationEvent.Id, OwnerName = previewCardPayload.OwnerName, ImageUrl = celebrationEvent.ImageURL, Message = celebrationEvent.Message, Title = celebrationEvent.Title, }; // Update the card IMessageActivity updatedMessage = context.MakeMessage(); updatedMessage.Attachments.Add(CelebrationCard.GetPreviewCard(eventMessageActivity, false).ToAttachment()); updatedMessage.ReplyToId = message.ReplyToId; await this.connectorClient.Conversations.UpdateActivityAsync(message.Conversation.Id, message.ReplyToId, (Activity)updatedMessage); replyMessage = string.Format(Strings.EventSkippedMessage, message.From.Name); } else { // event occurrence has already passed for current year. replyMessage = string.Format(Strings.EventPassedMessage); } } else { replyMessage = string.Format(Strings.EventNotExistMessage, previewCardPayload.Title); } await context.PostAsync(replyMessage); context.Done <object>(null); } }
/// <summary> /// Handles Ignore event sharing action. /// </summary> /// <param name="context">IDialogContext object.</param> /// <param name="activity">IAwaitable message activity.</param> /// <returns>Task.</returns> public async Task HandleIgnoreEventSharingAction(IDialogContext context, IAwaitable <IMessageActivity> activity) { var message = (Activity)await activity; if (message.Value != null) { var replyMessage = "Ok, if you change your mind you can share the events from the Events tab."; ShareEventPayload shareEventPayload = ((JObject)message.Value).ToObject <ShareEventPayload>(); // Update the card IMessageActivity updatedMessage = context.MakeMessage(); updatedMessage.Attachments.Add(CelebrationCard.GetShareEventAttachementWithoutActionButton(shareEventPayload.TeamName)); updatedMessage.ReplyToId = message.ReplyToId; await this.connectorClient.Conversations.UpdateActivityAsync(message.Conversation.Id, message.ReplyToId, (Activity)updatedMessage); await context.PostAsync(replyMessage); context.Done <object>(null); } }
/// <summary> /// Receives message from user and reply to it. /// </summary> /// <param name="activity">activity object.</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> public async Task <HttpResponseMessage> Post([FromBody] Activity activity) { UserTelemetryInitializer.SetTelemetryUserId(HttpContext.Current, activity.From.Id); this.LogUserActivity(activity); this.teamMembers = null; if (activity.Type == ActivityTypes.Invoke || (activity.Type == ActivityTypes.Message && activity.Value != null)) { using (var dialogScope = DialogModule.BeginLifetimeScope(Conversation.Container, activity)) { var dialog = dialogScope.Resolve <RootDialog>(); await Conversation.SendAsync(activity, () => dialog); } } else { using (var dialogScope = DialogModule.BeginLifetimeScope(this.scope, activity)) { IConnectorClient connectorClient = dialogScope.Resolve <IConnectorClient>(); this.connectorServiceHelper = new ConnectorServiceHelper(connectorClient, this.logProvider); if (activity.Type == ActivityTypes.Message) { Activity welcomeActivity = activity.CreateReply(); welcomeActivity.Attachments.Add(CelebrationCard.GetWelcomeCardInResponseToUserMessage().ToAttachment()); await connectorClient.Conversations.ReplyToActivityAsync(welcomeActivity); } else { await this.HandleSystemMessageAsync(connectorClient, activity); } } } var response = this.Request.CreateResponse(HttpStatusCode.OK); return(response); }
/// <summary> /// Send welcome message in general channel. /// </summary> /// <param name="activity">Activity instance.</param> /// <param name="isBotInstalledInTeam">true/false.</param> /// <param name="installerName">bot installer name.</param> /// <param name="teamName">TeamName</param> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> private async Task SendWelcomeMessageToGeneralChannel(Activity activity, bool isBotInstalledInTeam, string installerName, string teamName) { this.logProvider.LogInfo("Sending welcome message to general channel."); Activity welcomeActivity = activity.CreateReply(); AdaptiveCard welcomeCard; if (isBotInstalledInTeam) { welcomeCard = CelebrationCard.GetWelcomeMessageForGeneralChannelAndTeamMembers(installerName, teamName); } else { welcomeCard = CelebrationCard.GetWelcomeCardForInstaller(); } this.logProvider.LogInfo($"Welcome card json: {welcomeCard.ToJson()}"); welcomeActivity.Attachments.Add(welcomeCard.ToAttachment()); await this.connectorClient.Conversations.ReplyToActivityAsync(welcomeActivity); this.logProvider.LogInfo("Welcome message sent to general Chanel."); }
// send welcome message to all team members private async Task SendWelcomeMessageToTeamMembers(IList <ChannelAccount> teamMembers, TeamsChannelData channelData, string installerName, string teamName) { this.logProvider.LogInfo($"Sending welcome message to all the team members. Total team members of team: {channelData.Team.Id} = {teamMembers.Count}"); string conversationId = string.Empty; foreach (var member in teamMembers) { conversationId = this.connectorServiceHelper.CreateOrGetConversationIdAsync(channelData.Tenant.Id, member.Id); List <Attachment> attachmentList = new List <Attachment> { CelebrationCard.GetWelcomeMessageForGeneralChannelAndTeamMembers(installerName, teamName).ToAttachment() }; string userAadObjectId = (member.Properties["objectId"] ?? member.Properties["aadObjectId"]).ToString(); List <CelebrationEvent> celebrationEvents = await(await this.eventHelper.GetEventsByOwnerObjectIdAsync(userAadObjectId)).ToListAsync(); if (celebrationEvents.Count > 0) { attachmentList.Add(CelebrationCard.GetShareEventAttachement(channelData.Team.Id, channelData.Team.Name, userAadObjectId)); } this.logProvider.LogInfo($"Sending personal welcome message to {member.Name}", new Dictionary <string, string> { { "TeamId", channelData.Team.Id }, { "UserTeamId", member.Id }, { "UserObjectId", userAadObjectId }, { "ConversationId", conversationId }, { "Attachment", attachmentList.ToString() }, }); await this.connectorServiceHelper.SendPersonalMessageAsync(string.Empty, attachmentList, conversationId); this.logProvider.LogInfo("Saving member details to database."); // Save team member details. await this.SaveMemberDetailsAsync(member, channelData, conversationId); } }
/// <summary> /// Handles event Share action /// </summary> /// <param name="context">IDialogContext object</param> /// <param name="activity">IAwaitable message activity</param> /// <returns>Task.</returns> public async Task HandleEventShareAction(IDialogContext context, IAwaitable <IMessageActivity> activity) { var message = (Activity)await activity; var replyMessage = string.Empty; if (message.Value != null) { ShareEventPayload shareEventPayload = ((JObject)message.Value).ToObject <ShareEventPayload>(); try { var teamMembers = await this.connectorClient.Conversations.GetConversationMembersAsync(shareEventPayload.TeamId); var user = teamMembers.Select(x => x.AsTeamsChannelAccount()).Where(x => x.ObjectId == shareEventPayload.UserAadObjectId).FirstOrDefault(); // Fetch teamDetails from DB to check if bot is still present or un-installed from team var document = await this.userManagementHelper.GetTeamsDetailsByTeamIdAsync(shareEventPayload.TeamId); if (user == null) { replyMessage = string.Format(Strings.ShareWithTeamNotAMemberError, shareEventPayload.TeamName); } else if (document == null) { replyMessage = string.Format(Strings.ShareWithTeamsNotInstalledError, shareEventPayload.TeamName); } else { // Fetch all the events of user and share with team var celebrationEvents = await this.eventDataProvider.GetEventsByOwnerObjectIdAsync(shareEventPayload.UserAadObjectId); if (celebrationEvents.Count > 0) { foreach (var celebrationEvent in celebrationEvents) { celebrationEvent.Teams.Add(new Team { Id = shareEventPayload.TeamId }); await this.eventDataProvider.UpdateEventAsync(celebrationEvent); } } replyMessage = Strings.ShareWithTeamSuccessMessage; // Update the card IMessageActivity updatedMessage = context.MakeMessage(); updatedMessage.Attachments.Add(CelebrationCard.GetShareEventAttachmentWithoutActionButton(shareEventPayload.TeamName)); updatedMessage.ReplyToId = message.ReplyToId; await this.connectorClient.Conversations.UpdateActivityAsync(message.Conversation.Id, message.ReplyToId, (Activity)updatedMessage); } } catch (Exception ex) { this.logProvider.LogError("Failed to share the existing event with team", ex, new Dictionary <string, string>() { { "TeamId", shareEventPayload.TeamId }, { "UserAadObjectId", shareEventPayload.UserAadObjectId }, }); replyMessage = Strings.ShareWithTeamGenericError; } await context.PostAsync(replyMessage); context.Done <object>(null); } }
/// <summary> /// Handles event Share action. /// </summary> /// <param name="context">IDialogContext object.</param> /// <param name="activity">IAwaitable message activity.</param> /// <returns>Task.</returns> public async Task HandleEventShareAction(IDialogContext context, IAwaitable <IMessageActivity> activity) { var message = (Activity)await activity; var replyMessage = string.Empty; if (message.Value != null) { ShareEventPayload shareEventPayload = ((JObject)message.Value).ToObject <ShareEventPayload>(); try { var teamMembers = await this.connectorClient.Conversations.GetConversationMembersAsync(shareEventPayload.TeamId); var user = teamMembers.Where(x => x.Properties["objectId"].ToString() == shareEventPayload.UserAadObjectId).ToList().FirstOrDefault(); var document = await this.userManagementHelper.GetTeamsDetailsByTeamIdAsync(shareEventPayload.TeamId); bool isBotUnintsalledFromTeam = document == null ? true : false; if (user == null) { replyMessage = $"You are no longer a member of {shareEventPayload.TeamName}."; } else if (isBotUnintsalledFromTeam) { replyMessage = "Someone uninstalled me from your team, I can no longer share these events there"; } else { List <CelebrationEvent> celebrationEvents = await(await this.eventHelper.GetEventsByOwnerObjectIdAsync( shareEventPayload.UserAadObjectId)).ToListAsync(); if (celebrationEvents.Count > 0) { foreach (var celebrationEvent in celebrationEvents) { celebrationEvent.Teams.Add(new Team { Id = shareEventPayload.TeamId }); CelebrationEvent updatedEvent = (dynamic)celebrationEvent; updatedEvent.Teams = celebrationEvent.Teams; await this.eventHelper.UpdateEventAsync(updatedEvent); } } replyMessage = "I’ve set those events to be shared with the team when they occur."; // Update the card IMessageActivity updatedMessage = context.MakeMessage(); updatedMessage.Attachments.Add(CelebrationCard.GetShareEventAttachementWithoutActionButton(shareEventPayload.TeamName)); updatedMessage.ReplyToId = message.ReplyToId; await this.connectorClient.Conversations.UpdateActivityAsync(message.Conversation.Id, message.ReplyToId, (Activity)updatedMessage); } } catch (Exception ex) { this.logProvider.LogError("Failed to share the existing event with team", ex, new Dictionary <string, string>() { { "TeamId", shareEventPayload.TeamId }, { "TeamName", shareEventPayload.TeamName }, { "UserAadObjectId", shareEventPayload.UserAadObjectId }, }); replyMessage = "Some error occurred to share the event with team. Please try again."; } await context.PostAsync(replyMessage); context.Done <object>(null); } }
// Handle team members added event (in team scope) private async Task HandleTeamMembersAddedAsync(Activity activity, TeamsChannelData channelData) { string teamId = channelData.Team.Id; this.logProvider.LogInfo($"Handling team members added event in team {teamId}"); // Determine if the bot was installed to the team bool isBotAdded = activity.MembersAdded.Any(member => member.Id == activity.Recipient.Id); if (isBotAdded) { this.logProvider.LogInfo($"Bot was installed to team {teamId}"); var properties = new Dictionary <string, string> { { "Scope", activity.Conversation?.ConversationType }, { "TeamId", teamId }, { "InstallerId", activity.From.Id }, }; this.logProvider.LogEvent("AppInstalled", properties); } var membersAdded = activity.MembersAdded; // Ensure that we have an installation record for this team var isBackfill = false; var teamInfo = await this.userManagementHelper.GetTeamsDetailsByTeamIdAsync(teamId); if ((teamInfo == null) && !isBotAdded) { this.logProvider.LogInfo($"Detected a missed installation to team {teamId}, will attempt to backfill"); // We must have missed an event from this team-- attempt to backfill isBotAdded = true; isBackfill = true; } if (isBotAdded) { // Try to determine the name of the person that installed the app, which is usually the sender of the message (From.Id) // Note that in some cases we cannot resolve it to a team member, because the app was installed to the team programmatically via Graph string installerName = null; if (!isBackfill) { installerName = activity.From?.Name; if (installerName == null) { installerName = await this.GetUserNameAsync(activity.From, teamId); } } // Get team details this.logProvider.LogInfo("Getting team details"); var teamDetails = await this.connectorClient.GetTeamsConnectorClient().Teams.FetchTeamDetailsAsync(teamId); // Add team installation record this.logProvider.LogInfo("Recording team installation"); teamInfo = new Team { Id = teamId, Name = teamDetails.Name, ServiceUrl = activity.ServiceUrl, TenantId = channelData.Tenant.Id, InstallerName = installerName, }; await this.userManagementHelper.SaveTeamDetailsAsync(teamInfo); // Send welcome message to the General channel this.logProvider.LogInfo("Sending welcome message to general channel"); Activity reply = activity.CreateReply(); reply.Attachments.Add(CelebrationCard.GetWelcomeMessageForGeneralChannelAndTeamMembers(installerName, teamDetails.Name).ToAttachment()); await this.connectorClient.Conversations.SendToConversationWithRetriesAsync(reply); // Get all team members to welcome them this.logProvider.LogInfo("Getting all team members to send them a welcome message"); membersAdded = await this.connectorClient.Conversations.GetConversationMembersAsync(teamId); } else { this.logProvider.LogInfo($"Members added to team {teamId} ({membersAdded?.Count} new members)"); } // Process new team members await this.ProcessNewTeamMembersAsync(membersAdded.AsTeamsChannelAccounts(), teamInfo); }
/// <summary> /// Process request to send preview card /// </summary> /// <param name="currentDateTime">Current dateTime</param> /// <returns>A <see cref="Task"/>Representing the asynchronous operation</returns> public async Task <HttpResponseMessage> Post(string currentDateTime = "") { this.logProvider.LogInfo($"Processing events to send the reminder to owner. CurrentDateTime:{currentDateTime}"); DateTimeOffset currentDateTimeOffset; if (!DateTimeOffset.TryParse(currentDateTime, null, DateTimeStyles.AdjustToUniversal, out currentDateTimeOffset)) { currentDateTimeOffset = DateTimeOffset.UtcNow; } var events = await(await this.eventHelper.GetCelebrationEventsAsync(GetEventQuery(currentDateTimeOffset.Date))).ToListAsync(); this.logProvider.LogInfo($"found {events.Count} which are coming in next 72 hours."); if (events.Count > 0) { var existingRecurringEvents = await(await this.eventHelper.GetRecurringEventsAsync(events.Select(x => x.Id).ToList())).ToListAsync(); this.logProvider.LogInfo($"Found {existingRecurringEvents.Count} for which reminder has already sent"); int lastAttemptStatusCode = (int)HttpStatusCode.OK; string responseBody = string.Empty; // remove events which exist in Occurrences collection events.RemoveAll(x => existingRecurringEvents.Any(y => y.EventId == x.Id)); if (events.Count > 0) { this.users = await this.userManagementHelper.GetUsersByAadObjectIdsAsync(events.Select(x => x.OwnerAadObjectId).ToList()); this.connectorServiceHelper = new ConnectorServiceHelper(this.CreateConnectorClient(this.users.FirstOrDefault().ServiceUrl), this.logProvider); } // Loop each event and make entry in Occurrences collection to send preview and event card. foreach (var celebrationEvent in events) { this.logProvider.LogInfo("Processing event to send reminder", new Dictionary <string, string>() { { "EventId", celebrationEvent.Id }, { "UserObjectId", celebrationEvent.OwnerAadObjectId } }); // Get event owner information. var user = this.users.Where(x => x.AadObjectId == celebrationEvent.OwnerAadObjectId).FirstOrDefault(); // update conversation id if it is null. await this.ModifyUserDetailsAsync(user); DateTime upcomingEventDate = Common.GetUpcomingEventDate(celebrationEvent.Date, currentDateTimeOffset.Date); var timespan = Array.ConvertAll <string, int>(ApplicationSettings.TimeToPostCelebration.Split(':'), Convert.ToInt32); DateTime upcomingEventDateTime = upcomingEventDate.AddHours(timespan[0]).AddMinutes(timespan[1]); DateTimeOffset upcomingEventDateTimeInUTC = TimeZoneInfo.ConvertTimeToUtc(upcomingEventDateTime, TimeZoneInfo.FindSystemTimeZoneById(celebrationEvent.TimeZoneId)); // add an entry to Occurrence collection for all the upcoming event. EventOccurrence eventOccurrence = new EventOccurrence { EventId = celebrationEvent.Id, Date = upcomingEventDateTimeInUTC, }; await this.eventHelper.AddRecurringEventAsync(eventOccurrence); // Do not send reminder if event is today. if (upcomingEventDate != currentDateTimeOffset.Date) { // Add new entry to EventMessages collection for reminder. EventMessage eventMessage = new EventMessage { OccurrenceId = eventOccurrence.Id, EventId = celebrationEvent.Id, Activity = this.GetEventMessageActivity(celebrationEvent, user), MessageType = MessageType.Preview, ExpireAt = upcomingEventDate.AddHours(24), }; await this.eventHelper.AddEventMessageAsync(eventMessage); bool isMessageSentSuccessfully = false; Exception exception = null; try { HeroCard previewCard = CelebrationCard.GetPreviewCard(eventMessage.Activity); string message = string.Format(Strings.PreviewText, user.UserName); this.logProvider.LogInfo("Sending reminder message to the owner of the event", new Dictionary <string, string>() { { "EventId", celebrationEvent.Id }, { "Attachment", Newtonsoft.Json.JsonConvert.SerializeObject(previewCard) }, { "Message", message }, }); // Send reminder of event to owner. await this.connectorServiceHelper.SendPersonalMessageAsync( message, new List <Attachment> { previewCard.ToAttachment() }, user.ConversationId); this.logProvider.LogInfo($"Reminder message sent to the owner of the event. EventId: {celebrationEvent.Id}"); } catch (HttpException httpException) { lastAttemptStatusCode = httpException.GetHttpCode(); responseBody = httpException.GetHtmlErrorMessage(); exception = httpException; } catch (ErrorResponseException errorResponseException) { lastAttemptStatusCode = (int)errorResponseException.Response.StatusCode; responseBody = errorResponseException.Response.Content.ToString(); exception = errorResponseException; } catch (Exception ex) { lastAttemptStatusCode = (int)HttpStatusCode.BadRequest; responseBody = ex.ToString(); } finally { if (!isMessageSentSuccessfully) { this.logProvider.LogError("Failed to send reminder for upcoming event.", exception, new Dictionary <string, string> { { "EventId", eventMessage.EventId }, { "OccurrenceId", eventMessage.OccurrenceId }, { "eventActivity", eventMessage.Activity.ToString() }, { "LastAttemptStatusCode", lastAttemptStatusCode.ToString() }, { "LastAttemptTime", DateTime.UtcNow.ToString() }, { "ConversationId", user.ConversationId }, }); } MessageSendResult messageSendResult = new MessageSendResult() { LastAttemptTime = DateTime.UtcNow, StatusCode = lastAttemptStatusCode, ResponseBody = responseBody, }; await this.eventHelper.UpdateEventMessageAsync(eventMessage.Id, messageSendResult); } } else { this.logProvider.LogInfo("Not sending reminder for this event as its upcoming event date is today."); } } } return(new HttpResponseMessage(HttpStatusCode.OK)); }
/// <summary> /// Process and send all the due events in a team. /// </summary> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> private async Task ProcessEvents() { foreach (var teamId in this.teamsEventsDictionary.Keys) { List <string> eventIds = new List <string>(); this.teamsEventsDictionary.TryGetValue(teamId, out eventIds); List <Attachment> cardAttachments = new List <Attachment>(); List <EventMessage> eventMessages = new List <EventMessage>(); const int maxEventPerCarousel = 6; const int minEventToSendIndividually = 3; int counter = 0; List <EventNotificationCardPayload> eventNotificationCardPayloadList = new List <EventNotificationCardPayload>(); List <Entity> entities = new List <Entity>(); foreach (var eventId in eventIds) { counter++; var recurringEvent = this.recurringEvents.Where(x => x.EventId == eventId).FirstOrDefault(); var celebrationEvent = this.celebrationEvents.Where(x => x.Id == eventId).FirstOrDefault(); // Get event owner information. var user = this.users.Where(x => x.AadObjectId == celebrationEvent.OwnerAadObjectId).FirstOrDefault(); // Add an Entry to Messages collection. EventMessage eventMessage = await this.AddEntryToEventMessagesCollection(teamId, recurringEvent, celebrationEvent, user); eventMessages.Add(eventMessage); // Get Hero card for event. HeroCard card = CelebrationCard.GetEventCard(eventMessage.Activity); EventNotificationCardPayload eventNotificationCardPayload = new EventNotificationCardPayload() { UserName = user.UserName, UserTeamsId = user.TeamsId, Message = $"<at>{user.UserName}</at> is celebrating {celebrationEvent.Title}", Attachment = card.ToAttachment(), }; eventNotificationCardPayloadList.Add(eventNotificationCardPayload); if (eventIds.Count > minEventToSendIndividually && (counter % maxEventPerCarousel == 0 || counter == eventIds.Count)) { eventNotificationCardPayloadList = eventNotificationCardPayloadList.OrderBy(x => x.UserName).ToList(); string message = "Stop the presses! Today "; foreach (var notificationPayload in eventNotificationCardPayloadList) { message = message + notificationPayload.Message + ","; cardAttachments.Add(notificationPayload.Attachment); AddMentionedEntities(entities, notificationPayload); } message = message.TrimEnd(','); int position = message.LastIndexOf(','); message = (message.Substring(0, position) + " and " + message.Substring(position + 1)).Replace(",", ", ") + ". That’s a lot of merrymaking for one day—pace yourselves! \n\n"; // Do not send separate message in case of 1 event. if (eventNotificationCardPayloadList.Count == 1) { message = string.Empty; } // send event notification in team. this.logProvider.LogInfo("Sending event message in team", new Dictionary <string, string>() { { "EventId", celebrationEvent.Id }, { "TeamId", teamId }, { "Attachment", cardAttachments.ToString() }, { "Message", message }, }); await this.SendEventCard(message, cardAttachments, teamId, eventMessages, entities); // Reset list cardAttachments = new List <Attachment>(); eventMessages = new List <EventMessage>(); eventNotificationCardPayloadList = new List <EventNotificationCardPayload>(); entities = new List <Entity>(); } else if (eventIds.Count <= minEventToSendIndividually) { this.logProvider.LogInfo("Sending event message in team", new Dictionary <string, string>() { { "EventId", celebrationEvent.Id }, { "TeamId", teamId }, { "Attachment", cardAttachments.ToString() }, { "Message", string.Empty }, }); await this.SendEventCard(string.Empty, new List <Attachment> { eventNotificationCardPayload.Attachment }, teamId, eventMessages); // Reset list cardAttachments = new List <Attachment>(); eventMessages = new List <EventMessage>(); eventNotificationCardPayloadList = new List <EventNotificationCardPayload>(); entities = new List <Entity>(); } } } // Delete entry from occurrences collection. foreach (var recurringEvent in this.recurringEvents) { this.logProvider.LogInfo("Deleting recurring event", new Dictionary <string, string>() { { "EventId", recurringEvent.EventId }, { "RecurringEventId", recurringEvent.Id }, }); await this.eventHelper.DeleteRecurringEventAsync(recurringEvent.Id, recurringEvent.EventId); } }
/// <summary> /// Gets the card to send /// </summary> /// <returns>The card to send</returns> public Attachment GetCard() { return(CelebrationCard.GetEventCard(this.Event, this.User.DisplayName).ToAttachment()); }
// Send a messsage to the owner of the given event, reminding them that their event is coming up private async Task SendEventReminderAsync(CelebrationEvent celebrationEvent, DateTimeOffset currentDateTimeOffset) { this.logProvider.LogInfo($"Sending reminder for event {celebrationEvent.Id} (owner={celebrationEvent.OwnerAadObjectId}, date={celebrationEvent.Date.ToShortDateString()})"); // Determine the next occurrence of the event var deliveryTimeZone = TimeZoneInfo.FindSystemTimeZoneById(celebrationEvent.TimeZoneId); var currentTimeInDeliveryTimeZone = TimeZoneInfo.ConvertTimeFromUtc(currentDateTimeOffset.UtcDateTime, deliveryTimeZone); var upcomingEventDateTime = Common.GetNextOccurrenceAfterDateTime(celebrationEvent.Date.Add(this.timeToPostPreview), currentTimeInDeliveryTimeZone); var upcomingEventDateTimeInUTC = TimeZoneInfo.ConvertTimeToUtc(upcomingEventDateTime, deliveryTimeZone); // Do not send reminder if the next occurrence is not in the window var timeUntilNextOccurrence = upcomingEventDateTimeInUTC - currentDateTimeOffset; if ((timeUntilNextOccurrence.TotalMinutes < 0) || (upcomingEventDateTimeInUTC - currentDateTimeOffset) > TimeSpan.FromDays(this.daysInAdvanceToSendEventPreview)) { this.logProvider.LogInfo($"Next occurrence of event {celebrationEvent.Id} is not in the next {this.daysInAdvanceToSendEventPreview} days"); return; } // Add an entry to Occurrence collection for the event, so we know that we processed it var eventOccurrence = new EventOccurrence { EventId = celebrationEvent.Id, OwnerAadObjectId = celebrationEvent.OwnerAadObjectId, DateTime = upcomingEventDateTimeInUTC, }; eventOccurrence = await this.eventDataProvider.AddEventOccurrenceAsync(eventOccurrence); // Do not send reminder if we are within the period specified in the configuration if ((upcomingEventDateTimeInUTC - currentDateTimeOffset) < this.minimumTimeToProcessEvent) { this.logProvider.LogInfo($"Not sending reminder for event {celebrationEvent.Id} which is due in less than {(int)this.minimumTimeToProcessEvent.TotalHours} hours"); return; } // Get event owner information var user = await this.userManagementHelper.GetUserByAadObjectIdAsync(celebrationEvent.OwnerAadObjectId); await this.EnsureConversationWithUserAsync(user); // Send reminder of event to the owner var previewCard = CelebrationCard.GetPreviewCard(celebrationEvent, eventOccurrence.Id, user.DisplayName); var message = string.Format(Strings.EventPreviewMessageText, user.DisplayName); var activity = new Activity(ActivityTypes.Message) { Conversation = new ConversationAccount { Id = user.ConversationId }, Recipient = new ChannelAccount { Id = user.TeamsId }, Text = message, Summary = message, Attachments = new List <Attachment> { previewCard.ToAttachment() }, ServiceUrl = user.ServiceUrl, }; // Add new entry to EventMessages collection for message type "preview" to track the status of message sent var eventMessage = new EventMessage { EventId = celebrationEvent.Id, OccurrenceId = eventOccurrence.Id, Activity = activity, TenantId = user.TenantId, MessageType = MessageType.Preview, ExpireAt = upcomingEventDateTimeInUTC.AddHours(24), }; eventMessage = await this.eventDataProvider.AddEventMessageAsync(eventMessage); // Send the message try { await eventMessage.SendAsync(this.connectorClientFactory); this.logProvider.LogInfo($"Reminder message sent to the owner of event {celebrationEvent.Id}"); } catch (Exception ex) { this.logProvider.LogError($"Failed to send reminder for event {celebrationEvent.Id}", ex, new Dictionary <string, string> { { "EventId", eventMessage.EventId }, { "OccurrenceId", eventMessage.OccurrenceId }, { "ConversationId", user.ConversationId }, { "OccurrenceDateTime", eventOccurrence.DateTime.ToString() }, { "LastAttemptTime", DateTimeOffset.UtcNow.ToString() }, { "LastAttemptStatusCode", eventMessage.MessageSendResult?.StatusCode.ToString() }, { "ResponseBody", eventMessage.MessageSendResult?.ResponseBody }, }); throw; } finally { // Record the result of the send await this.eventDataProvider.UpdateEventMessageAsync(eventMessage); } }