Skip to content

Commit

Permalink
Merge pull request #612 from Ixrec/zjr
Browse files Browse the repository at this point in the history
prepare for zero join roles migration
  • Loading branch information
Ixrec authored Oct 5, 2024
2 parents a8f8d10 + 504a180 commit 7aea0e6
Show file tree
Hide file tree
Showing 10 changed files with 78 additions and 48 deletions.
4 changes: 4 additions & 0 deletions Izzy-Moonbot/Describers/ConfigDescriber.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ public ConfigDescriber()
new ConfigItem("RolesToReapplyOnRejoin", ConfigItemType.RoleSet,
"The roles I'll reapply to a user when they join **if they had that role when they left**.",
ConfigItemCategory.ManagedRoles));
_config.Add("ZeroJoinRoles",
new ConfigItem("ZeroJoinRoles", ConfigItemType.Boolean,
"TEMPORARY, I HOPE I HOPE.",
ConfigItemCategory.ManagedRoles));


// Filter settings
Expand Down
8 changes: 4 additions & 4 deletions Izzy-Moonbot/EventListeners/UserListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -255,21 +255,21 @@ private async Task MemberUpdateEvent(Cacheable<SocketGuildUser,ulong> oldUser, S
if (_config.MemberRole != null)
{
if (_users[newUser.Id].Silenced &&
newUser.Roles.Select(role => role.Id).Contains((ulong)_config.MemberRole))
!newUser.Roles.Select(role => role.Id).Contains(DiscordHelper.BanishedRoleId))
{
// Unsilenced, Remove the flag.
_logger.Log(
$"{newUser.DisplayName} ({newUser.Username}/{newUser.Id}) unsilenced, removing silence flag...");
$"{newUser.DisplayName} ({newUser.Username}/{newUser.Id}) unbanished, removing silence flag...");
_users[newUser.Id].Silenced = false;
changed = true;
}

if (!_users[newUser.Id].Silenced &&
!newUser.Roles.Select(role => role.Id).Contains((ulong)_config.MemberRole))
newUser.Roles.Select(role => role.Id).Contains(DiscordHelper.BanishedRoleId))
{
// Silenced, add the flag
_logger.Log(
$"{newUser.DisplayName} ({newUser.Username}/{newUser.Id}) silenced, adding silence flag...");
$"{newUser.DisplayName} ({newUser.Username}/{newUser.Id}) banished, adding silence flag...");
_users[newUser.Id].Silenced = true;
changed = true;
}
Expand Down
2 changes: 2 additions & 0 deletions Izzy-Moonbot/Helpers/DiscordHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -362,4 +362,6 @@ public static string DisplayName(IIzzyUser user, IIzzyGuild? guild)
// This is essentially https://stackoverflow.com/a/3809435 with added lookaround for <>s.
public static Regex UnfurlableUrl =
new(@"(?<!<)(https?://(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*))(?!>)");

