diff --git a/OpenSim/Region/Environment/Modules/ChatModule.cs b/OpenSim/Region/Environment/Modules/ChatModule.cs index d6df978f3d..e07c680133 100644 --- a/OpenSim/Region/Environment/Modules/ChatModule.cs +++ b/OpenSim/Region/Environment/Modules/ChatModule.cs @@ -52,6 +52,12 @@ namespace OpenSim.Region.Environment.Modules private IRCChatModule m_irc = null; + private string m_last_new_user = null; + private string m_last_leaving_user = null; + internal object m_syncInit = new object(); + internal object m_syncLogout = new object(); + private Thread m_irc_connector=null; + public ChatModule() { m_log = MainLog.Instance; @@ -59,34 +65,48 @@ namespace OpenSim.Region.Environment.Modules public void Initialise(Scene scene, IConfigSource config) { - // wrap this in a try block so that defaults will work if - // the config file doesn't specify otherwise. - try + lock (m_syncInit) { - m_whisperdistance = config.Configs["Chat"].GetInt("whisper_distance", m_whisperdistance); - m_saydistance = config.Configs["Chat"].GetInt("say_distance", m_saydistance); - m_shoutdistance = config.Configs["Chat"].GetInt("shout_distance", m_shoutdistance); - } - catch (Exception) - { - } + // wrap this in a try block so that defaults will work if + // the config file doesn't specify otherwise. + try + { + m_whisperdistance = config.Configs["Chat"].GetInt("whisper_distance", m_whisperdistance); + m_saydistance = config.Configs["Chat"].GetInt("say_distance", m_saydistance); + m_shoutdistance = config.Configs["Chat"].GetInt("shout_distance", m_shoutdistance); + } + catch (Exception) + { + } - if (!m_scenes.Contains(scene)) - { - m_scenes.Add(scene); - scene.EventManager.OnNewClient += NewClient; - scene.RegisterModuleInterface(this); - } + if (!m_scenes.Contains(scene)) + { + m_scenes.Add(scene); + scene.EventManager.OnNewClient += NewClient; + scene.RegisterModuleInterface(this); + } - // setup IRC Relay - m_irc = new IRCChatModule(config); + // setup IRC Relay + if (m_irc == null) { m_irc = new IRCChatModule(config); } + if (m_irc_connector == null) { m_irc_connector = new Thread(IRCConnectRun); } + + } } public void PostInitialise() { if (m_irc.Enabled) { - m_irc.Connect(m_scenes); + try + { + //m_irc.Connect(m_scenes); + if (m_irc_connector == null) { m_irc_connector = new Thread(IRCConnectRun); } + if (!m_irc_connector.IsAlive) { m_irc_connector.Start(); } + } + catch (Exception ex) + { + } + } } @@ -107,9 +127,60 @@ namespace OpenSim.Region.Environment.Modules public void NewClient(IClientAPI client) { - client.OnChatFromViewer += SimChat; + try + { + client.OnChatFromViewer += SimChat; + + if ((m_irc.Enabled) && (m_irc.Connected)) + { + string clientName = client.FirstName + " " + client.LastName; + // handles simple case. May not work for hundred connecting in per second. + // and the NewClients calles getting interleved + // but filters out multiple reports + if (clientName != m_last_new_user) + { + m_last_new_user = clientName; + string clientRegion = FindClientRegion(client.FirstName, client.LastName); + m_irc.PrivMsg(m_irc.Nick, "Sim", "notices " + clientName + " in "+clientRegion); + } + } + client.OnLogout += ClientLoggedOut; + client.OnConnectionClosed += ClientLoggedOut; + } + catch (Exception ex) + { + m_log.Error("IRC", "NewClient exception trap:" + ex.ToString()); + } } + public void ClientLoggedOut(IClientAPI client) + { + lock (m_syncLogout) + { + try + { + if ((m_irc.Enabled) && (m_irc.Connected)) + { + string clientName = client.FirstName + " " + client.LastName; + string clientRegion = FindClientRegion(client.FirstName, client.LastName); + // handles simple case. May not work for hundred connecting in per second. + // and the NewClients calles getting interleved + // but filters out multiple reports + if (clientName != m_last_leaving_user) + { + m_last_leaving_user = clientName; + m_irc.PrivMsg(m_irc.Nick, "Sim", "notices " + clientName + " left " + clientRegion); + m_log.Verbose("IRC", "IRC watcher notices " + clientName + " left " + clientRegion); + } + } + } + catch (Exception ex) + { + m_log.Error("IRC", "ClientLoggedOut exception trap:" + ex.ToString()); + } + } + + } private void TrySendChatMessage(ScenePresence presence, LLVector3 fromPos, LLVector3 regionPos, LLUUID fromAgentID, string fromName, ChatTypeEnum type, string message) { @@ -133,6 +204,8 @@ namespace OpenSim.Region.Environment.Modules public void SimChat(Object sender, ChatFromViewerArgs e) { + // FROM: Sim TO: IRC + ScenePresence avatar = null; //TODO: Move ForEachScenePresence and others into IScene. @@ -163,6 +236,20 @@ namespace OpenSim.Region.Environment.Modules fromAgentID = e.Sender.AgentId; } + // Try to reconnect to server if not connected + if ((m_irc.Enabled)&&(!m_irc.Connected)) + { + // In a non-blocking way. Eventually the connector will get it started + try + { + if (m_irc_connector == null) { m_irc_connector = new Thread(IRCConnectRun); } + if (!m_irc_connector.IsAlive) { m_irc_connector.Start(); } + } + catch (Exception ex) + { + } + } + if (e.Message.Length > 0) { if (m_irc.Connected && (avatar != null)) // this is to keep objects from talking to IRC @@ -183,6 +270,40 @@ namespace OpenSim.Region.Environment.Modules } } } + + // if IRC is enabled then just keep trying using a monitor thread + public void IRCConnectRun() + { + while(true) + { + if ((m_irc.Enabled)&&(!m_irc.Connected)) + { + m_irc.Connect(m_scenes); + + } + Thread.Sleep(15000); + } + } + + public string FindClientRegion(string client_FirstName,string client_LastName) + { + string sourceRegion = null; + foreach (Scene s in m_scenes) + { + s.ForEachScenePresence(delegate(ScenePresence presence) + { + if ((presence.IsChildAgent==false) + &&(presence.Firstname==client_FirstName) + &&(presence.Lastname==client_LastName)) + { + sourceRegion = presence.Scene.RegionInfo.RegionName; + //sourceRegion= s.RegionInfo.RegionName; + } + }); + if (sourceRegion != null) return sourceRegion; + } + return "Sim"; + } } internal class IRCChatModule @@ -191,6 +312,7 @@ namespace OpenSim.Region.Environment.Modules private uint m_port = 6668; private string m_user = "USER OpenSimBot 8 * :I'm a OpenSim to irc bot"; private string m_nick = null; + private string m_basenick = null; private string m_channel = null; private NetworkStream m_stream; @@ -200,11 +322,13 @@ namespace OpenSim.Region.Environment.Modules private Thread pingSender; private Thread listener; + internal object m_syncConnect = new object(); private bool m_enabled = false; private bool m_connected = false; private List m_scenes = null; + private List m_last_scenes = null; private LogBase m_log; public IRCChatModule(IConfigSource config) @@ -214,15 +338,32 @@ namespace OpenSim.Region.Environment.Modules m_writer = null; m_reader = null; + // configuration in OpenSim.ini + // [IRC] + // server = chat.freenode.net + // nick = OSimBot_mysim + // ;username = USER OpenSimBot 8 * :I'm a OpenSim to irc bot + // ; username is the IRC command line sent + // ; USER * : + // channel = #opensim-regions + // port = 6667 + // + // Traps I/O disconnects so it does not crash the sim + // Trys to reconnect if disconnected and someone says something + // Tells IRC server "QUIT" when doing a close (just to be nice) + // Default port back to 6667 + try { m_server = config.Configs["IRC"].GetString("server"); m_nick = config.Configs["IRC"].GetString("nick"); + m_basenick = m_nick; m_channel = config.Configs["IRC"].GetString("channel"); m_port = (uint) config.Configs["IRC"].GetInt("port", (int) m_port); m_user = config.Configs["IRC"].GetString("username", m_user); if (m_server != null && m_nick != null && m_channel != null) { + m_nick = m_nick + Util.RandomClass.Next(1, 99); m_enabled = true; } } @@ -235,37 +376,42 @@ namespace OpenSim.Region.Environment.Modules public bool Connect(List scenes) { - try + lock (m_syncConnect) { - m_scenes = scenes; + try + { + if (m_connected) return true; + m_scenes = scenes; + if (m_last_scenes == null) { m_last_scenes = scenes; } - m_tcp = new TcpClient(m_server, (int) m_port); - m_log.Verbose("IRC", "Connecting..."); - m_stream = m_tcp.GetStream(); - m_log.Verbose("IRC", "Connected to " + m_server); - m_reader = new StreamReader(m_stream); - m_writer = new StreamWriter(m_stream); + m_tcp = new TcpClient(m_server, (int)m_port); + m_log.Verbose("IRC", "Connecting..."); + m_stream = m_tcp.GetStream(); + m_log.Verbose("IRC", "Connected to " + m_server); + m_reader = new StreamReader(m_stream); + m_writer = new StreamWriter(m_stream); - pingSender = new Thread(new ThreadStart(PingRun)); - pingSender.Start(); + pingSender = new Thread(new ThreadStart(PingRun)); + pingSender.Start(); - listener = new Thread(new ThreadStart(ListenerRun)); - listener.Start(); + listener = new Thread(new ThreadStart(ListenerRun)); + listener.Start(); - m_writer.WriteLine(m_user); - m_writer.Flush(); - m_writer.WriteLine("NICK " + m_nick); - m_writer.Flush(); - m_writer.WriteLine("JOIN " + m_channel); - m_writer.Flush(); - m_log.Verbose("IRC", "Connection fully established"); - m_connected = true; + m_writer.WriteLine(m_user); + m_writer.Flush(); + m_writer.WriteLine("NICK " + m_nick); + m_writer.Flush(); + m_writer.WriteLine("JOIN " + m_channel); + m_writer.Flush(); + m_log.Verbose("IRC", "Connection fully established"); + m_connected = true; + } + catch (Exception e) + { + Console.WriteLine(e.ToString()); + } + return m_connected; } - catch (Exception e) - { - Console.WriteLine(e.ToString()); - } - return m_connected; } public bool Enabled @@ -278,26 +424,53 @@ namespace OpenSim.Region.Environment.Modules get { return m_connected; } } + public string Nick + { + get { return m_nick; } + } + + public void Reconnect() + { + m_connected = false; + listener.Abort(); + pingSender.Abort(); + m_writer.Close(); + m_reader.Close(); + m_tcp.Close(); + if (m_enabled) { Connect(m_last_scenes); } + + } + public void PrivMsg(string from, string region, string msg) { + // One message to the IRC server + try { m_writer.WriteLine("PRIVMSG {0} :<{1} in {2}>: {3}", m_channel, from, region, msg); m_writer.Flush(); + m_log.Verbose("IRC", "PrivMsg " + from + " in " + region + " :" + msg); } catch (IOException) { - m_log.Error("IRC", "Disconnected from IRC server."); - listener.Abort(); - pingSender.Abort(); - m_connected = false; + m_log.Error("IRC", "Disconnected from IRC server.(PrivMsg)"); + Reconnect(); + } + catch (Exception ex) + { + m_log.Error("IRC", "PrivMsg exception trap:" + ex.ToString()); } } private Dictionary ExtractMsg(string input) { + //examines IRC commands and extracts any private messages + // which will then be reboadcast in the Sim + + m_log.Verbose("IRC", "ExtractMsg: " + input); Dictionary result = null; - string regex = @":(?\w*)!~(?\S*) PRIVMSG (?\S+) :(?.*)"; + //string regex = @":(?\w*)!~(?\S*) PRIVMSG (?\S+) :(?.*)"; + string regex = @":(?\w*)!(?\S*) PRIVMSG (?\S+) :(?.*)"; Regex RE = new Regex(regex, RegexOptions.Multiline); MatchCollection matches = RE.Matches(input); // Get some direct matches $1 $4 is a @@ -322,11 +495,28 @@ namespace OpenSim.Region.Environment.Modules public void PingRun() { - while (true) + // IRC keep alive thread + // send PING ever 15 seconds + while (true) { - m_writer.WriteLine("PING :" + m_server); - m_writer.Flush(); - Thread.Sleep(15000); + try + { + if (m_connected == true) + { + m_writer.WriteLine("PING :" + m_server); + m_writer.Flush(); + Thread.Sleep(15000); + } + } + catch (IOException) + { + m_log.Error("IRC", "Disconnected from IRC server.(PingRun)"); + Reconnect(); + } + catch (Exception ex) + { + m_log.Error("IRC", "PingRun exception trap:" + ex.ToString()); + } } } @@ -336,36 +526,262 @@ namespace OpenSim.Region.Environment.Modules LLVector3 pos = new LLVector3(128, 128, 20); while (true) { - while ((inputLine = m_reader.ReadLine()) != null) + while ((m_connected == true) && ((inputLine = m_reader.ReadLine()) != null)) { - // Console.WriteLine(inputLine); - if (inputLine.Contains(m_channel)) + try { - Dictionary data = ExtractMsg(inputLine); - if (data != null) + // Console.WriteLine(inputLine); + if (inputLine.Contains(m_channel)) { - foreach (Scene m_scene in m_scenes) + Dictionary data = ExtractMsg(inputLine); + // Any chat ??? + if (data != null) { - m_scene.ForEachScenePresence(delegate(ScenePresence avatar) - { - if (!avatar.IsChildAgent) + foreach (Scene m_scene in m_scenes) + { + m_scene.ForEachScenePresence(delegate(ScenePresence avatar) { - avatar.ControllingClient.SendChatMessage( - Helpers.StringToField(data["msg"]), 255, - pos, data["nick"], - LLUUID.Zero); - } - }); + if (!avatar.IsChildAgent) + { + avatar.ControllingClient.SendChatMessage( + Helpers.StringToField(data["msg"]), 255, + pos, data["nick"], + LLUUID.Zero); + } + }); + } + + } + else + { + // Was an command from the IRC server + ProcessIRCCommand(inputLine); + } + } + else + { + // Was an command from the IRC server + ProcessIRCCommand(inputLine); } + Thread.Sleep(150); + } + catch (IOException) + { + m_log.Error("IRC", "ListenerRun IOException. Disconnected from IRC server ??? (ListenerRun)"); + Reconnect(); + } + catch (Exception ex) + { + m_log.Error("IRC", "ListenerRun exception trap:" + ex.ToString()); } } - Thread.Sleep(50); } } + public void BroadcastSim(string message,string sender) + { + LLVector3 pos = new LLVector3(128, 128, 20); + try + { + foreach (Scene m_scene in m_scenes) + { + m_scene.ForEachScenePresence(delegate(ScenePresence avatar) + { + if (!avatar.IsChildAgent) + { + avatar.ControllingClient.SendChatMessage( + Helpers.StringToField(message), 255, + pos, sender, + LLUUID.Zero); + } + }); + } + } + catch (Exception ex) // IRC gate should not crash Sim + { + m_log.Error("IRC", "BroadcastSim Exception Trap:" + ex.ToString()); + + } + + + } + public enum ErrorReplies + { + NotRegistered = 451, // ":You have not registered" + NicknameInUse = 433 // " :Nickname is already in use" + } + public enum Replies + { + MotdStart = 375, // ":- Message of the day - " + Motd = 372, // ":- " + EndOfMotd = 376 // ":End of /MOTD command" + + } + public void ProcessIRCCommand(string command) + { + //m_log.Verbose("IRC", "ProcessIRCCommand:"+command); + + string[] commArgs = new string[command.Split(' ').Length]; + string c_server = m_server; + + commArgs = command.Split(' '); + if (commArgs[0].Substring(0, 1) == ":") + { + commArgs[0] = commArgs[0].Remove(0, 1); + } + if (commArgs[1] == "002") + { + // fetch the correct servername + // ex: irc.freenode.net -> brown.freenode.net/kornbluth.freenode.net/... + // irc.bluewin.ch -> irc1.bluewin.ch/irc2.bluewin.ch + + c_server = (commArgs[6].Split('['))[0]; + m_server = c_server; + } + + if (commArgs[0] == "ERROR") + { + m_log.Error("IRC", "IRC SERVER ERROR:" + command); + } + + if (commArgs[0] == "PING") + { + string p_reply = ""; + + for (int i = 1; i < commArgs.Length; i++) + { + p_reply += commArgs[i] + " "; + } + + m_writer.WriteLine("PONG " + p_reply); + m_writer.Flush(); + + } + else if (commArgs[0] == c_server) + { + // server message + try + { + Int32 commandCode = Int32.Parse(commArgs[1]); + switch (commandCode) + { + case (int)ErrorReplies.NicknameInUse: + // Gen a new name + m_nick = m_basenick + Util.RandomClass.Next(1, 99); + m_log.Error("IRC", "IRC SERVER reports NicknameInUse, trying " + m_nick); + // Retry + m_writer.WriteLine("NICK " + m_nick); + m_writer.Flush(); + m_writer.WriteLine("JOIN " + m_channel); + m_writer.Flush(); + break; + case (int)ErrorReplies.NotRegistered: + break; + case (int)Replies.EndOfMotd: + break; + } + } + catch (Exception ex) + { + } + + } + else + { + // Normal message + string commAct = commArgs[1]; + switch (commAct) + { + case "JOIN": eventIrcJoin(commArgs); break; + case "PART": eventIrcPart(commArgs); break; + case "MODE": eventIrcMode(commArgs); break; + case "NICK": eventIrcNickChange(commArgs); break; + case "KICK": eventIrcKick(commArgs); break; + case "QUIT": eventIrcQuit(commArgs); break; + case "PONG": break; // that's nice + } + + + } + } + + public void eventIrcJoin(string[] commArgs) + { + string IrcChannel = commArgs[2]; + string IrcUser = commArgs[0].Split('!')[0]; + BroadcastSim(IrcUser + " is joining " + IrcChannel, m_nick); + + } + + public void eventIrcPart(string[] commArgs) + { + string IrcChannel = commArgs[2]; + string IrcUser = commArgs[0].Split('!')[0]; + BroadcastSim(IrcUser + " is parting " + IrcChannel, m_nick); + + } + public void eventIrcMode(string[] commArgs) + { + string IrcChannel = commArgs[2]; + string IrcUser = commArgs[0].Split('!')[0]; + string UserMode = ""; + for (int i = 3; i < commArgs.Length; i++) + { + UserMode += commArgs[i] + " "; + } + + if (UserMode.Substring(0, 1) == ":") + { + UserMode = UserMode.Remove(0, 1); + } + + } + public void eventIrcNickChange(string[] commArgs) + { + string UserOldNick = commArgs[0].Split('!')[0]; + string UserNewNick = commArgs[2].Remove(0, 1); + BroadcastSim(UserOldNick + " changed their nick to " + UserNewNick, m_nick); + + } + + public void eventIrcKick(string[] commArgs) + { + string UserKicker = commArgs[0].Split('!')[0]; + string UserKicked = commArgs[3]; + string IrcChannel = commArgs[2]; + string KickMessage = ""; + for (int i = 4; i < commArgs.Length; i++) + { + KickMessage += commArgs[i] + " "; + } + BroadcastSim(UserKicker + " kicked " + UserKicked +" on "+IrcChannel+" saying "+KickMessage, m_nick); + if (UserKicked == m_nick) + { + BroadcastSim("Hey, that was me!!!", m_nick); + + } + } + + public void eventIrcQuit(string[] commArgs) + { + string IrcUser = commArgs[0].Split('!')[0]; + string QuitMessage = ""; + + for (int i = 2; i < commArgs.Length; i++) + { + QuitMessage += commArgs[i] + " "; + } + BroadcastSim(IrcUser + " quits saying " + QuitMessage, m_nick); + + } + + public void Close() { + m_connected = false; + m_writer.WriteLine("QUIT :" + m_nick + " to " + m_channel + " wormhole with " + m_server + " closing"); + m_writer.Flush(); listener.Abort(); pingSender.Abort(); m_writer.Close(); @@ -373,4 +789,4 @@ namespace OpenSim.Region.Environment.Modules m_tcp.Close(); } } -} \ No newline at end of file +}