From 8534b82517018bae278a429b3bf6462e347d8def Mon Sep 17 00:00:00 2001 From: FloatingMilkshake Date: Sun, 1 Dec 2024 14:01:16 -0500 Subject: [PATCH 1/6] Migrate from joinwatch to notes Deprecate joinwatch commands, add show on join/leave option to notes, migrate joinwatches to notes on bot startup --- .../JoinwatchInteractions.cs | 71 ++----------------- .../UserNoteInteractions.cs | 12 +++- Commands/Lists.cs | 41 +---------- Events/MemberEvents.cs | 49 +++++++------ Helpers/UserNoteHelpers.cs | 1 + Program.cs | 41 +++++++++++ Structs.cs | 3 + 7 files changed, 89 insertions(+), 129 deletions(-) diff --git a/Commands/InteractionCommands/JoinwatchInteractions.cs b/Commands/InteractionCommands/JoinwatchInteractions.cs index 2bb23bfd..be1cbc9b 100644 --- a/Commands/InteractionCommands/JoinwatchInteractions.cs +++ b/Commands/InteractionCommands/JoinwatchInteractions.cs @@ -8,85 +8,24 @@ public class JoinwatchSlashCmds { [SlashCommand("add", "Watch for joins and leaves of a given user. Output goes to #investigations.")] public async Task JoinwatchAdd(InteractionContext ctx, - [Option("user", "The user to watch for joins and leaves of.")] DiscordUser user, - [Option("note", "An optional note for context.")] string note = "") + [Option("user", "The user to watch for joins and leaves of.")] DiscordUser _, + [Option("note", "An optional note for context.")] string __ = "") { - var joinWatchlist = await Program.db.ListRangeAsync("joinWatchedUsers"); - - if (joinWatchlist.Contains(user.Id)) - { - // User is already watched - - // Get current note; if it's the same, do nothing - var currentNote = await Program.db.HashGetAsync("joinWatchedUsersNotes", user.Id); - if (currentNote == note || (string.IsNullOrWhiteSpace(currentNote) && string.IsNullOrWhiteSpace(note))) - { - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} {user.Mention} is already being watched with the same note! Nothing to do."); - return; - } - - // If note is different, update it - - // If new note is empty, remove instead of changing to empty string! - if (string.IsNullOrWhiteSpace(note)) - { - await Program.db.HashDeleteAsync("joinWatchedUsersNotes", user.Id); - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Successfully removed the note for {user.Mention}! They are still being watched."); - } - else - { - await Program.db.HashSetAsync("joinWatchedUsersNotes", user.Id, note); - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Successfully updated the note for {user.Mention}:\n> {note}"); - } - } - else - { - // User is not joinwatched, watch - await Program.db.ListRightPushAsync("joinWatchedUsers", user.Id); - if (note != "") - await Program.db.HashSetAsync("joinWatchedUsersNotes", user.Id, note); - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Now watching for joins/leaves of {user.Mention} to send to the investigations channel" - + (note == "" ? "!" : $" with the following note:\n>>> {note}")); - } + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} This command is deprecated and no longer works; all joinwatches have been converted to notes. Please use `/note add` instead, with the `show_on_join_and_leave` option."); } [SlashCommand("remove", "Stop watching for joins and leaves of a user.")] public async Task JoinwatchRemove(InteractionContext ctx, [Option("user", "The user to stop watching for joins and leaves of.")] DiscordUser user) { - var joinWatchlist = await Program.db.ListRangeAsync("joinWatchedUsers"); - - // Check user watch status first; error if not watched - if (!joinWatchlist.Contains(user.Id)) - { - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} {user.Mention} is not being watched! Nothing to do."); - return; - } - - Program.db.ListRemove("joinWatchedUsers", joinWatchlist.First(x => x == user.Id)); - await Program.db.HashDeleteAsync("joinWatchedUsersNotes", user.Id); - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Successfully unwatched {user.Mention}!"); + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} This command is deprecated and no longer works; all joinwatches have been converted to notes. Please use `/note remove` instead."); } [SlashCommand("status", "Check the joinwatch status for a user.")] public async Task JoinwatchStatus(InteractionContext ctx, [Option("user", "The user whose joinwatch status to check.")] DiscordUser user) { - var joinWatchlist = await Program.db.ListRangeAsync("joinWatchedUsers"); - - if (joinWatchlist.Contains(user.Id)) - { - var note = await Program.db.HashGetAsync("joinWatchedUsersNotes", user.Id); - - if (string.IsNullOrWhiteSpace(note)) - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Information} {user.Mention} is currently being watched, but no note is set."); - else - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Information} {user.Mention} is currently being watched with the following note:\n> {note}"); - } - else - { - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} {user.Mention} is not being watched!"); - } + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} This command is deprecated and no longer works; all joinwatches have been converted to notes. Please use `/note list` or `/note details` instead."); } } } diff --git a/Commands/InteractionCommands/UserNoteInteractions.cs b/Commands/InteractionCommands/UserNoteInteractions.cs index 58bf8627..d39040a1 100644 --- a/Commands/InteractionCommands/UserNoteInteractions.cs +++ b/Commands/InteractionCommands/UserNoteInteractions.cs @@ -15,7 +15,8 @@ public async Task AddUserNoteAsync(InteractionContext ctx, [Option("show_on_modmail", "Whether to show the note when the user opens a modmail thread. Default: true")] bool showOnModmail = true, [Option("show_on_warn", "Whether to show the note when the user is warned. Default: true")] bool showOnWarn = true, [Option("show_all_mods", "Whether to show this note to all mods, versus just yourself. Default: true")] bool showAllMods = true, - [Option("show_once", "Whether to show this note once and then discard it. Default: false")] bool showOnce = false) + [Option("show_once", "Whether to show this note once and then discard it. Default: false")] bool showOnce = false, + [Option("show_on_join_and_leave", "Whether to show this note when the user joins & leaves. Works like joinwatch. Default: false")] bool showOnJoinAndLeave = false) { await ctx.DeferAsync(); @@ -30,6 +31,7 @@ public async Task AddUserNoteAsync(InteractionContext ctx, ShowOnWarn = showOnWarn, ShowAllMods = showAllMods, ShowOnce = showOnce, + ShowOnJoinAndLeave = showOnJoinAndLeave, NoteId = noteId, Timestamp = DateTime.Now, Type = WarningType.Note @@ -88,7 +90,8 @@ public async Task EditUserNoteAsync(InteractionContext ctx, [Option("show_on_modmail", "Whether to show the note when the user opens a modmail thread.")] bool? showOnModmail = null, [Option("show_on_warn", "Whether to show the note when the user is warned.")] bool? showOnWarn = null, [Option("show_all_mods", "Whether to show this note to all mods, versus just yourself.")] bool? showAllMods = null, - [Option("show_once", "Whether to show this note once and then discard it.")] bool? showOnce = null) + [Option("show_once", "Whether to show this note once and then discard it.")] bool? showOnce = null, + [Option("show_on_join_and_leave", "Whether to show this note when the user joins & leaves. Works like joinwatch. Default: false")] bool? showOnJoinAndLeave = false) { // Get note UserNote note; @@ -107,7 +110,7 @@ public async Task EditUserNoteAsync(InteractionContext ctx, newNoteText = note.NoteText; // If no changes are made, refuse the request - if (note.NoteText == newNoteText && showOnModmail is null && showOnWarn is null && showAllMods is null && showOnce is null) + if (note.NoteText == newNoteText && showOnModmail is null && showOnWarn is null && showAllMods is null && showOnce is null && showOnJoinAndLeave is null) { await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} You didn't change anything about the note!").AsEphemeral()); return; @@ -129,6 +132,8 @@ public async Task EditUserNoteAsync(InteractionContext ctx, showAllMods = note.ShowAllMods; if (showOnce is null) showOnce = note.ShowOnce; + if (showOnJoinAndLeave is null) + showOnJoinAndLeave = note.ShowOnJoinAndLeave; // Assemble new note note.ModUserId = ctx.User.Id; @@ -137,6 +142,7 @@ public async Task EditUserNoteAsync(InteractionContext ctx, note.ShowOnWarn = (bool)showOnWarn; note.ShowAllMods = (bool)showAllMods; note.ShowOnce = (bool)showOnce; + note.ShowOnJoinAndLeave = (bool)showOnJoinAndLeave; note.Type = WarningType.Note; await Program.db.HashSetAsync(user.Id.ToString(), note.NoteId, JsonConvert.SerializeObject(note)); diff --git a/Commands/Lists.cs b/Commands/Lists.cs index fdc7903b..78ef8d9f 100644 --- a/Commands/Lists.cs +++ b/Commands/Lists.cs @@ -169,46 +169,11 @@ public async Task ScamCheck(CommandContext ctx, [RemainingText, Description("Dom [HomeServer, RequireHomeserverPerm(ServerPermLevel.TrialModerator)] public async Task JoinWatch( CommandContext ctx, - [Description("The user to watch for joins and leaves of.")] DiscordUser user, - [Description("An optional note for context."), RemainingText] string note = "" + [Description("The user to watch for joins and leaves of.")] DiscordUser _, + [Description("An optional note for context."), RemainingText] string __ = "" ) { - var joinWatchlist = await Program.db.ListRangeAsync("joinWatchedUsers"); - - if (joinWatchlist.Contains(user.Id)) - { - if (note != "") - { - // User is already joinwatched, just update note - - // Get current note; if it's the same, do nothing - var currentNote = await Program.db.HashGetAsync("joinWatchedUsersNotes", user.Id); - if (currentNote == note || (string.IsNullOrWhiteSpace(currentNote) && string.IsNullOrWhiteSpace(note))) - { - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} {user.Mention} is already being watched with the same note! Nothing to do."); - return; - } - - // If note is different, update it - await Program.db.HashSetAsync("joinWatchedUsersNotes", user.Id, note); - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Successfully updated the note for {user.Mention} (run again with no note to unwatch):\n> {note}"); - return; - } - - // User is already joinwatched, unwatch - Program.db.ListRemove("joinWatchedUsers", joinWatchlist.First(x => x == user.Id)); - await Program.db.HashDeleteAsync("joinWatchedUsersNotes", user.Id); - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Successfully unwatched {user.Mention}, since they were already in the list."); - } - else - { - // User is not joinwatched, watch - await Program.db.ListRightPushAsync("joinWatchedUsers", user.Id); - if (note != "") - await Program.db.HashSetAsync("joinWatchedUsersNotes", user.Id, note); - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Now watching for joins/leaves of {user.Mention} to send to the investigations channel" - + (note == "" ? "!" : $" with the following note:\n>>> {note}")); - } + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} This command is deprecated and no longer works; all joinwatches have been converted to notes. Please use `/note add` instead, with the `show_on_join_and_leave` option."); } [Command("appealblock")] diff --git a/Events/MemberEvents.cs b/Events/MemberEvents.cs index 77d57768..58418580 100644 --- a/Events/MemberEvents.cs +++ b/Events/MemberEvents.cs @@ -11,7 +11,7 @@ public static async Task GuildMemberAdded(DiscordClient client, GuildMemberAdded if (e.Guild.Id != cfgjson.ServerID) return; - var embed = new DiscordEmbedBuilder() + var userLogEmbed = new DiscordEmbedBuilder() .WithColor(new DiscordColor(0x3E9D28)) .WithTimestamp(DateTimeOffset.Now) .WithThumbnail(e.Member.AvatarUrl) @@ -24,18 +24,20 @@ public static async Task GuildMemberAdded(DiscordClient client, GuildMemberAdded .AddField("Action", "Joined the server", false) .WithFooter($"{client.CurrentUser.Username}JoinEvent"); - LogChannelHelper.LogMessageAsync("users", $"{cfgjson.Emoji.UserJoin} **Member joined the server!** - {e.Member.Id}", embed); + LogChannelHelper.LogMessageAsync("users", $"{cfgjson.Emoji.UserJoin} **Member joined the server!** - {e.Member.Id}", userLogEmbed); - var joinWatchlist = await db.ListRangeAsync("joinWatchedUsers"); + // Get this user's notes that are set to show on join/leave + var userNotes = db.HashGetAll(e.Member.Id.ToString()) + .Where(x => JsonConvert.DeserializeObject(x.Value).Type == WarningType.Note + && JsonConvert.DeserializeObject(x.Value).ShowOnJoinAndLeave).ToDictionary( + x => x.Name.ToString(), + x => JsonConvert.DeserializeObject(x.Value) + ); - if (joinWatchlist.Contains(e.Member.Id)) + if (userNotes.Count > 0) { - if (await db.HashExistsAsync("joinWatchedUsersNotes", e.Member.Id)) - { - embed.AddField($"Joinwatch Note", await db.HashGetAsync("joinWatchedUsersNotes", e.Member.Id)); - } - - LogChannelHelper.LogMessageAsync("investigations", $"{cfgjson.Emoji.Warning} Watched user {e.Member.Mention} just joined the server!", embed); + var notesEmbed = await UserNoteHelpers.GenerateUserNotesEmbedAsync(e.Member, false, userNotes); + LogChannelHelper.LogMessageAsync("investigations", $"{cfgjson.Emoji.Warning} {e.Member.Mention} just joined the server with notes set to show on join!", notesEmbed); } if (db.HashExists("raidmode", e.Guild.Id)) @@ -155,7 +157,7 @@ public static async Task GuildMemberRemoved(DiscordClient client, GuildMemberRem } } - var embed = new DiscordEmbedBuilder() + var userLogEmbed = new DiscordEmbedBuilder() .WithColor(new DiscordColor(0xBA4119)) .WithTimestamp(DateTimeOffset.Now) .WithThumbnail(e.Member.AvatarUrl) @@ -169,18 +171,21 @@ public static async Task GuildMemberRemoved(DiscordClient client, GuildMemberRem .AddField("Roles", rolesStr) .WithFooter($"{client.CurrentUser.Username}LeaveEvent"); - LogChannelHelper.LogMessageAsync("users", $"{cfgjson.Emoji.UserLeave} **Member left the server!** - {e.Member.Id}", embed); - - var joinWatchlist = await db.ListRangeAsync("joinWatchedUsers"); - - if (joinWatchlist.Contains(e.Member.Id)) + LogChannelHelper.LogMessageAsync("users", $"{cfgjson.Emoji.UserLeave} **Member left the server!** - {e.Member.Id}", userLogEmbed); + + // Get this user's notes that are set to show on join/leave + var userNotes = db.HashGetAll(e.Member.Id.ToString()) + .Where(x => JsonConvert.DeserializeObject(x.Value).Type == WarningType.Note + && JsonConvert.DeserializeObject(x.Value).ShowOnJoinAndLeave).ToDictionary( + x => x.Name.ToString(), + x => JsonConvert.DeserializeObject(x.Value) + ); + + DiscordEmbed notesEmbed; + if (userNotes.Count > 0) { - if (await db.HashExistsAsync("joinWatchedUsersNotes", e.Member.Id)) - { - embed.AddField($"Joinwatch Note", await db.HashGetAsync("joinWatchedUsersNotes", e.Member.Id)); - } - - LogChannelHelper.LogMessageAsync("investigations", $"{cfgjson.Emoji.Warning} Watched user {e.Member.Mention} just left the server!", embed); + notesEmbed = await UserNoteHelpers.GenerateUserNotesEmbedAsync(e.Member, false, userNotes); + LogChannelHelper.LogMessageAsync("investigations", $"{cfgjson.Emoji.Warning} {e.Member.Mention} just left the server with notes set to show on leave!", notesEmbed); } } diff --git a/Helpers/UserNoteHelpers.cs b/Helpers/UserNoteHelpers.cs index 2f939d6a..7da45f30 100644 --- a/Helpers/UserNoteHelpers.cs +++ b/Helpers/UserNoteHelpers.cs @@ -120,6 +120,7 @@ await LykosAvatarMethods.UserOrMemberAvatarURL(user, Program.homeGuild, "png") .AddField("Show on Warn", note.ShowOnWarn ? "Yes" : "No", true) .AddField("Show all Mods", note.ShowAllMods ? "Yes" : "No", true) .AddField("Show Once", note.ShowOnce ? "Yes" : "No", true) + .AddField("Show on Join & Leave", note.ShowOnJoinAndLeave ? "Yes" : "No", true) .AddField("Responsible moderator", $"<@{note.ModUserId}>", true) .AddField("Time", $"", true); diff --git a/Program.cs b/Program.cs index c17bef67..8353981e 100644 --- a/Program.cs +++ b/Program.cs @@ -233,6 +233,47 @@ static async Task Main(string[] _) await ReadyEvent.OnStartup(discord); + // Migration from joinwatch to user notes + var joinWatchedUsersList = await Program.db.ListRangeAsync("joinWatchedUsers"); + var joinWatchNotesList = await Program.db.HashGetAllAsync("joinWatchedUsersNotes"); + int successfulMigrations = 0; + int numJoinWatches = joinWatchedUsersList.Length; + foreach (var user in joinWatchedUsersList) + { + // Get text for note; use joinwatch context if available, or "N/A; created from joinwatch without context" otherwise + string noteText; + if (joinWatchNotesList.FirstOrDefault(x => x.Name == user) == default) + noteText = "N/A; created from joinwatch without context"; + else + noteText = joinWatchNotesList.First(x => x.Name == user).Value; + + // Construct note + var note = new UserNote + { + TargetUserId = Convert.ToUInt64(user), + ModUserId = discord.CurrentUser.Id, + NoteText = noteText, + ShowOnModmail = false, + ShowOnWarn = false, + ShowAllMods = false, + ShowOnce = false, + ShowOnJoinAndLeave = true, + NoteId = db.StringIncrement("totalWarnings"), + Timestamp = DateTime.Now, + Type = WarningType.Note + }; + + // Save note & remove joinwatch + await db.HashSetAsync(note.TargetUserId.ToString(), note.NoteId, JsonConvert.SerializeObject(note)); + await db.ListRemoveAsync("joinWatchedUsers", note.TargetUserId); + await db.HashDeleteAsync("joinWatchedUsersNotes", note.TargetUserId); + successfulMigrations++; + } + if (successfulMigrations > 0) + { + discord.Logger.LogInformation(CliptokEventID, "Successfully migrated {count}/{total} joinwatches to notes.", successfulMigrations, numJoinWatches); + } + if (cfgjson.ForumChannelAutoWarnFallbackChannel != 0) ForumChannelAutoWarnFallbackChannel = await discord.GetChannelAsync(cfgjson.ForumChannelAutoWarnFallbackChannel); diff --git a/Structs.cs b/Structs.cs index f2c20af0..f6fc344a 100644 --- a/Structs.cs +++ b/Structs.cs @@ -587,6 +587,9 @@ public class UserNote [JsonProperty("showOnce")] public bool ShowOnce { get; set; } + [JsonProperty("showOnJoinAndLeave")] + public bool ShowOnJoinAndLeave { get; set; } + [JsonProperty("noteId")] public long NoteId { get; set; } From 951242294b4ac27ee9ae8c4c165bc4f39ae72d81 Mon Sep 17 00:00:00 2001 From: Milkshake Date: Sun, 1 Dec 2024 14:47:33 -0500 Subject: [PATCH 2/6] Accept review suggestion Co-authored-by: Erisa A --- Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Program.cs b/Program.cs index 8353981e..8f242413 100644 --- a/Program.cs +++ b/Program.cs @@ -240,10 +240,10 @@ static async Task Main(string[] _) int numJoinWatches = joinWatchedUsersList.Length; foreach (var user in joinWatchedUsersList) { - // Get text for note; use joinwatch context if available, or "N/A; created from joinwatch without context" otherwise + // Get text for note; use joinwatch context if available, or "N/A; imported from joinwatch without context" otherwise string noteText; if (joinWatchNotesList.FirstOrDefault(x => x.Name == user) == default) - noteText = "N/A; created from joinwatch without context"; + noteText = "N/A; imported from joinwatch without context"; else noteText = joinWatchNotesList.First(x => x.Name == user).Value; From cb546f3d327374932c442bb9b05a0a5079efc07d Mon Sep 17 00:00:00 2001 From: FloatingMilkshake Date: Sun, 1 Dec 2024 14:40:14 -0500 Subject: [PATCH 3/6] Do joinwatch migration in startup event, move to new Migrations namespace Also try/catches migration to avoid crashing the bot in case migration fails --- Events/ReadyEvent.cs | 9 ++++++ Migrations/JoinwatchMigration.cs | 51 ++++++++++++++++++++++++++++++++ Program.cs | 41 ------------------------- 3 files changed, 60 insertions(+), 41 deletions(-) create mode 100644 Migrations/JoinwatchMigration.cs diff --git a/Events/ReadyEvent.cs b/Events/ReadyEvent.cs index 24c3732d..7dc627b5 100644 --- a/Events/ReadyEvent.cs +++ b/Events/ReadyEvent.cs @@ -168,6 +168,15 @@ public static async Task OnStartup(DiscordClient client) discord.Logger.LogError("Heartbeat ping sent: {status} {content}", (int)response.StatusCode, await response.Content.ReadAsStringAsync()); } } + + try + { + await Migrations.JoinwatchMigration.MigrateJoinwatchesToNotesAsync(); + } + catch (Exception ex) + { + client.Logger.LogError(ex, "Failed to migrate joinwatches to notes!"); + } client.Logger.LogInformation(CliptokEventID, "Startup event complete, logged in as {user}", $"{DiscordHelpers.UniqueUsername(client.CurrentUser)}"); } diff --git a/Migrations/JoinwatchMigration.cs b/Migrations/JoinwatchMigration.cs new file mode 100644 index 00000000..1e065317 --- /dev/null +++ b/Migrations/JoinwatchMigration.cs @@ -0,0 +1,51 @@ +using static Cliptok.Program; + +namespace Cliptok.Migrations +{ + public class JoinwatchMigration + { + public static async Task MigrateJoinwatchesToNotesAsync() + { + // Migration from joinwatch to user notes + var joinWatchedUsersList = await Program.db.ListRangeAsync("joinWatchedUsers"); + var joinWatchNotesList = await Program.db.HashGetAllAsync("joinWatchedUsersNotes"); + int successfulMigrations = 0; + int numJoinWatches = joinWatchedUsersList.Length; + foreach (var user in joinWatchedUsersList) + { + // Get text for note; use joinwatch context if available, or "N/A; imported from joinwatch without context" otherwise + string noteText; + if (joinWatchNotesList.FirstOrDefault(x => x.Name == user) == default) + noteText = "N/A; imported from joinwatch without context"; + else + noteText = joinWatchNotesList.First(x => x.Name == user).Value; + + // Construct note + var note = new UserNote + { + TargetUserId = Convert.ToUInt64(user), + ModUserId = discord.CurrentUser.Id, + NoteText = noteText, + ShowOnModmail = false, + ShowOnWarn = false, + ShowAllMods = false, + ShowOnce = false, + ShowOnJoinAndLeave = true, + NoteId = db.StringIncrement("totalWarnings"), + Timestamp = DateTime.Now, + Type = WarningType.Note + }; + + // Save note & remove joinwatch + await db.HashSetAsync(note.TargetUserId.ToString(), note.NoteId, JsonConvert.SerializeObject(note)); + await db.ListRemoveAsync("joinWatchedUsers", note.TargetUserId); + await db.HashDeleteAsync("joinWatchedUsersNotes", note.TargetUserId); + successfulMigrations++; + } + if (successfulMigrations > 0) + { + discord.Logger.LogInformation(CliptokEventID, "Successfully migrated {count}/{total} joinwatches to notes.", successfulMigrations, numJoinWatches); + } + } + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs index 8f242413..c17bef67 100644 --- a/Program.cs +++ b/Program.cs @@ -233,47 +233,6 @@ static async Task Main(string[] _) await ReadyEvent.OnStartup(discord); - // Migration from joinwatch to user notes - var joinWatchedUsersList = await Program.db.ListRangeAsync("joinWatchedUsers"); - var joinWatchNotesList = await Program.db.HashGetAllAsync("joinWatchedUsersNotes"); - int successfulMigrations = 0; - int numJoinWatches = joinWatchedUsersList.Length; - foreach (var user in joinWatchedUsersList) - { - // Get text for note; use joinwatch context if available, or "N/A; imported from joinwatch without context" otherwise - string noteText; - if (joinWatchNotesList.FirstOrDefault(x => x.Name == user) == default) - noteText = "N/A; imported from joinwatch without context"; - else - noteText = joinWatchNotesList.First(x => x.Name == user).Value; - - // Construct note - var note = new UserNote - { - TargetUserId = Convert.ToUInt64(user), - ModUserId = discord.CurrentUser.Id, - NoteText = noteText, - ShowOnModmail = false, - ShowOnWarn = false, - ShowAllMods = false, - ShowOnce = false, - ShowOnJoinAndLeave = true, - NoteId = db.StringIncrement("totalWarnings"), - Timestamp = DateTime.Now, - Type = WarningType.Note - }; - - // Save note & remove joinwatch - await db.HashSetAsync(note.TargetUserId.ToString(), note.NoteId, JsonConvert.SerializeObject(note)); - await db.ListRemoveAsync("joinWatchedUsers", note.TargetUserId); - await db.HashDeleteAsync("joinWatchedUsersNotes", note.TargetUserId); - successfulMigrations++; - } - if (successfulMigrations > 0) - { - discord.Logger.LogInformation(CliptokEventID, "Successfully migrated {count}/{total} joinwatches to notes.", successfulMigrations, numJoinWatches); - } - if (cfgjson.ForumChannelAutoWarnFallbackChannel != 0) ForumChannelAutoWarnFallbackChannel = await discord.GetChannelAsync(cfgjson.ForumChannelAutoWarnFallbackChannel); From e31c1cc2a9fe132b378d82ec9474698c2d0ca3a2 Mon Sep 17 00:00:00 2001 From: FloatingMilkshake Date: Sun, 1 Dec 2024 14:42:49 -0500 Subject: [PATCH 4/6] Check whether joinwatches are present before attempting to migrate to notes --- Migrations/JoinwatchMigration.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Migrations/JoinwatchMigration.cs b/Migrations/JoinwatchMigration.cs index 1e065317..fac22f40 100644 --- a/Migrations/JoinwatchMigration.cs +++ b/Migrations/JoinwatchMigration.cs @@ -7,6 +7,9 @@ public class JoinwatchMigration public static async Task MigrateJoinwatchesToNotesAsync() { // Migration from joinwatch to user notes + if (!await db.KeyExistsAsync("joinWatchedUsers")) + return; + var joinWatchedUsersList = await Program.db.ListRangeAsync("joinWatchedUsers"); var joinWatchNotesList = await Program.db.HashGetAllAsync("joinWatchedUsersNotes"); int successfulMigrations = 0; From 1a53ff1e914002e3be5afb2a7897fbde236a35bc Mon Sep 17 00:00:00 2001 From: FloatingMilkshake Date: Sun, 1 Dec 2024 15:06:04 -0500 Subject: [PATCH 5/6] Log error for failed joinwatch migrations --- Migrations/JoinwatchMigration.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Migrations/JoinwatchMigration.cs b/Migrations/JoinwatchMigration.cs index fac22f40..8fd5cba2 100644 --- a/Migrations/JoinwatchMigration.cs +++ b/Migrations/JoinwatchMigration.cs @@ -45,10 +45,16 @@ public static async Task MigrateJoinwatchesToNotesAsync() await db.HashDeleteAsync("joinWatchedUsersNotes", note.TargetUserId); successfulMigrations++; } + if (successfulMigrations > 0) { discord.Logger.LogInformation(CliptokEventID, "Successfully migrated {count}/{total} joinwatches to notes.", successfulMigrations, numJoinWatches); } + + if (numJoinWatches != 0 && successfulMigrations != numJoinWatches) + { + discord.Logger.LogError(CliptokEventID, "Failed to migrate {count} joinwatches to notes!", numJoinWatches - successfulMigrations); + } } } } \ No newline at end of file From 2a2b6b8150c660289ee82294a19fd43459403846 Mon Sep 17 00:00:00 2001 From: FloatingMilkshake Date: Sun, 1 Dec 2024 15:20:49 -0500 Subject: [PATCH 6/6] Make joinwatch deprecation messages clearer, add note command examples --- Commands/InteractionCommands/JoinwatchInteractions.cs | 10 +++++----- Commands/Lists.cs | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Commands/InteractionCommands/JoinwatchInteractions.cs b/Commands/InteractionCommands/JoinwatchInteractions.cs index be1cbc9b..b1ac0fd6 100644 --- a/Commands/InteractionCommands/JoinwatchInteractions.cs +++ b/Commands/InteractionCommands/JoinwatchInteractions.cs @@ -8,24 +8,24 @@ public class JoinwatchSlashCmds { [SlashCommand("add", "Watch for joins and leaves of a given user. Output goes to #investigations.")] public async Task JoinwatchAdd(InteractionContext ctx, - [Option("user", "The user to watch for joins and leaves of.")] DiscordUser _, - [Option("note", "An optional note for context.")] string __ = "") + [Option("user", "The user to watch for joins and leaves of.")] DiscordUser user, + [Option("note", "An optional note for context.")] string note = "") { - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} This command is deprecated and no longer works; all joinwatches have been converted to notes. Please use `/note add` instead, with the `show_on_join_and_leave` option."); + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} This command is deprecated and no longer works; all joinwatches have been converted to notes. Please use `/note add` instead, like this: `/note add user:{user.Id} note:{(string.IsNullOrEmpty(note) ? "" : note)} show_on_join_and_leave:True`"); } [SlashCommand("remove", "Stop watching for joins and leaves of a user.")] public async Task JoinwatchRemove(InteractionContext ctx, [Option("user", "The user to stop watching for joins and leaves of.")] DiscordUser user) { - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} This command is deprecated and no longer works; all joinwatches have been converted to notes. Please use `/note remove` instead."); + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} This command is deprecated and no longer works; all joinwatches have been converted to notes. Please use `/note delete` instead, like this: `/note delete user:{user.Id} note:`"); } [SlashCommand("status", "Check the joinwatch status for a user.")] public async Task JoinwatchStatus(InteractionContext ctx, [Option("user", "The user whose joinwatch status to check.")] DiscordUser user) { - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} This command is deprecated and no longer works; all joinwatches have been converted to notes. Please use `/note list` or `/note details` instead."); + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} This command is deprecated and no longer works; all joinwatches have been converted to notes. Please use `/note list user:{user.Id}` to show all of this user's notes, or `/note details user:{user.Id} note:` for details on a specific note, instead. Notes with \"Show on Join & Leave\" enabled will behave like joinwatches."); } } } diff --git a/Commands/Lists.cs b/Commands/Lists.cs index 78ef8d9f..93ecdd63 100644 --- a/Commands/Lists.cs +++ b/Commands/Lists.cs @@ -169,11 +169,11 @@ public async Task ScamCheck(CommandContext ctx, [RemainingText, Description("Dom [HomeServer, RequireHomeserverPerm(ServerPermLevel.TrialModerator)] public async Task JoinWatch( CommandContext ctx, - [Description("The user to watch for joins and leaves of.")] DiscordUser _, - [Description("An optional note for context."), RemainingText] string __ = "" + [Description("The user to watch for joins and leaves of.")] DiscordUser user, + [Description("An optional note for context."), RemainingText] string note = "" ) { - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} This command is deprecated and no longer works; all joinwatches have been converted to notes. Please use `/note add` instead, with the `show_on_join_and_leave` option."); + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} This command is deprecated and no longer works; all joinwatches have been converted to notes. To add a note for this user, please use `/note add user:{user.Id} note:{(string.IsNullOrEmpty(note) ? "" : note)} show_on_join_and_leave:True`; to remove one, use `/note delete user:{user.Id} note:`."); } [Command("appealblock")]