public static ulong BanishedRoleId = 368961099925553153ul;
}
65 changes: 44 additions & 21 deletions Izzy-Moonbot/Helpers/UserHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public static bool updateUserInfoFromDiscord(User userInfo, SocketGuildUser sock
// - MemberRole (depending on whether the user was silenced when they left)
// - NewMemberRole ("applying" this includes scheduling its automated removal)
// - RolesToReapplyOnRejoin (varies by config)
public record JoinRoleResult(bool userInfoChanged, bool configChanged, HashSet<ulong> rolesAdded, ScheduledJob? newMemberRemovalJob = null);
public record JoinRoleResult(bool userInfoChanged, bool configChanged, HashSet<ulong> rolesAdded, ScheduledJob? newUserRoleUpdateJob = null);

public static async Task<JoinRoleResult> applyJoinRolesToUser(
User userInfo,
Expand All @@ -65,31 +65,54 @@ ScheduleService scheduleService
bool userInfoChanged = false;
bool configChanged = false;
HashSet<ulong> rolesToAddIfMissing = new HashSet<ulong>();
ScheduledJob? newMemberRemovalJob = null;
ScheduledJob? newUserRoleUpdateJob = null;

if (config.ManageNewUserRoles)
{
bool silencingUser = config.AutoSilenceNewJoins || userInfo.Silenced;
if (config.MemberRole != null && config.MemberRole > 0 && !silencingUser)
if (silencingUser)
{
rolesToAddIfMissing.Add((ulong)config.MemberRole);
rolesToAddIfMissing.Add(DiscordHelper.BanishedRoleId);
}

if (config.NewMemberRole != null && config.NewMemberRole > 0 && socketGuildUser.JoinedAt is not null)
if (config.ZeroJoinRoles)
{
var joinTime = socketGuildUser.JoinedAt.Value;
var newMemberExpiry = joinTime.AddMinutes(config.NewMemberRoleDecay);
if (config.MemberRole != null && config.MemberRole > 0 && socketGuildUser.JoinedAt is not null)
{
var joinTime = socketGuildUser.JoinedAt.Value;
var memberAcceptanceTime = joinTime.AddMinutes(config.NewMemberRoleDecay);

if (DateTimeOffset.UtcNow < memberAcceptanceTime)
{
// no change to rolesToAddIfMissing

if (DateTimeOffset.UtcNow < newMemberExpiry)
var action = new ScheduledRoleAdditionJob(config.MemberRole.Value, socketGuildUser.Id,
$"Member role added, {config.NewMemberRoleDecay} minutes (`NewMemberRoleDecay`) passed.");
var task = new ScheduledJob(DateTimeOffset.UtcNow, memberAcceptanceTime, action);
await scheduleService.CreateScheduledJob(task);

newUserRoleUpdateJob = task;
}
}
}
else
{
if (config.NewMemberRole != null && config.NewMemberRole > 0 && socketGuildUser.JoinedAt is not null)
{
rolesToAddIfMissing.Add((ulong)config.NewMemberRole);
var joinTime = socketGuildUser.JoinedAt.Value;
var newMemberExpiry = joinTime.AddMinutes(config.NewMemberRoleDecay);

if (DateTimeOffset.UtcNow < newMemberExpiry)
{
rolesToAddIfMissing.Add((ulong)config.NewMemberRole);

var action = new ScheduledRoleRemovalJob(config.NewMemberRole.Value, socketGuildUser.Id,
$"New member role removal, {config.NewMemberRoleDecay} minutes (`NewMemberRoleDecay`) passed.");
var task = new ScheduledJob(DateTimeOffset.UtcNow, newMemberExpiry, action);
await scheduleService.CreateScheduledJob(task);
var action = new ScheduledRoleRemovalJob(config.NewMemberRole.Value, socketGuildUser.Id,
$"New member role removal, {config.NewMemberRoleDecay} minutes (`NewMemberRoleDecay`) passed.");
var task = new ScheduledJob(DateTimeOffset.UtcNow, newMemberExpiry, action);
await scheduleService.CreateScheduledJob(task);

newMemberRemovalJob = task;
newUserRoleUpdateJob = task;
}
}
}
}
Expand Down Expand Up @@ -121,15 +144,15 @@ ScheduleService scheduleService
rolesToAddIfMissing.UnionWith(userInfo.RolesToReapplyOnRejoin);

if (rolesToAddIfMissing.Count == 0)
return new JoinRoleResult(userInfoChanged, configChanged, [], newMemberRemovalJob);
return new JoinRoleResult(userInfoChanged, configChanged, [], newUserRoleUpdateJob);

HashSet<ulong> existingRoleIds = socketGuildUser.Roles.Select(r => r.Id).ToHashSet();
HashSet<ulong> rolesToActuallyAdd = rolesToAddIfMissing.Where(r => !existingRoleIds.Contains(r)).ToHashSet();
await modService.AddRoles(socketGuildUser, rolesToActuallyAdd, auditLogMessage);
return new JoinRoleResult(userInfoChanged, configChanged, rolesToActuallyAdd, newMemberRemovalJob);
return new JoinRoleResult(userInfoChanged, configChanged, rolesToActuallyAdd, newUserRoleUpdateJob);
}

public record UserScanResult(int totalUsersCount, int updatedUserCount, int newUserCount, Dictionary<ulong, int> roleAddedCounts, HashSet<ulong> newMemberRemovalsScheduled);
public record UserScanResult(int totalUsersCount, int updatedUserCount, int newUserCount, Dictionary<ulong, int> roleAddedCounts, HashSet<ulong> newUserRoleUpdatesScheduled);

public static async Task<UserScanResult> scanAllUsers(
SocketGuild guild,
Expand All @@ -156,7 +179,7 @@ LoggingService logger
var newUserCount = 0;
var updatedUserCount = 0;
Dictionary<ulong, int> roleAddedCounts = new();
HashSet<ulong> newMemberRemovalsScheduled = new();
HashSet<ulong> newUserRoleUpdatesScheduled = new();

bool userInfoChanged = false;
bool configChanged = false;
Expand All @@ -174,8 +197,8 @@ LoggingService logger
var result = await applyJoinRolesToUser(userInfo, socketGuildUser, config, modService, scheduleService);
userInfoChanged |= result.userInfoChanged;
configChanged |= result.configChanged;
if (result.newMemberRemovalJob != null)
newMemberRemovalsScheduled.Add(socketGuildUser.Id);
if (result.newUserRoleUpdateJob != null)
newUserRoleUpdatesScheduled.Add(socketGuildUser.Id);
foreach (var roleId in result.rolesAdded)
if (roleAddedCounts.ContainsKey(roleId)) roleAddedCounts[roleId] += 1;
else roleAddedCounts[roleId] = 1;
Expand Down Expand Up @@ -203,6 +226,6 @@ LoggingService logger
if (userInfoChanged) await FileHelper.SaveUsersAsync(allUserInfo);
// we don't save the schedule file here because scheduling the job already does that; it's likely not worth batching that

return new UserScanResult(totalUsersCount, updatedUserCount, newUserCount, roleAddedCounts, newMemberRemovalsScheduled);
return new UserScanResult(totalUsersCount, updatedUserCount, newUserCount, roleAddedCounts, newUserRoleUpdatesScheduled);
}
}
14 changes: 4 additions & 10 deletions Izzy-Moonbot/Modules/ModMiscModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,9 @@ public async Task ScanCommandAsync()
$"The other {result.totalUsersCount - result.updatedUserCount} were up to date.";
if (result.roleAddedCounts.Count > 0)
reply += $"\nAdded {string.Join(", ", result.roleAddedCounts.Select(rac => $"{rac.Value} <@&{rac.Key}>(s)"))}";
if (result.newMemberRemovalsScheduled.Count > 0)
reply += $"\nScheduled <@&{_config.NewMemberRole!.Value}> removal(s) for {string.Join(", ", result.newMemberRemovalsScheduled.Select(u => $"<@{u}>"))}";
if (result.newUserRoleUpdatesScheduled.Count > 0)
// TODO: change after ZJR
reply += $"\nScheduled <@&{_config.NewMemberRole!.Value}> removal(s) for {string.Join(", ", result.newUserRoleUpdatesScheduled.Select(u => $"<@{u}>"))}";

