Using SmartIrc4net

Are you interested in increasing the functionality of your IRC client by using a .NET language? The SmartIrc4net library can help. Keep reading to learn more about its layers of interaction and how they work.

Introduction

Internet Relay Chat (IRC) is a very popular means of communication. One can simply download a free client, pick a nickname and join a server of his or her choice. Of course, it doesn’t stop there. It’s possible to interact with IRC in a variety of languages. It’s not hard (well, it doesn’t have to be), and it can be very rewarding, even if the reward is just a sense of personal satisfaction.

For .NET languages, the SmartIrc4net library stands out. It allows for varying degrees of complexity by providing three layers of interaction, each layer being a subclass of the previous layer and providing more advanced functionality.

This article will take a look at using each of the three connectivity layers provided by the SmartIrc4net library. First, though, you’ll need to download a copy of the library at its official website:

http://smartirc4net.meebey.net/

Download either the source or the DLL and place it in the same directory in which you’ll be working.

{mospagebreak title=IrcConnection}

The first layer of SmartIrc4net is the IrcConnection class. This class is very basic. It contains a handful of methods that do things like connect and disconnect from the server, as well as a few properties that you can access and a few events that you can respond to. Here’s the code required to connect to a given server:

using Meebey.SmartIrc4net;
using
System;
using
System.Collections.Generic;
using System.Text;

class  ConnectionDemo
{
    private  IrcConnection irc = new  IrcConnection
();
    private  string server = “irc.freenode.com”
;
    private  int
port = 6667;
    private  string channel = “#smartirc4net_test”;

    static  void Main()
    {
        ConnectionDemo demo = new  ConnectionDemo
();
    }

    public ConnectionDemo()
    {
        irc.Connect(server, port);
        irc.WriteLine(Rfc2812.Nick(“CSharp”), Priority
.Critical);
        irc.WriteLine(Rfc2812.User(“CSharp”, 0, “CSharp Bot”),
Priority
.Critical);
        irc.WriteLine(Rfc2812
.Join(channel));
        irc.Listen();
    }
}

The first line of the code points to the library, as you can see. We then create a class and provide a static Main method, which creates an instance of the class. Four member variables are created. One holds our IrcConnection instance, and the other three contain the server to connect to, the port to use and the channel to join. In the constructor, the Connect method of IrcConnection is called, and the server and port to use are passed as arguments.

Upon connecting to the server, we must provide a nickname. We do this by using WriteLine:

irc.WriteLine(Rfc2812.Nick(“CSharp”), Priority.Critical);

The WriteLine method writes a complete line to the server. Here, we provide two arguments. The first argument is a string containing the contents of the line. While it is always possible to provide the raw IRC command for this, the static method Nick of the Rfc2812 object serves as a nice shortcut to this. All we have to do is provide the nickname itself. Notice, however, that RFC 2812 is used. This is one of the “new” RFCs intended to improve on the original RFC 1459. That said, keep in mind that RFC 1459 is still the official IRC RFC.

The second argument we pass makes use of the Priority enumeration. This enumeration contains the members Low, BelowMedium, Medium, AboveMedium, High and Critical. Since we absolutely must provide a nickname as soon as possible (or else we’ll be disconnected), Critical is passed.

A username, userlevel and real name are then specified using WriteLine and User, and the priority is again set to Critical. Next, the application joins the channel specified in the channel string. Finally, the Listen method is called. The Listen method provides an infinite loop wherein data is read from the server as long as the application is still connected. Optionally, a Boolean can be passed as an argument. If false is passed, the method will return if there are no more messages to be received. The ListenOnce method also provides this functionality.

Of course, our application has a slight problem. When the server pings the application to make sure that it is still connected, our application doesn’t respond. However, this is easily fixed. We simply have to respond to the server when we receive a ping message. To do this, we need to examine the incoming messages. While we could always stuff everything in one loop, there is no need to do this in SmartIrc4net. The library raises an event each time a line is received. All we have to do is respond to this. Here, we create a method that will handle incoming messages and examine them for ping messages:

    void OnReadLine(object sender, ReadLineEventArgs e)
    {
        if (e.Line.StartsWith(PING
))
        {
            string server = e.Line.Split(‘ ‘
)[1];
            irc.WriteLine(“PONG “+ server, Priority
.Critical);
            Console.WriteLine(“Responded to ping at {0}.”,
DateTime
.Now.ToShortTimeString());
        }
    }

Notice how we are dealing with a ReadLineEventArgs object. This object contains a Line property, which simply contains the received line.

Now we have to hook our event handler up to the appropriate event. Do this in the constructor, like this:

    public ConnectionDemo()
    {
        irc.OnReadLine += new  ReadLineEventHandler(OnReadLine);

        irc.Connect(server, port);
        irc.WriteLine(Rfc2812.Nick(“CSharp”), Priority
.Critical);
        irc.WriteLine(Rfc2812.User(“CSharp”, 0, “CSharp Bot”),
Priority
.Critical);
        irc.WriteLine(Rfc2812
.Join(channel));
        irc.Listen();
    }

