Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 71 additions & 2 deletions Commands/UserNoteCmds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class UserNoteCmds
public async Task AddUserNoteAsync(SlashCommandContext ctx,
[Parameter("user"), Description("The user to add a note for.")] DiscordUser user,
[Parameter("note"), Description("The note to add.")] string noteText,
[Parameter("expires"), Description("When the note should automatically expire. Default: never")] string expires = default,
[Parameter("show_on_modmail"), Description("Whether to show the note when the user opens a modmail thread. Default: true")] bool showOnModmail = true,
[Parameter("show_on_warn"), Description("Whether to show the note when the user is warned. Default: true")] bool showOnWarn = true,
[Parameter("show_all_mods"), Description("Whether to show this note to all mods, versus just yourself. Default: true")] bool showAllMods = true,
Expand All @@ -21,6 +22,28 @@ public async Task AddUserNoteAsync(SlashCommandContext ctx,
{
await ctx.DeferResponseAsync();

DateTime? expireTime;
if (expires is null || expires.Equals("never", StringComparison.OrdinalIgnoreCase))
expireTime = default;
else
{
try
{
expireTime = HumanDateParser.HumanDateParser.Parse(expires).ToUniversalTime();
}
catch
{
await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} I couldn't parse the provided expiration time!"));
return;
}

if (expireTime <= DateTime.UtcNow)
{
await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} Notes can't be set to expire in the past!"));
return;
}
}

// Assemble new note
long noteId = Program.redis.StringIncrement("totalWarnings");
UserNote note = new()
Expand All @@ -35,11 +58,15 @@ public async Task AddUserNoteAsync(SlashCommandContext ctx,
ShowOnJoinAndLeave = showOnJoinAndLeave,
NoteId = noteId,
Timestamp = DateTime.UtcNow,
Type = WarningType.Note
Type = WarningType.Note,
ExpireTime = expireTime,
};

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

if (expireTime != default)
await Program.redis.HashSetAsync("expiringNotes", note.NoteId, JsonConvert.SerializeObject(note));

// Log to mod-logs
var embed = await GenerateUserNoteDetailEmbedAsync(note, user);
await LogChannelHelper.LogMessageAsync("mod", $"{Program.cfgjson.Emoji.Information} New note for {user.Mention}!", embed);
Expand Down Expand Up @@ -89,6 +116,7 @@ public async Task RemoveUserNoteAsync(SlashCommandContext ctx,
public async Task EditUserNoteAsync(SlashCommandContext ctx,
[Parameter("user"), Description("The user to edit a note for.")] DiscordUser user,
[SlashAutoCompleteProvider(typeof(NotesAutocompleteProvider))][Parameter("note"), Description("The note to edit.")] string targetNote,
[Parameter("expires"), Description("When the note should automatically expire. Type \"never\" to disable expiry.")] string expires = default,
[Parameter("new_text"), Description("The new note text. Leave empty to not change.")] string newNoteText = default,
[Parameter("show_on_modmail"), Description("Whether to show the note when the user opens a modmail thread.")] bool? showOnModmail = null,
[Parameter("show_on_warn"), Description("Whether to show the note when the user is warned.")] bool? showOnWarn = null,
Expand All @@ -113,7 +141,7 @@ public async Task EditUserNoteAsync(SlashCommandContext 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 && showOnJoinAndLeave is null)
if (note.NoteText == newNoteText && expires is null && showOnModmail is null && showOnWarn is null && showAllMods is null && showOnce is null && showOnJoinAndLeave is null)
{
await ctx.RespondAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} You didn't change anything about the note!").AsEphemeral());
return;
Expand All @@ -138,9 +166,38 @@ public async Task EditUserNoteAsync(SlashCommandContext ctx,
if (showOnJoinAndLeave is null)
showOnJoinAndLeave = note.ShowOnJoinAndLeave;

DateTime? expireTime;
if (expires == default)
{
expireTime = note.ExpireTime;
}
else if (expires.Equals("never", StringComparison.OrdinalIgnoreCase))
{
expireTime = null;
}
else
{
try
{
expireTime = HumanDateParser.HumanDateParser.Parse(expires).ToUniversalTime();
}
catch
{
await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} I couldn't parse the provided expiration time!"));
return;
}

if (expireTime <= DateTime.UtcNow)
{
await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} Notes can't be set to expire in the past!"));
return;
}
}

// Assemble new note
note.ModUserId = ctx.User.Id;
note.NoteText = newNoteText;
note.ExpireTime = expireTime;
note.ShowOnModmail = (bool)showOnModmail;
note.ShowOnWarn = (bool)showOnWarn;
note.ShowAllMods = (bool)showAllMods;
Expand All @@ -150,6 +207,18 @@ public async Task EditUserNoteAsync(SlashCommandContext ctx,

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

if (note.ExpireTime is null)
{
// This is unnecessary if the note wasn't previously set to expire, but it doesn't seem problematic to
// call this even if the hash already does not exist. Saves a call to HashExistsAsync
await Program.redis.HashDeleteAsync("expiringNotes", note.NoteId);
}
else
{
// This might also be unnecessary (see above) but saves a HashGetAsync in case the expire time was changed
await Program.redis.HashSetAsync("expiringNotes", note.NoteId, JsonConvert.SerializeObject(note));
}

// Log to mod-logs
var embed = await GenerateUserNoteDetailEmbedAsync(note, user);
await LogChannelHelper.LogMessageAsync("mod", $"{Program.cfgjson.Emoji.Information} Note edited: `{note.NoteId}` (belonging to {user.Mention})", embed);
Expand Down
5 changes: 3 additions & 2 deletions Helpers/UserNoteHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,9 @@ await LykosAvatarMethods.UserOrMemberAvatarURL(user, Program.homeGuild, "png")
.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);
.AddField("Time", $"<t:{TimeHelpers.ToUnixTimestamp(note.Timestamp)}:f>", true)
.AddField("Expires", note.ExpireTime == default ? "Never" : $"<t:{TimeHelpers.ToUnixTimestamp(note.ExpireTime)}:f>", true)
.AddField("Responsible moderator", $"<@{note.ModUserId}>", false);

return embed;
}
Expand Down
1 change: 1 addition & 0 deletions Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ static async Task Main(string[] _)
Tasks.PunishmentTasks.CheckMutesAsync(),
Tasks.PunishmentTasks.CheckBansAsync(),
Tasks.PunishmentTasks.CleanUpPunishmentMessagesAsync(),
Tasks.PunishmentTasks.CleanUpExpiredNotesAsync(),
Tasks.ReminderTasks.CheckRemindersAsync(),
Tasks.RaidmodeTasks.CheckRaidmodeAsync(cfgjson.ServerID),
Tasks.LockdownTasks.CheckUnlocksAsync(),
Expand Down
26 changes: 26 additions & 0 deletions Tasks/PunishmentTasks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,32 @@ public static async Task<bool> CleanUpPunishmentMessagesAsync()
Program.discord.Logger.LogDebug(Program.CliptokEventID, "Checked for auto-warn and compromised account ban messages at {time} with result: {result}", DateTime.UtcNow, success);
return success;
}