_logger.Log(reply);
await Context.Message.ReplyAsync(reply, allowedMentions: AllowedMentions.None);
Expand Down Expand Up @@ -198,13 +199,6 @@ public async Task TestableEchoCommandAsync(
[DevCommand(Group = "Permissions")]
public async Task StowawaysCommandAsync()
{
if (_config.MemberRole == null)
{
await ReplyAsync(
"I'm unable to detect stowaways because the `MemberRole` config value is set to nothing.");
return;
}

await Task.Run(async () =>
{
if (!Context.Guild.HasAllMembers) await Context.Guild.DownloadUsersAsync();
Expand All @@ -216,7 +210,7 @@ await Task.Run(async () =>
if (socketGuildUser.IsBot) continue; // Bots aren't stowaways
if (socketGuildUser.Roles.Select(role => role.Id).Contains(_config.ModRole)) continue; // Mods aren't stowaways
if (!socketGuildUser.Roles.Select(role => role.Id).Contains((ulong)_config.MemberRole))
if (socketGuildUser.Roles.Select(role => role.Id).Contains(DiscordHelper.BanishedRoleId))
{
// Doesn't have member role, add to stowaway set.
stowawaySet.Add(socketGuildUser);
Expand Down
12 changes: 4 additions & 8 deletions Izzy-Moonbot/Service/ModService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
Expand Down Expand Up @@ -27,9 +27,7 @@ public async Task SilenceUser(SocketGuildUser user, string? reason)
}
public async Task SilenceUser(IIzzyGuildUser user, string? reason)
{
if (_config.MemberRole == null) throw new TargetException("MemberRole config value is null (not set)");

await user.RemoveRoleAsync((ulong) _config.MemberRole, reason is null ? null : new Discord.RequestOptions { AuditLogReason = reason });
await user.RemoveRoleAsync(DiscordHelper.BanishedRoleId, reason is null ? null : new Discord.RequestOptions { AuditLogReason = reason });

_users[user.Id].Silenced = true;
await FileHelper.SaveUsersAsync(_users);
Expand All @@ -41,11 +39,9 @@ public async Task SilenceUsers(IEnumerable<SocketGuildUser> users, string? reaso
}
public async Task SilenceUsers(IEnumerable<IIzzyGuildUser> users, string? reason)
{
if (_config.MemberRole == null) throw new TargetException("MemberRole config value is null (not set)");

foreach (var user in users)
{
await user.RemoveRoleAsync((ulong)_config.MemberRole, reason is null ? null : new Discord.RequestOptions { AuditLogReason = reason });
await user.AddRoleAsync(DiscordHelper.BanishedRoleId, reason is null ? null : new Discord.RequestOptions { AuditLogReason = reason });

_users[user.Id].Silenced = true;
await FileHelper.SaveUsersAsync(_users);
Expand Down Expand Up @@ -78,4 +74,4 @@ public async Task AddRoles(IIzzyGuildUser user, IEnumerable<ulong> roles, string
{
await user.AddRolesAsync(roles, reason is null ? null : new Discord.RequestOptions { AuditLogReason = reason });
}
}
}
2 changes: 2 additions & 0 deletions Izzy-Moonbot/Settings/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public Config()
NewMemberRole = 0;
NewMemberRoleDecay = 0;
RolesToReapplyOnRejoin = new HashSet<ulong>();
ZeroJoinRoles = false;

// Filter Settings
FilterEnabled = true;
Expand Down Expand Up @@ -141,6 +142,7 @@ public double BannerInterval
public ulong? NewMemberRole { get; set; }
public double NewMemberRoleDecay { get; set; }
public HashSet<ulong> RolesToReapplyOnRejoin { get; set; }
public bool ZeroJoinRoles { get; set; }

// Filter settings
public bool FilterEnabled { get; set; }
Expand Down
9 changes: 5 additions & 4 deletions Izzy-Moonbot/Worker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -517,14 +517,15 @@ private void ResyncUsers()
new LoggingService(_logger)
);
if (result.newUserCount > 0 || result.roleAddedCounts.Count > 0 || result.newMemberRemovalsScheduled.Count > 0)
if (result.newUserCount > 0 || result.roleAddedCounts.Count > 0 || result.newUserRoleUpdatesScheduled.Count > 0)
{
var msg = $"After rebooting I found {result.newUserCount} user(s) who were new to me.";
if (result.roleAddedCounts.Count > 0)
msg += $"\nAdded {string.Join(", ", result.roleAddedCounts.Select(rac => $"{rac.Value} {guild.GetRole(rac.Key).Name}(s)"))}";
if (result.newMemberRemovalsScheduled.Count > 0)
msg += $"\nScheduled `NewMemberRole` removal(s) for {string.Join(", ", result.newMemberRemovalsScheduled.Select(u => $"<@{u}>"))}";
if (result.roleAddedCounts.Count == 0 && result.newMemberRemovalsScheduled.Count == 0)
if (result.newUserRoleUpdatesScheduled.Count > 0)
// TODO: change after ZJR
msg += $"\nScheduled `NewMemberRole` removal(s) for {string.Join(", ", result.newUserRoleUpdatesScheduled.Select(u => $"<@{u}>"))}";
if (result.roleAddedCounts.Count == 0 && result.newUserRoleUpdatesScheduled.Count == 0)
msg += " They required no role changes.";
_logger.LogInformation(msg);
Expand Down
9 changes: 8 additions & 1 deletion Izzy-MoonbotTests/Tests/ConfigCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,13 @@ public async Task ConfigCommand_EditEveryItemAsync()
$"New Pony",
generalChannel.Messages.Last().Content);

// post ".config ZeroJoinRoles true"
Assert.AreEqual(cfg.ZeroJoinRoles, false);
context = await client.AddMessageAsync(guild.Id, generalChannel.Id, sunny.Id, ".config ZeroJoinRoles true");
await ConfigCommand.TestableConfigCommandAsync(context, cfg, cd, "ZeroJoinRoles", "true");
Assert.AreEqual(cfg.ZeroJoinRoles, true);
Assert.AreEqual("I've set `ZeroJoinRoles` to the following content: True", generalChannel.Messages.Last().Content);

// post ".config FilterEnabled false"
Assert.AreEqual(cfg.FilterEnabled, true);
context = await client.AddMessageAsync(guild.Id, generalChannel.Id, sunny.Id, ".config FilterEnabled false");
Expand Down Expand Up @@ -800,7 +807,7 @@ public async Task ConfigCommand_EditEveryItemAsync()
// Ensure we can't forget to keep this test up to date
var configPropsCount = typeof(Config).GetProperties().Length;

Assert.AreEqual(59, configPropsCount,
Assert.AreEqual(60, configPropsCount,
$"\nIf you just added or removed a config item, then this test is probably out of date");

Assert.AreEqual(configPropsCount * 2, generalChannel.Messages.Count(),
Expand Down
1 change: 1 addition & 0 deletions Izzy-MoonbotTests/Tests/FileHelperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ public void ConfigRoundTrip()
"NewMemberRole": 1039194817231601695,
"NewMemberRoleDecay": 120.0,
"RolesToReapplyOnRejoin": [],
"ZeroJoinRoles": false,
"FilterEnabled": true,
"FilterIgnoredChannels": [
964283764240973844
Expand Down

0 comments on commit 7aea0e6

Please sign in to comment.