Several other events are available to us as well, such as OnConnecting, OnConnected and OnDisconnected. Let’s respond to these events, and let’s restructure the application so that we register with the server and join the specified channel when the OnConnected event is raised. The new constructor should look like this:

    public ConnectionDemo()
    {
        irc.OnConnected += new  EventHandler
(OnConnected);
        irc.OnConnecting += new  EventHandler
(OnConnecting);
        irc.OnDisconnected += new  EventHandler
(OnDisconnected);
        irc.OnReadLine += new  ReadLineEventHandler
(OnReadLine);

        irc.Connect(server, port);
    }

Here are the additional event handlers. Each writes a message to the console indicating the status of the application:

    void OnConnected(object sender, EventArgs e)
    {
        Console.WriteLine(“Connected.”
);
        irc.WriteLine(Rfc2812.Nick(“CSharp”), Priority
.Critical);
        irc.WriteLine(Rfc2812.User(“CSharp”, 0, “CSharp Bot”),
Priority
.Critical);
        irc.WriteLine(Rfc2812
.Join(channel));
        irc.Listen();
    }

    void OnConnecting(object sender, EventArgs e)
    {
        Console.WriteLine(“Connecting to {0}.”
, server);
    }

    void OnDisconnected(object sender, EventArgs e)
    {
        Console.WriteLine(“Disconnected.”
);
    }

We can also modify the application to handle connection failures. Our IrcConnection object can be configured so that it automatically retries a failed connection attempt and automatically reconnects upon being disconnected. Of course, the library won’t try to reconnect forever. Some basic error handling for this situation would be nice:

        irc.AutoRetry = true;
        irc.AutoRetryDelay = 10;
        irc.AutoReconnect = true;

        try
        {
            irc.Connect(server, port);
        }
        catch (Exception
e)
        {
            Console.WriteLine(“Failed to connect:n”
+ e.Message);
            Console
.ReadKey();
        }

 

The AutoRetryDelay property sets the interval between connection attempts.

{mospagebreak title=IrcCommands}

The next layer is provided by theIrcCommands class. IrcCommands derives from IrcConnection, but it adds several dozen additional methods that provide shortcuts to WriteLine (though WriteLine is still available should you ever need it). We can easily modify the previous application to use IrcCommands rather than IrcConnection. First, make irc an instance of IrcCommands rather than IrcConnection:

private  IrcCommandsirc = new  IrcCommands();

Then, we simply have to replace two methods:

    void OnConnected(object sender, EventArgs e)
    {
        Console.WriteLine(“Connected.”
);
        irc.RfcNick(“CSharp”, Priority
.Critical);
        irc.RfcUser(“CSharp”, 0, “CSharp Bot”,
Priority
.Critical);
        irc.RfcJoin(channel);
        irc.Listen();
    }

    void OnReadLine(object sender, ReadLineEventArgs e)
    {
        if (e.Line.StartsWith(PING
))
        {
            string server = e.Line.Split(‘ ‘
)[1];
            irc.RfcPong(server, Priority
.Critical);
            Console.WriteLine(“Responded to ping at {0}.”,
DateTime
.Now.ToShortTimeString());
        }
    }

There, our application now makes use of the new methods provided by IrcCommands. IrcCommands also defines a number of other methods, including methods such as Ban, Voice and Deop. These methods are pretty straightforward, but the SendMessage method is worth taking a look at. It requires three arguments, but it can also take an additional fourth argument containing a member of the Priority enumeration. Here are a few examples of the SendMessage method in use:

        irc.SendMessage(SendType.Message, “Peyton”, “Hello.”);
        irc.SendMessage(SendType.Message, channel, “Hello,
channel.”
, Priority
.BelowMedium);
        irc.SendMessage(SendType.Action, channel, “dances”
);
        irc.SendMessage(SendType.Notice, “Peyton”, “THIS IS A
NOTICE.”
);
        irc.SendMessage(SendType.CtcpRequest, “Peyton”,
“VERSION”);

        // Convert the time to a UNIX timestamp
        TimeSpan timestamp = (DateTime.UtcNow – new  DateTime
(1970, 1, 1));
        irc.SendMessage(SendType.CtcpReply, “Peyton”, PING+
(
int)timestamp.TotalSeconds);

{mospagebreak title=IrcClient}

IrcClient is the third and final layer of SmartIrc4net. It is a subclass of IrcCommands, and it provides the most functionality. It only defines a few more methods and properties, but it makes a great many more events available. It’s possible to create a very event-driven application using IrcClient. Let’s put together a very basic application upon which we can expand:

using Meebey.SmartIrc4net;
using
System;
using
System.Collections.Generic;
using System.Text;

class  ClientDemo
{
    private  IrcClientirc = new  IrcClient
();
    private  string server = “irc.freenode.com”
;
    private  int
port = 6667;
    private static  void
Main()
    {
        ClientDemo demo = new  ClientDemo
();
    }

