public static async Task DoWork(IConfiguration configuration, ILogger log = null) { var api = new CosmosInterface(configuration); var translator = new Translator(configuration); // The reminder message is static, so cache any translations to limit API calls. var translationCache = new Dictionary <string, string>(); // Get the current day. var day = DayFlagsHelpers.CurrentDay(); // Get all users. // TODO: this should likely be broken up so that it doesn't exceed the function time limit. var users = await api.GetUsers(); Helpers.LogInfo(log, $"Found {users.Count} users"); var queueHelper = new OutgoingMessageQueueHelpers(configuration.AzureWebJobsStorage()); foreach (var user in users) { // Check if the user should be reminded today. if (!user.ContactEnabled || !user.ReminderFrequency.HasFlag(day)) { continue; } // Check if the user should be reminded at this time. // Special case: use 6pm UTC (10am PST) if their reminder time isn't set. DateTime userReminderTime = string.IsNullOrEmpty(user.ReminderTime) ? DateTime.Parse("6:00pm") : DateTime.Parse(user.ReminderTime); // Using a 5 minute window in case the function triggers slightly early or late. if (Math.Abs((DateTime.UtcNow - userReminderTime).TotalMinutes) > 5) { continue; } var message = Phrases.Greeting.RemindToUpdate; // Check if the user's language is already cached. if (translationCache.TryGetValue(user.Language, out var translation)) { message = translation; } else { // Translate the message if necessary. if (translator.IsConfigured && user.Language != Translator.DefaultLanguage) { message = await translator.TranslateAsync(message, user.Language); } // Cache the message translationCache.Add(user.Language, message); } var data = new OutgoingMessageQueueData { PhoneNumber = user.PhoneNumber, Message = message }; await queueHelper.Enqueue(data); } }
public override Task <WaterfallDialog> GetWaterfallDialog(ITurnContext turnContext, CancellationToken cancellation) { return(Task.Run(() => { return new WaterfallDialog(Name, new WaterfallStep[] { async(dialogContext, cancellationToken) => { // Get the categories. var schema = Helpers.GetSchema(); List <string> categories = schema.Categories.Select(c => c.Name).ToList(); if (categories.Count == 1) { // No need to ask for a single category. var userContext = await this.state.GetUserContext(dialogContext.Context, cancellationToken); userContext.Category = schema.Categories.First().Name; // Skip this step. return await dialogContext.NextAsync(null, cancellationToken); } var choices = new List <Choice>(); categories.ForEach(c => choices.Add(new Choice { Value = c })); choices.Add(new Choice { Value = Phrases.None }); return await dialogContext.PromptAsync( Prompt.CategoryPrompt, new PromptOptions() { Prompt = Phrases.Provide.GetCategory, Choices = choices }, cancellationToken); }, async(dialogContext, cancellationToken) => { var schema = Helpers.GetSchema(); var userContext = await this.state.GetUserContext(dialogContext.Context, cancellationToken); if (dialogContext.Result is FoundChoice) { // Choice was validated in case the schema changed. var selectedCategory = ((FoundChoice)dialogContext.Result).Value; if (selectedCategory == Phrases.None) { return await dialogContext.EndDialogAsync(null, cancellationToken); } // Store the category in the user context. userContext.Category = selectedCategory; } // Get the resources in the category. var category = schema.Categories.FirstOrDefault(c => c.Name == userContext.Category); List <string> resources = category.Resources.Select(r => r.Name).ToList(); var choices = new List <Choice>(); resources.ForEach(r => choices.Add(new Choice { Value = r })); choices.Add(new Choice { Value = Phrases.None }); return await dialogContext.PromptAsync( Prompt.ResourcePrompt, new PromptOptions() { Prompt = Phrases.Request.Resources(userContext.Category), Choices = choices, Validations = new ResourcePromptValidations { Category = userContext.Category } }, cancellationToken); }, async(dialogContext, cancellationToken) => { // Choice was validated in case the schema changed. var selectedResource = ((FoundChoice)dialogContext.Result).Value; if (selectedResource == Phrases.None) { return await dialogContext.EndDialogAsync(null, cancellationToken); } var userContext = await this.state.GetUserContext(dialogContext.Context, cancellationToken); userContext.Resource = selectedResource; // Ask how many they need. return await dialogContext.PromptAsync( Prompt.IntPrompt, new PromptOptions { Prompt = Phrases.Request.GetQuantity(selectedResource) }, cancellationToken); }, async(dialogContext, cancellationToken) => { var userContext = await this.state.GetUserContext(dialogContext.Context, cancellationToken); userContext.NeedQuantity = (int)dialogContext.Result; if (userContext.NeedQuantity == 0) { // Delete the need if it has already been added. var user = await api.GetUser(dialogContext.Context); var need = await this.api.GetNeedForUser(user, userContext.Category, userContext.Resource); if (need != null) { await this.api.Delete(need); await Messages.SendAsync(Phrases.Request.CompleteDelete, dialogContext.Context, cancellationToken); } else { await Messages.SendAsync(Phrases.Request.CompleteUpdate, dialogContext.Context, cancellationToken); } return await dialogContext.EndDialogAsync(null, cancellationToken); } // Ask whether or not they are willing to take opened items. return await dialogContext.PromptAsync( Prompt.ConfirmPrompt, new PromptOptions { Prompt = Phrases.Request.GetOpenedOkay }, cancellationToken); }, async(dialogContext, cancellationToken) => { var userContext = await this.state.GetUserContext(dialogContext.Context, cancellationToken); userContext.NeedUnopenedOnly = !(bool)dialogContext.Result; // Ask for any instructions. return await dialogContext.PromptAsync( Prompt.TextPrompt, new PromptOptions { Prompt = Phrases.Request.Instructions }, cancellationToken); }, async(dialogContext, cancellationToken) => { var schema = Helpers.GetSchema(); var userContext = await this.state.GetUserContext(dialogContext.Context, cancellationToken); // Check if they have already added this resource. var user = await api.GetUser(dialogContext.Context); var need = await this.api.GetNeedForUser(user, userContext.Category, userContext.Resource); // Create or update the need. if (need == null) { need = new Need(); need.CreatedById = user.Id; need.Category = userContext.Category; need.Name = userContext.Resource; need.Quantity = userContext.NeedQuantity; need.UnopenedOnly = userContext.NeedUnopenedOnly; need.Instructions = (string)dialogContext.Result; await this.api.Create(need); } else { need.CreatedOn = DateTime.UtcNow; need.Quantity = userContext.NeedQuantity; need.UnopenedOnly = userContext.NeedUnopenedOnly; need.Instructions = (string)dialogContext.Result; await this.api.Update(need); } // The API input requires meters. // Default is 50, but we could make this configurable in the future. double requestMeters = Units.Miles.ToMeters(50); // Get all users within the distance from the user. var usersWithinDistance = await this.api.GetUsersWithinDistance(user.LocationCoordinates, requestMeters); if (usersWithinDistance.Count > 0) { var organization = schema.VerifiedOrganizations.FirstOrDefault(o => o.PhoneNumbers.Contains(user.PhoneNumber)); var message = Phrases.Match.GetMessage(organization.Name, need.Name, need.Quantity, need.Instructions); var queueHelper = new OutgoingMessageQueueHelpers(this.configuration.AzureWebJobsStorage()); // Cache any translations to limit API calls. var translationCache = new Dictionary <string, string>(); // Get any matching resources for the users. foreach (var userWithinDistance in usersWithinDistance) { var resource = await this.api.GetResourceForUser(userWithinDistance, need.Category, need.Name); if (!Helpers.DoesResourceMatchNeed(need, resource)) { continue; } // Check if the user's language is already cached. if (translationCache.TryGetValue(user.Language, out var translation)) { message = translation; } else { // Translate the message if necessary. if (translator.IsConfigured && user.Language != Translator.DefaultLanguage) { message = await translator.TranslateAsync(message, user.Language); } // Cache the message translationCache.Add(user.Language, message); } var data = new OutgoingMessageQueueData { PhoneNumber = userWithinDistance.PhoneNumber, Message = message }; await queueHelper.Enqueue(data); } } await Messages.SendAsync(Phrases.Request.CompleteCreate(user), turnContext, cancellationToken); return await dialogContext.EndDialogAsync(null, cancellationToken); } }); }));
public override Task <WaterfallDialog> GetWaterfallDialog(ITurnContext turnContext, CancellationToken cancellation) { return(Task.Run(() => { return new WaterfallDialog(Name, new WaterfallStep[] { async(dialogContext, cancellationToken) => { // Get the categories. var schema = Helpers.GetSchema(); List <string> categories = schema.Categories.Select(c => c.Name).ToList(); if (categories.Count == 1) { // No need to ask for a single category. var userContext = await this.state.GetUserContext(dialogContext.Context, cancellationToken); userContext.Category = schema.Categories.First().Name; // Skip this step. return await dialogContext.NextAsync(null, cancellationToken); } var choices = new List <Choice>(); categories.ForEach(c => choices.Add(new Choice { Value = c })); choices.Add(new Choice { Value = Phrases.None }); return await dialogContext.PromptAsync( Prompt.CategoryPrompt, new PromptOptions() { Prompt = Phrases.Provide.GetCategory, Choices = choices }, cancellationToken); }, async(dialogContext, cancellationToken) => { var schema = Helpers.GetSchema(); var userContext = await this.state.GetUserContext(dialogContext.Context, cancellationToken); if (dialogContext.Result is FoundChoice) { // Choice was validated in case the schema changed. var selectedCategory = ((FoundChoice)dialogContext.Result).Value; if (selectedCategory == Phrases.None) { return await dialogContext.EndDialogAsync(null, cancellationToken); } // Store the category in the user context. userContext.Category = selectedCategory; } // Get the resources in the category. var category = schema.Categories.FirstOrDefault(c => c.Name == userContext.Category); List <string> resources = category.Resources.Select(r => r.Name).ToList(); var choices = new List <Choice>(); resources.ForEach(r => choices.Add(new Choice { Value = r })); choices.Add(new Choice { Value = Phrases.None }); return await dialogContext.PromptAsync( Prompt.ResourcePrompt, new PromptOptions() { Prompt = Phrases.Request.Resources(userContext.Category), Choices = choices, Validations = new ResourcePromptValidations { Category = userContext.Category } }, cancellationToken); }, async(dialogContext, cancellationToken) => { // Choice was validated in case the schema changed. var selectedResource = ((FoundChoice)dialogContext.Result).Value; if (selectedResource == Phrases.None) { return await dialogContext.EndDialogAsync(null, cancellationToken); } // Store the resource in the user context. var userContext = await this.state.GetUserContext(dialogContext.Context, cancellationToken); userContext.Resource = selectedResource; // Ask how many they need. return await dialogContext.PromptAsync( Prompt.IntPrompt, new PromptOptions { Prompt = Phrases.Request.GetQuantity(selectedResource) }, cancellationToken); }, async(dialogContext, cancellationToken) => { // Store the quantity in the user context. var userContext = await this.state.GetUserContext(dialogContext.Context, cancellationToken); userContext.RequestQuantity = (int)dialogContext.Result; var schema = Helpers.GetSchema(); // Ask the distance they want to broadcast to. return await dialogContext.PromptAsync( Prompt.IntPrompt, new PromptOptions { Prompt = Phrases.Request.Distance(schema.Units) }, cancellationToken); }, async(dialogContext, cancellationToken) => { // Store the distance in the user context. var userContext = await this.state.GetUserContext(dialogContext.Context, cancellationToken); userContext.RequestDistance = (int)dialogContext.Result; // Ask for any instructions. return await dialogContext.PromptAsync( Prompt.TextPrompt, new PromptOptions { Prompt = Phrases.Request.Instructions }, cancellationToken); }, async(dialogContext, cancellationToken) => { var instructions = (string)dialogContext.Result; var schema = Helpers.GetSchema(); var user = await api.GetUser(dialogContext.Context); var userContext = await this.state.GetUserContext(dialogContext.Context, cancellationToken); // The API input requires meters. double requestMeters = schema.Units.ToMeters(userContext.RequestDistance); // Get all users within the distance from the user. var usersWithinDistance = await this.api.GetUsersWithinDistance(user.LocationCoordinates, userContext.RequestDistance); if (usersWithinDistance.Count > 0) { var organization = schema.VerifiedOrganizations.FirstOrDefault(o => o.PhoneNumbers.Contains(user.PhoneNumber)); var message = Phrases.Request.GetOutgoingMessage(organization.Name, userContext.Resource, userContext.RequestQuantity, instructions); var queueHelper = new OutgoingMessageQueueHelpers(this.configuration.AzureWebJobsStorage()); // Get any matching resources for the users. foreach (var userWithinDistance in usersWithinDistance) { var resource = await this.api.GetResourceForUser(userWithinDistance, userContext.Category, userContext.Resource); if (resource != null) { var data = new OutgoingMessageQueueData { PhoneNumber = userWithinDistance.PhoneNumber, Message = message }; await queueHelper.Enqueue(data); } } } await Messages.SendAsync(Phrases.Request.Sent, turnContext, cancellationToken); return await dialogContext.EndDialogAsync(null, cancellationToken); } }); })); }