aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCConnector.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Region/Environment/Modules/Avatar/Chat/IRCConnector.cs')
-rw-r--r--OpenSim/Region/Environment/Modules/Avatar/Chat/IRCConnector.cs710
1 files changed, 710 insertions, 0 deletions
diff --git a/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCConnector.cs b/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCConnector.cs
new file mode 100644
index 0000000..0eb303c
--- /dev/null
+++ b/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCConnector.cs
@@ -0,0 +1,710 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSim Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Collections.Generic;
30using System.IO;
31using System.Net.Sockets;
32using System.Reflection;
33using System.Text.RegularExpressions;
34using System.Threading;
35using OpenMetaverse;
36using log4net;
37using Nini.Config;
38using OpenSim.Framework;
39using OpenSim.Region.Environment.Interfaces;
40using OpenSim.Region.Environment.Scenes;
41
42namespace OpenSim.Region.Environment.Modules.Avatar.Chat
43{
44 public class IRCConnector
45 {
46 #region ErrorReplies enum
47
48 public enum ErrorReplies
49 {
50 NotRegistered = 451, // ":You have not registered"
51 NicknameInUse = 433 // "<nick> :Nickname is already in use"
52 }
53
54 #endregion
55
56 #region Replies enum
57
58 public enum Replies
59 {
60 MotdStart = 375, // ":- <server> Message of the day - "
61 Motd = 372, // ":- <text>"
62 EndOfMotd = 376 // ":End of /MOTD command"
63 }
64
65 #endregion
66
67 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
68
69 private Thread m_listener = null;
70 private Thread m_watchdog = null;
71 private Thread m_pinger = null;
72
73 private bool m_randomizeNick = true;
74
75 public string m_baseNick = null;
76 private string m_nick = null;
77 public string Nick
78 {
79 get { return m_baseNick; }
80 set { m_baseNick = value; }
81 }
82
83 private bool m_enabled = false;
84 public bool Enabled
85 {
86 get { return m_enabled; }
87 }
88
89 private bool m_connected = false;
90 public bool Connected
91 {
92 get { return m_connected; }
93 }
94
95 private string m_ircChannel;
96 public string IrcChannel
97 {
98 get { return m_ircChannel; }
99
100 set { m_ircChannel = value; }
101 }
102
103 private bool m_relayPrivateChannels = false;
104 public bool RelayPrivateChannels
105 {
106 get { return m_relayPrivateChannels; }
107 set { m_relayPrivateChannels = value; }
108 }
109
110 private int m_relayChannel = 0;
111 public int RelayChannel
112 {
113 get { return m_relayChannel; }
114 set { m_relayChannel = value; }
115 }
116
117 private bool m_clientReporting = true;
118 public bool ClientReporting
119 {
120 get { return m_clientReporting; }
121 set { m_clientReporting = value; }
122 }
123
124 private uint m_port = 6667;
125 public uint Port
126 {
127 get { return m_port; }
128 set { m_port = value; }
129 }
130
131 private string m_server = null;
132 public string Server
133 {
134 get { return m_server; }
135 set { m_server = value; }
136 }
137
138 public string m_accessPassword = "badkitty";
139
140 private string m_privmsgformat = "PRIVMSG {0} :<{1} in {2}>: {3}";
141 private StreamReader m_reader;
142 private List<Scene> m_scenes = new List<Scene>();
143
144 private NetworkStream m_stream = null;
145 internal object m_syncConnect = new object();
146 private TcpClient m_tcp;
147 private string m_user = "USER OpenSimBot 8 * :I'm an OpenSim to IRC bot";
148 private StreamWriter m_writer;
149
150
151 public IRCConnector(IConfigSource config)
152 {
153 m_tcp = null;
154 m_writer = null;
155 m_reader = null;
156
157 // configuration in OpenSim.ini
158 // [IRC]
159 // server = chat.freenode.net
160 // nick = OSimBot_mysim
161 // nicknum = true
162 // ;nicknum set to true appends a 2 digit random number to the nick
163 // ;username = USER OpenSimBot 8 * :I'm a OpenSim to irc bot
164 // ; username is the IRC command line sent
165 // ; USER <irc_user> <visible=8,invisible=0> * : <IRC_realname>
166 // channel = #opensim-regions
167 // port = 6667
168 // ;MSGformat fields : 0=botnick, 1=user, 2=region, 3=message
169 // ;for <bot>:<user in region> :<message>
170 // ;msgformat = "PRIVMSG {0} :<{1} in {2}>: {3}"
171 // ;for <bot>:<message> - <user of region> :
172 // ;msgformat = "PRIVMSG {0} : {3} - {1} of {2}"
173 // ;for <bot>:<message> - from <user> :
174 // ;msgformat = "PRIVMSG {0} : {3} - from {1}"
175 // Traps I/O disconnects so it does not crash the sim
176 // Trys to reconnect if disconnected and someone says something
177 // Tells IRC server "QUIT" when doing a close (just to be nice)
178 // Default port back to 6667
179
180 try
181 {
182 m_server = config.Configs["IRC"].GetString("server");
183 m_baseNick = config.Configs["IRC"].GetString("nick", "OSimBot");
184
185 m_randomizeNick = config.Configs["IRC"].GetBoolean("randomize_nick", m_randomizeNick);
186 m_randomizeNick = config.Configs["IRC"].GetBoolean("nicknum", m_randomizeNick); // compat
187 m_ircChannel = config.Configs["IRC"].GetString("channel");
188 m_port = (uint)config.Configs["IRC"].GetInt("port", (int)m_port);
189 m_user = config.Configs["IRC"].GetString("username", m_user);
190 m_privmsgformat = config.Configs["IRC"].GetString("msgformat", m_privmsgformat);
191 // m_commandChannel = config.Configs["IRC"].GetInt("commandchannel", m_commandChannel);
192
193 // m_verbosity = config.Configs["IRC"].GetInt("verbosity", m_verbosity);
194 m_clientReporting = config.Configs["IRC"].GetInt("verbosity", 2) > 0;
195 m_clientReporting = config.Configs["IRC"].GetBoolean("report_clients", m_clientReporting);
196
197 // m_messageOutChannel = config.Configs["IRC"].GetInt("outchannel", m_messageOutChannel);
198 // m_messageInChannel = config.Configs["IRC"].GetInt("inchannel", m_messageInChannel);
199 m_relayPrivateChannels = config.Configs["IRC"].GetBoolean("relay_private_channels", m_relayPrivateChannels);
200 m_relayPrivateChannels = config.Configs["IRC"].GetBoolean("useworldcomm", m_relayPrivateChannels); //compat
201 m_relayChannel = config.Configs["IRC"].GetInt("relay_private_channel_in", m_relayChannel);
202 m_relayChannel = config.Configs["IRC"].GetInt("inchannel", m_relayChannel);
203 m_accessPassword = config.Configs["IRC"].GetString("access_password",m_accessPassword);
204 // m_useWorldComm = config.Configs["IRC"].GetBoolean("useworldcomm", m_useWorldComm);
205
206 if (m_server != null && m_baseNick != null && m_ircChannel != null)
207 {
208 if (m_randomizeNick)
209 {
210 m_nick = m_baseNick + Util.RandomClass.Next(1, 99);
211 }
212 m_enabled = true;
213 }
214 }
215 catch (Exception ex)
216 {
217 m_log.Error("[IRCConnector]: Incomplete IRC configuration, skipping IRC bridge configuration");
218 m_log.DebugFormat("[IRCConnector] Incomplete IRC configuration: {0}", ex.ToString());
219 }
220
221 if (null == m_watchdog)
222 {
223 m_watchdog = new Thread(WatchdogRun);
224 m_watchdog.Name = "IRCWatchdog";
225 m_watchdog.IsBackground = true;
226 }
227 }
228
229 public void Start()
230 {
231 if (!m_watchdog.IsAlive)
232 {
233 m_watchdog.Start();
234 ThreadTracker.Add(m_watchdog);
235 }
236 }
237
238 public void AddScene(Scene scene)
239 {
240 lock(m_syncConnect) m_scenes.Add(scene);
241 }
242
243 public bool Connect()
244 {
245 lock (m_syncConnect)
246 {
247 try
248 {
249 if (m_connected) return true;
250
251 m_tcp = new TcpClient(m_server, (int)m_port);
252 m_stream = m_tcp.GetStream();
253 m_reader = new StreamReader(m_stream);
254 m_writer = new StreamWriter(m_stream);
255
256 m_log.DebugFormat("[IRCConnector]: Connected to {0}:{1}", m_server, m_port);
257
258 m_pinger = new Thread(new ThreadStart(PingRun));
259 m_pinger.Name = "PingSenderThread";
260 m_pinger.IsBackground = true;
261 m_pinger.Start();
262 ThreadTracker.Add(m_pinger);
263
264 m_listener = new Thread(new ThreadStart(ListenerRun));
265 m_listener.Name = "IRCConnectorListenerThread";
266 m_listener.IsBackground = true;
267 m_listener.Start();
268 ThreadTracker.Add(m_listener);
269
270 m_writer.WriteLine(m_user);
271 m_writer.Flush();
272 m_writer.WriteLine(String.Format("NICK {0}", m_nick));
273 m_writer.Flush();
274 m_writer.WriteLine(String.Format("JOIN {0}", m_ircChannel));
275 m_writer.Flush();
276 m_log.Info("[IRCConnector]: Connection fully established");
277 m_connected = true;
278 }
279 catch (Exception e)
280 {
281 m_log.ErrorFormat("[IRCConnector] cannot connect to {0}:{1}: {2}",
282 m_server, m_port, e.Message);
283 }
284 m_log.Debug("[IRCConnector] Connected");
285 return m_connected;
286 }
287 }
288
289 public void Reconnect()
290 {
291 m_connected = false;
292 try
293 {
294 m_listener.Abort();
295 m_pinger.Abort();
296
297 m_writer.Close();
298 m_reader.Close();
299
300 m_tcp.Close();
301 }
302 catch (Exception)
303 {
304 }
305
306 if (m_enabled)
307 {
308 Connect();
309 }
310 }
311
312 public void PrivMsg(string from, string region, string msg)
313 {
314 m_log.DebugFormat("[IRCConnector] Sending message to IRC from {0}: {1}", from, msg);
315
316 // One message to the IRC server
317 try
318 {
319 m_writer.WriteLine(m_privmsgformat, m_ircChannel, from, region, msg);
320 m_writer.Flush();
321 m_log.InfoFormat("[IRCConnector]: PrivMsg {0} in {1}: {2}", from, region, msg);
322 }
323 catch (IOException)
324 {
325 m_log.Error("[IRCConnector]: Disconnected from IRC server.(PrivMsg)");
326 Reconnect();
327 }
328 catch (Exception ex)
329 {
330 m_log.ErrorFormat("[IRCConnector]: PrivMsg exception trap: {0}", ex.ToString());
331 }
332 }
333
334 public void Send(string msg)
335 {
336 try
337 {
338 m_writer.WriteLine(msg);
339 m_writer.Flush();
340 m_log.Info("IRC: Sent command string: " + msg);
341 }
342 catch (IOException)
343 {
344 m_log.Error("[IRCConnector]: Disconnected from IRC server.(PrivMsg)");
345 Reconnect();
346 }
347 catch (Exception ex)
348 {
349 m_log.ErrorFormat("[IRCConnector]: PrivMsg exception trap: {0}", ex.ToString());
350 }
351
352 }
353
354
355 private Dictionary<string, string> ExtractMsg(string input)
356 {
357 //examines IRC commands and extracts any private messages
358 // which will then be reboadcast in the Sim
359
360 m_log.Info("[IRCConnector]: ExtractMsg: " + input);
361 Dictionary<string, string> result = null;
362 string regex = @":(?<nick>[\w-]*)!(?<user>\S*) PRIVMSG (?<channel>\S+) :(?<msg>.*)";
363 Regex RE = new Regex(regex, RegexOptions.Multiline);
364 MatchCollection matches = RE.Matches(input);
365
366 // Get some direct matches $1 $4 is a
367 if ((matches.Count == 0) || (matches.Count != 1) || (matches[0].Groups.Count != 5))
368 {
369 // m_log.Info("[IRCConnector]: Number of matches: " + matches.Count);
370 // if (matches.Count > 0)
371 // {
372 // m_log.Info("[IRCConnector]: Number of groups: " + matches[0].Groups.Count);
373 // }
374 return null;
375 }
376
377 result = new Dictionary<string, string>();
378 result.Add("nick", matches[0].Groups[1].Value);
379 result.Add("user", matches[0].Groups[2].Value);
380 result.Add("channel", matches[0].Groups[3].Value);
381 result.Add("msg", matches[0].Groups[4].Value);
382
383 return result;
384 }
385
386 public void PingRun()
387 {
388 // IRC keep alive thread
389 // send PING ever 15 seconds
390 while (m_enabled)
391 {
392 try
393 {
394 if (m_connected == true)
395 {
396 m_writer.WriteLine(String.Format("PING :{0}", m_server));
397 m_writer.Flush();
398 Thread.Sleep(15000);
399 }
400 }
401 catch (IOException)
402 {
403 if (m_enabled)
404 {
405 m_log.Error("[IRCConnector]: Disconnected from IRC server.(PingRun)");
406 Reconnect();
407 }
408 }
409 catch (Exception ex)
410 {
411 m_log.ErrorFormat("[IRCConnector]: PingRun exception trap: {0}\n{1}", ex.ToString(), ex.StackTrace);
412 }
413 }
414 }
415
416 static private Vector3 pos = new Vector3(128, 128, 20);
417 public void ListenerRun()
418 {
419 string inputLine;
420
421 while (m_enabled)
422 {
423 try
424 {
425 while ((m_connected) && ((inputLine = m_reader.ReadLine()) != null))
426 {
427 // m_log.Info("[IRCConnector]: " + inputLine);
428
429 if (inputLine.Contains(m_ircChannel))
430 {
431 Dictionary<string, string> data = ExtractMsg(inputLine);
432 // Any chat ???
433 if (data != null)
434 {
435 OSChatMessage c = new OSChatMessage();
436 c.Message = data["msg"];
437 c.Type = ChatTypeEnum.Region;
438 c.Position = pos;
439 c.Channel = m_relayPrivateChannels ? m_relayChannel : 0;
440 c.From = data["nick"];
441 c.Sender = null;
442 c.SenderUUID = UUID.Zero;
443
444 // is message "\001ACTION foo
445 // bar\001"? -> "/me foo bar"
446 if ((1 == c.Message[0]) && c.Message.Substring(1).StartsWith("ACTION"))
447 c.Message = String.Format("/me {0}", c.Message.Substring(8, c.Message.Length - 9));
448
449 m_log.DebugFormat("[IRCConnector] ListenerRun from: {0}, {1}", c.From, c.Message);
450
451 foreach (Scene scene in m_scenes)
452 {
453 c.Scene = scene;
454 scene.EventManager.TriggerOnChatBroadcast(this, c);
455 }
456 }
457
458 Thread.Sleep(150);
459 continue;
460 }
461
462 ProcessIRCCommand(inputLine);
463 Thread.Sleep(150);
464 }
465 }
466 catch (IOException)
467 {
468 if (m_enabled)
469 {
470 m_log.Error("[IRCConnector]: ListenerRun IOException. Disconnected from IRC server ??? (ListenerRun)");
471 Reconnect();
472 }
473 }
474 catch (Exception ex)
475 {
476 m_log.ErrorFormat("[IRCConnector]: ListenerRun exception trap: {0}\n{1}", ex.ToString(), ex.StackTrace);
477 }
478 }
479 }
480
481 public void BroadcastSim(string sender, string format, params string[] args)
482 {
483 try
484 {
485 OSChatMessage c = new OSChatMessage();
486 c.From = sender;
487 c.Message = String.Format(format, args);
488 c.Type = ChatTypeEnum.Region; // ChatTypeEnum.Say;
489 c.Channel = m_relayPrivateChannels ? m_relayChannel : 0;
490 c.Position = new Vector3(128, 128, 20);
491 c.Sender = null;
492 c.SenderUUID = UUID.Zero;
493
494 m_log.DebugFormat("[IRCConnector] BroadcastSim from {0}: {1}", c.From, c.Message);
495
496 foreach (Scene scene in m_scenes)
497 {
498 c.Scene = scene;
499 scene.EventManager.TriggerOnChatBroadcast(this, c);
500 // // m_scene.EventManager.TriggerOnChatFromWorld(this, c);
501 // IWorldComm wComm = m_scene.RequestModuleInterface<IWorldComm>();
502 // wComm.DeliverMessage(ChatTypeEnum.Region, m_messageInChannel, sender, UUID.Zero, c.Message);
503 // //IWorldComm wComm = m_ScriptEngine.World.RequestModuleInterface<IWorldComm>();
504 // //wComm.DeliverMessage(ChatTypeEnum.Region, channelID, m_host.Name, m_host.UUID, text);
505
506 }
507 }
508 catch (Exception ex) // IRC gate should not crash Sim
509 {
510 m_log.ErrorFormat("[IRCConnector]: BroadcastSim Exception Trap: {0}\n{1}", ex.ToString(), ex.StackTrace);
511 }
512 }
513
514 public void ProcessIRCCommand(string command)
515 {
516 // m_log.Debug("[IRCConnector]: ProcessIRCCommand:" + command);
517
518 string[] commArgs = new string[command.Split(' ').Length];
519 string c_server = m_server;
520
521 commArgs = command.Split(' ');
522 if (commArgs[0].Substring(0, 1) == ":")
523 {
524 commArgs[0] = commArgs[0].Remove(0, 1);
525 }
526
527 if (commArgs[1] == "002")
528 {
529 // fetch the correct servername
530 // ex: irc.freenode.net -> brown.freenode.net/kornbluth.freenode.net/...
531 // irc.bluewin.ch -> irc1.bluewin.ch/irc2.bluewin.ch
532
533 c_server = (commArgs[6].Split('['))[0];
534 m_server = c_server;
535 }
536
537 if (commArgs[0] == "ERROR")
538 {
539 m_log.ErrorFormat("[IRCConnector]: IRC SERVER ERROR: {0}", command);
540 }
541
542 if (commArgs[0] == "PING")
543 {
544 string p_reply = "";
545
546 for (int i = 1; i < commArgs.Length; i++)
547 {
548 p_reply += commArgs[i] + " ";
549 }
550
551 m_writer.WriteLine(String.Format("PONG {0}", p_reply));
552 m_writer.Flush();
553 }
554 else if (commArgs[0] == c_server)
555 {
556 // server message
557 try
558 {
559 Int32 commandCode = Int32.Parse(commArgs[1]);
560 switch (commandCode)
561 {
562 case (int)ErrorReplies.NicknameInUse:
563 // Gen a new name
564 m_nick = m_baseNick + Util.RandomClass.Next(1, 99);
565 m_log.ErrorFormat("[IRCConnector]: IRC SERVER reports NicknameInUse, trying {0}", m_nick);
566 // Retry
567 m_writer.WriteLine(String.Format("NICK {0}", m_nick));
568 m_writer.Flush();
569 m_writer.WriteLine(String.Format("JOIN {0}", m_ircChannel));
570 m_writer.Flush();
571 break;
572 case (int)ErrorReplies.NotRegistered:
573 break;
574 case (int)Replies.EndOfMotd:
575 break;
576 }
577 }
578 catch (Exception)
579 {
580 }
581 }
582 else
583 {
584 // Normal message
585 string commAct = commArgs[1];
586 switch (commAct)
587 {
588 case "JOIN":
589 eventIrcJoin(commArgs);
590 break;
591 case "PART":
592 eventIrcPart(commArgs);
593 break;
594 case "MODE":
595 eventIrcMode(commArgs);
596 break;
597 case "NICK":
598 eventIrcNickChange(commArgs);
599 break;
600 case "KICK":
601 eventIrcKick(commArgs);
602 break;
603 case "QUIT":
604 eventIrcQuit(commArgs);
605 break;
606 case "PONG":
607 break; // that's nice
608 }
609 }
610 }
611
612 public void eventIrcJoin(string[] commArgs)
613 {
614 string IrcChannel = commArgs[2];
615 if (IrcChannel.StartsWith(":"))
616 IrcChannel = IrcChannel.Substring(1);
617 string IrcUser = commArgs[0].Split('!')[0];
618 if (m_clientReporting)
619 BroadcastSim(IrcUser, "/me joins {0}", IrcChannel);
620 }
621
622 public void eventIrcPart(string[] commArgs)
623 {
624 string IrcChannel = commArgs[2];
625 string IrcUser = commArgs[0].Split('!')[0];
626 if (m_clientReporting)
627 BroadcastSim(IrcUser, "/me parts {0}", IrcChannel);
628 }
629
630 public void eventIrcMode(string[] commArgs)
631 {
632 string UserMode = "";
633 for (int i = 3; i < commArgs.Length; i++)
634 {
635 UserMode += commArgs[i] + " ";
636 }
637
638 if (UserMode.Substring(0, 1) == ":")
639 {
640 UserMode = UserMode.Remove(0, 1);
641 }
642 }
643
644 public void eventIrcNickChange(string[] commArgs)
645 {
646 string UserOldNick = commArgs[0].Split('!')[0];
647 string UserNewNick = commArgs[2].Remove(0, 1);
648 if (m_clientReporting)
649 BroadcastSim(UserOldNick, "/me is now known as {0}", UserNewNick);
650 }
651
652 public void eventIrcKick(string[] commArgs)
653 {
654 string UserKicker = commArgs[0].Split('!')[0];
655 string UserKicked = commArgs[3];
656 string IrcChannel = commArgs[2];
657 string KickMessage = "";
658 for (int i = 4; i < commArgs.Length; i++)
659 {
660 KickMessage += commArgs[i] + " ";
661 }
662 if (m_clientReporting)
663 BroadcastSim(UserKicker, "/me kicks kicks {0} off {1} saying \"{2}\"", UserKicked, IrcChannel, KickMessage);
664 if (UserKicked == m_nick)
665 {
666 BroadcastSim(m_nick, "Hey, that was me!!!");
667 }
668 }
669
670 public void eventIrcQuit(string[] commArgs)
671 {
672 string IrcUser = commArgs[0].Split('!')[0];
673 string QuitMessage = "";
674
675 for (int i = 2; i < commArgs.Length; i++)
676 {
677 QuitMessage += commArgs[i] + " ";
678 }
679 if (m_clientReporting)
680 BroadcastSim(IrcUser, "/me quits saying \"{0}\"", QuitMessage);
681 }
682
683 public void Close()
684 {
685 m_writer.WriteLine(String.Format("QUIT :{0} to {1} wormhole to {2} closing",
686 m_nick, m_ircChannel, m_server));
687 m_writer.Flush();
688
689 m_connected = false;
690 m_enabled = false;
691
692 //listener.Abort();
693 //pingSender.Abort();
694
695 m_writer.Close();
696 m_reader.Close();
697 m_stream.Close();
698 m_tcp.Close();
699 }
700
701 protected void WatchdogRun()
702 {
703 while (m_enabled)
704 {
705 if (!m_connected) Connect();
706 Thread.Sleep(15000);
707 }
708 }
709 }
710} \ No newline at end of file