RegexBot/Modules-SelfHosted/AutoScriptResponder/AutoScriptResponder.cs
Noikoio ffa5b5754b Several fixes after a round of testing
Fixed the following compilation errors:
-Moderators collection not initialized
-Outdated method signatures for ban and kick in ModuleBase
-Update author name in manifests
-Fixed incorrect method signature in AutoScriptResponder

Minor improvements:
-Updated external dependencies
-Remove unused variables in ConfDefinition of RegexModerator
-Improve parallel execution of matches?
-Send exception message on logging failure to reporting channel
-Slightly change ModuleLoader logging output
-Add Discord.Net unhandled exception output to logging
-Updated link to Github
-Changed GuildState exception handling message for conciseness

Fixes:
-SQL index creation in LoggingService
-SQL view creation in UserCacheService
-Add casts from ulong to long in SQL inserts
-External modules no longer loaded twice
-Non-Info messages from Discord.Net will now be reported
-User data had not been recorded at proper times
-Some modules had been returning null instead of Task with null
-Guild state exception handling should not have handled config errors
2019-06-21 15:05:58 -07:00

96 lines
3.7 KiB
C#

using Discord.WebSocket;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
namespace Kerobot.Modules.AutoScriptResponder
{
/// <summary>
/// Meant to be highly identical to AutoResponder, save for its differentiating feature.
/// This may not be the best approach to it, but do try and copy any relevant changes from one into
/// the other whenever they occur.
/// The feature in question: It executes external scripts and replies with their output.
/// </summary>
[KerobotModule]
class AutoScriptResponder : ModuleBase
{
public AutoScriptResponder(Kerobot kb) : base(kb)
{
DiscordClient.MessageReceived += DiscordClient_MessageReceived;
}
private async Task DiscordClient_MessageReceived(SocketMessage arg)
{
if (!(arg.Channel is SocketGuildChannel ch)) return;
if (arg.Author.IsBot || arg.Author.IsWebhook) return;
var definitions = GetGuildState<IEnumerable<Definition>>(ch.Guild.Id);
if (definitions == null) return; // No configuration in this guild; do no further processing
var tasks = new List<Task>();
foreach (var def in definitions)
{
tasks.Add(Task.Run(async () => await ProcessMessageAsync(arg, def)));
}
await Task.WhenAll(tasks);
}
public override Task<object> CreateGuildStateAsync(ulong guild, JToken config)
{
// Guild state is a read-only IEnumerable<Definition>
if (config == null) return Task.FromResult<object>(null);
var guildDefs = new List<Definition>();
foreach (var defconf in config.Children<JProperty>())
{
// Getting all JProperties in the section.
// Validation of data is left to the Definition constructor. ModuleLoadException thrown here:
var def = new Definition(defconf);
guildDefs.Add(def);
// TODO global options
}
return Task.FromResult<object>(guildDefs.AsReadOnly());
}
// ASR edit: this whole thing.
private async Task ProcessMessageAsync(SocketMessage msg, Definition def)
{
if (!def.Match(msg)) return;
var ch = (SocketGuildChannel)msg.Channel;
string[] cmdline = def.Command.Split(new char[] { ' ' }, 2);
var ps = new ProcessStartInfo()
{
FileName = cmdline[0],
Arguments = (cmdline.Length == 2 ? cmdline[1] : ""),
UseShellExecute = false, // ???
CreateNoWindow = true,
RedirectStandardOutput = true
};
using (Process p = Process.Start(ps))
{
p.WaitForExit(5000); // waiting 5 seconds at most
if (p.HasExited)
{
if (p.ExitCode != 0)
{
await LogAsync(ch.Guild.Id, $"'{def.Label}': 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
{
await LogAsync(ch.Guild.Id, $"'{def.Label}': Process has not exited in 5 seconds. Killing process.");
p.Kill();
}
}
}
}
}