root/TwitterIrcGateway/trunk/TwitterIrcGatewayCore/Session.cs

Revision 417, 55.9 kB (checked in by tomoyo, 13 days ago)

bodyを含めているとpostでエラーとなる問題を修正した。

  • Property svn:keywords set to Id
Line 
1using System;
2using System.Collections.Generic;
3using System.ComponentModel;
4using System.Diagnostics;
5using System.Globalization;
6using System.IO;
7using System.Net.Sockets;
8using System.Net;
9using System.Reflection;
10using System.Text;
11using System.Text.RegularExpressions;
12using System.Threading;
13using System.Web;
14using System.Xml;
15
16using TypableMap;
17using Misuzilla.Net.Irc;
18using Misuzilla.Applications.TwitterIrcGateway.Filter;
19using Misuzilla.Applications.TwitterIrcGateway.AddIns;
20
21namespace Misuzilla.Applications.TwitterIrcGateway
22{
23    public class Session : IDisposable
24    {
25        private readonly static String ConfigBasePath = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "Configs");
26
27        private Server _server;
28        private TcpClient _tcpClient;
29        private StreamWriter _writer;
30        private String _clientHost;
31        private TwitterService _twitter;
32        private TwitterIMService _twitterIm;
33        private LinkedList<Int32> _lastStatusIdsFromGateway;
34        private Groups _groups;
35        private Filters _filter;
36        private Config _config;
37        private AddInManager _addinManager;
38
39        private List<String> _nickNames = new List<string>();
40        private Boolean _isFirstTime = true;
41
42        private TraceListener _traceListener;
43
44        private String _username;
45        private String _password;
46        private String _nick;
47       
48        #region Events
49        public event EventHandler<MessageReceivedEventArgs> MessageReceived;
50        public event EventHandler<SessionStartedEventArgs> SessionStarted;
51        public event EventHandler<EventArgs> SessionEnded;
52       
53        public event EventHandler<EventArgs> ConfigChanged;
54
55        public event EventHandler<EventArgs> AddInsLoadCompleted;
56
57        public event EventHandler<TimelineStatusesEventArgs> PreProcessTimelineStatuses;
58        public event EventHandler<TimelineStatusEventArgs> PreProcessTimelineStatus;
59        public event EventHandler<TimelineStatusEventArgs> PreFilterProcessTimelineStatus;
60        public event EventHandler<TimelineStatusEventArgs> PostFilterProcessTimelineStatus;
61        public event EventHandler<TimelineStatusEventArgs> PreSendMessageTimelineStatus;
62        public event EventHandler<TimelineStatusEventArgs> PostSendMessageTimelineStatus;
63        public event EventHandler<TimelineStatusEventArgs> PostProcessTimelineStatus;
64        public event EventHandler<TimelineStatusesEventArgs> PostProcessTimelineStatuses;
65
66        public event EventHandler<StatusUpdateEventArgs> PreSendUpdateStatus;
67        #endregion
68
69        private Boolean _requireIMReconnect = false;
70        private Int32 _imReconnectCount = 0;
71
72        public Session(Server server, TcpClient tcpClient)
73        {
74            MessageReceived += new EventHandler<MessageReceivedEventArgs>(MessageReceived_USER);
75            MessageReceived += new EventHandler<MessageReceivedEventArgs>(MessageReceived_NICK);
76            MessageReceived += new EventHandler<MessageReceivedEventArgs>(MessageReceived_PASS);
77            MessageReceived += new EventHandler<MessageReceivedEventArgs>(MessageReceived_QUIT);
78            MessageReceived += new EventHandler<MessageReceivedEventArgs>(MessageReceived_PRIVMSG);
79            MessageReceived += new EventHandler<MessageReceivedEventArgs>(MessageReceived_WHOIS);
80            MessageReceived += new EventHandler<MessageReceivedEventArgs>(MessageReceived_INVITE);
81            MessageReceived += new EventHandler<MessageReceivedEventArgs>(MessageReceived_JOIN);
82            MessageReceived += new EventHandler<MessageReceivedEventArgs>(MessageReceived_PART);
83            MessageReceived += new EventHandler<MessageReceivedEventArgs>(MessageReceived_KICK);
84            MessageReceived += new EventHandler<MessageReceivedEventArgs>(MessageReceived_LIST);
85            MessageReceived += new EventHandler<MessageReceivedEventArgs>(MessageReceived_TOPIC);
86            MessageReceived += new EventHandler<MessageReceivedEventArgs>(MessageReceived_MODE);
87            MessageReceived += new EventHandler<MessageReceivedEventArgs>(MessageReceived_TIGIMENABLE);
88            MessageReceived += new EventHandler<MessageReceivedEventArgs>(MessageReceived_TIGIMDISABLE);
89
90            _groups = new Groups();
91            _filter = new Filters();
92            _config = new Config();
93
94            _server = server;
95            _tcpClient = tcpClient;
96            _lastStatusIdsFromGateway = new LinkedList<int>();
97           
98            _addinManager = new AddInManager();
99        }
100
101        ~Session()
102        {
103            this.Dispose();
104        }
105
106        /// <summary>
107        ///
108        /// </summary>
109        public TcpClient TcpClient
110        {
111            get { return _tcpClient; }
112        }
113       
114        /// <summary>
115        ///
116        /// </summary>
117        public String Nick
118        {
119            get { return _nick; }
120        }
121       
122        /// <summary>
123        ///
124        /// </summary>
125        public String ClientHost
126        {
127            get { return _clientHost; }
128        }
129       
130        /// <summary>
131        /// セッションに結びつけられたTwitterへのAPIアクセスのためのサービス
132        /// </summary>
133        public TwitterService TwitterService
134        {
135            get { return _twitter; }
136        }
137       
138        /// <summary>
139        /// セッションに結びつけられた設定
140        /// </summary>
141        public Config Config
142        {
143            get { return _config; }
144        }
145       
146        /// <summary>
147        /// セッションが持つグループのコレクション
148        /// </summary>
149        public Groups Groups
150        {
151            get { return _groups;  }
152        }
153
154        /// <summary>
155        /// セッションを開始します。
156        /// </summary>
157        public void Start()
158        {
159            CheckDisposed();
160            try
161            {
162                using (NetworkStream stream = _tcpClient.GetStream())
163                using (StreamReader sr = new StreamReader(stream, _server.Encoding))
164                using (StreamWriter sw = new StreamWriter(stream, _server.Encoding))
165                {
166                    _writer = sw;
167
168                    String line;
169                    while (_tcpClient.Connected && (line = sr.ReadLine()) != null)
170                    {
171                        try
172                        {
173                            IRCMessage msg = IRCMessage.CreateMessage(line);
174                            OnMessageReceived(msg);
175                        }
176                        catch (IRCException)
177                        {}
178                    }
179                }
180            }
181            catch (IOException)
182            {}
183            catch (NullReferenceException)
184            {}
185            finally
186            {
187                OnSessionEnded();
188                this.Close();
189            }
190        }
191
192        #region イベント実行メソッド
193        protected virtual void OnMessageReceived(IRCMessage msg)
194        {
195            FireEvent(MessageReceived, new MessageReceivedEventArgs(msg, _writer, _tcpClient));
196        }
197        protected virtual void OnSessionStarted(String username)
198        {
199            LoadConfig();
200            OnConfigChanged();
201           
202            LoadGroups();
203            LoadFilters();
204
205            if (!String.IsNullOrEmpty(_config.IMServiceServerName))
206            {
207                ConnectToIMService(true);
208            }
209
210            _addinManager.Load(_server, this);
211            FireEvent(AddInsLoadCompleted, EventArgs.Empty);
212           
213            FireEvent(SessionStarted, new SessionStartedEventArgs(username));
214        }
215        protected virtual void OnSessionEnded()
216        {
217            FireEvent(SessionEnded, EventArgs.Empty);
218        }
219        protected virtual void OnConfigChanged()
220        {
221            if (ConfigChanged != null)
222                ConfigChanged(this, EventArgs.Empty);
223
224            if (_traceListener == null && (_config.EnableTrace || _server.EnableTrace))
225            {
226                _traceListener = new IrcTraceListener(this);
227                Trace.Listeners.Add(_traceListener);
228            }
229            else if ((_traceListener != null) && !_config.EnableTrace && !_server.EnableTrace)
230            {
231                Trace.Listeners.Remove(_traceListener);
232                _traceListener = null;
233            }
234
235        }
236       
237        /// <summary>
238        ///
239        /// </summary>
240        public void LoadFilters()
241        {
242            // filters 読み取り
243            String path = Path.Combine(ConfigBasePath, Path.Combine(_username, "Filters.xml"));
244            try
245            {
246                _filter = Filters.Load(path);
247            }
248            catch (IOException ie)
249            {
250                SendTwitterGatewayServerMessage("エラー: " + ie.Message);
251            }
252        }
253        /// <summary>
254        ///
255        /// </summary>
256        public void LoadGroups()
257        {
258            // group 読み取り
259            lock (_groups)
260            {
261                String path = Path.Combine(ConfigBasePath, Path.Combine(_username, "Groups.xml"));
262                try
263                {
264                    _groups = Groups.Load(path);
265
266                    // 下位互換性FIX: グループに自分自身のNICKは存在しないようにします
267                    foreach (Group g in _groups.Values)
268                    {
269                        g.Members.Remove(_nick);
270                    }
271                }
272                catch (IOException ie)
273                {
274                    SendTwitterGatewayServerMessage("エラー: " + ie.Message);
275                }
276            }
277        }
278        /// <summary>
279        ///
280        /// </summary>
281        public void SaveGroups()
282        {
283            // group 読み取り
284            lock (_groups)
285            {
286                String path = Path.Combine(ConfigBasePath, Path.Combine(_username, "Groups.xml"));
287                try
288                {
289                    _groups.Save(path);
290                }
291                catch (IOException ie)
292                {
293                    SendTwitterGatewayServerMessage("エラー: " + ie.Message);
294                }
295            }
296        }
297        /// <summary>
298        ///
299        /// </summary>
300        public void LoadConfig()
301        {
302            lock (_config)
303            {
304                String path = Path.Combine(ConfigBasePath, Path.Combine(_username, "Config.xml"));
305                try
306                {
307                    _config = Config.Load(path);
308                }
309                catch (IOException ie)
310                {
311                    SendTwitterGatewayServerMessage("エラー: " + ie.Message);
312                }
313            }
314        }       
315        /// <summary>
316        ///
317        /// </summary>
318        public void SaveConfig()
319        {
320            lock (_config)
321            {
322                String path = Path.Combine(ConfigBasePath, Path.Combine(_username, "Config.xml"));
323                try
324                {
325                    _config.Save(path);
326                    OnConfigChanged();
327                }
328                catch (IOException ie)
329                {
330                    SendTwitterGatewayServerMessage("エラー: " + ie.Message);
331                }
332            }
333        }
334
335        #endregion
336
337        private Group GetGroupByChannelName(String channelName)
338        {
339            // グループを取得/作成
340            Group group;
341            if (!_groups.TryGetValue(channelName, out group))
342            {
343                group = new Group(channelName);
344                _groups.Add(channelName, group);
345            }
346            return group;
347        }
348
349        #region メッセージ処理イベント
350        private void MessageReceived_JOIN(object sender, MessageReceivedEventArgs e)
351        {
352            Trace.WriteLine(e.Message.ToString());
353            if (!(e.Message is JoinMessage)) return;
354            if (String.IsNullOrEmpty(e.Message.CommandParams[0]))
355            {
356                SendErrorReply(ErrorReply.ERR_NEEDMOREPARAMS, "Not enough parameters");
357                return;
358            }
359
360            JoinMessage joinMsg = e.Message as JoinMessage;
361            Trace.WriteLine(String.Format("Join: {0} -> {1}", joinMsg.Sender, joinMsg.Channel));
362            String[] channelNames = joinMsg.Channel.Split(new Char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
363            foreach (String channelName in channelNames)
364            {
365                if (!channelName.StartsWith("#") || channelName.Length < 3 || String.Compare(channelName, _server.ChannelName, true) == 0)
366                {
367                    Trace.WriteLine(String.Format("No nick/such channel: {0}", channelName));
368                    SendErrorReply(ErrorReply.ERR_NOSUCHCHANNEL, "No such nick/channel");
369                    continue;
370                }
371
372                // グループを取得/作成
373                Group group = GetGroupByChannelName(channelName);
374                if (!group.IsJoined)
375                {
376                    joinMsg = new JoinMessage(channelName, "");
377                    SendServer(joinMsg);
378
379                    SendNumericReply(NumericReply.RPL_NAMREPLY, "=", channelName, String.Format("@{0} ", _nick) + String.Join(" ", group.Members.ToArray()));
380                    SendNumericReply(NumericReply.RPL_ENDOFNAMES, channelName, "End of NAMES list");
381                    group.IsJoined = true;
382
383                    // mode
384                    foreach (ChannelMode mode in group.ChannelModes)
385                    {
386                        Send(new ModeMessage(channelName, mode.ToString()));
387                    }
388                   
389                    // Set topic of client, if topic was set
390                    if (!String.IsNullOrEmpty(group.Topic))
391                    {
392                        Send(new TopicMessage(channelName, group.Topic));
393                    }
394                    else
395                    {
396                        SendNumericReply(NumericReply.RPL_NOTOPIC, channelName, "No topic is set");
397                    }
398                }
399            }
400       }
401
402        private void MessageReceived_PART(object sender, MessageReceivedEventArgs e)
403        {
404            if (!(e.Message is PartMessage)) return;
405            if (String.IsNullOrEmpty(e.Message.CommandParams[0]))
406            {
407                SendErrorReply(ErrorReply.ERR_NEEDMOREPARAMS, "Not enough parameters");
408                return;
409            }
410
411            PartMessage partMsg = e.Message as PartMessage;
412            Trace.WriteLine(String.Format("Part: {0} -> {1}", partMsg.Sender, partMsg.Channel));
413            String[] channelNames = partMsg.Channel.Split(new Char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
414            foreach (String channelName in channelNames)
415            {
416                if (!channelName.StartsWith("#") || channelName.Length < 3 || String.Compare(channelName, _server.ChannelName, true) == 0)
417                {
418                    SendErrorReply(ErrorReply.ERR_NOSUCHCHANNEL, "No such nick/channel");
419                    continue;
420                }
421
422                // グループを取得/作成
423                Group group;
424                if (_groups.TryGetValue(channelName, out group))
425                {
426                    group.IsJoined = false;
427                }
428                else
429                {
430                    SendErrorReply(ErrorReply.ERR_NOTONCHANNEL, "You're not on that channel");
431                    continue;
432                }
433                partMsg = new PartMessage(channelName, "");
434                SendServer(partMsg);
435
436                // もう捨てていい?
437                if (group.Members.Count == 0)
438                {
439                    _groups.Remove(group.Name);
440                    SendTwitterGatewayServerMessage("グループ \""+group.Name+"\" を削除しました。");
441                }
442            }
443        }
444        private void MessageReceived_KICK(object sender, MessageReceivedEventArgs e)
445        {
446            if (String.Compare(e.Message.Command, "KICK", true) != 0) return;
447
448            String[] channels = e.Message.CommandParams[0].Split(new Char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
449            String[] kickTargets = e.Message.CommandParams[1].Split(new Char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
450            if (channels.Length == 0 || (channels.Length != 1 && channels.Length != kickTargets.Length))
451            {
452                SendErrorReply(ErrorReply.ERR_NEEDMOREPARAMS, "Not enough parameters");
453                return;
454            }
455
456            if (channels.Length == 1)
457            {
458                // 一チャンネルから複数けりだす
459                Group group;
460                if (!_groups.TryGetValue(channels[0], out group))
461                {
462                    SendErrorReply(ErrorReply.ERR_NOTONCHANNEL, "You're not on that channel");
463                    return;
464                }
465                foreach (String kickTarget in kickTargets)
466                {
467                    if (group.Exists(kickTarget))
468                    {
469                        group.Remove(kickTarget);
470
471                        OtherMessage kickMsg = new OtherMessage("KICK");
472                        kickMsg.Sender = e.Message.Sender;
473                        kickMsg.CommandParams[0] = channels[0];
474                        kickMsg.CommandParams[1] = kickTarget;
475                        kickMsg.CommandParams[2] = e.Message.CommandParams[2];
476                        Send(kickMsg);
477                    }
478                    else
479                    {
480                        SendErrorReply(ErrorReply.ERR_NOSUCHNICK, "No such nick/channel");
481                        return;
482                    }
483                }
484            }
485            else
486            {
487                // 複数チャンネルからそれぞれ
488                for (Int32 i = 0; i < channels.Length; i++)
489                {
490                    String channelName = channels[i];
491                    Group group;
492                    if (!_groups.TryGetValue(channelName, out group))
493                    {
494                        SendErrorReply(ErrorReply.ERR_NOTONCHANNEL, "You're not on that channel");
495                        return;
496                    }
497                    if (group.Exists(kickTargets[i]))
498                    {
499                        group.Remove(kickTargets[i]);
500                       
501                        OtherMessage kickMsg = new OtherMessage("KICK");
502                        kickMsg.Sender = e.Message.Sender;
503                        kickMsg.CommandParams[0] = group.Name;
504                        kickMsg.CommandParams[1] = kickTargets[i];
505                        kickMsg.CommandParams[2] = e.Message.CommandParams[2];
506                        Send(kickMsg);
507                    }
508                    else
509                    {
510                        SendErrorReply(ErrorReply.ERR_NOSUCHNICK, "No such nick/channel");
511                        return;
512                    }
513                }
514            }
515
516            SaveGroups();
517        }
518        private void MessageReceived_LIST(object sender, MessageReceivedEventArgs e)
519        {
520            if (String.Compare(e.Message.Command, "LIST", true) != 0) return;
521            foreach (Group group in _groups.Values)
522            {
523                SendNumericReply(NumericReply.RPL_LIST, group.Name, group.Members.Count.ToString(), "");
524            }
525            SendNumericReply(NumericReply.RPL_LISTEND, "End of LIST");
526        }
527        private void MessageReceived_INVITE(object sender, MessageReceivedEventArgs e)
528        {
529            if (String.Compare(e.Message.Command, "INVITE", true) != 0) return;
530            if (String.IsNullOrEmpty(e.Message.CommandParams[0]))
531            {
532                SendErrorReply(ErrorReply.ERR_NONICKNAMEGIVEN, "No nickname given");
533                return;
534            }
535
536            String userName = e.Message.CommandParams[0];
537            String channelName = e.Message.CommandParams[1];
538            Trace.WriteLine(String.Format("Invite: {0} -> {1}", userName, channelName));
539            if (!channelName.StartsWith("#") || channelName.Length < 3 || String.Compare(channelName, _server.ChannelName, true) == 0)
540            {
541                SendErrorReply(ErrorReply.ERR_NOSUCHCHANNEL, "No such nick/channel");
542                return;
543            }
544
545            // グループを取得、ユーザ追加
546            Group group = GetGroupByChannelName(channelName);
547            if (!group.Exists(userName))
548            {
549                group.Add(userName);
550            }
551            if (group.IsJoined)
552            {
553                JoinMessage joinMsg = new JoinMessage(channelName, "");
554                joinMsg.SenderHost = "twitter@" + Server.ServerName;
555                joinMsg.SenderNick = userName;
556                Send(joinMsg);
557            }
558
559            SaveGroups();
560        }
561
562        private void MessageReceived_USER(object sender, MessageReceivedEventArgs e)
563        {
564            if (!(e.Message is UserMessage)) return;
565
566            if (String.IsNullOrEmpty(_nick))
567            {
568                SendErrorReply(ErrorReply.ERR_NONICKNAMEGIVEN, "No nickname given");
569                return;
570            }
571            else if (String.IsNullOrEmpty(_password))
572            {
573                SendErrorReply(ErrorReply.ERR_PASSWDMISMATCH, "Password Incorrect");
574                return;
575            }
576
577            //_username = e.Message.CommandParams[0]; // usernameがtwitterのIDとなる
578            _username = e.Message.CommandParams[3];
579
580            Type t = typeof(Server);
581            _clientHost = String.Format("{0}!{1}@{2}", _nick, e.Message.CommandParams[0], ((IPEndPoint)(e.Client.Client.RemoteEndPoint)).Address);
582
583            SendNumericReply(NumericReply.RPL_WELCOME
584                , String.Format("Welcome to the Internet Relay Network {0}", _clientHost));
585            SendNumericReply(NumericReply.RPL_YOURHOST
586                , String.Format("Your host is {0}, running version {1}", t.FullName, t.Assembly.GetName().Version));
587            SendNumericReply(NumericReply.RPL_CREATED
588                , String.Format("This server was created {0}", DateTime.Now));
589            SendNumericReply(NumericReply.RPL_MYINFO
590                , String.Format("{0} {1}-{2} {3} {4}", Environment.MachineName, t.FullName, t.Assembly.GetName().Version, "", ""));
591
592            JoinMessage joinMsg = new JoinMessage(_server.ChannelName, "");
593            PrivMsgMessage autoMsg = new PrivMsgMessage();
594            autoMsg.SenderNick = Server.ServerNick;
595            autoMsg.SenderHost = "twitter@" + Server.ServerName;
596            autoMsg.Receiver = _server.ChannelName;
597            autoMsg.Content = "Twitter IRC Gateway Server Connected.";
598
599            SendServer(joinMsg);
600            Send(autoMsg);
601
602            //
603            // Twitte Service Setup
604            //
605            _twitter = new TwitterService(_username, _password);
606            _twitter.CookieLoginMode = _server.CookieLoginMode;
607            _twitter.Interval = _server.Interval;
608            _twitter.IntervalDirectMessage = _server.IntervalDirectMessage;
609            _twitter.IntervalReplies = _server.IntervalReplies;
610            _twitter.EnableRepliesCheck = _server.EnableRepliesCheck;
611            _twitter.POSTFetchMode = _server.POSTFetchMode;
612            _twitter.RepliesReceived += new EventHandler<StatusesUpdatedEventArgs>(twitter_RepliesReceived);
613            _twitter.TimelineStatusesReceived += new EventHandler<StatusesUpdatedEventArgs>(twitter_TimelineStatusesReceived);
614            _twitter.CheckError += new EventHandler<ErrorEventArgs>(twitter_CheckError);
615            _twitter.DirectMessageReceived += new EventHandler<DirectMessageEventArgs>(twitter_DirectMessageReceived);
616            if (_server.Proxy != null)
617                _twitter.Proxy = _server.Proxy;
618
619            OnSessionStarted(_username);
620            Trace.WriteLine(String.Format("SessionStarted: UserName={0}; Nickname={1}", _username, _nick));
621
622            _twitter.Start();
623        }
624
625        void MessageReceived_NICK(object sender, MessageReceivedEventArgs e)
626        {
627            if (!(e.Message is NickMessage)) return;
628
629            _nick = ((NickMessage)(e.Message)).NewNick;
630        }
631
632        void MessageReceived_PASS(object sender, MessageReceivedEventArgs e)
633        {
634            if (String.Compare(e.Message.Command, "PASS", true) != 0) return;
635
636            if (e.Message.CommandParam.Length != 0)
637            {
638                _password = e.Message.CommandParam.Substring(1);
639            }
640        }
641
642        void MessageReceived_QUIT(object sender, MessageReceivedEventArgs e)
643        {
644            if (!(e.Message is QuitMessage)) return;
645
646            try
647            {
648                e.Client.Close();
649            }
650            catch { }
651        }
652
653        void MessageReceived_PRIVMSG(object sender, MessageReceivedEventArgs e)
654        {
655            PrivMsgMessage message = e.Message as PrivMsgMessage;
656            if (message == null) return;
657
658            StatusUpdateEventArgs eventArgs = new StatusUpdateEventArgs(message, message.Content);
659            if (!FireEvent(PreSendUpdateStatus, eventArgs)) return;
660
661            Boolean isRetry = false;
662            Retry:
663            try
664            {
665                // チャンネル宛は自分のメッセージを書き換え
666                if ((String.Compare(message.Receiver, _server.ChannelName, true) == 0) || message.Receiver.StartsWith("#"))
667                {
668                    try
669                    {
670                        Status status = _twitter.UpdateStatus(message.Content);
671                        if (status != null)
672                        {
673                            Trace.WriteLineIf(status != null, String.Format("Status Update: {0} (ID:{1}, CreatedAt:{2})", status.Text, status.Id.ToString(), status.CreatedAt.ToString()));
674
675                            _lastStatusIdsFromGateway.AddLast(status.Id);
676                            if (_lastStatusIdsFromGateway.Count > 100)
677                            {
678                                _lastStatusIdsFromGateway.RemoveFirst();
679                            }
680                        }
681                    }
682                    catch (TwitterServiceException tse)
683                    {
684                        SendTwitterGatewayServerMessage("エラー: メッセージは完了しましたが、レスポンスを正しく受信できませんでした。(" + tse.Message + ")");
685                    }
686
687                    // topic にする
688                    if (_server.SetTopicOnStatusChanged)
689                    {
690                        TopicMessage topicMsg = new TopicMessage(_server.ChannelName, message.Content);
691                        topicMsg.Sender = _clientHost;
692                        Send(topicMsg);
693                    }
694
695                    // 他のチャンネルにも投げる
696                    if (_server.BroadcastUpdate)
697                    {
698                        // #Twitter
699                        if (String.Compare(message.Receiver, _server.ChannelName, true) != 0)
700                        {
701                            // XXX: 例によってIRCライブラリのバージョンアップでどうにかしたい
702                            if (_server.BroadcastUpdateMessageIsNotice)
703                            {
704                                Send(new NoticeMessage()
705                                {
706                                    Sender = _clientHost,
707                                    Receiver = _server.ChannelName,
708                                    Content = message