using Microsoft.EntityFrameworkCore; using NetCord.Services; using StreakBot.Data; using StreakBot.Data.Entities; namespace StreakBot.Services; public class StreakService { private readonly StreakDbContext _context; private readonly ChannelService _channelService; public StreakService(StreakDbContext context, ChannelService channelService) { _context = context; _channelService = channelService; } public async Task ProcessMessageAsync(ulong userId, ulong serverId) { //Streaks are per server var streak = await _context.Streaks .Where(s => s.ServerId == serverId && (s.User1Id == userId || s.User2Id == userId)) .FirstOrDefaultAsync(); if (streak == null) { return; } ProcessStreakMessage(streak, userId); if (_context.ChangeTracker.HasChanges()) { await _context.SaveChangesAsync(); if (streak.User1MessageSent && streak.User2MessageSent) { await _channelService.UpdateStreakChannelAsync(serverId, streak.StreakNumber); } } } public async Task ProcessDmMessageAsync(ulong userId, ulong channelId) { var streaks = await _context.Streaks .Where(s => s.ServerId == 0 && s.ChannelId == channelId && (s.User1Id == userId || s.User2Id == userId)) .ToListAsync(); foreach (var streak in streaks) { ProcessStreakMessage(streak, userId); } if (_context.ChangeTracker.HasChanges()) { await _context.SaveChangesAsync(); // Update DM message for each affected streak foreach (var streak in streaks) { var channel = await _context.Channels .FirstOrDefaultAsync(c => c.ChannelId == channelId && c.ChannelType == Data.Entities.ChannelType.DmChannel); if (channel?.MessageId != null) { await _channelService.UpdateDmStreakMessageAsync(channelId, channel.MessageId.Value, streak.StreakNumber, streak.CreatedDate.TimeOfDay); } } } } private static void ProcessStreakMessage(Streak streak, ulong userId) { bool wasUser1 = streak.User1Id == userId; bool wasUser2 = streak.User2Id == userId; bool previousUser1Sent = streak.User1MessageSent; bool previousUser2Sent = streak.User2MessageSent; if (wasUser1 && !streak.User1MessageSent) { streak.User1MessageSent = true; } else if (wasUser2 && !streak.User2MessageSent) { streak.User2MessageSent = true; } if (streak.User1MessageSent && streak.User2MessageSent && !(previousUser1Sent && previousUser2Sent)) { streak.StreakNumber++; } } public async Task CreateStreakAsync(ulong user1Id, ulong user2Id, ulong serverId, ulong channelId = 0) { var streaks = serverId == 0 ? await _context.Streaks .Where(s => s.ChannelId == channelId && (s.User1Id == user1Id || s.User2Id == user1Id)) .ToListAsync() : await _context.Streaks .Where(s => s.ServerId == serverId && (s.User1Id == user1Id || s.User2Id == user1Id)) .ToListAsync(); if (streaks.Any(c => (c.User1Id == user1Id && c.User2Id == user2Id) || (c.User1Id == user2Id && c.User2Id == user1Id))) { return null; } var now = DateTime.UtcNow; var streak = new Streak { CreatedDate = now, User1Id = user1Id, User2Id = user2Id, ServerId = serverId, ChannelId = channelId, User1MessageSent = false, User2MessageSent = false, StreakNumber = 0, LastResetCheck = now.AddDays(1) }; _context.Streaks.Add(streak); await _context.SaveChangesAsync(); if (serverId == 0) { await _channelService.CreateDmStreakMessageAsync(channelId, streak.StreakNumber, streak.CreatedDate.TimeOfDay); } else { await _channelService.UpdateStreakChannelAsync(serverId, streak.StreakNumber); await _channelService.UpdateTimeChannelAsync(serverId, streak.CreatedDate.TimeOfDay); } return streak; } public async Task> CheckStreaksAsync(ulong user1Id) { var streaks = await _context.Streaks .Where(s => s.User1Id == user1Id || s.User2Id == user1Id) .ToListAsync(); return streaks; } public async Task GetStreakAsync(ulong user1Id, ulong user2Id) { return await _context.Streaks .Where(s => (s.User1Id == user1Id && s.User2Id == user2Id) || (s.User1Id == user2Id && s.User2Id == user1Id)) .FirstOrDefaultAsync(); } public async Task RestoreStreakAsync(ulong user1Id, ulong user2Id) { var streak = await GetStreakAsync(user1Id, user2Id); if (streak == null) { return $"No streak found between <@{user1Id}> and <@{user2Id}>."; } if (streak.MonthlyRestores <= 0) { return "No restores available this month. Restores reset at the start of each month."; } if (streak.StreakNumberToRestore == null || streak.StreakNumber >= streak.StreakNumberToRestore) { return "No broken streak to restore."; } var restoredNumber = streak.StreakNumberToRestore.Value; if (streak.User1MessageSent && streak.User2MessageSent) { restoredNumber++; } streak.StreakNumber = restoredNumber; streak.StreakNumberToRestore = null; streak.MonthlyRestores--; await _context.SaveChangesAsync(); if (streak.ServerId != 0) { await _channelService.UpdateStreakChannelAsync(streak.ServerId, streak.StreakNumber, !(streak.User1MessageSent && streak.User2MessageSent)); } else { var channel = await _context.Channels .FirstOrDefaultAsync(c => c.ChannelId == streak.ChannelId && c.ChannelType == ChannelType.DmChannel); if (channel?.MessageId != null) { await _channelService.UpdateDmStreakMessageAsync(streak.ChannelId, channel.MessageId.Value, streak.StreakNumber, streak.CreatedDate.TimeOfDay); } } return $"Streak restored to {restoredNumber}! You have {streak.MonthlyRestores} restore(s) remaining this month."; } public async Task DeleteStreakAsync(ulong user1Id, ulong user2Id) { var streak = await GetStreakAsync(user1Id, user2Id); if (streak == null) { return null; } var channels = await _context.Channels .Where(c => c.ServerId == streak.ServerId) .ToListAsync(); foreach (var channel in channels) { await _channelService.DeleteChannelAsync(channel.ChannelId); } return true; } }