Skip to content

Commit 43176ea

Browse files
Migrate from joinwatch to notes (#246)
* 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 <[email protected]> * 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 <[email protected]>
1 parent 57e090b commit 43176ea

File tree

8 files changed

+113
-125
lines changed

8 files changed

+113
-125
lines changed

Commands/InteractionCommands/JoinwatchInteractions.cs

+3-64
Original file line numberDiff line numberDiff line change
@@ -11,82 +11,21 @@ public async Task JoinwatchAdd(InteractionContext ctx,
1111
[Option("user", "The user to watch for joins and leaves of.")] DiscordUser user,
1212
[Option("note", "An optional note for context.")] string note = "")
1313
{
14-
var joinWatchlist = await Program.db.ListRangeAsync("joinWatchedUsers");
15-
16-
if (joinWatchlist.Contains(user.Id))
17-
{
18-
// User is already watched
19-
20-
// Get current note; if it's the same, do nothing
21-
var currentNote = await Program.db.HashGetAsync("joinWatchedUsersNotes", user.Id);
22-
if (currentNote == note || (string.IsNullOrWhiteSpace(currentNote) && string.IsNullOrWhiteSpace(note)))
23-
{
24-
await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} {user.Mention} is already being watched with the same note! Nothing to do.");
25-
return;
26-
}
27-
28-
// If note is different, update it
29-
30-
// If new note is empty, remove instead of changing to empty string!
31-
if (string.IsNullOrWhiteSpace(note))
32-
{
33-
await Program.db.HashDeleteAsync("joinWatchedUsersNotes", user.Id);
34-
await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Successfully removed the note for {user.Mention}! They are still being watched.");
35-
}
36-
else
37-
{
38-
await Program.db.HashSetAsync("joinWatchedUsersNotes", user.Id, note);
39-
await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Successfully updated the note for {user.Mention}:\n> {note}");
40-
}
41-
}
42-
else
43-
{
44-
// User is not joinwatched, watch
45-
await Program.db.ListRightPushAsync("joinWatchedUsers", user.Id);
46-
if (note != "")
47-
await Program.db.HashSetAsync("joinWatchedUsersNotes", user.Id, note);
48-
await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Now watching for joins/leaves of {user.Mention} to send to the investigations channel"
49-
+ (note == "" ? "!" : $" with the following note:\n>>> {note}"));
50-
}
14+
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`");
5115
}
5216

5317
[SlashCommand("remove", "Stop watching for joins and leaves of a user.")]
5418
public async Task JoinwatchRemove(InteractionContext ctx,
5519
[Option("user", "The user to stop watching for joins and leaves of.")] DiscordUser user)
5620
{
57-
var joinWatchlist = await Program.db.ListRangeAsync("joinWatchedUsers");
58-
59-
// Check user watch status first; error if not watched
60-
if (!joinWatchlist.Contains(user.Id))
61-
{
62-
await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} {user.Mention} is not being watched! Nothing to do.");
63-
return;
64-
}
65-
66-
Program.db.ListRemove("joinWatchedUsers", joinWatchlist.First(x => x == user.Id));
67-
await Program.db.HashDeleteAsync("joinWatchedUsersNotes", user.Id);
68-
await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Successfully unwatched {user.Mention}!");
21+
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>`");
6922
}
7023

7124
[SlashCommand("status", "Check the joinwatch status for a user.")]
7225
public async Task JoinwatchStatus(InteractionContext ctx,
7326
[Option("user", "The user whose joinwatch status to check.")] DiscordUser user)
7427
{
75-
var joinWatchlist = await Program.db.ListRangeAsync("joinWatchedUsers");
76-
77-
if (joinWatchlist.Contains(user.Id))
78-
{
79-
var note = await Program.db.HashGetAsync("joinWatchedUsersNotes", user.Id);
80-
81-
if (string.IsNullOrWhiteSpace(note))
82-
await ctx.RespondAsync($"{Program.cfgjson.Emoji.Information} {user.Mention} is currently being watched, but no note is set.");
83-
else
84-
await ctx.RespondAsync($"{Program.cfgjson.Emoji.Information} {user.Mention} is currently being watched with the following note:\n> {note}");
85-
}
86-
else
87-
{
88-
await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} {user.Mention} is not being watched!");
89-
}
28+
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.");
9029
}
9130
}
9231
}

