C# Discord bot made using NetCord for keeping a TikTok-style streak
at main 146 lines 5.0 kB view raw
1using Microsoft.EntityFrameworkCore; 2using Microsoft.Extensions.DependencyInjection; 3using Microsoft.Extensions.Hosting; 4using Microsoft.Extensions.Logging; 5using StreakBot.Data; 6using StreakBot.Data.Entities; 7 8namespace StreakBot.Services; 9 10public class StreakResetBackgroundService : BackgroundService 11{ 12 private readonly IServiceScopeFactory _scopeFactory; 13 private readonly ILogger<StreakResetBackgroundService> _logger; 14 private readonly TimeSpan _checkInterval = TimeSpan.FromMinutes(1); 15 16 public StreakResetBackgroundService( 17 IServiceScopeFactory scopeFactory, 18 ILogger<StreakResetBackgroundService> logger) 19 { 20 _scopeFactory = scopeFactory; 21 _logger = logger; 22 } 23 24 protected override async Task ExecuteAsync(CancellationToken stoppingToken) 25 { 26 while (!stoppingToken.IsCancellationRequested) 27 { 28 try 29 { 30 await CheckAllStreaksAsync(); 31 } 32 catch (Exception ex) 33 { 34 _logger.LogError(ex, "Error checking streaks for reset"); 35 } 36 37 await Task.Delay(_checkInterval, stoppingToken); 38 } 39 } 40 41 private async Task CheckAllStreaksAsync() 42 { 43 using var scope = _scopeFactory.CreateScope(); 44 var context = scope.ServiceProvider.GetRequiredService<StreakDbContext>(); 45 var channelService = scope.ServiceProvider.GetRequiredService<ChannelService>(); 46 47 var streaks = await context.Streaks.ToListAsync(); 48 var affectedStreaks = new List<Streak>(); 49 50 foreach (var streak in streaks) 51 { 52 var result = await CheckAndResetIfNeededAsync(streak, channelService); 53 54 if (result != null) 55 { 56 affectedStreaks.Add(result); 57 } 58 } 59 60 if (context.ChangeTracker.HasChanges()) 61 { 62 var count = await context.SaveChangesAsync(); 63 _logger.LogInformation("Reset {Count} streak(s)", count); 64 } 65 66 foreach (var streak in affectedStreaks.Where(s => s.ServerId != 0)) 67 { 68 await channelService.UpdateStreakChannelAsync(streak.ServerId, streak.StreakNumber, true); 69 } 70 71 await UpdateAllTimeChannelsAsync(streaks, channelService); 72 await UpdateAllDmMessagesAsync(streaks, channelService, context); 73 } 74 75 private async Task<Streak?> CheckAndResetIfNeededAsync(Streak streak, ChannelService channelService) 76 { 77 var now = DateTime.UtcNow; 78 var timeUntilReset = streak.LastResetCheck - now; 79 80 // Check if we need to send a reminder (less than 1 hour until reset and not both have sent) 81 if (timeUntilReset > TimeSpan.Zero && timeUntilReset <= TimeSpan.FromHours(1)) 82 { 83 if ((!streak.User1MessageSent || !streak.User2MessageSent) && !streak.ReminderSent) 84 { 85 await channelService.SendStreakReminderAsync( 86 streak.User1Id, 87 streak.User2Id, 88 !streak.User1MessageSent, 89 !streak.User2MessageSent, 90 streak.ServerId); 91 92 streak.ReminderSent = true; 93 } 94 } 95 96 if (now <= streak.LastResetCheck) 97 { 98 return null; 99 } 100 101 if (!streak.User1MessageSent || !streak.User2MessageSent) 102 { 103 streak.StreakNumberToRestore = streak.StreakNumber; 104 streak.StreakNumber = 0; 105 _logger.LogInformation("Resetting streak between {0} and {1}", streak.User1Id, streak.User2Id); 106 } 107 108 streak.User1MessageSent = false; 109 streak.User2MessageSent = false; 110 streak.ReminderSent = false; 111 112 var daysToAdd = (int)Math.Ceiling((now - streak.LastResetCheck).TotalDays); 113 streak.LastResetCheck = streak.LastResetCheck.AddDays(daysToAdd); 114 115 return streak; 116 } 117 118 private async Task UpdateAllTimeChannelsAsync(List<Streak> streaks, ChannelService channelService) 119 { 120 var serverStreaks = streaks 121 .Where(s => s.ServerId != 0) 122 .DistinctBy(s => s.ServerId) 123 .ToList(); 124 125 foreach (var streak in serverStreaks) 126 { 127 await channelService.UpdateTimeChannelAsync(streak.ServerId, streak.CreatedDate.TimeOfDay); 128 } 129 } 130 131 private async Task UpdateAllDmMessagesAsync(List<Streak> streaks, ChannelService channelService, StreakDbContext context) 132 { 133 var dmStreaks = streaks.Where(s => s.ServerId == 0).ToList(); 134 135 foreach (var streak in dmStreaks) 136 { 137 var channel = await context.Channels 138 .FirstOrDefaultAsync(c => c.ChannelId == streak.ChannelId && c.ChannelType == ChannelType.DmChannel); 139 140 if (channel?.MessageId != null) 141 { 142 await channelService.UpdateDmStreakMessageAsync(channel.ChannelId, channel.MessageId.Value, streak.StreakNumber, streak.CreatedDate.TimeOfDay); 143 } 144 } 145 } 146}