Created
June 27, 2011 15:01
-
-
Save iseebi/1049032 to your computer and use it in GitHub Desktop.
EbiSoft.EbIRC.IRC.IRCClient in Siverlight for Windows Phone
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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