using System;
using System.Collections.Generic;
using System.Threading;
using TGS.Interface;
using Meebey.SmartIrc4net;
namespace TGS.Server.ChatProviders
{
///
/// for internet relay chat
///
sealed class IRCChatProvider : IChatProvider
{
///
/// Header used to mark that a channel is actually a query message
///
const string PrivateMessageMarker = "---PRIVATE-MSG---";
///
/// The irc client
///
IrcFeatures irc;
///
/// Used for multithreading safety
///
object IRCLock = new object();
///
/// The setup info for the provider
///
IRCSetupInfo IRCConfig;
///
public event OnChatMessage OnChatMessage;
///
public ChatSetupInfo ProviderInfo()
{
return IRCConfig;
}
///
/// Construct a
///
/// The
public IRCChatProvider(ChatSetupInfo info)
{
IRCConfig = new IRCSetupInfo(info);
irc = new IrcFeatures()
{
SupportNonRfc = true,
CtcpUserInfo = Server.VersionString,
AutoRejoin = true,
AutoRejoinOnKick = true,
AutoRelogin = true,
AutoRetry = true,
AutoRetryLimit = 5,
AutoRetryDelay = 5,
ActiveChannelSyncing = true,
};
irc.OnChannelMessage += Irc_OnChannelMessage;
irc.OnQueryMessage += Irc_OnQueryMessage;
}
///
public string SendMessageDirect(string message, string channel)
{
try
{
if (!Connected())
return "Disconnected.";
lock (IRCLock)
{
SendType st;
if (channel.Contains(PrivateMessageMarker))
{
channel = channel.Replace(PrivateMessageMarker, "");
st = SendType.Notice;
}
else
st = SendType.Message;
irc.SendMessage(st, channel, message);
}
return null;
}
catch (Exception e)
{
return e.ToString();
}
}
///
public string SetProviderInfo(ChatSetupInfo info)
{
var convertedInfo = (IRCSetupInfo)info;
var serverChange = convertedInfo.URL != IRCConfig.URL || convertedInfo.Port != IRCConfig.Port;
IRCConfig = convertedInfo;
if (!IRCConfig.Enabled)
{
Disconnect();
return null;
}
else if (serverChange || !Connected())
return Reconnect();
if (IRCConfig.Nickname != irc.Nickname)
irc.RfcNick(convertedInfo.Nickname);
Login();
JoinChannels();
return null;
}
///
/// Checks if a message is considered sent from a chat admin
///
/// The
/// if was sent by a chat admin, otherwise
private bool CheckAdmin(IrcMessageData e)
{
if (IRCConfig.AdminsAreSpecial)
{
if (e.Channel == null)
return false;
var user = (NonRfcChannelUser)irc.GetChannelUser(e.Channel, e.Nick);
if (user != null)
switch (IRCConfig.AuthLevel)
{
case IRCMode.Voice:
if (user.IsVoice)
return true;
goto case IRCMode.Halfop;
case IRCMode.Halfop:
if (user.IsHalfop)
return true;
goto case IRCMode.Op;
case IRCMode.Op:
if (user.IsOp)
return true;
goto case IRCMode.Owner;
case IRCMode.Owner:
if (user.IsOwner)
return true;
break;
}
}
else
{
var lowerNick = e.Nick.ToLower();
foreach (var I in IRCConfig.AdminList)
if (lowerNick == I.ToLower())
return true;
}
return false;
}
///
/// Called when the bot recieves a query message
///
/// The sender of the event (usually )
/// The
private void Irc_OnQueryMessage(object sender, IrcEventArgs e)
{
OnChatMessage(this, e.Data.Nick, e.Data.Nick + PrivateMessageMarker, e.Data.Message, CheckAdmin(e.Data), true);
}
///
/// Called when a channel the bot is in recieves a message
///
/// The sender of the event (usually )
/// The
private void Irc_OnChannelMessage(object sender, IrcEventArgs e)
{
var formattedMessage = e.Data.Message.Trim();
var splits = new List(formattedMessage.Split(' '));
var test = splits[0];
if (test.Length > 1 && (test[test.Length - 1] == ':' || test[test.Length - 1] == ','))
test = test.Substring(0, test.Length - 1);
test = test.ToLower();
if (test != irc.Nickname.ToLower() && test != "!tgs")
return;
splits.RemoveAt(0);
formattedMessage = String.Join(" ", splits);
OnChatMessage(this, e.Data.Nick, e.Data.Channel, formattedMessage, CheckAdmin(e.Data), IRCConfig.AdminChannels.Contains(e.Data.Channel.ToLower()));
}
///
/// Joins all channels specified in
///
void JoinChannels()
{
var hs = new HashSet(); //for unique inserts
foreach (var I in IRCConfig.AdminChannels)
hs.Add(I);
foreach (var I in IRCConfig.DevChannels)
hs.Add(I);
foreach (var I in IRCConfig.GameChannels)
hs.Add(I);
foreach (var I in IRCConfig.WatchdogChannels)
hs.Add(I);
var ToPart = new List();
foreach (var I in irc.JoinedChannels)
if (!hs.Remove(I))
ToPart.Add(I);
foreach (var I in ToPart)
irc.RfcPart(I);
foreach (var I in hs)
irc.RfcJoin(I);
}
///
/// Sends a login query to with message
///
void Login()
{
lock (IRCLock)
{
if (IRCConfig.AuthTarget != null)
irc.SendMessage(SendType.Message, IRCConfig.AuthTarget, IRCConfig.AuthMessage);
}
}
///
public string Connect()
{
if (Connected() || !IRCConfig.Enabled)
return null;
lock (IRCLock)
{
try
{
try
{
irc.Connect(IRCConfig.URL, IRCConfig.Port);
}
catch (Exception e)
{
return "IRC server unreachable: " + e.ToString();
}
try
{
irc.Login(IRCConfig.Nickname, IRCConfig.Nickname);
}
catch (Exception e)
{
return "Bot name is already taken: " + e.ToString();
}
Login();
JoinChannels();
new Thread(new ThreadStart(IRCListen)) { IsBackground = true }.Start();
return null;
}
catch (Exception e)
{
return e.ToString();
}
}
}
///
/// Runs the listener in a safe loop
///
void IRCListen()
{
Thread.CurrentThread.Name = "IRC Listener Thread";
while (irc != null && Connected())
try
{
irc.Listen();
}
catch { }
}
///
public string Reconnect()
{
Disconnect();
return Connect();
}
///
public void Disconnect()
{
try
{
lock (IRCLock)
{
if (irc.IsConnected)
{
irc.RfcQuit("Twas meant to be...");
irc.Disconnect();
}
}
}
catch { }
}
///
public bool Connected()
{
lock (IRCLock)
{
return irc != null && irc.IsConnected;
}
}
///
public void SendMessage(string message, MessageType mt)
{
if (!Connected())
return;
lock (IRCLock)
{
string cids = "";
for (var I = 0; I < irc.JoinedChannels.Count; ++I)
{
var cid = irc.JoinedChannels[I];
bool SendToThisChannel = (mt.HasFlag(MessageType.AdminInfo) && IRCConfig.AdminChannels.Contains(cid))
|| (mt.HasFlag(MessageType.DeveloperInfo) && IRCConfig.DevChannels.Contains(cid))
|| (mt.HasFlag(MessageType.GameInfo) && IRCConfig.GameChannels.Contains(cid))
|| (mt.HasFlag(MessageType.WatchdogInfo) && IRCConfig.WatchdogChannels.Contains(cid));
if (SendToThisChannel)
cids += I > 0 ? ',' + cid : cid;
}
irc.SendMessage(SendType.Message, cids, message);
}
}
#region IDisposable Support
///
/// To detect redundant calls
///
private bool disposedValue = false;
///
/// Implements the pattern. Calls and sets to
///
/// if was called manually, if it was from the finalizer
void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects).
Disconnect();
irc = null;
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
disposedValue = true;
}
}
// TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
// ~TGIRCChatProvider() {
// // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
// Dispose(false);
// }
// This code added to correctly implement the disposable pattern.
///
/// Implements the pattern
///
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
// TODO: uncomment the following line if the finalizer is overridden above.
// GC.SuppressFinalize(this);
}
#endregion
}
}