add registered role to bot
This commit is contained in:
@@ -22,7 +22,7 @@ internal class DiscordBot : IHostedService
|
|||||||
private readonly IServiceProvider _services;
|
private readonly IServiceProvider _services;
|
||||||
private InteractionService _interactionModule;
|
private InteractionService _interactionModule;
|
||||||
private readonly CancellationTokenSource? _processReportQueueCts;
|
private readonly CancellationTokenSource? _processReportQueueCts;
|
||||||
private CancellationTokenSource? _updateStatusCts;
|
private CancellationTokenSource? _clientConnectedCts;
|
||||||
|
|
||||||
public DiscordBot(DiscordBotServices botServices, IServiceProvider services, IConfigurationService<ServicesConfiguration> configuration,
|
public DiscordBot(DiscordBotServices botServices, IServiceProvider services, IConfigurationService<ServicesConfiguration> configuration,
|
||||||
IDbContextFactory<MareDbContext> dbContextFactory,
|
IDbContextFactory<MareDbContext> dbContextFactory,
|
||||||
@@ -36,7 +36,8 @@ internal class DiscordBot : IHostedService
|
|||||||
_connectionMultiplexer = connectionMultiplexer;
|
_connectionMultiplexer = connectionMultiplexer;
|
||||||
_discordClient = new(new DiscordSocketConfig()
|
_discordClient = new(new DiscordSocketConfig()
|
||||||
{
|
{
|
||||||
DefaultRetryMode = RetryMode.AlwaysRetry
|
DefaultRetryMode = RetryMode.AlwaysRetry,
|
||||||
|
GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.GuildMembers
|
||||||
});
|
});
|
||||||
|
|
||||||
_discordClient.Log += Log;
|
_discordClient.Log += Log;
|
||||||
@@ -76,7 +77,7 @@ internal class DiscordBot : IHostedService
|
|||||||
{
|
{
|
||||||
await _botServices.Stop().ConfigureAwait(false);
|
await _botServices.Stop().ConfigureAwait(false);
|
||||||
_processReportQueueCts?.Cancel();
|
_processReportQueueCts?.Cancel();
|
||||||
_updateStatusCts?.Cancel();
|
_clientConnectedCts?.Cancel();
|
||||||
|
|
||||||
await _discordClient.LogoutAsync().ConfigureAwait(false);
|
await _discordClient.LogoutAsync().ConfigureAwait(false);
|
||||||
await _discordClient.StopAsync().ConfigureAwait(false);
|
await _discordClient.StopAsync().ConfigureAwait(false);
|
||||||
@@ -88,16 +89,17 @@ internal class DiscordBot : IHostedService
|
|||||||
{
|
{
|
||||||
var guild = (await _discordClient.Rest.GetGuildsAsync().ConfigureAwait(false)).First();
|
var guild = (await _discordClient.Rest.GetGuildsAsync().ConfigureAwait(false)).First();
|
||||||
await _interactionModule.RegisterCommandsToGuildAsync(guild.Id, true).ConfigureAwait(false);
|
await _interactionModule.RegisterCommandsToGuildAsync(guild.Id, true).ConfigureAwait(false);
|
||||||
_updateStatusCts?.Cancel();
|
_clientConnectedCts?.Cancel();
|
||||||
_updateStatusCts?.Dispose();
|
_clientConnectedCts?.Dispose();
|
||||||
_updateStatusCts = new();
|
_clientConnectedCts = new();
|
||||||
_ = UpdateStatusAsync(_updateStatusCts.Token);
|
_ = UpdateStatusAsync(_clientConnectedCts.Token);
|
||||||
|
|
||||||
await CreateOrUpdateModal(guild).ConfigureAwait(false);
|
await CreateOrUpdateModal(guild).ConfigureAwait(false);
|
||||||
_botServices.UpdateGuild(guild);
|
_botServices.UpdateGuild(guild);
|
||||||
await _botServices.LogToChannel("Bot startup complete.").ConfigureAwait(false);
|
await _botServices.LogToChannel("Bot startup complete.").ConfigureAwait(false);
|
||||||
_ = UpdateVanityRoles(guild, _updateStatusCts.Token);
|
_ = UpdateVanityRoles(guild, _clientConnectedCts.Token);
|
||||||
_ = RemoveUsersNotInVanityRole(_updateStatusCts.Token);
|
_ = RemoveUsersNotInVanityRole(_clientConnectedCts.Token);
|
||||||
|
_ = RemoveUnregisteredUsers(_clientConnectedCts.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateVanityRoles(RestGuild guild, CancellationToken token)
|
private async Task UpdateVanityRoles(RestGuild guild, CancellationToken token)
|
||||||
@@ -207,10 +209,66 @@ internal class DiscordBot : IHostedService
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task RemoveUnregisteredUsers(CancellationToken token)
|
||||||
|
{
|
||||||
|
var guild = (await _discordClient.Rest.GetGuildsAsync().ConfigureAwait(false)).First();
|
||||||
|
while (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await ProcessUserRoles(guild, token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _botServices.LogToChannel($"Error during user procesing: {ex.Message}").ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(TimeSpan.FromDays(1)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ProcessUserRoles(RestGuild guild, CancellationToken token)
|
||||||
|
{
|
||||||
|
using MareDbContext dbContext = await _dbContextFactory.CreateDbContextAsync(token).ConfigureAwait(false);
|
||||||
|
var roleId = _configurationService.GetValueOrDefault<ulong?>(nameof(ServicesConfiguration.DiscordRoleRegistered), 0);
|
||||||
|
var kickUnregistered = _configurationService.GetValueOrDefault(nameof(ServicesConfiguration.KickNonRegisteredUsers), false);
|
||||||
|
if (roleId == null) return;
|
||||||
|
|
||||||
|
var registrationRole = guild.Roles.FirstOrDefault(f => f.Id == roleId.Value);
|
||||||
|
var registeredUsers = new HashSet<ulong>(await dbContext.LodeStoneAuth.AsNoTracking().Select(c => c.DiscordId).ToListAsync().ConfigureAwait(false));
|
||||||
|
|
||||||
|
var executionStartTime = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
|
await _botServices.LogToChannel($"Starting to process registered users: Adding Role {registrationRole.Name}. Kick Stale Unregistered: {kickUnregistered}.").ConfigureAwait(false);
|
||||||
|
|
||||||
|
await foreach (var userList in guild.GetUsersAsync(new RequestOptions { CancelToken = token }).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Processing chunk of {count} users", userList.Count);
|
||||||
|
foreach (var user in userList)
|
||||||
|
{
|
||||||
|
if (registeredUsers.Contains(user.Id))
|
||||||
|
await _botServices.AddRegisteredRoleAsync(user, registrationRole).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (kickUnregistered)
|
||||||
|
{
|
||||||
|
if ((executionStartTime - user.JoinedAt.Value).TotalDays > 7)
|
||||||
|
await _botServices.KickUserAsync(user).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _botServices.LogToChannel("Processing registered users finished").ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task RemoveUsersNotInVanityRole(CancellationToken token)
|
private async Task RemoveUsersNotInVanityRole(CancellationToken token)
|
||||||
{
|
{
|
||||||
var guild = (await _discordClient.Rest.GetGuildsAsync().ConfigureAwait(false)).First();
|
var guild = (await _discordClient.Rest.GetGuildsAsync().ConfigureAwait(false)).First();
|
||||||
var appId = await _discordClient.GetApplicationInfoAsync().ConfigureAwait(false);
|
|
||||||
|
|
||||||
while (!token.IsCancellationRequested)
|
while (!token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using Discord;
|
||||||
|
using Discord.Net;
|
||||||
using Discord.Rest;
|
using Discord.Rest;
|
||||||
|
using Discord.WebSocket;
|
||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
|
using MareSynchronosShared.Models;
|
||||||
using MareSynchronosShared.Services;
|
using MareSynchronosShared.Services;
|
||||||
using MareSynchronosShared.Utils.Configuration;
|
using MareSynchronosShared.Utils.Configuration;
|
||||||
|
using StackExchange.Redis;
|
||||||
|
|
||||||
namespace MareSynchronosServices.Discord;
|
namespace MareSynchronosServices.Discord;
|
||||||
|
|
||||||
@@ -51,6 +56,7 @@ public class DiscordBotServices
|
|||||||
public async Task LogToChannel(string msg)
|
public async Task LogToChannel(string msg)
|
||||||
{
|
{
|
||||||
if (_guild == null) return;
|
if (_guild == null) return;
|
||||||
|
Logger.LogInformation("LogToChannel: {msg}", msg);
|
||||||
var logChannelId = _configuration.GetValueOrDefault<ulong?>(nameof(ServicesConfiguration.DiscordChannelForBotLog), null);
|
var logChannelId = _configuration.GetValueOrDefault<ulong?>(nameof(ServicesConfiguration.DiscordChannelForBotLog), null);
|
||||||
if (logChannelId == null) return;
|
if (logChannelId == null) return;
|
||||||
if (logChannelId != _logChannelId)
|
if (logChannelId != _logChannelId)
|
||||||
@@ -67,10 +73,74 @@ public class DiscordBotServices
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_logChannel == null) return;
|
if (_logChannel == null) return;
|
||||||
|
|
||||||
await _logChannel.SendMessageAsync(msg).ConfigureAwait(false);
|
await _logChannel.SendMessageAsync(msg).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task RetryAsync(Task action, IUser user, string operation, bool logInfoToChannel = true)
|
||||||
|
{
|
||||||
|
int retryCount = 0;
|
||||||
|
int maxRetries = 5;
|
||||||
|
var retryDelay = TimeSpan.FromSeconds(5);
|
||||||
|
|
||||||
|
while (retryCount < maxRetries)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await action.ConfigureAwait(false);
|
||||||
|
if (logInfoToChannel)
|
||||||
|
await LogToChannel($"{user.Mention} {operation} SUCCESS").ConfigureAwait(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (RateLimitedException)
|
||||||
|
{
|
||||||
|
retryCount++;
|
||||||
|
await LogToChannel($"{user.Mention} {operation} RATELIMIT, retry {retryCount} in {retryDelay}.").ConfigureAwait(false);
|
||||||
|
await Task.Delay(retryDelay).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await LogToChannel($"{user.Mention} {operation} FAILED: {ex.Message}").ConfigureAwait(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retryCount == maxRetries)
|
||||||
|
{
|
||||||
|
await LogToChannel($"{user.Mention} FAILED: RetryCount exceeded.").ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveRegisteredRoleAsync(IUser user)
|
||||||
|
{
|
||||||
|
var registeredRole = _configuration.GetValueOrDefault<ulong?>(nameof(ServicesConfiguration.DiscordRoleRegistered), null);
|
||||||
|
if (registeredRole == null) return;
|
||||||
|
var restUser = await _guild.GetUserAsync(user.Id).ConfigureAwait(false);
|
||||||
|
if (restUser == null) return;
|
||||||
|
if (!restUser.RoleIds.Contains(registeredRole.Value)) return;
|
||||||
|
await RetryAsync(restUser.RemoveRoleAsync(registeredRole.Value), user, $"Remove Registered Role").ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddRegisteredRoleAsync(IUser user)
|
||||||
|
{
|
||||||
|
var registeredRole = _configuration.GetValueOrDefault<ulong?>(nameof(ServicesConfiguration.DiscordRoleRegistered), null);
|
||||||
|
if (registeredRole == null) return;
|
||||||
|
var restUser = await _guild.GetUserAsync(user.Id).ConfigureAwait(false);
|
||||||
|
if (restUser == null) return;
|
||||||
|
if (restUser.RoleIds.Contains(registeredRole.Value)) return;
|
||||||
|
await RetryAsync(restUser.AddRoleAsync(registeredRole.Value), user, $"Add Registered Role").ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddRegisteredRoleAsync(RestGuildUser user, RestRole role)
|
||||||
|
{
|
||||||
|
if (user.RoleIds.Contains(role.Id)) return;
|
||||||
|
await RetryAsync(user.AddRoleAsync(role), user, $"Add Registered Role", false).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task KickUserAsync(RestGuildUser user)
|
||||||
|
{
|
||||||
|
await RetryAsync(user.KickAsync("No registration found"), user, "Kick").ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ProcessVerificationQueue()
|
private async Task ProcessVerificationQueue()
|
||||||
{
|
{
|
||||||
while (!verificationTaskCts.IsCancellationRequested)
|
while (!verificationTaskCts.IsCancellationRequested)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using Discord;
|
using Discord;
|
||||||
using MareSynchronosShared.Utils;
|
using MareSynchronosShared.Utils;
|
||||||
using MareSynchronosShared.Utils.Configuration;
|
using MareSynchronosShared.Utils.Configuration;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
|
||||||
namespace MareSynchronosServices.Discord;
|
namespace MareSynchronosServices.Discord;
|
||||||
|
|
||||||
@@ -100,6 +101,8 @@ public partial class MareWizardModule
|
|||||||
await ModifyModalInteraction(eb, cb).ConfigureAwait(false);
|
await ModifyModalInteraction(eb, cb).ConfigureAwait(false);
|
||||||
|
|
||||||
await _botServices.LogToChannel($"{Context.User.Mention} DELETE SUCCESS: {uid}").ConfigureAwait(false);
|
await _botServices.LogToChannel($"{Context.User.Mention} DELETE SUCCESS: {uid}").ConfigureAwait(false);
|
||||||
|
|
||||||
|
await _botServices.RemoveRegisteredRoleAsync(Context.Interaction.User).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ using MareSynchronosShared.Data;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using MareSynchronosShared.Utils;
|
using MareSynchronosShared.Utils;
|
||||||
using MareSynchronosShared.Models;
|
using MareSynchronosShared.Models;
|
||||||
|
using MareSynchronosShared.Services;
|
||||||
|
using MareSynchronosShared.Utils.Configuration;
|
||||||
|
using Discord.Rest;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
|
||||||
namespace MareSynchronosServices.Discord;
|
namespace MareSynchronosServices.Discord;
|
||||||
|
|
||||||
@@ -140,6 +144,8 @@ public partial class MareWizardModule
|
|||||||
+ Environment.NewLine
|
+ Environment.NewLine
|
||||||
+ "Have fun.");
|
+ "Have fun.");
|
||||||
AddHome(cb);
|
AddHome(cb);
|
||||||
|
|
||||||
|
await _botServices.AddRegisteredRoleAsync(Context.Interaction.User).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -129,6 +129,8 @@ public partial class MareWizardModule
|
|||||||
+ Environment.NewLine
|
+ Environment.NewLine
|
||||||
+ "Have fun.");
|
+ "Have fun.");
|
||||||
AddHome(cb);
|
AddHome(cb);
|
||||||
|
|
||||||
|
await _botServices.AddRegisteredRoleAsync(Context.Interaction.User).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ public class ServicesConfiguration : MareConfigurationBase
|
|||||||
public ulong? DiscordChannelForCommands { get; set; } = null;
|
public ulong? DiscordChannelForCommands { get; set; } = null;
|
||||||
public ulong? DiscordRoleAprilFools2024 { get; set; } = null;
|
public ulong? DiscordRoleAprilFools2024 { get; set; } = null;
|
||||||
public ulong? DiscordChannelForBotLog { get; set; } = null!;
|
public ulong? DiscordChannelForBotLog { get; set; } = null!;
|
||||||
|
public ulong? DiscordRoleRegistered { get; set; } = null!;
|
||||||
|
public bool KickNonRegisteredUsers { get; set; } = false;
|
||||||
public Uri MainServerAddress { get; set; } = null;
|
public Uri MainServerAddress { get; set; } = null;
|
||||||
public Dictionary<ulong, string> VanityRoles { get; set; } = new Dictionary<ulong, string>();
|
public Dictionary<ulong, string> VanityRoles { get; set; } = new Dictionary<ulong, string>();
|
||||||
|
|
||||||
@@ -21,6 +23,8 @@ public class ServicesConfiguration : MareConfigurationBase
|
|||||||
sb.AppendLine($"{nameof(DiscordChannelForMessages)} => {DiscordChannelForMessages}");
|
sb.AppendLine($"{nameof(DiscordChannelForMessages)} => {DiscordChannelForMessages}");
|
||||||
sb.AppendLine($"{nameof(DiscordChannelForCommands)} => {DiscordChannelForCommands}");
|
sb.AppendLine($"{nameof(DiscordChannelForCommands)} => {DiscordChannelForCommands}");
|
||||||
sb.AppendLine($"{nameof(DiscordRoleAprilFools2024)} => {DiscordRoleAprilFools2024}");
|
sb.AppendLine($"{nameof(DiscordRoleAprilFools2024)} => {DiscordRoleAprilFools2024}");
|
||||||
|
sb.AppendLine($"{nameof(DiscordRoleRegistered)} => {DiscordRoleRegistered}");
|
||||||
|
sb.AppendLine($"{nameof(KickNonRegisteredUsers)} => {KickNonRegisteredUsers}");
|
||||||
foreach (var role in VanityRoles)
|
foreach (var role in VanityRoles)
|
||||||
{
|
{
|
||||||
sb.AppendLine($"{nameof(VanityRoles)} => {role.Key} = {role.Value}");
|
sb.AppendLine($"{nameof(VanityRoles)} => {role.Key} = {role.Value}");
|
||||||
|
|||||||
Reference in New Issue
Block a user