private async Task CheckSchedules() { while (true) { foreach (var workspace in Settings.SettingsObject.Workspaces.Workspaces) { WorkspaceInfo workspaceInfo = workspace.Value.ToObject <WorkspaceInfo>() !; MongoDatabase database = new MongoDatabase(workspace.Key, mongoDatabaseLogger); SlackCore slackApi = new SlackCore(workspaceInfo.BotToken); var slackUsers = await slackApi.UsersList(); List <Database.Models.VenmoUser> users = database.GetAllUsers(); foreach (var user in users) { if (user.Schedule != null && user.Schedule.Count > 0) { VenmoApi venmoApi = new VenmoApi(venmoApiLogger); string? accessToken = await helperMethods.CheckIfVenmoAccessTokenIsExpired(user, venmoApi, database); if (string.IsNullOrEmpty(accessToken)) { logger.LogError($"Unable to refresh Venmo access token for {user.UserId}"); await WebhookController.SendSlackMessage(workspaceInfo, "Unable to process scheduled Venmos as your token has expired. Please refresh it.", user.UserId, httpClient); continue; } venmoApi.AccessToken = accessToken; bool saveNeeded = false; for (int i = 0; i < user.Schedule.Count; i++) { VenmoSchedule schedule = user.Schedule[i]; Instant now = clock.GetCurrentInstant(); Instant scheduledTime = Instant.FromDateTimeUtc(schedule.NextExecution); bool deleteSchedule = false; if (now > scheduledTime) { saveNeeded = true; await WebhookController.SendSlackMessage(workspaceInfo, $"Processing {schedule.Command}", user.UserId, httpClient); string[] splitPaymentMessage = helperMethods.ConvertScheduleMessageIntoPaymentMessage(schedule.Command.Split(' ')); ParsedVenmoPayment parsedVenmoPayment; try { parsedVenmoPayment = helperMethods.ParseVenmoPaymentMessage(splitPaymentMessage); } catch (Exception ex) { logger.LogWarning(ex, "Failed to parse payment, this shouldn't happen as it was parsed before being saved."); continue; } var response = await helperMethods.VenmoPayment(venmoApi, user, database, parsedVenmoPayment.Amount, parsedVenmoPayment.Note, parsedVenmoPayment.Recipients, parsedVenmoPayment.Action, parsedVenmoPayment.Audience); foreach (var r in response.responses) { if (!string.IsNullOrEmpty(r.Error)) { await WebhookController.SendSlackMessage(workspaceInfo, $"Venmo error: {r.Error}", user.UserId, httpClient); continue; } if (parsedVenmoPayment.Action == VenmoAction.Charge) { await WebhookController.SendSlackMessage(workspaceInfo, $"Successfully charged {r.Data!.Payment.Target!.User.Username} ${r.Data.Payment.Amount} for {r.Data.Payment.Note}. Audience is {r.Data.Payment.Audience}", user.UserId, httpClient); } else { await WebhookController.SendSlackMessage(workspaceInfo, $"Successfully paid {r.Data!.Payment.Target!.User.Username} ${r.Data.Payment.Amount} for {r.Data.Payment.Note}. Audience is {r.Data.Payment.Audience}", user.UserId, httpClient); } } foreach (var u in response.unprocessedRecipients) { await WebhookController.SendSlackMessage(workspaceInfo, $"You are not friends with {u}.", user.UserId, httpClient); } if (schedule.Verb == "at" || schedule.Verb == "on") { deleteSchedule = true; } else if (schedule.Verb == "every") { SlackUser?slackUser = helperMethods.GetSlackUser(user.UserId, slackUsers); if (slackUser == null) { // user somehow doesn't exist? logger.LogError($"While trying to process schedule for a slack user they disappeared? {user.UserId}"); deleteSchedule = true; } else { ZonedDateTime nextExecution = helperMethods.ConvertScheduleMessageIntoDateTime( schedule.Command.Split(' '), slackUser.TimeZone, clock); await WebhookController.SendSlackMessage(workspaceInfo, $"Next execution is {nextExecution.GetFriendlyZonedDateTimeString()}", user.UserId, httpClient); schedule.NextExecution = nextExecution.ToDateTimeUtc(); } } } if (deleteSchedule) { user.Schedule.RemoveAt(i); i--; } } if (saveNeeded) { database.SaveUser(user); } } } } await Task.Delay(CheckDuration.ToTimeSpan()); } }