Add common incoming message check, fix auto role entry

This commit is contained in:
Noi 2022-06-02 20:43:34 -07:00
parent 94d4a27e85
commit da31ce3e0d
6 changed files with 77 additions and 49 deletions

View file

@ -14,8 +14,7 @@ public class AutoResponder : RegexbotModule {
}
private async Task DiscordClient_MessageReceived(SocketMessage arg) {
if (arg.Channel is not SocketGuildChannel ch) return;
if (arg.Author.IsBot || arg.Author.IsWebhook) return;
if (!Common.Misc.IsValidUserMessage(arg, out var ch)) return;
var definitions = GetGuildState<IEnumerable<Definition>>(ch.Guild.Id);
if (definitions == null) return; // No configuration in this guild; do no further processing
@ -49,7 +48,7 @@ public class AutoResponder : RegexbotModule {
await msg.Channel.SendMessageAsync(def.GetResponse());
} else {
var ch = (SocketGuildChannel)msg.Channel;
string[] cmdline = def.Command.Split(new char[] { ' ' }, 2);
var cmdline = def.Command.Split(new char[] { ' ' }, 2);
var ps = new ProcessStartInfo() {
FileName = cmdline[0],
@ -63,13 +62,13 @@ public class AutoResponder : RegexbotModule {
p.WaitForExit(5000); // waiting 5 seconds at most
if (p.HasExited) {
if (p.ExitCode != 0) {
PLog($"Command execution in {ch.Guild.Id}: Process exited abnormally (with code {p.ExitCode}).");
Log(ch.Guild, $"Command execution: Process exited abnormally (with code {p.ExitCode}).");
}
using var stdout = p.StandardOutput;
var result = await stdout.ReadToEndAsync();
if (!string.IsNullOrWhiteSpace(result)) await msg.Channel.SendMessageAsync(result);
} else {
PLog($"Command execution in {ch.Guild.Id}: Process has not exited in 5 seconds. Killing process.");
Log(ch.Guild, $"Command execution: Process has not exited in 5 seconds. Killing process.");
p.Kill();
}
}

View file

@ -1,16 +1,17 @@
using System.Text;
namespace RegexBot.Modules.EntryTimeRole;
namespace RegexBot.Modules.EntryRole;
/// <summary>
/// Automatically sets a role onto users entering the guild after a predefined amount of time.
/// </summary>
[RegexbotModule]
public class EntryTimeRole : RegexbotModule {
public sealed class EntryRole : RegexbotModule, IDisposable {
readonly Task _workerTask;
readonly CancellationTokenSource _workerTaskToken; // TODO make use of this when possible
readonly CancellationTokenSource _workerTaskToken;
public EntryTimeRole(RegexbotClient bot) : base(bot) {
public EntryRole(RegexbotClient bot) : base(bot) {
DiscordClient.GuildMembersDownloaded += DiscordClient_GuildMembersDownloaded;
DiscordClient.UserJoined += DiscordClient_UserJoined;
DiscordClient.UserLeft += DiscordClient_UserLeft;
@ -19,6 +20,28 @@ public class EntryTimeRole : RegexbotModule {
TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
void IDisposable.Dispose() {
_workerTaskToken.Cancel();
_workerTask.Wait(2000);
_workerTask.Dispose();
}
private Task DiscordClient_GuildMembersDownloaded(SocketGuild arg) {
var data = GetGuildState<GuildData>(arg.Id);
if (data == null) return Task.CompletedTask;
var rolecheck = data.TargetRole.FindRoleIn(arg);
if (rolecheck == null) {
Log(arg, "Unable to find target role to be applied. Initial check has been skipped.");
return Task.CompletedTask;
}
foreach (var user in arg.Users.Where(u => !u.Roles.Contains(rolecheck))) {
data.WaitlistAdd(user.Id);
}
return Task.CompletedTask;
}
private Task DiscordClient_UserJoined(SocketGuildUser arg) {
GetGuildState<GuildData>(arg.Guild.Id)?.WaitlistAdd(arg.Id);
return Task.CompletedTask;
@ -88,7 +111,7 @@ public class EntryTimeRole : RegexbotModule {
// Attempt to get role.
var targetRole = gconf.TargetRole.FindRoleIn(g, true);
if (targetRole == null) {
ReportFailure(g.Id, "Unable to determine role to be applied. Does it still exist?", gusers);
ReportFailure(g, "Unable to determine role to be applied. Does it still exist?", gusers);
return;
}
@ -99,11 +122,11 @@ public class EntryTimeRole : RegexbotModule {
await item.AddRoleAsync(targetRole);
}
} catch (Discord.Net.HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden) {
ReportFailure(g.Id, "Unable to set role due to a permissions issue.", gusers);
ReportFailure(g, "Unable to set role due to a permissions issue.", gusers);
}
}
private void ReportFailure(ulong gid, string message, IEnumerable<SocketGuildUser> failedUserList) {
private void ReportFailure(SocketGuild g, string message, IEnumerable<SocketGuildUser> failedUserList) {
var failList = new StringBuilder();
var count = 0;
foreach (var item in failedUserList) {
@ -115,6 +138,6 @@ public class EntryTimeRole : RegexbotModule {
}
}
failList.Remove(0, 2);
Log(gid, message + " Failed while attempting to set role on the following users: " + failList.ToString());
Log(g, message + " Failed while attempting to set role on the following users: " + failList.ToString());
}
}

View file

@ -1,6 +1,6 @@
using RegexBot.Common;
namespace RegexBot.Modules.EntryTimeRole;
namespace RegexBot.Modules.EntryRole;
/// <summary>
/// Contains configuration data as well as per-guild timers for those awaiting role assignment.
@ -37,7 +37,7 @@ class GuildData {
}
try {
WaitTime = conf["WaitTime"].Value<int>();
WaitTime = conf[nameof(WaitTime)]!.Value<int>();
} catch (NullReferenceException) {
throw new ModuleLoadException("WaitTime value not specified.");
} catch (InvalidCastException) {
@ -54,7 +54,9 @@ class GuildData {
}
public void WaitlistAdd(ulong userId) {
lock (WaitingList) WaitingList.Add(userId, DateTimeOffset.UtcNow.AddSeconds(WaitTime));
lock (WaitingList) {
if (!WaitingList.ContainsKey(userId)) WaitingList.Add(userId, DateTimeOffset.UtcNow.AddSeconds(WaitTime));
}
}
public void WaitlistRemove(ulong userId) {

View file

@ -7,22 +7,22 @@
[RegexbotModule]
public class PendingOutRole : RegexbotModule {
public PendingOutRole(RegexbotClient bot) : base(bot) {
DiscordClient.GuildAvailable += DiscordClient_GuildAvailable;
DiscordClient.GuildMembersDownloaded += DiscordClient_GuildMembersDownloaded;
DiscordClient.GuildMemberUpdated += DiscordClient_GuildMemberUpdated;
}
private async Task DiscordClient_GuildAvailable(SocketGuild arg) {
private async Task DiscordClient_GuildMembersDownloaded(SocketGuild arg) {
var conf = GetGuildState<ModuleConfig>(arg.Id);
if (conf == null) return;
var trole = GetRole(arg);
if (trole == null) {
Log(arg.Id, "Unable to find target role to be applied. Was it renamed or deleted?");
var targetRole = conf.Role.FindRoleIn(arg, true);
if (targetRole == null) {
Log(arg, "Unable to find role to be applied. Initial check has been skipped.");
return;
}
foreach (var user in arg.Users.Where(u => u.IsPending.HasValue && u.IsPending.Value == false)) {
if (user.Roles.Contains(trole)) continue;
await user.AddRoleAsync(trole);
if (user.Roles.Contains(targetRole)) continue;
await user.AddRoleAsync(targetRole);
}
}
@ -32,9 +32,9 @@ public class PendingOutRole : RegexbotModule {
if (!(previous.Value.IsPending.HasValue && current.IsPending.HasValue)) return;
if (previous.Value.IsPending == true && current.IsPending == false) {
var r = GetRole(current.Guild);
var r = conf.Role.FindRoleIn(current.Guild, true);
if (r == null) {
Log(current.Guild.Id, $"Failed to update {current} - was the role renamed or deleted?");
Log(current.Guild, $"Failed to update role for {current} - was the role renamed or deleted?");
return;
}
await current.AddRoleAsync(r);
@ -47,20 +47,4 @@ public class PendingOutRole : RegexbotModule {
throw new ModuleLoadException("Configuration for this section is invalid.");
return Task.FromResult<object?>(new ModuleConfig((JObject)config));
}
private SocketRole? GetRole(SocketGuild g) {
var conf = GetGuildState<ModuleConfig>(g.Id);
if (conf == null) return null;
if (conf.Role.Id.HasValue) {
var result = g.GetRole(conf.Role.Id.Value);
if (result != null) return result;
} else {
foreach (var role in g.Roles) {
if (string.Equals(conf.Role.Name, role.Name, StringComparison.OrdinalIgnoreCase)) return role;
}
}
Log(g.Id, "Unable to find role in " + g.Name);
return null;
}
}

23
RegexBot/Common/Misc.cs Normal file
View file

@ -0,0 +1,23 @@
using Discord;
using Discord.WebSocket;
using System.Diagnostics.CodeAnalysis;
namespace RegexBot.Common;
/// <summary>
/// Miscellaneous useful functions that don't have a particular place anywhere else.
/// </summary>
public static class Misc {
/// <summary>
/// Performs common checks on the specified message to see if it fits all the criteria of a
/// typical, ordinary message sent by an ordinary guild user.
/// </summary>
public static bool IsValidUserMessage(SocketMessage msg, [NotNullWhen(true)] out SocketTextChannel? channel) {
channel = default;
if (msg.Channel is not SocketTextChannel ch) return false;
if (msg.Author.IsBot || msg.Author.IsWebhook) return false;
if (((IMessage)msg).Type != MessageType.Default) return false;
if (msg is SocketSystemMessage) return false;
channel = ch;
return true;
}
}

View file

@ -16,16 +16,13 @@ class MessageCachingSubservice {
bot.DiscordClient.MessageUpdated += DiscordClient_MessageUpdated;
}
private Task DiscordClient_MessageReceived(SocketMessage arg) {
if (arg.Channel is IDMChannel || arg is not SocketSystemMessage) return Task.CompletedTask;
return AddOrUpdateCacheItemAsync(arg);
}
private Task DiscordClient_MessageUpdated(Cacheable<IMessage, ulong> arg1, SocketMessage arg2, ISocketMessageChannel arg3) {
if (arg2.Channel is IDMChannel || arg2 is not SocketSystemMessage) return Task.CompletedTask;
return AddOrUpdateCacheItemAsync(arg2);
}
private Task DiscordClient_MessageReceived(SocketMessage arg)
=> AddOrUpdateCacheItemAsync(arg);
private Task DiscordClient_MessageUpdated(Cacheable<IMessage, ulong> arg1, SocketMessage arg2, ISocketMessageChannel arg3)
=> AddOrUpdateCacheItemAsync(arg2);
private async Task AddOrUpdateCacheItemAsync(SocketMessage arg) {
if (!Common.Misc.IsValidUserMessage(arg, out _)) return;
using var db = new BotDatabaseContext();
CachedGuildMessage? msg = db.GuildMessageCache.Where(m => m.MessageId == (long)arg.Id).SingleOrDefault();