451 lines
18 KiB
C#
451 lines
18 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.Collections.Generic;
|
|
using System.Reflection;
|
|
using System.Text.RegularExpressions;
|
|
using log4net;
|
|
using Nini.Config;
|
|
using OpenSim.Framework;
|
|
using OpenSim.Region.Framework.Interfaces;
|
|
using OpenSim.Region.Framework.Scenes;
|
|
|
|
namespace OpenSim.Region.OptionalModules.Avatar.Chat
|
|
{
|
|
// An instance of this class exists for every active region
|
|
|
|
internal class RegionState
|
|
{
|
|
private static readonly ILog m_log =
|
|
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
|
|
|
private static readonly OpenMetaverse.Vector3 CenterOfRegion = new OpenMetaverse.Vector3(((int)Constants.RegionSize * 0.5f), ((int)Constants.RegionSize * 0.5f), 20);
|
|
private const int DEBUG_CHANNEL = 2147483647;
|
|
|
|
private static int _idk_ = 0;
|
|
|
|
// Runtime variables; these values are assigned when the
|
|
// IrcState is created and remain constant thereafter.
|
|
|
|
internal string Region = String.Empty;
|
|
internal string Host = String.Empty;
|
|
internal string LocX = String.Empty;
|
|
internal string LocY = String.Empty;
|
|
internal string IDK = String.Empty;
|
|
|
|
// System values - used only be the IRC classes themselves
|
|
|
|
internal ChannelState cs = null; // associated IRC configuration
|
|
internal Scene scene = null; // associated scene
|
|
internal IConfig config = null; // configuration file reference
|
|
internal bool enabled = true;
|
|
|
|
//AgentAlert
|
|
internal bool showAlert = false;
|
|
internal string alertMessage = String.Empty;
|
|
internal IDialogModule dialogModule = null;
|
|
|
|
// This list is used to keep track of who is here, and by
|
|
// implication, who is not.
|
|
|
|
internal List<IClientAPI> clients = new List<IClientAPI>();
|
|
|
|
// Setup runtime variable values
|
|
|
|
public RegionState(Scene p_scene, IConfig p_config)
|
|
{
|
|
scene = p_scene;
|
|
config = p_config;
|
|
|
|
Region = scene.RegionInfo.RegionName;
|
|
Host = scene.RegionInfo.ExternalHostName;
|
|
LocX = Convert.ToString(scene.RegionInfo.LegacyRegionLocX);
|
|
LocY = Convert.ToString(scene.RegionInfo.LegacyRegionLocY);
|
|
IDK = Convert.ToString(_idk_++);
|
|
|
|
showAlert = config.GetBoolean("alert_show", false);
|
|
string alertServerInfo = String.Empty;
|
|
|
|
if (showAlert)
|
|
{
|
|
bool showAlertServerInfo = config.GetBoolean("alert_show_serverinfo", true);
|
|
|
|
if (showAlertServerInfo)
|
|
alertServerInfo = String.Format("\nServer: {0}\nPort: {1}\nChannel: {2}\n\n",
|
|
config.GetString("server", ""), config.GetString("port", ""), config.GetString("channel", ""));
|
|
|
|
string alertPreMessage = config.GetString("alert_msg_pre", "This region is linked to Irc.");
|
|
string alertPostMessage = config.GetString("alert_msg_post", "Everything you say in public chat can be listened.");
|
|
|
|
alertMessage = String.Format("{0}\n{1}{2}", alertPreMessage, alertServerInfo, alertPostMessage);
|
|
|
|
dialogModule = scene.RequestModuleInterface<IDialogModule>();
|
|
}
|
|
|
|
// OpenChannel conditionally establishes a connection to the
|
|
// IRC server. The request will either succeed, or it will
|
|
// throw an exception.
|
|
|
|
ChannelState.OpenChannel(this, config);
|
|
|
|
// Connect channel to world events
|
|
|
|
scene.EventManager.OnChatFromWorld += OnSimChat;
|
|
scene.EventManager.OnChatFromClient += OnSimChat;
|
|
scene.EventManager.OnMakeRootAgent += OnMakeRootAgent;
|
|
scene.EventManager.OnMakeChildAgent += OnMakeChildAgent;
|
|
|
|
m_log.InfoFormat("[IRC-Region {0}] Initialization complete", Region);
|
|
|
|
}
|
|
|
|
// Auto cleanup when abandoned
|
|
|
|
~RegionState()
|
|
{
|
|
if (cs != null)
|
|
cs.RemoveRegion(this);
|
|
}
|
|
|
|
// Called by PostInitialize after all regions have been created
|
|
|
|
public void Open()
|
|
{
|
|
cs.Open(this);
|
|
enabled = true;
|
|
}
|
|
|
|
// Called by IRCBridgeModule.Close immediately prior to unload
|
|
// of the module for this region. This happens when the region
|
|
// is being removed or the server is terminating. The IRC
|
|
// BridgeModule will remove the region from the region list
|
|
// when control returns.
|
|
|
|
public void Close()
|
|
{
|
|
enabled = false;
|
|
cs.Close(this);
|
|
}
|
|
|
|
// The agent has disconnected, cleanup associated resources
|
|
|
|
private void OnClientLoggedOut(IClientAPI client)
|
|
{
|
|
try
|
|
{
|
|
if (clients.Contains(client))
|
|
{
|
|
if (enabled && (cs.irc.Enabled) && (cs.irc.Connected) && (cs.ClientReporting))
|
|
{
|
|
m_log.InfoFormat("[IRC-Region {0}]: {1} has left", Region, client.Name);
|
|
//Check if this person is excluded from IRC
|
|
if (!cs.ExcludeList.Contains(client.Name.ToLower()))
|
|
{
|
|
cs.irc.PrivMsg(cs.NoticeMessageFormat, cs.irc.Nick, Region, String.Format("{0} has left", client.Name));
|
|
}
|
|
}
|
|
client.OnLogout -= OnClientLoggedOut;
|
|
client.OnConnectionClosed -= OnClientLoggedOut;
|
|
clients.Remove(client);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
m_log.ErrorFormat("[IRC-Region {0}]: ClientLoggedOut exception: {1}", Region, ex.Message);
|
|
m_log.Debug(ex);
|
|
}
|
|
}
|
|
|
|
// This event indicates that the agent has left the building. We should treat that the same
|
|
// as if the agent has logged out (we don't want cross-region noise - or do we?)
|
|
|
|
private void OnMakeChildAgent(ScenePresence presence)
|
|
{
|
|
|
|
IClientAPI client = presence.ControllingClient;
|
|
|
|
try
|
|
{
|
|
if (clients.Contains(client))
|
|
{
|
|
if (enabled && (cs.irc.Enabled) && (cs.irc.Connected) && (cs.ClientReporting))
|
|
{
|
|
string clientName = String.Format("{0} {1}", presence.Firstname, presence.Lastname);
|
|
m_log.DebugFormat("[IRC-Region {0}] {1} has left", Region, clientName);
|
|
cs.irc.PrivMsg(cs.NoticeMessageFormat, cs.irc.Nick, Region, String.Format("{0} has left", clientName));
|
|
}
|
|
client.OnLogout -= OnClientLoggedOut;
|
|
client.OnConnectionClosed -= OnClientLoggedOut;
|
|
clients.Remove(client);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
m_log.ErrorFormat("[IRC-Region {0}]: MakeChildAgent exception: {1}", Region, ex.Message);
|
|
m_log.Debug(ex);
|
|
}
|
|
|
|
}
|
|
|
|
// An agent has entered the region (from another region). Add the client to the locally
|
|
// known clients list
|
|
|
|
private void OnMakeRootAgent(ScenePresence presence)
|
|
{
|
|
IClientAPI client = presence.ControllingClient;
|
|
|
|
try
|
|
{
|
|
if (!clients.Contains(client))
|
|
{
|
|
client.OnLogout += OnClientLoggedOut;
|
|
client.OnConnectionClosed += OnClientLoggedOut;
|
|
clients.Add(client);
|
|
if (enabled && (cs.irc.Enabled) && (cs.irc.Connected) && (cs.ClientReporting))
|
|
{
|
|
string clientName = String.Format("{0} {1}", presence.Firstname, presence.Lastname);
|
|
m_log.DebugFormat("[IRC-Region {0}] {1} has arrived", Region, clientName);
|
|
//Check if this person is excluded from IRC
|
|
if (!cs.ExcludeList.Contains(clientName.ToLower()))
|
|
{
|
|
cs.irc.PrivMsg(cs.NoticeMessageFormat, cs.irc.Nick, Region, String.Format("{0} has arrived", clientName));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dialogModule != null && showAlert)
|
|
dialogModule.SendAlertToUser(client, alertMessage, true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
m_log.ErrorFormat("[IRC-Region {0}]: MakeRootAgent exception: {1}", Region, ex.Message);
|
|
m_log.Debug(ex);
|
|
}
|
|
}
|
|
|
|
// This handler detects chat events int he virtual world.
|
|
public void OnSimChat(Object sender, OSChatMessage msg)
|
|
{
|
|
|
|
// early return if this comes from the IRC forwarder
|
|
|
|
if (cs.irc.Equals(sender)) return;
|
|
|
|
// early return if nothing to forward
|
|
|
|
if (msg.Message.Length == 0) return;
|
|
|
|
// check for commands coming from avatars or in-world
|
|
// object (if commands are enabled)
|
|
|
|
if (cs.CommandsEnabled && msg.Channel == cs.CommandChannel)
|
|
{
|
|
|
|
m_log.DebugFormat("[IRC-Region {0}] command on channel {1}: {2}", Region, msg.Channel, msg.Message);
|
|
|
|
string[] messages = msg.Message.Split(' ');
|
|
string command = messages[0].ToLower();
|
|
|
|
try
|
|
{
|
|
switch (command)
|
|
{
|
|
|
|
// These commands potentially require a change in the
|
|
// underlying ChannelState.
|
|
|
|
case "server":
|
|
cs.Close(this);
|
|
cs = cs.UpdateServer(this, messages[1]);
|
|
cs.Open(this);
|
|
break;
|
|
case "port":
|
|
cs.Close(this);
|
|
cs = cs.UpdatePort(this, messages[1]);
|
|
cs.Open(this);
|
|
break;
|
|
case "channel":
|
|
cs.Close(this);
|
|
cs = cs.UpdateChannel(this, messages[1]);
|
|
cs.Open(this);
|
|
break;
|
|
case "nick":
|
|
cs.Close(this);
|
|
cs = cs.UpdateNickname(this, messages[1]);
|
|
cs.Open(this);
|
|
break;
|
|
|
|
// These may also (but are less likely) to require a
|
|
// change in ChannelState.
|
|
|
|
case "client-reporting":
|
|
cs = cs.UpdateClientReporting(this, messages[1]);
|
|
break;
|
|
case "in-channel":
|
|
cs = cs.UpdateRelayIn(this, messages[1]);
|
|
break;
|
|
case "out-channel":
|
|
cs = cs.UpdateRelayOut(this, messages[1]);
|
|
break;
|
|
|
|
// These are all taken to be temporary changes in state
|
|
// so the underlying connector remains intact. But note
|
|
// that with regions sharing a connector, there could
|
|
// be interference.
|
|
|
|
case "close":
|
|
enabled = false;
|
|
cs.Close(this);
|
|
break;
|
|
|
|
case "connect":
|
|
enabled = true;
|
|
cs.Open(this);
|
|
break;
|
|
|
|
case "reconnect":
|
|
enabled = true;
|
|
cs.Close(this);
|
|
cs.Open(this);
|
|
break;
|
|
|
|
// This one is harmless as far as we can judge from here.
|
|
// If it is not, then the complaints will eventually make
|
|
// that evident.
|
|
|
|
default:
|
|
m_log.DebugFormat("[IRC-Region {0}] Forwarding unrecognized command to IRC : {1}",
|
|
Region, msg.Message);
|
|
cs.irc.Send(msg.Message);
|
|
break;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
m_log.WarnFormat("[IRC-Region {0}] error processing in-world command channel input: {1}",
|
|
Region, ex.Message);
|
|
m_log.Debug(ex);
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// The command channel remains enabled, even if we have otherwise disabled the IRC
|
|
// interface.
|
|
|
|
if (!enabled)
|
|
return;
|
|
|
|
// drop messages unless they are on a valid in-world
|
|
// channel as configured in the ChannelState
|
|
|
|
if (!cs.ValidInWorldChannels.Contains(msg.Channel))
|
|
{
|
|
m_log.DebugFormat("[IRC-Region {0}] dropping message {1} on channel {2}", Region, msg, msg.Channel);
|
|
return;
|
|
}
|
|
|
|
ScenePresence avatar = null;
|
|
string fromName = msg.From;
|
|
|
|
if (msg.Sender != null)
|
|
{
|
|
avatar = scene.GetScenePresence(msg.Sender.AgentId);
|
|
if (avatar != null) fromName = avatar.Name;
|
|
}
|
|
|
|
if (!cs.irc.Connected)
|
|
{
|
|
m_log.WarnFormat("[IRC-Region {0}] IRCConnector not connected: dropping message from {1}", Region, fromName);
|
|
return;
|
|
}
|
|
|
|
m_log.DebugFormat("[IRC-Region {0}] heard on channel {1} : {2}", Region, msg.Channel, msg.Message);
|
|
|
|
if (null != avatar && cs.RelayChat && (msg.Channel == 0 || msg.Channel == DEBUG_CHANNEL))
|
|
{
|
|
string txt = msg.Message;
|
|
if (txt.StartsWith("/me "))
|
|
txt = String.Format("{0} {1}", fromName, msg.Message.Substring(4));
|
|
|
|
cs.irc.PrivMsg(cs.PrivateMessageFormat, fromName, Region, txt);
|
|
return;
|
|
}
|
|
|
|
if (null == avatar && cs.RelayPrivateChannels && null != cs.AccessPassword &&
|
|
msg.Channel == cs.RelayChannelOut)
|
|
{
|
|
Match m = cs.AccessPasswordRegex.Match(msg.Message);
|
|
if (null != m)
|
|
{
|
|
m_log.DebugFormat("[IRC] relaying message from {0}: {1}", m.Groups["avatar"].ToString(),
|
|
m.Groups["message"].ToString());
|
|
cs.irc.PrivMsg(cs.PrivateMessageFormat, m.Groups["avatar"].ToString(),
|
|
scene.RegionInfo.RegionName, m.Groups["message"].ToString());
|
|
}
|
|
}
|
|
}
|
|
|
|
// This method gives the region an opportunity to interfere with
|
|
// message delivery. For now we just enforce the enable/disable
|
|
// flag.
|
|
|
|
internal void OSChat(Object irc, OSChatMessage msg)
|
|
{
|
|
if (enabled)
|
|
{
|
|
// m_log.DebugFormat("[IRC-OSCHAT] Region {0} being sent message", region.Region);
|
|
msg.Scene = scene;
|
|
scene.EventManager.TriggerOnChatBroadcast(irc, msg);
|
|
}
|
|
}
|
|
|
|
// This supports any local message traffic that might be needed in
|
|
// support of command processing. At present there is none.
|
|
|
|
internal void LocalChat(string msg)
|
|
{
|
|
if (enabled)
|
|
{
|
|
OSChatMessage osm = new OSChatMessage();
|
|
osm.From = "IRC Agent";
|
|
osm.Message = msg;
|
|
osm.Type = ChatTypeEnum.Region;
|
|
osm.Position = CenterOfRegion;
|
|
osm.Sender = null;
|
|
osm.SenderUUID = OpenMetaverse.UUID.Zero; // Hmph! Still?
|
|
osm.Channel = 0;
|
|
OSChat(this, osm);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|