diff --git a/Chaskis/ChaskisCore/Chaskis.Core.csproj b/Chaskis/ChaskisCore/Chaskis.Core.csproj index 2b3177dc..d62e8fb1 100644 --- a/Chaskis/ChaskisCore/Chaskis.Core.csproj +++ b/Chaskis/ChaskisCore/Chaskis.Core.csproj @@ -23,6 +23,10 @@ + + + + ChaskisCore 0.20.0 diff --git a/Chaskis/ChaskisCore/Handlers/SendPart/SendPartEventArgs.cs b/Chaskis/ChaskisCore/Handlers/SendPart/SendPartEventArgs.cs new file mode 100644 index 00000000..f8559739 --- /dev/null +++ b/Chaskis/ChaskisCore/Handlers/SendPart/SendPartEventArgs.cs @@ -0,0 +1,98 @@ +// +// Copyright Seth Hendrick 2020. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +using System.Collections.Generic; +using System.Xml.Linq; +using SethCS.Exceptions; +using SethCS.Extensions; + +namespace Chaskis.Core +{ + /// + /// Args that are passed into when + /// the bot leaves a channel. + /// + public sealed class SendPartEventArgs : BaseCoreEventArgs + { + // ---------------- Fields ---------------- + + internal const string XmlRootName = "chaskis_SendPart_event"; + + // ---------------- Constructor ---------------- + + internal SendPartEventArgs() : + base() + { + this.Channel = null; + this.PartReason = null; + } + + // ---------------- Properties ---------------- + + public IIrcWriter Writer { get; internal set; } + + /// + /// The channel the bot left. + /// + public string Channel { get; internal set; } + + /// + /// The reason they bot left the channel. + /// Set to if there was no reason. + /// + public string PartReason { get; internal set; } + + protected override string XmlElementName => XmlRootName; + + protected override IEnumerable ChildToXml() + { + return new List + { + new XElement( "channel", this.Channel ), + new XElement( "reason", this.PartReason ) + }; + } + } + + /// + /// Extensions to + /// + internal static class SendPartEventArgsExtensions + { + public static SendPartEventArgs FromXml( string xmlString, IIrcWriter writer ) + { + SendPartEventArgs args = new SendPartEventArgs + { + Writer = writer + }; + + XElement root = BaseCoreEventArgs.ParseXml( args, xmlString ); + BaseCoreEventArgs.ParseBaseXml( args, root ); + + foreach( XElement child in root.Elements() ) + { + if( "channel".EqualsIgnoreCase( child.Name.LocalName ) ) + { + args.Channel = child.Value; + } + else if( "reason".EqualsIgnoreCase( child.Name.LocalName ) ) + { + args.PartReason = child.Value; + } + } + + if( ( args.Channel == null ) || ( args.PartReason == null ) ) + { + throw new ValidationException( + $"Could not find all required properties when creating {nameof( SendPartEventArgs )}" + ); + } + + return args; + } + } +} diff --git a/Chaskis/ChaskisCore/Handlers/SendPart/SendPartEventConfig.cs b/Chaskis/ChaskisCore/Handlers/SendPart/SendPartEventConfig.cs new file mode 100644 index 00000000..5a55532c --- /dev/null +++ b/Chaskis/ChaskisCore/Handlers/SendPart/SendPartEventConfig.cs @@ -0,0 +1,37 @@ +// +// Copyright Seth Hendrick 2020. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +using System.Collections.Generic; + +namespace Chaskis.Core +{ + /// + /// Event to configure + /// + public sealed class SendPartEventConfig : + BaseCoreEvent + { + // ---------------- Constructor ---------------- + + public SendPartEventConfig() + { + } + + // ---------------- Functions ---------------- + + public override SendPartEventConfig Clone() + { + return (SendPartEventConfig)this.MemberwiseClone(); + } + + protected override IEnumerable ValidateChild() + { + // Nothing to validate. + return null; + } + } +} diff --git a/Chaskis/ChaskisCore/Handlers/SendPart/SendPartEventHandler.cs b/Chaskis/ChaskisCore/Handlers/SendPart/SendPartEventHandler.cs new file mode 100644 index 00000000..35e78057 --- /dev/null +++ b/Chaskis/ChaskisCore/Handlers/SendPart/SendPartEventHandler.cs @@ -0,0 +1,41 @@ +// +// Copyright Seth Hendrick 2020. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +using System.Text.RegularExpressions; + +namespace Chaskis.Core +{ + public delegate void SendPartHandlerAction( SendPartEventArgs args ); + + /// + /// Event that gets fired when the bot joins a server. + /// + public sealed class SendPartEventHandler : BaseCoreEventHandler + { + // ---------------- Fields ---------------- + + private static readonly Regex regex = new Regex( + $@"^<{SendPartEventArgs.XmlRootName}>.+", + RegexOptions.ExplicitCapture | RegexOptions.Compiled | RegexOptions.IgnoreCase + ); + + // ---------------- Constructor ---------------- + + public SendPartEventHandler( SendPartEventConfig config ) : + base( config, regex ) + { + } + + // ---------------- Functions ---------------- + + protected override void HandleEventInternal( HandlerArgs args, Match match ) + { + SendPartEventArgs connectionArgs = SendPartEventArgsExtensions.FromXml( args.Line, args.IrcWriter ); + this.config.LineAction( connectionArgs ); + } + } +} diff --git a/Chaskis/ChaskisCore/IrcConnection.cs b/Chaskis/ChaskisCore/IrcConnection.cs index c13c91a1..8e9286d5 100644 --- a/Chaskis/ChaskisCore/IrcConnection.cs +++ b/Chaskis/ChaskisCore/IrcConnection.cs @@ -6,7 +6,6 @@ // using System; -using System.Collections.Generic; using System.IO; using System.Net.Sockets; using System.Threading; @@ -294,7 +293,7 @@ public void Connect() Thread.Sleep( this.Config.RateLimit ); } - this.ReadEvent( + this.OnReadLine( new FinishedJoiningChannelsEventArgs { Protocol = ChaskisEventProtocol.IRC, @@ -488,7 +487,20 @@ public void SendPong( string response ) public void SendPart( string reason, string channel ) { // TODO: Make reason string more smart if not specified. - string partString = string.Format( "PART {0} :{1}", channel, reason ?? this.Config.QuitMessage ); + reason = reason ?? this.Config.QuitMessage; + + this.OnReadLine( + new SendPartEventArgs + { + Channel = channel, + PartReason = reason, + Protocol = ChaskisEventProtocol.IRC, + Server = this.Config.Server, + Writer = this + }.ToXml() + ); + + string partString = string.Format( "PART {0} :{1}", channel, reason ); this.SendRawCmd( partString ); } @@ -719,24 +731,6 @@ public void SendChaskisEvent( ChaskisEvent e ) this.OnReadLine( s ); } - private void AddCoreEvent( string eventStr ) - { - ChaskisEvent e = new ChaskisEvent( - ChaskisEventSource.CORE, - ChaskisEventProtocol.IRC.ToString(), - string.Empty, // For BCAST - new Dictionary() - { - ["event_id"] = eventStr, - ["server"] = this.Config.Server, - ["nick"] = this.Config.Nick - }, - null - ); - - this.SendChaskisEvent( e ); - } - private void OnReadLine( string s ) { this.ReadEvent?.Invoke( s ); @@ -844,7 +838,7 @@ private void AttemptReconnect() timeoutMinutes++; } - this.ReadEvent( + this.OnReadLine( new ReconnectingEventArgs { Protocol = ChaskisEventProtocol.IRC, @@ -919,7 +913,7 @@ private void WriterQueue_OnError( Exception err ) private void Watchdog_OnFailure() { - this.ReadEvent( + this.OnReadLine( new WatchdogFailedEventArgs { Protocol = ChaskisEventProtocol.IRC, diff --git a/Chaskis/UnitTests/CoreTests/Handlers/SendPart/SendPartEventArgsTests.cs b/Chaskis/UnitTests/CoreTests/Handlers/SendPart/SendPartEventArgsTests.cs new file mode 100644 index 00000000..263ffd29 --- /dev/null +++ b/Chaskis/UnitTests/CoreTests/Handlers/SendPart/SendPartEventArgsTests.cs @@ -0,0 +1,116 @@ +// +// Copyright Seth Hendrick 2020. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +using System; +using Chaskis.Core; +using Moq; +using NUnit.Framework; +using SethCS.Exceptions; + +namespace Chaskis.UnitTests.CoreTests.Handlers.SendPart +{ + [TestFixture] + public class SendPartEventArgsTests + { + // ---------------- Fields ---------------- + + private const string channel = "#somechannel"; + private const string reason = "Some Reason"; + private const string server = "irc.somewhere.net"; + private const ChaskisEventProtocol protocol = ChaskisEventProtocol.IRC; + + private Mock mockWriter; + + // ---------------- Setup / Teardown ---------------- + + [SetUp] + public void TestSetup() + { + this.mockWriter = new Mock( MockBehavior.Strict ); + } + + [TearDown] + public void TestTeardown() + { + this.mockWriter = null; + } + + // ---------------- Tests ---------------- + + [Test] + public void XmlRoundTripTest() + { + SendPartEventArgs uut = new SendPartEventArgs + { + Protocol = protocol, + Server = server, + Writer = mockWriter.Object, + + Channel = channel, + PartReason = reason + }; + string xmlString = uut.ToXml(); + SendPartEventArgs postXml = SendPartEventArgsExtensions.FromXml( xmlString, mockWriter.Object ); + + Console.WriteLine( xmlString ); + + Assert.AreEqual( uut.Server, postXml.Server ); + Assert.AreEqual( uut.Protocol, postXml.Protocol ); + Assert.AreSame( uut.Writer, postXml.Writer ); + } + + [Test] + public void InvalidXmlRootName() + { + string xmlString = $"{server}{protocol}{channel}{reason}"; + + Assert.Throws( + () => SendPartEventArgsExtensions.FromXml( xmlString, mockWriter.Object ) + ); + } + + [Test] + public void MissingServerDuringXmlParsing() + { + string xmlString = $"{protocol}{channel}{reason}"; + + Assert.Throws( + () => SendPartEventArgsExtensions.FromXml( xmlString, mockWriter.Object ) + ); + } + + [Test] + public void MissingProtocolDuringXmlParsing() + { + string xmlString = $"{server}{channel}{reason}"; + + Assert.Throws( + () => SendPartEventArgsExtensions.FromXml( xmlString, mockWriter.Object ) + ); + } + + [Test] + public void MissingChannelDuringXmlParsing() + { + string xmlString = $"{server}{protocol}{reason}"; + + Assert.Throws( + () => SendPartEventArgsExtensions.FromXml( xmlString, mockWriter.Object ) + ); + } + + [Test] + public void MissingReasonDuringXmlParsing() + { + string xmlString = $"{server}{protocol}{channel}"; + + Assert.Throws( + () => SendPartEventArgsExtensions.FromXml( xmlString, mockWriter.Object ) + ); + } + } +} diff --git a/Chaskis/UnitTests/CoreTests/Handlers/SendPart/SendPartEventConfigTests.cs b/Chaskis/UnitTests/CoreTests/Handlers/SendPart/SendPartEventConfigTests.cs new file mode 100644 index 00000000..ae86cea4 --- /dev/null +++ b/Chaskis/UnitTests/CoreTests/Handlers/SendPart/SendPartEventConfigTests.cs @@ -0,0 +1,44 @@ +// +// Copyright Seth Hendrick 2020. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +using Chaskis.Core; +using NUnit.Framework; +using SethCS.Exceptions; + +namespace Chaskis.UnitTests.CoreTests.Handlers.SendPart +{ + [TestFixture] + public class SendPartEventConfigTests + { + // ---------------- Tests ---------------- + + [Test] + public void ValidateTest() + { + SendPartEventConfig config = new SendPartEventConfig + { + LineAction = null + }; + Assert.Throws( () => config.Validate() ); + + config.LineAction = delegate ( SendPartEventArgs args ) + { + }; + + Assert.DoesNotThrow( () => config.Validate() ); + } + + [Test] + public void CloneTest() + { + SendPartEventConfig config = new SendPartEventConfig(); + SendPartEventConfig clone = config.Clone(); + + Assert.AreNotSame( config, clone ); + } + } +} diff --git a/Chaskis/UnitTests/CoreTests/Handlers/SendPart/SendPartEventHandlerTests.cs b/Chaskis/UnitTests/CoreTests/Handlers/SendPart/SendPartEventHandlerTests.cs new file mode 100644 index 00000000..c183bf91 --- /dev/null +++ b/Chaskis/UnitTests/CoreTests/Handlers/SendPart/SendPartEventHandlerTests.cs @@ -0,0 +1,125 @@ +// +// Copyright Seth Hendrick 2020. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +using Chaskis.Core; +using Chaskis.UnitTests.Common; +using Moq; +using NUnit.Framework; +using SethCS.Exceptions; + +namespace Chaskis.UnitTests.CoreTests.Handlers.SendPart +{ + [TestFixture] + public class SendPartEventHandlerTests + { + // ---------------- Fields ---------------- + + private SendPartEventHandler uut; + + private IrcConfig ircConfig; + + private Mock ircWriter; + + private SendPartEventArgs responseReceived; + + private const string channel = "#somechannel"; + private const string reason = "Some Reason"; + + // ---------------- Setup / Teardown ---------------- + + [SetUp] + public void TestSetup() + { + this.ircConfig = TestHelpers.GetTestIrcConfig(); + this.ircWriter = new Mock( MockBehavior.Strict ); + this.responseReceived = null; + + SendPartEventConfig config = new SendPartEventConfig + { + LineAction = SendPartFunction + }; + + this.uut = new SendPartEventHandler( config ); + } + + [TearDown] + public void TestTeardown() + { + } + + // ---------------- Tests ---------------- + + [Test] + public void InvalidConfigTest() + { + Assert.Throws( + () => new SendPartEventHandler( new SendPartEventConfig() ) + ); + } + + [Test] + public void ConstructionTest() + { + // Keep Handling should be true by default. + Assert.IsTrue( this.uut.KeepHandling ); + } + + [Test] + public void SuccessTest() + { + SendPartEventArgs expectedArgs = new SendPartEventArgs + { + Protocol = ChaskisEventProtocol.IRC, + Server = "server", + Writer = this.ircWriter.Object, + + Channel = channel, + PartReason = reason + }; + + HandlerArgs handlerArgs = ConstructArgs( expectedArgs.ToXml() ); + + this.uut.HandleEvent( handlerArgs ); + + Assert.IsNotNull( this.responseReceived ); + Assert.AreEqual( expectedArgs.Server, this.responseReceived.Server ); + Assert.AreEqual( expectedArgs.Protocol, this.responseReceived.Protocol ); + Assert.AreEqual( expectedArgs.Channel, this.responseReceived.Channel ); + Assert.AreEqual( expectedArgs.PartReason, this.responseReceived.PartReason ); + Assert.AreSame( expectedArgs.Writer, this.responseReceived.Writer ); + } + + [Test] + public void FailureTest() + { + HandlerArgs handlerArgs = ConstructArgs( "lol" ); + + this.uut.HandleEvent( handlerArgs ); + + Assert.IsNull( this.responseReceived ); + } + + // ---------------- Test Helpers ---------------- + + private void SendPartFunction( SendPartEventArgs args ) + { + this.responseReceived = args; + } + + private HandlerArgs ConstructArgs( string line ) + { + HandlerArgs args = new HandlerArgs + { + Line = line, + IrcWriter = this.ircWriter.Object, + IrcConfig = this.ircConfig + }; + + return args; + } + } +}