wip reimplementation of the voicexp

This commit is contained in:
Toastie 2025-02-06 12:54:30 +13:00
parent 5d9326b65e
commit dbc312dd9d
Signed by: toastie_t0ast
GPG key ID: 0861BE54AD481DC7
2 changed files with 61 additions and 131 deletions
src/EllieBot

View file

@ -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>

View file

@ -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();