--- layout: post status: publish published: true title: Creating an IRC front/back-end from a C# web application wordpress_id: 249 wordpress_url: http://pro.grammatic.org/post-creating-an-irc-frontbackend-from-a-c-web-application-53.aspx date: !binary |- MjAwOC0wOS0xNyAxMzowMzoyMCArMDIwMA== date_gmt: !binary |- MjAwOC0wOS0xNyAxMzowMzoyMCArMDIwMA== categories: - Technology - .NET tags: - C# - eggdrop - IRC - bot comments: [] ---

This lengthy howto will show you how to hook up C# to an eggdrop IRC bot. I've taken this approach because it avoids the overhead of managing a fully fledged IRC client in C# whilst still providing 2-way command functionality between IRC and the application.

The app works by:

  1. Connecting to the eggdrop
  2. Logging in to the eggdrop
  3. Sending .say and .topic commands to the eggdrop
  4. Providing a web service to which the eggdrop may send a SOAP request to perform specific actions

So, let's get started!

The first part is the main IRC class. The version implemented here comes with a log4net appender implementation so that errors can be logged directly to IRC.

IRC.cs

{% highlight csharp %} using System; using System.Collections.Generic; using System.Text; using System.Net; using System.Net.Sockets; using System.Collections.Specialized; using System.Configuration; using System.IO; using System.Threading; namespace Logging { public class IRCLogger : log4net.Appender.AppenderSkeleton { protected override void Append(log4net.Core.LoggingEvent loggingEvent) { if (loggingEvent.Level == log4net.Core.Level.Error) { if (loggingEvent.ExceptionObject != null) { IRC.SendAdminMessage(loggingEvent.MessageObject.ToString() + ": " + loggingEvent.ExceptionObject.Message); } else { IRC.SendAdminMessage(loggingEvent.MessageObject.ToString()); } DateTime gmt = DateTime.Now.AddHours(5); IRC.SetAdminTopic("Last error rececived on " + gmt.ToShortDateString() + " at " + gmt.ToShortTimeString() + " (GMT)."); } } } public class IRC { static TcpClient client = null; static NetworkStream ns = null; static StreamWriter sw = null; static StreamReader sr = null; static bool isConnecting = false; static bool isSending = false; static List commands = new List(); private static void GetConnection() { bool ret = false; if (isConnecting) ret = true; while (isConnecting) { Thread.Sleep(1000); } if (ret) return; isConnecting = true; try { NameValueCollection botConfig = ConfigurationManager.GetSection("ircSetup/botDetails") as NameValueCollection; client = new TcpClient(botConfig["host"], int.Parse(botConfig["port"])); if (client.Connected) { ns = client.GetStream(); sw = new StreamWriter(ns); sr = new StreamReader(ns); sw.WriteLine(botConfig["username"]); sr.ReadLine(); sw.WriteLine(botConfig["password"]); sr.ReadLine(); sw.Flush(); } } finally { isConnecting = false; } } public static void SetTopic(string topic) { commands.Add(".topic #default [-http://mysite.com-] " + topic); DoSend(); } public static void SendMessage(string msg) { commands.Add(".say #default [-default-] " + msg); DoSend(); } public static void SendMessage(string msg, string channel) { commands.Add(".say " + channel + " [-default-] " + msg); DoSend(); } public static void SetAdminTopic(string topic) { commands.Add(".topic #adminchannel [-admin-] " + topic); DoSend(); } public static void SendAdminMessage(string msg) { commands.Add(".say #adminchannel [-admin-] " + msg); DoSend(); } private static void DoSend() { ThreadStart pts = new ThreadStart(SendToBot); Thread t = new Thread(pts); t.Start(); } private static void SendToBot() { while (isSending) { System.Threading.Thread.Sleep(1000); } isSending = true; try { while (commands.Count > 0) { //Filter the data using safe whitelist string msg = commands[0].ToString(); string regex = @".*[\r\n].*"; if (System.Text.RegularExpressions.Regex.IsMatch(msg, regex)) return; int attempts = 0; if (client == null) { GetConnection(); attempts++; if (attempts > 3) return; } while (client == null || !client.Connected) { GetConnection(); attempts++; if (attempts > 3) return; } if (client.Connected) { sw.WriteLine(msg); sw.Flush(); sr.ReadLine(); } while (ns.DataAvailable) { sr.ReadLine(); } commands.RemoveAt(0); } } catch (Exception ex) { Logging.Logger.Log.Info("Error sending to IRC: " + ex.Message); } finally { isSending = false; } } } } {% endhighlight %}

Then, put the relevant stuff into your web.config file:

{% highlight xml %}
{% endhighlight %}

Tada - it should hook up! But what about getting commands from the bot I hear you cry? Well, first of all you'll need to hook up the following TCL to your eggdrop:

{% highlight tcl %} package require http 2.7 bind pubm - * webServiceRequest proc webServiceRequest {nick uhost hand chan text} { if {[string index $text 0] == "!"} { set token [::http::geturl "http://www.yoursite.com/webservice.asmx" \ -type "text/xml; charset=utf-8" \ -headers {"SOAPAction" "http://www.yoursite.com/IrcMessage"} \ -query " \ \ \ \ YourUsername \ YourPassword \ Bot_1.0 \ \ \ \ \ $nick \ $uhost \ $chan \ $text \ \ \ "] upvar #0 $token state if {$state(status) != "ok"} { putserv "PRIVMSG $chan :ERROR" } } } {% endhighlight %}

Then, the webservice at http://www.yoursite.com/webservice.asmx should expose the following web method:

{% highlight csharp %} [WebMethod] [PrincipalPermissionAttribute(SecurityAction.Demand, Role = "Bot")] [SoapHeader("authentication")] public void IrcMessage(string user, string hostmark, string channel, string message) { IRC.ParseIrcMessage(user, hostmark, channel, message); } {% endhighlight %}

With which you can do what you like! The only proviso you might require is the following setup for the authentication SOAP header:

{% highlight csharp %} /// /// An Authentication class /// public class Authentication : SoapHeader { public string User; public string Password; public string Version; } {% endhighlight %}

And the following HttpModule:

{% highlight csharp %} using System; using System.Web; using System.IO; using System.Xml; using System.Xml.XPath; using System.Text; using System.Web.Services.Protocols; using System.Security.Principal; using System.Web.Security; using System.Diagnostics; using Logging; namespace YourWebService { public sealed class WebServiceAuthenticationModule : IHttpModule { public void Dispose() { } public void Init(HttpApplication app) { app.AuthenticateRequest += new EventHandler(this.OnEnter); } public string ModuleName { get { return "WebServiceAuthentication"; } } void OnEnter(Object source, EventArgs eventArgs) { HttpApplication app = (HttpApplication)source; HttpContext context = app.Context; Stream HttpStream = context.Request.InputStream; // Save the current position of stream. long posStream = HttpStream.Position; // If the request contains an HTTP_SOAPACTION // header, look at this message. if (context.Request.ServerVariables["HTTP_SOAPACTION"] == null) return; // Load the body of the HTTP message // into an XML document. XmlDocument dom = new XmlDocument(); string soapUser; string soapPassword; string soapVersion; try { dom.Load(HttpStream); // Reset the stream position. HttpStream.Position = posStream; // Bind to the Authentication header. soapUser = dom.GetElementsByTagName("User").Item(0).InnerText; soapPassword = dom.GetElementsByTagName("Password").Item(0).InnerText; soapVersion = dom.GetElementsByTagName("Version").Item(0).InnerText; if (Membership.ValidateUser(soapUser, soapPassword)) { MembershipUser user = Membership.GetUser(soapUser); GenericIdentity identity = new GenericIdentity(user.UserName); RolePrincipal rp = new RolePrincipal(identity); HttpContext.Current.User = rp; user.Comment = soapVersion; Membership.UpdateUser(user); Logger.Log.Info("Authenticated: " + soapUser + " :: " + soapVersion); } else { Logger.Log.Info("Failed to login as " + soapUser); Logging.IRC.SendAdminMessage("Attempt to login as " + soapUser + " with password " + soapPassword + " failed."); } } catch (Exception e) { Logging.IRC.SendAdminMessage("Error in SOAP parser: " + e.Message); // Reset the position of stream. HttpStream.Position = posStream; // Throw a SOAP exception. XmlQualifiedName name = new XmlQualifiedName("Load"); SoapException soapException = new SoapException( "Unable to read SOAP request", name, e); throw soapException; } return; } } } {% endhighlight %}

Which, of course, you will need to load in web.config:

{% highlight xml %} {% endhighlight %}