mirror of
https://github.com/opensim/opensim.git
synced 2026-06-27 09:15:49 +08:00
This is to achieve a clean separation of concerns - the watchdog is an inappropriate place for work management. Also adds a WorkManager.RunInThreadPool() class which feeds through to Util.FireAndForget. Also switches around the name and obj arguments to the new RunInThread() and RunJob() methods so that the callback obj comes after the callback as seen in the SDK and elsewhere
914 lines
33 KiB
C#
914 lines
33 KiB
C#
/*
|
|
* Copyright (c) Contributors, http://opensimulator.org/
|
|
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of the OpenSimulator Project nor the
|
|
* names of its contributors may be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
using System;
|
|
using System.Timers;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Net.Sockets;
|
|
using System.Reflection;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using OpenMetaverse;
|
|
using log4net;
|
|
using Nini.Config;
|
|
using OpenSim.Framework;
|
|
using OpenSim.Framework.Monitoring;
|
|
using OpenSim.Region.Framework.Interfaces;
|
|
using OpenSim.Region.Framework.Scenes;
|
|
|
|
namespace OpenSim.Region.OptionalModules.Avatar.Chat
|
|
{
|
|
public class IRCConnector
|
|
{
|
|
|
|
#region Global (static) state
|
|
|
|
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
|
|
|
// Local constants
|
|
|
|
// This computation is not the real region center if the region is larger than 256.
|
|
// This computation isn't fixed because there is not a handle back to the region.
|
|
private static readonly Vector3 CenterOfRegion = new Vector3(((int)Constants.RegionSize * 0.5f), ((int)Constants.RegionSize * 0.5f), 20);
|
|
private static readonly char[] CS_SPACE = { ' ' };
|
|
|
|
private const int WD_INTERVAL = 1000; // base watchdog interval
|
|
private static int PING_PERIOD = 15; // WD intervals per PING
|
|
private static int ICCD_PERIOD = 10; // WD intervals between Connects
|
|
private static int L_TIMEOUT = 25; // Login time out interval
|
|
|
|
private static int _idk_ = 0; // core connector identifier
|
|
private static int _pdk_ = 0; // ping interval counter
|
|
private static int _icc_ = ICCD_PERIOD; // IRC connect counter
|
|
|
|
// List of configured connectors
|
|
|
|
private static List<IRCConnector> m_connectors = new List<IRCConnector>();
|
|
|
|
// Watchdog state
|
|
|
|
private static System.Timers.Timer m_watchdog = null;
|
|
|
|
// The watch-dog gets started as soon as the class is instantiated, and
|
|
// ticks once every second (WD_INTERVAL)
|
|
|
|
static IRCConnector()
|
|
{
|
|
m_log.DebugFormat("[IRC-Connector]: Static initialization started");
|
|
m_watchdog = new System.Timers.Timer(WD_INTERVAL);
|
|
m_watchdog.Elapsed += new ElapsedEventHandler(WatchdogHandler);
|
|
m_watchdog.AutoReset = true;
|
|
m_watchdog.Start();
|
|
m_log.DebugFormat("[IRC-Connector]: Static initialization complete");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Instance state
|
|
|
|
// Connector identity
|
|
|
|
internal int idn = _idk_++;
|
|
|
|
// How many regions depend upon this connection
|
|
// This count is updated by the ChannelState object and reflects the sum
|
|
// of the region clients associated with the set of associated channel
|
|
// state instances. That's why it cannot be managed here.
|
|
|
|
internal int depends = 0;
|
|
|
|
// This variable counts the number of resets that have been performed
|
|
// on the connector. When a listener thread terminates, it checks to
|
|
// see of the reset count has changed before it schedules another
|
|
// reset.
|
|
|
|
internal int m_resetk = 0;
|
|
|
|
private Object msyncConnect = new Object();
|
|
|
|
internal bool m_randomizeNick = true; // add random suffix
|
|
internal string m_baseNick = null; // base name for randomizing
|
|
internal string m_nick = null; // effective nickname
|
|
|
|
public string Nick // Public property
|
|
{
|
|
get { return m_nick; }
|
|
set { m_nick = value; }
|
|
}
|
|
|
|
private bool m_enabled = false; // connector enablement
|
|
public bool Enabled
|
|
{
|
|
get { return m_enabled; }
|
|
}
|
|
|
|
private bool m_connected = false; // connection status
|
|
private bool m_pending = false; // login disposition
|
|
private int m_timeout = L_TIMEOUT; // login timeout counter
|
|
public bool Connected
|
|
{
|
|
get { return m_connected; }
|
|
}
|
|
|
|
private string m_ircChannel; // associated channel id
|
|
public string IrcChannel
|
|
{
|
|
get { return m_ircChannel; }
|
|
set { m_ircChannel = value; }
|
|
}
|
|
|
|
private uint m_port = 6667; // session port
|
|
public uint Port
|
|
{
|
|
get { return m_port; }
|
|
set { m_port = value; }
|
|
}
|
|
|
|
private string m_server = null; // IRC server name
|
|
public string Server
|
|
{
|
|
get { return m_server; }
|
|
set { m_server = value; }
|
|
}
|
|
private string m_password = null;
|
|
public string Password
|
|
{
|
|
get { return m_password; }
|
|
set { m_password = value; }
|
|
}
|
|
|
|
private string m_user = "USER OpenSimBot 8 * :I'm an OpenSim to IRC bot";
|
|
public string User
|
|
{
|
|
get { return m_user; }
|
|
}
|
|
|
|
// Network interface
|
|
|
|
private TcpClient m_tcp;
|
|
private NetworkStream m_stream = null;
|
|
private StreamReader m_reader;
|
|
private StreamWriter m_writer;
|
|
|
|
// Channel characteristic info (if available)
|
|
|
|
internal string usermod = String.Empty;
|
|
internal string chanmod = String.Empty;
|
|
internal string version = String.Empty;
|
|
internal bool motd = false;
|
|
|
|
#endregion
|
|
|
|
#region connector instance management
|
|
|
|
internal IRCConnector(ChannelState cs)
|
|
{
|
|
|
|
// Prepare network interface
|
|
|
|
m_tcp = null;
|
|
m_writer = null;
|
|
m_reader = null;
|
|
|
|
// Setup IRC session parameters
|
|
|
|
m_server = cs.Server;
|
|
m_password = cs.Password;
|
|
m_baseNick = cs.BaseNickname;
|
|
m_randomizeNick = cs.RandomizeNickname;
|
|
m_ircChannel = cs.IrcChannel;
|
|
m_port = cs.Port;
|
|
m_user = cs.User;
|
|
|
|
if (m_watchdog == null)
|
|
{
|
|
// Non-differentiating
|
|
|
|
ICCD_PERIOD = cs.ConnectDelay;
|
|
PING_PERIOD = cs.PingDelay;
|
|
|
|
// Smaller values are not reasonable
|
|
|
|
if (ICCD_PERIOD < 5)
|
|
ICCD_PERIOD = 5;
|
|
|
|
if (PING_PERIOD < 5)
|
|
PING_PERIOD = 5;
|
|
|
|
_icc_ = ICCD_PERIOD; // get started right away!
|
|
|
|
}
|
|
|
|
// The last line of defense
|
|
|
|
if (m_server == null || m_baseNick == null || m_ircChannel == null || m_user == null)
|
|
throw new Exception("Invalid connector configuration");
|
|
|
|
// Generate an initial nickname
|
|
|
|
if (m_randomizeNick)
|
|
m_nick = m_baseNick + Util.RandomClass.Next(1, 99);
|
|
else
|
|
m_nick = m_baseNick;
|
|
|
|
m_log.InfoFormat("[IRC-Connector-{0}]: Initialization complete", idn);
|
|
|
|
}
|
|
|
|
~IRCConnector()
|
|
{
|
|
m_watchdog.Stop();
|
|
Close();
|
|
}
|
|
|
|
// Mark the connector as connectable. Harmless if already enabled.
|
|
|
|
public void Open()
|
|
{
|
|
if (!m_enabled)
|
|
{
|
|
|
|
if (!Connected)
|
|
{
|
|
Connect();
|
|
}
|
|
|
|
lock (m_connectors)
|
|
m_connectors.Add(this);
|
|
|
|
m_enabled = true;
|
|
|
|
}
|
|
}
|
|
|
|
// Only close the connector if the dependency count is zero.
|
|
|
|
public void Close()
|
|
{
|
|
|
|
m_log.InfoFormat("[IRC-Connector-{0}] Closing", idn);
|
|
|
|
lock (msyncConnect)
|
|
{
|
|
|
|
if ((depends == 0) && Enabled)
|
|
{
|
|
|
|
m_enabled = false;
|
|
|
|
if (Connected)
|
|
{
|
|
m_log.DebugFormat("[IRC-Connector-{0}] Closing interface", idn);
|
|
|
|
// Cleanup the IRC session
|
|
|
|
try
|
|
{
|
|
m_writer.WriteLine(String.Format("QUIT :{0} to {1} wormhole to {2} closing",
|
|
m_nick, m_ircChannel, m_server));
|
|
m_writer.Flush();
|
|
}
|
|
catch (Exception) { }
|
|
|
|
|
|
m_connected = false;
|
|
|
|
try { m_writer.Close(); }
|
|
catch (Exception) { }
|
|
try { m_reader.Close(); }
|
|
catch (Exception) { }
|
|
try { m_stream.Close(); }
|
|
catch (Exception) { }
|
|
try { m_tcp.Close(); }
|
|
catch (Exception) { }
|
|
|
|
}
|
|
|
|
lock (m_connectors)
|
|
m_connectors.Remove(this);
|
|
|
|
}
|
|
}
|
|
|
|
m_log.InfoFormat("[IRC-Connector-{0}] Closed", idn);
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region session management
|
|
|
|
// Connect to the IRC server. A connector should always be connected, once enabled
|
|
|
|
public void Connect()
|
|
{
|
|
|
|
if (!m_enabled)
|
|
return;
|
|
|
|
// Delay until next WD cycle if this is too close to the last start attempt
|
|
|
|
while (_icc_ < ICCD_PERIOD)
|
|
return;
|
|
|
|
m_log.DebugFormat("[IRC-Connector-{0}]: Connection request for {1} on {2}:{3}", idn, m_nick, m_server, m_ircChannel);
|
|
|
|
lock (msyncConnect)
|
|
{
|
|
|
|
_icc_ = 0;
|
|
|
|
try
|
|
{
|
|
|
|
if (m_connected) return;
|
|
|
|
m_connected = true;
|
|
m_pending = true;
|
|
m_timeout = L_TIMEOUT;
|
|
|
|
m_tcp = new TcpClient(m_server, (int)m_port);
|
|
m_stream = m_tcp.GetStream();
|
|
m_reader = new StreamReader(m_stream);
|
|
m_writer = new StreamWriter(m_stream);
|
|
|
|
m_log.InfoFormat("[IRC-Connector-{0}]: Connected to {1}:{2}", idn, m_server, m_port);
|
|
|
|
WorkManager.StartThread(ListenerRun, "IRCConnectionListenerThread", ThreadPriority.Normal, true, false);
|
|
|
|
// This is the message order recommended by RFC 2812
|
|
if (m_password != null)
|
|
m_writer.WriteLine(String.Format("PASS {0}", m_password));
|
|
m_writer.WriteLine(String.Format("NICK {0}", m_nick));
|
|
m_writer.Flush();
|
|
m_writer.WriteLine(m_user);
|
|
m_writer.Flush();
|
|
m_writer.WriteLine(String.Format("JOIN {0}", m_ircChannel));
|
|
m_writer.Flush();
|
|
|
|
m_log.InfoFormat("[IRC-Connector-{0}]: {1} has asked to join {2}", idn, m_nick, m_ircChannel);
|
|
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
m_log.ErrorFormat("[IRC-Connector-{0}] cannot connect {1} to {2}:{3}: {4}",
|
|
idn, m_nick, m_server, m_port, e.Message);
|
|
// It might seem reasonable to reset connected and pending status here
|
|
// Seeing as we know that the login has failed, but if we do that, then
|
|
// connection will be retried each time the interconnection interval
|
|
// expires. By leaving them as they are, the connection will be retried
|
|
// when the login timeout expires. Which is preferred.
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Reconnect is used to force a re-cycle of the IRC connection. Should generally
|
|
// be a transparent event
|
|
|
|
public void Reconnect()
|
|
{
|
|
m_log.DebugFormat("[IRC-Connector-{0}]: Reconnect request for {1} on {2}:{3}", idn, m_nick, m_server, m_ircChannel);
|
|
|
|
// Don't do this if a Connect is in progress...
|
|
|
|
lock (msyncConnect)
|
|
{
|
|
|
|
if (m_connected)
|
|
{
|
|
|
|
m_log.InfoFormat("[IRC-Connector-{0}] Resetting connector", idn);
|
|
|
|
// Mark as disconnected. This will allow the listener thread
|
|
// to exit if still in-flight.
|
|
|
|
|
|
// The listener thread is not aborted - it *might* actually be
|
|
// the thread that is running the Reconnect! Instead just close
|
|
// the socket and it will disappear of its own accord, once this
|
|
// processing is completed.
|
|
|
|
try { m_writer.Close(); }
|
|
catch (Exception) { }
|
|
try { m_reader.Close(); }
|
|
catch (Exception) { }
|
|
try { m_tcp.Close(); }
|
|
catch (Exception) { }
|
|
|
|
m_connected = false;
|
|
m_pending = false;
|
|
m_resetk++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Connect();
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Outbound (to-IRC) message handlers
|
|
|
|
public void PrivMsg(string pattern, string from, string region, string msg)
|
|
{
|
|
|
|
// m_log.DebugFormat("[IRC-Connector-{0}] PrivMsg to IRC from {1}: <{2}>", idn, from,
|
|
// String.Format(pattern, m_ircChannel, from, region, msg));
|
|
|
|
// One message to the IRC server
|
|
|
|
try
|
|
{
|
|
m_writer.WriteLine(pattern, m_ircChannel, from, region, msg);
|
|
m_writer.Flush();
|
|
// m_log.DebugFormat("[IRC-Connector-{0}]: PrivMsg from {1} in {2}: {3}", idn, from, region, msg);
|
|
}
|
|
catch (IOException)
|
|
{
|
|
m_log.ErrorFormat("[IRC-Connector-{0}]: PrivMsg I/O Error: disconnected from IRC server", idn);
|
|
Reconnect();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
m_log.ErrorFormat("[IRC-Connector-{0}]: PrivMsg exception : {1}", idn, ex.Message);
|
|
m_log.Debug(ex);
|
|
}
|
|
|
|
}
|
|
|
|
public void Send(string msg)
|
|
{
|
|
|
|
// m_log.DebugFormat("[IRC-Connector-{0}] Send to IRC : <{1}>", idn, msg);
|
|
|
|
try
|
|
{
|
|
m_writer.WriteLine(msg);
|
|
m_writer.Flush();
|
|
// m_log.DebugFormat("[IRC-Connector-{0}] Sent command string: {1}", idn, msg);
|
|
}
|
|
catch (IOException)
|
|
{
|
|
m_log.ErrorFormat("[IRC-Connector-{0}] Disconnected from IRC server.(Send)", idn);
|
|
Reconnect();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
m_log.ErrorFormat("[IRC-Connector-{0}] Send exception trap: {0}", idn, ex.Message);
|
|
m_log.Debug(ex);
|
|
}
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
public void ListenerRun()
|
|
{
|
|
|
|
string inputLine;
|
|
int resetk = m_resetk;
|
|
|
|
try
|
|
{
|
|
while (m_enabled && m_connected)
|
|
{
|
|
if ((inputLine = m_reader.ReadLine()) == null)
|
|
throw new Exception("Listener input socket closed");
|
|
|
|
Watchdog.UpdateThread();
|
|
|
|
// m_log.Info("[IRCConnector]: " + inputLine);
|
|
|
|
if (inputLine.Contains("PRIVMSG"))
|
|
{
|
|
Dictionary<string, string> data = ExtractMsg(inputLine);
|
|
|
|
// Any chat ???
|
|
if (data != null)
|
|
{
|
|
OSChatMessage c = new OSChatMessage();
|
|
c.Message = data["msg"];
|
|
c.Type = ChatTypeEnum.Region;
|
|
c.Position = CenterOfRegion;
|
|
c.From = data["nick"];
|
|
c.Sender = null;
|
|
c.SenderUUID = UUID.Zero;
|
|
|
|
// Is message "\001ACTION foo bar\001"?
|
|
// Then change to: "/me foo bar"
|
|
|
|
if ((1 == c.Message[0]) && c.Message.Substring(1).StartsWith("ACTION"))
|
|
c.Message = String.Format("/me {0}", c.Message.Substring(8, c.Message.Length - 9));
|
|
|
|
ChannelState.OSChat(this, c, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ProcessIRCCommand(inputLine);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception /*e*/)
|
|
{
|
|
// m_log.ErrorFormat("[IRC-Connector-{0}]: ListenerRun exception trap: {1}", idn, e.Message);
|
|
// m_log.Debug(e);
|
|
}
|
|
|
|
// This is potentially circular, but harmless if so.
|
|
// The connection is marked as not connected the first time
|
|
// through reconnect.
|
|
|
|
if (m_enabled && (m_resetk == resetk))
|
|
Reconnect();
|
|
|
|
Watchdog.RemoveThread();
|
|
}
|
|
|
|
private Regex RE = new Regex(@":(?<nick>[\w-]*)!(?<user>\S*) PRIVMSG (?<channel>\S+) :(?<msg>.*)",
|
|
RegexOptions.Multiline);
|
|
|
|
private Dictionary<string, string> ExtractMsg(string input)
|
|
{
|
|
//examines IRC commands and extracts any private messages
|
|
// which will then be reboadcast in the Sim
|
|
|
|
// m_log.InfoFormat("[IRC-Connector-{0}]: ExtractMsg: {1}", idn, input);
|
|
|
|
Dictionary<string, string> result = null;
|
|
MatchCollection matches = RE.Matches(input);
|
|
|
|
// Get some direct matches $1 $4 is a
|
|
if ((matches.Count == 0) || (matches.Count != 1) || (matches[0].Groups.Count != 5))
|
|
{
|
|
// m_log.Info("[IRCConnector]: Number of matches: " + matches.Count);
|
|
// if (matches.Count > 0)
|
|
// {
|
|
// m_log.Info("[IRCConnector]: Number of groups: " + matches[0].Groups.Count);
|
|
// }
|
|
return null;
|
|
}
|
|
|
|
result = new Dictionary<string, string>();
|
|
result.Add("nick", matches[0].Groups[1].Value);
|
|
result.Add("user", matches[0].Groups[2].Value);
|
|
result.Add("channel", matches[0].Groups[3].Value);
|
|
result.Add("msg", matches[0].Groups[4].Value);
|
|
|
|
return result;
|
|
}
|
|
|
|
public void BroadcastSim(string sender, string format, params string[] args)
|
|
{
|
|
try
|
|
{
|
|
OSChatMessage c = new OSChatMessage();
|
|
c.From = sender;
|
|
c.Message = String.Format(format, args);
|
|
c.Type = ChatTypeEnum.Region; // ChatTypeEnum.Say;
|
|
c.Position = CenterOfRegion;
|
|
c.Sender = null;
|
|
c.SenderUUID = UUID.Zero;
|
|
|
|
ChannelState.OSChat(this, c, true);
|
|
|
|
}
|
|
catch (Exception ex) // IRC gate should not crash Sim
|
|
{
|
|
m_log.ErrorFormat("[IRC-Connector-{0}]: BroadcastSim Exception Trap: {1}\n{2}", idn, ex.Message, ex.StackTrace);
|
|
}
|
|
}
|
|
|
|
#region IRC Command Handlers
|
|
|
|
public void ProcessIRCCommand(string command)
|
|
{
|
|
|
|
string[] commArgs;
|
|
string c_server = m_server;
|
|
|
|
string pfx = String.Empty;
|
|
string cmd = String.Empty;
|
|
string parms = String.Empty;
|
|
|
|
// ":" indicates that a prefix is present
|
|
// There are NEVER more than 17 real
|
|
// fields. A parameter that starts with
|
|
// ":" indicates that the remainder of the
|
|
// line is a single parameter value.
|
|
|
|
commArgs = command.Split(CS_SPACE, 2);
|
|
|
|
if (commArgs[0].StartsWith(":"))
|
|
{
|
|
pfx = commArgs[0].Substring(1);
|
|
commArgs = commArgs[1].Split(CS_SPACE, 2);
|
|
}
|
|
|
|
cmd = commArgs[0];
|
|
parms = commArgs[1];
|
|
|
|
// m_log.DebugFormat("[IRC-Connector-{0}] prefix = <{1}> cmd = <{2}>", idn, pfx, cmd);
|
|
|
|
switch (cmd)
|
|
{
|
|
|
|
// Messages 001-004 are always sent
|
|
// following signon.
|
|
|
|
case "001": // Welcome ...
|
|
case "002": // Server information
|
|
case "003": // Welcome ...
|
|
break;
|
|
case "004": // Server information
|
|
m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
|
|
commArgs = parms.Split(CS_SPACE);
|
|
c_server = commArgs[1];
|
|
m_server = c_server;
|
|
version = commArgs[2];
|
|
usermod = commArgs[3];
|
|
chanmod = commArgs[4];
|
|
break;
|
|
case "005": // Server information
|
|
break;
|
|
case "042":
|
|
case "250":
|
|
case "251":
|
|
case "252":
|
|
case "254":
|
|
case "255":
|
|
case "265":
|
|
case "266":
|
|
case "332": // Subject
|
|
case "333": // Subject owner (?)
|
|
case "353": // Name list
|
|
case "366": // End-of-Name list marker
|
|
case "372": // MOTD body
|
|
case "375": // MOTD start
|
|
// m_log.InfoFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE,2)[1]);
|
|
break;
|
|
case "376": // MOTD end
|
|
// m_log.InfoFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE,2)[1]);
|
|
motd = true;
|
|
break;
|
|
case "451": // Not registered
|
|
break;
|
|
case "433": // Nickname in use
|
|
// Gen a new name
|
|
m_nick = m_baseNick + Util.RandomClass.Next(1, 99);
|
|
m_log.ErrorFormat("[IRC-Connector-{0}]: [{1}] IRC SERVER reports NicknameInUse, trying {2}", idn, cmd, m_nick);
|
|
// Retry
|
|
m_writer.WriteLine(String.Format("NICK {0}", m_nick));
|
|
m_writer.Flush();
|
|
m_writer.WriteLine(m_user);
|
|
m_writer.Flush();
|
|
m_writer.WriteLine(String.Format("JOIN {0}", m_ircChannel));
|
|
m_writer.Flush();
|
|
break;
|
|
case "479": // Bad channel name, etc. This will never work, so disable the connection
|
|
m_log.ErrorFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE, 2)[1]);
|
|
m_log.ErrorFormat("[IRC-Connector-{0}] [{1}] Connector disabled", idn, cmd);
|
|
m_enabled = false;
|
|
m_connected = false;
|
|
m_pending = false;
|
|
break;
|
|
case "NOTICE":
|
|
// m_log.WarnFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE,2)[1]);
|
|
break;
|
|
case "ERROR":
|
|
m_log.ErrorFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE, 2)[1]);
|
|
if (parms.Contains("reconnect too fast"))
|
|
ICCD_PERIOD++;
|
|
m_pending = false;
|
|
Reconnect();
|
|
break;
|
|
case "PING":
|
|
m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
|
|
m_writer.WriteLine(String.Format("PONG {0}", parms));
|
|
m_writer.Flush();
|
|
break;
|
|
case "PONG":
|
|
break;
|
|
case "JOIN":
|
|
if (m_pending)
|
|
{
|
|
m_log.InfoFormat("[IRC-Connector-{0}] [{1}] Connected", idn, cmd);
|
|
m_pending = false;
|
|
}
|
|
m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
|
|
eventIrcJoin(pfx, cmd, parms);
|
|
break;
|
|
case "PART":
|
|
m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
|
|
eventIrcPart(pfx, cmd, parms);
|
|
break;
|
|
case "MODE":
|
|
m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
|
|
eventIrcMode(pfx, cmd, parms);
|
|
break;
|
|
case "NICK":
|
|
m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
|
|
eventIrcNickChange(pfx, cmd, parms);
|
|
break;
|
|
case "KICK":
|
|
m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
|
|
eventIrcKick(pfx, cmd, parms);
|
|
break;
|
|
case "QUIT":
|
|
m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
|
|
eventIrcQuit(pfx, cmd, parms);
|
|
break;
|
|
default:
|
|
m_log.DebugFormat("[IRC-Connector-{0}] Command '{1}' ignored, parms = {2}", idn, cmd, parms);
|
|
break;
|
|
}
|
|
|
|
// m_log.DebugFormat("[IRC-Connector-{0}] prefix = <{1}> cmd = <{2}> complete", idn, pfx, cmd);
|
|
|
|
}
|
|
|
|
public void eventIrcJoin(string prefix, string command, string parms)
|
|
{
|
|
string[] args = parms.Split(CS_SPACE, 2);
|
|
string IrcUser = prefix.Split('!')[0];
|
|
string IrcChannel = args[0];
|
|
|
|
if (IrcChannel.StartsWith(":"))
|
|
IrcChannel = IrcChannel.Substring(1);
|
|
|
|
m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCJoin {1}:{2}", idn, m_server, m_ircChannel);
|
|
BroadcastSim(IrcUser, "/me joins {0}", IrcChannel);
|
|
}
|
|
|
|
public void eventIrcPart(string prefix, string command, string parms)
|
|
{
|
|
string[] args = parms.Split(CS_SPACE, 2);
|
|
string IrcUser = prefix.Split('!')[0];
|
|
string IrcChannel = args[0];
|
|
|
|
m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCPart {1}:{2}", idn, m_server, m_ircChannel);
|
|
BroadcastSim(IrcUser, "/me parts {0}", IrcChannel);
|
|
}
|
|
|
|
public void eventIrcMode(string prefix, string command, string parms)
|
|
{
|
|
string[] args = parms.Split(CS_SPACE, 2);
|
|
string UserMode = args[1];
|
|
|
|
m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCMode {1}:{2}", idn, m_server, m_ircChannel);
|
|
if (UserMode.Substring(0, 1) == ":")
|
|
{
|
|
UserMode = UserMode.Remove(0, 1);
|
|
}
|
|
}
|
|
|
|
public void eventIrcNickChange(string prefix, string command, string parms)
|
|
{
|
|
string[] args = parms.Split(CS_SPACE, 2);
|
|
string UserOldNick = prefix.Split('!')[0];
|
|
string UserNewNick = args[0].Remove(0, 1);
|
|
|
|
m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCNickChange {1}:{2}", idn, m_server, m_ircChannel);
|
|
BroadcastSim(UserOldNick, "/me is now known as {0}", UserNewNick);
|
|
}
|
|
|
|
public void eventIrcKick(string prefix, string command, string parms)
|
|
{
|
|
string[] args = parms.Split(CS_SPACE, 3);
|
|
string UserKicker = prefix.Split('!')[0];
|
|
string IrcChannel = args[0];
|
|
string UserKicked = args[1];
|
|
string KickMessage = args[2];
|
|
|
|
m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCKick {1}:{2}", idn, m_server, m_ircChannel);
|
|
BroadcastSim(UserKicker, "/me kicks kicks {0} off {1} saying \"{2}\"", UserKicked, IrcChannel, KickMessage);
|
|
|
|
if (UserKicked == m_nick)
|
|
{
|
|
BroadcastSim(m_nick, "Hey, that was me!!!");
|
|
}
|
|
|
|
}
|
|
|
|
public void eventIrcQuit(string prefix, string command, string parms)
|
|
{
|
|
string IrcUser = prefix.Split('!')[0];
|
|
string QuitMessage = parms;
|
|
|
|
m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCQuit {1}:{2}", idn, m_server, m_ircChannel);
|
|
BroadcastSim(IrcUser, "/me quits saying \"{0}\"", QuitMessage);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Connector Watch Dog
|
|
|
|
// A single watch dog monitors extant connectors and makes sure that they
|
|
// are re-connected as necessary. If a connector IS connected, then it is
|
|
// pinged, but only if a PING period has elapsed.
|
|
|
|
protected static void WatchdogHandler(Object source, ElapsedEventArgs args)
|
|
{
|
|
|
|
// m_log.InfoFormat("[IRC-Watchdog] Status scan, pdk = {0}, icc = {1}", _pdk_, _icc_);
|
|
|
|
_pdk_ = (_pdk_ + 1) % PING_PERIOD; // cycle the ping trigger
|
|
_icc_++; // increment the inter-consecutive-connect-delay counter
|
|
|
|
lock (m_connectors)
|
|
foreach (IRCConnector connector in m_connectors)
|
|
{
|
|
|
|
// m_log.InfoFormat("[IRC-Watchdog] Scanning {0}", connector);
|
|
|
|
if (connector.Enabled)
|
|
{
|
|
if (!connector.Connected)
|
|
{
|
|
try
|
|
{
|
|
// m_log.DebugFormat("[IRC-Watchdog] Connecting {1}:{2}", connector.idn, connector.m_server, connector.m_ircChannel);
|
|
connector.Connect();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
m_log.ErrorFormat("[IRC-Watchdog] Exception on connector {0}: {1} ", connector.idn, e.Message);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
if (connector.m_pending)
|
|
{
|
|
if (connector.m_timeout == 0)
|
|
{
|
|
m_log.ErrorFormat("[IRC-Watchdog] Login timed-out for connector {0}, reconnecting", connector.idn);
|
|
connector.Reconnect();
|
|
}
|
|
else
|
|
connector.m_timeout--;
|
|
}
|
|
|
|
// Being marked connected is not enough to ping. Socket establishment can sometimes take a long
|
|
// time, in which case the watch dog might try to ping the server before the socket has been
|
|
// set up, with nasty side-effects.
|
|
|
|
else if (_pdk_ == 0)
|
|
{
|
|
try
|
|
{
|
|
connector.m_writer.WriteLine(String.Format("PING :{0}", connector.m_server));
|
|
connector.m_writer.Flush();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
m_log.ErrorFormat("[IRC-PingRun] Exception on connector {0}: {1} ", connector.idn, e.Message);
|
|
m_log.Debug(e);
|
|
connector.Reconnect();
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
// m_log.InfoFormat("[IRC-Watchdog] Status scan completed");
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
}
|