diff --git a/Tasks/EventTasks.cs b/Tasks/EventTasks.cs index 31fd7ebe..cf21b4bf 100644 --- a/Tasks/EventTasks.cs +++ b/Tasks/EventTasks.cs @@ -7,136 +7,149 @@ public class EventTasks public static async Task HandlePendingChannelUpdateEventsAsync() { bool success = false; - - foreach (var pendingEvent in PendingChannelUpdateEvents) + + try { - // This is the timestamp on this event, used to identify it / keep events in order in the list - var timestamp = pendingEvent.Key; - - // This is a set of ChannelUpdatedEventArgs for the event we are processing - var e = pendingEvent.Value; - - try + foreach (var pendingEvent in PendingChannelUpdateEvents) { - // Sync channel overwrites with db so that they can be restored when a user leaves & rejoins. - - // Get the current channel overwrites - var currentChannelOverwrites = e.ChannelAfter.PermissionOverwrites; + // This is the timestamp on this event, used to identify it / keep events in order in the list + var timestamp = pendingEvent.Key; - // Get the db overwrites - var dbOverwrites = await Program.db.HashGetAllAsync("overrides"); + // This is a set of ChannelUpdatedEventArgs for the event we are processing + var e = pendingEvent.Value; - // Compare the two and sync them, prioritizing overwrites on channel over stored overwrites - - foreach (var userOverwrites in dbOverwrites) + try { - var overwriteDict = - JsonConvert.DeserializeObject>(userOverwrites.Value); - - // If the db overwrites are not in the current channel overwrites, remove them from the db. + // Sync channel overwrites with db so that they can be restored when a user leaves & rejoins. - foreach (var overwrite in overwriteDict) - { - // (if overwrite is for a different channel, skip) - if (overwrite.Key != e.ChannelAfter.Id) continue; + // Get the current channel overwrites + var currentChannelOverwrites = e.ChannelAfter.PermissionOverwrites; - // (if current overwrite is in the channel, skip) - // checking individual properties here because sometimes they are the same but the one from Discord has - // other properties like Discord (DiscordClient) that I don't care about and will wrongly mark the overwrite as different - if (currentChannelOverwrites.Any(a => CompareOverwrites(a, overwrite.Value))) - continue; + // Get the db overwrites + var dbOverwrites = await Program.db.HashGetAllAsync("overrides"); - // If it looks like the member left, do NOT remove their overrides. + // Compare the two and sync them, prioritizing overwrites on channel over stored overwrites - // Try to fetch member. If it fails, they are not in the guild. If this is a voice channel, remove the override. - // (if they are not in the guild & this is not a voice channel, skip; otherwise, code below handles removal) - if (!e.Guild.Members.ContainsKey((ulong)userOverwrites.Name) && - e.ChannelAfter.Type != DiscordChannelType.Voice) - continue; + foreach (var userOverwrites in dbOverwrites) + { + var overwriteDict = + JsonConvert.DeserializeObject>(userOverwrites + .Value); - // User could be fetched, so they are in the server and their override was removed. Remove from db. - // (or user could not be fetched & this is a voice channel; remove) + // If the db overwrites are not in the current channel overwrites, remove them from the db. - var overrides = await Program.db.HashGetAsync("overrides", userOverwrites.Name); - var dict = JsonConvert.DeserializeObject>(overrides); - dict.Remove(e.ChannelAfter.Id); - if (dict.Count > 0) - await Program.db.HashSetAsync("overrides", userOverwrites.Name, - JsonConvert.SerializeObject(dict)); - else + foreach (var overwrite in overwriteDict) { - await Program.db.HashDeleteAsync("overrides", userOverwrites.Name); + // (if overwrite is for a different channel, skip) + if (overwrite.Key != e.ChannelAfter.Id) continue; + + // (if current overwrite is in the channel, skip) + // checking individual properties here because sometimes they are the same but the one from Discord has + // other properties like Discord (DiscordClient) that I don't care about and will wrongly mark the overwrite as different + if (currentChannelOverwrites.Any(a => CompareOverwrites(a, overwrite.Value))) + continue; + + // If it looks like the member left, do NOT remove their overrides. + + // Try to fetch member. If it fails, they are not in the guild. If this is a voice channel, remove the override. + // (if they are not in the guild & this is not a voice channel, skip; otherwise, code below handles removal) + if (!e.Guild.Members.ContainsKey((ulong)userOverwrites.Name) && + e.ChannelAfter.Type != DiscordChannelType.Voice) + continue; + + // User could be fetched, so they are in the server and their override was removed. Remove from db. + // (or user could not be fetched & this is a voice channel; remove) + + var overrides = await Program.db.HashGetAsync("overrides", userOverwrites.Name); + var dict = JsonConvert + .DeserializeObject>(overrides); + dict.Remove(e.ChannelAfter.Id); + if (dict.Count > 0) + await Program.db.HashSetAsync("overrides", userOverwrites.Name, + JsonConvert.SerializeObject(dict)); + else + { + await Program.db.HashDeleteAsync("overrides", userOverwrites.Name); + } } } - } - foreach (var overwrite in currentChannelOverwrites) - { - // Ignore role overrides because we aren't storing those - if (overwrite.Type == DiscordOverwriteType.Role) continue; + foreach (var overwrite in currentChannelOverwrites) + { + // Ignore role overrides because we aren't storing those + if (overwrite.Type == DiscordOverwriteType.Role) continue; - // If the current channel overwrites are not in the db, add them to the db. - - // Pull out db overwrites into list - - var dbOverwriteRaw = await Program.db.HashGetAllAsync("overrides"); - var dbOverwriteList = new List(); + // If the current channel overwrites are not in the db, add them to the db. - foreach (var dbOverwrite in dbOverwriteRaw) - { - var dict = JsonConvert.DeserializeObject>(dbOverwrite.Value); - dbOverwriteList.AddRange(dict.Values); - } - - // If the overwrite is already in the db, skip - if (dbOverwriteList.Any(dbOverwrite => CompareOverwrites(dbOverwrite, overwrite))) - continue; - - if ((await Program.db.HashKeysAsync("overrides")).Any(a => a == overwrite.Id.ToString())) - { - // User has an overwrite in the db; add this one to their list of overrides without - // touching existing ones + // Pull out db overwrites into list - var overwrites = await Program.db.HashGetAsync("overrides", overwrite.Id); + var dbOverwriteRaw = await Program.db.HashGetAllAsync("overrides"); + var dbOverwriteList = new List(); - if (!string.IsNullOrWhiteSpace(overwrites)) + foreach (var dbOverwrite in dbOverwriteRaw) { var dict = - JsonConvert.DeserializeObject>(overwrites); + JsonConvert.DeserializeObject>( + dbOverwrite.Value); + dbOverwriteList.AddRange(dict.Values); + } - if (dict is not null) - { - dict.Add(e.ChannelAfter.Id, overwrite); + // If the overwrite is already in the db, skip + if (dbOverwriteList.Any(dbOverwrite => CompareOverwrites(dbOverwrite, overwrite))) + continue; + + if ((await Program.db.HashKeysAsync("overrides")).Any(a => a == overwrite.Id.ToString())) + { + // User has an overwrite in the db; add this one to their list of overrides without + // touching existing ones + + var overwrites = await Program.db.HashGetAsync("overrides", overwrite.Id); - if (dict.Count > 0) - await Program.db.HashSetAsync("overrides", overwrite.Id, - JsonConvert.SerializeObject(dict)); - else - await Program.db.HashDeleteAsync("overrides", overwrite.Id); + if (!string.IsNullOrWhiteSpace(overwrites)) + { + var dict = + JsonConvert.DeserializeObject>(overwrites); + + if (dict is not null) + { + dict.Add(e.ChannelAfter.Id, overwrite); + + if (dict.Count > 0) + await Program.db.HashSetAsync("overrides", overwrite.Id, + JsonConvert.SerializeObject(dict)); + else + await Program.db.HashDeleteAsync("overrides", overwrite.Id); + } } } - } - else - { - // User doesn't have any overrides in db, so store new dictionary + else + { + // User doesn't have any overrides in db, so store new dictionary - await Program.db.HashSetAsync("overrides", - overwrite.Id, JsonConvert.SerializeObject(new Dictionary - { { e.ChannelAfter.Id, overwrite } })); + await Program.db.HashSetAsync("overrides", + overwrite.Id, JsonConvert.SerializeObject(new Dictionary + { { e.ChannelAfter.Id, overwrite } })); + } } + + PendingChannelUpdateEvents.Remove(timestamp); + success = true; + } + catch (Exception ex) + { + // Log the exception + Program.discord.Logger.LogWarning(ex, + "Failed to process pending channel update event for channel {channel}", e.ChannelAfter.Id); + + // Always remove the event from the pending list, even if we failed to process it + PendingChannelUpdateEvents.Remove(timestamp); } - PendingChannelUpdateEvents.Remove(timestamp); - success = true; - } - catch (Exception ex) - { - // Log the exception - Program.discord.Logger.LogWarning(ex, "Failed to process pending channel update event for channel {channel}", e.ChannelAfter.Id); - - // Always remove the event from the pending list, even if we failed to process it - PendingChannelUpdateEvents.Remove(timestamp); } } + catch (InvalidOperationException ex) + { + Program.discord.Logger.LogDebug(ex, "Failed to enumerate pending channel update events; this usually means a Channel Update event was just added to the list, or one was processed and removed from the list. Will try again on next task run."); + } Program.discord.Logger.LogDebug(Program.CliptokEventID, "Checked pending channel update events at {time} with result: {success}", DateTime.Now, success); return success;