Skip to content

Instantly share code, notes, and snippets.

@iseebi
Created June 27, 2011 15:01
Show Gist options
  • Save iseebi/1049032 to your computer and use it in GitHub Desktop.
Save iseebi/1049032 to your computer and use it in GitHub Desktop.
EbiSoft.EbIRC.IRC.IRCClient in Siverlight for Windows Phone
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Text;
namespace EbiSoft.EbIRC.IRC
{
public class ConnectionInformation
{
/// <summary>
/// 接続先ホスト名 (サーバー系設定)
/// </summary>
public string Hostname { get; set; }
/// <summary>
/// 接続先ポート (サーバー系設定)
/// </summary>
public int Port { get; set; }
/// <summary>
/// SSLを使用する (サーバー系設定)
/// </summary>
public bool UseSsl { get; set; }
/// <summary>
/// SSL使用時署名検証しない (サーバー系設定)
/// </summary>
public bool NoValidationSSL { get; set; }
/// <summary>
/// サーバーパスワード (サーバー系設定)
/// </summary>
public string ServerPassword { get; set; }
/// <summary>
/// エンコーディング (サーバー系設定)
/// </summary>
public string EncodingName { get; set; }
/// <summary>
/// ニックネーム (ユーザー系設定)
/// </summary>
public string NickName { get; set; }
/// <summary>
/// リアルネーム (ユーザー系設定)
/// </summary>
public string RealName { get; set; }
/// <summary>
/// ログインネーム (ユーザー系設定)
/// </summary>
public string LoginName { get; set; }
/// <summary>
/// Nickservパスワード (ユーザー系設定)
/// </summary>
public string NickservPassword { get; set; }
/// <summary>
/// エンドポイントを取得する
/// </summary>
/// <returns></returns>
public EndPoint GetEndPoint()
{
return new DnsEndPoint(Hostname, Port, System.Net.Sockets.AddressFamily.InterNetwork);
}
/// <summary>
/// エンコーディングを取得する
/// </summary>
/// <returns></returns>
public Encoding GetEncoding()
{
return EncodingFactory.GetEncoding(EncodingName);
}
}
}
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Text;
using Japanese.Text.Encoding;
namespace EbiSoft.EbIRC.IRC
{
public class EncodingFactory
{
public static Encoding GetEncoding(string encoding)
{
switch (encoding.ToLower().Replace("_","").Replace("-",""))
{
case "sjis":
case "shiftjis":
return new SjisEncoding();
case "euc":
case "eucjp":
case "euc-jp":
return new EucjpEncoding();
case "jis":
case "iso2022jp":
return new JisEncoding();
case "utf8":
return new UTF8Encoding(false);
case "unicode":
return new UnicodeEncoding(false, false);
default:
throw new ArgumentException("not found encoding", "encoding");
}
}
}
}
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace EbiSoft.EbIRC.IRC
{
public static class Extensions
{
public static int IndexOfArray(this byte[] search, int offset, int length, byte[] needle)
{
for (int i = offset; i < length; i++)
{
if ((search[i] == needle[0]) && ((i + needle.Length - 1) < length))
{
bool failed = false;
for (int j = 1; j < needle.Length; j++)
{
if (search[i + j] != needle[j])
{
failed = true;
break;
}
}
if (!failed) return i;
}
}
return -1;
}
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
namespace EbiSoft.EbIRC.IRC
{
public class IRCClient
{
Socket m_socket; // ソケット
private Queue<string> m_sendQueue; // 送信キュー
Dictionary<string, string[]> m_namelist; // 名前リスト一時保存用
private bool m_online; // 接続完了フラグ
private bool m_sendLoopStop = false;
private byte[] m_receiveBuffer;
private byte[] m_fragmentBuffer;
private byte[] m_processBuffer;
private int m_fragmentBufferLength;
private byte[] m_messageDelimiterBytes;
#region 定数
/// <summary>
/// ニックネームの最大長
/// </summary>
public const int MaxNickLength = 9;
/// <summary>
/// 行端コードを除いた1メッセージの最大長
/// </summary>
public const int MaxMessageLength = 510;
/// <summary>
/// メッセージデリミタ
/// </summary>
public const string MessageDelimiter = "\r\n";
/// <summary>
/// 受信バッファサイズ
/// </summary>
private const int ReceiveBufferLength = 2048;
#endregion
#region 変数型プロパティ
/// <summary>
/// サーバー情報を取得します
/// </summary>
public ConnectionInformation ConnectionInformation { get; private set; }
/// <summary>
/// ユーザーフルネームを取得します
/// </summary>
public string UserString { get; private set; }
/// <summary>
/// エンコーディングを取得または設定します
/// </summary>
public Encoding Encoding { get; private set; }
/// <summary>
/// 自動メッセージ送信
/// </summary>
public bool AutoFlush { get; set; }
/// <summary>
/// 現在のニックネーム
/// </summary>
public string CurrentNickName { get; private set; }
#endregion
#region イベント定義
/// <summary>
/// イベントの実行処理を行います
/// </summary>
/// <param name="handler">実行するイベントハンドラ</param>
/// <param name="e">イベントパラメータ</param>
private void InvokeEvent(Delegate handler, EventArgs e)
{
if (handler != null)
handler.DynamicInvoke(this, e);
}
#region Connected
private static readonly object eventKeyOfConnected = new object();
/// <summary>
/// サーバーに接続したときに発生します。
/// </summary>
public event EventHandler Connected;
/// <summary>
/// Connected イベントを発生させます。
/// </summary>
protected void OnConnected()
{
InvokeEvent(Connected, EventArgs.Empty);
}
#endregion
#region ConnectionFailed
private static readonly object eventKeyOfConnectionFailed = new object();
/// <summary>
/// サーバーへの接続が失敗したときに発生します。
/// </summary>
public event EventHandler ConnectionFailed;
/// <summary>
/// ConnectionFailed イベントを発生させます。
/// </summary>
protected void OnConnectionFailed()
{
InvokeEvent(ConnectionFailed, EventArgs.Empty);
}
#endregion
#region Disconnected
private static readonly object eventKeyOfDisconnected = new object();
/// <summary>
/// 接続が切断した時に発生します。
/// </summary>
public event EventHandler Disconnected;
/// <summary>
/// Disconnected イベントを発生させます。
/// </summary>
protected void OnDisconnected()
{
InvokeEvent(ConnectionFailed, EventArgs.Empty);
}
#endregion
#region StartMessageEvents
private static object eventKeyOfStartMessageEvents = new object();
/// <summary>
/// メッセージイベントの処理が始まる前に発生します。
/// </summary>
public event EventHandler StartMessageEvents;
/// <summary>
/// StartMessageEvents イベントを発生させます。
/// </summary>
protected void OnStartMessageEvents()
{
InvokeEvent(StartMessageEvents, EventArgs.Empty);
}
#endregion
#region FinishMessageEvents
/// <summary>
/// メッセージイベントの処理が完了した後にに発生します。
/// </summary>
public event EventHandler FinishMessageEvents;
/// <summary>
/// FinishMessageEvents イベントを発生させます。
/// </summary>
protected void OnFinishMessageEvents()
{
InvokeEvent(FinishMessageEvents, EventArgs.Empty);
}
#endregion
#region ProcessedConnection
private static readonly object eventKeyOfProcessedConnection = new object();
/// <summary>
/// 接続が完了したときにに発生します。
/// </summary>
public event EventHandler ProcessedConnection;
/// <summary>
/// ProcessedConnection イベントを発生させます。
/// </summary>
protected void OnProcessedConnection()
{
InvokeEvent(ProcessedConnection, EventArgs.Empty);
}
#endregion
#region ChangedMyNickname
private static readonly object eventKeyOfChangedMyNickname = new object();
/// <summary>
/// ユーザーのニックネームが変更されたときに発生します。
/// </summary>
public event NickNameChangeEventHandler ChangedMyNickname;
/// <summary>
/// ChangedMyNickname イベントを発生させます。
/// </summary>
protected void OnChangedMyNickname(NickNameChangeEventArgs e)
{
InvokeEvent(ChangedMyNickname, e);
}
#endregion
#region ChangedNickname
private static readonly object eventKeyOfChangedNickname = new object();
/// <summary>
/// ユーザーのニックネームが変更されたときに発生します。
/// </summary>
public event NickNameChangeEventHandler ChangedNickname;
/// <summary>
/// ChangedMyNickname イベントを発生させます。
/// </summary>
protected void OnChangedNickname(NickNameChangeEventArgs e)
{
InvokeEvent(ChangedNickname, e);
}
#endregion
#region ReceiveServerReply
private static readonly object eventKeyOfReceiveServerReply = new object();
/// <summary>
/// サーバーメッセージを受信したときに発生します。
/// </summary>
public event ReceiveServerReplyEventHandler ReceiveServerReply;
/// <summary>
/// ReceiveServerReply イベントを発生させます。
/// </summary>
protected void OnReceiveServerReply(ReceiveServerReplyEventArgs e)
{
InvokeEvent(ReceiveServerReply, e);
}
#endregion
#region ReceiveMotdMesage
private static readonly object eventKeyOfReceiveMotdMesage = new object();
/// <summary>
/// MOTDを受信したときに発生します。
/// </summary>
public event ReceiveMessageEventHandler ReceiveMotdMesage;
/// <summary>
/// ReceiveMotdMesage イベントを発生させます。
/// </summary>
protected void OnReceiveMotdMesage(ReceiveMessageEventArgs e)
{
InvokeEvent(ReceiveMotdMesage, e);
}
#endregion
#region ReceiveMessage
private static readonly object eventKeyOfReceiveMessage = new object();
/// <summary>
/// PRIVMSGメッセージを受信したときに発生します。
/// </summary>
public event ReceiveMessageEventHandler ReceiveMessage;
/// <summary>
/// ReceiveMessage イベントを発生させます。
/// </summary>
protected void OnReceiveMessage(ReceiveMessageEventArgs e)
{
InvokeEvent(ReceiveMessage, e);
}
#endregion
#region ReceiveNotice
private static readonly object eventKeyOfReceiveNotice = new object();
/// <summary>
/// NOTICEメッセージを受信したときにに発生します。
/// </summary>
public event ReceiveMessageEventHandler ReceiveNotice;
/// <summary>
/// ReceiveNotice イベントを発生させます。
/// </summary>
protected void OnReceiveNotice(ReceiveMessageEventArgs e)
{
InvokeEvent(ReceiveNotice, e);
}
#endregion
#region ReceiveNames
private static readonly object eventKeyOfReceiveNames = new object();
/// <summary>
/// 参加者リストを受信したときに発生します。
/// </summary>
public event ReceiveNamesEventHandler ReceiveNames;
/// <summary>
/// ReceiveNames イベントを発生させます。
/// </summary>
protected void OnReceiveNames(ReceiveNamesEventArgs e)
{
InvokeEvent(ReceiveNames, e);
}
#endregion
#region ReceiveCtcpQuery
private static readonly object eventKeyOfReceiveCtcpQuery = new object();
/// <summary>
/// CTCPクエリを受信したときに発生します。
/// </summary>
public event CtcpEventHandler ReceiveCtcpQuery;
/// <summary>
/// ReceiveCtcpQuery イベントを発生させます。
/// </summary>
protected void OnReceiveCtcpQuery(CtcpEventArgs e)
{
InvokeEvent(ReceiveCtcpQuery, e);
if (!string.IsNullOrEmpty(e.Reply))
{
SendCtcpReply(e.Sender, string.Format("{0} {1}", e.Command, e.Reply));
}
}
#endregion
#region ReceiveCtcpReply
private static readonly object eventKeyOfReceiveCtcpReply = new object();
/// <summary>
/// CTCPリプライを受信したときに発生します。
/// </summary>
public event CtcpEventHandler ReceiveCtcpReply;
/// <summary>
/// ReceiveCtcpReply イベントを発生させます。
/// </summary>
protected void OnReceiveCtcpReply(CtcpEventArgs e)
{
InvokeEvent(ReceiveCtcpReply, e);
}
#endregion
#region UserInOut
private static readonly object eventKeyOfUserInOut = new object();
/// <summary>
/// ユーザーの出入りがあったときに発生します。
/// </summary>
public event UserInOutEventHandler UserInOut;
/// <summary>
/// UserInOut イベントを発生させます。
/// </summary>
protected void OnUserInOut(UserInOutEventArgs e)
{
InvokeEvent(UserInOut, e);
}
#endregion
#region Kick
private static readonly object eventKeyOfKick = new object();
/// <summary>
/// キックが実行されたときに発生します。
/// </summary>
public event KickEventHandler Kick;
/// <summary>
/// Kick イベントを発生させます。
/// </summary>
protected void OnKick(KickEventArgs e)
{
InvokeEvent(Kick, e);
}
#endregion
#region TopicChange
private static readonly object eventKeyOfTopicChange = new object();
/// <summary>
/// トピックが変更されたときに発生します。
/// </summary>
public event TopicChangeEventHandler TopicChange;
/// <summary>
/// TopicChange イベントを発生させます。
/// </summary>
protected void OnTopicChange(TopicChangeEventArgs e)
{
InvokeEvent(TopicChange, e);
}
#endregion
#region ModeChange
private static readonly object eventKeyOfModeChange = new object();
/// <summary>
/// モードが変更されたときに発生します。
/// </summary>
public event ModeChangeEventHandler ModeChange;
/// <summary>
/// ModeChange イベントを発生させます。
/// </summary>
protected void OnModeChange(ModeChangeEventArgs e)
{
InvokeEvent(ModeChange, e);
}
#endregion
#endregion
#region コンストラクタ
public IRCClient()
{
m_sendQueue = new Queue<string>();
m_namelist = new Dictionary<string,string[]>();
m_receiveBuffer = new byte[ReceiveBufferLength];
m_fragmentBuffer = new byte[ReceiveBufferLength];
m_processBuffer = new byte[ReceiveBufferLength];
m_fragmentBufferLength = 0;
}
#endregion
#region 接続
public void Connect(ConnectionInformation info)
{
if (Status != IRCClientStatus.Disconnected)
{
throw new InvalidOperationException();
}
try
{
ConnectionInformation = info;
Encoding = info.GetEncoding();
CurrentNickName = info.NickName;
m_messageDelimiterBytes = Encoding.GetBytes(MessageDelimiter);
AutoFlush = true;
m_online = false;
m_sendLoopStop = false;
lock (m_sendQueue) { m_sendQueue.Clear(); }
lock (m_namelist) { m_namelist.Clear(); }
EndPoint ep = info.GetEndPoint();
SocketAsyncEventArgs eargs = new SocketAsyncEventArgs();
eargs.RemoteEndPoint = ep;
eargs.Completed += new EventHandler<SocketAsyncEventArgs>(ConnectCompleted);
Debug.WriteLine("接続を開始します。", "IRCClient");
m_socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
if (!m_socket.ConnectAsync(eargs))
{
// 同期的に処理が完了した場合
ConnectCompleted(m_socket, eargs);
}
}
catch (Exception ex)
{
OnConnectionFailed();
Close();
}
}
void ConnectCompleted(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
// 接続完了イベント発行
Debug.WriteLine("接続しました。", "IRCClient");
OnConnected();
// ログインコマンド送信
Debug.WriteLine("ログインコマンド送信します。", "IRCClient");
SendConnectCommand();
// 再帰受信開始
Receive();
}
else
{
OnConnectionFailed();
Close();
}
}
#endregion
#region 切断
public void Close()
{
if (m_socket != null)
{
m_socket.Close();
ProcessDisconnected();
}
}
private void ProcessDisconnected()
{
if (m_socket != null && m_socket.Connected)
{
m_socket.Dispose();
}
m_socket = null;
m_sendLoopStop = true;
}
#endregion
#region 受信系メソッド
private void Receive()
{
m_receiveBuffer = new byte[2048];
SocketAsyncEventArgs e = new SocketAsyncEventArgs();
e.SetBuffer(m_receiveBuffer, 0, m_receiveBuffer.Length);
e.Completed += new EventHandler<SocketAsyncEventArgs>(ReceiveCompleted);
m_socket.ReceiveAsync(e);
}
void ReceiveCompleted(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
if (e.BytesTransferred > 0)
{
Queue<EventData> eventQueue = new Queue<EventData>();
int rbp = e.Offset; // 受信バッファポインタ
int terminal = e.Offset + e.BytesTransferred;
while (rbp < terminal)
{
// 行端コードを検索
int crlfPtr = m_receiveBuffer.IndexOfArray(rbp, terminal, m_messageDelimiterBytes);
if (crlfPtr != -1 && (crlfPtr < terminal))
{
string line;
int length = crlfPtr - rbp;
// 残り物があれば、それも使う
if (m_fragmentBufferLength > 0)
{
Array.Copy(m_fragmentBuffer, m_processBuffer, m_fragmentBufferLength);
Array.Copy(m_receiveBuffer, rbp, m_processBuffer, m_fragmentBufferLength, length);
line = Encoding.GetString(m_processBuffer, 0, m_fragmentBufferLength + length);
m_fragmentBufferLength = 0;
}
else
{
line = Encoding.GetString(m_receiveBuffer, rbp, length);
}
ProcessIRCMessage(eventQueue, line);
rbp += length + m_messageDelimiterBytes.Length;
}
else
{
// 見つからなかったので、残り物にコピーする
// TODO バッファオーバーラン感あるのでなんとかする
Array.Copy(m_receiveBuffer, rbp, m_fragmentBuffer, m_fragmentBufferLength, terminal - rbp);
m_fragmentBufferLength += terminal - rbp;
break;
}
}
// イベント発生処理
if (eventQueue.Count > 0)
{
OnStartMessageEvents();
try
{
// キューにあるイベントをすべて発生させる
while (eventQueue.Count > 0)
{
EventData eventData = eventQueue.Dequeue();
#region イベントディスパッチ
if (eventData.EventKey == eventKeyOfProcessedConnection)
{
OnProcessedConnection();
}
else if (eventData.EventKey == eventKeyOfChangedMyNickname)
{
OnChangedMyNickname(eventData.Argument as NickNameChangeEventArgs);
}
else if (eventData.EventKey == eventKeyOfChangedNickname)
{
OnChangedNickname(eventData.Argument as NickNameChangeEventArgs);
}
else if (eventData.EventKey == eventKeyOfReceiveServerReply)
{
OnReceiveServerReply(eventData.Argument as ReceiveServerReplyEventArgs);
}
else if (eventData.EventKey == eventKeyOfReceiveMotdMesage)
{
OnReceiveMotdMesage(eventData.Argument as ReceiveMessageEventArgs);
}
else if (eventData.EventKey == eventKeyOfReceiveMessage)
{
OnReceiveMessage(eventData.Argument as ReceiveMessageEventArgs);
}
else if (eventData.EventKey == eventKeyOfReceiveNotice)
{
OnReceiveNotice(eventData.Argument as ReceiveMessageEventArgs);
}
else if (eventData.EventKey == eventKeyOfReceiveNames)
{
OnReceiveNames(eventData.Argument as ReceiveNamesEventArgs);
}
else if (eventData.EventKey == eventKeyOfReceiveCtcpQuery)
{
OnReceiveCtcpQuery(eventData.Argument as CtcpEventArgs);
}
else if (eventData.EventKey == eventKeyOfReceiveCtcpReply)
{
OnReceiveCtcpReply(eventData.Argument as CtcpEventArgs);
}
else if (eventData.EventKey == eventKeyOfUserInOut)
{
OnUserInOut(eventData.Argument as UserInOutEventArgs);
}
else if (eventData.EventKey == eventKeyOfModeChange)
{
OnModeChange(eventData.Argument as ModeChangeEventArgs);
}
else if (eventData.EventKey == eventKeyOfTopicChange)
{
OnTopicChange(eventData.Argument as TopicChangeEventArgs);
}
else if (eventData.EventKey == eventKeyOfKick)
{
OnKick(eventData.Argument as KickEventArgs);
}
else
{
Debug.WriteLine("Undefined event:" + eventData.Argument.GetType().ToString());
}
#endregion
}
}
catch (Exception) { }
finally
{
OnFinishMessageEvents();
}
}
}
if (!m_sendLoopStop)
Receive();
}
else
{
ProcessDisconnected();
}
}
#endregion
#region 受信メッセージ解析
/// <summary>
/// 受信したメッセージを処理
/// </summary>
/// <param name="queue">追加先のイベントキュー</param>
/// <param name="message">受信メッセージ</param>
/// <returns>発生させるイベントの情報</returns>
protected void ProcessIRCMessage(Queue<EventData> queue, string message)
{
try
{
#region メッセージパース
Debug.WriteLine("Recv> " + message, "IRCClient");
// とりあえず、半角スペースで区切る
string[] messageParams = message.TrimEnd(' ').Split(' ');
// メッセージ情報格納変数
string sender; // 送信者
string command; // コマンド
string[] parameters; // パラメータ
int parameterStartIndex; // パラメータの開始インデックス
// 基本情報を得る
if (messageParams[0].StartsWith(":"))
{
// 通常のメッセージ
sender = messageParams[0].TrimStart(':');
command = messageParams[1];
parameterStartIndex = 2;
}
else
{
// 通常以外のメッセージ
sender = string.Empty;
command = messageParams[0];
parameterStartIndex = 1;
}
// パラメータがまだ残っている場合、残りのパラメータを処理する
if (parameterStartIndex < messageParams.Length)
{
// パラメータ項目の最大数は半角スペーススプリット済みと開始インデックスの差よりも少なくなる
List<string> parameterList = new List<string>(messageParams.Length - parameterStartIndex);
for (int i = parameterStartIndex; i < messageParams.Length; i++)
{
// : で始まる場合、そこから先は一つのパラメータとして処理する
if (messageParams[i].StartsWith(":"))
{
string tempParam = messageParams[i].Substring(1);
// まだパラメータの残っている場合は一気につなぐ
if ((i + 1) < messageParams.Length)
{
tempParam += " " + string.Join(" ", messageParams, (i + 1), messageParams.Length - (i + 1));
}
parameterList.Add(tempParam);
break;
}
else
{
parameterList.Add(messageParams[i]);
}
}
parameters = new string[parameterList.Count];
parameterList.CopyTo(parameters);
}
else
{
// パラメータなし
parameters = new string[] { };
}
#endregion
// メッセージ振り分けで共通使用する変数
string channel; // チャンネル
string receiver; // 受信者
string[] receivers; // 受信者一覧
if (char.IsNumber(command[0]))
{
#region ニューメリックリプライ
// リプライ番号
ReplyNumbers number = (ReplyNumbers)int.Parse(command);
// ニューメリックリプライの parameter[0] は必ず受信者
receiver = parameters[0];
switch (number)
{
// 接続完了
case ReplyNumbers.RPL_WELCOME:
if (Status != IRCClientStatus.Online)
{
m_online = true;
// ニックネームをセット
CurrentNickName = GetUserName(receiver);
// ユーザーストリングを得る (最後のパラメータをスペースで切った最後の項目)
string[] userStringParseTemp = parameters[parameters.Length - 1].Split(' ');
UserString = userStringParseTemp[userStringParseTemp.Length - 1];
// イベントを通知
queue.Enqueue(new EventData(eventKeyOfProcessedConnection, EventArgs.Empty));
queue.Enqueue(new EventData(eventKeyOfReceiveServerReply, new ReceiveServerReplyEventArgs(number, SliceArray(parameters, 1))));
}
break;
// 名前のコンフリクト
case ReplyNumbers.ERR_NICKNAMEINUSE:
queue.Enqueue(new EventData(eventKeyOfReceiveServerReply, new ReceiveServerReplyEventArgs(number, SliceArray(parameters, 1))));
// 新しいNickname を用意する。
string newNickname = GetNextNick(CurrentNickName);
CurrentNickName = newNickname;
ChangeNickname(newNickname);
break;
#region MOTDメッセージ
case ReplyNumbers.RPL_MOTDSTART:
queue.Enqueue(new EventData(eventKeyOfReceiveMotdMesage, new ReceiveMessageEventArgs(sender, receiver, parameters[1])));
break;
case ReplyNumbers.RPL_MOTD:
queue.Enqueue(new EventData(eventKeyOfReceiveMotdMesage, new ReceiveMessageEventArgs(sender, receiver, parameters[1])));
break;
case ReplyNumbers.RPL_ENDOFMOTD:
queue.Enqueue(new EventData(eventKeyOfReceiveMotdMesage, new ReceiveMessageEventArgs(sender, receiver, parameters[1])));
break;
#endregion
#region 参加者の受信
// 参加者一覧の受信
case ReplyNumbers.RPL_NAMREPLY:
// チャンネル名を名前リストを取得
channel = parameters[2];
string[] names = parameters[3].Split(' ');
// 名前リストに項目があれば追加
if (m_namelist.ContainsKey(channel))
{
// 新しい配列を作って、そこに新旧のデータを貼り付ける
string[] tempArr = new string[m_namelist[channel].Length + names.Length];
m_namelist[channel].CopyTo(tempArr, 0);
names.CopyTo(tempArr, m_namelist[channel].Length);
m_namelist[channel] = tempArr;
}
// 名前リストに項目がなければ追加
else
{
m_namelist.Add(channel, names);
}
break;
// 参加者受信完了
case ReplyNumbers.RPL_ENDOFNAMES:
channel = parameters[1];
// チャンネルのデータがあるときのみ
if (m_namelist.ContainsKey(channel))
{
// イベントで一覧を通知した後、リストから削除
queue.Enqueue(new EventData(eventKeyOfReceiveNames, new ReceiveNamesEventArgs(channel, m_namelist[channel])));
m_namelist.Remove(channel);
}
break;
#endregion
// トピックの受信
case ReplyNumbers.RPL_TOPIC:
string topic;
channel = parameters[1];
topic = parameters[2];
queue.Enqueue(new EventData(eventKeyOfTopicChange, new TopicChangeEventArgs(channel, topic)));
break;
// チャンネルのモード変更
case ReplyNumbers.RPL_CHANNELMODEIS:
channel = parameters[1];
queue.Enqueue(new EventData(eventKeyOfModeChange, new ModeChangeEventArgs(string.Empty, channel, parameters[2])));
break;
// それ以外
default:
queue.Enqueue(new EventData(eventKeyOfReceiveServerReply, new ReceiveServerReplyEventArgs(number, SliceArray(parameters, 1))));
break;
}
#endregion
}
else
{
#region コマンド
// とりあえず parameter[0] を受信者とする
receiver = parameters[0];
switch (command.ToUpper())
{
#region メッセージ受信コマンド
case "PRIVMSG":
receivers = receiver.Split(',');
if (parameters[1].StartsWith("\x001") && parameters[1].EndsWith("\x001"))
{
// CTCPクエリを受信した場合
CtcpEventArgs arg = CreateCtcpEventArgs(sender, parameters[1]);
queue.Enqueue(new EventData(eventKeyOfReceiveCtcpQuery, arg));
}
else
{
foreach (string r in receivers)
{
queue.Enqueue(new EventData(eventKeyOfReceiveMessage,
new ReceiveMessageEventArgs(sender, r, parameters[1].Replace("\x001", ""))));
}
}
break;
case "NOTICE":
receivers = receiver.Split(',');
if (parameters[1].StartsWith("\x001") && parameters[1].EndsWith("\x001"))
{
// CTCP Reply のとき
queue.Enqueue(new EventData(eventKeyOfReceiveCtcpReply,
CreateCtcpEventArgs(sender, parameters[1])));
}
else
{
foreach (string r in receivers)
{
// イベントを通知
queue.Enqueue(new EventData(eventKeyOfReceiveNotice,
new ReceiveMessageEventArgs(sender, r, parameters[1].Replace("\x001", ""))));
}
}
break;
#endregion
case "PING":
SendCommand("PONG :" + string.Join(" ", parameters));
break;
case "JOIN":
ProcessUserInOut(queue, InOutCommands.Join, sender, receiver);
break;
case "QUIT":
ProcessUserInOut(queue, InOutCommands.Quit, sender, receiver);
break;
case "PART":
ProcessUserInOut(queue, InOutCommands.Leave, sender, receiver);
break;
case "KICK":
// イベントを通知
queue.Enqueue(new EventData(eventKeyOfKick,
new KickEventArgs(sender, receiver, parameters[1])));
break;
case "NICK":
if (sender == UserString)
{
// 自分の変更を受信したとき
// ユーザーストリングを更新
UserString = receiver + UserString.Substring(UserString.IndexOf("!"));
// 自分のニックネームを変更
CurrentNickName = GetUserName(UserString);
queue.Enqueue(new EventData(eventKeyOfChangedMyNickname,
new NickNameChangeEventArgs(IRCClient.GetUserName(sender), receiver)));
}
else
{
// 他人の名前の変更を受信したとき
queue.Enqueue(new EventData(eventKeyOfChangedNickname,
new NickNameChangeEventArgs(IRCClient.GetUserName(sender), receiver)));
}
break;
case "TOPIC":
queue.Enqueue(new EventData(eventKeyOfTopicChange,
new TopicChangeEventArgs(sender, receiver, parameters[1])));
break;
case "MODE":
if (parameters.Length > 1)
{
receivers = new string[parameters.Length - 1];
Array.Copy(parameters, 1, receivers, 0, receivers.Length);
queue.Enqueue(new EventData(eventKeyOfModeChange,
new ModeChangeEventArgs(sender, receiver, parameters[1], receivers)));
}
else if (parameters.Length == 1)
{
queue.Enqueue(new EventData(eventKeyOfModeChange,
new ModeChangeEventArgs(sender, receiver, parameters[1])));
}
break;
}
#endregion
}
}
catch (IOException ex)
{
Debug.WriteLine("MessageParseException:" + ex.Message);
throw new MessageParseException(ex);
}
}
/// <summary>
/// ユーザー入退室メッセージを処理する
/// </summary>
/// <param name="command">コマンド</param>
/// <param name="sender">送信者</param>
/// <param name="receiver">送信先</param>
protected void ProcessUserInOut(Queue<EventData> queue, InOutCommands command, string sender, string receiver)
{
string[] targets = receiver.Split(',');
// 受信先ごとにイベントを呼ぶ
foreach (string target in targets)
{
queue.Enqueue(new EventData(eventKeyOfUserInOut, new UserInOutEventArgs(sender, command, target)));
}
}
/// <summary>
/// CTCPイベントデータを生成する
/// </summary>
/// <param name="sender">送信者</param>
/// <param name="parameter">パラメータ</param>
/// <returns>生成されたイベントデータ</returns>
private CtcpEventArgs CreateCtcpEventArgs(string sender, string parameter)
{
// CTCP Query
string query = parameter.Trim('\x001');
string cmd;
string param;
int idx = query.IndexOf(" ");
if (idx < 1)
{
cmd = query;
param = string.Empty;
}
else
{
cmd = query.Substring(0, idx);
param = query.Substring(idx + 1);
}
return new CtcpEventArgs(sender, cmd, param);
}
#endregion
#region 送信系メソッド
#region 送信キューコントロール
/// <summary>
/// コマンドの送信
/// </summary>
/// <param name="message">送信するコマンド</param>
public void SendCommand(string message)
{
if (Encoding.GetByteCount(message) > MaxMessageLength)
throw new ArgumentException("message is too long", "message");
// 接続済みのときは追加
if ((Status == IRCClientStatus.Connected) || (Status == IRCClientStatus.Online))
{
lock (m_sendQueue)
{
m_sendQueue.Enqueue(message);
}
if (AutoFlush)
FlushSendQueue();
}
else
{
throw new InvalidOperationException();
}
}
/// <summary>
/// キューをフラッシュしてデータを送信する
/// </summary>
public void FlushSendQueue()
{
StringBuilder sb = new StringBuilder();
lock (m_sendQueue)
{
while (m_sendQueue.Count > 0)
{
// Enviroment.NewLineは環境依存なので、AppendLineは使えない。
string message = m_sendQueue.Dequeue();
Debug.WriteLine(string.Format("S> {0}", message), "IRCClient");
sb.Append(message);
sb.Append(MessageDelimiter);
}
}
byte[] buffer = Encoding.GetBytes(sb.ToString());
SocketAsyncEventArgs e = new SocketAsyncEventArgs();
e.SetBuffer(buffer, 0, buffer.Length);
e.Completed += new EventHandler<SocketAsyncEventArgs>(SendCompleted);
m_socket.SendAsync(e);
}
void SendCompleted(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
}
else
{
ProcessDisconnected();
}
}
#endregion
#region メッセージ作成
/// <summary>
/// 接続コマンドの送信
/// </summary>
private void SendConnectCommand()
{
bool beforeAutoFlush = AutoFlush;
AutoFlush = false;
// パスワードが設定されていれば送信
if (ConnectionInformation.ServerPassword != string.Empty)
{
SendCommand(string.Format("PASS {0}", ConnectionInformation.ServerPassword));
}
// ニックネームを送信
ChangeNickname(CurrentNickName);
// ユーザー情報送信
string loginName;
string realName;
if (!string.IsNullOrEmpty(ConnectionInformation.LoginName))
{
loginName = ConnectionInformation.LoginName;
}
else
{
loginName = ConnectionInformation.NickName;
}
if (!string.IsNullOrEmpty(ConnectionInformation.RealName))
{
realName = ConnectionInformation.RealName;
}
else
{
realName = ConnectionInformation.NickName;
}
SendCommand(string.Format("USER {0} {1} {2} :{3}", loginName, "LocalEndPoint", "RemoteEndPoint", realName));
// NickServパスワードが設定されているときは送信する
if (!string.IsNullOrEmpty(ConnectionInformation.NickservPassword))
{
SendCommand(string.Format("PRIVMSG NickServ :identify {0}", ConnectionInformation.NickservPassword));
}
AutoFlush = beforeAutoFlush;
FlushSendQueue();
}
/// <summary>
/// ニックネーム変更コマンドの送信
/// </summary>
/// <param name="newnickname">新しいニックネーム</param>
public void ChangeNickname(string newnickname)
{
// ニックネーム送信
SendCommand(string.Format("NICK {0}", newnickname));
}
/// <summary>
/// チャンネルに参加
/// </summary>
/// <param name="channel">参加するチャンネル</param>
public void JoinChannel(string channel)
{
JoinChannel(channel, null);
}
/// <summary>
/// チャンネルに参加
/// </summary>
/// <param name="channel">参加するチャンネル</param>
/// <param name="password">パスワード</param>
public void JoinChannel(string channel, string password)
{
if (!string.IsNullOrEmpty(password))
{
SendCommand(string.Format("JOIN {0} {1}", channel, password));
}
else
{
SendCommand(string.Format("JOIN {0}", channel));
}
}
/// <summary>
/// チャンネルから退室
/// </summary>
/// <param name="channel">退室するチャンネル</param>
public void LeaveChannel(string channel)
{
SendCommand(string.Format("PART {0}", channel));
}
/// <summary>
/// プライベートメッセージ送信
/// </summary>
/// <param name="receiver">送信先</param>
/// <param name="message">メッセージ</param>
public void SendPrivateMessage(string receiver, string message)
{
SendCommand(string.Format("PRIVMSG {0} :{1}", receiver, message));
}
/// <summary>
/// notice メッセージ送信
/// </summary>
/// <param name="receiver">送信先</param>
/// <param name="message">メッセージ</param>
public void SendNoticeMessage(string receiver, string message)
{
SendCommand(string.Format("NOTICE {0} :{1}", receiver, message));
}
/// <summary>
/// CTCPクエリ送信
/// </summary>
/// <param name="receiver">送信先</param>
/// <param name="message">メッセージ</param>
public void SendCtcpQuery(string receiver, string message)
{
SendPrivateMessage(receiver, string.Format("{0}{1}{0}", '\x001', message));
}
/// <summary>
/// CTCPリプライ送信
/// </summary>
/// <param name="receiver">送信先</param>
/// <param name="message">メッセージ</param>
public void SendCtcpReply(string receiver, string message)
{
SendNoticeMessage(receiver, string.Format("{0}{1}{0}", '\x001', message));
}
#endregion
#endregion
#region 動的プロパティ
/// <summary>
/// 接続ステータスを取得します
/// </summary>
public IRCClientStatus Status
{
get
{
if (m_socket != null)
{
if (m_socket.Connected)
{
if (m_online)
{
// オンライン (ログイン完了)
return IRCClientStatus.Online;
}
else
{
// 接続済み (ログイン処理中)
return IRCClientStatus.Connected;
}
}
else
{
// ソケット接続処理中
return IRCClientStatus.EstablishConnection;
}
}
else
{
// 接続されていない
return IRCClientStatus.Disconnected;
}
}
}
#endregion
#region スタティックメソッド
/// <summary>
/// 配列の先頭の要素を指定された数だけ切り落とします
/// </summary>
/// <param name="array"></param>
/// <param name="sliceLength"></param>
/// <returns></returns>
private static string[] SliceArray(string[] array, int sliceLength)
{
// 受信者の分のパラメータを切り詰める
string[] sliceTempArray = new string[array.Length - 1];
Array.Copy(array, sliceLength, sliceTempArray, 0, sliceTempArray.Length);
return sliceTempArray;
}
/// <summary>
/// ユーザーフルネームからユーザー名を取得します
/// </summary>
/// <param name="userstring"></param>
/// <returns></returns>
public static string GetUserName(string userstring)
{
int temp = userstring.IndexOf("!");
if (temp > 0)
{
return userstring.Substring(0, temp);
}
else
{
return userstring;
}
}
/// <summary>
/// NICKコンフリクト時用の新しいニックネームを生成します。
/// </summary>
/// <param name="nick"></param>
/// <returns></returns>
public static string GetNextNick(string nick)
{
// 最大文字数オーバー時は、最大文字数以内になるように削る
if (nick.Length >= MaxNickLength)
{
nick = nick.Substring(0, MaxNickLength);
}
// 末尾の数字を取得する
string name;
string number;
Match match = Regex.Match(nick, @"([^\d]*)(\d+)$");
if (match.Success)
{
name = match.Groups[1].Value;
number = (int.Parse(match.Groups[2].Value) + 1).ToString();
}
else
{
name = nick;
number = "0";
}
// 名前を生成する
if ((name.Length + number.Length) > MaxNickLength)
{
return name.Substring(0, MaxNickLength - number.Length) + number;
}
else
{
return name + number;
}
}
/// <summary>
/// 指定された文字列がチャンネル形式かどうか調べます。
/// </summary>
/// <param name="text">調べる文字列</param>
/// <returns>チャンネルの形式の場合は true</returns>
public static bool IsChannelString(string text)
{
if (text.StartsWith("#") || text.StartsWith("&")
|| text.StartsWith("+") || text.StartsWith("!"))
{
if (text.Length <= 50)
{
return true;
}
else
{
return false;
}
}
return false;
}
#endregion
#region 受信処理用内部クラス
/// <summary>
/// 受信処理のパース結果のイベントのデータを表すクラス
/// </summary>
protected class EventData
{
private object m_key;
private EventArgs m_arg;
/// <summary>
/// イベントデータのキー
/// </summary>
public object EventKey
{
get { return m_key; }
}
/// <summary>
/// イベントデータ
/// </summary>
public EventArgs Argument
{
get { return m_arg; }
}
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="eventKey">イベントキー</param>
/// <param name="argument">イベントデータ</param>
public EventData(object eventKey, EventArgs argument)
{
m_key = eventKey;
m_arg = argument;
}
}
#endregion
}
/// <summary>
/// IRCClient のステータスをあらわす定数
/// </summary>
public enum IRCClientStatus
{
/// <summary>
/// 接続していない
/// </summary>
Disconnected,
/// <summary>
/// 接続処理中
/// </summary>
EstablishConnection,
/// <summary>
/// サーバーに接続済み
/// </summary>
Connected,
/// <summary>
/// 接続処理完了
/// </summary>
Online
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment