RegexBot/Module/AutoRespond/AutoRespond.cs
Noikoio 287bb33d77 Removed manual module name setting
Modules will now be named based on their existing class name.
Their respective configuration sections are now also defined by the
same value. Documentation has been updated to reflect this.
2018-03-21 23:51:50 -07:00

108 lines
4 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 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));
}
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;
}
}
}
}
}