    public ClientDemo()
    {
        irc.OnConnected += new  EventHandler(OnConnected);

        try
        {
            irc.Connect(server, port);
        }
        catch (Exception
e)
        {
            Console.Write(“Failed to connect:n”
+ e.Message);
            Console
.ReadKey();
        }
    }

    void OnConnected(object sender, EventArgs e)
     {
        irc.Login(“CSharp”, “CSharp Bot”, 0, “CSharp”
);
        irc.RfcJoin(channel);
        irc.Listen();
    }
}

In the above application, the Login method is used rather than the Nick and User methods. There are several constructors for this method, but the one we use takes arguments for the username, real name, userlevel and nickname, respectively.

Notice how the application doesn’t respond to ping messages. This is because IrcClient handles them automatically. However, it’s still possible to respond to ping messages in case extra handling is necessary. Instead of subscribing to OnReadLine as was done before, however, we can subscribe to OnPing:

irc.OnPing += new  PingEventHandler(OnPing);

Then we can provide an event handler:

    void OnPing(object sender, PingEventArgs e)
    {
        Console.WriteLine(“Responded to ping at {0}.”,
DateTime
.Now.ToShortTimeString());
    }

The IrcClient class contains many events similar to OnPing. Not all of them can be covered in this article, but we will take a look at OnQueryMessage and OnChannelMessage, which are triggered by personal messages and channel messages:

irc.OnChannelMessage += new  IrcEventHandler(OnChannelMessage);
irc.OnQueryMessage += new  IrcEventHandler(OnQueryMessage);

IrcEventHandler contains a member named Data of the type IrcMessageData, which describes a message. IrcMessageData has properties that describe things such as the channel the message comes from, the nickname of the person who sent it and the type of the message. Here, we use some of these properties when we respond to the two events:

    void OnChannelMessage(object  sender, IrcEventArgs e)
    {
        Console.WriteLine(e.Data.Type + “:”
);
        Console.WriteLine(“(“+ e.Data.Channel + “) <“+
e.Data.Nick +
“> “
+ e.Data.Message);
    }

    void OnQueryMessage(object sender, IrcEventArgs e)
    {
        Console.WriteLine(e.Data.Type + “:”
);
        Console.WriteLine(“(private) <“+ e.Data.Nick + “> “
+
e.Data.Message);
    }

The final feature I’d like to point out in IrcClient is the ability to keep track of channels over time. This feature is named active channel syncing and has to be enabled before it can be used:

irc.ActiveChannelSyncing = true;

To demonstrate active channel syncing, we’ll revise OnQueryMessage to examine incoming messages for the commands “!list” and “!info.” If the former command is received, the querying user will be sent a list of channels that the application is currently in. If the latter command is received, along with a channel (for example, “!info #smartirc4net_test”), then some basic information about that channel will be sent to the querying user:

    void OnQueryMessage(object sender, IrcEventArgs e)
    {
        if (e.Data.MessageArray[0] == “!list”
)
        {
            string channels = String.Join(“, “
, irc.GetChannels
());
            irc.SendMessage(SendType
.Message, e.Data.Nick,
channels);
        }
        else  if (e.Data.MessageArray[0] == “!info”
)
        {
            Channel channel = irc.GetChannel(e.Data.MessageArray
[1]);

            List<string> userList = new  List<string>();
            foreach (ChannelUseruser in
channel.Users.Values)
            {
                string
nick = user.Nick;
                if
(user.IsOp)
                    nick = “@”
+ nick;
                else  if
(user.IsVoice)
                    nick = “+”
+ nick;
                userList.Add(nick);
            }
            string users = String.Join(“, “, userList.ToArray());

            irc.SendMessage(SendType.Message, e.Data.Nick,
channel.Name);
            irc.SendMessage(SendType.Message, e.Data.Nick,
“Topic: “
+ channel.Topic);
            irc.SendMessage(SendType.Message, e.Data.Nick,
“Users: “
+ users);
        }
    }

The first situation is pretty simple. The GetChannels method returns an array of strings representing channel names. The strings are joined together, and the resulting string is sent to the user. In the second situation, the GetChannel method is used to retrieve a Channel object representing the given channel. The channel name, the channel topic and a list of channel users are sent to the querying user. The users are obtained by examining the Users Hashtable. We extract a ChannelUser object for each user in the channel, and we format a nickname string depending on the user being opped or voiced.

Conclusion

The SmartIrc4net library offers users both flexibility and power in developing IRC-enabled applications by providing three layers of connectivity: IrcConnection, IrcCommands and IrcClient, each layer built upon the previous one. The library allows developers to build event-driven applications, too, which eliminates unnecessary coding. The SmartIrc4net library proves that working with IRC through .NET is not difficult at all.

One thought on “Using SmartIrc4net

  1. Working with IRC might not be productive on, say, a business scale, but I’ve always found it fun and rewarding on a more personal scale. It is easily done, here with SmartIrc4net, which takes an interesting approach by providing three levels of interaction, and the results can be quite creative. There are bots in existance for relaying Counter-Strike scores, for providing up-to-date news and for doing many other things, both useful and silly. If you create anything in either category, feel free to share it.

[gp-comments width="770" linklove="off" ]