From dbc312dd9dd138633bc534185bfd34a191edbbb3 Mon Sep 17 00:00:00 2001 From: Toastie <toastie@toastiet0ast.com> Date: Thu, 6 Feb 2025 12:54:30 +1300 Subject: [PATCH] wip reimplementation of the voicexp --- src/EllieBot/EllieBot.csproj | 2 +- src/EllieBot/Modules/Xp/XpService.cs | 190 +++++++++------------------ 2 files changed, 61 insertions(+), 131 deletions(-) diff --git a/src/EllieBot/EllieBot.csproj b/src/EllieBot/EllieBot.csproj index 72b6799..35d0275 100644 --- a/src/EllieBot/EllieBot.csproj +++ b/src/EllieBot/EllieBot.csproj @@ -154,7 +154,7 @@ <PropertyGroup Condition=" '$(Configuration)' == 'GlobalEllie' "> <!-- Define trace doesn't seem to affect the build at all so I had to remove $(DefineConstants)--> <DefineTrace>false</DefineTrace> - <DefineConstants>GLOBAL_NADEKO</DefineConstants> + <DefineConstants>GLOBAL_ELLIE</DefineConstants> <NoWarn>$(NoWarn);CS1573;CS1591</NoWarn> <Optimize>true</Optimize> <DebugType>portable</DebugType> diff --git a/src/EllieBot/Modules/Xp/XpService.cs b/src/EllieBot/Modules/Xp/XpService.cs index 109d9b0..54884c3 100644 --- a/src/EllieBot/Modules/Xp/XpService.cs +++ b/src/EllieBot/Modules/Xp/XpService.cs @@ -140,6 +140,22 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand return; + async Task VoiceUpdateTimer() + { + using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1)); + while (await timer.WaitForNextTickAsync()) + { + try + { + await UpdateVoiceXp(); + } + catch (Exception ex) + { + Log.Error(ex, "Error updating voice xp"); + } + } + } + async Task UpdateTimer() { // todo a bigger loop that runs once every XpTimer @@ -164,17 +180,46 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand /// </summary> private readonly ConcurrentHashSet<IGuildUser> _usersBatch = new(); + private readonly ConcurrentHashSet<IGuildUser> _voiceXPBatch = new(); + + private async Task UpdateVoiceXp() + { + var xpAmount = (int)_xpConfig.Data.VoiceXpPerMinute; + var oldBatch = _voiceXPBatch.ToArray(); + _voiceXPBatch.Clear(); + var validUsers = new HashSet<IGuildUser>(); + + var guilds = _client.Guilds; + + foreach (var g in guilds) + foreach (var vc in g.VoiceChannels) + foreach (var u in vc.ConnectedUsers) + if (!u.IsMuted && !u.IsDeafened + && vc.ConnectedUsers.Count(x => !x.IsBot) > 1) + { + if (oldBatch.Contains(u)) + validUsers.Add(u); + + _voiceXPBatch.Add(u); + } + + await UpdateXpInternalAsync(validUsers.DistinctBy(x => x.Id).ToArray(), xpAmount); + } + private async Task UpdateXp() { var xpAmount = _xpConfig.Data.XpPerMessage; var currentBatch = _usersBatch.ToArray(); _usersBatch.Clear(); + await UpdateXpInternalAsync(currentBatch, xpAmount); + } + + private async Task UpdateXpInternalAsync(IGuildUser[] currentBatch, int xpAmount) + { if (currentBatch.Length == 0) return; - var ids = currentBatch.Select(x => x.Id).ToArray(); - await using var ctx = _db.GetDbContext(); await using var lctx = ctx.CreateLinqToDBConnection(); @@ -492,7 +537,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand }); } - uow.SaveChanges(); + await uow.SaveChangesAsync(); } public async Task<IReadOnlyCollection<UserXpStats>> GetGuildUserXps(ulong guildId, int page) @@ -552,29 +597,16 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand return Task.CompletedTask; } - private Task Client_OnUserVoiceStateUpdated(SocketUser socketUser, SocketVoiceState before, SocketVoiceState after) + private async Task Client_OnUserVoiceStateUpdated(SocketUser socketUser, SocketVoiceState before, + SocketVoiceState after) { if (socketUser is not SocketGuildUser user || user.IsBot) - return Task.CompletedTask; + return; - _ = Task.Run(async () => + if (after.VoiceChannel is not null) { - if (before.VoiceChannel is not null) - await ScanChannelForVoiceXp(before.VoiceChannel); - - if (after.VoiceChannel is not null && after.VoiceChannel != before.VoiceChannel) - { - await ScanChannelForVoiceXp(after.VoiceChannel); - } - else if (after.VoiceChannel is null && before.VoiceChannel is not null) - { - // In this case, the user left the channel and the previous for loops didn't catch - // it because it wasn't in any new channel. So we need to get rid of it. - await UserLeftVoiceChannel(user, before.VoiceChannel); - } - }); - - return Task.CompletedTask; + await ScanChannelForVoiceXp(after.VoiceChannel); + } } private async Task ScanChannelForVoiceXp(SocketVoiceChannel channel) @@ -582,27 +614,13 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand if (ShouldTrackVoiceChannel(channel)) { foreach (var user in channel.ConnectedUsers) - await ScanUserForVoiceXp(user, channel); - } - else - { - foreach (var user in channel.ConnectedUsers) - await UserLeftVoiceChannel(user, channel); + { + if (UserParticipatingInVoiceChannel(user) && ShouldTrackXp(user, channel)) + await UserJoinedVoiceChannel(user); + } } } - /// <summary> - /// Assumes that the channel itself is valid and adding xp. - /// </summary> - /// <param name="user"></param> - /// <param name="channel"></param> - private async Task ScanUserForVoiceXp(SocketGuildUser user, SocketVoiceChannel channel) - { - if (UserParticipatingInVoiceChannel(user) && ShouldTrackXp(user, channel)) - await UserJoinedVoiceChannel(user); - else - await UserLeftVoiceChannel(user, channel); - } private bool ShouldTrackVoiceChannel(SocketVoiceChannel channel) => channel.ConnectedUsers.Where(UserParticipatingInVoiceChannel).Take(2).Count() >= 2; @@ -619,84 +637,10 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand await _c.AddAsync(GetVoiceXpKey(user.Id), value, - TimeSpan.FromMinutes(_xpConfig.Data.VoiceMaxMinutes), - overwrite: false); + TimeSpan.FromMinutes(1), + overwrite: true); } - // private void UserJoinedVoiceChannel(SocketGuildUser user) - // { - // var key = $"{_creds.RedisKey()}_user_xp_vc_join_{user.Id}"; - // var value = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - // - // _cache.Redis.GetDatabase() - // .StringSet(key, - // value, - // TimeSpan.FromMinutes(_xpConfig.Data.VoiceMaxMinutes), - // when: When.NotExists); - // } - - private async Task UserLeftVoiceChannel(SocketGuildUser user, SocketVoiceChannel channel) - { - var key = GetVoiceXpKey(user.Id); - var result = await _c.GetAsync(key); - if (!await _c.RemoveAsync(key)) - return; - - // Allow for if this function gets called multiple times when a user leaves a channel. - if (!result.TryGetValue(out var unixTime)) - return; - - var dateStart = DateTimeOffset.FromUnixTimeSeconds(unixTime); - var dateEnd = DateTimeOffset.UtcNow; - var minutes = (dateEnd - dateStart).TotalMinutes; - var xp = _xpConfig.Data.VoiceXpPerMinute * minutes; - var actualXp = (int)Math.Floor(xp); - - if (actualXp > 0) - { - Log.Information("Adding {Amount} voice xp to {User}", actualXp, user.ToString()); - await _xpGainQueue.Writer.WriteAsync(new() - { - Guild = channel.Guild, - User = user, - XpAmount = actualXp, - Channel = channel - }); - } - } - - /* - * private void UserLeftVoiceChannel(SocketGuildUser user, SocketVoiceChannel channel) - { - var key = $"{_creds.RedisKey()}_user_xp_vc_join_{user.Id}"; - var value = _cache.Redis.GetDatabase().StringGet(key); - _cache.Redis.GetDatabase().KeyDelete(key); - - // Allow for if this function gets called multiple times when a user leaves a channel. - if (value.IsNull) - return; - - if (!value.TryParse(out long startUnixTime)) - return; - - var dateStart = DateTimeOffset.FromUnixTimeSeconds(startUnixTime); - var dateEnd = DateTimeOffset.UtcNow; - var minutes = (dateEnd - dateStart).TotalMinutes; - var xp = _xpConfig.Data.VoiceXpPerMinute * minutes; - var actualXp = (int)Math.Floor(xp); - - if (actualXp > 0) - { - _addMessageXp.Enqueue(new() - { - Guild = channel.Guild, - User = user, - XpAmount = actualXp - }); - } - } - */ - private bool ShouldTrackXp(SocketGuildUser user, IMessageChannel channel) { var channelId = channel.Id; @@ -744,20 +688,6 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand return Task.CompletedTask; } - // public void AddXpDirectly(IGuildUser user, IMessageChannel channel, int amount) - // { - // if (amount <= 0) - // throw new ArgumentOutOfRangeException(nameof(amount)); - // - // _xpGainQueue.Writer.WriteAsync(new() - // { - // Guild = user.Guild, - // Channel = channel, - // User = user, - // XpAmount = amount - // }); - // } - public async Task<int> AddXpToUsersAsync(ulong guildId, long amount, params ulong[] userIds) { await using var ctx = _db.GetDbContext();