Import TechBot

svn path=/trunk/; revision=13064
This commit is contained in:
Casper Hornstrup 2005-01-15 19:27:25 +00:00
parent 568b27baeb
commit 9dab4509fa
94 changed files with 24386 additions and 0 deletions

View file

@ -0,0 +1,32 @@
using System.Reflection;
using System.Runtime.CompilerServices;
// Information about this assembly is defined by the following
// attributes.
//
// change them to the information which is associated with the assembly
// you compile.
[assembly: AssemblyTitle("")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// The assembly version has following format :
//
// Major.Minor.Build.Revision
//
// You can specify all values by your own or you can build default build and revision
// numbers with the '*' character (the default):
[assembly: AssemblyVersion("1.0.*")]
// The following attributes specify the key for the sign of your assembly. See the
// .NET Framework documentation for more information about signing.
// This is not required, if you don't want signing let these attributes like they're.
[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile("")]

View file

@ -0,0 +1,20 @@
<?xml version="1.0"?>
<project name="TechBot.IRCLibrary" default="build">
<property name="output.dir" value="..\bin" />
<target name="build" description="Build component">
<mkdir dir="${output.dir}" />
<csc target="library"
output="${output.dir}\TechBot.IRCLibrary.dll"
optimize="true"
debug="true"
doc="${output.dir}\TechBot.IRCLibrary.xml"
warninglevel="0">
<sources>
<include name="*.cs" />
</sources>
</csc>
</target>
</project>

View file

@ -0,0 +1,26 @@
using System;
namespace TechBot.IRCLibrary
{
/// <summary>
/// IRC constants and helper methods.
/// </summary>
public class IRC
{
#region IRC commands
public const string JOIN = "JOIN";
public const string NICK = "NICK";
public const string PART = "PART";
public const string PING = "PING";
public const string PONG = "PONG";
public const string PRIVMSG = "PRIVMSG";
public const string USER = "USER";
public const string RPL_NAMREPLY = "353";
public const string RPL_ENDOFNAMES = "366";
#endregion
}
}

View file

@ -0,0 +1,134 @@
/*
Channels names are strings (beginning with a '&' or '#' character) of
length up to 200 characters. Apart from the the requirement that the
first character being either '&' or '#'; the only restriction on a
channel name is that it may not contain any spaces (' '), a control G
(^G or ASCII 7), or a comma (',' which is used as a list item
separator by the protocol).
*/
using System;
using System.Collections;
namespace TechBot.IRCLibrary
{
/// <summary>
/// IRC channel type.
/// </summary>
public enum IrcChannelType
{
Public,
Private,
Secret
}
/// <summary>
/// IRC channel.
/// </summary>
public class IrcChannel
{
#region Private fields
private IrcClient owner;
private string name;
private IrcChannelType type = IrcChannelType.Public;
private ArrayList users = new ArrayList();
#endregion
#region Public properties
/// <summary>
/// Owner of this channel.
/// </summary>
public IrcClient Owner
{
get
{
return owner;
}
}
/// <summary>
/// Name of channel (no leading #).
/// </summary>
public string Name
{
get
{
return name;
}
}
/// <summary>
/// Type of channel.
/// </summary>
public IrcChannelType Type
{
get
{
return type;
}
}
/// <summary>
/// Users in this channel.
/// </summary>
public ArrayList Users
{
get
{
return users;
}
}
#endregion
/// <summary>
/// Constructor.
/// </summary>
/// <param name="owner">Owner of this channel.</param>
/// <param name="name">Name of channel.</param>
public IrcChannel(IrcClient owner, string name)
{
if (owner == null)
{
throw new ArgumentNullException("owner", "Owner cannot be null.");
}
if (name == null)
{
throw new ArgumentNullException("name", "Name cannot be null.");
}
this.owner = owner;
this.name = name;
}
/// <summary>
/// Locate a user.
/// </summary>
/// <param name="nickname">Nickname of user (no decorations).</param>
/// <returns>User or null if not found.</returns>
public IrcUser LocateUser(string nickname)
{
foreach (IrcUser user in Users)
{
/* FIXME: There are special cases for nickname comparison */
if (nickname.ToLower().Equals(user.Nickname.ToLower()))
{
return user;
}
}
return null;
}
/// <summary>
/// Talk to the channel.
/// </summary>
/// <param name="text">Text to send to the channel.</param>
public void Talk(string text)
{
owner.SendMessage(new IrcMessage(IRC.PRIVMSG, String.Format("#{0} :{1}", name, text)));
}
}
}

View file

@ -0,0 +1,654 @@
using System;
using System.IO;
using System.Text;
using System.Collections;
using System.Net.Sockets;
namespace TechBot.IRCLibrary
{
/// <summary>
/// Delegate that delivers an IRC message.
/// </summary>
public delegate void MessageReceivedHandler(IrcMessage message);
/// <summary>
/// Delegate that notifies if the user database for a channel has changed.
/// </summary>
public delegate void ChannelUserDatabaseChangedHandler(IrcChannel channel);
/// <summary>
/// An IRC client.
/// </summary>
public class IrcClient
{
/// <summary>
/// Monitor when an IRC command is received.
/// </summary>
private class IrcCommandEventRegistration
{
/// <summary>
/// IRC command to monitor.
/// </summary>
private string command;
public string Command
{
get
{
return command;
}
}
/// <summary>
/// Handler to call when command is received.
/// </summary>
private MessageReceivedHandler handler;
public MessageReceivedHandler Handler
{
get
{
return handler;
}
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="command">IRC command to monitor.</param>
/// <param name="handler">Handler to call when command is received.</param>
public IrcCommandEventRegistration(string command,
MessageReceivedHandler handler)
{
this.command = command;
this.handler = handler;
}
}
/// <summary>
/// A buffer to store lines of text.
/// </summary>
private class LineBuffer
{
/// <summary>
/// Full lines of text in buffer.
/// </summary>
private ArrayList strings;
/// <summary>
/// Part of the last line of text in buffer.
/// </summary>
private string left = "";
/// <summary>
/// Standard constructor.
/// </summary>
public LineBuffer()
{
strings = new ArrayList();
}
/// <summary>
/// Return true if there is a complete line in the buffer or false if there is not.
/// </summary>
public bool DataAvailable
{
get
{
return (strings.Count > 0);
}
}
/// <summary>
/// Return next complete line in buffer or null if none exists.
/// </summary>
/// <returns>Next complete line in buffer or null if none exists.</returns>
public string Read()
{
if (DataAvailable)
{
string line = strings[0] as string;
strings.RemoveAt(0);
return line;
}
else
{
return null;
}
}
/// <summary>
/// Write a string to buffer splitting it into lines.
/// </summary>
/// <param name="data"></param>
public void Write(string data)
{
data = left + data;
left = "";
string[] sa = data.Split(new char[] { '\n' });
if (sa.Length <= 0)
{
left = data;
return;
}
else
{
left = "";
}
for (int i = 0; i < sa.Length; i++)
{
if (i < sa.Length - 1)
{
/* This is a complete line. Remove any \r characters at the end of the line. */
string line = sa[i].TrimEnd(new char[] { '\r', '\n'});
/* Silently ignore empty lines */
if (!line.Equals(String.Empty))
{
strings.Add(line);
}
}
else
{
/* This may be a partial line. */
left = sa[i];
}
}
}
}
/// <summary>
/// State for asynchronous reads.
/// </summary>
private class StateObject
{
/// <summary>
/// Network stream where data is read from.
/// </summary>
public NetworkStream Stream;
/// <summary>
/// Buffer where data is put.
/// </summary>
public byte[] Buffer;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="stream">Network stream where data is read from.</param>
/// <param name="buffer">Buffer where data is put.</param>
public StateObject(NetworkStream stream, byte[] buffer)
{
this.Stream = stream;
this.Buffer = buffer;
}
}
#region Private fields
private bool firstPingReceived = false;
private System.Text.Encoding encoding = System.Text.Encoding.UTF8;
private TcpClient tcpClient;
private NetworkStream networkStream;
private bool connected = false;
private LineBuffer messageStream;
private ArrayList ircCommandEventRegistrations = new ArrayList();
private ArrayList channels = new ArrayList();
#endregion
#region Public events
public event MessageReceivedHandler MessageReceived;
public event ChannelUserDatabaseChangedHandler ChannelUserDatabaseChanged;
#endregion
#region Public properties
/// <summary>
/// Encoding used.
/// </summary>
public System.Text.Encoding Encoding
{
get
{
return encoding;
}
set
{
encoding = value;
}
}
/// <summary>
/// List of joined channels.
/// </summary>
public ArrayList Channels
{
get
{
return channels;
}
}
#endregion
#region Private methods
/// <summary>
/// Signal MessageReceived event.
/// </summary>
/// <param name="message">Message that was received.</param>
private void OnMessageReceived(IrcMessage message)
{
foreach (IrcCommandEventRegistration icre in ircCommandEventRegistrations)
{
if (message.Command.ToLower().Equals(icre.Command.ToLower()))
{
icre.Handler(message);
}
}
if (MessageReceived != null)
{
MessageReceived(message);
}
}
/// <summary>
/// Signal ChannelUserDatabaseChanged event.
/// </summary>
/// <param name="channel">Message that was received.</param>
private void OnChannelUserDatabaseChanged(IrcChannel channel)
{
if (ChannelUserDatabaseChanged != null)
{
ChannelUserDatabaseChanged(channel);
}
}
/// <summary>
/// Start an asynchronous read.
/// </summary>
private void Receive()
{
if ((networkStream != null) && (networkStream.CanRead))
{
byte[] buffer = new byte[1024];
networkStream.BeginRead(buffer, 0, buffer.Length,
new AsyncCallback(ReadComplete),
new StateObject(networkStream, buffer));
}
else
{
throw new Exception("Socket is closed.");
}
}
/// <summary>
/// Asynchronous read has completed.
/// </summary>
/// <param name="ar">IAsyncResult object.</param>
private void ReadComplete(IAsyncResult ar)
{
StateObject stateObject = (StateObject) ar.AsyncState;
if (stateObject.Stream.CanRead)
{
int bytesReceived = stateObject.Stream.EndRead(ar);
if (bytesReceived > 0)
{
messageStream.Write(Encoding.GetString(stateObject.Buffer, 0, bytesReceived));
while (messageStream.DataAvailable)
{
OnMessageReceived(new IrcMessage(messageStream.Read()));
}
}
}
Receive();
}
/// <summary>
/// Locate channel.
/// </summary>
/// <param name="name">Channel name.</param>
/// <returns>Channel or null if none was found.</returns>
private IrcChannel LocateChannel(string name)
{
foreach (IrcChannel channel in Channels)
{
if (name.ToLower().Equals(channel.Name.ToLower()))
{
return channel;
}
}
return null;
}
/// <summary>
/// Send a PONG message when a PING message is received.
/// </summary>
/// <param name="message">Received IRC message.</param>
private void PingMessageReceived(IrcMessage message)
{
SendMessage(new IrcMessage(IRC.PONG, message.Parameters));
firstPingReceived = true;
}
/// <summary>
/// Process RPL_NAMREPLY message.
/// </summary>
/// <param name="message">Received IRC message.</param>
private void RPL_NAMREPLYMessageReceived(IrcMessage message)
{
try
{
// :Oslo2.NO.EU.undernet.org 353 E101 = #E101 :E101 KongFu_uK @Exception
/* "( "=" / "*" / "@" ) <channel>
:[ "@" / "+" ] <nick> *( " " [ "@" / "+" ] <nick> )
- "@" is used for secret channels, "*" for private
channels, and "=" for others (public channels). */
if (message.Parameters == null)
{
System.Diagnostics.Debug.WriteLine(String.Format("Message has no parameters."));
return;
}
string[] parameters = message.Parameters.Split(new char[] { ' '});
if (parameters.Length < 5)
{
System.Diagnostics.Debug.WriteLine(String.Format("{0} is two few parameters.", parameters.Length));
return;
}
IrcChannelType type;
switch (parameters[1])
{
case "=":
type = IrcChannelType.Public;
break;
case "*":
type = IrcChannelType.Private;
break;
case "@":
type = IrcChannelType.Secret;
break;
default:
type = IrcChannelType.Public;
break;
}
IrcChannel channel = LocateChannel(parameters[2].Substring(1));
if (channel == null)
{
System.Diagnostics.Debug.WriteLine(String.Format("Channel not found '{0}'.",
parameters[2].Substring(1)));
return;
}
string nickname = parameters[3];
if (nickname[0] != ':')
{
System.Diagnostics.Debug.WriteLine(String.Format("String should start with : and not {0}.", nickname[0]));
return;
}
/* Skip : */
IrcUser user = channel.LocateUser(nickname.Substring(1));
if (user == null)
{
user = new IrcUser(nickname.Substring(1));
channel.Users.Add(user);
}
for (int i = 4; i < parameters.Length; i++)
{
nickname = parameters[i];
user = channel.LocateUser(nickname);
if (user == null)
{
user = new IrcUser(nickname);
channel.Users.Add(user);
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(String.Format("Ex. {0}", ex));
}
}
/// <summary>
/// Process RPL_ENDOFNAMES message.
/// </summary>
/// <param name="message">Received IRC message.</param>
private void RPL_ENDOFNAMESMessageReceived(IrcMessage message)
{
try
{
/* <channel> :End of NAMES list */
if (message.Parameters == null)
{
System.Diagnostics.Debug.WriteLine(String.Format("Message has no parameters."));
return;
}
string[] parameters = message.Parameters.Split(new char[] { ' ' });
IrcChannel channel = LocateChannel(parameters[1].Substring(1));
if (channel == null)
{
System.Diagnostics.Debug.WriteLine(String.Format("Channel not found '{0}'.",
parameters[0].Substring(1)));
return;
}
OnChannelUserDatabaseChanged(channel);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(String.Format("Ex. {0}", ex));
}
}
#endregion
/// <summary>
/// Connect to the specified IRC server on the specified port.
/// </summary>
/// <param name="server">Address of IRC server.</param>
/// <param name="port">Port of IRC server.</param>
public void Connect(string server, int port)
{
if (connected)
{
throw new AlreadyConnectedException();
}
else
{
messageStream = new LineBuffer();
tcpClient = new TcpClient();
tcpClient.Connect(server, port);
tcpClient.NoDelay = true;
tcpClient.LingerState = new LingerOption(false, 0);
networkStream = tcpClient.GetStream();
connected = networkStream.CanRead && networkStream.CanWrite;
if (!connected)
{
throw new Exception("Cannot read and write from socket.");
}
/* Install PING message handler */
MonitorCommand(IRC.PING, new MessageReceivedHandler(PingMessageReceived));
/* Install RPL_NAMREPLY message handler */
MonitorCommand(IRC.RPL_NAMREPLY, new MessageReceivedHandler(RPL_NAMREPLYMessageReceived));
/* Install RPL_ENDOFNAMES message handler */
MonitorCommand(IRC.RPL_ENDOFNAMES, new MessageReceivedHandler(RPL_ENDOFNAMESMessageReceived));
/* Start receiving data */
Receive();
}
}
/// <summary>
/// Disconnect from IRC server.
/// </summary>
public void Diconnect()
{
if (!connected)
{
throw new NotConnectedException();
}
else
{
connected = false;
tcpClient.Close();
tcpClient = null;
}
}
/// <summary>
/// Send an IRC message.
/// </summary>
/// <param name="message">The message to be sent.</param>
public void SendMessage(IrcMessage message)
{
if (!connected)
{
throw new NotConnectedException();
}
/* Serialize sending messages */
lock (typeof(IrcClient))
{
NetworkStream networkStream = tcpClient.GetStream();
byte[] bytes = Encoding.GetBytes(message.Line);
networkStream.Write(bytes, 0, bytes.Length);
networkStream.Flush();
}
}
/// <summary>
/// Monitor when a message with an IRC command is received.
/// </summary>
/// <param name="command">IRC command to monitor.</param>
/// <param name="handler">Handler to call when command is received.</param>
public void MonitorCommand(string command, MessageReceivedHandler handler)
{
if (command == null)
{
throw new ArgumentNullException("command", "Command cannot be null.");
}
if (handler == null)
{
throw new ArgumentNullException("handler", "Handler cannot be null.");
}
ircCommandEventRegistrations.Add(new IrcCommandEventRegistration(command, handler));
}
/// <summary>
/// Talk to the channel.
/// </summary>
/// <param name="nickname">Nickname of user to talk to.</param>
/// <param name="text">Text to send to the channel.</param>
public void TalkTo(string nickname, string text)
{
if (nickname == null)
{
throw new ArgumentNullException("nickname", "Nickname cannot be null.");
}
if (text == null)
{
throw new ArgumentNullException("text", "Text cannot be null.");
}
SendMessage(new IrcMessage(IRC.PRIVMSG, String.Format("{0} :{1}", nickname, text)));
}
/// <summary>
/// Change nickname.
/// </summary>
/// <param name="nickname">New nickname.</param>
public void ChangeNick(string nickname)
{
if (nickname == null)
{
throw new ArgumentNullException("nickname", "Nickname cannot be null.");
}
/* NICK <nickname> [ <hopcount> ] */
SendMessage(new IrcMessage(IRC.NICK, nickname));
}
/// <summary>
/// Register.
/// </summary>
/// <param name="nickname">New nickname.</param>
/// <param name="realname">Real name. Can be null.</param>
public void Register(string nickname, string realname)
{
if (nickname == null)
{
throw new ArgumentNullException("nickname", "Nickname cannot be null.");
}
firstPingReceived = false;
ChangeNick(nickname);
/* OLD: USER <username> <hostname> <servername> <realname> */
/* NEW: USER <user> <mode> <unused> <realname> */
SendMessage(new IrcMessage(IRC.USER, String.Format("{0} 0 * :{1}",
nickname, realname != null ? realname : "Anonymous")));
/* Wait for PING for up til 10 seconds */
int timer = 0;
while (!firstPingReceived && timer < 200)
{
System.Threading.Thread.Sleep(50);
timer++;
}
}
/// <summary>
/// Join an IRC channel.
/// </summary>
/// <param name="name">Name of channel (without leading #).</param>
/// <returns>New channel.</returns>
public IrcChannel JoinChannel(string name)
{
IrcChannel channel = new IrcChannel(this, name);
channels.Add(channel);
/* JOIN ( <channel> *( "," <channel> ) [ <key> *( "," <key> ) ] ) / "0" */
SendMessage(new IrcMessage(IRC.JOIN, String.Format("#{0}", name)));
return channel;
}
/// <summary>
/// Part an IRC channel.
/// </summary>
/// <param name="channel">IRC channel. If null, the user parts from all channels.</param>
/// <param name="message">Part message. Can be null.</param>
public void PartChannel(IrcChannel channel, string message)
{
/* PART <channel> *( "," <channel> ) [ <Part Message> ] */
if (channel != null)
{
SendMessage(new IrcMessage(IRC.PART, String.Format("#{0}{1}",
channel.Name, message != null ? String.Format(" :{0}", message) : "")));
channels.Remove(channel);
}
else
{
string channelList = null;
foreach (IrcChannel myChannel in Channels)
{
if (channelList == null)
{
channelList = "";
}
else
{
channelList += ",";
}
channelList += myChannel.Name;
}
if (channelList != null)
{
SendMessage(new IrcMessage(IRC.PART, String.Format("#{0}{1}",
channelList, message != null ? String.Format(" :{0}", message) : "")));
Channels.Clear();
}
}
}
}
}

View file

@ -0,0 +1,50 @@
using System;
namespace TechBot.IRCLibrary
{
/// <summary>
/// Base class for all IRC exceptions.
/// </summary>
public class IrcException : Exception
{
public IrcException() : base()
{
}
public IrcException(string message) : base(message)
{
}
public IrcException(string message, Exception innerException) : base(message, innerException)
{
}
}
/// <summary>
/// Thrown when there is no connection to an IRC server.
/// </summary>
public class NotConnectedException : IrcException
{
}
/// <summary>
/// Thrown when there is an attempt to connect to an IRC server and there is already a connection.
/// </summary>
public class AlreadyConnectedException : IrcException
{
}
/// <summary>
/// Thrown when there is attempted to parse a malformed or invalid IRC message.
/// </summary>
public class MalformedMessageException : IrcException
{
public MalformedMessageException(string message) : base(message)
{
}
public MalformedMessageException(string message, Exception innerException) : base(message, innerException)
{
}
}
}

View file

@ -0,0 +1,503 @@
using System;
namespace TechBot.IRCLibrary
{
/*
<message> ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>
<prefix> ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
<command> ::= <letter> { <letter> } | <number> <number> <number>
<SPACE> ::= ' ' { ' ' }
<params> ::= <SPACE> [ ':' <trailing> | <middle> <params> ]
<middle> ::= <Any *non-empty* sequence of octets not including SPACE
or NUL or CR or LF, the first of which may not be ':'>
<trailing> ::= <Any, possibly *empty*, sequence of octets not including
NUL or CR or LF>
<crlf> ::= CR LF
NOTES:
1) <SPACE> is consists only of SPACE character(s) (0x20).
Specially notice that TABULATION, and all other control
characters are considered NON-WHITE-SPACE.
2) After extracting the parameter list, all parameters are equal,
whether matched by <middle> or <trailing>. <Trailing> is just
a syntactic trick to allow SPACE within parameter.
3) The fact that CR and LF cannot appear in parameter strings is
just artifact of the message framing. This might change later.
4) The NUL character is not special in message framing, and
basically could end up inside a parameter, but as it would
cause extra complexities in normal C string handling. Therefore
NUL is not allowed within messages.
5) The last parameter may be an empty string.
6) Use of the extended prefix (['!' <user> ] ['@' <host> ]) must
not be used in server to server communications and is only
intended for server to client messages in order to provide
clients with more useful information about who a message is
from without the need for additional queries.
*/
/*
NOTICE AUTH :*** Looking up your hostname
NOTICE AUTH :*** Checking Ident
NOTICE AUTH :*** Found your hostname
NOTICE AUTH :*** No ident response
*/
/// <summary>
/// IRC message.
/// </summary>
public class IrcMessage
{
#region Private fields
private string line;
private string prefix;
private string prefixServername;
private string prefixNickname;
private string prefixUser;
private string prefixHost;
private string command;
private string parameters;
#endregion
/// <summary>
/// Line of text that is to be parsed as an IRC message.
/// </summary>
public string Line
{
get
{
return line;
}
}
/// <summary>
/// Does the message have a prefix?
/// </summary>
public bool HasPrefix
{
get
{
return prefix != null;
}
}
/// <summary>
/// Prefix or null if none exists.
/// </summary>
public string Prefix
{
get
{
return prefix;
}
}
/// <summary>
/// Servername part of prefix or null if no prefix or servername exists.
/// </summary>
public string PrefixServername
{
get
{
return prefixServername;
}
}
/// <summary>
/// Nickname part of prefix or null if no prefix or nick exists.
/// </summary>
public string PrefixNickname
{
get
{
return prefixNickname;
}
}
/// <summary>
/// User part of (extended) prefix or null if no (extended) prefix exists.
/// </summary>
public string PrefixUser
{
get
{
return prefixUser;
}
}
/// <summary>
/// Host part of (extended) prefix or null if no (extended) prefix exists.
/// </summary>
public string PrefixHost
{
get
{
return prefixHost;
}
}
/// <summary>
/// Command part of message.
/// </summary>
public string Command
{
get
{
return command;
}
}
/// <summary>
/// Is command numeric?
/// </summary>
public bool IsCommandNumeric
{
get
{
if (command == null || command.Length != 3)
{
return false;
}
try
{
Int32.Parse(command);
return true;
}
catch (Exception)
{
return false;
}
}
}
/// <summary>
/// Command part of message as text.
/// </summary>
public string CommandText
{
get
{
return command;
}
}
/// <summary>
/// Command part of message as a number.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if IsCommandNumeric returns false.</exception>
public int CommandNumber
{
get
{
if (IsCommandNumeric)
{
return Int32.Parse(command);
}
else
{
throw new InvalidOperationException();
}
}
}
/// <summary>
/// Parameters part of message.
/// </summary>
public string Parameters
{
get
{
return parameters;
}
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="line">Line of text that is to be parsed as an IRC message.</param>
public IrcMessage(string line)
{
/*
* <message> ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>
* <prefix> ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
* :Oslo1.NO.EU.undernet.org 461 MYNICK USER :Not enough parameters
*/
try
{
this.line = line;
int i = 0;
#region Prefix
if (line[i].Equals(':'))
{
i++;
prefix = "";
/* This message has a prefix */
string s = "";
while (i < line.Length && line[i] != ' ' && line[i] != '!' && line[i] != '@')
{
s += line[i++];
}
if (IsValidIrcNickname(s))
{
prefixNickname = s;
prefix += prefixNickname;
if (line[i] == '!')
{
/* This message has an extended prefix */
i++;
s = "";
while (i < line.Length && line[i] != ' ' && line[i] != '@')
{
s += line[i];
i++;
}
prefixUser = s;
prefix += "!" + prefixUser;
}
if (line[i] == '@')
{
/* This message has a host prefix */
s = "";
do
{
s += line[++i];
}
while (i < line.Length && line[i] != ' ');
prefixHost = s;
prefix += "@" + prefixHost;
}
}
else /* Assume it is a servername */
{
prefixServername = s;
prefix += prefixServername;
}
/* Skip spaces */
while (i < line.Length && line[i] == ' ')
{
i++;
}
}
else
{
prefix = null;
}
#endregion
#region Command
if (Char.IsDigit(line[i]))
{
if (!Char.IsDigit(line, i + 1) || !Char.IsDigit(line, i + 2))
{
throw new Exception();
}
command = String.Format("{0}{1}{2}", line[i++], line[i++], line[i++]);
}
else
{
command = "";
while (i < line.Length && Char.IsLetter(line[i]))
{
command += line[i];
i++;
}
}
#endregion
#region Parameters
while (true)
{
/* Skip spaces */
while (i < line.Length && line[i] == ' ')
{
i++;
}
if (i < line.Length && line[i].Equals(':'))
{
i++;
/* Trailing */
while (i < line.Length && line[i] != ' ' && line[i] != '\r' && line[i] != '\n' && line[i] != 0)
{
if (parameters == null)
{
parameters = "";
}
parameters += line[i];
i++;
}
}
else
{
/* Middle */
while (i < line.Length && line[i] != '\r' && line[i] != '\n' && line[i] != 0)
{
if (parameters == null)
{
parameters = "";
}
parameters += line[i];
i++;
}
}
if (i >= line.Length)
{
break;
}
}
#endregion
}
catch (Exception ex)
{
throw new MalformedMessageException("The message is malformed.", ex);
}
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="prefixServername"></param>
/// <param name="prefixNickname"></param>
/// <param name="prefixUser"></param>
/// <param name="prefixHost"></param>
/// <param name="command"></param>
/// <param name="parameters"></param>
public IrcMessage(string prefixServername,
string prefixNickname,
string prefixUser,
string prefixHost,
string command,
string parameters)
{
throw new NotImplementedException();
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="command">IRC command.</param>
/// <param name="parameters">IRC command parameters. May be null if there are no parameters.</param>
public IrcMessage(string command,
string parameters)
{
if (command == null || !IsValidIrcCommand(command))
{
throw new ArgumentException("Command is not a valid IRC command.", "command");
}
/* An IRC message must not be longer than 512 characters (including terminating CRLF) */
int parametersLength = (parameters != null) ? 1 + parameters.Length : 0;
if (command.Length + parametersLength > 510)
{
throw new MalformedMessageException("IRC message cannot be longer than 512 characters.");
}
this.command = command;
this.parameters = parameters;
if (parameters != null)
{
this.line = String.Format("{0} {1}\r\n", command, parameters);
}
else
{
this.line = String.Format("{0}\r\n", command);
}
}
/// <summary>
/// Returns wether a string of text is a valid IRC command.
/// </summary>
/// <param name="command">The IRC command.</param>
/// <returns>True, if <c ref="command">command</c> is a valid IRC command, false if not.</returns>
private static bool IsValidIrcCommand(string command)
{
foreach (char c in command)
{
if (!Char.IsLetter(c))
{
return false;
}
}
return true;
}
private const string IrcSpecial = @"-[]\`^{}";
private const string IrcSpecialNonSpecs = @"_";
/// <summary>
/// Returns wether a character is an IRC special character.
/// </summary>
/// <param name="c">Character to test.</param>
/// <returns>True if the character is an IRC special character, false if not.</returns>
private static bool IsSpecial(char c)
{
foreach (char validCharacter in IrcSpecial)
{
if (c.Equals(validCharacter))
{
return true;
}
}
foreach (char validCharacter in IrcSpecialNonSpecs)
{
if (c.Equals(validCharacter))
{
return true;
}
}
return false;
}
/// <summary>
/// Returns wether a string of text is a valid IRC nickname.
/// </summary>
/// <param name="nickname">The IRC nickname.</param>
/// <returns>True, if <c ref="nickname">nickname</c> is a valid IRC nickname, false if not.</returns>
private static bool IsValidIrcNickname(string nickname)
{
/*
* <nick> ::= <letter> { <letter> | <number> | <special> }
* <letter> ::= 'a' ... 'z' | 'A' ... 'Z'
* <number> ::= '0' ... '9'
* <special> ::= '-' | '[' | ']' | '\' | '`' | '^' | '{' | '}'
*/
/* An IRC nicknmame must be 1 - 9 characters in length. We don't care so much if it is larger */
if ((nickname.Length < 1) || (nickname.Length > 30))
{
return false;
}
/* First character must be a letter. */
if (!Char.IsLetter(nickname[0]))
{
return false;
}
/* Check the other valid characters for validity. */
foreach (char c in nickname)
{
if (!Char.IsLetter(c) && !Char.IsDigit(c) && !IsSpecial(c))
{
return false;
}
}
return true;
}
/// <summary>
/// Write contents to a string.
/// </summary>
/// <returns>Contents as a string.</returns>
public override string ToString()
{
return String.Format("Line({0})Prefix({1})Command({2})Parameters({3})",
line, prefix != null ? prefix : "(null)",
command != null ? command : "(null)",
parameters != null ? parameters : "(null)");
}
}
}

View file

@ -0,0 +1,96 @@
using System;
namespace TechBot.IRCLibrary
{
/// <summary>
/// IRC user.
/// </summary>
public class IrcUser
{
#region Private fields
private string nickname;
private string decoratedNickname;
#endregion
#region Public properties
/// <summary>
/// Nickname of user.
/// </summary>
public string Nickname
{
get
{
return nickname;
}
}
/// <summary>
/// Decorated nickname of user.
/// </summary>
public string DecoratedNickname
{
get
{
return decoratedNickname;
}
}
/// <summary>
/// Wether user is channel operator.
/// </summary>
public bool Operator
{
get
{
return decoratedNickname.StartsWith("@");
}
}
/// <summary>
/// Wether user has voice.
/// </summary>
public bool Voice
{
get
{
return decoratedNickname.StartsWith("+");
}
}
#endregion
/// <summary>
/// Constructor.
/// </summary>
/// <param name="nickname">Nickname (possibly decorated) of user.</param>
public IrcUser(string nickname)
{
this.decoratedNickname = nickname.Trim();
this.nickname = StripDecoration(decoratedNickname);
}
/// <summary>
/// Strip docoration of nickname.
/// </summary>
/// <param name="nickname">Possible decorated nickname.</param>
/// <returns>Undecorated nickname.</returns>
public static string StripDecoration(string decoratedNickname)
{
if (decoratedNickname.StartsWith("@"))
{
return decoratedNickname.Substring(1);
}
else if (decoratedNickname.StartsWith("+"))
{
return decoratedNickname.Substring(1);
}
else
{
return decoratedNickname;
}
}
}
}

View file

@ -0,0 +1,16 @@
<Combine fileversion="1.0" name="TechBot.IRCLibrary" description="">
<StartMode startupentry="TechBot.IRCLibrary" single="True">
<Execute entry="TechBot.IRCLibrary" type="None" />
</StartMode>
<Entries>
<Entry filename=".\.\TechBot.IRCLibrary.prjx" />
</Entries>
<Configurations active="Debug">
<Configuration name="Release">
<Entry name="TechBot.IRCLibrary" configurationname="Debug" build="False" />
</Configuration>
<Configuration name="Debug">
<Entry name="TechBot.IRCLibrary" configurationname="Debug" build="False" />
</Configuration>
</Configurations>
</Combine>

View file

@ -0,0 +1,31 @@
<Project name="TechBot.IRCLibrary" standardNamespace="TechBot.IRCLibrary" description="" newfilesearch="None" enableviewstate="True" version="1.1" projecttype="C#">
<Contents>
<File name=".\AssemblyInfo.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
<File name=".\IrcException.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
<File name=".\IRC.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
<File name=".\IrcChannel.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
<File name=".\IrcMessage.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
<File name=".\IrcUser.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
<File name=".\IrcClient.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
<File name=".\Default.build" subtype="Code" buildaction="Nothing" dependson="" data="" />
</Contents>
<References />
<DeploymentInformation target="" script="" strategy="File" />
<Configuration runwithwarnings="True" name="Debug">
<CodeGeneration runtime="MsNet" compiler="Csc" compilerversion="" warninglevel="4" nowarn="" includedebuginformation="True" optimize="False" unsafecodeallowed="False" generateoverflowchecks="True" mainclass="" target="Library" definesymbols="" generatexmldocumentation="False" win32Icon="" noconfig="False" nostdlib="False" />
<Execution commandlineparameters="" consolepause="False" />
<Output directory="..\bin\Debug" assembly="TechBot.IRCLibrary" executeScript="" executeBeforeBuild="" executeAfterBuild="" executeBeforeBuildArguments="" executeAfterBuildArguments="" />
</Configuration>
<Configurations active="Debug">
<Configuration runwithwarnings="True" name="Debug">
<CodeGeneration runtime="MsNet" compiler="Csc" compilerversion="" warninglevel="4" nowarn="" includedebuginformation="True" optimize="False" unsafecodeallowed="False" generateoverflowchecks="True" mainclass="" target="Library" definesymbols="" generatexmldocumentation="False" win32Icon="" noconfig="False" nostdlib="False" />
<Execution commandlineparameters="" consolepause="False" />
<Output directory="..\bin\Debug" assembly="TechBot.IRCLibrary" executeScript="" executeBeforeBuild="" executeAfterBuild="" executeBeforeBuildArguments="" executeAfterBuildArguments="" />
</Configuration>
<Configuration runwithwarnings="True" name="Release">
<CodeGeneration runtime="MsNet" compiler="Csc" compilerversion="" warninglevel="4" nowarn="" includedebuginformation="False" optimize="True" unsafecodeallowed="False" generateoverflowchecks="False" mainclass="" target="Library" definesymbols="" generatexmldocumentation="False" win32Icon="" noconfig="False" nostdlib="False" />
<Execution commandlineparameters="" consolepause="False" />
<Output directory="..\bin\Release" assembly="TechBot.IRCLibrary" executeScript="" executeBeforeBuild="" executeAfterBuild="" executeBeforeBuildArguments="" executeAfterBuildArguments="" />
</Configuration>
</Configurations>
</Project>