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}>.+{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;
+ }
+ }
+}