OpenSimMirror/OpenSim/Region/OptionalModules/Avatar/Chat/ChannelState.cs

642 lines
29 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 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;
private static int DEBUG_CHANNEL = 2147483647;
// These are the IRC Connector configurable parameters with hard-wired
// default values (retained for compatability).
internal string Server = null;
internal string Password = null;
internal string IrcChannel = null;
internal string BaseNickname = "OSimBot";
internal uint Port = 6667;
internal string User = null;
internal bool ClientReporting = true;
internal bool RelayChat = true;
internal bool RelayPrivateChannels = false;
internal int RelayChannel = 1;
internal List<int> ValidInWorldChannels = new List<int>();
// 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 bool CommandsEnabled = false;
internal int CommandChannel = -1;
internal int ConnectDelay = 10;
internal int PingDelay = 15;
internal string DefaultZone = "Sim";
internal string _accessPassword = String.Empty;
internal Regex AccessPasswordRegex = null;
internal List<string> ExcludeList = new List<string>();
internal string AccessPassword
{
get { return _accessPassword; }
set
{
_accessPassword = value;
AccessPasswordRegex = new Regex(String.Format(@"^{0},\s*(?<avatar>[^,]+),\s*(?<message>.+)$", _accessPassword),
RegexOptions.Compiled);
}
}
// IRC connector reference
internal IRCConnector 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;
Password = model.Password;
IrcChannel = model.IrcChannel;
Port = model.Port;
BaseNickname = model.BaseNickname;
RandomizeNickname = model.RandomizeNickname;
User = model.User;
CommandsEnabled = model.CommandsEnabled;
CommandChannel = model.CommandChannel;
RelayChat = model.RelayChat;
RelayPrivateChannels = model.RelayPrivateChannels;
RelayChannelOut = model.RelayChannelOut;
RelayChannel = model.RelayChannel;
ValidInWorldChannels = model.ValidInWorldChannels;
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.Password = Substitute(rs, config.GetString("password", null));
// probably not a good idea to put a password in the log file
cs.User = Substitute(rs, config.GetString("user", null));
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.RelayChat = Convert.ToBoolean(Substitute(rs, config.GetString("relay_chat", Convert.ToString(cs.RelayChat))));
m_log.DebugFormat("[IRC-Channel-{0}] RelayChat : <{1}>", cs.idn, cs.RelayChat);
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.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);
cs.AccessPassword = Substitute(rs, config.GetString("access_password", cs.AccessPassword));
m_log.DebugFormat("[IRC-Channel-{0}] AccessPassword : <{1}>", cs.idn, cs.AccessPassword);
string[] excludes = config.GetString("exclude_list", "").Trim().Split(new Char[] { ',' });
cs.ExcludeList = new List<string>(excludes.Length);
foreach (string name in excludes)
{
cs.ExcludeList.Add(name.Trim().ToLower());
}
// Fail if fundamental information is still missing
if (cs.Server == null)
throw new Exception(String.Format("[IRC-Channel-{0}] Invalid configuration for region {1}: server missing", cs.idn, rs.Region));
else if (cs.IrcChannel == null)
throw new Exception(String.Format("[IRC-Channel-{0}] Invalid configuration for region {1}: channel missing", cs.idn, rs.Region));
else if (cs.BaseNickname == null)
throw new Exception(String.Format("[IRC-Channel-{0}] Invalid configuration for region {1}: nick missing", cs.idn, rs.Region));
else if (cs.User == null)
throw new Exception(String.Format("[IRC-Channel-{0}] Invalid configuration for region {1}: user missing", 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
if (cs.RelayChat)
{
cs.ValidInWorldChannels.Add(0);
cs.ValidInWorldChannels.Add(DEBUG_CHANNEL);
}
if (cs.RelayPrivateChannels)
cs.ValidInWorldChannels.Add(cs.RelayChannelOut);
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.
lock (IRCBridgeModule.m_channels)
{
foreach (ChannelState xcs in IRCBridgeModule.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 IRCConnector(cs)) != null)
{
IRCBridgeModule.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} associated with 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 &&
RelayChat == cs.RelayChat &&
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.
// m_log.DebugFormat("[IRC-Channel] Parse[1]: {0}", result);
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;
}
// m_log.DebugFormat("[IRC-Channel] Parse[2]: {0}", result);
}
// m_log.DebugFormat("[IRC-Channel] Parse[3]: {0}", result);
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();
}
// Close is called to ensure that the IRC session is terminated if this is the
// only client.
public void Close(RegionState rs)
{
RemoveRegion(rs);
lock (IRCBridgeModule.m_channels)
{
if (clientregions.Count == 0)
{
Close();
IRCBridgeModule.m_channels.Remove(this);
m_log.InfoFormat("[IRC-Channel-{0}] Region {1} is last user of channel <{2}> to server <{3}:{4}>",
idn, rs.Region, IrcChannel, Server, Port);
m_log.InfoFormat("[IRC-Channel-{0}] Removed", idn);
}
}
}
// 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(IRCConnector 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
lock (IRCBridgeModule.m_channels)
{
foreach (ChannelState cs in IRCBridgeModule.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);
}
}
}
}