OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
IRCConnector.cs
Go to the documentation of this file.
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 OpenSimulator 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 
28 using System;
29 using System.Timers;
30 using System.Collections.Generic;
31 using System.IO;
32 using System.Net.Sockets;
33 using System.Reflection;
34 using System.Text.RegularExpressions;
35 using System.Threading;
36 using OpenMetaverse;
37 using log4net;
38 using Nini.Config;
39 using OpenSim.Framework;
40 using OpenSim.Framework.Monitoring;
41 using OpenSim.Region.Framework.Interfaces;
42 using OpenSim.Region.Framework.Scenes;
43 
44 namespace OpenSim.Region.OptionalModules.Avatar.Chat
45 {
46  public class IRCConnector
47  {
48 
49  #region Global (static) state
50 
51  private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
52 
53  // Local constants
54 
55  // This computation is not the real region center if the region is larger than 256.
56  // This computation isn't fixed because there is not a handle back to the region.
57  private static readonly Vector3 CenterOfRegion = new Vector3(((int)Constants.RegionSize * 0.5f), ((int)Constants.RegionSize * 0.5f), 20);
58  private static readonly char[] CS_SPACE = { ' ' };
59 
60  private const int WD_INTERVAL = 1000; // base watchdog interval
61  private static int PING_PERIOD = 15; // WD intervals per PING
62  private static int ICCD_PERIOD = 10; // WD intervals between Connects
63  private static int L_TIMEOUT = 25; // Login time out interval
64 
65  private static int _idk_ = 0; // core connector identifier
66  private static int _pdk_ = 0; // ping interval counter
67  private static int _icc_ = ICCD_PERIOD; // IRC connect counter
68 
69  // List of configured connectors
70 
71  private static List<IRCConnector> m_connectors = new List<IRCConnector>();
72 
73  // Watchdog state
74 
75  private static System.Timers.Timer m_watchdog = null;
76 
77  // The watch-dog gets started as soon as the class is instantiated, and
78  // ticks once every second (WD_INTERVAL)
79 
80  static IRCConnector()
81  {
82  m_log.DebugFormat("[IRC-Connector]: Static initialization started");
83  m_watchdog = new System.Timers.Timer(WD_INTERVAL);
84  m_watchdog.Elapsed += new ElapsedEventHandler(WatchdogHandler);
85  m_watchdog.AutoReset = true;
86  m_watchdog.Start();
87  m_log.DebugFormat("[IRC-Connector]: Static initialization complete");
88  }
89 
90  #endregion
91 
92  #region Instance state
93 
94  // Connector identity
95 
96  internal int idn = _idk_++;
97 
98  // How many regions depend upon this connection
99  // This count is updated by the ChannelState object and reflects the sum
100  // of the region clients associated with the set of associated channel
101  // state instances. That's why it cannot be managed here.
102 
103  internal int depends = 0;
104 
105  // This variable counts the number of resets that have been performed
106  // on the connector. When a listener thread terminates, it checks to
107  // see of the reset count has changed before it schedules another
108  // reset.
109 
110  internal int m_resetk = 0;
111 
112  private Object msyncConnect = new Object();
113 
114  internal bool m_randomizeNick = true; // add random suffix
115  internal string m_baseNick = null; // base name for randomizing
116  internal string m_nick = null; // effective nickname
117 
118  public string Nick // Public property
119  {
120  get { return m_nick; }
121  set { m_nick = value; }
122  }
123 
124  private bool m_enabled = false; // connector enablement
125  public bool Enabled
126  {
127  get { return m_enabled; }
128  }
129 
130  private bool m_connected = false; // connection status
131  private bool m_pending = false; // login disposition
132  private int m_timeout = L_TIMEOUT; // login timeout counter
133  public bool Connected
134  {
135  get { return m_connected; }
136  }
137 
138  private string m_ircChannel; // associated channel id
139  public string IrcChannel
140  {
141  get { return m_ircChannel; }
142  set { m_ircChannel = value; }
143  }
144 
145  private uint m_port = 6667; // session port
146  public uint Port
147  {
148  get { return m_port; }
149  set { m_port = value; }
150  }
151 
152  private string m_server = null; // IRC server name
153  public string Server
154  {
155  get { return m_server; }
156  set { m_server = value; }
157  }
158  private string m_password = null;
159  public string Password
160  {
161  get { return m_password; }
162  set { m_password = value; }
163  }
164 
165  private string m_user = "USER OpenSimBot 8 * :I'm an OpenSim to IRC bot";
166  public string User
167  {
168  get { return m_user; }
169  }
170 
171  // Network interface
172 
173  private TcpClient m_tcp;
174  private NetworkStream m_stream = null;
175  private StreamReader m_reader;
176  private StreamWriter m_writer;
177 
178  // Channel characteristic info (if available)
179 
180  internal string usermod = String.Empty;
181  internal string chanmod = String.Empty;
182  internal string version = String.Empty;
183  internal bool motd = false;
184 
185  #endregion
186 
187  #region connector instance management
188 
189  internal IRCConnector(ChannelState cs)
190  {
191 
192  // Prepare network interface
193 
194  m_tcp = null;
195  m_writer = null;
196  m_reader = null;
197 
198  // Setup IRC session parameters
199 
200  m_server = cs.Server;
201  m_password = cs.Password;
202  m_baseNick = cs.BaseNickname;
203  m_randomizeNick = cs.RandomizeNickname;
204  m_ircChannel = cs.IrcChannel;
205  m_port = cs.Port;
206  m_user = cs.User;
207 
208  if (m_watchdog == null)
209  {
210  // Non-differentiating
211 
212  ICCD_PERIOD = cs.ConnectDelay;
213  PING_PERIOD = cs.PingDelay;
214 
215  // Smaller values are not reasonable
216 
217  if (ICCD_PERIOD < 5)
218  ICCD_PERIOD = 5;
219 
220  if (PING_PERIOD < 5)
221  PING_PERIOD = 5;
222 
223  _icc_ = ICCD_PERIOD; // get started right away!
224 
225  }
226 
227  // The last line of defense
228 
229  if (m_server == null || m_baseNick == null || m_ircChannel == null || m_user == null)
230  throw new Exception("Invalid connector configuration");
231 
232  // Generate an initial nickname
233 
234  if (m_randomizeNick)
235  m_nick = m_baseNick + Util.RandomClass.Next(1, 99);
236  else
237  m_nick = m_baseNick;
238 
239  m_log.InfoFormat("[IRC-Connector-{0}]: Initialization complete", idn);
240 
241  }
242 
243  ~IRCConnector()
244  {
245  m_watchdog.Stop();
246  Close();
247  }
248 
249  // Mark the connector as connectable. Harmless if already enabled.
250 
251  public void Open()
252  {
253  if (!m_enabled)
254  {
255 
256  if (!Connected)
257  {
258  Connect();
259  }
260 
261  lock (m_connectors)
262  m_connectors.Add(this);
263 
264  m_enabled = true;
265 
266  }
267  }
268 
269  // Only close the connector if the dependency count is zero.
270 
271  public void Close()
272  {
273 
274  m_log.InfoFormat("[IRC-Connector-{0}] Closing", idn);
275 
276  lock (msyncConnect)
277  {
278 
279  if ((depends == 0) && Enabled)
280  {
281 
282  m_enabled = false;
283 
284  if (Connected)
285  {
286  m_log.DebugFormat("[IRC-Connector-{0}] Closing interface", idn);
287 
288  // Cleanup the IRC session
289 
290  try
291  {
292  m_writer.WriteLine(String.Format("QUIT :{0} to {1} wormhole to {2} closing",
293  m_nick, m_ircChannel, m_server));
294  m_writer.Flush();
295  }
296  catch (Exception) { }
297 
298 
299  m_connected = false;
300 
301  try { m_writer.Close(); }
302  catch (Exception) { }
303  try { m_reader.Close(); }
304  catch (Exception) { }
305  try { m_stream.Close(); }
306  catch (Exception) { }
307  try { m_tcp.Close(); }
308  catch (Exception) { }
309 
310  }
311 
312  lock (m_connectors)
313  m_connectors.Remove(this);
314 
315  }
316  }
317 
318  m_log.InfoFormat("[IRC-Connector-{0}] Closed", idn);
319 
320  }
321 
322  #endregion
323 
324  #region session management
325 
326  // Connect to the IRC server. A connector should always be connected, once enabled
327 
328  public void Connect()
329  {
330 
331  if (!m_enabled)
332  return;
333 
334  // Delay until next WD cycle if this is too close to the last start attempt
335 
336  while (_icc_ < ICCD_PERIOD)
337  return;
338 
339  m_log.DebugFormat("[IRC-Connector-{0}]: Connection request for {1} on {2}:{3}", idn, m_nick, m_server, m_ircChannel);
340 
341  lock (msyncConnect)
342  {
343 
344  _icc_ = 0;
345 
346  try
347  {
348 
349  if (m_connected) return;
350 
351  m_connected = true;
352  m_pending = true;
353  m_timeout = L_TIMEOUT;
354 
355  m_tcp = new TcpClient(m_server, (int)m_port);
356  m_stream = m_tcp.GetStream();
357  m_reader = new StreamReader(m_stream);
358  m_writer = new StreamWriter(m_stream);
359 
360  m_log.InfoFormat("[IRC-Connector-{0}]: Connected to {1}:{2}", idn, m_server, m_port);
361 
362  WorkManager.StartThread(ListenerRun, "IRCConnectionListenerThread", ThreadPriority.Normal, true, false);
363 
364  // This is the message order recommended by RFC 2812
365  if (m_password != null)
366  m_writer.WriteLine(String.Format("PASS {0}", m_password));
367  m_writer.WriteLine(String.Format("NICK {0}", m_nick));
368  m_writer.Flush();
369  m_writer.WriteLine(m_user);
370  m_writer.Flush();
371  }
372  catch (Exception e)
373  {
374  m_log.ErrorFormat("[IRC-Connector-{0}] cannot connect {1} to {2}:{3}: {4}",
375  idn, m_nick, m_server, m_port, e.Message);
376  // It might seem reasonable to reset connected and pending status here
377  // Seeing as we know that the login has failed, but if we do that, then
378  // connection will be retried each time the interconnection interval
379  // expires. By leaving them as they are, the connection will be retried
380  // when the login timeout expires. Which is preferred.
381  }
382 
383  }
384 
385  return;
386 
387  }
388 
389  // Reconnect is used to force a re-cycle of the IRC connection. Should generally
390  // be a transparent event
391 
392  public void Reconnect()
393  {
394  m_log.DebugFormat("[IRC-Connector-{0}]: Reconnect request for {1} on {2}:{3}", idn, m_nick, m_server, m_ircChannel);
395 
396  // Don't do this if a Connect is in progress...
397 
398  lock (msyncConnect)
399  {
400 
401  if (m_connected)
402  {
403 
404  m_log.InfoFormat("[IRC-Connector-{0}] Resetting connector", idn);
405 
406  // Mark as disconnected. This will allow the listener thread
407  // to exit if still in-flight.
408 
409 
410  // The listener thread is not aborted - it *might* actually be
411  // the thread that is running the Reconnect! Instead just close
412  // the socket and it will disappear of its own accord, once this
413  // processing is completed.
414 
415  try { m_writer.Close(); }
416  catch (Exception) { }
417  try { m_reader.Close(); }
418  catch (Exception) { }
419  try { m_tcp.Close(); }
420  catch (Exception) { }
421 
422  m_connected = false;
423  m_pending = false;
424  m_resetk++;
425 
426  }
427 
428  }
429 
430  Connect();
431 
432  }
433 
434  #endregion
435 
436  #region Outbound (to-IRC) message handlers
437 
438  public void PrivMsg(string pattern, string from, string region, string msg)
439  {
440 
441  // m_log.DebugFormat("[IRC-Connector-{0}] PrivMsg to IRC from {1}: <{2}>", idn, from,
442  // String.Format(pattern, m_ircChannel, from, region, msg));
443 
444  // One message to the IRC server
445 
446  try
447  {
448  m_writer.WriteLine(pattern, m_ircChannel, from, region, msg);
449  m_writer.Flush();
450  // m_log.DebugFormat("[IRC-Connector-{0}]: PrivMsg from {1} in {2}: {3}", idn, from, region, msg);
451  }
452  catch (IOException)
453  {
454  m_log.ErrorFormat("[IRC-Connector-{0}]: PrivMsg I/O Error: disconnected from IRC server", idn);
455  Reconnect();
456  }
457  catch (Exception ex)
458  {
459  m_log.ErrorFormat("[IRC-Connector-{0}]: PrivMsg exception : {1}", idn, ex.Message);
460  m_log.Debug(ex);
461  }
462 
463  }
464 
465  public void Send(string msg)
466  {
467 
468  // m_log.DebugFormat("[IRC-Connector-{0}] Send to IRC : <{1}>", idn, msg);
469 
470  try
471  {
472  m_writer.WriteLine(msg);
473  m_writer.Flush();
474  // m_log.DebugFormat("[IRC-Connector-{0}] Sent command string: {1}", idn, msg);
475  }
476  catch (IOException)
477  {
478  m_log.ErrorFormat("[IRC-Connector-{0}] Disconnected from IRC server.(Send)", idn);
479  Reconnect();
480  }
481  catch (Exception ex)
482  {
483  m_log.ErrorFormat("[IRC-Connector-{0}] Send exception trap: {0}", idn, ex.Message);
484  m_log.Debug(ex);
485  }
486 
487  }
488 
489  #endregion
490 
491  public void ListenerRun()
492  {
493 
494  string inputLine;
495  int resetk = m_resetk;
496 
497  try
498  {
499  while (m_enabled && m_connected)
500  {
501  if ((inputLine = m_reader.ReadLine()) == null)
502  throw new Exception("Listener input socket closed");
503 
504  Watchdog.UpdateThread();
505 
506  // m_log.Info("[IRCConnector]: " + inputLine);
507 
508  if (inputLine.Contains("PRIVMSG"))
509  {
510  Dictionary<string, string> data = ExtractMsg(inputLine);
511 
512  // Any chat ???
513  if (data != null)
514  {
515  OSChatMessage c = new OSChatMessage();
516  c.Message = data["msg"];
517  c.Type = ChatTypeEnum.Region;
518  c.Position = CenterOfRegion;
519  c.From = data["nick"] + "@IRC";
520  c.Sender = null;
521  c.SenderUUID = UUID.Zero;
522 
523  // Is message "\001ACTION foo bar\001"?
524  // Then change to: "/me foo bar"
525 
526  if ((1 == c.Message[0]) && c.Message.Substring(1).StartsWith("ACTION"))
527  c.Message = String.Format("/me {0}", c.Message.Substring(8, c.Message.Length - 9));
528 
529  ChannelState.OSChat(this, c, false);
530  }
531  }
532  else
533  {
534  ProcessIRCCommand(inputLine);
535  }
536  }
537  }
538  catch (Exception /*e*/)
539  {
540  // m_log.ErrorFormat("[IRC-Connector-{0}]: ListenerRun exception trap: {1}", idn, e.Message);
541  // m_log.Debug(e);
542  }
543 
544  // This is potentially circular, but harmless if so.
545  // The connection is marked as not connected the first time
546  // through reconnect.
547 
548  if (m_enabled && (m_resetk == resetk))
549  Reconnect();
550 
551  Watchdog.RemoveThread();
552  }
553 
554  private Regex RE = new Regex(@":(?<nick>[\w-]*)!(?<user>\S*) PRIVMSG (?<channel>\S+) :(?<msg>.*)",
555  RegexOptions.Multiline);
556 
557  private Dictionary<string, string> ExtractMsg(string input)
558  {
559  //examines IRC commands and extracts any private messages
560  // which will then be reboadcast in the Sim
561 
562  // m_log.InfoFormat("[IRC-Connector-{0}]: ExtractMsg: {1}", idn, input);
563 
564  Dictionary<string, string> result = null;
565  MatchCollection matches = RE.Matches(input);
566 
567  // Get some direct matches $1 $4 is a
568  if ((matches.Count == 0) || (matches.Count != 1) || (matches[0].Groups.Count != 5))
569  {
570  // m_log.Info("[IRCConnector]: Number of matches: " + matches.Count);
571  // if (matches.Count > 0)
572  // {
573  // m_log.Info("[IRCConnector]: Number of groups: " + matches[0].Groups.Count);
574  // }
575  return null;
576  }
577 
578  result = new Dictionary<string, string>();
579  result.Add("nick", matches[0].Groups[1].Value);
580  result.Add("user", matches[0].Groups[2].Value);
581  result.Add("channel", matches[0].Groups[3].Value);
582  result.Add("msg", matches[0].Groups[4].Value);
583 
584  return result;
585  }
586 
587  public void BroadcastSim(string sender, string format, params string[] args)
588  {
589  try
590  {
591  OSChatMessage c = new OSChatMessage();
592  c.From = sender;
593  c.Message = String.Format(format, args);
594  c.Type = ChatTypeEnum.Region; // ChatTypeEnum.Say;
595  c.Position = CenterOfRegion;
596  c.Sender = null;
597  c.SenderUUID = UUID.Zero;
598 
599  ChannelState.OSChat(this, c, true);
600 
601  }
602  catch (Exception ex) // IRC gate should not crash Sim
603  {
604  m_log.ErrorFormat("[IRC-Connector-{0}]: BroadcastSim Exception Trap: {1}\n{2}", idn, ex.Message, ex.StackTrace);
605  }
606  }
607 
608  #region IRC Command Handlers
609 
610  public void ProcessIRCCommand(string command)
611  {
612 
613  string[] commArgs;
614  string c_server = m_server;
615 
616  string pfx = String.Empty;
617  string cmd = String.Empty;
618  string parms = String.Empty;
619 
620  // ":" indicates that a prefix is present
621  // There are NEVER more than 17 real
622  // fields. A parameter that starts with
623  // ":" indicates that the remainder of the
624  // line is a single parameter value.
625 
626  commArgs = command.Split(CS_SPACE, 2);
627 
628  if (commArgs[0].StartsWith(":"))
629  {
630  pfx = commArgs[0].Substring(1);
631  commArgs = commArgs[1].Split(CS_SPACE, 2);
632  }
633 
634  cmd = commArgs[0];
635  parms = commArgs[1];
636 
637  // m_log.DebugFormat("[IRC-Connector-{0}] prefix = <{1}> cmd = <{2}>", idn, pfx, cmd);
638 
639  switch (cmd)
640  {
641 
642  // Messages 001-004 are always sent
643  // following signon.
644 
645  case "001": // Welcome ...
646  case "002": // Server information
647  case "003": // Welcome ...
648  break;
649  case "004": // Server information
650  m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
651  commArgs = parms.Split(CS_SPACE);
652  c_server = commArgs[1];
653  m_server = c_server;
654  version = commArgs[2];
655  usermod = commArgs[3];
656  chanmod = commArgs[4];
657 
658  m_writer.WriteLine(String.Format("JOIN {0}", m_ircChannel));
659  m_writer.Flush();
660  m_log.InfoFormat("[IRC-Connector-{0}]: sent request to join {1} ", idn, m_ircChannel);
661 
662  break;
663  case "005": // Server information
664  break;
665  case "042":
666  case "250":
667  case "251":
668  case "252":
669  case "254":
670  case "255":
671  case "265":
672  case "266":
673  case "332": // Subject
674  case "333": // Subject owner (?)
675  case "353": // Name list
676  case "366": // End-of-Name list marker
677  case "372": // MOTD body
678  case "375": // MOTD start
679  // m_log.InfoFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE,2)[1]);
680  break;
681  case "376": // MOTD end
682  // m_log.InfoFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE,2)[1]);
683  motd = true;
684  break;
685  case "451": // Not registered
686  break;
687  case "433": // Nickname in use
688  // Gen a new name
689  m_nick = m_baseNick + Util.RandomClass.Next(1, 99);
690  m_log.ErrorFormat("[IRC-Connector-{0}]: [{1}] IRC SERVER reports NicknameInUse, trying {2}", idn, cmd, m_nick);
691  // Retry
692  m_writer.WriteLine(String.Format("NICK {0}", m_nick));
693  m_writer.Flush();
694  m_writer.WriteLine(m_user);
695  m_writer.Flush();
696  m_writer.WriteLine(String.Format("JOIN {0}", m_ircChannel));
697  m_writer.Flush();
698  break;
699  case "479": // Bad channel name, etc. This will never work, so disable the connection
700  m_log.ErrorFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE, 2)[1]);
701  m_log.ErrorFormat("[IRC-Connector-{0}] [{1}] Connector disabled", idn, cmd);
702  m_enabled = false;
703  m_connected = false;
704  m_pending = false;
705  break;
706  case "NOTICE":
707  // m_log.WarnFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE,2)[1]);
708  break;
709  case "ERROR":
710  m_log.ErrorFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE, 2)[1]);
711  if (parms.Contains("reconnect too fast"))
712  ICCD_PERIOD++;
713  m_pending = false;
714  Reconnect();
715  break;
716  case "PING":
717  m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
718  m_writer.WriteLine(String.Format("PONG {0}", parms));
719  m_writer.Flush();
720  break;
721  case "PONG":
722  break;
723  case "JOIN":
724 
725  m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
726  eventIrcJoin(pfx, cmd, parms);
727  break;
728  case "PART":
729  m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
730  eventIrcPart(pfx, cmd, parms);
731  break;
732  case "MODE":
733  m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
734  eventIrcMode(pfx, cmd, parms);
735  break;
736  case "NICK":
737  m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
738  eventIrcNickChange(pfx, cmd, parms);
739  break;
740  case "KICK":
741  m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
742  eventIrcKick(pfx, cmd, parms);
743  break;
744  case "QUIT":
745  m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
746  eventIrcQuit(pfx, cmd, parms);
747  break;
748  default:
749  m_log.DebugFormat("[IRC-Connector-{0}] Command '{1}' ignored, parms = {2}", idn, cmd, parms);
750  break;
751  }
752 
753  // m_log.DebugFormat("[IRC-Connector-{0}] prefix = <{1}> cmd = <{2}> complete", idn, pfx, cmd);
754 
755  }
756 
757  public void eventIrcJoin(string prefix, string command, string parms)
758  {
759  string[] args = parms.Split(CS_SPACE, 2);
760  string IrcUser = prefix.Split('!')[0];
761  string IrcChannel = args[0];
762 
763  if (IrcChannel.StartsWith(":"))
764  IrcChannel = IrcChannel.Substring(1);
765 
766  if(IrcChannel == m_ircChannel)
767  {
768  m_log.InfoFormat("[IRC-Connector-{0}] Joined requested channel {1} at {2}", idn, IrcChannel,m_server);
769  m_pending = false;
770  }
771  else
772  m_log.InfoFormat("[IRC-Connector-{0}] Joined unknown channel {1} at {2}", idn, IrcChannel,m_server);
773  BroadcastSim(IrcUser, "/me joins {0}", IrcChannel);
774  }
775 
776  public void eventIrcPart(string prefix, string command, string parms)
777  {
778  string[] args = parms.Split(CS_SPACE, 2);
779  string IrcUser = prefix.Split('!')[0];
780  string IrcChannel = args[0];
781 
782  m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCPart {1}:{2}", idn, m_server, m_ircChannel);
783  BroadcastSim(IrcUser, "/me parts {0}", IrcChannel);
784  }
785 
786  public void eventIrcMode(string prefix, string command, string parms)
787  {
788  string[] args = parms.Split(CS_SPACE, 2);
789  string UserMode = args[1];
790 
791  m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCMode {1}:{2}", idn, m_server, m_ircChannel);
792  if (UserMode.Substring(0, 1) == ":")
793  {
794  UserMode = UserMode.Remove(0, 1);
795  }
796  }
797 
798  public void eventIrcNickChange(string prefix, string command, string parms)
799  {
800  string[] args = parms.Split(CS_SPACE, 2);
801  string UserOldNick = prefix.Split('!')[0];
802  string UserNewNick = args[0].Remove(0, 1);
803 
804  m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCNickChange {1}:{2}", idn, m_server, m_ircChannel);
805  BroadcastSim(UserOldNick, "/me is now known as {0}", UserNewNick);
806  }
807 
808  public void eventIrcKick(string prefix, string command, string parms)
809  {
810  string[] args = parms.Split(CS_SPACE, 3);
811  string UserKicker = prefix.Split('!')[0];
812  string IrcChannel = args[0];
813  string UserKicked = args[1];
814  string KickMessage = args[2];
815 
816  m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCKick {1}:{2}", idn, m_server, m_ircChannel);
817  BroadcastSim(UserKicker, "/me kicks kicks {0} off {1} saying \"{2}\"", UserKicked, IrcChannel, KickMessage);
818 
819  if (UserKicked == m_nick)
820  {
821  BroadcastSim(m_nick, "Hey, that was me!!!");
822  }
823 
824  }
825 
826  public void eventIrcQuit(string prefix, string command, string parms)
827  {
828  string IrcUser = prefix.Split('!')[0];
829  string QuitMessage = parms;
830 
831  m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCQuit {1}:{2}", idn, m_server, m_ircChannel);
832  BroadcastSim(IrcUser, "/me quits saying \"{0}\"", QuitMessage);
833  }
834 
835  #endregion
836 
837  #region Connector Watch Dog
838 
839  // A single watch dog monitors extant connectors and makes sure that they
840  // are re-connected as necessary. If a connector IS connected, then it is
841  // pinged, but only if a PING period has elapsed.
842 
843  protected static void WatchdogHandler(Object source, ElapsedEventArgs args)
844  {
845 
846  // m_log.InfoFormat("[IRC-Watchdog] Status scan, pdk = {0}, icc = {1}", _pdk_, _icc_);
847 
848  _pdk_ = (_pdk_ + 1) % PING_PERIOD; // cycle the ping trigger
849  _icc_++; // increment the inter-consecutive-connect-delay counter
850 
851  lock (m_connectors)
852  foreach (IRCConnector connector in m_connectors)
853  {
854 
855  // m_log.InfoFormat("[IRC-Watchdog] Scanning {0}", connector);
856 
857  if (connector.Enabled)
858  {
859  if (!connector.Connected)
860  {
861  try
862  {
863  // m_log.DebugFormat("[IRC-Watchdog] Connecting {1}:{2}", connector.idn, connector.m_server, connector.m_ircChannel);
864  connector.Connect();
865  }
866  catch (Exception e)
867  {
868  m_log.ErrorFormat("[IRC-Watchdog] Exception on connector {0}: {1} ", connector.idn, e.Message);
869  }
870  }
871  else
872  {
873 
874  if (connector.m_pending)
875  {
876  if (connector.m_timeout == 0)
877  {
878  m_log.ErrorFormat("[IRC-Watchdog] Login timed-out for connector {0}, reconnecting", connector.idn);
879  connector.Reconnect();
880  }
881  else
882  connector.m_timeout--;
883  }
884 
885  // Being marked connected is not enough to ping. Socket establishment can sometimes take a long
886  // time, in which case the watch dog might try to ping the server before the socket has been
887  // set up, with nasty side-effects.
888 
889  else if (_pdk_ == 0)
890  {
891  try
892  {
893  connector.m_writer.WriteLine(String.Format("PING :{0}", connector.m_server));
894  connector.m_writer.Flush();
895  }
896  catch (Exception e)
897  {
898  m_log.ErrorFormat("[IRC-PingRun] Exception on connector {0}: {1} ", connector.idn, e.Message);
899  m_log.Debug(e);
900  connector.Reconnect();
901  }
902  }
903 
904  }
905  }
906  }
907 
908  // m_log.InfoFormat("[IRC-Watchdog] Status scan completed");
909 
910  }
911 
912  #endregion
913 
914  }
915 }
void PrivMsg(string pattern, string from, string region, string msg)
void eventIrcKick(string prefix, string command, string parms)
static void WatchdogHandler(Object source, ElapsedEventArgs args)
void eventIrcQuit(string prefix, string command, string parms)
string Message
The message sent by the user
void eventIrcJoin(string prefix, string command, string parms)
void eventIrcMode(string prefix, string command, string parms)
void eventIrcPart(string prefix, string command, string parms)
ChatFromViewer Arguments
void BroadcastSim(string sender, string format, params string[] args)
void eventIrcNickChange(string prefix, string command, string parms)