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 } }