Commands/InteractionCommands/UserNoteInteractions.cs

+9-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ public async Task AddUserNoteAsync(InteractionContext ctx,
1515
[Option("show_on_modmail", "Whether to show the note when the user opens a modmail thread. Default: true")] bool showOnModmail = true,
1616
[Option("show_on_warn", "Whether to show the note when the user is warned. Default: true")] bool showOnWarn = true,
1717
[Option("show_all_mods", "Whether to show this note to all mods, versus just yourself. Default: true")] bool showAllMods = true,
18-
[Option("show_once", "Whether to show this note once and then discard it. Default: false")] bool showOnce = false)
18+
[Option("show_once", "Whether to show this note once and then discard it. Default: false")] bool showOnce = false,
19+
[Option("show_on_join_and_leave", "Whether to show this note when the user joins & leaves. Works like joinwatch. Default: false")] bool showOnJoinAndLeave = false)
1920
{
2021
await ctx.DeferAsync();
2122

@@ -30,6 +31,7 @@ public async Task AddUserNoteAsync(InteractionContext ctx,
3031
ShowOnWarn = showOnWarn,
3132
ShowAllMods = showAllMods,
3233
ShowOnce = showOnce,
34+
ShowOnJoinAndLeave = showOnJoinAndLeave,
3335
NoteId = noteId,
3436
Timestamp = DateTime.Now,
3537
Type = WarningType.Note
@@ -88,7 +90,8 @@ public async Task EditUserNoteAsync(InteractionContext ctx,
8890
[Option("show_on_modmail", "Whether to show the note when the user opens a modmail thread.")] bool? showOnModmail = null,
8991
[Option("show_on_warn", "Whether to show the note when the user is warned.")] bool? showOnWarn = null,
9092
[Option("show_all_mods", "Whether to show this note to all mods, versus just yourself.")] bool? showAllMods = null,
91-
[Option("show_once", "Whether to show this note once and then discard it.")] bool? showOnce = null)
93+
[Option("show_once", "Whether to show this note once and then discard it.")] bool? showOnce = null,
94+
[Option("show_on_join_and_leave", "Whether to show this note when the user joins & leaves. Works like joinwatch. Default: false")] bool? showOnJoinAndLeave = false)
9295
{
9396
// Get note
9497
UserNote note;
@@ -107,7 +110,7 @@ public async Task EditUserNoteAsync(InteractionContext ctx,
107110
newNoteText = note.NoteText;
108111

109112
// If no changes are made, refuse the request
110-
if (note.NoteText == newNoteText && showOnModmail is null && showOnWarn is null && showAllMods is null && showOnce is null)
113+
if (note.NoteText == newNoteText && showOnModmail is null && showOnWarn is null && showAllMods is null && showOnce is null && showOnJoinAndLeave is null)
111114
{
112115
await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} You didn't change anything about the note!").AsEphemeral());
113116
return;
@@ -129,6 +132,8 @@ public async Task EditUserNoteAsync(InteractionContext ctx,
129132
showAllMods = note.ShowAllMods;
130133
if (showOnce is null)
131134
showOnce = note.ShowOnce;
135+
if (showOnJoinAndLeave is null)
136+
showOnJoinAndLeave = note.ShowOnJoinAndLeave;
132137

133138
// Assemble new note
134139
note.ModUserId = ctx.User.Id;
@@ -137,6 +142,7 @@ public async Task EditUserNoteAsync(InteractionContext ctx,
137142
note.ShowOnWarn = (bool)showOnWarn;
138143
note.ShowAllMods = (bool)showAllMods;
139144
note.ShowOnce = (bool)showOnce;
145+
note.ShowOnJoinAndLeave = (bool)showOnJoinAndLeave;
140146
note.Type = WarningType.Note;
141147

142148
await Program.db.HashSetAsync(user.Id.ToString(), note.NoteId, JsonConvert.SerializeObject(note));

Commands/Lists.cs

+1-36
Original file line numberDiff line numberDiff line change
@@ -173,42 +173,7 @@ public async Task JoinWatch(
173173
[Description("An optional note for context."), RemainingText] string note = ""
174174
)
175175
{
176-
var joinWatchlist = await Program.db.ListRangeAsync("joinWatchedUsers");
177-
178-
if (joinWatchlist.Contains(user.Id))
179-
{
180-
if (note != "")
181-
{
182-
// User is already joinwatched, just update note
183-
184-
// Get current note; if it's the same, do nothing
185-
var currentNote = await Program.db.HashGetAsync("joinWatchedUsersNotes", user.Id);
186-
if (currentNote == note || (string.IsNullOrWhiteSpace(currentNote) && string.IsNullOrWhiteSpace(note)))
187-
{
188-
await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} {user.Mention} is already being watched with the same note! Nothing to do.");
189-
return;
190-
}
191-
192-
// If note is different, update it
193-
await Program.db.HashSetAsync("joinWatchedUsersNotes", user.Id, note);
194-
await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Successfully updated the note for {user.Mention} (run again with no note to unwatch):\n> {note}");
195-
return;
196-
}
197-
198-
// User is already joinwatched, unwatch
199-
Program.db.ListRemove("joinWatchedUsers", joinWatchlist.First(x => x == user.Id));
200-
await Program.db.HashDeleteAsync("joinWatchedUsersNotes", user.Id);
201-
await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Successfully unwatched {user.Mention}, since they were already in the list.");
202-
}
203-
else
204-
{
205-
// User is not joinwatched, watch
206-
await Program.db.ListRightPushAsync("joinWatchedUsers", user.Id);
207-
if (note != "")
208-
await Program.db.HashSetAsync("joinWatchedUsersNotes", user.Id, note);
209-
await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Now watching for joins/leaves of {user.Mention} to send to the investigations channel"
210-
+ (note == "" ? "!" : $" with the following note:\n>>> {note}"));
211-
}
176+
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>`.");
212177
}
213178

214179
[Command("appealblock")]

Events/MemberEvents.cs

+27-22
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public static async Task GuildMemberAdded(DiscordClient client, GuildMemberAdded
1111
if (e.Guild.Id != cfgjson.ServerID)
1212
return;
1313

14-
var embed = new DiscordEmbedBuilder()
14+
var userLogEmbed = new DiscordEmbedBuilder()
1515
.WithColor(new DiscordColor(0x3E9D28))
1616
.WithTimestamp(DateTimeOffset.Now)
1717
.WithThumbnail(e.Member.AvatarUrl)
@@ -24,18 +24,20 @@ public static async Task GuildMemberAdded(DiscordClient client, GuildMemberAdded
2424
.AddField("Action", "Joined the server", false)
2525
.WithFooter($"{client.CurrentUser.Username}JoinEvent");
2626

27-
LogChannelHelper.LogMessageAsync("users", $"{cfgjson.Emoji.UserJoin} **Member joined the server!** - {e.Member.Id}", embed);
27+
LogChannelHelper.LogMessageAsync("users", $"{cfgjson.Emoji.UserJoin} **Member joined the server!** - {e.Member.Id}", userLogEmbed);
2828

29-
var joinWatchlist = await db.ListRangeAsync("joinWatchedUsers");
29+
// Get this user's notes that are set to show on join/leave
30+
var userNotes = db.HashGetAll(e.Member.Id.ToString())
31+
.Where(x => JsonConvert.DeserializeObject<UserNote>(x.Value).Type == WarningType.Note
32+
&& JsonConvert.DeserializeObject<UserNote>(x.Value).ShowOnJoinAndLeave).ToDictionary(
33+
x => x.Name.ToString(),
34+
x => JsonConvert.DeserializeObject<UserNote>(x.Value)
35+
);
3036

31-
if (joinWatchlist.Contains(e.Member.Id))
37+
if (userNotes.Count > 0)
3238
{
33-
if (await db.HashExistsAsync("joinWatchedUsersNotes", e.Member.Id))
34-
{
35-
embed.AddField($"Joinwatch Note", await db.HashGetAsync("joinWatchedUsersNotes", e.Member.Id));
36-
}
37-
38-
LogChannelHelper.LogMessageAsync("investigations", $"{cfgjson.Emoji.Warning} Watched user {e.Member.Mention} just joined the server!", embed);
39+
var notesEmbed = await UserNoteHelpers.GenerateUserNotesEmbedAsync(e.Member, false, userNotes);
40+
LogChannelHelper.LogMessageAsync("investigations", $"{cfgjson.Emoji.Warning} {e.Member.Mention} just joined the server with notes set to show on join!", notesEmbed);
3941
}
4042

4143
if (db.HashExists("raidmode", e.Guild.Id))
@@ -155,7 +157,7 @@ public static async Task GuildMemberRemoved(DiscordClient client, GuildMemberRem
155157
}
156158
}
157159

158-
var embed = new DiscordEmbedBuilder()
160+
var userLogEmbed = new DiscordEmbedBuilder()
159161
.WithColor(new DiscordColor(0xBA4119))
160162
.WithTimestamp(DateTimeOffset.Now)
161163
.WithThumbnail(e.Member.AvatarUrl)
@@ -169,18 +171,21 @@ public static async Task GuildMemberRemoved(DiscordClient client, GuildMemberRem
169171
.AddField("Roles", rolesStr)
170172
.WithFooter($"{client.CurrentUser.Username}LeaveEvent");
171173

172-
LogChannelHelper.LogMessageAsync("users", $"{cfgjson.Emoji.UserLeave} **Member left the server!** - {e.Member.Id}", embed);
173-
174-
var joinWatchlist = await db.ListRangeAsync("joinWatchedUsers");
175-
176-
if (joinWatchlist.Contains(e.Member.Id))
174+
LogChannelHelper.LogMessageAsync("users", $"{cfgjson.Emoji.UserLeave} **Member left the server!** - {e.Member.Id}", userLogEmbed);
175+
176+
// Get this user's notes that are set to show on join/leave
177+
var userNotes = db.HashGetAll(e.Member.Id.ToString())
178+
.Where(x => JsonConvert.DeserializeObject<UserNote>(x.Value).Type == WarningType.Note
179+
&& JsonConvert.DeserializeObject<UserNote>(x.Value).ShowOnJoinAndLeave).ToDictionary(
180+
x => x.Name.ToString(),
181+
x => JsonConvert.DeserializeObject<UserNote>(x.Value)
182+
);
183+
184+
DiscordEmbed notesEmbed;
185+
if (userNotes.Count > 0)
177186
{
178-
if (await db.HashExistsAsync("joinWatchedUsersNotes", e.Member.Id))
179-
{
180-
embed.AddField($"Joinwatch Note", await db.HashGetAsync("joinWatchedUsersNotes", e.Member.Id));
181-
}
182-
183-
LogChannelHelper.LogMessageAsync("investigations", $"{cfgjson.Emoji.Warning} Watched user {e.Member.Mention} just left the server!", embed);
187+
notesEmbed = await UserNoteHelpers.GenerateUserNotesEmbedAsync(e.Member, false, userNotes);
188+
LogChannelHelper.LogMessageAsync("investigations", $"{cfgjson.Emoji.Warning} {e.Member.Mention} just left the server with notes set to show on leave!", notesEmbed);
184189
}
185190
}
186191

Events/ReadyEvent.cs

+9
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,15 @@ public static async Task OnStartup(DiscordClient client)
168168
discord.Logger.LogError("Heartbeat ping sent: {status} {content}", (int)response.StatusCode, await response.Content.ReadAsStringAsync());
169169
}
170170
}
171+
172+
try
173+
{
174+
await Migrations.JoinwatchMigration.MigrateJoinwatchesToNotesAsync();
175+
}
176+
catch (Exception ex)
177+
{
178+
client.Logger.LogError(ex, "Failed to migrate joinwatches to notes!");
179+
}
171180

172181
client.Logger.LogInformation(CliptokEventID, "Startup event complete, logged in as {user}", $"{DiscordHelpers.UniqueUsername(client.CurrentUser)}");
173182
}

Helpers/UserNoteHelpers.cs

+1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ await LykosAvatarMethods.UserOrMemberAvatarURL(user, Program.homeGuild, "png")
120120
.AddField("Show on Warn", note.ShowOnWarn ? "Yes" : "No", true)
121121
.AddField("Show all Mods", note.ShowAllMods ? "Yes" : "No", true)
122122
.AddField("Show Once", note.ShowOnce ? "Yes" : "No", true)
123+
.AddField("Show on Join & Leave", note.ShowOnJoinAndLeave ? "Yes" : "No", true)
123124
.AddField("Responsible moderator", $"<@{note.ModUserId}>", true)
124125
.AddField("Time", $"<t:{TimeHelpers.ToUnixTimestamp(note.Timestamp)}:f>", true);
125126

0 commit comments

Comments
 (0)