mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-11-02 00:34:28 -04:00
Fixed .streamrole not updating in real time, closes #345
This commit is contained in:
62
src/NadekoBot/Common/QueueRunner.cs
Normal file
62
src/NadekoBot/Common/QueueRunner.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using System.Threading.Channels;
|
||||||
|
|
||||||
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public sealed class QueueRunner
|
||||||
|
{
|
||||||
|
private readonly Channel<Func<Task>> _channel;
|
||||||
|
private readonly int _delayMs;
|
||||||
|
|
||||||
|
public QueueRunner(int delayMs = 0, int maxCapacity = -1)
|
||||||
|
{
|
||||||
|
if (delayMs < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(delayMs));
|
||||||
|
|
||||||
|
_delayMs = delayMs;
|
||||||
|
_channel = maxCapacity switch
|
||||||
|
{
|
||||||
|
0 or < -1 => throw new ArgumentOutOfRangeException(nameof(maxCapacity)),
|
||||||
|
-1 => Channel.CreateUnbounded<Func<Task>>(new UnboundedChannelOptions()
|
||||||
|
{
|
||||||
|
SingleReader = true,
|
||||||
|
SingleWriter = false,
|
||||||
|
AllowSynchronousContinuations = true,
|
||||||
|
}),
|
||||||
|
_ => Channel.CreateBounded<Func<Task>>(new BoundedChannelOptions(maxCapacity)
|
||||||
|
{
|
||||||
|
Capacity = maxCapacity,
|
||||||
|
FullMode = BoundedChannelFullMode.DropOldest,
|
||||||
|
SingleReader = true,
|
||||||
|
SingleWriter = false,
|
||||||
|
AllowSynchronousContinuations = true
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RunAsync(CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var func = await _channel.Reader.ReadAsync(cancel);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await func();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning(ex, "Exception executing a staggered func: {ErrorMessage}", ex.Message);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_delayMs != 0)
|
||||||
|
{
|
||||||
|
await Task.Delay(_delayMs, cancel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask Enqueue(Func<Task> action)
|
||||||
|
=> _channel.Writer.WriteAsync(action);
|
||||||
|
}
|
||||||
@@ -1,17 +1,20 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
using NadekoBot.Db;
|
using NadekoBot.Db;
|
||||||
using NadekoBot.Modules.Utility.Common;
|
using NadekoBot.Modules.Utility.Common;
|
||||||
using NadekoBot.Modules.Utility.Common.Exceptions;
|
using NadekoBot.Modules.Utility.Common.Exceptions;
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Utility.Services;
|
namespace NadekoBot.Modules.Utility.Services;
|
||||||
|
|
||||||
public class StreamRoleService : INService
|
public class StreamRoleService : IReadyExecutor, INService
|
||||||
{
|
{
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
private readonly ConcurrentDictionary<ulong, StreamRoleSettings> _guildSettings;
|
private readonly ConcurrentDictionary<ulong, StreamRoleSettings> _guildSettings;
|
||||||
|
private readonly QueueRunner _queueRunner;
|
||||||
|
|
||||||
public StreamRoleService(DiscordSocketClient client, DbService db, Bot bot)
|
public StreamRoleService(DiscordSocketClient client, DbService db, Bot bot)
|
||||||
{
|
{
|
||||||
@@ -22,33 +25,35 @@ public class StreamRoleService : INService
|
|||||||
.Where(x => x.Value is { Enabled: true })
|
.Where(x => x.Value is { Enabled: true })
|
||||||
.ToConcurrent();
|
.ToConcurrent();
|
||||||
|
|
||||||
_client.GuildMemberUpdated += Client_GuildMemberUpdated;
|
_client.PresenceUpdated += OnPresenceUpdate;
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
_queueRunner = new QueueRunner();
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await client.Guilds.Select(g => RescanUsers(g)).WhenAll();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task Client_GuildMemberUpdated(Cacheable<SocketGuildUser, ulong> cacheable, SocketGuildUser after)
|
private Task OnPresenceUpdate(SocketUser user, SocketPresence oldPresence, SocketPresence newPresence)
|
||||||
{
|
{
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
//if user wasn't streaming or didn't have a game status at all
|
if (oldPresence.Activities.Count != newPresence.Activities.Count)
|
||||||
if (_guildSettings.TryGetValue(after.Guild.Id, out var setting))
|
{
|
||||||
await RescanUser(after, setting);
|
var guildUsers = _client.Guilds
|
||||||
|
.Select(x => x.GetUser(user.Id));
|
||||||
|
|
||||||
|
foreach (var guildUser in guildUsers)
|
||||||
|
{
|
||||||
|
if (_guildSettings.TryGetValue(guildUser.Guild.Id, out var s))
|
||||||
|
await RescanUser(guildUser, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task OnReadyAsync()
|
||||||
|
=> Task.WhenAll(_client.Guilds.Select(RescanUsers).WhenAll(), _queueRunner.RunAsync());
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds or removes a user from a blacklist or a whitelist in the specified guild.
|
/// Adds or removes a user from a blacklist or a whitelist in the specified guild.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -135,7 +140,7 @@ public class StreamRoleService : INService
|
|||||||
|
|
||||||
streamRoleSettings.Keyword = keyword;
|
streamRoleSettings.Keyword = keyword;
|
||||||
UpdateCache(guild.Id, streamRoleSettings);
|
UpdateCache(guild.Id, streamRoleSettings);
|
||||||
uow.SaveChanges();
|
await uow.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
await RescanUsers(guild);
|
await RescanUsers(guild);
|
||||||
@@ -191,8 +196,7 @@ public class StreamRoleService : INService
|
|||||||
|
|
||||||
foreach (var usr in await fromRole.GetMembersAsync())
|
foreach (var usr in await fromRole.GetMembersAsync())
|
||||||
{
|
{
|
||||||
if (usr is { } x)
|
await RescanUser(usr, setting, addRole);
|
||||||
await RescanUser(x, setting, addRole);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,7 +220,10 @@ public class StreamRoleService : INService
|
|||||||
await RescanUsers(guild);
|
await RescanUsers(guild);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RescanUser(IGuildUser user, StreamRoleSettings setting, IRole addRole = null)
|
private async ValueTask RescanUser(IGuildUser user, StreamRoleSettings setting, IRole addRole = null)
|
||||||
|
=> await _queueRunner.Enqueue(() => RescanUserInternal(user, setting, addRole));
|
||||||
|
|
||||||
|
private async Task RescanUserInternal(IGuildUser user, StreamRoleSettings setting, IRole addRole = null)
|
||||||
{
|
{
|
||||||
if (user.IsBot)
|
if (user.IsBot)
|
||||||
return;
|
return;
|
||||||
@@ -232,58 +239,77 @@ public class StreamRoleService : INService
|
|||||||
&& setting.Blacklist.All(x => x.UserId != user.Id)
|
&& setting.Blacklist.All(x => x.UserId != user.Id)
|
||||||
&& user.RoleIds.Contains(setting.FromRoleId))
|
&& user.RoleIds.Contains(setting.FromRoleId))
|
||||||
{
|
{
|
||||||
try
|
await _queueRunner.Enqueue(async () =>
|
||||||
{
|
{
|
||||||
addRole ??= user.Guild.GetRole(setting.AddRoleId);
|
try
|
||||||
if (addRole is null)
|
{
|
||||||
|
addRole ??= user.Guild.GetRole(setting.AddRoleId);
|
||||||
|
if (addRole is null)
|
||||||
|
{
|
||||||
|
await StopStreamRole(user.Guild);
|
||||||
|
Log.Warning("Stream role in server {RoleId} no longer exists. Stopping", setting.AddRoleId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//check if he doesn't have addrole already, to avoid errors
|
||||||
|
if (!user.RoleIds.Contains(addRole.Id))
|
||||||
|
{
|
||||||
|
await user.AddRoleAsync(addRole);
|
||||||
|
Log.Information("Added stream role to user {User} in {Server} server",
|
||||||
|
user.ToString(),
|
||||||
|
user.Guild.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden)
|
||||||
{
|
{
|
||||||
await StopStreamRole(user.Guild);
|
await StopStreamRole(user.Guild);
|
||||||
Log.Warning("Stream role in server {RoleId} no longer exists. Stopping", setting.AddRoleId);
|
Log.Warning(ex, "Error adding stream role(s). Forcibly disabling stream role feature");
|
||||||
return;
|
throw new StreamRolePermissionException();
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
//check if he doesn't have addrole already, to avoid errors
|
|
||||||
if (!user.RoleIds.Contains(addRole.Id))
|
|
||||||
{
|
{
|
||||||
await user.AddRoleAsync(addRole);
|
Log.Warning(ex, "Failed adding stream role");
|
||||||
Log.Information("Added stream role to user {User} in {Server} server",
|
|
||||||
user.ToString(),
|
|
||||||
user.Guild.ToString());
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden)
|
|
||||||
{
|
|
||||||
await StopStreamRole(user.Guild);
|
|
||||||
Log.Warning(ex, "Error adding stream role(s). Forcibly disabling stream role feature");
|
|
||||||
throw new StreamRolePermissionException();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Warning(ex, "Failed adding stream role");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//check if user is in the addrole
|
//check if user is in the addrole
|
||||||
if (user.RoleIds.Contains(setting.AddRoleId))
|
if (user.RoleIds.Contains(setting.AddRoleId))
|
||||||
{
|
{
|
||||||
try
|
await _queueRunner.Enqueue(async () =>
|
||||||
{
|
{
|
||||||
addRole ??= user.Guild.GetRole(setting.AddRoleId);
|
try
|
||||||
if (addRole is null)
|
{
|
||||||
throw new StreamRoleNotFoundException();
|
addRole ??= user.Guild.GetRole(setting.AddRoleId);
|
||||||
|
if (addRole is null)
|
||||||
|
{
|
||||||
|
await StopStreamRole(user.Guild);
|
||||||
|
Log.Warning(
|
||||||
|
"Addrole doesn't exist in {GuildId} server. Forcibly disabling stream role feature",
|
||||||
|
user.Guild.Id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await user.RemoveRoleAsync(addRole);
|
// need to check again in case queuer is taking too long to execute
|
||||||
Log.Information("Removed stream role from the user {User} in {Server} server",
|
if (user.RoleIds.Contains(setting.AddRoleId))
|
||||||
user.ToString(),
|
{
|
||||||
user.Guild.ToString());
|
await user.RemoveRoleAsync(addRole);
|
||||||
}
|
}
|
||||||
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden)
|
|
||||||
{
|
Log.Information("Removed stream role from the user {User} in {Server} server",
|
||||||
await StopStreamRole(user.Guild);
|
user.ToString(),
|
||||||
Log.Warning(ex, "Error removing stream role(s). Forcibly disabling stream role feature");
|
user.Guild.ToString());
|
||||||
throw new StreamRolePermissionException();
|
}
|
||||||
}
|
catch (HttpException ex)
|
||||||
|
{
|
||||||
|
if (ex.HttpCode == HttpStatusCode.Forbidden)
|
||||||
|
{
|
||||||
|
await StopStreamRole(user.Guild);
|
||||||
|
Log.Warning(ex, "Error removing stream role(s). Forcibly disabling stream role feature");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ public static class Extensions
|
|||||||
|
|
||||||
public static void Lap(this Stopwatch sw, string checkpoint)
|
public static void Lap(this Stopwatch sw, string checkpoint)
|
||||||
{
|
{
|
||||||
Log.Information("Checkpoint {CheckPoint}: {Time}", checkpoint, sw.Elapsed.TotalMilliseconds);
|
Log.Information("Checkpoint {CheckPoint}: {Time}ms", checkpoint, sw.Elapsed.TotalMilliseconds);
|
||||||
sw.Restart();
|
sw.Restart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user