using Discord.WebSocket; using Newtonsoft.Json.Linq; using System.Collections.Generic; using System.Diagnostics; using System.Threading.Tasks; namespace Noikoio.RegexBot.Feature.AutoRespond { /// /// Similar to , but lightweight. /// Provides the capability to define autoresponses for fun or informational purposes. /// /// The major differences between this and include: /// /// Does not listen for message edits. /// Moderators are not exempt from any defined triggers. /// Responses are limited to the invoking channel. /// Per-channel rate limiting. /// /// /// 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; if (defs == null) return; foreach (var def in defs) await Task.Run(async () => await ProcessMessage(arg, def)); } [ConfigSection("autoresponses")] public override async Task ProcessConfiguration(JToken configSection) { var responses = new List(); foreach (var def in configSection.Children()) { // 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; } } } } }