public static async Task<bool> CleanUpExpiredNotesAsync()
{
var expiringNotes = await Program.redis.HashGetAllAsync("expiringNotes");

bool success = false;

foreach (var note in expiringNotes.Select(x => JsonConvert.DeserializeObject<UserNote>(x.Value)))
{
if (note.ExpireTime is not null && note.ExpireTime < DateTime.UtcNow)
{
await Program.redis.HashDeleteAsync(note.TargetUserId.ToString(), note.NoteId);
await Program.redis.HashDeleteAsync("expiringNotes", note.NoteId);

// Log to mod-logs
var user = await Program.discord.GetUserAsync(note.TargetUserId);
var embed = new DiscordEmbedBuilder(await UserNoteHelpers.GenerateUserNoteDetailEmbedAsync(note, user)).WithColor(0xf03916);
await LogChannelHelper.LogMessageAsync("mod", $"{Program.cfgjson.Emoji.Deleted} Note expired: `{note.NoteId}` (belonging to {user.Mention})", embed);

success = true;
}
}

Program.discord.Logger.LogDebug(Program.CliptokEventID, "Checked notes at {time} with result: {success}", DateTime.UtcNow, success);
return success;
}
}

}
3 changes: 3 additions & 0 deletions Types/RedisData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ public class UserNote

[JsonProperty("type")]
public WarningType Type { get; set; }

[JsonProperty("expireTime")]
public DateTime? ExpireTime { get; set; }
}

public class PendingUserOverride
Expand Down