Skip to content

Commit

Permalink
Migrate from joinwatch to notes (#246)
Browse files Browse the repository at this point in the history
* Migrate from joinwatch to notes

Deprecate joinwatch commands, add show on join/leave option to notes, migrate joinwatches to notes on bot startup

* Accept review suggestion

Co-authored-by: Erisa A <erisa@erisa.uk>

* Do joinwatch migration in startup event, move to new Migrations namespace

Also try/catches migration to avoid crashing the bot in case migration fails

* Check whether joinwatches are present before attempting to migrate to notes

* Log error for failed joinwatch migrations

* Make joinwatch deprecation messages clearer, add note command examples

---------

Co-authored-by: Erisa A <erisa@erisa.uk>
  • Loading branch information
FloatingMilkshake and Erisa authored Dec 15, 2024
1 parent 57e090b commit 43176ea
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 125 deletions.
67 changes: 3 additions & 64 deletions Commands/InteractionCommands/JoinwatchInteractions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,82 +11,21 @@ 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 = "")
{
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, like this: `/note add user:{user.Id} note:{(string.IsNullOrEmpty(note) ? "<context>" : 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)
{
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 delete` instead, like this: `/note delete user:{user.Id} note:<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)
{
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 user:{user.Id}` to show all of this user's notes, or `/note details user:{user.Id} note:<note>` for details on a specific note, instead. Notes with \"Show on Join & Leave\" enabled will behave like joinwatches.");
}
}
}
Expand Down
12 changes: 9 additions & 3 deletions Commands/InteractionCommands/UserNoteInteractions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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));
Expand Down
37 changes: 1 addition & 36 deletions Commands/Lists.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,42 +173,7 @@ public async Task JoinWatch(
[Description("An optional note for context."), RemainingText] string note = ""
)
{
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. To add a note for this user, please use `/note add user:{user.Id} note:{(string.IsNullOrEmpty(note) ? "<context>" : note)} show_on_join_and_leave:True`; to remove one, use `/note delete user:{user.Id} note:<note>`.");
}

[Command("appealblock")]
Expand Down
49 changes: 27 additions & 22 deletions Events/MemberEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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<UserNote>(x.Value).Type == WarningType.Note
&& JsonConvert.DeserializeObject<UserNote>(x.Value).ShowOnJoinAndLeave).ToDictionary(
x => x.Name.ToString(),
x => JsonConvert.DeserializeObject<UserNote>(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))
Expand Down Expand Up @@ -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)
Expand All @@ -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<UserNote>(x.Value).Type == WarningType.Note
&& JsonConvert.DeserializeObject<UserNote>(x.Value).ShowOnJoinAndLeave).ToDictionary(
x => x.Name.ToString(),
x => JsonConvert.DeserializeObject<UserNote>(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);
}
}

Expand Down
9 changes: 9 additions & 0 deletions Events/ReadyEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)}");
}
Expand Down
1 change: 1 addition & 0 deletions Helpers/UserNoteHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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", $"<t:{TimeHelpers.ToUnixTimestamp(note.Timestamp)}:f>", true);

Expand Down
Loading

0 comments on commit 43176ea

Please sign in to comment.