What's new
  • Visit Rebornbuddy
  • Visit Panda Profiles
  • Visit LLamamMagic
  • Visit Resources
  • Visit Downloads
  • Visit Portal

[Proof of Concept] ShutUp - AIML Chat Plugin

Apoc

Well-Known Member
Joined
Jan 16, 2010
Messages
2,790
Reaction score
94
This is just some proof of concept code. (The AIML processing code is ripped from here. All credits go to Dean for the processing code. I made very minor changes to make it more compliant with HB.

I'm not including any AIML, so you guys can handle all that. This is simply some POC code to show just how simple it is. (I don't have the time to write a proper AIML chatbot for WoW. Plus, it's not in my interests to do so. I have no reason to talk to people. :))

Anyhow, here's the code. I have it hooked up with HBs chat events, so assuming they all work, there shouldn't be any major bugs. Feel free to play with it.

Code is 100% reusable, so feel free to write your own version of this plugin and do whatever you want with it.

Also note; I wrote this in the span of about 15 minutes. So take it with a grain of salt.

Code:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;

using Styx;
using Styx.Helpers;
using Styx.Plugins.PluginClass;
using Styx.WoWInternals;

namespace ShutUp
{
    public class ShutUpPlugin : HBPlugin
    {
        #region Overrides of HBPlugin

        // Just keeps track of whether this is the first pulse or not.
        private ChatProcessor _chatProcessor;
        private bool _firstPulse = true;
        public override string Name { get { return "ShutUp"; } }
        public override string Author { get { return "Apoc"; } }
        public override Version Version { get { return new Version(1, 0, 0, 0); } }

        public override void Pulse()
        {
            if (_firstPulse)
            {
                Initialize();
                _firstPulse = false;
            }
        }

        private void Initialize()
        {
            _chatProcessor = new ChatProcessor();
            // Load from C:\Path\To\Honorbuddy\AIML\
            _chatProcessor.Load(Path.Combine(Logging.ApplicationPath, "AIML"));
            WoWChat.NewGuildMessage += WoWChat_HandleMessage;
            WoWChat.NewOfficerMessage += WoWChat_HandleMessage;
            WoWChat.NewPartyMessage += WoWChat_HandleMessage;
            WoWChat.NewRaidMessage += WoWChat_HandleMessage;
            WoWChat.NewSayMessage += WoWChat_HandleMessage;
            WoWChat.NewWhisperFromMessage += WoWChat_HandleMessage;
            WoWChat.NewYellMessage += WoWChat_HandleMessage;
        }

        private void WoWChat_HandleMessage(ChatMessageEventArgs e)
        {
            if (e.Message.Text.ToUpper().Contains(StyxWoW.Me.Name.ToUpper()))
            {
                _chatProcessor.Inputs.IndexOf(e.Message.Text, 0);
                _chatProcessor.Thats.Insert(0, _chatProcessor.FindTemplate(e.Message.Text, _chatProcessor.Thats[0], "*"));
                string returnMessage = _chatProcessor.Thats[0];
                if (e.Message.ChatType == ChatType.WhisperInform)
                {
                    WoWChat.SendChatMessage(returnMessage, ChatType.WhisperTo, e.Message.Sender);
                }
                else
                {
                    WoWChat.SendChatMessage(returnMessage, e.Message.ChatType, "");
                }
            }
        }

        #endregion
    }

    internal class ChatProcessor
    {
        public readonly List<string> Thats = new List<string>();
        public readonly List<string> Inputs = new List<string>();
        private readonly Dictionary<string, string> _botData = new Dictionary<string, string>();
        private readonly AIMLData _data = new AIMLData(null, string.Empty);
        private readonly Dictionary<string, string> _predicates = new Dictionary<string, string>();
        private readonly Random _rand = new Random(DateTime.Now.Millisecond);

        public void Load(string aimlDirectory)
        {
            foreach (string file in Directory.GetFiles(aimlDirectory, "*.aiml"))
            {
                XDocument doc = XDocument.Load(file);
                XElement first = doc.Element("aiml");
                IEnumerable<XElement> topics = first.Elements("topic");
                IEnumerable<XElement> cats = first.Elements("category");
                if (topics != null && topics.Count() > 0)
                {
                    foreach (XElement topic in topics)
                    {
                        string topicName = topic.Attribute("name") == null
                                               ? "*"
                                               : topic.Attribute("name").Value;
                        foreach (XElement cat in topic.Elements())
                            ProcessCategory(cat, topicName);
                    }
                }
                if (cats == null || cats.Count() <= 0)
                    continue;
                foreach (XElement cat in cats)
                    ProcessCategory(cat, "*");
            }
        }

        private void ProcessCategory(XElement cat, string topic)
        {
            XElement patt = cat.Element("pattern");
            XElement temp = cat.Element("template");
            XElement that = cat.Element("that");
            if (patt == null || temp == null)
                return;
            string t = that != null ? that.InnerText() : "*";
            string pattern = string.Format("{0} <THAT> {1} <TOPIC> {2}", patt.Nodes().First(), t.Trim(), topic.Trim());
            LoadWord(_data, new List<string>(pattern.Split(' ')), temp.InnerText());
        }

        public string FindTemplate(string text, string that, string topic)
        {
            string pattern = string.Format("{0} <THAT> {1} <TOPIC> {2}", Normalise(text).Trim(), "*", topic.Trim());
            PatternData temp = FindTemplateRecursive(_data, new List<string>(pattern.Split(' ')), new PatternData(), 0);
            string result = InterpretTemplate(temp.Template, temp.WildTexts, text, that, topic);
            return result.Trim().Replace("  ", " ");
        }

        public string Normalise(string text)
        {
            return Regex.Replace(text, @"[^\w\ ]", "").Trim().Replace("  ", " ");
        }

        private string InterpretTemplate(string template, List<WildData> wilds, string text, string that, string topic)
        {
            string xmltemplate = string.Format("<process>{0}</process>", template);
            XElement doc = XElement.Parse(xmltemplate);
            if (doc.Elements().Count() == 0)
                return doc.Value;
            string response = "";
            foreach (XNode node in doc.Nodes())
            {
                if (node.NodeType != XmlNodeType.Element)
                {
                    response += " " + node;
                    continue;
                }
                var element = node as XElement;
                switch (element.Name.LocalName.ToUpper())
                {
                    case "INPUT":
                        response += " " + ProcessInput(element).Trim();
                        break;
                    case "THAT":
                        response += " " + ProcessThat(element).Trim();
                        break;
                    case "THATSTAR":
                        response += " " + ProcessThatStar(element, wilds).Trim();
                        break;
                    case "TOPICSTAR":
                        response += " " + ProcessTopicStar(element, wilds).Trim();
                        break;
                    case "STAR":
                        response += " " + ProcessStar(element, wilds).Trim();
                        break;
                    case "PERSON":
                        response += " " + ProcessPerson(wilds).Trim();
                        break;
                    case "SET":
                        response += " " + ProcessSet(element, wilds, text, that, topic).Trim();
                        break;
                    case "GET":
                        response += " " + ProcessGet(element).Trim();
                        break;
                    case "THINK":
                        response += " " + ProcessThink(element, wilds, text, that, topic).Trim();
                        break;
                    case "RANDOM":
                        response += " " + ProcessRandom(element, wilds, text, that, topic).Trim();
                        break;
                    case "BOT":
                        response += " " + ProcessBot(element).Trim();
                        break;
                    case "SR":
                        response += " " + ProcessSR(element, wilds, text, that, topic).Trim();
                        break;
                    case "SRAI":
                        response += " " + ProcessSRAI(element, wilds, text, that, topic).Trim();
                        break;
                    default:
                        return element.ToString().Trim();
                }
            }
            return response.Trim();
        }

        private string ProcessInput(XElement element)
        {
            int num = 0;
            XAttribute index = element.Attribute("index");
            if (index != null)
                num = int.Parse(index.Value.Split(',')[0]) - 1;
            if (num >= Inputs.Count)
                return string.Empty;
            return Inputs[num];
        }

        private string ProcessThat(XElement element)
        {
            int num = 0;
            XAttribute index = element.Attribute("index");
            if (index != null)
                num = int.Parse(index.Value.Split(',')[0]) - 1;
            if (num >= Thats.Count)
                return string.Empty;
            return Thats[num];
        }

        private static string ProcessThatStar(XElement element, IEnumerable<WildData> wilds)
        {
            List<WildData> w = wilds.Where(a => a.WildType == StarType.That).ToList();
            if (w.Count() == 0)
                return string.Empty;
            int num = 0;
            XAttribute index = element.Attribute("index");
            if (index != null)
                num = int.Parse(index.Value.Split(',')[0]) - 1;
            if (num >= w.Count)
                return string.Empty;
            return w[num].WildText;
        }

        private static string ProcessTopicStar(XElement element, IEnumerable<WildData> wilds)
        {
            List<WildData> w = wilds.Where(a => a.WildType == StarType.Topic).ToList();
            if (w.Count() == 0)
                return string.Empty;
            int num = 0;
            XAttribute index = element.Attribute("index");
            if (index != null)
                num = int.Parse(index.Value.Split(',')[0]) - 1;
            if (num >= w.Count)
                return string.Empty;
            return w[num].WildText;
        }

        private static string ProcessStar(XElement element, IEnumerable<WildData> wilds)
        {
            List<WildData> w = wilds.Where(a => a.WildType == StarType.Pattern).ToList();
            if (w.Count() == 0)
                return string.Empty;
            int num = 0;
            XAttribute index = element.Attribute("index");
            if (index != null)
                num = int.Parse(index.Value) - 1;
            if (num >= w.Count)
                return string.Empty;
            return w[num].WildText;
        }

        private static string ProcessPerson(IList<WildData> wilds)
        {
            if (wilds.Count == 0)
                return string.Empty;
            string[] words = wilds[0].WildText.Split(' ');
            for (int i = 0; i < words.Count(); i++)
            {
                if (words[i].Trim().ToUpper() == "I")
                    words[i] = "you";
                if (words[i].Trim().ToUpper() == "MY")
                    words[i] = "your";
            }
            return string.Join(" ", words).Replace("you am", "you are");
        }

        private string ProcessSet(XElement element, List<WildData> wilds, string text,
                                  string that, string topic)
        {
            if (element.Attribute("name") == null)
                return string.Empty;
            string att = element.Attribute("name").Value;
            if (!_predicates.ContainsKey(att))
                _predicates.Add(att, "");
            _predicates[att] = InterpretTemplate(element.InnerText(), wilds, text, that, topic);
            return _predicates[att];
        }

        private string ProcessGet(XElement element)
        {
            if (element.Attribute("name") == null)
                return string.Empty;
            string att = element.Attribute("name").Value;
            return !_predicates.ContainsKey(att) ? string.Empty : _predicates[att];
        }

        private string ProcessThink(XElement element, List<WildData> wilds, string text,
                                    string that, string topic)
        {
            InterpretTemplate(element.InnerText(), wilds, text, that, topic);
            return string.Empty;
        }

        private string ProcessRandom(XElement element, List<WildData> wilds, string text,
                                     string that, string topic)
        {
            int num = _rand.Next(0, element.Elements().Count());
            var ret = element.Elements().ToList()[num].InnerText();
            return InterpretTemplate(ret, wilds, text, that, topic);
        }

        private string ProcessSRAI(XElement element, List<WildData> wilds, string text,
                                   string that, string topic)
        {
            string t = InterpretTemplate(element.InnerText(), wilds, text, that, topic);
            return FindTemplate(t, that, topic);
        }

        private string ProcessSR(XElement element, List<WildData> wilds, string text,
                                 string that, string topic)
        {
            List<WildData> w = wilds.Where(a => a.WildType == StarType.Pattern).ToList();

            if (w.Count() == 0)
                return string.Empty;
            int num = 0;
            XAttribute index = element.Attribute("index");
            if (index != null)
                num = int.Parse(index.Value) - 1;
            if (num >= w.Count)
                return string.Empty;
            string t = InterpretTemplate(w[num].WildText, wilds, text, that, topic);
            return FindTemplate(t, that, topic);
        }

        private string ProcessBot(XElement element)
        {
            return _botData[element.Attribute("name").Value.ToUpper()];
        }

        private static PatternData FindTemplateRecursive(AIMLData ai, List<string> text, PatternData data, int searchPos)
        {
            string key = text[searchPos];
            if (data.IsInWildcard && searchPos < text.Count - 1 && !ai.Data.ContainsKey("_") && !ai.Data.ContainsKey(key.ToUpper()) && !ai.Data.ContainsKey("*"))
            {
                data.WildTexts[data.WildTexts.Count - 1].WildText += key + " ";
                return FindTemplateRecursive(ai, text, data, searchPos + 1);
            }
            if (ai.Data.ContainsKey("_"))
            {
                if (searchPos == text.Count - 1)
                {
                    data.IsAnswer = true;
                    data.Template = ai.Data["_"].Template;
                    return data;
                }
                data.WildTexts.Add(new WildData {WildText = key + " ", WildType = data.WildType});
                data.IsInWildcard = true;
                data = FindTemplateRecursive(ai.Data["_"], text, data, searchPos + 1);
                if (data.IsAnswer)
                    return data;
                data.WildTexts.RemoveAt(data.WildTexts.Count - 1);
                data.WildType = data.WildTexts.Count == 0 ? StarType.Pattern : data.WildTexts[data.WildTexts.Count - 1].WildType;
            }
            if (ai.Data.ContainsKey(key.ToUpper()))
            {
                if (searchPos == text.Count - 1)
                {
                    data.IsAnswer = true;
                    data.Template = ai.Data[key.ToUpper()].Template;
                    return data;
                }
                if (key.ToUpper() == "<THAT>")
                    data.WildType = StarType.That;
                if (key.ToUpper() == "<TOPIC>")
                    data.WildType = StarType.Topic;
                data.IsInWildcard = false;
                data = FindTemplateRecursive(ai.Data[key.ToUpper()], text, data, searchPos + 1);
                if (data.IsAnswer)
                    return data;
            }
            if (!data.IsAnswer && ai.Data.ContainsKey("*"))
            {
                if (searchPos == text.Count - 1)
                {
                    data.IsAnswer = true;
                    data.Template = ai.Data["*"].Template;
                    return data;
                }
                data.WildTexts.Add(new WildData {WildText = key + " ", WildType = data.WildType});
                data.IsInWildcard = true;
                data = FindTemplateRecursive(ai.Data["*"], text, data, searchPos + 1);
                if (data.IsAnswer)
                    return data;
                data.WildTexts.RemoveAt(data.WildTexts.Count - 1);
                data.WildType = data.WildTexts.Count == 0 ? StarType.Pattern : data.WildTexts[data.WildTexts.Count - 1].WildType;
            }
            return data;
        }

        private static void LoadWord(AIMLData parent, List<string> pattern, string template)
        {
            string key = pattern[0].ToUpper().Trim();
            pattern.RemoveAt(0);
            if (!parent.Data.ContainsKey(key))
                parent.Data.Add(key, new AIMLData(parent, key));
            if (pattern.Count > 0)
                LoadWord(parent.Data[key], pattern, template);
            else
                parent.Data[key].Template = template;
        }
    }

    public class AIMLData
    {
        public AIMLData(AIMLData parent, string key)
        {
            Parent = parent;
            Key = key;
            Data = new Dictionary<string, AIMLData>();
        }
        public Dictionary<string, AIMLData> Data { get; private set; }
        public AIMLData Parent { get; private set; }
        public string Template { get; set; }
        public string Key { get; set; }
    }

    public class PatternData
    {
        public PatternData()
        {
            WildTexts = new List<WildData>();
            WildType = StarType.Pattern;
        }
        public string Template { get; set; }
        public bool IsAnswer { get; set; }
        public bool IsInWildcard { get; set; }
        public StarType WildType { get; set; }
        public List<WildData> WildTexts { get; private set; }
    }

    public class WildData
    {
        public string WildText { get; set; }
        public StarType WildType { get; set; }
    }

    public enum StarType
    {
        Pattern,
        That,
        Topic
    }

    public static class ExtensionMethods
    {
        public static string InnerText(this XElement element)
        {
            return element.Nodes().Aggregate("",
                (current, node) => current + node.ToString());
        }
    }
}

Enjoy folks.
 
Back
Top