112 lines
4.2 KiB
C#
112 lines
4.2 KiB
C#
using Discord.WebSocket;
|
|
using Newtonsoft.Json.Linq;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Noikoio.RegexBot.Feature.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 : BotFeature
|
|
{
|
|
#region BotFeature 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;
|
|
|
|
// TODO either search server by name or remove server name support entirely
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|