RegexBot/Module/AutoRespond/AutoRespond.cs
2017-11-11 19:12:24 -08:00

111 lines
4.1 KiB
C#

using Discord.WebSocket;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
namespace Noikoio.RegexBot.Module.AutoRespond
{
/// <summary>
/// Similar to <see cref="AutoMod"/>, but lightweight.
/// Provides the capability to define autoresponses for fun or informational purposes.
/// <para>
/// The major differences between this and <see cref="AutoMod"/> include:
/// <list type="bullet">
/// <item><description>Does not listen for message edits.</description></item>
/// <item><description>Moderators are not exempt from any defined triggers.</description></item>
/// <item><description>Responses are limited to the invoking channel.</description></item>
/// <item><description>Per-channel rate limiting.</description></item>
/// </list>
/// </para>
/// </summary>
partial class AutoRespond : BotModule
{
#region BotModule implementation
public override string Name => "AutoRespond";
public AutoRespond(DiscordSocketClient client) : base(client)
{
client.MessageReceived += Client_MessageReceived;
}
private async Task Client_MessageReceived(SocketMessage arg)
{
// Determine channel type - if not a guild channel, stop.
var ch = arg.Channel as SocketGuildChannel;
if (ch == null) return;
var defs = GetConfig(ch.Guild.Id) as IEnumerable<ConfigItem>;
if (defs == null) return;
foreach (var def in defs)
await Task.Run(async () => await ProcessMessage(arg, def));
}
[ConfigSection("autoresponses")]
public override async Task<object> ProcessConfiguration(JToken configSection)
{
var responses = new List<ConfigItem>();
foreach (var def in configSection.Children<JProperty>())
{
// All validation is left to the constructor
var resp = new ConfigItem(def);
responses.Add(resp);
}
if (responses.Count > 0)
await Log($"Loaded {responses.Count} definition(s) from configuration.");
return responses.AsReadOnly();
}
#endregion
private async Task ProcessMessage(SocketMessage msg, ConfigItem def)
{
if (!def.Match(msg)) return;
await Log($"'{def.Label}' triggered by {msg.Author} in {((SocketGuildChannel)msg.Channel).Guild.Name}/#{msg.Channel.Name}");
var (type, text) = def.Response;
if (type == ConfigItem.ResponseType.Reply) await ProcessReply(msg, text);
else if (type == ConfigItem.ResponseType.Exec) await ProcessExec(msg, text);
}
private async Task ProcessReply(SocketMessage msg, string text)
{
await msg.Channel.SendMessageAsync(text);
}
private async Task ProcessExec(SocketMessage msg, string text)
{
string[] cmdline = text.Split(new char[] { ' ' }, 2);
ProcessStartInfo 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 at most 5 seconds
if (p.HasExited)
{
if (p.ExitCode != 0) await Log("exec: Process returned exit code " + p.ExitCode);
using (var stdout = p.StandardOutput)
{
var result = await stdout.ReadToEndAsync();
await msg.Channel.SendMessageAsync(result);
}
}
else
{
await Log("exec: Process is taking too long to exit. Killing process.");
p.Kill();
return;
}
}
}
}
}