From: Alan Webb (alan_webb@us.ibm.com)
XIRCBrigeModule is transient, will merge it with IRCBridgeModule: extends/refactors IRCBridgeModule to support channel-per-region (if desired).0.6.0-stable
parent
3a6037b421
commit
b222d11b12
|
@ -0,0 +1,570 @@
|
|||
/*
|
||||
* 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 OpenSim 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.Environment.Interfaces;
|
||||
using OpenSim.Region.Environment.Scenes;
|
||||
|
||||
namespace OpenSim.Region.Environment.Modules.Avatar.Chat
|
||||
{
|
||||
|
||||
// An instance of this class exists for each unique combination of
|
||||
// IRC chat interface characteristics, as determined by the supplied
|
||||
// configuration file.
|
||||
|
||||
internal class ChannelState
|
||||
{
|
||||
|
||||
private static readonly ILog m_log =
|
||||
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
private static Regex arg = new Regex(@"\[[^\[\]]*\]");
|
||||
private static int _idk_ = 0;
|
||||
|
||||
// These are the IRC Connector configurable parameters with hard-wired
|
||||
// default values (retained for compatability).
|
||||
|
||||
internal string Server = null;
|
||||
internal string IrcChannel = null;
|
||||
internal string BaseNickname = "OSimBot";
|
||||
internal uint Port = 6667;
|
||||
internal string User = "USER OpenSimBot 8 * :I'm an OpenSim to IRC bot";
|
||||
|
||||
internal bool ClientReporting = true;
|
||||
internal bool RelayPrivateChannels = false;
|
||||
internal int RelayChannel = 1;
|
||||
|
||||
// Connector agnostic parameters. These values are NOT shared with the
|
||||
// connector and do not differentiate at an IRC level
|
||||
|
||||
internal string PrivateMessageFormat = "PRIVMSG {0} :<{2}> {1} {3}";
|
||||
internal string NoticeMessageFormat = "PRIVMSG {0} :<{2}> {3}";
|
||||
internal int RelayChannelOut = -1;
|
||||
internal bool RandomizeNickname = true;
|
||||
internal string AccessPassword = "badkitty";
|
||||
internal bool CommandsEnabled = false;
|
||||
internal int CommandChannel = -1;
|
||||
internal int ConnectDelay = 10;
|
||||
internal int PingDelay = 15;
|
||||
internal string DefaultZone = "Sim";
|
||||
|
||||
// IRC connector reference
|
||||
|
||||
internal XIRCConnector irc = null;
|
||||
|
||||
internal int idn = _idk_++;
|
||||
|
||||
// List of regions dependent upon this connection
|
||||
|
||||
internal List<RegionState> clientregions = new List<RegionState>();
|
||||
|
||||
// Needed by OpenChannel
|
||||
|
||||
internal ChannelState()
|
||||
{
|
||||
}
|
||||
|
||||
// This constructor is used by the Update* methods. A copy of the
|
||||
// existing channel state is created, and distinguishing characteristics
|
||||
// are copied across.
|
||||
|
||||
internal ChannelState(ChannelState model)
|
||||
{
|
||||
Server = model.Server;
|
||||
IrcChannel = model.IrcChannel;
|
||||
Port = model.Port;
|
||||
BaseNickname = model.BaseNickname;
|
||||
RandomizeNickname = model.RandomizeNickname;
|
||||
User = model.User;
|
||||
CommandsEnabled = model.CommandsEnabled;
|
||||
CommandChannel = model.CommandChannel;
|
||||
RelayPrivateChannels = model.RelayPrivateChannels;
|
||||
RelayChannelOut = model.RelayChannelOut;
|
||||
RelayChannel = model.RelayChannel;
|
||||
PrivateMessageFormat = model.PrivateMessageFormat;
|
||||
NoticeMessageFormat = model.NoticeMessageFormat;
|
||||
ClientReporting = model.ClientReporting;
|
||||
AccessPassword = model.AccessPassword;
|
||||
DefaultZone = model.DefaultZone;
|
||||
ConnectDelay = model.ConnectDelay;
|
||||
PingDelay = model.PingDelay;
|
||||
}
|
||||
|
||||
// Read the configuration file, performing variable substitution and any
|
||||
// necessary aliasing. See accompanying documentation for how this works.
|
||||
// If you don't need variables, then this works exactly as before.
|
||||
// If either channel or server are not specified, the request fails.
|
||||
|
||||
internal static void OpenChannel(RegionState rs, IConfig config)
|
||||
{
|
||||
|
||||
// Create a new instance of a channel. This may not actually
|
||||
// get used if an equivalent channel already exists.
|
||||
|
||||
ChannelState cs = new ChannelState();
|
||||
|
||||
// Read in the configuration file and filter everything for variable
|
||||
// subsititution.
|
||||
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] Initial request by Region {1} to connect to IRC", cs.idn, rs.Region);
|
||||
|
||||
cs.Server = Substitute(rs, config.GetString("server", null));
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] Server : <{1}>", cs.idn, cs.Server);
|
||||
cs.IrcChannel = Substitute(rs, config.GetString("channel", null));
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] IrcChannel : <{1}>", cs.idn, cs.IrcChannel);
|
||||
cs.Port = Convert.ToUInt32(Substitute(rs, config.GetString("port", Convert.ToString(cs.Port))));
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] Port : <{1}>", cs.idn, cs.Port);
|
||||
cs.BaseNickname = Substitute(rs, config.GetString("nick", cs.BaseNickname));
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] BaseNickname : <{1}>", cs.idn, cs.BaseNickname);
|
||||
cs.RandomizeNickname = Convert.ToBoolean(Substitute(rs, config.GetString("randomize_nick", Convert.ToString(cs.RandomizeNickname))));
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] RandomizeNickname : <{1}>", cs.idn, cs.RandomizeNickname);
|
||||
cs.RandomizeNickname = Convert.ToBoolean(Substitute(rs, config.GetString("nicknum", Convert.ToString(cs.RandomizeNickname))));
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] RandomizeNickname : <{1}>", cs.idn, cs.RandomizeNickname);
|
||||
cs.User = Substitute(rs, config.GetString("username", cs.User));
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] User : <{1}>", cs.idn, cs.User);
|
||||
cs.CommandsEnabled = Convert.ToBoolean(Substitute(rs, config.GetString("commands_enabled", Convert.ToString(cs.CommandsEnabled))));
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] CommandsEnabled : <{1}>", cs.idn, cs.CommandsEnabled);
|
||||
cs.CommandChannel = Convert.ToInt32(Substitute(rs, config.GetString("commandchannel", Convert.ToString(cs.CommandChannel))));
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] CommandChannel : <{1}>", cs.idn, cs.CommandChannel);
|
||||
cs.CommandChannel = Convert.ToInt32(Substitute(rs, config.GetString("command_channel", Convert.ToString(cs.CommandChannel))));
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] CommandChannel : <{1}>", cs.idn, cs.CommandChannel);
|
||||
cs.RelayPrivateChannels = Convert.ToBoolean(Substitute(rs, config.GetString("relay_private_channels", Convert.ToString(cs.RelayPrivateChannels))));
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] RelayPrivateChannels : <{1}>", cs.idn, cs.RelayPrivateChannels);
|
||||
cs.RelayPrivateChannels = Convert.ToBoolean(Substitute(rs, config.GetString("useworldcomm", Convert.ToString(cs.RelayPrivateChannels))));
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] RelayPrivateChannels : <{1}>", cs.idn, cs.RelayPrivateChannels);
|
||||
cs.RelayChannelOut = Convert.ToInt32(Substitute(rs, config.GetString("relay_private_channel_out", Convert.ToString(cs.RelayChannelOut))));
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] RelayChannelOut : <{1}>", cs.idn, cs.RelayChannelOut);
|
||||
cs.RelayChannel = Convert.ToInt32(Substitute(rs, config.GetString("relay_private_channel_in", Convert.ToString(cs.RelayChannel))));
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] RelayChannel : <{1}>", cs.idn, cs.RelayChannel);
|
||||
cs.RelayChannel = Convert.ToInt32(Substitute(rs, config.GetString("inchannel", Convert.ToString(cs.RelayChannel))));
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] RelayChannel : <{1}>", cs.idn, cs.RelayChannel);
|
||||
cs.PrivateMessageFormat = Substitute(rs, config.GetString("msgformat", cs.PrivateMessageFormat));
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] PrivateMessageFormat : <{1}>", cs.idn, cs.PrivateMessageFormat);
|
||||
cs.NoticeMessageFormat = Substitute(rs, config.GetString("noticeformat", cs.NoticeMessageFormat));
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] NoticeMessageFormat : <{1}>", cs.idn, cs.NoticeMessageFormat);
|
||||
cs.ClientReporting = Convert.ToInt32(Substitute(rs, config.GetString("verbosity", cs.ClientReporting?"1":"0"))) > 0;
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] ClientReporting : <{1}>", cs.idn, cs.ClientReporting);
|
||||
cs.ClientReporting = Convert.ToBoolean(Substitute(rs, config.GetString("report_clients", Convert.ToString(cs.ClientReporting))));
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] ClientReporting : <{1}>", cs.idn, cs.ClientReporting);
|
||||
cs.AccessPassword = Substitute(rs, config.GetString("access_password", cs.AccessPassword));
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] AccessPassword : <{1}>", cs.idn, cs.AccessPassword);
|
||||
cs.DefaultZone = Substitute(rs, config.GetString("fallback_region", cs.DefaultZone));
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] DefaultZone : <{1}>", cs.idn, cs.DefaultZone);
|
||||
cs.ConnectDelay = Convert.ToInt32(Substitute(rs, config.GetString("connect_delay", Convert.ToString(cs.ConnectDelay))));
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] ConnectDelay : <{1}>", cs.idn, cs.ConnectDelay);
|
||||
cs.PingDelay = Convert.ToInt32(Substitute(rs, config.GetString("ping_delay", Convert.ToString(cs.PingDelay))));
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] PingDelay : <{1}>", cs.idn, cs.PingDelay);
|
||||
|
||||
// Fail if fundamental information is still missing
|
||||
|
||||
if (cs.Server == null || cs.IrcChannel == null || cs.BaseNickname == null || cs.User == null)
|
||||
throw new Exception(String.Format("[IRC-Channel-{0}] Invalid configuration for region {1}", cs.idn, rs.Region));
|
||||
|
||||
m_log.InfoFormat("[IRC-Channel-{0}] Configuration for Region {1} is valid", cs.idn, rs.Region);
|
||||
m_log.InfoFormat("[IRC-Channel-{0}] Server = {1}", cs.idn, cs.Server);
|
||||
m_log.InfoFormat("[IRC-Channel-{0}] Channel = {1}", cs.idn, cs.IrcChannel);
|
||||
m_log.InfoFormat("[IRC-Channel-{0}] Port = {1}", cs.idn, cs.Port);
|
||||
m_log.InfoFormat("[IRC-Channel-{0}] Nickname = {1}", cs.idn, cs.BaseNickname);
|
||||
m_log.InfoFormat("[IRC-Channel-{0}] User = {1}", cs.idn, cs.User);
|
||||
|
||||
// Set the channel state for this region
|
||||
|
||||
rs.cs = Integrate(rs, cs);
|
||||
|
||||
}
|
||||
|
||||
// An initialized channel state instance is passed in. If an identical
|
||||
// channel state instance already exists, then the existing instance
|
||||
// is used to replace the supplied value.
|
||||
// If the instance matches with respect to IRC, then the underlying
|
||||
// IRCConnector is assigned to the supplied channel state and the
|
||||
// updated value is returned.
|
||||
// If there is no match, then the supplied instance is completed by
|
||||
// creating and assigning an instance of an IRC connector.
|
||||
|
||||
private static ChannelState Integrate(RegionState rs, ChannelState p_cs)
|
||||
{
|
||||
|
||||
ChannelState cs = p_cs;
|
||||
|
||||
// Check to see if we have an existing server/channel setup that can be used
|
||||
// In the absence of variable substitution this will always resolve to the
|
||||
// same ChannelState instance, and the table will only contains a single
|
||||
// entry, so the performance considerations for the existing behavior are
|
||||
// zero. Only the IRC connector is shared, the ChannelState still contains
|
||||
// values that, while independent of the IRC connetion, do still distinguish
|
||||
// this region's behavior.
|
||||
|
||||
foreach (ChannelState xcs in XIRCBridgeModule.m_channels)
|
||||
{
|
||||
if (cs.IsAPerfectMatchFor(xcs))
|
||||
{
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] Channel state matched", cs.idn);
|
||||
cs = xcs;
|
||||
break;
|
||||
}
|
||||
if (cs.IsAConnectionMatchFor(xcs))
|
||||
{
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] Channel matched", cs.idn);
|
||||
cs.irc = xcs.irc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// No entry was found, so this is going to be a new entry.
|
||||
|
||||
if (cs.irc == null)
|
||||
{
|
||||
|
||||
m_log.DebugFormat("[IRC-Channel-{0}] New channel required", cs.idn);
|
||||
|
||||
if ((cs.irc = new XIRCConnector(cs)) != null)
|
||||
{
|
||||
XIRCBridgeModule.m_channels.Add(cs);
|
||||
|
||||
m_log.InfoFormat("[IRC-Channel-{0}] New channel initialized for {1}, nick: {2}, commands {3}, private channels {4}",
|
||||
cs.idn, rs.Region, cs.DefaultZone,
|
||||
cs.CommandsEnabled ? "enabled" : "not enabled",
|
||||
cs.RelayPrivateChannels ? "relayed" : "not relayed");
|
||||
}
|
||||
else
|
||||
{
|
||||
string txt = String.Format("[IRC-Channel-{0}] Region {1} failed to connect to channel {2} on server {3}:{4}",
|
||||
cs.idn, rs.Region, cs.IrcChannel, cs.Server, cs.Port);
|
||||
m_log.Error(txt);
|
||||
throw new Exception(txt);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_log.InfoFormat("[IRC-Channel-{0}] Region {1} reusing existing connection to channel {2} on server {3}:{4}",
|
||||
cs.idn, rs.Region, cs.IrcChannel, cs.Server, cs.Port);
|
||||
}
|
||||
|
||||
m_log.InfoFormat("[IRC-Channel-{0}] Region {1} connected to channel {2} on server {3}:{4}",
|
||||
cs.idn, rs.Region, cs.IrcChannel, cs.Server, cs.Port);
|
||||
|
||||
// We're finally ready to commit ourselves
|
||||
|
||||
return cs;
|
||||
|
||||
}
|
||||
|
||||
// These routines allow differentiating changes to
|
||||
// the underlying channel state. If necessary, a
|
||||
// new channel state will be created.
|
||||
|
||||
internal ChannelState UpdateServer(RegionState rs, string server)
|
||||
{
|
||||
RemoveRegion(rs);
|
||||
ChannelState cs = new ChannelState(this);
|
||||
cs.Server = server;
|
||||
cs = Integrate(rs, cs);
|
||||
cs.AddRegion(rs);
|
||||
return cs;
|
||||
}
|
||||
|
||||
internal ChannelState UpdatePort(RegionState rs, string port)
|
||||
{
|
||||
RemoveRegion(rs);
|
||||
ChannelState cs = new ChannelState(this);
|
||||
cs.Port = Convert.ToUInt32(port);
|
||||
cs = Integrate(rs, cs);
|
||||
cs.AddRegion(rs);
|
||||
return cs;
|
||||
}
|
||||
|
||||
internal ChannelState UpdateChannel(RegionState rs, string channel)
|
||||
{
|
||||
RemoveRegion(rs);
|
||||
ChannelState cs = new ChannelState(this);
|
||||
cs.IrcChannel = channel;
|
||||
cs = Integrate(rs, cs);
|
||||
cs.AddRegion(rs);
|
||||
return cs;
|
||||
}
|
||||
|
||||
internal ChannelState UpdateNickname(RegionState rs, string nickname)
|
||||
{
|
||||
RemoveRegion(rs);
|
||||
ChannelState cs = new ChannelState(this);
|
||||
cs.BaseNickname = nickname;
|
||||
cs = Integrate(rs, cs);
|
||||
cs.AddRegion(rs);
|
||||
return cs;
|
||||
}
|
||||
|
||||
internal ChannelState UpdateClientReporting(RegionState rs, string cr)
|
||||
{
|
||||
RemoveRegion(rs);
|
||||
ChannelState cs = new ChannelState(this);
|
||||
cs.ClientReporting = Convert.ToBoolean(cr);
|
||||
cs = Integrate(rs, cs);
|
||||
cs.AddRegion(rs);
|
||||
return cs;
|
||||
}
|
||||
|
||||
internal ChannelState UpdateRelayIn(RegionState rs, string channel)
|
||||
{
|
||||
RemoveRegion(rs);
|
||||
ChannelState cs = new ChannelState(this);
|
||||
cs.RelayChannel = Convert.ToInt32(channel);
|
||||
cs = Integrate(rs, cs);
|
||||
cs.AddRegion(rs);
|
||||
return cs;
|
||||
}
|
||||
|
||||
internal ChannelState UpdateRelayOut(RegionState rs, string channel)
|
||||
{
|
||||
RemoveRegion(rs);
|
||||
ChannelState cs = new ChannelState(this);
|
||||
cs.RelayChannelOut = Convert.ToInt32(channel);
|
||||
cs = Integrate(rs, cs);
|
||||
cs.AddRegion(rs);
|
||||
return cs;
|
||||
}
|
||||
|
||||
// Determine whether or not this is a 'new' channel. Only those
|
||||
// attributes that uniquely distinguish an IRC connection should
|
||||
// be included here (and only those attributes should really be
|
||||
// in the ChannelState structure)
|
||||
|
||||
private bool IsAConnectionMatchFor(ChannelState cs)
|
||||
{
|
||||
return (
|
||||
Server == cs.Server &&
|
||||
IrcChannel == cs.IrcChannel &&
|
||||
Port == cs.Port &&
|
||||
BaseNickname == cs.BaseNickname &&
|
||||
User == cs.User
|
||||
);
|
||||
}
|
||||
|
||||
// This level of obsessive matching allows us to produce
|
||||
// a minimal overhead int he case of a server which does
|
||||
// need to differentiate IRC at a region level.
|
||||
|
||||
private bool IsAPerfectMatchFor(ChannelState cs)
|
||||
{
|
||||
return ( IsAConnectionMatchFor(cs) &&
|
||||
|
||||
RelayChannelOut == cs.RelayChannelOut &&
|
||||
PrivateMessageFormat == cs.PrivateMessageFormat &&
|
||||
NoticeMessageFormat == cs.NoticeMessageFormat &&
|
||||
RandomizeNickname == cs.RandomizeNickname &&
|
||||
AccessPassword == cs.AccessPassword &&
|
||||
CommandsEnabled == cs.CommandsEnabled &&
|
||||
CommandChannel == cs.CommandChannel &&
|
||||
DefaultZone == cs.DefaultZone &&
|
||||
RelayPrivateChannels == cs.RelayPrivateChannels &&
|
||||
RelayChannel == cs.RelayChannel &&
|
||||
ClientReporting == cs.ClientReporting
|
||||
);
|
||||
}
|
||||
|
||||
// This function implements the variable substitution mechanism
|
||||
// for the configuration values. Each string read from the
|
||||
// configuration file is scanned for '[...]' enclosures. Each
|
||||
// one that is found is replaced by either a runtime variable
|
||||
// (%xxx) or an existing configuration key. When no further
|
||||
// substitution is possible, the remaining string is returned
|
||||
// to the caller. This allows for arbitrarily nested
|
||||
// enclosures.
|
||||
|
||||
private static string Substitute(RegionState rs, string instr)
|
||||
{
|
||||
|
||||
string result = instr;
|
||||
|
||||
if (result == null || result.Length == 0)
|
||||
return result;
|
||||
|
||||
// Repeatedly scan the string until all possible
|
||||
// substitutions have been performed.
|
||||
|
||||
while (arg.IsMatch(result))
|
||||
{
|
||||
|
||||
string vvar = arg.Match(result).ToString();
|
||||
string var = vvar.Substring(1,vvar.Length-2).Trim();
|
||||
|
||||
switch (var.ToLower())
|
||||
{
|
||||
case "%region" :
|
||||
result = result.Replace(vvar, rs.Region);
|
||||
break;
|
||||
case "%host" :
|
||||
result = result.Replace(vvar, rs.Host);
|
||||
break;
|
||||
case "%master1" :
|
||||
result = result.Replace(vvar, rs.MA1);
|
||||
break;
|
||||
case "%master2" :
|
||||
result = result.Replace(vvar, rs.MA2);
|
||||
break;
|
||||
case "%locx" :
|
||||
result = result.Replace(vvar, rs.LocX);
|
||||
break;
|
||||
case "%locy" :
|
||||
result = result.Replace(vvar, rs.LocY);
|
||||
break;
|
||||
case "%k" :
|
||||
result = result.Replace(vvar, rs.IDK);
|
||||
break;
|
||||
default :
|
||||
result = result.Replace(vvar, rs.config.GetString(var,var));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
|
||||
m_log.InfoFormat("[IRC-Channel-{0}] Closing channel <{1} to server <{2}:{3}>",
|
||||
idn, IrcChannel, Server, Port);
|
||||
|
||||
m_log.InfoFormat("[IRC-Channel-{0}] There are {1} active clients",
|
||||
idn, clientregions.Count);
|
||||
|
||||
irc.Close();
|
||||
|
||||
}
|
||||
|
||||
public void Open()
|
||||
{
|
||||
m_log.InfoFormat("[IRC-Channel-{0}] Opening channel <{1} to server <{2}:{3}>",
|
||||
idn, IrcChannel, Server, Port);
|
||||
|
||||
irc.Open();
|
||||
|
||||
}
|
||||
|
||||
// These are called by each region that attaches to this channel. The call affects
|
||||
// only the relationship of the region with the channel. Not the channel to IRC
|
||||
// relationship (unless it is closed and we want it open).
|
||||
|
||||
public void Open(RegionState rs)
|
||||
{
|
||||
AddRegion(rs);
|
||||
Open();
|
||||
}
|
||||
|
||||
public void Close(RegionState rs)
|
||||
{
|
||||
RemoveRegion(rs);
|
||||
}
|
||||
|
||||
// Add a client region to this channel if it is not already known
|
||||
|
||||
public void AddRegion(RegionState rs)
|
||||
{
|
||||
m_log.InfoFormat("[IRC-Channel-{0}] Adding region {1} to channel <{2} to server <{3}:{4}>",
|
||||
idn, rs.Region, IrcChannel, Server, Port);
|
||||
if (!clientregions.Contains(rs))
|
||||
{
|
||||
clientregions.Add(rs);
|
||||
lock (irc) irc.depends++;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove a client region from the channel. If this is the last
|
||||
// region, then clean up the channel. The connector will clean itself
|
||||
// up if it finds itself about to be GC'd.
|
||||
|
||||
public void RemoveRegion(RegionState rs)
|
||||
{
|
||||
|
||||
m_log.InfoFormat("[IRC-Channel-{0}] Removing region {1} from channel <{2} to server <{3}:{4}>",
|
||||
idn, rs.Region, IrcChannel, Server, Port);
|
||||
|
||||
if (clientregions.Contains(rs))
|
||||
{
|
||||
clientregions.Remove(rs);
|
||||
lock (irc) irc.depends--;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// This function is lifted from the IRCConnector because it
|
||||
// contains information that is not differentiating from an
|
||||
// IRC point-of-view.
|
||||
|
||||
public static void OSChat(XIRCConnector p_irc, OSChatMessage c, bool cmsg)
|
||||
{
|
||||
|
||||
// m_log.DebugFormat("[IRC-OSCHAT] from {0}:{1}", p_irc.Server, p_irc.IrcChannel);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
// Scan through the set of unique channel configuration for those
|
||||
// that belong to this connector. And then forward the message to
|
||||
// all regions known to those channels.
|
||||
// Note that this code is responsible for completing some of the
|
||||
// settings for the inbound OSChatMessage
|
||||
|
||||
foreach (ChannelState cs in XIRCBridgeModule.m_channels)
|
||||
{
|
||||
if ( p_irc == cs.irc)
|
||||
{
|
||||
|
||||
// This non-IRC differentiator moved to here
|
||||
|
||||
if (cmsg && !cs.ClientReporting)
|
||||
continue;
|
||||
|
||||
// This non-IRC differentiator moved to here
|
||||
|
||||
c.Channel = (cs.RelayPrivateChannels ? cs.RelayChannel : 0);
|
||||
|
||||
foreach (RegionState region in cs.clientregions)
|
||||
{
|
||||
region.OSChat(cs.irc, c);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
m_log.ErrorFormat("[IRC-OSCHAT]: BroadcastSim Exception: {0}", ex.Message);
|
||||
m_log.Debug(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -242,7 +242,7 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat
|
|||
break;
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
m_log.DebugFormat("[IRC] error processing in-world command channel input: {0}", ex);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,435 @@
|
|||
/*
|
||||
* 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 OpenSim 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 log4net;
|
||||
using Nini.Config;
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Region.Environment.Interfaces;
|
||||
using OpenSim.Region.Environment.Scenes;
|
||||
|
||||
namespace OpenSim.Region.Environment.Modules.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(128, 128, 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 MA1 = String.Empty;
|
||||
internal string MA2 = 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;
|
||||
|
||||
// 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.RegionLocX);
|
||||
LocY = Convert.ToString(scene.RegionInfo.RegionLocY);
|
||||
MA1 = scene.RegionInfo.MasterAvatarFirstName;
|
||||
MA2 = scene.RegionInfo.MasterAvatarLastName;
|
||||
IDK = Convert.ToString(_idk_++);
|
||||
|
||||
// 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
|
||||
|
||||
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);
|
||||
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);
|
||||
cs.irc.PrivMsg(cs.NoticeMessageFormat, cs.irc.Nick, Region, String.Format("{0} has arrived", clientName));
|
||||
}
|
||||
}
|
||||
}
|
||||
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 all messages coming in on a private channel,
|
||||
// except if we are relaying private channels, in which
|
||||
// case we drop if the private channel is not the
|
||||
// configured m_relayChannelOut
|
||||
|
||||
if (cs.RelayPrivateChannels)
|
||||
{
|
||||
if (msg.Channel != 0 && msg.Channel != DEBUG_CHANNEL && msg.Channel != cs.RelayChannelOut)
|
||||
{
|
||||
m_log.DebugFormat("[IRC-Region {0}] dropping message {1} on channel {2}", Region, msg, msg.Channel);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (msg.Channel != 0 && msg.Channel != DEBUG_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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Message came from an object
|
||||
char[] splits = { ',' };
|
||||
string[] tokens = msg.Message.Split(splits,3); // This is certainly wrong
|
||||
|
||||
if (tokens.Length == 3)
|
||||
{
|
||||
if (tokens[0] == cs.AccessPassword) // This is my really simple check
|
||||
{
|
||||
m_log.DebugFormat("[IRC-Region {0}] message from object {1}, {2}", Region, tokens[0], tokens[1]);
|
||||
cs.irc.PrivMsg(cs.PrivateMessageFormat, tokens[1], scene.RegionInfo.RegionName, tokens[2]);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_log.WarnFormat("[IRC-Region {0}] prim security key mismatch <{1}> not <{2}>", Region, tokens[0], cs.AccessPassword);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,280 @@
|
|||
/*
|
||||
* 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 OpenSim 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using log4net;
|
||||
using Nini.Config;
|
||||
using Nwc.XmlRpc;
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Region.Environment.Interfaces;
|
||||
using OpenSim.Region.Environment.Scenes;
|
||||
|
||||
namespace OpenSim.Region.Environment.Modules.Avatar.Chat
|
||||
{
|
||||
|
||||
public class XIRCBridgeModule : IRegionModule
|
||||
{
|
||||
|
||||
private static readonly ILog m_log =
|
||||
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
internal static bool configured = false;
|
||||
internal static bool enabled = false;
|
||||
internal static IConfig m_config = null;
|
||||
|
||||
internal static List<RegionState> m_regions = new List<RegionState>();
|
||||
internal static List<ChannelState> m_channels = new List<ChannelState>();
|
||||
|
||||
internal static string password = String.Empty;
|
||||
|
||||
#region IRegionModule Members
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return "XIRCBridgeModule"; }
|
||||
}
|
||||
|
||||
public bool IsSharedModule
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public void Initialise(Scene scene, IConfigSource config)
|
||||
{
|
||||
|
||||
// Do a once-only scan of the configuration file to make
|
||||
// sure it's basically intact.
|
||||
|
||||
if (!configured)
|
||||
{
|
||||
|
||||
configured = true;
|
||||
|
||||
try
|
||||
{
|
||||
if ((m_config = config.Configs["XIRC"]) == null)
|
||||
{
|
||||
m_log.InfoFormat("[XIRC-Bridge] module not configured");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_config.GetBoolean("enabled", false))
|
||||
{
|
||||
m_log.InfoFormat("[XIRC-Bridge] module disabled in configuration");
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.ErrorFormat("[XIRC-Bridge] configuration failed : {0}", e.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
enabled = true;
|
||||
|
||||
if(config.Configs["RemoteAdmin"] != null)
|
||||
{
|
||||
password = config.Configs["RemoteAdmin"].GetString("access_password", password);
|
||||
scene.CommsManager.HttpServer.AddXmlRPCHandler("xirc_admin", XmlRpcAdminMethod, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Iff the IRC bridge is enabled, then each new region may be
|
||||
// connected to IRC. But it should NOT be obligatory (and it
|
||||
// is not).
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_log.InfoFormat("[XIRC-Bridge] Connecting region {0}", scene.RegionInfo.RegionName);
|
||||
m_regions.Add(new RegionState(scene, m_config));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.WarnFormat("[XIRC-Bridge] Region {0} not connected to IRC : {1}", scene.RegionInfo.RegionName, e.Message);
|
||||
m_log.Debug(e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_log.WarnFormat("[XIRC-Bridge] Not enabled. Connect for region {0} ignored", scene.RegionInfo.RegionName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Called after all region modules have been loaded.
|
||||
// Iff the IRC bridge is enabled, then start all of the
|
||||
// configured channels. The set of channels is a side
|
||||
// effect of RegionState creation.
|
||||
|
||||
public void PostInitialise()
|
||||
{
|
||||
|
||||
if (!enabled)
|
||||
return;
|
||||
|
||||
foreach (RegionState region in m_regions)
|
||||
{
|
||||
m_log.InfoFormat("[XIRC-Bridge] Opening connection for {0}:{1} on IRC server {2}:{3}",
|
||||
region.Region, region.cs.BaseNickname, region.cs.Server, region.cs.IrcChannel);
|
||||
try
|
||||
{
|
||||
region.Open();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.ErrorFormat("[XIRC-Bridge] Open failed for {0}:{1} on IRC server {2}:{3} : {4}",
|
||||
region.Region, region.cs.BaseNickname, region.cs.Server, region.cs.IrcChannel,
|
||||
e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Called immediately before the region module is unloaded. Close all
|
||||
// associated channels.
|
||||
|
||||
public void Close()
|
||||
{
|
||||
|
||||
if (!enabled)
|
||||
return;
|
||||
|
||||
// Stop each of the region sessions
|
||||
|
||||
foreach (RegionState region in m_regions)
|
||||
{
|
||||
m_log.InfoFormat("[XIRC-Bridge] Closing connection for {0}:{1} on IRC server {2}:{3}",
|
||||
region.Region, region.cs.BaseNickname, region.cs.Server, region.cs.IrcChannel);
|
||||
try
|
||||
{
|
||||
region.Close();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.ErrorFormat("[XIRC-Bridge] Close failed for {0}:{1} on IRC server {2}:{3} : {4}",
|
||||
region.Region, region.cs.BaseNickname, region.cs.Server, region.cs.IrcChannel,
|
||||
e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
// Perform final cleanup of the channels (they now have no active clients)
|
||||
|
||||
foreach (ChannelState channel in m_channels)
|
||||
{
|
||||
m_log.InfoFormat("[XIRC-Bridge] Closing connection for {0} on IRC server {1}:{2}",
|
||||
channel.BaseNickname, channel.Server, channel.IrcChannel);
|
||||
try
|
||||
{
|
||||
channel.Close();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.ErrorFormat("[XIRC-Bridge] Close failed for {0} on IRC server {1}:{2} : {3}",
|
||||
channel.BaseNickname, channel.Server, channel.IrcChannel,
|
||||
e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public XmlRpcResponse XmlRpcAdminMethod(XmlRpcRequest request)
|
||||
{
|
||||
|
||||
m_log.Info("[XIRC-Bridge]: XML RPC Admin Entry");
|
||||
|
||||
XmlRpcResponse response = new XmlRpcResponse();
|
||||
Hashtable responseData = new Hashtable();
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
Hashtable requestData = (Hashtable)request.Params[0];
|
||||
bool found = false;
|
||||
string region = String.Empty;
|
||||
|
||||
if(password != String.Empty)
|
||||
{
|
||||
if(!requestData.ContainsKey("password"))
|
||||
throw new Exception("Invalid request");
|
||||
if(requestData["password"] != password)
|
||||
throw new Exception("Invalid request");
|
||||
}
|
||||
|
||||
if(!requestData.ContainsKey("region"))
|
||||
throw new Exception("No region name specified");
|
||||
|
||||
foreach(RegionState rs in m_regions)
|
||||
{
|
||||
if(rs.Region == region)
|
||||
{
|
||||
responseData["server"] = rs.cs.Server;
|
||||
responseData["port"] = rs.cs.Port;
|
||||
responseData["user"] = rs.cs.User;
|
||||
responseData["channel"] = rs.cs.IrcChannel;
|
||||
responseData["enabled"] = rs.cs.irc.Enabled;
|
||||
responseData["connected"] = rs.cs.irc.Connected;
|
||||
responseData["nickname"] = rs.cs.irc.Nick;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!found) throw new Exception(String.Format("Region <{0}> not found", region));
|
||||
|
||||
responseData["success"] = true;
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.InfoFormat("[XIRC-Bridge] XML RPC Admin request failed : {0}", e.Message);
|
||||
|
||||
responseData["success"] = "false";
|
||||
responseData["error"] = e.Message;
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
response.Value = responseData;
|
||||
}
|
||||
|
||||
m_log.Debug("[XIRC-Bridge]: XML RPC Admin Exit");
|
||||
|
||||
return response;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,842 @@
|
|||
/*
|
||||
* 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 OpenSim 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.Region.Environment.Interfaces;
|
||||
using OpenSim.Region.Environment.Scenes;
|
||||
|
||||
namespace OpenSim.Region.Environment.Modules.Avatar.Chat
|
||||
{
|
||||
public class XIRCConnector
|
||||
{
|
||||
|
||||
#region Global (static) state
|
||||
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
|
||||
// Local constants
|
||||
|
||||
private static readonly Vector3 CenterOfRegion = new Vector3(128, 128, 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 _idk_ = 0; // core connector identifier
|
||||
private static int _pdk_ = 0; // ping interval counter
|
||||
private static int _icc_ = 0; // IRC connect counter
|
||||
|
||||
// List of configured connectors
|
||||
|
||||
private static List<XIRCConnector> m_connectors = new List<XIRCConnector>();
|
||||
|
||||
// Watchdog state
|
||||
|
||||
private static System.Timers.Timer m_watchdog = null;
|
||||
|
||||
static XIRCConnector()
|
||||
{
|
||||
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;
|
||||
|
||||
// Working threads
|
||||
|
||||
private Thread m_listener = null;
|
||||
|
||||
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
|
||||
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_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 XIRCConnector(ChannelState cs)
|
||||
{
|
||||
|
||||
// Prepare network interface
|
||||
|
||||
m_tcp = null;
|
||||
m_writer = null;
|
||||
m_reader = null;
|
||||
|
||||
// Setup IRC session parameters
|
||||
|
||||
m_server = cs.Server;
|
||||
m_baseNick = cs.BaseNickname;
|
||||
m_randomizeNick = cs.RandomizeNickname;
|
||||
m_ircChannel = cs.IrcChannel;
|
||||
m_port = (uint) 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 randomizing is enabled
|
||||
|
||||
if (m_randomizeNick)
|
||||
{
|
||||
m_nick = m_baseNick + Util.RandomClass.Next(1, 99);
|
||||
}
|
||||
|
||||
// Add the newly created connector to the known connectors list
|
||||
|
||||
m_connectors.Add(this);
|
||||
|
||||
m_log.InfoFormat("[IRC-Connector-{0}]: Initialization complete", idn);
|
||||
|
||||
}
|
||||
|
||||
~XIRCConnector()
|
||||
{
|
||||
m_watchdog.Stop();
|
||||
Close();
|
||||
}
|
||||
|
||||
// Mark the connector as connectable. Harmless if already enabled.
|
||||
|
||||
public void Open()
|
||||
{
|
||||
if (!m_enabled)
|
||||
{
|
||||
|
||||
m_connectors.Add(this);
|
||||
m_enabled = true;
|
||||
|
||||
if (!Connected)
|
||||
{
|
||||
Connect();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {}
|
||||
|
||||
}
|
||||
|
||||
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_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);
|
||||
|
||||
m_listener = new Thread(new ThreadStart(ListenerRun));
|
||||
m_listener.Name = "IRCConnectorListenerThread";
|
||||
m_listener.IsBackground = true;
|
||||
m_listener.Start();
|
||||
ThreadTracker.Add(m_listener);
|
||||
|
||||
// This is the message order recommended by RFC 2812
|
||||
|
||||
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 joined {2}", idn, m_nick, m_ircChannel);
|
||||
m_log.InfoFormat("[IRC-Connector-{0}] Connected", idn);
|
||||
|
||||
}
|
||||
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);
|
||||
m_connected = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
try
|
||||
{
|
||||
while (m_enabled && m_connected)
|
||||
{
|
||||
|
||||
if ((inputLine = m_reader.ReadLine()) == null)
|
||||
throw new Exception("Listener input socket closed");
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
if (m_enabled) Reconnect();
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
string regex = @":(?<nick>[\w-]*)!(?<user>\S*) PRIVMSG (?<channel>\S+) :(?<msg>.*)";
|
||||
Regex RE = new Regex(regex, RegexOptions.Multiline);
|
||||
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}] parms = <{1}>", idn, 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}", idn, parms.Split(CS_SPACE,2)[1]);
|
||||
break;
|
||||
case "376" : // MOTD end
|
||||
m_log.InfoFormat("[IRC-Connector-{0}] {1}", idn, 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}]: IRC SERVER reports NicknameInUse, trying {1}", idn, 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 "NOTICE" :
|
||||
m_log.WarnFormat("[IRC-Connector-{0}] {1}", idn, parms.Split(CS_SPACE,2)[1]);
|
||||
break;
|
||||
case "ERROR" :
|
||||
m_log.ErrorFormat("[IRC-Connector-{0}] {1}", idn, parms.Split(CS_SPACE,2)[1]);
|
||||
if (parms.Contains("reconnect too fast"))
|
||||
ICCD_PERIOD++;
|
||||
break;
|
||||
case "PING" :
|
||||
m_log.DebugFormat("[IRC-Connector-{0}] parms = <{1}>", idn, parms);
|
||||
m_writer.WriteLine(String.Format("PONG {0}", parms));
|
||||
m_writer.Flush();
|
||||
break;
|
||||
case "PONG" :
|
||||
break;
|
||||
case "JOIN":
|
||||
m_log.DebugFormat("[IRC-Connector-{0}] parms = <{1}>", idn, parms);
|
||||
eventIrcJoin(pfx, cmd, parms);
|
||||
break;
|
||||
case "PART":
|
||||
m_log.DebugFormat("[IRC-Connector-{0}] parms = <{1}>", idn, parms);
|
||||
eventIrcPart(pfx, cmd, parms);
|
||||
break;
|
||||
case "MODE":
|
||||
m_log.DebugFormat("[IRC-Connector-{0}] parms = <{1}>", idn, parms);
|
||||
eventIrcMode(pfx, cmd, parms);
|
||||
break;
|
||||
case "NICK":
|
||||
m_log.DebugFormat("[IRC-Connector-{0}] parms = <{1}>", idn, parms);
|
||||
eventIrcNickChange(pfx, cmd, parms);
|
||||
break;
|
||||
case "KICK":
|
||||
m_log.DebugFormat("[IRC-Connector-{0}] parms = <{1}>", idn, parms);
|
||||
eventIrcKick(pfx, cmd, parms);
|
||||
break;
|
||||
case "QUIT":
|
||||
m_log.DebugFormat("[IRC-Connector-{0}] parms = <{1}>", idn, 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_ = (_pdk_+1)%PING_PERIOD; // cycle the ping trigger
|
||||
_icc_++; // increment the inter-consecutive-connect-delay counter
|
||||
|
||||
foreach (XIRCConnector connector in m_connectors)
|
||||
{
|
||||
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 (_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
|
||||
|
||||
}
|
||||
}
|
|
@ -428,16 +428,6 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Concierge
|
|||
}
|
||||
}
|
||||
|
||||
// private static void checkIntegerParams(XmlRpcRequest request, string[] param)
|
||||
// {
|
||||
// Hashtable requestData = (Hashtable) request.Params[0];
|
||||
// foreach (string p in param)
|
||||
// {
|
||||
// if (!requestData.Contains(p))
|
||||
// throw new Exception(String.Format("missing integer parameter {0}", p));
|
||||
// }
|
||||
// }
|
||||
|
||||
public XmlRpcResponse XmlRpcUpdateWelcomeMethod(XmlRpcRequest request)
|
||||
{
|
||||
_log.Info("[Concierge]: processing UpdateWelcome request");
|
||||
|
|
|
@ -36,6 +36,7 @@ using System.IO;
|
|||
using System.IO.Compression;
|
||||
using System.Reflection;
|
||||
using System.Xml;
|
||||
using System.Net;
|
||||
using OpenMetaverse;
|
||||
using log4net;
|
||||
|
||||
|
@ -70,7 +71,8 @@ namespace OpenSim.Region.Environment.Modules.World.Archiver
|
|||
{
|
||||
TarArchiveReader archive
|
||||
= new TarArchiveReader(
|
||||
new GZipStream(new FileStream(m_loadPath, FileMode.Open), CompressionMode.Decompress));
|
||||
new GZipStream(GetStream(m_loadPath), CompressionMode.Decompress));
|
||||
|
||||
//AssetsDearchiver dearchiver = new AssetsDearchiver(m_scene.AssetCache);
|
||||
|
||||
List<string> serialisedSceneObjects = new List<string>();
|
||||
|
@ -284,5 +286,65 @@ namespace OpenSim.Region.Environment.Modules.World.Archiver
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve path to a working FileStream
|
||||
/// </summary>
|
||||
|
||||
private Stream GetStream(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
return new FileStream(path, FileMode.Open);
|
||||
}
|
||||
else
|
||||
{
|
||||
Uri uri = new Uri(path); // throw exception if not valid URI
|
||||
if (uri.Scheme == "file")
|
||||
{
|
||||
return new FileStream(uri.AbsolutePath, FileMode.Open);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (uri.Scheme != "http")
|
||||
throw new Exception(String.Format("Unsupported URI scheme ({0})", path));
|
||||
|
||||
// OK, now we know we have an HTTP URI to work with
|
||||
|
||||
return URIFetch(uri);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception(String.Format("Unable to create file input stream for {0}: {1}", path, e));
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream URIFetch(Uri uri)
|
||||
{
|
||||
|
||||
HttpWebRequest request = (HttpWebRequest) WebRequest.Create(uri);
|
||||
|
||||
// request.Credentials = credentials;
|
||||
|
||||
request.ContentLength = 0;
|
||||
|
||||
WebResponse response = request.GetResponse();
|
||||
Stream file = response.GetResponseStream();
|
||||
|
||||
if (response.ContentType != "application/x-oar")
|
||||
throw new Exception(String.Format("{0} does not identify an OAR file", uri.ToString()));
|
||||
|
||||
if (response.ContentLength == 0)
|
||||
throw new Exception(String.Format("{0} returned an empty file", uri.ToString()));
|
||||
|
||||
return new BufferedStream(file, (int) response.ContentLength);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -448,6 +448,11 @@ msgformat = "PRIVMSG {0} : {3} - {1} of {2}"
|
|||
;for <bot>:<message> - from <user> :
|
||||
;msgformat = "PRIVMSG {0} : {3} - from {1}"
|
||||
|
||||
; work-in-progress IRCBridge capabable of supporting multiple IRC channels
|
||||
; [XIRC]
|
||||
; enabled = false
|
||||
|
||||
|
||||
; Uncomment the following for experiment in world versioning
|
||||
; support. This is so experimental at this point that I'm not going
|
||||
; to explain it further, you'll need to read the source code
|
||||
|
|
Loading…
Reference in New Issue