using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using StreakBot.Data; using StreakBot.Data.Entities; namespace StreakBot.Services; public class StreakResetBackgroundService : BackgroundService { private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; private readonly TimeSpan _checkInterval = TimeSpan.FromMinutes(1); public StreakResetBackgroundService( IServiceScopeFactory scopeFactory, ILogger logger) { _scopeFactory = scopeFactory; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { try { await CheckAllStreaksAsync(); } catch (Exception ex) { _logger.LogError(ex, "Error checking streaks for reset"); } await Task.Delay(_checkInterval, stoppingToken); } } private async Task CheckAllStreaksAsync() { using var scope = _scopeFactory.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); var channelService = scope.ServiceProvider.GetRequiredService(); var streaks = await context.Streaks.ToListAsync(); var affectedStreaks = new List(); foreach (var streak in streaks) { var result = await CheckAndResetIfNeededAsync(streak, channelService); if (result != null) { affectedStreaks.Add(result); } } if (context.ChangeTracker.HasChanges()) { var count = await context.SaveChangesAsync(); _logger.LogInformation("Reset {Count} streak(s)", count); } foreach (var streak in affectedStreaks.Where(s => s.ServerId != 0)) { await channelService.UpdateStreakChannelAsync(streak.ServerId, streak.StreakNumber, true); } await UpdateAllTimeChannelsAsync(streaks, channelService); await UpdateAllDmMessagesAsync(streaks, channelService, context); } private async Task CheckAndResetIfNeededAsync(Streak streak, ChannelService channelService) { var now = DateTime.UtcNow; var timeUntilReset = streak.LastResetCheck - now; // Check if we need to send a reminder (less than 1 hour until reset and not both have sent) if (timeUntilReset > TimeSpan.Zero && timeUntilReset <= TimeSpan.FromHours(1)) { if ((!streak.User1MessageSent || !streak.User2MessageSent) && !streak.ReminderSent) { await channelService.SendStreakReminderAsync( streak.User1Id, streak.User2Id, !streak.User1MessageSent, !streak.User2MessageSent, streak.ServerId); streak.ReminderSent = true; } } if (now <= streak.LastResetCheck) { return null; } if (!streak.User1MessageSent || !streak.User2MessageSent) { streak.StreakNumberToRestore = streak.StreakNumber; streak.StreakNumber = 0; _logger.LogInformation("Resetting streak between {0} and {1}", streak.User1Id, streak.User2Id); } streak.User1MessageSent = false; streak.User2MessageSent = false; streak.ReminderSent = false; var daysToAdd = (int)Math.Ceiling((now - streak.LastResetCheck).TotalDays); streak.LastResetCheck = streak.LastResetCheck.AddDays(daysToAdd); return streak; } private async Task UpdateAllTimeChannelsAsync(List streaks, ChannelService channelService) { var serverStreaks = streaks .Where(s => s.ServerId != 0) .DistinctBy(s => s.ServerId) .ToList(); foreach (var streak in serverStreaks) { await channelService.UpdateTimeChannelAsync(streak.ServerId, streak.CreatedDate.TimeOfDay); } } private async Task UpdateAllDmMessagesAsync(List streaks, ChannelService channelService, StreakDbContext context) { var dmStreaks = streaks.Where(s => s.ServerId == 0).ToList(); foreach (var streak in dmStreaks) { var channel = await context.Channels .FirstOrDefaultAsync(c => c.ChannelId == streak.ChannelId && c.ChannelType == ChannelType.DmChannel); if (channel?.MessageId != null) { await channelService.UpdateDmStreakMessageAsync(channel.ChannelId, channel.MessageId.Value, streak.StreakNumber, streak.CreatedDate.TimeOfDay); } } } }