From 30f929ac447df36356ea75ca24ad139855c4863c Mon Sep 17 00:00:00 2001 From: Evan Dixon Date: Sun, 23 Sep 2018 18:16:00 -0500 Subject: [PATCH 1/7] Set all to netcoreapp2.0 and remove watchog --- Ditto.sln | 8 ----- Ditto/Ditto.csproj | 11 +----- Ditto/shutdown.sh | 17 --------- Ditto/startup.sh | 15 -------- IrcDotNet/IrcDotNet.csproj | 2 +- Watchog/Program.cs | 72 -------------------------------------- Watchog/Watchog.csproj | 17 --------- 7 files changed, 2 insertions(+), 140 deletions(-) delete mode 100644 Ditto/shutdown.sh delete mode 100644 Ditto/startup.sh delete mode 100644 Watchog/Program.cs delete mode 100644 Watchog/Watchog.csproj diff --git a/Ditto.sln b/Ditto.sln index 7d3d490..89f559e 100644 --- a/Ditto.sln +++ b/Ditto.sln @@ -7,8 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ditto", "Ditto\Ditto.csproj EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IrcDotNet", "IrcDotNet\IrcDotNet.csproj", "{5A8EF7A9-4DFB-4DA6-A73B-C0028E26DBFA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Watchog", "Watchog\Watchog.csproj", "{4E71E8AA-FA91-4D78-A770-2E080E2D8263}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0E2536C6-C76A-46E2-99EA-2398F2437739}" ProjectSection(SolutionItems) = preProject README.MD = README.MD @@ -33,12 +31,6 @@ Global {5A8EF7A9-4DFB-4DA6-A73B-C0028E26DBFA}.Debug-IPv4|Any CPU.Build.0 = Debug-IPv4|Any CPU {5A8EF7A9-4DFB-4DA6-A73B-C0028E26DBFA}.Release|Any CPU.ActiveCfg = Release|Any CPU {5A8EF7A9-4DFB-4DA6-A73B-C0028E26DBFA}.Release|Any CPU.Build.0 = Release|Any CPU - {4E71E8AA-FA91-4D78-A770-2E080E2D8263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4E71E8AA-FA91-4D78-A770-2E080E2D8263}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4E71E8AA-FA91-4D78-A770-2E080E2D8263}.Debug-IPv4|Any CPU.ActiveCfg = Debug|Any CPU - {4E71E8AA-FA91-4D78-A770-2E080E2D8263}.Debug-IPv4|Any CPU.Build.0 = Debug|Any CPU - {4E71E8AA-FA91-4D78-A770-2E080E2D8263}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4E71E8AA-FA91-4D78-A770-2E080E2D8263}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Ditto/Ditto.csproj b/Ditto/Ditto.csproj index cd9f91f..5a9382c 100644 --- a/Ditto/Ditto.csproj +++ b/Ditto/Ditto.csproj @@ -3,7 +3,7 @@ Exe netcoreapp2.0 - win10-x64;linux-x64 + win-x64;linux-x64;debian.9-x64 Debug;Release;Debug-IPv4 @@ -16,13 +16,4 @@ - - - Always - - - Always - - - \ No newline at end of file diff --git a/Ditto/shutdown.sh b/Ditto/shutdown.sh deleted file mode 100644 index 0486478..0000000 --- a/Ditto/shutdown.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -pidFile='pids.pid' - -if [ -f $pidFile ]; -then - pids=`cat ${pidFile}` - - for pid in "${pids[@]}" - do - kill $pid - done - - rm $pidFile -else - echo "Process file wasn't found. Aborting..." -fi \ No newline at end of file diff --git a/Ditto/startup.sh b/Ditto/startup.sh deleted file mode 100644 index e0f5c27..0000000 --- a/Ditto/startup.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -basePath=$("pwd") -pidFile="${basePath}/pids.pid" - -if [ -f $pidFile ]; -then - echo "$pidFile already exists. Stop the process before attempting to start." -else - echo -n "" > $pidFile - cd ${basePath} - echo "Starting Ditto" - nohup ./Watchog Ditto noprompt >>WatchogExecution.log 2>&1 & - echo -n "$! " >> $pidFile -fi \ No newline at end of file diff --git a/IrcDotNet/IrcDotNet.csproj b/IrcDotNet/IrcDotNet.csproj index 3c4cf37..df5e001 100644 --- a/IrcDotNet/IrcDotNet.csproj +++ b/IrcDotNet/IrcDotNet.csproj @@ -2,7 +2,7 @@ 0.7.0 - netstandard1.5 + netcoreapp2.0 IrcDotNet IrcDotNet communication;irc;networking;ctcp diff --git a/Watchog/Program.cs b/Watchog/Program.cs deleted file mode 100644 index 6edcec1..0000000 --- a/Watchog/Program.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; - -namespace Watchog -{ - class Program - { - static void Main(string[] args) - { - try - { - if (args.Length < 2) - { - Console.WriteLine("Usage: Watchog [args]"); - return; - } - - Console.WriteLine($"[{DateTime.Now.ToString()}] Watchog started."); - - var p = new Process(); - - Console.CancelKeyPress += delegate - { - Console.WriteLine($"[{DateTime.Now.ToString()}] Received Control+C. Closing program."); - p.Close(); - if (!p.HasExited) - { - Console.WriteLine($"[{DateTime.Now.ToString()}] Program not running. Waiting for up to 10 seconds."); - p.WaitForExit(10 * 1000); - if (!p.HasExited) - { - p.Kill(); - Console.WriteLine($"[{DateTime.Now.ToString()}] Killed program."); - } - else - { - Console.WriteLine($"[{DateTime.Now.ToString()}] Program exited on its own. Program not killed."); - } - } - Console.WriteLine($"[{DateTime.Now.ToString()}] Watchog exiting."); - }; - - if (File.Exists(args[0])) - { - while (true) - { - p = new Process(); - p.StartInfo.FileName = args[0]; - p.StartInfo.Arguments = args[1]; - p.Start(); - p.WaitForExit(); - p.Dispose(); - p = null; - - // Program exited, and Watchog should restart - Console.WriteLine($"[{DateTime.Now.ToString()}] Program exited. Restarting..."); - } - } - else - { - throw new FileNotFoundException("Can't find the file on disk", args[0]); - } - } - catch (Exception ex) - { - Console.WriteLine($"[{DateTime.Now.ToString()}] Watchog encountered an exception: {ex.ToString()}"); - throw; - } - } - } -} diff --git a/Watchog/Watchog.csproj b/Watchog/Watchog.csproj deleted file mode 100644 index fb0a403..0000000 --- a/Watchog/Watchog.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - Exe - netcoreapp2.0 - win10-x64;linux-x64 - - - - ..\Ditto\bin\Debug\ - - - - ..\Ditto\bin\Release\ - - - From ab9c7fc5b6ba610cc3dc1b53c9bda96d7d3c15ca Mon Sep 17 00:00:00 2001 From: Evan Dixon Date: Sun, 23 Sep 2018 18:25:44 -0500 Subject: [PATCH 2/7] Update packages --- Ditto.sln | 8 - Ditto/Ditto.csproj | 7 +- IrcDotNet/CircularBufferStream.cs | 141 -- IrcDotNet/ClassDiagram.cd | 255 -- IrcDotNet/CollectionUtilities.cs | 13 - IrcDotNet/Collections/CollectionsUtilities.cs | 84 - IrcDotNet/Collections/ReadOnlyDictionary.cs | 298 --- IrcDotNet/Collections/ReadOnlySet.cs | 327 --- IrcDotNet/Ctcp/CtcpClient.cs | 599 ----- IrcDotNet/Ctcp/CtcpClientMessageProcessing.cs | 117 - IrcDotNet/Ctcp/CtcpClientMessageSending.cs | 76 - IrcDotNet/Ctcp/CtcpEventArgs.cs | 222 -- IrcDotNet/DebugUtilities.cs | 24 - IrcDotNet/IIrcFloodPreventer.cs | 19 - IrcDotNet/IIrcMessageReceiveHandler.cs | 26 - IrcDotNet/IIrcMessageReceiver.cs | 20 - IrcDotNet/IIrcMessageSendHandler.cs | 24 - IrcDotNet/IIrcMessageSource.cs | 14 - IrcDotNet/IIrcMessageTarget.cs | 14 - IrcDotNet/IrcChannel.cs | 633 ----- IrcDotNet/IrcChannelCollection.cs | 73 - IrcDotNet/IrcChannelInfo.cs | 36 - IrcDotNet/IrcChannelUser.cs | 154 -- IrcDotNet/IrcChannelUserCollection.cs | 35 - IrcDotNet/IrcClient.cs | 2064 ----------------- IrcDotNet/IrcClientMessageProcessing.cs | 1214 ---------- IrcDotNet/IrcClientMessageSending.cs | 590 ----- IrcDotNet/IrcDotNet.csproj | 28 - IrcDotNet/IrcEventArgs.cs | 670 ------ IrcDotNet/IrcLocalUser.cs | 505 ---- IrcDotNet/IrcNetworkInfo.cs | 53 - IrcDotNet/IrcRegistrationInfo.cs | 76 - IrcDotNet/IrcServer.cs | 38 - IrcDotNet/IrcServerInfo.cs | 37 - IrcDotNet/IrcServerStatisticalEntry.cs | 84 - IrcDotNet/IrcStandardFloodPreventer.cs | 86 - IrcDotNet/IrcTargetMask.cs | 121 - IrcDotNet/IrcUser.cs | 415 ---- IrcDotNet/IrcUserCollection.cs | 25 - IrcDotNet/IrcUtilities.cs | 62 - IrcDotNet/MessageProcessorAttribute.cs | 15 - IrcDotNet/Properties/Resources.Designer.cs | 348 --- IrcDotNet/Properties/Resources.resx | 213 -- IrcDotNet/ReflectionUtilities.cs | 44 - IrcDotNet/SafeLineReader.cs | 54 - IrcDotNet/StandardIrcClient.cs | 619 ----- IrcDotNet/TextUtilities.cs | 88 - IrcDotNet/TwitchIrcClient.cs | 58 - 48 files changed, 2 insertions(+), 10724 deletions(-) delete mode 100644 IrcDotNet/CircularBufferStream.cs delete mode 100644 IrcDotNet/ClassDiagram.cd delete mode 100644 IrcDotNet/CollectionUtilities.cs delete mode 100644 IrcDotNet/Collections/CollectionsUtilities.cs delete mode 100644 IrcDotNet/Collections/ReadOnlyDictionary.cs delete mode 100644 IrcDotNet/Collections/ReadOnlySet.cs delete mode 100644 IrcDotNet/Ctcp/CtcpClient.cs delete mode 100644 IrcDotNet/Ctcp/CtcpClientMessageProcessing.cs delete mode 100644 IrcDotNet/Ctcp/CtcpClientMessageSending.cs delete mode 100644 IrcDotNet/Ctcp/CtcpEventArgs.cs delete mode 100644 IrcDotNet/DebugUtilities.cs delete mode 100644 IrcDotNet/IIrcFloodPreventer.cs delete mode 100644 IrcDotNet/IIrcMessageReceiveHandler.cs delete mode 100644 IrcDotNet/IIrcMessageReceiver.cs delete mode 100644 IrcDotNet/IIrcMessageSendHandler.cs delete mode 100644 IrcDotNet/IIrcMessageSource.cs delete mode 100644 IrcDotNet/IIrcMessageTarget.cs delete mode 100644 IrcDotNet/IrcChannel.cs delete mode 100644 IrcDotNet/IrcChannelCollection.cs delete mode 100644 IrcDotNet/IrcChannelInfo.cs delete mode 100644 IrcDotNet/IrcChannelUser.cs delete mode 100644 IrcDotNet/IrcChannelUserCollection.cs delete mode 100644 IrcDotNet/IrcClient.cs delete mode 100644 IrcDotNet/IrcClientMessageProcessing.cs delete mode 100644 IrcDotNet/IrcClientMessageSending.cs delete mode 100644 IrcDotNet/IrcDotNet.csproj delete mode 100644 IrcDotNet/IrcEventArgs.cs delete mode 100644 IrcDotNet/IrcLocalUser.cs delete mode 100644 IrcDotNet/IrcNetworkInfo.cs delete mode 100644 IrcDotNet/IrcRegistrationInfo.cs delete mode 100644 IrcDotNet/IrcServer.cs delete mode 100644 IrcDotNet/IrcServerInfo.cs delete mode 100644 IrcDotNet/IrcServerStatisticalEntry.cs delete mode 100644 IrcDotNet/IrcStandardFloodPreventer.cs delete mode 100644 IrcDotNet/IrcTargetMask.cs delete mode 100644 IrcDotNet/IrcUser.cs delete mode 100644 IrcDotNet/IrcUserCollection.cs delete mode 100644 IrcDotNet/IrcUtilities.cs delete mode 100644 IrcDotNet/MessageProcessorAttribute.cs delete mode 100644 IrcDotNet/Properties/Resources.Designer.cs delete mode 100644 IrcDotNet/Properties/Resources.resx delete mode 100644 IrcDotNet/ReflectionUtilities.cs delete mode 100644 IrcDotNet/SafeLineReader.cs delete mode 100644 IrcDotNet/StandardIrcClient.cs delete mode 100644 IrcDotNet/TextUtilities.cs delete mode 100644 IrcDotNet/TwitchIrcClient.cs diff --git a/Ditto.sln b/Ditto.sln index 89f559e..8e779f4 100644 --- a/Ditto.sln +++ b/Ditto.sln @@ -5,8 +5,6 @@ VisualStudioVersion = 15.0.27004.2008 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ditto", "Ditto\Ditto.csproj", "{B4D93EE6-97DA-43DD-84CB-DCDC3E843BC8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IrcDotNet", "IrcDotNet\IrcDotNet.csproj", "{5A8EF7A9-4DFB-4DA6-A73B-C0028E26DBFA}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0E2536C6-C76A-46E2-99EA-2398F2437739}" ProjectSection(SolutionItems) = preProject README.MD = README.MD @@ -25,12 +23,6 @@ Global {B4D93EE6-97DA-43DD-84CB-DCDC3E843BC8}.Debug-IPv4|Any CPU.Build.0 = Debug-IPv4|Any CPU {B4D93EE6-97DA-43DD-84CB-DCDC3E843BC8}.Release|Any CPU.ActiveCfg = Release|Any CPU {B4D93EE6-97DA-43DD-84CB-DCDC3E843BC8}.Release|Any CPU.Build.0 = Release|Any CPU - {5A8EF7A9-4DFB-4DA6-A73B-C0028E26DBFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5A8EF7A9-4DFB-4DA6-A73B-C0028E26DBFA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5A8EF7A9-4DFB-4DA6-A73B-C0028E26DBFA}.Debug-IPv4|Any CPU.ActiveCfg = Debug-IPv4|Any CPU - {5A8EF7A9-4DFB-4DA6-A73B-C0028E26DBFA}.Debug-IPv4|Any CPU.Build.0 = Debug-IPv4|Any CPU - {5A8EF7A9-4DFB-4DA6-A73B-C0028E26DBFA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5A8EF7A9-4DFB-4DA6-A73B-C0028E26DBFA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Ditto/Ditto.csproj b/Ditto/Ditto.csproj index 5a9382c..b1bb472 100644 --- a/Ditto/Ditto.csproj +++ b/Ditto/Ditto.csproj @@ -9,11 +9,8 @@ - - - - - + + \ No newline at end of file diff --git a/IrcDotNet/CircularBufferStream.cs b/IrcDotNet/CircularBufferStream.cs deleted file mode 100644 index efb1003..0000000 --- a/IrcDotNet/CircularBufferStream.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System; -using System.IO; - -namespace IrcDotNet -{ - // Allows reading and writing to circular buffer as stream. - // Note: Stream is non-blocking and non-thread-safe. - internal class CircularBufferStream : Stream - { - // Buffer for storing data. - private long readPosition; - - // Current index within buffer for writing and reading. - private long writePosition; - - public CircularBufferStream(int length) - : this(new byte[length]) - { - } - - public CircularBufferStream(byte[] buffer) - { - Buffer = buffer; - writePosition = 0; - readPosition = 0; - } - - public byte[] Buffer { get; } - - public long WritePosition - { - get { return writePosition; } - set { writePosition = value%Buffer.Length; } - } - - public override long Position - { - get { return readPosition; } - set { readPosition = value%Buffer.Length; } - } - - public override long Length - { - get - { - var length = writePosition - readPosition; - return length < 0 ? Buffer.Length + length : length; - } - } - - public override bool CanSeek - { - get { return true; } - } - - public override bool CanWrite - { - get { return true; } - } - - public override bool CanRead - { - get { return true; } - } - - public override void Flush() - { - // - } - - public override long Seek(long offset, SeekOrigin origin) - { - switch (origin) - { - case SeekOrigin.Begin: - readPosition = offset%Buffer.Length; - break; - case SeekOrigin.End: - readPosition = (Buffer.Length - offset)%Buffer.Length; - break; - case SeekOrigin.Current: - readPosition = (readPosition + offset)%Buffer.Length; - break; - default: - throw new NotSupportedException(); - } - - return readPosition; - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - // Write block of bytes from given buffer into circular buffer, wrapping around when necessary. - int writeCount; - while ((writeCount = Math.Min(count, (int) (Buffer.Length - writePosition))) > 0) - { - var oldWritePosition = writePosition; - var newWritePosition = (writePosition + writeCount)%Buffer.Length; - if (newWritePosition > readPosition && oldWritePosition < readPosition) - { -#if !SILVERLIGHT && !NETSTANDARD1_5 - throw new InternalBufferOverflowException("The CircularBuffer was overflowed!"); -#else - throw new IOException("The CircularBuffer was overflowed!"); -#endif - } - System.Buffer.BlockCopy(buffer, offset, Buffer, (int) writePosition, writeCount); - writePosition = newWritePosition; - - offset += writeCount; - count -= writeCount; //writeCount <= count => now is count >=0 - } - } - - public override int Read(byte[] buffer, int offset, int count) - { - // Read block of bytes from circular buffer, wrapping around when necessary. - var totalReadCount = 0; - int readCount; - count = Math.Min(buffer.Length - offset, count); - while ((readCount = Math.Min(count, (int) Length)) > 0) - { - if (readCount > Buffer.Length - readPosition) - { - readCount = (int) (Buffer.Length - readPosition); - } - System.Buffer.BlockCopy(Buffer, (int) readPosition, buffer, offset, readCount); - readPosition = (readPosition + readCount)%Buffer.Length; - offset += readCount; - count = Math.Min(buffer.Length - offset, count); - totalReadCount += readCount; - } - return totalReadCount; - } - } -} \ No newline at end of file diff --git a/IrcDotNet/ClassDiagram.cd b/IrcDotNet/ClassDiagram.cd deleted file mode 100644 index 8db9736..0000000 --- a/IrcDotNet/ClassDiagram.cd +++ /dev/null @@ -1,255 +0,0 @@ - - - - - - - - - KowQMyEAQIALCFAEoBQIGI0BwUEFIkAEIQmCBSCBAwQ= - IrcChannel.cs - - - - - - - - - - AAEBAAAgAAACIAAEIBAIAAAA4AAAAEAAAIAAAAAACQg= - IrcChannelUser.cs - - - - - - - - - - AAAAAAAAAAAAAAgAAAAAAAAAAAEAAAAAAAAABAAAAAQ= - IrcChannelCollection.cs - - - - - - - - - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQ= - IrcUserCollection.cs - - - - - - - - - gAiAAIIAAICIJASEKCRUEIBIgQCIwEMUAQAABAEIgQQ= - IrcUser.cs - - - - - - - - - - Pkfc0nZ3LnOvU/rVuc/IW/PVv+/6M1496f9ev05r2/s= - IrcClient.cs - - - - - - - - - - IAAAAAAgAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - IrcChannelUserCollection.cs - - - - - - - - - CAgBggEAREADCGwAIDUMAIAEQUJAgUAMAAEAAAiBDwA= - IrcLocalUser.cs - - - - - - - gAAAAIAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAA= - IrcServer.cs - - - - - - - - - - AAAAAAAAAAAAAAAEAAAAEAAAAAABAAAAAQCAAAAAgAA= - IrcTargetMask.cs - - - - - - - - - - AABAFAAAAAAAAAAAAAAAAAAAAAAAAAAAgIAAAACCSAA= - IrcStandardFloodPreventer.cs - - - - - - - AAAAAAAAAAAgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - IrcRegistrationInfo.cs - - - - - - AAAAAAAAAAAQAAAAACAAAAAAAAAAAAAAAQAAAAAAAAA= - IrcRegistrationInfo.cs - - - - - - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAECAAAAAAAAAAA= - IrcRegistrationInfo.cs - - - - - - AAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAA= - IrcUtilities.cs - - - - - - AAAAAAAAIgAAAAAAAAAAAAAAgBAAAAAAAAAAAAACAAA= - TextUtilities.cs - - - - - - AAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAA= - ReflectionUtilities.cs - - - - - - - - - AQUICWQMCwGRQUCFYdACARBIACkcAAAQQgkAiACSmEA= - Ctcp\CtcpClient.cs - - - - - - AAAAAAAAAAAAAAAAAAAAAAQAAAAAAAIAAAAAACAAAAA= - IrcChannelInfo.cs - - - - - - AAAAAAAAAAAAAAQAAAAAAgEIAAAACAYBAAAAAAAAAAA= - IrcNetworkInfo.cs - - - - - - gAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAEAAAAA= - IrcServerInfo.cs - - - - - - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAA= - IrcServerStatisticalEntry.cs - - - - - - CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAA= - IIrcMessageReceiveHandler.cs - - - - - - AAAAAAAAQAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - IIrcMessageReceiver.cs - - - - - - AAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAA= - IIrcMessageSource.cs - - - - - - AAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAA= - IIrcMessageTarget.cs - - - - - - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAA= - IIrcMessageSendHandler.cs - - - - - - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAACAA= - IIrcFloodPreventer.cs - - - - - - AAAAAAAAAAAAAAAAQAAABAAAAAAAAAAAAAAAAAAAAAA= - IrcTargetMask.cs - - - - - - AAAAAAABAAACAAEAAIQABAAAAAAAAAgAQAAAAACQAEA= - IrcServerStatisticalEntry.cs - - - - \ No newline at end of file diff --git a/IrcDotNet/CollectionUtilities.cs b/IrcDotNet/CollectionUtilities.cs deleted file mode 100644 index 0d08286..0000000 --- a/IrcDotNet/CollectionUtilities.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace IrcDotNet -{ - internal static class CollectionUtilities - { - public static IDictionary Invert(this IDictionary dictionary) - { - return dictionary.ToDictionary(pair => pair.Value, pair => pair.Key); - } - } -} \ No newline at end of file diff --git a/IrcDotNet/Collections/CollectionsUtilities.cs b/IrcDotNet/Collections/CollectionsUtilities.cs deleted file mode 100644 index 9778b74..0000000 --- a/IrcDotNet/Collections/CollectionsUtilities.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace IrcDotNet.Collections -{ - /// - /// Contains common utilities for functionality relating to collections. - /// - public static class CollectionsUtilities - { - /// - /// Sets the value for the specified key in a dictionary. - /// If the given key already exists, overwrite its value; otherwise, add a new key/value pair. - /// - /// The type of keys in the dictionary. - /// The type of values in the dictionary.. - /// The dictionary in which to set the value. - /// The object to use as the key of the element to add/update. - /// The object to use as the value of the element to add/update. - public static void Set(this IDictionary dictionary, TKey key, TValue value) - { - if (dictionary == null) - throw new ArgumentNullException("collection"); - - if (dictionary.ContainsKey(key)) - dictionary[key] = value; - else - dictionary.Add(key, value); - } - - /// - /// Adds the specified items to the collection. - /// - /// The type of the items in the collection. - /// The collection to which to add the items. - /// A collection of items to add to . - public static void AddRange(this ICollection collection, IEnumerable range) - { - if (collection == null) - throw new ArgumentNullException("collection"); - if (range == null) - throw new ArgumentNullException("range"); - - foreach (var item in range) - collection.Add(item); - } - - /// - /// Removes the specified items from the collection. - /// - /// The type of the items in the collection. - /// The collection fom which to remove the items. - /// A collection of items to remove from . - public static void RemoveRange(this ICollection collection, IEnumerable range) - { - if (collection == null) - throw new ArgumentNullException("collection"); - if (range == null) - throw new ArgumentNullException("range"); - - foreach (var item in range) - collection.Remove(item); - } - - /// - /// Performs the specified action on each item in the collection. - /// - /// The type of the items in the collection. - /// The collection on whose items to perform the action. - /// The action to perform on each item of the collection. - public static void ForEach(this IEnumerable source, Action action) - { - if (source == null) - throw new ArgumentNullException("source"); - if (action == null) - throw new ArgumentNullException("action"); - - foreach (var item in source) - action(item); - } - } -} diff --git a/IrcDotNet/Collections/ReadOnlyDictionary.cs b/IrcDotNet/Collections/ReadOnlyDictionary.cs deleted file mode 100644 index b45ee27..0000000 --- a/IrcDotNet/Collections/ReadOnlyDictionary.cs +++ /dev/null @@ -1,298 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; - -#if !NETSTANDARD1_5 -using System.Runtime.Serialization; -#endif - -namespace IrcDotNet.Collections -{ - /// - /// Represents a read-only collection of keys and values. - /// - /// The type of the keys in the dictionary. - /// The type of the values in the dictionary. -#if !SILVERLIGHT && !NETSTANDARD1_5 - [Serializable()] -#endif - [DebuggerDisplay("Count = {Count}")] - public class ReadOnlyDictionary : IDictionary, ICollection>, - IEnumerable>, IDictionary, ICollection, IEnumerable -#if !SILVERLIGHT && !NETSTANDARD1_5 - , ISerializable, IDeserializationCallback -#endif - { - // Dictionary to expose as read-only. - private IDictionary dictionary; - - /// - /// Initializes a new instance of the class. - /// - /// The dictionary to wrap. - /// is . - public ReadOnlyDictionary(IDictionary dictionary) - { - if (dictionary == null) - throw new ArgumentNullException("dictionary"); - - this.dictionary = dictionary; - } - -#region IDictionary Members - - /// - /// Gets a collection containing the keys in the dictionary. - /// - /// A collection containing the keys in the dictionary. - public ICollection Keys - { - get { return this.dictionary.Keys; } - } - - /// - /// Gets a collection containing the values in the dictionary. - /// - /// A collection containing the values in the dictionary. - public ICollection Values - { - get { return this.dictionary.Values; } - } - - /// - /// Gets or sets the element with the specified key. - /// - /// The element with the specified key. - /// This operation is not supported on a read-only dictionary. - /// - public TValue this[TKey key] - { - get - { - return this.dictionary[key]; - } - set - { - throw new NotSupportedException(); - } - } - - /// - /// Determines whether the dictionary contains the specified key. - /// - /// The key to locate in the dictionary. - /// if the dictionary contains an element with the specified key; - /// , otherwise. - /// is . - public bool ContainsKey(TKey key) - { - if (key == null) - throw new ArgumentNullException("key"); - - return this.dictionary.ContainsKey(key); - } - - /// - /// Gets the value associated with the specified key. - /// - /// The key of the value to get. - /// When this method returns, contains the value associated with the specified key, if the - /// key is found; otherwise, the default value for the type of the value parameter. This parameter is passed - /// uninitialized. - /// if the dictionary contains an element with the specified key; - /// , otherwise. - /// is . - public bool TryGetValue(TKey key, out TValue value) - { - if (key == null) - throw new ArgumentNullException("key"); - - return this.dictionary.TryGetValue(key, out value); - } - - void IDictionary.Add(TKey key, TValue value) - { - throw new NotSupportedException(); - } - - bool IDictionary.Remove(TKey key) - { - throw new NotSupportedException(); - } - -#endregion - -#region ICollection> Members - - /// - /// Gets the number of key/value pairs contained in the dictionary. - /// - /// The number of key/value pairs contained in the dictionary. - public int Count - { - get { return this.dictionary.Count; } - } - - bool ICollection>.IsReadOnly - { - get { return true; } - } - - void ICollection>.Add(KeyValuePair item) - { - throw new NotSupportedException(); - } - - bool ICollection>.Remove(KeyValuePair item) - { - throw new NotSupportedException(); - } - - void ICollection>.Clear() - { - throw new NotSupportedException(); - } - - bool ICollection>.Contains(KeyValuePair item) - { - return this.dictionary.Contains(item); - } - - void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) - { - this.dictionary.CopyTo(array, arrayIndex); - } - -#endregion - -#region IEnumerable> Members - - /// - /// Returns an enumerator that iterates through the dictionary. - /// - /// An enumerator for the dictionary. - public IEnumerator> GetEnumerator() - { - return ((IEnumerable>)this.dictionary).GetEnumerator(); - } - -#endregion - -#region IDictionary Members - - ICollection IDictionary.Keys - { - get { return ((IDictionary)this.dictionary).Keys; } - } - - ICollection IDictionary.Values - { - get { return ((IDictionary)this.dictionary).Values; } - } - - bool IDictionary.IsFixedSize - { - get { return ((IDictionary)this.dictionary).IsFixedSize; } - } - - bool IDictionary.IsReadOnly - { - get { return true; } - } - - object IDictionary.this[object key] - { - get - { - return ((IDictionary)this.dictionary)[key]; - } - set - { - throw new NotSupportedException(); - } - } - - void IDictionary.Add(object key, object value) - { - throw new NotSupportedException(); - } - - void IDictionary.Remove(object key) - { - throw new NotSupportedException(); - } - - void IDictionary.Clear() - { - throw new NotSupportedException(); - } - - bool IDictionary.Contains(object key) - { - return ((IDictionary)this.dictionary).Contains(key); - } - - IDictionaryEnumerator IDictionary.GetEnumerator() - { - return ((IDictionary)this.dictionary).GetEnumerator(); - } - -#endregion - -#region ICollection Members - - void ICollection.CopyTo(Array array, int index) - { - ((ICollection)this.dictionary).CopyTo(array, index); - } - - int ICollection.Count - { - get { return ((ICollection)this.dictionary).Count; } - } - - bool ICollection.IsSynchronized - { - get { return ((ICollection)this.dictionary).IsSynchronized; } - } - - object ICollection.SyncRoot - { - get { return ((ICollection)this.dictionary).SyncRoot; } - } - -#endregion - -#region IEnumerable Members - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)this.dictionary).GetEnumerator(); - } - -#endregion - -#if !SILVERLIGHT && !NETSTANDARD1_5 - -#region ISerializable Members - - void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) - { - ((ISerializable)this.dictionary).GetObjectData(info, context); - } - -#endregion - -#region IDeserializationCallback Members - - void IDeserializationCallback.OnDeserialization(object sender) - { - ((IDeserializationCallback)this.dictionary).OnDeserialization(sender); - } - -#endregion - -#endif - } -} diff --git a/IrcDotNet/Collections/ReadOnlySet.cs b/IrcDotNet/Collections/ReadOnlySet.cs deleted file mode 100644 index ae812ff..0000000 --- a/IrcDotNet/Collections/ReadOnlySet.cs +++ /dev/null @@ -1,327 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; - -#if !NETSTANDARD1_5 -using System.Runtime.Serialization; -#endif - -namespace IrcDotNet.Collections -{ - /// - /// Represents a read-only set of values. - /// - /// The type of elements in the set. -#if !SILVERLIGHT && !NETSTANDARD1_5 - [Serializable()] -#endif - [DebuggerDisplay("Count = {Count}")] - public class ReadOnlySet : ISet, ICollection, IEnumerable, ICollection, IEnumerable -#if !SILVERLIGHT && !NETSTANDARD1_5 - , ISerializable, IDeserializationCallback -#endif - { - // Set to expose as read-only. - private ISet set; - - private object syncRoot = new object(); - - /// - /// Initializes a new instance of the class. - /// - /// The set to wrap. - /// is . - public ReadOnlySet(ISet set) - { - if (set == null) - throw new ArgumentNullException("set"); - - this.set = set; - } - - public override string ToString() - { - StringBuilder sb = new StringBuilder(); - - bool first = true; - foreach (T t in this) - { - if (!first) - sb.Append(' '); - - first = false; - sb.Append(t.ToString()); - } - - return sb.ToString(); - } - -#region ISet Members - - bool ISet.Add(T item) - { - throw new NotSupportedException(); - } - - void ISet.ExceptWith(IEnumerable other) - { - throw new NotSupportedException(); - } - - void ISet.SymmetricExceptWith(IEnumerable other) - { - throw new NotSupportedException(); - } - - void ISet.IntersectWith(IEnumerable other) - { - throw new NotSupportedException(); - } - - void ISet.UnionWith(IEnumerable other) - { - throw new NotSupportedException(); - } - - /// - /// Determines whether the set is a proper subset of the specified collection. - /// - /// The collection to compare to the current set. - /// - /// if the set is a proper subset of ; - /// , otherwise. - /// - /// is . - public bool IsProperSubsetOf(IEnumerable other) - { - if (other == null) - throw new ArgumentNullException("other"); - - return this.set.IsProperSubsetOf(other); - } - - /// - /// Determines whether the set is a proper superset of the specified collection. - /// - /// The collection to compare to the current set. - /// - /// if the set is a proper superset of ; - /// , otherwise. - /// - /// is . - public bool IsProperSupersetOf(IEnumerable other) - { - if (other == null) - throw new ArgumentNullException("other"); - - return this.set.IsProperSupersetOf(other); - } - - /// - /// Determines whether the set is a subset of the specified collection. - /// - /// The collection to compare to the current set. - /// - /// if the set is a subset of ; - /// , otherwise. - /// - /// is . - public bool IsSubsetOf(IEnumerable other) - { - if (other == null) - throw new ArgumentNullException("other"); - - return this.set.IsSubsetOf(other); - } - - /// - /// Determines whether the set is a superset of the specified collection. - /// - /// The collection to compare to the current set. - /// - /// if the set is a superset of ; - /// , otherwise. - /// - /// is . - public bool IsSupersetOf(IEnumerable other) - { - if (other == null) - throw new ArgumentNullException("other"); - - return this.set.IsSupersetOf(other); - } - - /// - /// Determines whether the set and the specified collection share common elements. - /// - /// The collection to compare to the current set. - /// - /// if the set and share at least one common element; - /// , otherwise. - /// - /// is . - public bool Overlaps(IEnumerable other) - { - if (other == null) - throw new ArgumentNullException("other"); - - return this.set.Overlaps(other); - } - - /// - /// Determines whether the set and the specified collection contain the same elements. - /// - /// The collection to compare to the current set. - /// - /// if the set and are equal; - /// , otherwise. - /// - /// is . - public bool SetEquals(IEnumerable other) - { - if (other == null) - throw new ArgumentNullException("other"); - - return this.set.SetEquals(other); - } - -#endregion - -#region ICollection Members - - /// - /// Gets the number of elements that are contained in the set. - /// - /// The number of elements that are contained in the set. - public int Count - { - get { return this.set.Count; } - } - - bool ICollection.IsReadOnly - { - get { return true; } - } - - void ICollection.Add(T item) - { - throw new NotSupportedException(); - } - - bool ICollection.Remove(T item) - { - throw new NotSupportedException(); - } - - void ICollection.Clear() - { - throw new NotSupportedException(); - } - - /// - /// Determines whether the set contains the specified element. - /// - /// The element to locate in the set. - /// if the set contains the specified element; - /// , otherwise. - /// is . - public bool Contains(T item) - { - if (item == null) - throw new ArgumentNullException("item"); - - return this.set.Contains(item); - } - - /// - public void CopyTo(T[] array) - { - CopyTo(array, 0); - } - - /// - /// Copies the elements of the set to an array. - /// - /// The one-dimensional array that is the destination of the elements copied from the - /// set. The array must have zero-based indexing. - /// The zero-based index in at which copying begins. - /// is . - /// is less than 0. - /// is greater than the length of the - /// destination array. - public void CopyTo(T[] array, int arrayIndex) - { - if (array == null) - throw new ArgumentNullException("item"); - - this.set.CopyTo(array, arrayIndex); - } - -#endregion - -#region IEnumerable Members - - /// - /// Returns an enumerator that iterates through the set. - /// - /// An enumerator for the set. - public IEnumerator GetEnumerator() - { - return ((IEnumerable)this.set).GetEnumerator(); - } - -#endregion - -#region ICollection Members - - void ICollection.CopyTo(Array array, int index) - { - this.set.CopyTo((T[])array, index); - } - - bool ICollection.IsSynchronized - { - get { return true; } - } - - object ICollection.SyncRoot - { - get { return this.syncRoot; } - } - -#endregion - -#region IEnumerable Members - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)this.set).GetEnumerator(); - } - -#endregion - -#if !SILVERLIGHT && !NETSTANDARD1_5 - -#region ISerializable Members - - void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) - { - ((ISerializable)this.set).GetObjectData(info, context); - } - -#endregion - -#region IDeserializationCallback Members - - void IDeserializationCallback.OnDeserialization(object sender) - { - ((IDeserializationCallback)this.set).OnDeserialization(sender); - } - -#endregion - -#endif - } -} diff --git a/IrcDotNet/Ctcp/CtcpClient.cs b/IrcDotNet/Ctcp/CtcpClient.cs deleted file mode 100644 index 67f813a..0000000 --- a/IrcDotNet/Ctcp/CtcpClient.cs +++ /dev/null @@ -1,599 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using IrcDotNet.Collections; -using IrcDotNet.Properties; - -namespace IrcDotNet.Ctcp -{ - /// - /// Represents a client that communicates with a server using CTCP (Client to Client Protocol), operating over an - /// IRC connection. - /// Do not inherit this class unless the protocol itself is being extended. - /// - /// - /// All collection objects must be locked on the object for thread-safety. - /// They can however be used safely without locking within event handlers. - /// - /// - /// - [DebuggerDisplay("{ToString(), nq}")] - public partial class CtcpClient - { - // Message indicating that no error occurred. - private const string messageNoError = "no error"; - - // Tag used for checking whether no error occurred for remote user. - private const string noErrorTag = "NO_ERROR"; - - // Character that marks start and end of tagged data. - private const char taggedDataDelimeterChar = '\x001'; - - // Information for low-level quoting of messages. - private const char lowLevelQuotingEscapeChar = '\x10'; - - // Information for CTCP-quoting of messages. - private const char ctcpQuotingEscapeChar = '\x5C'; - - private static readonly IDictionary lowLevelQuotedChars = new Dictionary - { - {'\0', '0'}, - {'\n', 'n'}, - {'\r', 'r'} - }; - - private static readonly IDictionary lowLevelDequotedChars = lowLevelQuotedChars.Invert(); - - private static readonly IDictionary ctcpQuotedChars = new Dictionary - { - {taggedDataDelimeterChar, 'a'} - }; - - private static readonly IDictionary ctcpDequotedChars = ctcpQuotedChars.Invert(); - - // IRC client for communication. - - // Dictionary of message processor routines, keyed by their command names. - private readonly Dictionary messageProcessors; - - /// - /// Initializes a new instance of the class. - /// - /// The IRC client by which the CTCP client should communicate. - public CtcpClient(IrcClient ircClient) - { - if (ircClient == null) - throw new ArgumentNullException("ircClient"); - - IrcClient = ircClient; - messageProcessors = new Dictionary( - StringComparer.OrdinalIgnoreCase); - - InitializeMessageProcessors(); - - IrcClient.Connected += ircClient_Connected; - IrcClient.Disconnected += ircClient_Disconnected; - } - - /// - /// Gets or sets information about the client version. - /// - /// Information about the client version. - public string ClientVersion { get; set; } - - /// - /// Gets or sets the IRC client by which the CTCP client should communicate. - /// - /// The IRC client. - public IrcClient IrcClient { get; } - - /// - /// Occurs when an action has been sent to a user. - /// - public event EventHandler ActionSent; - - /// - /// Occurs when an action has been received from a user. - /// - public event EventHandler ActionReceived; - - /// - /// Occurs when a response to a date/time request has been received from a user. - /// - public event EventHandler TimeResponseReceived; - - /// - /// Occurs when a response to a version request has been received from a user. - /// - public event EventHandler VersionResponseReceived; - - /// - /// Occurs when an error message has been received from a user. - /// - public event EventHandler ErrorMessageReceived; - - /// - /// Occurs when a ping response has been received from a user. - /// - public event EventHandler PingResponseReceived; - - /// - /// Occurs when a raw message has been sent to a user. - /// - public event EventHandler RawMessageSent; - - /// - /// Occurs when a raw message has been received from a user. - /// - public event EventHandler RawMessageReceived; - - /// - /// Occurs when the client encounters an error during execution. - /// - public event EventHandler Error; - - /// - /// - /// Sends an action message to the specified list of users. - /// - /// The user to which to send the request. - public void SendAction(IIrcMessageTarget user, string text) - { - SendMessageAction(new[] {user}, text); - } - - /// - /// Sends an action message to the specified list of users. - /// - /// A list of users to which to send the request. - /// The text of the message. - public void SendAction(IList users, string text) - { - SendMessageAction(users, text); - } - - /// - /// - /// Gets the local date/time of the specified user. - /// - /// The user to which to send the request. - public void GetTime(IIrcMessageTarget user) - { - GetTime(new[] {user}); - } - - /// - /// Gets the local date/time of the specified list of users. - /// - /// A list of users to which to send the request. - public void GetTime(IList users) - { - SendMessageTime(users, null, false); - } - - /// - /// - /// Gets the client version of the specified user. - /// - /// The user to which to send the request. - public void GetVersion(IIrcMessageTarget user) - { - GetVersion(new[] {user}); - } - - /// - /// Gets the client version of the specified list of users. - /// - /// A list of users to which to send the request. - public void GetVersion(IList users) - { - SendMessageVersion(users, null, false); - } - - /// - /// - /// Asks the specified user whether an error just occurred. - /// - /// The user to which to send the request. - public void CheckErrorOccurred(IIrcMessageTarget user) - { - CheckErrorOccurred(new[] {user}); - } - - /// - /// Asks the specified list of users whether an error just occurred. - /// - /// A list of users to which to send the request. - public void CheckErrorOccurred(IList users) - { - SendMessageErrMsg(users, noErrorTag, false); - } - - /// - /// - /// Pings the specified user. - /// - /// The user to which to send the request. - public void Ping(IIrcMessageTarget user) - { - Ping(new[] {user}); - } - - /// - /// Pings the specified list of users. - /// - /// A list of users to which to send the request. - public void Ping(IList users) - { - SendMessagePing(users, DateTime.Now.Ticks.ToString(), false); - } - - private void ircClient_Connected(object sender, EventArgs e) - { - if (IrcClient.LocalUser != null) - { - IrcClient.LocalUser.PreviewMessageReceived += ircClient_LocalUser_PreviewMessageReceived; - IrcClient.LocalUser.PreviewNoticeReceived += ircClient_LocalUser_PreviewNoticeReceived; - } - } - - private void ircClient_Disconnected(object sender, EventArgs e) - { - if (IrcClient.LocalUser != null) - { - IrcClient.LocalUser.PreviewMessageReceived -= ircClient_LocalUser_PreviewMessageReceived; - IrcClient.LocalUser.PreviewNoticeReceived -= ircClient_LocalUser_PreviewNoticeReceived; - } - } - - private void ircClient_LocalUser_PreviewMessageReceived(object sender, IrcPreviewMessageEventArgs e) - { - ReadMessage(e, false); - } - - private void ircClient_LocalUser_PreviewNoticeReceived(object sender, IrcPreviewMessageEventArgs e) - { - ReadMessage(e, true); - } - - private void InitializeMessageProcessors() - { - // Find each method defined as processor for CTCP message. - this.GetAttributedMethods().ForEach(item => - { - var attribute = item.Item1; - var methodDelegate = item.Item2; - - messageProcessors.Add(attribute.CommandName, methodDelegate); - }); - } - - private void ReadMessage(IrcPreviewMessageEventArgs previewMessageEventArgs, bool isNotice) - { - if (!(previewMessageEventArgs.Source is IrcUser)) - return; - - // Check if message represents tagged data. - if (previewMessageEventArgs.Text.First() == taggedDataDelimeterChar && - previewMessageEventArgs.Text.Last() == taggedDataDelimeterChar) - { - if (previewMessageEventArgs.Source is IrcUser) - { - var message = new CtcpMessage(); - message.Source = (IrcUser) previewMessageEventArgs.Source; - message.Targets = previewMessageEventArgs.Targets; - message.IsResponse = isNotice; - - // Parse tagged data into message. - var dequotedText = LowLevelDequote(CtcpDequote(previewMessageEventArgs.Text.Substring( - 1, previewMessageEventArgs.Text.Length - 2))); - var firstSpaceIndex = dequotedText.IndexOf(' '); - if (firstSpaceIndex == -1) - { - message.Tag = dequotedText; - message.Data = null; - } - else - { - message.Tag = dequotedText.Substring(0, firstSpaceIndex); - message.Data = dequotedText.Substring(firstSpaceIndex + 1).TrimStart(':'); - } - - ReadMessage(message); - previewMessageEventArgs.Handled = true; - } - } - } - - private void ReadMessage(CtcpMessage message) - { - OnRawMessageReceived(new CtcpRawMessageEventArgs(message)); - - // Try to find corresponding message processor for command of given message. - MessageProcessor messageProcessor; - if (messageProcessors.TryGetValue(message.Tag, out messageProcessor)) - { - try - { - messageProcessor(message); - } -#if !DEBUG - catch (Exception ex) - { - OnError(new IrcErrorEventArgs(ex)); - } -#endif - finally - { - } - } - else - { - // Command is unknown. - DebugUtilities.WriteEvent("Unknown CTCP message tag '{0}'.", message.Tag); - } - } - - /// - /// The tag of the message. - /// The data contained by the message. - /// - /// if the message is a response to another message; - /// , otherwise. - /// - protected void WriteMessage(IList targets, string tag, string data = null, - bool isResponse = false) - { - WriteMessage(targets, new CtcpMessage(IrcClient.LocalUser, targets, tag, data, isResponse)); - } - - /// - /// The message to write. - /// - /// contains more than 15 many parameters. - /// - /// - /// The value of of - /// is invalid. - /// - protected void WriteMessage(IList targets, CtcpMessage message) - { - if (message.Tag == null) - throw new ArgumentException(Resources.MessageInvalidTag, "message"); - - var tag = message.Tag.ToUpper(); - var taggedData = message.Data == null ? tag : tag + " :" + message.Data; - WriteMessage(targets, taggedData, message.IsResponse); - OnRawMessageSent(new CtcpRawMessageEventArgs(message)); - } - - /// - /// Writes the specified message to a target. - /// - /// A list of the targets to which to write the message. - /// The tagged data to write. - /// - /// if the message is a response to another message; - /// , otherwise. - /// - private void WriteMessage(IList targets, string taggedData, bool isResponse) - { - Debug.Assert(taggedData != null); - var text = taggedDataDelimeterChar + LowLevelQuote(CtcpQuote(taggedData)) + taggedDataDelimeterChar; - - if (isResponse) - IrcClient.LocalUser.SendNotice(targets, text); - else - IrcClient.LocalUser.SendMessage(targets, text); - } - - private string LowLevelQuote(string value) - { - return value.Quote(lowLevelQuotingEscapeChar, lowLevelQuotedChars); - } - - private string LowLevelDequote(string value) - { - return value.Dequote(lowLevelQuotingEscapeChar, lowLevelDequotedChars); - } - - private string CtcpQuote(string value) - { - return value.Quote(ctcpQuotingEscapeChar, ctcpQuotedChars); - } - - private string CtcpDequote(string value) - { - return value.Dequote(ctcpQuotingEscapeChar, ctcpDequotedChars); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnActionSent(CtcpMessageEventArgs e) - { - var handler = ActionSent; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnActionReceived(CtcpMessageEventArgs e) - { - var handler = ActionReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// - /// The instance containing the event data. - /// - protected virtual void OnTimeResponseReceived(CtcpTimeResponseReceivedEventArgs e) - { - var handler = TimeResponseReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// - /// The instance containing the event data. - /// - protected virtual void OnVersionResponseReceived(CtcpVersionResponseReceivedEventArgs e) - { - var handler = VersionResponseReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// - /// The instance containing the event - /// data. - /// - protected virtual void OnErrorMessageResponseReceived(CtcpErrorMessageReceivedEventArgs e) - { - var handler = ErrorMessageReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// - /// The instance containing the event data. - /// - protected virtual void OnPingResponseReceived(CtcpPingResponseReceivedEventArgs e) - { - var handler = PingResponseReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnRawMessageSent(CtcpRawMessageEventArgs e) - { - var handler = RawMessageSent; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnRawMessageReceived(CtcpRawMessageEventArgs e) - { - var handler = RawMessageReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnError(IrcErrorEventArgs e) - { - var handler = Error; - if (handler != null) - handler(this, e); - } - - /// - /// Returns a string representation of this instance. - /// - /// A string that represents this instance. - public override string ToString() - { - return string.Format("CTCP / {0}", IrcClient); - } - - /// - /// Represents a method that processes objects. - /// - /// The message to be processed. - protected delegate void MessageProcessor(CtcpMessage message); - - /// - /// Represents a raw CTCP message that is sent/received by . - /// - /// - [DebuggerDisplay("{ToString(), nq}")] - public struct CtcpMessage - { - /// - /// The user that sent the message. - /// - public IrcUser Source; - - /// - /// A list of users to which to send the message. - /// - public IList Targets; - - /// - /// The tag of the message, that specifies the kind of data it contains or the type of the request. - /// - public string Tag; - - /// - /// The data contained by the message. - /// - public string Data; - - /// - /// if this message is a response to another message; , - /// otherwise. - /// - public bool IsResponse; - - /// - /// Initializes a new instance of the structure. - /// - /// The source of the message. - /// A list of the targets of the message. - /// The tag of the message. - /// The data contained by the message, or for no data. - /// - /// if the message is a response to another message; - /// , otherwise. - /// - public CtcpMessage(IrcUser source, IList targets, string tag, string data, - bool isResponse) - { - Source = source; - Targets = targets; - Tag = tag; - Data = data; - IsResponse = isResponse; - } - - /// - /// Returns a string representation of this instance. - /// - /// A string that represents this instance. - public override string ToString() - { - return string.Format("{0} {1}", Tag, Data); - } - } - } -} \ No newline at end of file diff --git a/IrcDotNet/Ctcp/CtcpClientMessageProcessing.cs b/IrcDotNet/Ctcp/CtcpClientMessageProcessing.cs deleted file mode 100644 index ed9ce81..0000000 --- a/IrcDotNet/Ctcp/CtcpClientMessageProcessing.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using System.Diagnostics; - -namespace IrcDotNet.Ctcp -{ - // Defines all message processors for the client. - partial class CtcpClient - { - /// - /// Process ACTION messages received from a user. - /// - /// The message received from the user. - [MessageProcessor("action")] - protected internal void ProcessMessageAction(CtcpMessage message) - { - Debug.Assert(message.Data != null); - - if (!message.IsResponse) - { - var text = message.Data; - - OnActionReceived(new CtcpMessageEventArgs(message.Source, message.Targets, text)); - } - } - - /// - /// Process TIME messages received from a user. - /// - /// The message received from the user. - [MessageProcessor("time")] - protected internal void ProcessMessageTime(CtcpMessage message) - { - if (message.IsResponse) - { - var dateTime = message.Data; - - OnTimeResponseReceived(new CtcpTimeResponseReceivedEventArgs(message.Source, dateTime)); - } - else - { - var localDateTime = DateTimeOffset.Now.ToString("o"); - - SendMessageTime(new[] {message.Source}, localDateTime, true); - } - } - - /// - /// Process VERSION messages received from a user. - /// - /// The message received from the user. - [MessageProcessor("version")] - protected internal void ProcessMessageVersion(CtcpMessage message) - { - if (message.IsResponse) - { - var versionInfo = message.Data; - - OnVersionResponseReceived(new CtcpVersionResponseReceivedEventArgs(message.Source, versionInfo)); - } - else - { - if (ClientVersion != null) - { - SendMessageVersion(new[] {message.Source}, ClientVersion, true); - } - } - } - - /// - /// Process ERRMSG messages received from a user. - /// - /// The message received from the user. - [MessageProcessor("errmsg")] - protected internal void ProcessMessageErrMsg(CtcpMessage message) - { - Debug.Assert(message.Data != null); - - if (message.IsResponse) - { - // Get failed query and error message from data. - var parts = message.Data.SplitIntoPair(" :"); - var failedQuery = parts.Item1; - var errorMessage = parts.Item2; - - OnErrorMessageResponseReceived(new CtcpErrorMessageReceivedEventArgs(message.Source, - failedQuery, errorMessage)); - } - else - { - SendMessageErrMsg(new[] {message.Source}, message.Data + " :" + messageNoError, true); - } - } - - /// - /// Process PING messages received from a user. - /// - /// The message received from the user. - [MessageProcessor("ping")] - protected internal void ProcessMessagePing(CtcpMessage message) - { - Debug.Assert(message.Data != null); - - if (message.IsResponse) - { - // Calculate time elapsed since the ping request was sent. - var sendTime = new DateTime(long.Parse(message.Data)); - var pingTime = DateTime.Now - sendTime; - - OnPingResponseReceived(new CtcpPingResponseReceivedEventArgs(message.Source, pingTime)); - } - else - { - SendMessagePing(new[] {message.Source}, message.Data, true); - } - } - } -} \ No newline at end of file diff --git a/IrcDotNet/Ctcp/CtcpClientMessageSending.cs b/IrcDotNet/Ctcp/CtcpClientMessageSending.cs deleted file mode 100644 index 0736736..0000000 --- a/IrcDotNet/Ctcp/CtcpClientMessageSending.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Collections.Generic; - -namespace IrcDotNet.Ctcp -{ - // Defines all message senders for the client. - partial class CtcpClient - { - /// - /// Sends an action message to the specified target. - /// - /// A list of the targets of the message. - /// The message text. - protected void SendMessageAction(IList targets, string text) - { - WriteMessage(targets, "action", text); - - OnActionSent(new CtcpMessageEventArgs(IrcClient.LocalUser, targets, text)); - } - - /// - /// Sends a request for the local date/time to the specified target. - /// - /// A list of the targets of the message. - /// The information to send. - /// - /// if the message is a response; , - /// otherwise. - /// - protected void SendMessageTime(IList targets, string info, bool isResponse) - { - WriteMessage(targets, "time", info, isResponse); - } - - /// - /// Sends a request or response for information about the version of the client. - /// - /// A list of the targets of the message. - /// The information to send. - /// - /// if the message is a response; , - /// otherwise. - /// - protected void SendMessageVersion(IList targets, string info, bool isResponse) - { - WriteMessage(targets, "version", info, isResponse); - } - - /// - /// Sends a request for confirming that no error has occurred. - /// - /// A list of the targets of the message. - /// A tag that can be used for tracking the response. - /// - /// if the message is a response; , - /// otherwise. - /// - protected void SendMessageErrMsg(IList targets, string tag, bool isResponse) - { - WriteMessage(targets, "errmsg", tag, isResponse); - } - - /// - /// Sends a ping request or response to the specified target. - /// - /// A list of the targets of the message. - /// The information to send. - /// - /// if the message is a response; , - /// otherwise. - /// - protected void SendMessagePing(IList targets, string info, bool isResponse) - { - WriteMessage(targets, "ping", info, isResponse); - } - } -} \ No newline at end of file diff --git a/IrcDotNet/Ctcp/CtcpEventArgs.cs b/IrcDotNet/Ctcp/CtcpEventArgs.cs deleted file mode 100644 index af78537..0000000 --- a/IrcDotNet/Ctcp/CtcpEventArgs.cs +++ /dev/null @@ -1,222 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; - -namespace IrcDotNet.Ctcp -{ - /// - /// Provides data for events that are raised when a CTCP message or notice is sent or received. - /// - /// - public class CtcpMessageEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// The source of the message. - /// A list of the targets of the message. - /// The text of the message. - /// is . - /// is . - public CtcpMessageEventArgs(IrcUser source, IList targets, string text) - { - if (targets == null) - throw new ArgumentNullException("target"); - if (text == null) - throw new ArgumentNullException("text"); - - Source = source; - Targets = new ReadOnlyCollection(targets); - Text = text; - } - - /// - /// Gets the source of the message. - /// - /// The source of the message. - public IrcUser Source { get; private set; } - - /// - /// Gets a list of the targets of the message. - /// - /// The targets of the message. - public IList Targets { get; private set; } - - /// - /// Gets the text of the message. - /// - /// The text of the message. - public string Text { get; private set; } - } - - /// - /// Provides data for the event. - /// - /// - public class CtcpTimeResponseReceivedEventArgs : CtcpResponseReceivedEventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// The local date/time received from the user. - public CtcpTimeResponseReceivedEventArgs(IrcUser user, string dateTime) - : base(user) - { - DateTime = dateTime; - } - - /// - /// Gets the local date/time for the user. - /// - /// The local date/time for the user. - public string DateTime { get; private set; } - } - - /// - /// Provides data for the event. - /// - /// - public class CtcpVersionResponseReceivedEventArgs : CtcpResponseReceivedEventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// The information about the client version. - public CtcpVersionResponseReceivedEventArgs(IrcUser user, string versionInfo) - : base(user) - { - VersionInfo = versionInfo; - } - - /// - /// Gets the information about the client version of the user. - /// - /// The ping time. - public string VersionInfo { get; private set; } - } - - /// - /// Provides data for the event. - /// - /// - public class CtcpErrorMessageReceivedEventArgs : CtcpResponseReceivedEventArgs - { - /// - /// Initializes a new instance of the class, - /// specifying that no error occurred. - /// - /// The message indicating that no error occurred. - public CtcpErrorMessageReceivedEventArgs(IrcUser user, string noErrorMessage) - : base(user) - { - ErrorOccurred = false; - FailedQuery = null; - ErrorMessage = noErrorMessage; - } - - /// - /// Initializes a new instance of the class, - /// specifying the query that failed with an error message. - /// - /// A string containing the query that failed. - /// The message describing the error that occurred for the remote user. - public CtcpErrorMessageReceivedEventArgs(IrcUser user, string failedQuery, string errorMessage) - : base(user) - { - ErrorOccurred = true; - FailedQuery = failedQuery; - ErrorMessage = errorMessage; - } - - /// - /// Gets a value indicating whether an error occurred or the user confirmed that no error occurred. - /// - /// - /// if an error occurred; if the remote user confirmed - /// that no error occurred. - /// - public bool ErrorOccurred { get; private set; } - - /// - /// Gets a string containing the query that failed - /// - /// The failed query. - public string FailedQuery { get; private set; } - - /// - /// Gets message describing the error that occurred for the remote user. - /// - /// The error message. - public string ErrorMessage { get; private set; } - } - - /// - /// Provides data for the event. - /// - /// - public class CtcpPingResponseReceivedEventArgs : CtcpResponseReceivedEventArgs - { - /// - /// - /// Initializes a new instance of the class. - /// - /// The ping time. - public CtcpPingResponseReceivedEventArgs(IrcUser user, TimeSpan pingTime) - : base(user) - { - PingTime = pingTime; - } - - /// - /// Gets the duration of time elapsed between the sending of the ping request and the receiving of the ping - /// response. - /// - /// The ping time. - public TimeSpan PingTime { get; private set; } - } - - /// - /// Provides data for events that indicate a response to a CTCP request. - /// - /// - public class CtcpResponseReceivedEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// The user from which the response was received. - public CtcpResponseReceivedEventArgs(IrcUser user) - { - User = user; - } - - /// - /// Gets the user from which the response was received. - /// - /// The user from which the request was received. - public IrcUser User { get; private set; } - } - - /// - /// Provides data for the and - /// events. - /// - /// - public class CtcpRawMessageEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// The message that was sent/received. - public CtcpRawMessageEventArgs(CtcpClient.CtcpMessage message) - { - Message = message; - } - - /// - /// Gets the message that was sent/received by the client. - /// - /// The message that was sent/received by the client. - public CtcpClient.CtcpMessage Message { get; private set; } - } -} \ No newline at end of file diff --git a/IrcDotNet/DebugUtilities.cs b/IrcDotNet/DebugUtilities.cs deleted file mode 100644 index 11ff7c0..0000000 --- a/IrcDotNet/DebugUtilities.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Diagnostics; - -namespace IrcDotNet -{ - // Utilities for debugging execution. - // TODO: Use TraceSource here and configure trace listeners in test project. - internal static class DebugUtilities - { - [Conditional("DEBUG")] - public static void WriteIrcRawLine(IrcClient client, string line) - { -#if DEBUG - WriteEvent("({0}) {1}", client.ClientId, line); -#endif - } - - [Conditional("DEBUG")] - public static void WriteEvent(string message, params object[] args) - { - Debug.WriteLine("{0:HH:mm:ss} {1}", DateTime.Now, string.Format(message, args)); - } - } -} \ No newline at end of file diff --git a/IrcDotNet/IIrcFloodPreventer.cs b/IrcDotNet/IIrcFloodPreventer.cs deleted file mode 100644 index d1e3e7a..0000000 --- a/IrcDotNet/IIrcFloodPreventer.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace IrcDotNet -{ - /// - /// Defines a mechanism for preventing server floods by limiting the rate of outgoing raw messages from the client. - /// - public interface IIrcFloodPreventer - { - /// - /// Gets the time delay before which the client may currently send the next message. - /// - /// The time delay before the next message may be sent, in milliseconds. - long GetSendDelay(); - - /// - /// Notifies the flood preventer that a message has just been send by the client. - /// - void HandleMessageSent(); - } -} \ No newline at end of file diff --git a/IrcDotNet/IIrcMessageReceiveHandler.cs b/IrcDotNet/IIrcMessageReceiveHandler.cs deleted file mode 100644 index 31801df..0000000 --- a/IrcDotNet/IIrcMessageReceiveHandler.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; - -namespace IrcDotNet -{ - /// - /// Represents an object that handles messages and notices received by an IRC client. - /// - internal interface IIrcMessageReceiveHandler - { - /// - /// Handles the specified message that was received by the client. - /// - /// The source of the message. - /// A collection of the target of the message. - /// The text of the message. - void HandleMessageReceived(IIrcMessageSource source, IList targets, string text); - - /// - /// Handles the specified notice that was received by the client. - /// - /// The source of the notice. - /// A collection of the target of the notice. - /// The text of the message. - void HandleNoticeReceived(IIrcMessageSource source, IList targets, string text); - } -} \ No newline at end of file diff --git a/IrcDotNet/IIrcMessageReceiver.cs b/IrcDotNet/IIrcMessageReceiver.cs deleted file mode 100644 index 2eaecbc..0000000 --- a/IrcDotNet/IIrcMessageReceiver.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace IrcDotNet -{ - /// - /// Represents an object that raises an event when a message or notice has been received. - /// - public interface IIrcMessageReceiver - { - /// - /// Occurs when a message has been received by the object. - /// - event EventHandler MessageReceived; - - /// - /// Occurs when a notice has been received by the object. - /// - event EventHandler NoticeReceived; - } -} \ No newline at end of file diff --git a/IrcDotNet/IIrcMessageSendHandler.cs b/IrcDotNet/IIrcMessageSendHandler.cs deleted file mode 100644 index b829862..0000000 --- a/IrcDotNet/IIrcMessageSendHandler.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections.Generic; - -namespace IrcDotNet -{ - /// - /// Represents an object that handles messages and notices sent by an IRC client. - /// - internal interface IIrcMessageSendHandler - { - /// - /// Handles the specified message that was sent by the client. - /// - /// A collection of the target of the message. - /// The text of the message. - void HandleMessageSent(IList targets, string text); - - /// - /// Handles the specified notice that was sent by the client. - /// - /// A collection of the target of the notice. - /// The text of the message. - void HandleNoticeSent(IList targets, string text); - } -} \ No newline at end of file diff --git a/IrcDotNet/IIrcMessageSource.cs b/IrcDotNet/IIrcMessageSource.cs deleted file mode 100644 index 085501c..0000000 --- a/IrcDotNet/IIrcMessageSource.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace IrcDotNet -{ - /// - /// Represents the source of a message or notice sent by an IRC client. - /// - public interface IIrcMessageSource - { - /// - /// Gets the name of the source, as understood by the IRC protocol. - /// - /// The name of the source. - string Name { get; } - } -} \ No newline at end of file diff --git a/IrcDotNet/IIrcMessageTarget.cs b/IrcDotNet/IIrcMessageTarget.cs deleted file mode 100644 index a068cbc..0000000 --- a/IrcDotNet/IIrcMessageTarget.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace IrcDotNet -{ - /// - /// Represents the target of a message or notice sent by an IRC client. - /// - public interface IIrcMessageTarget - { - /// - /// Gets the name of the source, as understood by the IRC protocol. - /// - /// The name of the target. - string Name { get; } - } -} \ No newline at end of file diff --git a/IrcDotNet/IrcChannel.cs b/IrcDotNet/IrcChannel.cs deleted file mode 100644 index ec18cdd..0000000 --- a/IrcDotNet/IrcChannel.cs +++ /dev/null @@ -1,633 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics; -using System.Linq; -using IrcDotNet.Collections; - -namespace IrcDotNet -{ - /// - /// Represents an IRC channel that exists on a specific . - /// - /// - [DebuggerDisplay("{ToString(), nq}")] - public class IrcChannel : INotifyPropertyChanged, IIrcMessageTarget, IIrcMessageReceiveHandler, IIrcMessageReceiver - { - private IrcClient client; - - // Collection of current modes of channel. - private readonly HashSet modes; - - // Current topic of channel. - private string topic; - - private IrcChannelType type; - - // Collection of users that are currently members of this channel. - private readonly Collection users; - - internal IrcChannel(string name) - { - Name = name; - type = IrcChannelType.Unspecified; - modes = new HashSet(); - Modes = new ReadOnlySet(modes); - users = new Collection(); - Users = new IrcChannelUserCollection(this, users); - } - - /// - /// Gets the name of the channel. - /// - /// The name of the channel. - public string Name { get; } - - /// - /// Gets the type of the channel. - /// - /// The type of the channel. - public IrcChannelType Type - { - get { return type; } - private set - { - type = value; - OnPropertyChanged(new PropertyChangedEventArgs("Type")); - } - } - - /// - /// Gets the current topic of the channel. - /// - /// The current topic of the channel. - public string Topic - { - get { return topic; } - private set - { - topic = value; - OnPropertyChanged(new PropertyChangedEventArgs("Topic")); - } - } - - /// - /// Gets a read-only collection of the modes the channel currently has. - /// - /// The current modes of the channel. - public ReadOnlySet Modes { get; } - - /// - /// Gets a collection of all channel users currently in the channel. - /// - /// A collection of all users currently in the channel. - public IrcChannelUserCollection Users { get; } - - /// - /// Gets the client to which the channel belongs. - /// - /// The client to which the channel belongs. - public IrcClient Client - { - get { return client; } - internal set - { - client = value; - OnPropertyChanged(new PropertyChangedEventArgs("Client")); - } - } - - /// - /// Occurs when the channel has received a message. - /// - public event EventHandler MessageReceived; - - /// - /// Occurs when the channel has received a notice. - /// - public event EventHandler NoticeReceived; - - #region IIrcMessageTarget Members - - string IIrcMessageTarget.Name - { - get { return Name; } - } - - #endregion - - /// - /// Occurs when a property value changes. - /// - public event PropertyChangedEventHandler PropertyChanged; - - /// - /// Occurs when the list of users in the channel has been received. - /// The list of users is sent initially upon joining the channel, or on the request of the client. - /// - public event EventHandler UsersListReceived; - - /// - /// Occurs when any of the modes of the channel have changed. - /// - public event EventHandler ModesChanged; - - /// - /// Occurs when the topic of the channel has changed. - /// - public event EventHandler TopicChanged; - - /// - /// Occurs when a user has joined the channel. - /// - public event EventHandler UserJoined; - - /// - /// Occurs when a user has left the channel. - /// - public event EventHandler UserLeft; - - /// - /// Occurs when a user is kicked from the channel. - /// - public event EventHandler UserKicked; - - /// - /// Occurs when a user is invited to join the channel. - /// - public event EventHandler UserInvited; - - /// - /// Occurs when the channel has received a message, before the event. - /// - public event EventHandler PreviewMessageReceived; - - /// - /// Occurs when the channel has received a notice, before the event. - /// - public event EventHandler PreviewNoticeReceived; - - /// - /// Gets the in the channel that corresponds to the specified - /// , or if none is found. - /// - /// The for which to look. - /// - /// The in the channel that corresponds to the specified - /// , or if none is found. - /// - /// is . - public IrcChannelUser GetChannelUser(IrcUser user) - { - if (user == null) - throw new ArgumentNullException("user"); - - return users.SingleOrDefault(cu => cu.User == user); - } - - /// - /// The user to invite to the channel - public void Invite(IrcUser user) - { - Invite(user.NickName); - } - - /// - /// Invites the the specified user to the channel. - /// - /// The nick name of the user to invite. - public void Invite(string userNickName) - { - client.Invite(this, userNickName); - } - - /// - /// Kicks the specified user from the channel, giving the specified comment. - /// - /// The nick name of the user to kick from the channel. - /// The comment to give for the kick, or for none. - public void Kick(string userNickName, string comment = null) - { - client.Kick(this, new[] {userNickName}, comment); - } - - /// - /// Requests the current topic of the channel. - /// - public void GetTopic() - { - client.SetTopic(Name); - } - - /// - /// Sets the topic of the channel to the specified text. - /// - /// The new topic to set. - public void SetTopic(string newTopic) - { - client.SetTopic(Name, newTopic); - } - - /// - /// Requests a list of the current modes of the channel, or if is specified, the - /// settings for the specified modes. - /// - /// - /// The modes for which to get the current settings, or for all - /// current channel modes. - /// - public void GetModes(string modes = null) - { - client.GetChannelModes(this, modes); - } - - /// - public void SetModes(params char[] newModes) - { - SetModes((IEnumerable) newModes); - } - - /// - /// - /// A collection of mode characters that should become the new modes. - /// Any modes in the collection that are not currently set will be set, and any nodes not in the collection that - /// are currently set will be unset. - /// - /// is . - public void SetModes(IEnumerable newModes) - { - if (newModes == null) - throw new ArgumentNullException("newModes"); - - lock (((ICollection) Modes).SyncRoot) - SetModes(newModes.Except(modes), modes.Except(newModes)); - } - - /// - /// is . - /// is . - public void SetModes(IEnumerable setModes, IEnumerable unsetModes, - IEnumerable modeParameters = null) - { - if (setModes == null) - throw new ArgumentNullException("setModes"); - if (unsetModes == null) - throw new ArgumentNullException("unsetModes"); - - SetModes("+" + string.Join(string.Empty, setModes) + "-" + string.Join(string.Empty, unsetModes), - modeParameters); - } - - /// - public void SetModes(string modes, params string[] modeParameters) - { - if (modes == null) - throw new ArgumentNullException("modes"); - - SetModes(modes, (IEnumerable) modeParameters); - } - - /// - /// Sets the specified modes on the channel. - /// - /// - /// The mode string that specifies mode changes, which takes the form - /// `( "+" / "-" ) *( mode character )`. - /// - /// - /// A collection of parameters to he modes, or for no - /// parameters. - /// - /// is . - public void SetModes(string modes, IEnumerable modeParameters = null) - { - if (modes == null) - throw new ArgumentNullException("modes"); - - client.SetChannelModes(this, modes, modeParameters); - } - - /// - /// Leaves the channel, giving the specified comment. - /// - /// - /// The comment to send the server upon leaving the channel, or for - /// no comment. - /// - public void Leave(string comment = null) - { - client.Leave(new[] {Name}, comment); - } - - internal void HandleUserNameReply(IrcChannelUser channelUser) - { - lock (((ICollection) Modes).SyncRoot) - { - if (users.Contains(channelUser)) - { -#if SILVERLIGHT - Debug.Assert(false, "User already in channel."); -#else - Debug.Fail("User already in channel."); -#endif - return; - } - } - - channelUser.Channel = this; - lock (((ICollection) Users).SyncRoot) - users.Add(channelUser); - } - - internal void HandleTypeChanged(IrcChannelType type) - { - Type = type; - } - - internal void HandleUsersListReceived() - { - OnUsersListReceived(new EventArgs()); - } - - internal void HandleTopicChanged(IrcUser source, string newTopic) - { - Topic = newTopic; - - OnTopicChanged(new IrcUserEventArgs(source)); - } - - internal void HandleModesChanged(IrcUser source, string newModes, IEnumerable newModeParameters) - { - lock (((ICollection) Modes).SyncRoot) - modes.UpdateModes(newModes, newModeParameters, client.ChannelUserModes, - (add, mode, modeParameter) => users.Single( - cu => cu.User.NickName == modeParameter).HandleModeChanged(add, mode)); - - OnModesChanged(new IrcUserEventArgs(source)); - } - - internal void HandleUserJoined(IrcChannelUser channelUser) - { - lock (((ICollection) Modes).SyncRoot) - { - if (users.Contains(channelUser)) - { -#if SILVERLIGHT - Debug.Assert(false, "User already in channel."); -#else - Debug.Fail("User already in channel."); -#endif - return; - } - } - - channelUser.Channel = this; - lock (((ICollection) Users).SyncRoot) - users.Add(channelUser); - - OnUserJoined(new IrcChannelUserEventArgs(channelUser, null)); - } - - internal void HandleUserLeft(IrcUser user, string comment) - { - lock (((ICollection) Modes).SyncRoot) - HandleUserLeft(users.Single(u => u.User == user), comment); - } - - internal void HandleUserLeft(IrcChannelUser channelUser, string comment) - { - lock (((ICollection) Users).SyncRoot) - users.Remove(channelUser); - - OnUserLeft(new IrcChannelUserEventArgs(channelUser, comment)); - } - - internal void HandleUserKicked(IrcUser user, string comment) - { - lock (((ICollection) Modes).SyncRoot) - HandleUserKicked(users.Single(u => u.User == user), comment); - } - - internal void HandleUserKicked(IrcChannelUser channelUser, string comment) - { - lock (((ICollection) Users).SyncRoot) - users.Remove(channelUser); - - OnUserKicked(new IrcChannelUserEventArgs(channelUser, comment)); - } - - internal void HandleUserInvited(IrcUser user) - { - lock (((ICollection) Modes).SyncRoot) - OnUserInvited(new IrcUserEventArgs(user)); - } - - internal void HandleUserQuit(IrcChannelUser channelUser, string comment) - { - lock (((ICollection) Users).SyncRoot) - users.Remove(channelUser); - } - - internal void HandleMessageReceived(IIrcMessageSource source, IList targets, string text) - { - var previewEventArgs = new IrcPreviewMessageEventArgs(source, targets, text, Client.TextEncoding); - OnPreviewMessageReceived(previewEventArgs); - if (!previewEventArgs.Handled) - OnMessageReceived(new IrcMessageEventArgs(source, targets, text, Client.TextEncoding)); - } - - internal void HandleNoticeReceived(IIrcMessageSource source, IList targets, string text) - { - var previewEventArgs = new IrcPreviewMessageEventArgs(source, targets, text, Client.TextEncoding); - OnPreviewNoticeReceived(previewEventArgs); - if (!previewEventArgs.Handled) - OnNoticeReceived(new IrcMessageEventArgs(source, targets, text, Client.TextEncoding)); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnUsersListReceived(EventArgs e) - { - var handler = UsersListReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnModesChanged(IrcUserEventArgs e) - { - var handler = ModesChanged; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnTopicChanged(IrcUserEventArgs e) - { - var handler = TopicChanged; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnUserJoined(IrcChannelUserEventArgs e) - { - var handler = UserJoined; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnUserLeft(IrcChannelUserEventArgs e) - { - var handler = UserLeft; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnUserKicked(IrcChannelUserEventArgs e) - { - var handler = UserKicked; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnUserInvited(IrcUserEventArgs e) - { - var handler = UserInvited; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnMessageReceived(IrcMessageEventArgs e) - { - var handler = MessageReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnPreviewMessageReceived(IrcPreviewMessageEventArgs e) - { - var handler = PreviewMessageReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnNoticeReceived(IrcMessageEventArgs e) - { - var handler = NoticeReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnPreviewNoticeReceived(IrcPreviewMessageEventArgs e) - { - var handler = PreviewNoticeReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) - { - var handler = PropertyChanged; - if (handler != null) - handler(this, e); - } - - /// - /// Returns a string representation of this instance. - /// - /// A string that represents this instance. - public override string ToString() - { - return Name; - } - - #region IIrcMessageReceiveHandler Members - - void IIrcMessageReceiveHandler.HandleMessageReceived(IIrcMessageSource source, IList targets, - string text) - { - HandleMessageReceived(source, targets, text); - } - - void IIrcMessageReceiveHandler.HandleNoticeReceived(IIrcMessageSource source, IList targets, - string text) - { - HandleNoticeReceived(source, targets, text); - } - - #endregion - } - - /// - /// Defines the types of channels. Each channel may only be of a single type at any one time. - /// - public enum IrcChannelType - { - /// - /// The channel type is unspecified. - /// - Unspecified, - - /// - /// The channel is public. The server always lists this channel. - /// - Public, - - /// - /// The channel is private. The server never lists this channel. - /// - Private, - - /// - /// The channel is secret. The server never lists this channel and pretends it does not exist when responding to - /// queries. - /// - Secret - } -} \ No newline at end of file diff --git a/IrcDotNet/IrcChannelCollection.cs b/IrcDotNet/IrcChannelCollection.cs deleted file mode 100644 index d6b9385..0000000 --- a/IrcDotNet/IrcChannelCollection.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; - -namespace IrcDotNet -{ - /// - /// Represents a collection of objects. - /// - /// - /// - public class IrcChannelCollection : ReadOnlyCollection - { - internal IrcChannelCollection(IrcClient client, IList list) - : base(list) - { - Client = client; - } - - /// - /// Gets the client to which the collection of channels belongs. - /// - /// The client to which the collection of channels belongs. - public IrcClient Client { get; } - - /// - public void Join(params string[] channels) - { - Join((IEnumerable) channels); - } - - /// - /// A collection of the names of channels to join. - public void Join(IEnumerable channels) - { - Client.Join(channels); - } - - /// - public void Join(params Tuple[] channels) - { - Join((IEnumerable>) channels); - } - - /// - /// Joins the specified channels. - /// - /// A collection of 2-tuples of the names of channels to join and their keys. - public void Join(IEnumerable> channels) - { - Client.Join(channels); - } - - /// - public void Leave(params string[] channels) - { - Leave((IEnumerable) channels); - } - - /// - /// Leaves the specified channels, giving the specified comment. - /// - /// A collection of the names of channels to leave. - /// - /// The comment to send the server upon leaving the channel, or for - /// no comment. - /// - public void Leave(IEnumerable channels, string comment = null) - { - Client.Leave(channels, comment); - } - } -} \ No newline at end of file diff --git a/IrcDotNet/IrcChannelInfo.cs b/IrcDotNet/IrcChannelInfo.cs deleted file mode 100644 index 765f281..0000000 --- a/IrcDotNet/IrcChannelInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace IrcDotNet -{ - /// - /// Stores information about a particular channel on an IRC network. - /// - public struct IrcChannelInfo - { - /// - /// The name of the channel. - /// - public string Name; - - /// - /// The number of visible users in the channel. - /// - public int? VisibleUsersCount; - - /// - /// The current topic of the channel. - /// - public string Topic; - - /// - /// Initializes a new instance of the structure with the specified properties. - /// - /// The name of the channel. - /// The number of visible users in the channel. - /// The current topic of the channel. - public IrcChannelInfo(string name, int? visibleUsersCount, string topic) - { - Name = name; - VisibleUsersCount = visibleUsersCount; - Topic = topic; - } - } -} \ No newline at end of file diff --git a/IrcDotNet/IrcChannelUser.cs b/IrcDotNet/IrcChannelUser.cs deleted file mode 100644 index 16a816f..0000000 --- a/IrcDotNet/IrcChannelUser.cs +++ /dev/null @@ -1,154 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using IrcDotNet.Collections; - -namespace IrcDotNet -{ - /// - /// Represents an IRC user that exists on a specific channel on a specific . - /// - /// - /// - /// - [DebuggerDisplay("{ToString(), nq}")] - public class IrcChannelUser : INotifyPropertyChanged - { - private IrcChannel channel; - // Collection of channel modes currently active on user. - private readonly HashSet modes; - - internal IrcChannelUser(IrcUser user, IEnumerable modes = null) - { - User = user; - - this.modes = new HashSet(); - Modes = new ReadOnlySet(this.modes); - if (modes != null) - this.modes.AddRange(modes); - } - - /// - /// A read-only collection of the channel modes the user currently has. - /// - /// The current channel modes of the user. - public ReadOnlySet Modes { get; } - - /// - /// Gets or sets the channel. - /// - /// The channel. - public IrcChannel Channel - { - get { return channel; } - internal set - { - channel = value; - OnPropertyChanged(new PropertyChangedEventArgs("Channel")); - } - } - - /// - /// Gets the that is represented by the . - /// - /// The that is represented by the . - public IrcUser User { get; } - - /// - /// Occurs when a property value changes. - /// - public event PropertyChangedEventHandler PropertyChanged; - - /// - /// Occurs when the channel modes of the user have changed. - /// - public event EventHandler ModesChanged; - - /// - /// Kicks the user from the channel, giving the specified comment. - /// - /// The comment to give for the kick, or for none. - public void Kick(string comment = null) - { - channel.Kick(User.NickName, comment); - } - - /// - /// Gives the user operator privileges in the channel. - /// - public void Op() - { - channel.SetModes("+o", User.NickName); - } - - /// - /// Removes operator privileges from the user in the channel. - /// - public void DeOp() - { - channel.SetModes("-o", User.NickName); - } - - /// - /// Voices the user in the channel. - /// - public void Voice() - { - channel.SetModes("+v", User.NickName); - } - - /// - /// Devoices the user in the channel - /// - public void DeVoice() - { - channel.SetModes("-v", User.NickName); - } - - internal void HandleModeChanged(bool add, char mode) - { - lock (((ICollection) Modes).SyncRoot) - { - if (add) - modes.Add(mode); - else - modes.Remove(mode); - } - - OnModesChanged(new EventArgs()); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnModesChanged(EventArgs e) - { - var handler = ModesChanged; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) - { - var handler = PropertyChanged; - if (handler != null) - handler(this, e); - } - - /// - /// Returns a string representation of this instance. - /// - /// A string that represents this instance. - public override string ToString() - { - return string.Format("{0}/{1}", channel.Name, User.NickName); - } - } -} \ No newline at end of file diff --git a/IrcDotNet/IrcChannelUserCollection.cs b/IrcDotNet/IrcChannelUserCollection.cs deleted file mode 100644 index 24ff6aa..0000000 --- a/IrcDotNet/IrcChannelUserCollection.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; - -namespace IrcDotNet -{ - /// - /// Represents a collection of objects. - /// - /// - /// - public class IrcChannelUserCollection : ReadOnlyCollection - { - internal IrcChannelUserCollection(IrcChannel channel, IList list) - : base(list) - { - Channel = channel; - } - - /// - /// Gets the channel to which the collection of channel users belongs. - /// - /// The channel to which the collection of channel users belongs.. - public IrcChannel Channel { get; } - - /// - /// Gets a collection of all users that correspond to the channel users in the collection. - /// - /// A collection of users. - public IEnumerable GetUsers() - { - return Items.Select(channelUser => channelUser.User); - } - } -} \ No newline at end of file diff --git a/IrcDotNet/IrcClient.cs b/IrcDotNet/IrcClient.cs deleted file mode 100644 index e97ea99..0000000 --- a/IrcDotNet/IrcClient.cs +++ /dev/null @@ -1,2064 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Linq; -using System.Net; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using IrcDotNet.Collections; -using IrcDotNet.Properties; - -namespace IrcDotNet -{ - /// - /// Represents a client that communicates with a server using the IRC (Internet Relay Chat) protocol. - /// Do not inherit this class unless the protocol itself is being extended. - /// - /// - /// All collection objects must be locked on the object for thread-safety. - /// They can however be used safely without locking within event handlers. - /// - /// - [DebuggerDisplay("{ToString(), nq}")] - public abstract partial class IrcClient : IDisposable - { - // Maximum number of parameters that can be sent in single raw message. - private const int maxParamsCount = 15; - - // Default port on which to connect to IRC server. - public static readonly int DefaultPort = 6667; - - // Regular expressions used for extracting information from protocol messages. - protected static readonly string regexNickName; - protected static readonly string regexUserName; - protected static readonly string regexHostName; - protected static readonly string regexChannelName; - protected static readonly string regexTargetMask; - protected static readonly string regexServerName; - protected static readonly string regexNickNameId; - protected static readonly string regexUserNameId; - protected static readonly string regexMessagePrefix; - protected static readonly string regexMessageTarget; - - protected static readonly string isupportPrefix; - - // Non-zero if object has been disposed or is currently being disposed. - private int disposedFlag; - - // Prevents client from flooding server with messages by limiting send rate. - - // Dictionary of message processor routines, keyed by their command names. - private readonly Dictionary messageProcessors; - - // Dictionary of message processor routines, keyed by their numeric codes (000 to 999). - private readonly Dictionary numericMessageProcessors; - - static IrcClient() - { - regexNickName = @"(?[^!@]+)"; - regexUserName = @"(?[^!@]+)"; - regexHostName = @"(?[^%@]+)"; - regexChannelName = @"(?[#+!&].+)"; - regexTargetMask = @"(?[$#].+)"; - regexServerName = @"(?[^%@]+?\.[^%@]*)"; - regexNickNameId = string.Format(@"{0}(?:(?:!{1})?@{2})?", regexNickName, regexUserName, regexHostName); - regexUserNameId = string.Format(@"{0}(?:(?:%{1})?@{2}|%{1})", regexUserName, regexHostName, - regexServerName); - regexMessagePrefix = string.Format(@"^(?:{0}|{1})$", regexServerName, regexNickNameId); - regexMessageTarget = string.Format(@"^(?:{0}|{1}|{2}|{3})$", regexChannelName, regexUserNameId, - regexTargetMask, regexNickNameId); - - isupportPrefix = @"\((?.*)\)(?.*)"; - } - - /// - /// Initializes a new instance of the class. - /// - public IrcClient() - { - TextEncoding = Encoding.UTF8; - messageProcessors = new Dictionary( - StringComparer.OrdinalIgnoreCase); - numericMessageProcessors = new Dictionary(1000); - FloodPreventer = null; - - InitializeMessageProcessors(); - } - -#if DEBUG - public string ClientId { get; set; } -#endif - - /// - /// Gets whether the client connection has been registered with the server. - /// - /// - /// if the connection has been registered; , otherwise. - /// - public bool IsRegistered - { - get { return isRegistered; } - } - - /// - /// Gets the local user. The local user is the user managed by this client connection. - /// - /// The local user. - public IrcLocalUser LocalUser - { - get { return localUser; } - } - - /// - /// Gets the 'Welcome' message sent by the server. - /// This value is set after successful registration of the connection. - /// - /// The 'Welcome' message received from the server.. - public string WelcomeMessage { get; protected set; } - - /// - /// Gets the 'Your Host' message sent by the server. - /// This value is set after successful registration of the connection. - /// - /// The 'Your Host' message received from the server. - public string YourHostMessage { get; private set; } - - /// - /// Gets the 'Created' message sent by the server. - /// This value is set after successful registration of the connection. - /// - /// The 'Created' message received from the server. - public string ServerCreatedMessage { get; private set; } - - /// - /// Gets the host name of the server. - /// This value is set after successful registration of the connection. - /// - /// The host name given received from the server. - public string ServerName { get; private set; } - - /// - /// Gets the version of the server. - /// This value is set after successful registration of the connection. - /// - /// The version given received from the server. - public string ServerVersion { get; private set; } - - /// - /// Gets a collection of the user modes available on the server. - /// This value is set after successful registration of the connection. - /// - /// A list of user modes available on the server. - public IEnumerable ServerAvailableUserModes { get; private set; } - - /// - /// Gets a collection of the channel modes available on the server. - /// This value is set after successful registration of the connection. - /// - /// A list of channel modes available on the server. - public IEnumerable ServerAvailableChannelModes { get; private set; } - - /// - /// Gets a dictionary of the features supported by the server, keyed by feature name, as returned by the - /// ISUPPORT message. - /// This value is set after successful registration of the connection. - /// - /// A dictionary of features supported by the server. - public Collections.ReadOnlyDictionary ServerSupportedFeatures { get; private set; } - - /// - /// Gets a collection of channel modes that apply to users in a channel. - /// - /// A collection of channel modes that apply to users. - public ReadOnlyCollection ChannelUserModes { get; private set; } - - /// - /// Gets the Message of the Day (MOTD) sent by the server. - /// This value is set after successful registration of the connection. - /// - /// The Message of the Day sent by the server. - public string MessageOfTheDay - { - get { return motdBuilder.ToString(); } - } - - /// - /// Gets information about the IRC network that is given by the server. - /// This value is set after successful registration of the connection. - /// - /// The Message of the Day sent by the server. - public IrcNetworkInfo? NetworkInformation - { - get { return networkInformation; } - } - - /// - /// Gets a collection of all channels known to the client. - /// - /// A collection of known channels. - public IrcChannelCollection Channels { get; private set; } - - /// - /// Gets a collection of all users known to the client, including the local user. - /// - /// A collection of known users. - public IrcUserCollection Users { get; private set; } - - /// - /// Gets or sets an object that limits the rate of outgoing messages in order to prevent flooding the server. - /// The value is by default, which indicates that no flood prevention should be - /// performed. - /// - /// A flood preventer object. - public IIrcFloodPreventer FloodPreventer { get; set; } - - /// - /// Gets or sets the text encoding to use for reading from and writing to the network data stream. - /// - /// The text encoding of the network stream. - public Encoding TextEncoding { get; set; } - - /// - /// Gets whether the client is currently connected to a server. - /// - /// if the client is connected; , otherwise. - public abstract bool IsConnected { get; } - - /// - /// Gets whether the object has been disposed. - /// - /// - /// if the object has been disposed; - /// , otherwise. - /// - protected bool IsDisposed - { - get { return Interlocked.CompareExchange(ref disposedFlag, 0, 0) > 0; } - } - - /// - /// Releases all resources used by the object. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Finalizes an instance of the class. - /// - ~IrcClient() - { - Dispose(false); - } - - /// - /// Releases all resources used by the . - /// - /// - /// if the consumer is actively disposing the object; - /// if the garbage collector is finalizing the object. - /// - protected virtual void Dispose(bool disposing) - { - if (Interlocked.CompareExchange(ref disposedFlag, 1, 0) > 0) - return; - } - - /// - /// Occurs when the client has connected to the server. - /// - /// - /// Note that the object is not yet set when this event occurs, but is only accessible - /// when the event is raised. - /// - public event EventHandler Connected; - - /// - /// Occurs when the client has failed to connect to the server. - /// - public event EventHandler ConnectFailed; - - /// - /// Occurs when the client has disconnected from the server. - /// - public event EventHandler Disconnected; - - /// - /// Occurs when the client encounters an error during execution, while connected. - /// - public event EventHandler Error; - -#if !SILVERLIGHT - - /// - /// Occurs when the SSL certificate received from the server should be validated. - /// The certificate is automatically validated if this event is not handled. - /// - public event EventHandler ValidateSslCertificate; - -#endif - - /// - /// Occurs when a raw message has been sent to the server. - /// - public event EventHandler RawMessageSent; - - /// - /// Occurs when a raw message has been received from the server. - /// - public event EventHandler RawMessageReceived; - - /// - /// Occurs when a protocol (numeric) error is received from the server. - /// - public event EventHandler ProtocolError; - - /// - /// Occurs when an error message (ERROR command) is received from the server. - /// - public event EventHandler ErrorMessageReceived; - - /// - /// Occurs when the connection has been registered. - /// - /// - /// The object is set when this event occurs. - /// - public event EventHandler Registered; - - /// - /// Occurs when the client information has been received from the server, following registration. - /// - /// - /// Client information is accessible via , , - /// , , , - /// , and . - /// - public event EventHandler ClientInfoReceived; - - /// - /// Occurs when a bounce message is received from the server, telling the client to connect to a new server. - /// - public event EventHandler ServerBounce; - - /// - /// Occurs when a list of features supported by the server (ISUPPORT) has been received. - /// This event may be raised more than once after registration, depending on the size of the list received. - /// - public event EventHandler ServerSupportedFeaturesReceived; - - /// - /// Occurs when a ping query is received from the server. - /// The client automatically replies to pings from the server; this event is only a notification. - /// - public event EventHandler PingReceived; - - /// - /// Occurs when a pong reply is received from the server. - /// - public event EventHandler PongReceived; - - /// - /// Occurs when the Message of the Day (MOTD) has been received from the server. - /// - public event EventHandler MotdReceived; - - /// - /// Occurs when information about the IRC network has been received from the server. - /// - public event EventHandler NetworkInformationReceived; - - /// - /// Occurs when information about a specific server on the IRC network has been received from the server. - /// - public event EventHandler ServerVersionInfoReceived; - - /// - /// Occurs when the local date/time for a specific server has been received from the server. - /// - public event EventHandler ServerTimeReceived; - - /// - /// Occurs when a list of server links has been received from the server. - /// - public event EventHandler ServerLinksListReceived; - - /// - /// Occurs when server statistics have been received from the server. - /// - public event EventHandler ServerStatsReceived; - - /// - /// Occurs when a reply to a Who query has been received from the server. - /// - public event EventHandler WhoReplyReceived; - - /// - /// Occurs when a reply to a Who Is query has been received from the server. - /// - public event EventHandler WhoIsReplyReceived; - - /// - /// Occurs when a reply to a Who Was query has been received from the server. - /// - public event EventHandler WhoWasReplyReceived; - - /// - /// Occurs when a list of channels has been received from the server in response to a query. - /// - public event EventHandler ChannelListReceived; - - /// - public void ListChannels(params string[] channelNames) - { - CheckDisposed(); - - if (channelNames == null) - throw new ArgumentNullException("channelNames"); - - SendMessageList(channelNames); - } - - /// - /// Requests a list of information about the specified (or all) channels on the network. - /// - /// - /// The names of the channels to list, or to list all channels - /// on the network. - /// - public void ListChannels(IEnumerable channelNames = null) - { - CheckDisposed(); - - SendMessageList(channelNames); - } - - /// - /// Requests the Message of the Day (MOTD) from the specified server. - /// - /// - /// The name of the server from which to request the MOTD, or - /// for the current server. - /// - /// The current instance has already been disposed. - public void GetMessageOfTheDay(string targetServer = null) - { - CheckDisposed(); - - SendMessageMotd(targetServer); - } - - /// - /// Requests statistics about the connected IRC network. - /// If is specified, then the server only returns information about the part of - /// the network formed by the servers whose names match the mask; otherwise, the information concerns the whole - /// network - /// - /// - /// A wildcard expression for matching against server names, or - /// to match the entire network. - /// - /// - /// The name of the server to which to forward the message, or - /// for the current server. - /// - /// The current instance has already been disposed. - public void GetNetworkInfo(string serverMask = null, string targetServer = null) - { - CheckDisposed(); - - SendMessageLUsers(serverMask, targetServer); - } - - /// - /// Requests the version of the specified server. - /// - /// The name of the server whose version to request. - /// The current instance has already been disposed. - public void GetServerVersion(string targetServer = null) - { - CheckDisposed(); - - SendMessageVersion(targetServer); - } - - /// - /// Requests statistics about the specified server. - /// - /// - /// The query character that indicates which server statistics to return. - /// The set of valid query characters is dependent on the implementation of the particular IRC server. - /// - /// The name of the server whose statistics to request. - /// - /// The server may not accept the command if is unspecified. - /// - /// The current instance has already been disposed. - public void GetServerStatistics(char? query = null, string targetServer = null) - { - CheckDisposed(); - - SendMessageStats(query == null ? null : query.Value.ToString(), targetServer); - } - - /// - /// Requests a list of all servers known by the target server. - /// If is specified, then the server only returns information about the part of - /// the network formed by the servers whose names match the mask; otherwise, the information concerns the whole - /// network. - /// - /// - /// A wildcard expression for matching against server names, or - /// to match the entire network. - /// - /// - /// The name of the server to which to forward the request, or - /// for the current server. - /// - /// The current instance has already been disposed. - public void GetServerLinks(string serverMask = null, string targetServer = null) - { - CheckDisposed(); - - SendMessageLinks(serverMask, targetServer); - } - - /// - /// Requests the local time on the specified server. - /// - /// The name of the server whose local time to request. - /// The current instance has already been disposed. - public void GetServerTime(string targetServer = null) - { - CheckDisposed(); - - SendMessageTime(targetServer); - } - - /// - /// Sends a ping to the specified server. - /// - /// The name of the server to ping. - /// The current instance has already been disposed. - public void Ping(string targetServer = null) - { - CheckDisposed(); - - SendMessagePing(localUser.NickName, targetServer); - } - - /// - /// Sends a Who query to the server targeting the specified channel or user masks. - /// - /// - /// A wildcard expression for matching against channel names; or if none can be found, - /// host names, server names, real names, and nick names of users. If the value is , - /// all users are matched. - /// - /// - /// to match only server operators; - /// to match all users. - /// - /// The current instance has already been disposed. - public void QueryWho(string mask = null, bool onlyOperators = false) - { - CheckDisposed(); - - SendMessageWho(mask, onlyOperators); - } - - /// - public void QueryWhoIs(params string[] nickNameMasks) - { - CheckDisposed(); - - if (nickNameMasks == null) - throw new ArgumentNullException("nickNames"); - - QueryWhoIs((IEnumerable) nickNameMasks); - } - - /// Sends a Who Is query to the server. - /// - /// Sends a Who Is query to server targeting the specified nick name masks. - /// - /// - /// A collection of wildcard expressions for matching against nick names of users. - /// - /// The current instance has already been disposed. - /// is . - public void QueryWhoIs(IEnumerable nickNameMasks) - { - CheckDisposed(); - - if (nickNameMasks == null) - throw new ArgumentNullException("nickNames"); - - SendMessageWhoIs(nickNameMasks); - } - - /// - public void QueryWhoWas(params string[] nickNames) - { - CheckDisposed(); - - if (nickNames == null) - throw new ArgumentNullException("nickNames"); - - QueryWhoWas((IEnumerable) nickNames); - } - - /// - /// Sends a Who Was query to server targeting the specified nick names. - /// - /// The nick names of the users to query. - /// - /// The maximum number of entries to return from the query. A negative value - /// specifies to return an unlimited number of entries. - /// - /// The current instance has already been disposed. - /// is . - public void QueryWhoWas(IEnumerable nickNames, int entriesCount = -1) - { - CheckDisposed(); - - if (nickNames == null) - throw new ArgumentNullException("nickNames"); - - SendMessageWhoWas(nickNames, entriesCount); - } - - /// - /// - /// Quits the server, giving the specified comment. Waits the specified duration of time before forcibly - /// disconnecting. - /// - /// The number of milliseconds to wait before forcibly disconnecting. - /// - /// - /// The current instance has already been disposed. - public virtual void Quit(int timeout, string comment = null) - { - SendMessageQuit(comment); - } - - /// - /// Quits the server, giving the specified comment. - /// - /// The comment to send to the server. - /// - /// Note that because this message is not sent immediately, calling immediately after - /// this will likely disconnect the client before it has a chance to quit the server properly. - /// Quitting the server should automatically disconnect the client. - /// - /// The current instance has already been disposed. - public void Quit(string comment = null) - { - Quit(0, comment); - } - - /// - /// Sends the specified raw message to the server. - /// - /// The text (single line) of the message to send the server. - /// The current instance has already been disposed. - /// is . - public void SendRawMessage(string message) - { - CheckDisposed(); - - if (message == null) - throw new ArgumentNullException("message"); - - WriteMessage(message); - } - - /// - /// Handles the specified statistical entry for the server, received in response to a STATS message. - /// - /// The type of the statistical entry for the server. - /// The message that contains the statistical entry. - protected void HandleStatsEntryReceived(int type, IrcMessage message) - { - // Add statistical entry to temporary list. - listedStatsEntries.Add(new IrcServerStatisticalEntry - { - Type = type, - Parameters = message.Parameters.Skip(1).ToArray() - }); - } - - /// - /// Handles the specified parameter value of an ISUPPORT message, received from the server upon registration. - /// - /// The name of the parameter. - /// - /// The value of the parameter, or if it does not have a value. - /// - protected bool HandleISupportParameter(string paramName, string paramValue) - { - if (paramName == null) - throw new ArgumentNullException("paramName"); - if (paramName.Length == 0) - throw new ArgumentException(Resources.MessageValueCannotBeEmptyString, "paramName"); - - // Check name of parameter. - switch (paramName.ToLowerInvariant()) - { - case "prefix": - var prefixValueMatch = Regex.Match(paramValue, isupportPrefix); - ; - var prefixes = prefixValueMatch.Groups["prefixes"].GetValue(); - var modes = prefixValueMatch.Groups["modes"].GetValue(); - - if (prefixes.Length != modes.Length) - throw new ProtocolViolationException(Resources.MessageISupportPrefixInvalid); - - lock (((ICollection) ChannelUserModes).SyncRoot) - { - channelUserModes.Clear(); - channelUserModes.AddRange(modes); - } - - channelUserModesPrefixes.Clear(); - for (var i = 0; i < prefixes.Length; i++) - channelUserModesPrefixes.Add(prefixes[i], modes[i]); - - return true; - default: - return false; - } - } - - /// - /// Extracts the the mode and nick name of a user from the specified value. - /// - /// The input value, containing a nick name optionally prefixed by a mode character. - /// A 2-tuple of the nick name and user mode. - protected Tuple GetUserModeAndNickName(string input) - { - if (input == null) - throw new ArgumentNullException("input"); - if (input.Length == 0) - throw new ArgumentException(Resources.MessageValueCannotBeEmptyString, "input"); - - char mode; - if (channelUserModesPrefixes.TryGetValue(input[0], out mode)) - return Tuple.Create(input.Substring(1), mode.ToString()); - return Tuple.Create(input, string.Empty); - } - - /// - /// Gets a collection of mode characters and mode parameters from the specified mode parameters. - /// Combines multiple mode strings into a single mode string. - /// - /// - /// A collection of message parameters, which consists of mode strings and mode - /// parameters. A mode string is of the form `( "+" / "-" ) *( mode character )`, and specifies mode changes. - /// A mode parameter is arbitrary text associated with a certain mode. - /// - /// - /// A 2-tuple of a single mode string and a collection of mode parameters. - /// Each mode parameter corresponds to a single mode character, in the same order. - /// - protected Tuple> GetModeAndParameters(IEnumerable messageParameters) - { - if (messageParameters == null) - throw new ArgumentNullException("messageParameters"); - - var modes = new StringBuilder(); - var modeParameters = new List(); - foreach (var p in messageParameters) - { - if (p == null) - break; - if (p.Length == 0) - continue; - if (p[0] == '+' || p[0] == '-') - modes.Append(p); - else - modeParameters.Add(p); - } - return Tuple.Create(modes.ToString(), (IEnumerable) modeParameters.AsReadOnly()); - } - - /// - /// Gets a list of channel objects from the specified comma-separated list of channel names. - /// - /// A value that contains a comma-separated list of names of channels. - /// A list of channel objects that corresponds to the given list of channel names. - protected IEnumerable GetChannelsFromList(string namesList) - { - if (namesList == null) - throw new ArgumentNullException("namesList"); - - return namesList.Split(',').Select(n => GetChannelFromName(n)); - } - - /// - /// Gets a list of user objects from the specified comma-separated list of nick names. - /// - /// A value that contains a comma-separated list of nick names of users. - /// A list of user objects that corresponds to the given list of nick names. - protected IEnumerable GetUsersFromList(string nickNamesList) - { - if (nickNamesList == null) - throw new ArgumentNullException("nickNamesList"); - - lock (((ICollection) Users).SyncRoot) - return nickNamesList.Split(',').Select(n => users.Single(u => u.NickName == n)); - } - - /// - /// Determines whether the specified name refers to a channel. - /// - /// The name to check. - /// - /// if the specified name represents a channel; , - /// otherwise. - /// - protected bool IsChannelName(string name) - { - if (name == null) - throw new ArgumentNullException("name"); - - return Regex.IsMatch(name, regexChannelName); - } - - /// - /// Gets the type of the channel from the specified character. - /// - /// - /// A character that represents the type of the channel. - /// The character may be one of the following: - /// - /// - /// Character - /// Channel type - /// - /// - /// = - /// Public channel - /// - /// - /// * - /// Private channel - /// - /// - /// @ - /// Secret channel - /// - /// - /// - /// The channel type that corresponds to the specified character. - /// - /// does not correspond to any known channel type. - /// - protected IrcChannelType GetChannelType(char type) - { - switch (type) - { - case '=': - return IrcChannelType.Public; - case '*': - return IrcChannelType.Private; - case '@': - return IrcChannelType.Secret; - default: - throw new ArgumentException(string.Format( - Resources.MessageInvalidChannelType, type), "type"); - } - } - - /// - /// Gets the target of a message from the specified name. - /// A message target may be an , , or . - /// - /// The name of the target. - /// The target object that corresponds to the given name. - /// - /// does not represent a valid message target. - /// - protected IIrcMessageTarget GetMessageTarget(string targetName) - { - if (targetName == null) - throw new ArgumentNullException("targetName"); - if (targetName.Length == 0) - throw new ArgumentException(Resources.MessageValueCannotBeEmptyString, "targetName"); - - // Check whether target name represents channel, user, or target mask. - var targetNameMatch = Regex.Match(targetName, regexMessageTarget); - var channelName = targetNameMatch.Groups["channel"].GetValue(); - var nickName = targetNameMatch.Groups["nick"].GetValue(); - var userName = targetNameMatch.Groups["user"].GetValue(); - var hostName = targetNameMatch.Groups["host"].GetValue(); - var serverName = targetNameMatch.Groups["server"].GetValue(); - var targetMask = targetNameMatch.Groups["targetMask"].GetValue(); - if (channelName != null) - { - return GetChannelFromName(channelName); - } - if (nickName != null) - { - // Find user by nick name. If no user exists in list, create it and set its properties. - var user = GetUserFromNickName(nickName, true); - if (user.UserName == null) - user.UserName = userName; - if (user.HostName == null) - user.HostName = hostName; - - return user; - } - if (userName != null) - { - // Find user by user name. If no user exists in list, create it and set its properties. - var user = GetUserFromNickName(nickName, true); - if (user.HostName == null) - user.HostName = hostName; - - return user; - } - if (targetMask != null) - { - return new IrcTargetMask(targetMask); - } - throw new ArgumentException(string.Format( - Resources.MessageInvalidSource, targetName), "targetName"); - } - - /// - /// Gets the source of a message from the specified prefix. - /// A message source may be a or . - /// - /// The raw prefix of the message. - /// - /// The message source that corresponds to the specified prefix. The object is an instance of - /// or . - /// - /// - /// does not represent a valid message source. - /// - protected IIrcMessageSource GetSourceFromPrefix(string prefix) - { - if (prefix == null) - return null; - if (prefix.Length == 0) - throw new ArgumentException(Resources.MessageValueCannotBeEmptyString, "prefix"); - - // Check whether prefix represents server or user. - var prefixMatch = Regex.Match(prefix, regexMessagePrefix); - var serverName = prefixMatch.Groups["server"].GetValue(); - var nickName = prefixMatch.Groups["nick"].GetValue(); - var userName = prefixMatch.Groups["user"].GetValue(); - var hostName = prefixMatch.Groups["host"].GetValue(); - if (serverName != null) - { - return GetServerFromHostName(serverName); - } - if (nickName != null) - { - // Find user by nick name. If no user exists in list, create it and set its properties. - var user = GetUserFromNickName(nickName, true); - if (user.UserName == null) - user.UserName = userName; - if (user.HostName == null) - user.HostName = hostName; - - return user; - } - throw new ArgumentException(string.Format( - Resources.MessageInvalidSource, prefix), "prefix"); - } - - /// - protected IrcServer GetServerFromHostName(string hostName) - { - bool createdNew; - return GetServerFromHostName(hostName, out createdNew); - } - - /// - /// Gets the server with the specified host name, creating it if necessary. - /// - /// The host name of the server. - /// - /// if the server object was created during the call; - /// , otherwise. - /// - /// The server object that corresponds to the specified host name. - protected IrcServer GetServerFromHostName(string hostName, out bool createdNew) - { - if (hostName == null) - throw new ArgumentNullException("hostName"); - if (hostName.Length == 0) - throw new ArgumentException(Resources.MessageValueCannotBeEmptyString, "hostName"); - - // Search for server with given name in list of known servers. If it does not exist, add it. - var server = servers.SingleOrDefault(s => s.HostName == hostName); - if (server == null) - { - server = new IrcServer(hostName); - servers.Add(server); - - createdNew = true; - } - else - { - createdNew = false; - } - return server; - } - - /// - protected IrcChannel GetChannelFromName(string channelName) - { - bool createdNew; - return GetChannelFromName(channelName, out createdNew); - } - - /// - /// Gets the channel with the specified name, creating it if necessary. - /// - /// The name of the channel. - /// - /// if the channel object was created during the call; - /// , otherwise. - /// - /// The channel object that corresponds to the specified name. - protected IrcChannel GetChannelFromName(string channelName, out bool createdNew) - { - if (channelName == null) - throw new ArgumentNullException("channelName"); - if (channelName.Length == 0) - throw new ArgumentException(Resources.MessageValueCannotBeEmptyString, "channelName"); - - // Search for channel with given name in list of known channel. If it does not exist, add it. - lock (((ICollection) Channels).SyncRoot) - { - var channel = channels.SingleOrDefault(c => c.Name == channelName); - if (channel == null) - { - channel = new IrcChannel(channelName); - channel.Client = this; - channels.Add(channel); - createdNew = true; - } - else - { - createdNew = false; - } - - return channel; - } - } - - /// - protected IrcUser GetUserFromNickName(string nickName, bool isOnline = true) - { - bool createdNew; - return GetUserFromNickName(nickName, isOnline, out createdNew); - } - - /// - /// Gets the user with the specified nick name, creating it if necessary. - /// - /// The nick name of the user. - /// - /// if the user is currently online; - /// , if the user is currently offline. - /// The property of the user object is set to this value. - /// - /// - /// if the user object was created during the call; - /// , otherwise. - /// - /// The user object that corresponds to the specified nick name. - protected IrcUser GetUserFromNickName(string nickName, bool isOnline, out bool createdNew) - { - if (nickName == null) - throw new ArgumentNullException("nickName"); - if (nickName.Length == 0) - throw new ArgumentException(Resources.MessageValueCannotBeEmptyString, "nickName"); - - // Search for user with given nick name in list of known users. If it does not exist, add it. - IrcUser user; - lock (((ICollection) Users).SyncRoot) - { - user = users.SingleOrDefault(u => u.NickName == nickName); - if (user == null) - { - user = new IrcUser - { - Client = this, - NickName = nickName - }; - users.Add(user); - createdNew = true; - } - else - { - createdNew = false; - } - } - user.IsOnline = isOnline; - return user; - } - - /// - protected IrcUser GetUserFromUserName(string userName) - { - bool createdNew; - return GetUserFromUserName(userName, out createdNew); - } - - /// - /// Gets the user with the specified user name, creating it if necessary. - /// - /// The user name of the user. - /// - /// if the user object was created during the call; - /// , otherwise. - /// - /// The user object that corresponds to the specified user name. - protected IrcUser GetUserFromUserName(string userName, out bool createdNew) - { - if (userName == null) - throw new ArgumentNullException("userName"); - if (userName.Length == 0) - throw new ArgumentException(Resources.MessageValueCannotBeEmptyString, "userName"); - - // Search for user with given nick name in list of known users. If it does not exist, add it. - lock (((ICollection) Users).SyncRoot) - { - var user = users.SingleOrDefault(u => u.UserName == userName); - if (user == null) - { - user = new IrcUser(); - user.Client = this; - user.UserName = userName; - users.Add(user); - - createdNew = true; - } - else - { - createdNew = false; - } - - return user; - } - } - - protected int GetNumericUserMode(ICollection modes) - { - var value = 0; - if (modes == null) - return value; - if (modes.Contains('w')) - value |= 0x02; - if (modes.Contains('i')) - value |= 0x04; - return value; - } - - protected virtual void ResetState() - { - // Reset fully state of client. - servers = new Collection(); - isRegistered = false; - localUser = null; - serverSupportedFeatures = new Dictionary(); - ServerSupportedFeatures = new Collections.ReadOnlyDictionary(serverSupportedFeatures); - channelUserModes = new Collection - { - 'o', - 'v' - }; - ChannelUserModes = new ReadOnlyCollection(channelUserModes); - channelUserModesPrefixes = new Dictionary - { - {'@', 'o'}, - {'+', 'v'} - }; - motdBuilder = new StringBuilder(); - networkInformation = new IrcNetworkInfo(); - channels = new Collection(); - Channels = new IrcChannelCollection(this, channels); - users = new Collection(); - Users = new IrcUserCollection(this, users); - listedChannels = new List(); - listedServerLinks = new List(); - listedStatsEntries = new List(); - } - - protected void InitializeMessageProcessors() - { - // Find each method defined as processor for IRC message. - foreach (var method in this.GetAttributedMethods()) - { - var attribute = method.Item1; - var methodDelegate = method.Item2; - - var commandRangeParts = attribute.CommandName.Split('-'); - if (commandRangeParts.Length == 2) - { - // Numeric command range was defined. - var commandRangeStart = int.Parse(commandRangeParts[0]); - var commandRangeEnd = int.Parse(commandRangeParts[1]); - for (var code = commandRangeStart; code <= commandRangeEnd; code++) - numericMessageProcessors.Add(code, methodDelegate); - } - else if (commandRangeParts.Length == 1) - { - // Single command name was defined. Check whether it is numeric or alphabetic. - int commandCode; - if (int.TryParse(attribute.CommandName, out commandCode)) - // Command is numeric. - numericMessageProcessors.Add(commandCode, methodDelegate); - else - // Command is alphabetic. - messageProcessors.Add(attribute.CommandName, methodDelegate); - } - else - { - throw new ProtocolViolationException(string.Format( - Resources.MessageInvalidCommandDefinition, attribute.CommandName)); - } - } - ; - } - - /// - /// The current instance has already been disposed. - protected void WriteMessage(string prefix, string command, IEnumerable parameters) - { - CheckDisposed(); - - WriteMessage(prefix, command, parameters.ToArray()); - } - - /// - /// The message prefix that represents the source of the message. - /// The name of the command. - /// A collection of the parameters to the command. - /// The current instance has already been disposed. - protected void WriteMessage(string prefix, string command, params string[] parameters) - { - CheckDisposed(); - - var message = new IrcMessage(this, prefix, command, parameters.ToArray()); - if (message.Source == null) - message.Source = localUser; - WriteMessage(message); - } - - /// - /// - /// Writes the specified message (prefix, command, and parameters) to the network stream. - /// - /// The message to write. - /// - /// contains more than 15 many parameters. - /// - /// - /// The value of of - /// is invalid. - /// - /// - /// The value of one of the items of of - /// is invalid. - /// - /// The current instance has already been disposed. - protected void WriteMessage(IrcMessage message) - { - CheckDisposed(); - - if (message.Command == null) - throw new ArgumentException(Resources.MessageInvalidCommand, "message"); - if (message.Parameters.Count > maxParamsCount) - throw new ArgumentException(Resources.MessageTooManyParams, "parameters"); - - var lineBuilder = new StringBuilder(); - - // Append prefix to line, if specified. - if (message.Prefix != null) - lineBuilder.Append(":" + CheckPrefix(message.Prefix) + " "); - - // Append command name to line. - lineBuilder.Append(CheckCommand(message.Command).ToUpper()); - - // Append each parameter to line, adding ':' character before last parameter. - for (var i = 0; i < message.Parameters.Count - 1; i++) - { - if (message.Parameters[i] != null) - lineBuilder.Append(" " + CheckMiddleParameter(message.Parameters[i])); - } - if (message.Parameters.Count > 0) - { - var lastParameter = message.Parameters[message.Parameters.Count - 1]; - if (lastParameter != null) - lineBuilder.Append(" :" + CheckTrailingParameter(lastParameter)); - } - - // Send raw message as line of text. - var line = lineBuilder.ToString(); - var messageSentEventArgs = new IrcRawMessageEventArgs(message, line); - WriteMessage(line, messageSentEventArgs); - } - - protected virtual void WriteMessage(string line, object token = null) - { - CheckDisposed(); - - Debug.Assert(line != null); - } - - private void ReadMessage(IrcMessage message, string line) - { - // Try to find corresponding message processor for command of given message. - MessageProcessor messageProcessor; - int commandCode; - if (messageProcessors.TryGetValue(message.Command, out messageProcessor) || - (int.TryParse(message.Command, out commandCode) && - numericMessageProcessors.TryGetValue(commandCode, out messageProcessor))) - { - try - { - messageProcessor(message); - } -#if !DEBUG - catch (Exception ex) - { - OnError(new IrcErrorEventArgs(ex)); - } -#endif - finally - { - } - } - else - { - // Unknown command. - DebugUtilities.WriteEvent("Unknown IRC message command '{0}'.", message.Command); - } - } - - private string CheckPrefix(string value) - { - Debug.Assert(value != null); - - if (value.Length == 0 || value.Any(IsInvalidMessageChar)) - { - throw new ArgumentException(string.Format( - Resources.MessageInvalidPrefix, value), "value"); - } - - return value; - } - - private string CheckCommand(string value) - { - Debug.Assert(value != null); - - if (value.Length == 0 || value.Any(IsInvalidMessageChar)) - { - throw new ArgumentException(string.Format( - Resources.MessageInvalidCommand, value), "value"); - } - - return value; - } - - private string CheckMiddleParameter(string value) - { - Debug.Assert(value != null); - - if (value.Length == 0 || value.Any(c => IsInvalidMessageChar(c) || c == ' ') || value[0] == ':') - { - throw new ArgumentException(string.Format( - Resources.MessageInvalidMiddleParameter, value), "value"); - } - - return value; - } - - private string CheckTrailingParameter(string value) - { - Debug.Assert(value != null); - - if (value.Any(c => IsInvalidMessageChar(c))) - { - throw new ArgumentException(string.Format( - Resources.MessageInvalidMiddleParameter, value), "value"); - } - - return value; - } - - private bool IsInvalidMessageChar(char value) - { - return value == '\0' || value == '\r' || value == '\n'; - } - - protected void Connect(IrcRegistrationInfo registrationInfo) - { - CheckDisposed(); - - if (registrationInfo == null) - throw new ArgumentNullException("registrationInfo"); - - CheckRegistrationInfo(registrationInfo, "registrationInfo"); - ResetState(); - } - - protected void CheckRegistrationInfo(IrcRegistrationInfo registrationInfo, string registrationInfoParamName) - { - // Check that given registration info is valid. - if (registrationInfo is IrcUserRegistrationInfo) - { - if (registrationInfo.NickName == null || - ((IrcUserRegistrationInfo) registrationInfo).UserName == null) - throw new ArgumentException(Resources.MessageInvalidUserRegistrationInfo, - registrationInfoParamName); - } - else if (registrationInfo is IrcServiceRegistrationInfo) - { - if (registrationInfo.NickName == null || - ((IrcServiceRegistrationInfo) registrationInfo).Description == null) - throw new ArgumentException(Resources.MessageInvalidServiceRegistrationInfo, - registrationInfoParamName); - } - else - { - throw new ArgumentException(Resources.MessageInvalidRegistrationInfoObject, - registrationInfoParamName); - } - } - - /// - /// Disconnects asynchronously from the server. - /// - /// The current instance has already been disposed. - /// - /// This method closes the client connection immediately and forcibly, and does not send a quit message to the - /// server. To disconnect from the IRC server gracefully, call and wait for the - /// connection to be closed. - /// - public virtual void Disconnect() - { - CheckDisposed(); - } - - protected void ParseMessage(string line) - { - string prefix = null; - string lineAfterPrefix = null; - - // Extract prefix from message line, if it contains one. - if (line[0] == ':') - { - var firstSpaceIndex = line.IndexOf(' '); - Debug.Assert(firstSpaceIndex != -1); - prefix = line.Substring(1, firstSpaceIndex - 1); - lineAfterPrefix = line.Substring(firstSpaceIndex + 1); - } - else - { - lineAfterPrefix = line; - } - - // Extract command from message. - var spaceIndex = lineAfterPrefix.IndexOf(' '); - Debug.Assert(spaceIndex != -1); - var command = lineAfterPrefix.Substring(0, spaceIndex); - var paramsLine = lineAfterPrefix.Substring(command.Length + 1); - - // Extract parameters from message. - // Each parameter is separated by single space, except last one, which may contain spaces if it - // is prefixed by colon. - var parameters = new string[maxParamsCount]; - int paramStartIndex, paramEndIndex = -1; - var lineColonIndex = paramsLine.IndexOf(" :"); - if (lineColonIndex == -1 && !paramsLine.StartsWith(":")) - lineColonIndex = paramsLine.Length; - for (var i = 0; i < parameters.Length; i++) - { - paramStartIndex = paramEndIndex + 1; - paramEndIndex = paramsLine.IndexOf(' ', paramStartIndex); - if (paramEndIndex == -1) - paramEndIndex = paramsLine.Length; - if (paramEndIndex > lineColonIndex) - { - paramStartIndex++; - paramEndIndex = paramsLine.Length; - } - parameters[i] = paramsLine.Substring(paramStartIndex, paramEndIndex - paramStartIndex); - if (paramEndIndex == paramsLine.Length) - break; - } - - // Parse received IRC message. - var message = new IrcMessage(this, prefix, command, parameters); - var messageReceivedEventArgs = new IrcRawMessageEventArgs(message, line); - OnRawMessageReceived(messageReceivedEventArgs); - ReadMessage(message, line); - -#if DEBUG - DebugUtilities.WriteIrcRawLine(this, ">>> " + messageReceivedEventArgs.RawContent); -#endif - } - - protected void HandleClientConnecting() - { - DebugUtilities.WriteEvent("Connecting to server..."); - } - - protected virtual void HandleClientConnected(IrcRegistrationInfo regInfo) - { - if (regInfo.Password != null) - // Authenticate with server using password. - SendMessagePassword(regInfo.Password); - - // Check if client is registering as service or normal user. - if (regInfo is IrcServiceRegistrationInfo) - { - // Register client as service. - var serviceRegInfo = (IrcServiceRegistrationInfo) regInfo; - SendMessageService(serviceRegInfo.NickName, serviceRegInfo.Distribution, - serviceRegInfo.Description); - - localUser = new IrcLocalUser(serviceRegInfo.NickName, serviceRegInfo.Distribution, - serviceRegInfo.Description); - } - else - { - // Register client as normal user. - var userRegInfo = (IrcUserRegistrationInfo) regInfo; - SendMessageNick(userRegInfo.NickName); - SendMessageUser(userRegInfo.UserName, GetNumericUserMode(userRegInfo.UserModes), - userRegInfo.RealName); - - localUser = new IrcLocalUser(userRegInfo.NickName, userRegInfo.UserName, userRegInfo.RealName, - userRegInfo.UserModes); - } - localUser.Client = this; - - // Add local user to list of known users. - lock (((ICollection) Users).SyncRoot) - users.Add(localUser); - - OnConnected(new EventArgs()); - } - - protected virtual void HandleClientDisconnected() - { - OnDisconnected(new EventArgs()); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnConnected(EventArgs e) - { - var handler = Connected; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnConnectFailed(IrcErrorEventArgs e) - { - var handler = ConnectFailed; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnDisconnected(EventArgs e) - { - var handler = Disconnected; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnError(IrcErrorEventArgs e) - { - var handler = Error; - if (handler != null) - handler(this, e); - } - -#if !SILVERLIGHT - - /// - /// Raises the event. - /// - /// - /// The instance containing the event data. - /// - protected virtual void OnValidateSslCertificate(IrcValidateSslCertificateEventArgs e) - { - var handler = ValidateSslCertificate; - if (handler != null) - handler(this, e); - } - -#endif - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnRawMessageSent(IrcRawMessageEventArgs e) - { - var handler = RawMessageSent; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnRawMessageReceived(IrcRawMessageEventArgs e) - { - var handler = RawMessageReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnProtocolError(IrcProtocolErrorEventArgs e) - { - var handler = ProtocolError; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnErrorMessageReceived(IrcErrorMessageEventArgs e) - { - var handler = ErrorMessageReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnClientInfoReceived(EventArgs e) - { - var handler = ClientInfoReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnRegistered(EventArgs e) - { - var handler = Registered; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnServerBounce(IrcServerInfoEventArgs e) - { - var handler = ServerBounce; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnServerSupportedFeaturesReceived(EventArgs e) - { - var handler = ServerSupportedFeaturesReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnPingReceived(IrcPingOrPongReceivedEventArgs e) - { - var handler = PingReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnPongReceived(IrcPingOrPongReceivedEventArgs e) - { - var handler = PongReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnMotdReceived(EventArgs e) - { - var handler = MotdReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnNetworkInformationReceived(IrcCommentEventArgs e) - { - var handler = NetworkInformationReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnServerVersionInfoReceived(IrcServerVersionInfoEventArgs e) - { - var handler = ServerVersionInfoReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnServerTimeReceived(IrcServerTimeEventArgs e) - { - var handler = ServerTimeReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// - /// The instance containing the event data. - /// - protected virtual void OnServerLinksListReceived(IrcServerLinksListReceivedEventArgs e) - { - var handler = ServerLinksListReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// - /// The instance containing the event data. - /// - protected virtual void OnServerStatsReceived(IrcServerStatsReceivedEventArgs e) - { - var handler = ServerStatsReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnWhoReplyReceived(IrcNameEventArgs e) - { - var handler = WhoReplyReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnWhoIsReplyReceived(IrcUserEventArgs e) - { - var handler = WhoIsReplyReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnWhoWasReplyReceived(IrcUserEventArgs e) - { - var handler = WhoWasReplyReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// - /// The instance containing the event data. - /// - protected virtual void OnChannelListReceived(IrcChannelListReceivedEventArgs e) - { - var handler = ChannelListReceived; - if (handler != null) - handler(this, e); - } - - protected void CheckDisposed() - { - if (IsDisposed) - throw new ObjectDisposedException(GetType().FullName); - } - - /// - /// Returns a string representation of this instance. - /// - /// A string that represents this instance. - public override string ToString() - { - if (!IsDisposed && IsConnected) - return string.Format("{0}@{1}", localUser.UserName, - ServerName); - return "(Not connected)"; - } - - /// - /// Represents a method that processes objects. - /// - /// The message to be processed. - protected delegate void MessageProcessor(IrcMessage message); - - /// - /// Represents a raw IRC message that is sent/received by . - /// A message contains a prefix (representing the source), a command name (a word or three-digit number), - /// and any number of parameters (up to a maximum of 15). - /// - /// - [DebuggerDisplay("{ToString(), nq}")] - public struct IrcMessage - { - /// - /// The source of the message, which is the object represented by the value of . - /// - public IIrcMessageSource Source; - - /// - /// The message prefix. - /// - public string Prefix; - - /// - /// The name of the command. - /// - public string Command; - - /// - /// A list of the parameters to the message. - /// - public IList Parameters; - - /// - /// Initializes a new instance of the structure. - /// - /// A client object that has sent/will receive the message. - /// The message prefix that represents the source of the message. - /// The command name; either an alphabetic word or 3-digit number. - /// - /// A list of the parameters to the message. Can contain a maximum of 15 items. - /// - public IrcMessage(IrcClient client, string prefix, string command, IList parameters) - { - Prefix = prefix; - Command = command; - Parameters = parameters; - - Source = client.GetSourceFromPrefix(prefix); - } - - /// - /// Returns a string representation of this instance. - /// - /// A string that represents this instance. - public override string ToString() - { - return string.Format("{0} ({1} parameters)", Command, Parameters.Count); - } - } - - #region Protocol Data - - // Internal collection of all known servers. - private Collection servers; - - // True if connection has been registered with server; - protected bool isRegistered; - - // Stores information about local user. - protected IrcLocalUser localUser; - - // Dictionary of protocol features supported by server. - private Dictionary serverSupportedFeatures; - - // Collection of channel modes that apply to users in a channel. - private Collection channelUserModes; - - // Dictionary of nick name prefixes (keys) and their corresponding channel modes. - private Dictionary channelUserModesPrefixes; - - // Builds MOTD (message of the day) string as it is received from server. - protected StringBuilder motdBuilder; - - // Information about the IRC network given by the server. - private IrcNetworkInfo networkInformation; - - // Collection of all currently joined channels. - private Collection channels; - - // Collection of all known users. - private Collection users; - - // List of information about channels, returned by server in response to last LIST message. - private List listedChannels; - - // List of other servers to which server links, returned by server in response to last LINKS message. - private List listedServerLinks; - - // List of statistical entries, returned by server in response to last STATS message. - private List listedStatsEntries; - - #endregion - - #region Proxy Methods - - internal void SetTopic(string channel, string topic = null) - { - SendMessageTopic(channel, topic); - } - - internal void GetChannelModes(IrcChannel channel, string modes = null) - { - SendMessageChannelMode(channel.Name, modes); - } - - internal void SetChannelModes(IrcChannel channel, string modes, IEnumerable modeParameters = null) - { - SendMessageChannelMode(channel.Name, modes, modeParameters); - } - - internal void Invite(IrcChannel channel, string userNickName) - { - SendMessageInvite(channel.Name, userNickName); - } - - internal void Kick(IrcChannel channel, IEnumerable usersNickNames, string comment = null) - { - SendMessageKick(channel.Name, usersNickNames, comment); - } - - internal void Kick(IEnumerable channelUsers, string comment = null) - { - SendMessageKick(channelUsers.Select(cu => Tuple.Create(cu.Channel.Name, cu.User.NickName)), comment); - } - - internal void Join(IEnumerable channels) - { - SendMessageJoin(channels); - } - - internal void Join(IEnumerable> channels) - { - SendMessageJoin(channels); - } - - internal void Leave(IEnumerable channels, string comment = null) - { - SendMessagePart(channels, comment); - } - - internal void SendPrivateMessage(IEnumerable targetsNames, string text) - { - var targetsNamesArray = targetsNames.ToArray(); - var targets = targetsNamesArray.Select(n => GetMessageTarget(n)).ToArray(); - SendMessagePrivateMessage(targetsNamesArray, text); - localUser.HandleMessageSent(targets, text); - } - - internal void SendNotice(IEnumerable targetsNames, string text) - { - var targetsNamesArray = targetsNames.ToArray(); - var targets = targetsNamesArray.Select(n => GetMessageTarget(n)).ToArray(); - SendMessageNotice(targetsNamesArray, text); - localUser.HandleNoticeSent(targets, text); - } - - internal void SetAway(string text) - { - SendMessageAway(text); - } - - internal void UnsetAway() - { - SendMessageAway(); - } - - internal void SetNickName(string nickName) - { - SendMessageNick(nickName); - } - - internal void GetLocalUserModes(IrcLocalUser user) - { - SendMessageUserMode(user.NickName); - } - - internal void SetLocalUserModes(IrcLocalUser user, string modes) - { - SendMessageUserMode(user.NickName, modes); - } - - #endregion - } -} \ No newline at end of file diff --git a/IrcDotNet/IrcClientMessageProcessing.cs b/IrcDotNet/IrcClientMessageProcessing.cs deleted file mode 100644 index 050f56f..0000000 --- a/IrcDotNet/IrcClientMessageProcessing.cs +++ /dev/null @@ -1,1214 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Net; -using System.Text.RegularExpressions; -using IrcDotNet.Collections; -using IrcDotNet.Properties; - -namespace IrcDotNet -{ - // Defines all message processors for the client. - partial class IrcClient - { - /// - /// Process NICK messages received from the server. - /// - /// The message received from the server. - [MessageProcessor("nick")] - protected internal void ProcessMessageNick(IrcMessage message) - { - var sourceUser = message.Source as IrcUser; - if (sourceUser == null) - throw new ProtocolViolationException(string.Format( - Resources.MessageSourceNotUser, message.Source.Name)); - - // Local or remote user has changed nick name. - Debug.Assert(message.Parameters[0] != null); - sourceUser.NickName = message.Parameters[0]; - } - - /// - /// Process QUIT messages received from the server. - /// - /// The message received from the server. - [MessageProcessor("quit")] - protected internal void ProcessMessageQuit(IrcMessage message) - { - var sourceUser = message.Source as IrcUser; - if (sourceUser == null) - throw new ProtocolViolationException(string.Format( - Resources.MessageSourceNotUser, message.Source.Name)); - - // Remote user has quit server. - Debug.Assert(message.Parameters[0] != null); - sourceUser.HandeQuit(message.Parameters[0]); - - lock (((ICollection) Users).SyncRoot) - users.Remove(sourceUser); - } - - /// - /// Process JOIN messages received from the server. - /// - /// The message received from the server. - [MessageProcessor("join")] - protected internal void ProcessMessageJoin(IrcMessage message) - { - var sourceUser = message.Source as IrcUser; - if (sourceUser == null) - throw new ProtocolViolationException(string.Format( - Resources.MessageSourceNotUser, message.Source.Name)); - - // Local or remote user has joined one or more channels. - Debug.Assert(message.Parameters[0] != null); - var chans = GetChannelsFromList(message.Parameters[0]).ToArray(); - if (sourceUser == localUser) - chans.ForEach(c => localUser.HandleJoinedChannel(c)); - else - chans.ForEach(c => c.HandleUserJoined(new IrcChannelUser(sourceUser))); - } - - /// - /// Process PART messages received from the server. - /// - /// The message received from the server. - [MessageProcessor("part")] - protected internal void ProcessMessagePart(IrcMessage message) - { - var sourceUser = message.Source as IrcUser; - if (sourceUser == null) - throw new ProtocolViolationException(string.Format( - Resources.MessageSourceNotUser, message.Source.Name)); - - // Local or remote user has left one or more channels. - Debug.Assert(message.Parameters[0] != null); - var comment = message.Parameters[1]; - var chans = GetChannelsFromList(message.Parameters[0]).ToArray(); - if (sourceUser == localUser) - chans.ForEach(c => localUser.HandleLeftChannel(c)); - else - chans.ForEach(c => c.HandleUserLeft(sourceUser, comment)); - } - - /// - /// Process MODE messages received from the server. - /// - /// The message received from the server. - [MessageProcessor("mode")] - protected internal void ProcessMessageMode(IrcMessage message) - { - // Check if mode applies to channel or user. - Debug.Assert(message.Parameters[0] != null); - if (IsChannelName(message.Parameters[0])) - { - var channel = GetChannelFromName(message.Parameters[0]); - - // Get channel modes and list of mode parameters from message parameters. - Debug.Assert(message.Parameters[1] != null); - var modesAndParameters = GetModeAndParameters(message.Parameters.Skip(1)); - var source = message.Source as IrcUser; - OnChannelModeChanged(channel, source, modesAndParameters.Item1, modesAndParameters.Item2); - channel.HandleModesChanged(source, modesAndParameters.Item1, - modesAndParameters.Item2); - } - else if (message.Parameters[0] == localUser.NickName) - { - Debug.Assert(message.Parameters[1] != null); - localUser.HandleModesChanged(message.Parameters[1]); - } - else - { - throw new ProtocolViolationException(string.Format(Resources.MessageCannotSetUserMode, - message.Parameters[0])); - } - } - - protected virtual void OnChannelModeChanged(IrcChannel channel, IrcUser source, string newModes, - IEnumerable newModeParameters) - { - } - - /// - /// Process TOPIC messages received from the server. - /// - /// The message received from the server. - [MessageProcessor("topic")] - protected internal void ProcessMessageTopic(IrcMessage message) - { - Debug.Assert(message.Parameters[0] != null); - var channel = GetChannelFromName(message.Parameters[0]); - Debug.Assert(message.Parameters[1] != null); - channel.HandleTopicChanged(message.Source as IrcUser, message.Parameters[1]); - } - - /// - /// Process KICK messages received from the server. - /// - /// The message received from the server. - [MessageProcessor("kick")] - protected internal void ProcessMessageKick(IrcMessage message) - { - Debug.Assert(message.Parameters[0] != null); - var channels = GetChannelsFromList(message.Parameters[0]); - Debug.Assert(message.Parameters[1] != null); - var users = GetUsersFromList(message.Parameters[1]).ToArray(); - var comment = message.Parameters[2]; - - // Handle kick command for each user given in message. - foreach (var channelUser in channels.Zip(users, - (channel, user) => channel.GetChannelUser(user))) - { - if (channelUser.User == localUser) - { - // Local user was kicked from channel. - var channel = channelUser.Channel; - lock (((ICollection) Channels).SyncRoot) - this.channels.Remove(channel); - - channelUser.Channel.HandleUserKicked(channelUser, comment); - localUser.HandleLeftChannel(channel); - - // Local user has left channel. Do not process kicks of remote users. - break; - } - // Remote user was kicked from channel. - channelUser.Channel.HandleUserKicked(channelUser, comment); - } - } - - /// - /// Process INVITE messages received from the server. - /// - /// The message received from the server. - [MessageProcessor("invite")] - protected internal void ProcessMessageInvite(IrcMessage message) - { - Debug.Assert(message.Parameters[0] != null); - var user = GetUserFromNickName(message.Parameters[0]); - Debug.Assert(message.Parameters[1] != null); - var channel = GetChannelFromName(message.Parameters[1]); - - Debug.Assert(message.Source is IrcUser); - if (message.Source is IrcUser) - user.HandleInviteReceived((IrcUser) message.Source, channel); - } - - /// - /// Process PRIVMSG messages received from the server. - /// - /// The message received from the server. - [MessageProcessor("privmsg")] - protected internal void ProcessMessagePrivateMessage(IrcMessage message) - { - // Get list of message targets. - Debug.Assert(message.Parameters[0] != null); - var targets = message.Parameters[0].Split(',').Select(n => GetMessageTarget(n)).ToArray(); - - // Get message text. - Debug.Assert(message.Parameters[1] != null); - var text = message.Parameters[1]; - - // Process message for each given target. - foreach (var curTarget in targets) - { - Debug.Assert(curTarget != null); - var messageHandler = curTarget as IIrcMessageReceiveHandler ?? localUser; - messageHandler.HandleMessageReceived(message.Source, targets, text); - } - } - - /// - /// Process NOTICE messages received from the server. - /// - /// The message received from the server. - [MessageProcessor("notice")] - protected internal void ProcessMessageNotice(IrcMessage message) - { - // Get list of message targets. - Debug.Assert(message.Parameters[0] != null); - var targets = message.Parameters[0].Split(',').Select(n => GetMessageTarget(n)).ToArray(); - - // Get message text. - Debug.Assert(message.Parameters[1] != null); - var text = message.Parameters[1]; - - // Process notice for each given target. - foreach (var curTarget in targets) - { - Debug.Assert(curTarget != null); - var messageHandler = curTarget as IIrcMessageReceiveHandler ?? localUser; - if (messageHandler != null) - messageHandler.HandleNoticeReceived(message.Source, targets, text); - } - } - - /// - /// Process PING messages received from the server. - /// - /// The message received from the server. - [MessageProcessor("ping")] - protected internal void ProcessMessagePing(IrcMessage message) - { - Debug.Assert(message.Parameters[0] != null); - var server = message.Parameters[0]; - var target = message.Parameters[1]; - var ircPingReceivedEventArgs = new IrcPingReceivedEventArgs(server); - try - { - OnPingReceived(ircPingReceivedEventArgs); - } - finally - { - if (ircPingReceivedEventArgs.SendPong) - { - SendMessagePong(server, target); - } - } - } - - /// - /// Process PONG messages received from the server. - /// - /// The message received from the server. - [MessageProcessor("pong")] - protected internal void ProcessMessagePong(IrcMessage message) - { - Debug.Assert(message.Parameters[0] != null); - var server = message.Parameters[0]; - OnPongReceived(new IrcPingOrPongReceivedEventArgs(server)); - } - - /// - /// Process ERROR messages received from the server. - /// - /// The message received from the server. - [MessageProcessor("error")] - protected internal void ProcessMessageError(IrcMessage message) - { - Debug.Assert(message.Parameters[0] != null); - var errorMessage = message.Parameters[0]; - OnErrorMessageReceived(new IrcErrorMessageEventArgs(errorMessage)); - } - - /// - /// Process RPL_WELCOME responses from the server. - /// - /// The message received from the server. - [MessageProcessor("001")] - protected internal virtual void ProcessMessageReplyWelcome(IrcMessage message) - { - Debug.Assert(message.Parameters[0] != null); - - Debug.Assert(message.Parameters[1] != null); - WelcomeMessage = message.Parameters[1]; - - // Extract nick name, user name, and host name from welcome message. Use fallback info if not present. - var nickNameIdMatch = Regex.Match(WelcomeMessage.Split(' ').Last(), regexNickNameId); - localUser.NickName = nickNameIdMatch.Groups["nick"].GetValue() ?? localUser.NickName; - localUser.UserName = nickNameIdMatch.Groups["user"].GetValue() ?? localUser.UserName; - localUser.HostName = nickNameIdMatch.Groups["host"].GetValue() ?? localUser.HostName; - - isRegistered = true; - OnRegistered(new EventArgs()); - } - - /// - /// Process RPL_YOURHOST responses from the server. - /// - /// The message received from the server. - [MessageProcessor("002")] - protected internal void ProcessMessageReplyYourHost(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - YourHostMessage = message.Parameters[1]; - } - - /// - /// Process RPL_CREATED responses from the server. - /// - /// The message received from the server. - [MessageProcessor("003")] - protected internal void ProcessMessageReplyCreated(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - ServerCreatedMessage = message.Parameters[1]; - } - - /// - /// Process RPL_MYINFO responses from the server. - /// - /// The message received from the server. - [MessageProcessor("004")] - protected internal virtual void ProcessMessageReplyMyInfo(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - ServerName = message.Parameters[1]; - Debug.Assert(message.Parameters[2] != null); - ServerVersion = message.Parameters[2]; - Debug.Assert(message.Parameters[3] != null); - ServerAvailableUserModes = message.Parameters[3]; - Debug.Assert(message.Parameters[4] != null); - ServerAvailableChannelModes = message.Parameters[4]; - - // All initial information about client has now been received. - OnClientInfoReceived(new EventArgs()); - } - - /// - /// Process RPL_BOUNCE and RPL_ISUPPORT responses from the server. - /// - /// The message received from the server. - [MessageProcessor("005")] - protected internal void ProcessMessageReplyBounceOrISupport(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - // Check if message is RPL_BOUNCE or RPL_ISUPPORT. - Debug.Assert(message.Parameters[1] != null); - if (message.Parameters[1].StartsWith("Try server")) - { - // Message is RPL_BOUNCE. - // Current server is redirecting client to new server. - var textParts = message.Parameters[0].Split(' ', ','); - var serverAddress = textParts[2]; - var serverPort = int.Parse(textParts[6]); - - OnServerBounce(new IrcServerInfoEventArgs(serverAddress, serverPort)); - } - else - { - // Message is RPL_ISUPPORT. - // Add key/value pairs to dictionary of supported server features. - for (var i = 1; i < message.Parameters.Count - 1; i++) - { - if (message.Parameters[i + 1] == null) - break; - - var paramParts = message.Parameters[i].Split('='); - var paramName = paramParts[0]; - var paramValue = paramParts.Length == 1 ? null : paramParts[1]; - HandleISupportParameter(paramName, paramValue); - serverSupportedFeatures.Set(paramName, paramValue); - } - - OnServerSupportedFeaturesReceived(new EventArgs()); - } - } - - /// - /// Process RPL_STATSLINKINFO responses from the server. - /// - /// The message received from the server. - [MessageProcessor("211")] - protected internal void ProcessMessageStatsLinkInfo(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - HandleStatsEntryReceived((int) IrcServerStatisticalEntryCommonType.Connection, message); - } - - /// - /// Process RPL_STATSCOMMANDS responses from the server. - /// - /// The message received from the server. - [MessageProcessor("212")] - protected internal void ProcessMessageStatsCommands(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - HandleStatsEntryReceived((int) IrcServerStatisticalEntryCommonType.Command, message); - } - - /// - /// Process RPL_STATSCLINE responses from the server. - /// - /// The message received from the server. - [MessageProcessor("213")] - protected internal void ProcessMessageStatsCLine(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - HandleStatsEntryReceived((int) IrcServerStatisticalEntryCommonType.AllowedServerConnect, message); - } - - /// - /// Process RPL_STATSNLINE responses from the server. - /// - /// The message received from the server. - [MessageProcessor("214")] - protected internal void ProcessMessageStatsNLine(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - HandleStatsEntryReceived((int) IrcServerStatisticalEntryCommonType.AllowedServerAccept, message); - } - - /// - /// Process RPL_STATSILINE responses from the server. - /// - /// The message received from the server. - [MessageProcessor("215")] - protected internal void ProcessMessageStatsILine(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - HandleStatsEntryReceived((int) IrcServerStatisticalEntryCommonType.AllowedClient, message); - } - - /// - /// Process RPL_STATSKLINE responses from the server. - /// - /// The message received from the server. - [MessageProcessor("216")] - protected internal void ProcessMessageStatsKLine(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - HandleStatsEntryReceived((int) IrcServerStatisticalEntryCommonType.BannedClient, message); - } - - /// - /// Process RPL_STATSYLINE responses from the server. - /// - /// The message received from the server. - [MessageProcessor("218")] - protected internal void ProcessMessageStatsYLine(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - HandleStatsEntryReceived((int) IrcServerStatisticalEntryCommonType.ConnectionClass, message); - } - - /// - /// Process RPL_ENDOFSTATS responses from the server. - /// - /// The message received from the server. - [MessageProcessor("219")] - protected internal void ProcessMessageEndOfStats(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - OnServerStatsReceived(new IrcServerStatsReceivedEventArgs(listedStatsEntries)); - listedStatsEntries = new List(); - } - - /// - /// Process RPL_STATSLLINE responses from the server. - /// - /// The message received from the server. - [MessageProcessor("241")] - protected internal void ProcessMessageStatsLLine(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - HandleStatsEntryReceived((int) IrcServerStatisticalEntryCommonType.LeafDepth, message); - } - - /// - /// Process RPL_STATSUPTIME responses from the server. - /// - /// The message received from the server. - [MessageProcessor("242")] - protected internal void ProcessMessageStatsUpTime(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - HandleStatsEntryReceived((int) IrcServerStatisticalEntryCommonType.Uptime, message); - } - - /// - /// Process RPL_STATSOLINE responses from the server. - /// - /// The message received from the server. - [MessageProcessor("243")] - protected internal void ProcessMessageStatsOLine(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - HandleStatsEntryReceived((int) IrcServerStatisticalEntryCommonType.AllowedOperator, message); - } - - /// - /// Process RPL_STATSHLINE responses from the server. - /// - /// The message received from the server. - [MessageProcessor("244")] - protected internal void ProcessMessageStatsHLine(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - HandleStatsEntryReceived((int) IrcServerStatisticalEntryCommonType.HubServer, message); - } - - /// - /// Process RPL_LUSERCLIENT responses from the server. - /// - /// The message received from the server. - [MessageProcessor("251")] - protected internal void ProcessMessageLUserClient(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - // Extract network information from text. - Debug.Assert(message.Parameters[1] != null); - var info = message.Parameters[1]; - var infoParts = info.Split(' '); - Debug.Assert(infoParts.Length == 10); - networkInformation.VisibleUsersCount = int.Parse(infoParts[2]); - networkInformation.InvisibleUsersCount = int.Parse(infoParts[5]); - networkInformation.ServersCount = int.Parse(infoParts[8]); - - OnNetworkInformationReceived(new IrcCommentEventArgs(info)); - } - - /// - /// Process RPL_LUSEROP responses from the server. - /// - /// The message received from the server. - [MessageProcessor("252")] - protected internal void ProcessMessageLUserOp(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - // Extract network information from text. - Debug.Assert(message.Parameters[1] != null); - var info = message.Parameters[1]; - networkInformation.OperatorsCount = int.Parse(info); - - OnNetworkInformationReceived(new IrcCommentEventArgs(info)); - } - - /// - /// Process RPL_LUSERUNKNOWN responses from the server. - /// - /// The message received from the server. - [MessageProcessor("253")] - protected internal void ProcessMessageLUserUnknown(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - // Extract network information from text. - Debug.Assert(message.Parameters[1] != null); - var info = message.Parameters[1]; - networkInformation.UnknownConnectionsCount = int.Parse(info); - - OnNetworkInformationReceived(new IrcCommentEventArgs(info)); - } - - /// - /// Process RPL_LUSERCHANNELS responses from the server. - /// - /// The message received from the server. - [MessageProcessor("254")] - protected internal void ProcessMessageLUserChannels(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - // Extract network information from text. - Debug.Assert(message.Parameters[1] != null); - var info = message.Parameters[1]; - networkInformation.ChannelsCount = int.Parse(info); - - OnNetworkInformationReceived(new IrcCommentEventArgs(info)); - } - - /// - /// Process RPL_LUSERME responses from the server. - /// - /// The message received from the server. - [MessageProcessor("255")] - protected internal void ProcessMessageLUserMe(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - // Extract network information from text. - Debug.Assert(message.Parameters[1] != null); - var info = message.Parameters[1]; - var infoParts = info.Split(' '); - for (var i = 0; i < infoParts.Length; i++) - { - switch (infoParts[i].ToLowerInvariant()) - { - case "user": - case "users": - networkInformation.ServerClientsCount = int.Parse(infoParts[i - 1]); - break; - case "server": - case "servers": - networkInformation.ServerServersCount = int.Parse(infoParts[i - 1]); - break; - case "service": - case "services": - networkInformation.ServerClientsCount = int.Parse(infoParts[i - 1]); - break; - } - } - - OnNetworkInformationReceived(new IrcCommentEventArgs(info)); - } - - /// - /// Process RPL_AWAY responses from the server. - /// - /// The message received from the server. - [MessageProcessor("301")] - protected internal void ProcessMessageReplyAway(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - var user = GetUserFromNickName(message.Parameters[1]); - Debug.Assert(message.Parameters[2] != null); - user.AwayMessage = message.Parameters[2]; - user.IsAway = true; - } - - /// - /// Process RPL_ISON responses from the server. - /// - /// The message received from the server. - [MessageProcessor("303")] - protected internal void ProcessMessageReplyIsOn(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - // Set each user listed in reply as online. - Debug.Assert(message.Parameters[1] != null); - var onlineUsers = message.Parameters[1].Split(' ').Select(n => GetUserFromNickName(n)); - onlineUsers.ForEach(u => u.IsOnline = true); - } - - /// - /// Process RPL_UNAWAY responses from the server. - /// - /// The message received from the server. - [MessageProcessor("305")] - protected internal void ProcessMessageReplyUnAway(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - localUser.IsAway = false; - } - - /// - /// Process RPL_NOWAWAY responses from the server. - /// - /// The message received from the server. - [MessageProcessor("306")] - protected internal void ProcessMessageReplyNowAway(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - localUser.IsAway = true; - } - - /// - /// Process RPL_WHOISUSER responses from the server. - /// - /// The message received from the server. - [MessageProcessor("311")] - protected internal void ProcessMessageReplyWhoIsUser(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - var user = GetUserFromNickName(message.Parameters[1]); - Debug.Assert(message.Parameters[2] != null); - user.UserName = message.Parameters[2]; - Debug.Assert(message.Parameters[3] != null); - user.HostName = message.Parameters[3]; - Debug.Assert(message.Parameters[4] != null); - Debug.Assert(message.Parameters[5] != null); - user.RealName = message.Parameters[5]; - } - - /// - /// Process RPL_WHOISSERVER responses from the server. - /// - /// The message received from the server. - [MessageProcessor("312")] - protected internal void ProcessMessageReplyWhoIsServer(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - var user = GetUserFromNickName(message.Parameters[1]); - Debug.Assert(message.Parameters[2] != null); - user.ServerName = message.Parameters[2]; - Debug.Assert(message.Parameters[3] != null); - user.ServerInfo = message.Parameters[3]; - } - - /// - /// Process RPL_WHOISOPERATOR responses from the server. - /// - /// The message received from the server. - [MessageProcessor("313")] - protected internal void ProcessMessageReplyWhoIsOperator(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - var user = GetUserFromNickName(message.Parameters[1]); - user.IsOperator = true; - } - - /// - /// Process RPL_WHOWASUSER responses from the server. - /// - /// The message received from the server. - [MessageProcessor("314")] - protected internal void ProcessMessageReplyWhoWasUser(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - var user = GetUserFromNickName(message.Parameters[1], false); - Debug.Assert(message.Parameters[2] != null); - user.UserName = message.Parameters[2]; - Debug.Assert(message.Parameters[3] != null); - user.HostName = message.Parameters[3]; - Debug.Assert(message.Parameters[4] != null); - Debug.Assert(message.Parameters[5] != null); - user.RealName = message.Parameters[5]; - } - - /// - /// Process RPL_ENDOFWHO responses from the server. - /// - /// The message received from the server. - [MessageProcessor("315")] - protected internal void ProcessMessageReplyEndOfWho(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - var mask = message.Parameters[1]; - OnWhoReplyReceived(new IrcNameEventArgs(mask)); - } - - /// - /// Process RPL_WHOISIDLE responses from the server. - /// - /// The message received from the server. - [MessageProcessor("317")] - protected internal void ProcessMessageReplyWhoIsIdle(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - var user = GetUserFromNickName(message.Parameters[1]); - Debug.Assert(message.Parameters[2] != null); - user.IdleDuration = TimeSpan.FromSeconds(int.Parse(message.Parameters[2])); - } - - /// - /// Process 318 responses from the server. - /// - /// The message received from the server. - [MessageProcessor("318")] - protected internal void ProcessMessageReplyEndOfWhoIs(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - var user = GetUserFromNickName(message.Parameters[1]); - OnWhoIsReplyReceived(new IrcUserEventArgs(user, null)); - } - - /// - /// Process RPL_WHOISCHANNELS responses from the server. - /// - /// The message received from the server. - [MessageProcessor("319")] - protected internal void ProcessMessageReplyWhoIsChannels(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - var user = GetUserFromNickName(message.Parameters[1]); - - Debug.Assert(message.Parameters[2] != null); - foreach (var channelId in message.Parameters[2].Split(' ')) - { - if (channelId.Length == 0) - return; - - // Find user by nick name and add it to collection of channel users. - var channelNameAndUserMode = GetUserModeAndNickName(channelId); - var channel = GetChannelFromName(channelNameAndUserMode.Item1); - if (channel.GetChannelUser(user) == null) - channel.HandleUserJoined(new IrcChannelUser(user, channelNameAndUserMode.Item2)); - } - } - - /// - /// Process RPL_LIST responses from the server. - /// - /// The message received from the server. - [MessageProcessor("322")] - protected internal void ProcessMessageReplyList(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - var channelName = message.Parameters[1]; - Debug.Assert(message.Parameters[2] != null); - var visibleUsersCount = int.Parse(message.Parameters[2]); - Debug.Assert(message.Parameters[3] != null); - var topic = message.Parameters[3]; - - // Add channel information to temporary list. - listedChannels.Add(new IrcChannelInfo(channelName, visibleUsersCount, topic)); - } - - /// - /// Process RPL_LISTEND responses from the server. - /// - /// The message received from the server. - [MessageProcessor("323")] - protected internal void ProcessMessageReplyListEnd(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - OnChannelListReceived(new IrcChannelListReceivedEventArgs(listedChannels)); - listedChannels = new List(); - } - - /// - /// Process RPL_NOTOPIC responses from the server. - /// - /// The message received from the server. - [MessageProcessor("331")] - protected internal void ProcessMessageReplyNoTopic(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - var channel = GetChannelFromName(message.Parameters[1]); - channel.HandleTopicChanged(null, null); - } - - /// - /// Process RPL_TOPIC responses from the server. - /// - /// The message received from the server. - [MessageProcessor("332")] - protected internal void ProcessMessageReplyTopic(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - var channel = GetChannelFromName(message.Parameters[1]); - Debug.Assert(message.Parameters[2] != null); - channel.HandleTopicChanged(null, message.Parameters[2]); - } - - /// - /// Process RPL_INVITING responses from the server. - /// - /// The message received from the server. - [MessageProcessor("341")] - protected internal void ProcessMessageReplyInviting(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - var invitedUser = GetUserFromNickName(message.Parameters[1]); - Debug.Assert(message.Parameters[2] != null); - var channel = GetChannelFromName(message.Parameters[2]); - - channel.HandleUserInvited(invitedUser); - } - - /// - /// Process RPL_VERSION responses from the server. - /// - /// The message received from the server. - [MessageProcessor("351")] - protected internal void ProcessMessageReplyVersion(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - var versionInfo = message.Parameters[1]; - var versionSplitIndex = versionInfo.LastIndexOf('.'); - var version = versionInfo.Substring(0, versionSplitIndex); - var debugLevel = versionInfo.Substring(versionSplitIndex + 1); - Debug.Assert(message.Parameters[2] != null); - var server = message.Parameters[2]; - Debug.Assert(message.Parameters[3] != null); - var comments = message.Parameters[3]; - - OnServerVersionInfoReceived(new IrcServerVersionInfoEventArgs(version, debugLevel, server, comments)); - } - - /// - /// Process RPL_WHOREPLY responses from the server. - /// - /// The message received from the server. - [MessageProcessor("352")] - protected internal void ProcessMessageReplyWhoReply(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - var channel = message.Parameters[1] == "*" ? null : GetChannelFromName(message.Parameters[1]); - - Debug.Assert(message.Parameters[5] != null); - var user = GetUserFromNickName(message.Parameters[5]); - - Debug.Assert(message.Parameters[2] != null); - var userName = message.Parameters[2]; - Debug.Assert(message.Parameters[3] != null); - user.HostName = message.Parameters[3]; - Debug.Assert(message.Parameters[4] != null); - user.ServerName = message.Parameters[4]; - - Debug.Assert(message.Parameters[6] != null); - var userModeFlags = message.Parameters[6]; - Debug.Assert(userModeFlags.Length > 0); - if (userModeFlags.Contains('H')) - user.IsAway = false; - else if (userModeFlags.Contains('G')) - user.IsAway = true; - user.IsOperator = userModeFlags.Contains('*'); - if (channel != null) - { - // Add user to channel if it does not already exist in it. - var channelUser = channel.GetChannelUser(user); - if (channelUser == null) - { - channelUser = new IrcChannelUser(user); - channel.HandleUserJoined(channelUser); - } - - // Set modes on user corresponding to given mode flags (prefix characters). - foreach (var c in userModeFlags) - { - char mode; - if (channelUserModesPrefixes.TryGetValue(c, out mode)) - channelUser.HandleModeChanged(true, mode); - else - break; - } - } - - Debug.Assert(message.Parameters[7] != null); - var lastParamParts = message.Parameters[7].SplitIntoPair(" "); - user.HopCount = int.Parse(lastParamParts.Item1); - if (lastParamParts.Item2 != null) - user.RealName = lastParamParts.Item2; - } - - /// - /// Process RPL_NAMEREPLY responses from the server. - /// - /// The message received from the server. - [MessageProcessor("353")] - protected internal void ProcessMessageReplyNameReply(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[2] != null); - var channel = GetChannelFromName(message.Parameters[2]); - if (channel != null) - { - Debug.Assert(message.Parameters[1] != null); - Debug.Assert(message.Parameters[1].Length == 1); - channel.HandleTypeChanged(GetChannelType(message.Parameters[1][0])); - - Debug.Assert(message.Parameters[3] != null); - foreach (var userId in message.Parameters[3].Split(' ')) - { - if (userId.Length == 0) - return; - - // Find user by nick name and add it to collection of channel users. - var userNickNameAndMode = GetUserModeAndNickName(userId); - var user = GetUserFromNickName(userNickNameAndMode.Item1); - channel.HandleUserNameReply(new IrcChannelUser(user, userNickNameAndMode.Item2)); - } - } - } - - /// - /// Process RPL_LINKS responses from the server. - /// - /// The message received from the server. - [MessageProcessor("364")] - protected internal void ProcessMessageReplyLinks(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - var hostName = message.Parameters[1]; - Debug.Assert(message.Parameters[2] != null); - var clientServerHostName = message.Parameters[2]; - Debug.Assert(ServerName == null || clientServerHostName == ServerName); - Debug.Assert(message.Parameters[3] != null); - var infoParts = message.Parameters[3].SplitIntoPair(" "); - Debug.Assert(infoParts.Item2 != null); - var hopCount = int.Parse(infoParts.Item1); - var info = infoParts.Item2; - - // Add server information to temporary list. - listedServerLinks.Add(new IrcServerInfo(hostName, hopCount, info)); - } - - /// - /// Process RPL_ENDOFLINKS responses from the server. - /// - /// The message received from the server. - [MessageProcessor("365")] - protected internal void ProcessMessageReplyEndOfLinks(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - var mask = message.Parameters[1]; - - OnServerLinksListReceived(new IrcServerLinksListReceivedEventArgs(listedServerLinks)); - listedServerLinks = new List(); - } - - /// - /// Process RPL_ENDOFNAMES responses from the server. - /// - /// The message received from the server. - [MessageProcessor("366")] - protected internal void ProcessMessageReplyEndOfNames(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - var channel = GetChannelFromName(message.Parameters[1]); - channel.HandleUsersListReceived(); - } - - /// - /// Process RPL_ENDOFWHOWAS responses from the server. - /// - /// The message received from the server. - [MessageProcessor("369")] - protected internal void ProcessMessageReplyEndOfWhoWas(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - var user = GetUserFromNickName(message.Parameters[1], false); - OnWhoWasReplyReceived(new IrcUserEventArgs(user, null)); - } - - /// - /// Process RPL_MOTD responses from the server. - /// - /// The message received from the server. - [MessageProcessor("372")] - protected internal void ProcessMessageReplyMotd(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - motdBuilder.AppendLine(message.Parameters[1]); - } - - /// - /// Process RPL_MOTDSTART responses from the server. - /// - /// The message received from the server. - [MessageProcessor("375")] - protected internal virtual void ProcessMessageReplyMotdStart(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - motdBuilder.Clear(); - motdBuilder.AppendLine(message.Parameters[1]); - } - - /// - /// Process RPL_ENDOFMOTD responses from the server. - /// - /// The message received from the server. - [MessageProcessor("376")] - protected internal void ProcessMessageReplyMotdEnd(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - Debug.Assert(message.Parameters[1] != null); - motdBuilder.AppendLine(message.Parameters[1]); - - OnMotdReceived(new EventArgs()); - } - - /// - /// Process RPL_YOURESERVICE responses from the server. - /// - /// The message received from the server. - [MessageProcessor("383")] - protected internal void ProcessMessageReplyYouAreService(IrcMessage message) - { - Debug.Assert(message.Parameters[0] != null); - - Debug.Assert(message.Parameters[1] != null); - localUser.NickName = message.Parameters[1].Split(' ')[3]; - - isRegistered = true; - OnRegistered(new EventArgs()); - } - - /// - /// Process RPL_TIME responses from the server. - /// - /// The message received from the server. - [MessageProcessor("391")] - protected internal void ProcessMessageReplyTime(IrcMessage message) - { - Debug.Assert(message.Parameters[0] != null); - - Debug.Assert(message.Parameters[1] != null); - var server = message.Parameters[1]; - Debug.Assert(message.Parameters[2] != null); - var dateTime = message.Parameters[2]; - - OnServerTimeReceived(new IrcServerTimeEventArgs(server, dateTime)); - } - - /// - /// Process numeric error (from 400 to 599) responses from the server. - /// - /// The message received from the server. - [MessageProcessor("400-599")] - protected internal void ProcessMessageNumericError(IrcMessage message) - { - Debug.Assert(message.Parameters[0] != null); - - // Extract error parameters and message text from message parameters. - Debug.Assert(message.Parameters[1] != null); - var errorParameters = new List(); - string errorMessage = null; - for (var i = 1; i < message.Parameters.Count; i++) - { - if (i + 1 == message.Parameters.Count || message.Parameters[i + 1] == null) - { - errorMessage = message.Parameters[i]; - break; - } - errorParameters.Add(message.Parameters[i]); - } - - Debug.Assert(errorMessage != null); - OnProtocolError(new IrcProtocolErrorEventArgs(int.Parse(message.Command), errorParameters, errorMessage)); - } - } -} \ No newline at end of file diff --git a/IrcDotNet/IrcClientMessageSending.cs b/IrcDotNet/IrcClientMessageSending.cs deleted file mode 100644 index 896f1eb..0000000 --- a/IrcDotNet/IrcClientMessageSending.cs +++ /dev/null @@ -1,590 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using IrcDotNet.Properties; - -namespace IrcDotNet -{ - // Defines all message senders for the client. - partial class IrcClient - { - private void EnsureChannelName(string c) - { - // NOTE: we are missing the Control G (Asci 7) restriction - if (!IsChannelName(c) || c.Length > 50 || c.IndexOfAny(new[] {' ', ',', ':'}) != -1) - { - var t = new ArgumentException(string.Format("collection contains an invalid Channelname ({0})!", c)); - - t.Data["ChannelNameRequirements"] = "http://www.irchelp.org/irchelp/rfc/rfc2811.txt"; - throw t; - } - } - - /// - /// Sends the password for registering the connection. - /// This message must only be sent before the actual registration, which is done by - /// (for normal users) or (for services). - /// - /// The connection password. - protected void SendMessagePassword(string password) - { - WriteMessage(null, "pass", password); - } - - /// - /// Sends the nick name of the local user to the server. This command may be used either for intitially setting - /// the nick name or changing it at any point. - /// - /// The nick name to set. - protected void SendMessageNick(string nickName) - { - WriteMessage(null, "nick", nickName); - } - - /// - /// Sends a request to register the client as a user on the server. - /// - /// The user name of the user. - /// The initial mode of the user. - /// The real name of the user. - protected void SendMessageUser(string userName, int userMode, string realName) - { - WriteMessage(null, "user", userName, userMode.ToString(), "*", realName); - } - - /// - /// Sends a request to register the client as a service on the server. - /// - /// The nick name of the service. - /// - /// A wildcard expression for matching against server names, which determines where - /// the service is visible. - /// - /// A description of the service. - protected void SendMessageService(string nickName, string distribution, string description = "") - { - WriteMessage(null, "service", nickName, distribution, "0", "0", description); - } - - /// - /// Sends a request for server operator privileges. - /// - /// The user name with which to register. - /// The password with which to register. - protected void SendMessageOper(string userName, string password) - { - WriteMessage(null, "oper", userName, password); - } - - /// - /// Sends an update or request for the current modes of the specified user. - /// - /// The nick name of the user whose modes to update/request. - /// The mode string that indicates the user modes to change. - protected void SendMessageUserMode(string nickName, string modes = null) - { - WriteMessage(null, "mode", nickName, modes); - } - - /// - /// Sends a notification to the server indicating that the client is quitting the network. - /// - /// The comment to send the server, or for none. - protected void SendMessageQuit(string comment = null) - { - WriteMessage(null, "quit", comment); - } - - /// - /// Sends a request to disconnect the specified server from the network. - /// This command is only available to oeprators. - /// - /// The name of the server to disconnected from the network. - /// The comment to send the server. - protected void SendMessageSquit(string targetServer, string comment) - { - WriteMessage(null, "squit", targetServer, comment); - } - - /// - /// Sends a request to leave all channels in which the user is currently present. - /// - protected void SendMessageLeaveAll() - { - WriteMessage(null, "join", "0"); - } - - /// - /// A collection of 2-tuples of the names and keys of the channels to join. - protected void SendMessageJoin(IEnumerable> channels) - { - var secureChannels = channels.Select(c => - { - EnsureChannelName(c.Item1); - return c; - }).ToList(); - WriteMessage(null, "join", string.Join(",", secureChannels.Select(c => c.Item1)), - string.Join(",", secureChannels.Select(c => c.Item2))); - } - - /// - /// Sends a request to join the specified channels. - /// - /// A collection of the names of the channels to join. - protected void SendMessageJoin(IEnumerable channels) - { - var secureChannels = channels.Select(c => - { - EnsureChannelName(c); - return c; - }).ToList(); - WriteMessage(null, "join", string.Join(",", secureChannels)); - } - - /// - /// Sends a request to leave the specified channels. - /// - /// A collection of the names of the channels to leave. - /// The comment to send the server, or for none. - protected void SendMessagePart(IEnumerable channels, string comment = null) - { - WriteMessage(null, "part", string.Join(",", channels), comment); - } - - /// - /// Sends an update for the modes of the specified channel. - /// - /// The channel whose modes to update. - /// The mode string that indicates the channel modes to change. - /// A collection of parameters to the specified . - protected void SendMessageChannelMode(string channel, string modes = null, - IEnumerable modeParameters = null) - { - string modeParametersList = null; - if (modeParameters != null) - { - var modeParametersArray = modeParameters.ToArray(); - if (modeParametersArray.Length > 3) - throw new ArgumentException(Resources.MessageTooManyModeParameters); - modeParametersList = string.Join(",", modeParametersArray); - } - WriteMessage(null, "mode", channel, modes, modeParametersList); - } - - /// - /// Sends an update or request for the topic of the specified channel. - /// - /// The name of the channel whose topic to change. - /// The new topic to set, or to request the current topic. - protected void SendMessageTopic(string channel, string topic = null) - { - WriteMessage(null, "topic", channel, topic); - } - - /// - /// Sends a request to list all names visible to the client. - /// - /// - /// A collection of the names of channels for which to list users, or - /// for all channels. - /// - /// - /// The name of the server to which to forward the message, or - /// for the current server. - /// - protected void SendMessageNames(IEnumerable channels = null, string targetServer = null) - { - WriteMessage(null, "names", channels == null ? null : string.Join(",", channels), targetServer); - } - - /// - /// Sends a request to list channels and their topics. - /// - /// - /// A collection of the names of channels to list, or for all - /// channels. - /// - /// - /// The name of the server to which to forward the message, or - /// for the current server. - /// - protected void SendMessageList(IEnumerable channels = null, string targetServer = null) - { - WriteMessage(null, "list", channels == null ? null : string.Join(",", channels), targetServer); - } - - /// - /// Sends a request to invite the specified user to the specified channel. - /// - /// The name of the channel to which to invite the user. - /// The nick name of the user to invite. - protected void SendMessageInvite(string channel, string nickName) - { - WriteMessage(null, "invite", nickName, channel); - } - - /// - /// The name of the channel from which to kick the users. - /// A collection of the nick names of the users to kick from the channel. - protected void SendMessageKick(string channel, IEnumerable nickNames, string comment = null) - { - WriteMessage(null, "kick", channel, string.Join(",", nickNames), comment); - } - - /// - /// Sends a request to kick the specifier users from the specified channel. - /// - /// - /// A collection of 2-tuples of channel names and the nick names of the users to - /// kick from the channel. - /// - /// The comment to send the server, or for none. - protected void SendMessageKick(IEnumerable> channelsUsers, string comment = null) - { - WriteMessage(null, "kick", string.Join(",", channelsUsers.Select(user => user.Item1)), - string.Join(",", channelsUsers.Select(user => user.Item2)), comment); - } - - /// - /// Sends a private message to the specified targets. - /// - /// A collection of the targets to which to send the message. - /// The text of the message to send. - protected void SendMessagePrivateMessage(IEnumerable targets, string text) - { - var targetsArray = targets.ToArray(); - foreach (var target in targetsArray) - { - if (target.Contains(",")) - throw new ArgumentException(Resources.MessageInvalidTargetName, "arguments"); - } - WriteMessage(null, "privmsg", string.Join(",", targetsArray), text); - } - - /// - /// Sends a notice to the specified targets. - /// - /// A collection of the targets to which to send the message. - /// The text of the message to send. - protected void SendMessageNotice(IEnumerable targets, string text) - { - var targetsArray = targets.ToArray(); - foreach (var target in targetsArray) - { - if (target.Contains(",")) - throw new ArgumentException(Resources.MessageInvalidTargetName, "arguments"); - } - WriteMessage(null, "notice", string.Join(",", targetsArray), text); - } - - /// - /// Sends a request to receive the Message of the Day (MOTD) from the server. - /// - /// - /// The name of the server to which to forward the message, or for - /// the current server. - /// - protected void SendMessageMotd(string targetServer = null) - { - WriteMessage(null, "motd", targetServer); - } - - /// - /// Sends a request to get statistics about the size of the IRC network. - /// - /// - /// A wildcard expression for matching against the names of servers, or - /// to match the entire network. - /// - /// - /// The name of the server to which to forward the message, or - /// for the current server. - /// - protected void SendMessageLUsers(string serverMask = null, string targetServer = null) - { - WriteMessage(null, "lusers", serverMask, targetServer); - } - - /// - /// Sends a request for the version of the server program. - /// - /// - /// The name of the server to which to forward the message, or - /// for the current server. - /// - protected void SendMessageVersion(string targetServer = null) - { - WriteMessage(null, "version", targetServer); - } - - /// - /// Sends a request to query statistics for the server. - /// - /// The query to send the server. - /// - /// The name of the server to which to forward the message, or - /// for the current server. - /// - protected void SendMessageStats(string query = null, string targetServer = null) - { - WriteMessage(null, "stats", query, targetServer); - } - - /// - /// Sends a request to list all other servers linked to the server. - /// - /// A wildcard expression for matching the names of servers to list. - /// - /// The name of the server to which to forward the message, or - /// for the current server. - /// - protected void SendMessageLinks(string serverMask = null, string targetServer = null) - { - WriteMessage(null, "links", targetServer, serverMask); - } - - /// - /// Sends a request to query the local time on the server. - /// - /// - /// The name of the server to which to forward the message, or - /// for the current server. - /// - protected void SendMessageTime(string targetServer = null) - { - WriteMessage(null, "time", targetServer); - } - - /// - /// Sends a request for the server to try to connect to another server. - /// - /// The host name of the other server to which the server should connect. - /// The port on the other server to which the server should connect. - /// - /// The name of the server to which to forward the message, or - /// for the current server. - /// - protected void SendMessageConnect(string hostName, int port, string targetServer = null) - { - WriteMessage(null, "connect", hostName, port.ToString(), targetServer); - } - - /// - /// Sends a query to trace the route to the server. - /// - /// - /// The name of the server to which to forward the message, or - /// for the current server. - /// - protected void SendMessageTrace(string targetServer = null) - { - WriteMessage(null, "trace", targetServer); - } - - /// - /// Sends a request for information about the administrator of the server. - /// - /// - /// The name of the server to which to forward the message, or - /// for the current server. - /// - protected void SendMessageAdmin(string targetServer = null) - { - WriteMessage(null, "admin", targetServer); - } - - /// - /// Sends a request for general information about the server program. - /// - /// - /// The name of the server to which to forward the message, or - /// for the current server. - /// - protected void SendMessageInfo(string targetServer = null) - { - WriteMessage(null, "info", targetServer); - } - - /// - /// Sends a request to list services currently connected to the netwrok/ - /// - /// A wildcard expression for matching against the names of services. - /// The type of services to list. - protected void SendMessageServlist(string mask = null, string type = null) - { - WriteMessage(null, "servlist", mask, type); - } - - /// - /// Sends a query message to a service. - /// - /// The name of the service. - /// The text of the message to send. - protected void SendMessageSquery(string serviceName, string text) - { - WriteMessage(null, "squery", serviceName, text); - } - - /// - /// Sends a request to perform a Who query on users. - /// - /// - /// A wildcard expression for matching against channel names; or if none can be found, - /// host names, server names, real names, and nick names of users. If the value is , - /// all users are matched. - /// - /// - /// to match only server operators; - /// to match all users. - /// - protected void SendMessageWho(string mask = null, bool onlyOperators = false) - { - WriteMessage(null, "who", mask, onlyOperators ? "o" : null); - } - - /// - /// Sends a request to perform a WhoIs query on users. - /// - /// - /// A collection of wildcard expressions for matching against the nick names of - /// users. - /// - /// - /// The name of the server to which to forward the message, or - /// for the current server. - /// - protected void SendMessageWhoIs(IEnumerable nickNameMasks, string targetServer = null) - { - WriteMessage(null, "whois", targetServer, string.Join(",", nickNameMasks)); - } - - /// - /// Sends a request to perform a WhoWas query on users. - /// - /// - /// A collection of wildcard expressions for matching against the nick names of - /// users. - /// - /// The maximum number of (most recent) entries to return. - /// - /// The name of the server to which to forward the message, or - /// for the current server. - /// - protected void SendMessageWhoWas(IEnumerable nickNames, int entriesCount = -1, - string targetServer = null) - { - WriteMessage(null, "whowas", string.Join(",", nickNames), entriesCount.ToString(), targetServer); - } - - /// - /// Sends a request to disconnect the specified user from the server. - /// - /// The nick name of the user to disconnect. - /// The comment to send the server. - protected void SendMessageKill(string nickName, string comment) - { - WriteMessage(null, "kill", nickName, comment); - } - - /// - /// Sends a ping request to the server. - /// - /// The name of the server to which to send the request. - /// - /// The name of the server to which to forward the message, or - /// for the current server. - /// - protected void SendMessagePing(string server, string targetServer = null) - { - WriteMessage(null, "ping", server, targetServer); - } - - /// - /// Sends a pong response (to a ping) to the server. - /// - /// The name of the server to which to send the response. - /// - /// The name of the server to which to forward the message, or - /// for the current server. - /// - protected void SendMessagePong(string server, string targetServer = null) - { - WriteMessage(null, "pong", server, targetServer); - } - - /// - /// Sends an update to the server indicating that the local user is away. - /// - /// - /// The text of the away message. The away message is sent to any user that tries to contact - /// the local user while it is away. - /// - protected void SendMessageAway(string text = null) - { - WriteMessage(null, "away", text); - } - - /// - /// Sends a request to the server telling it to reprocess its configuration settings. - /// - protected void SendMessageRehash() - { - WriteMessage(null, "rehash"); - } - - /// - /// Sends a request to the server telling it to shut down. - /// - protected void SendMessageDie() - { - WriteMessage(null, "die"); - } - - /// - /// Sends a message to the server telling it to restart. - /// - protected void SendMessageRestart() - { - WriteMessage(null, "restart"); - } - - /// - /// Sends a request to return a list of information about all users currently registered on the server. - /// - /// - /// The name of the server to which to forward the message, or - /// for the current server. - /// - protected void SendMessageUsers(string targetServer = null) - { - WriteMessage(null, "users", targetServer); - } - - /// - /// Sends a message to all connected users that have the 'w' mode set. - /// - /// The text of the message to send. - protected void SendMessageWallops(string text) - { - WriteMessage(null, "wallops", text); - } - - /// - /// Sends a request to return the host names of the specified users. - /// - /// A collection of the nick names of the users to query. - protected void SendMessageUserHost(IEnumerable nickNames) - { - WriteMessage(null, "userhost", nickNames); - } - - /// - /// Sends a request to check whether the specified users are currently online. - /// - /// A collection of the nick names of the users to query. - protected void SendMessageIsOn(IEnumerable nickNames) - { - WriteMessage(null, "ison", nickNames); - } - } -} \ No newline at end of file diff --git a/IrcDotNet/IrcDotNet.csproj b/IrcDotNet/IrcDotNet.csproj deleted file mode 100644 index df5e001..0000000 --- a/IrcDotNet/IrcDotNet.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - 0.7.0 - netcoreapp2.0 - IrcDotNet - IrcDotNet - communication;irc;networking;ctcp - https://github.com/IrcDotNet/IrcDotNet/releases/tag/0.6.0 - https://raw.githubusercontent.com/IrcDotNet/IrcDotNet/master/doc/content/img/logo.png - https://IrcDotNet.github.io/IrcDotNet/ - https://github.com/IrcDotNet/IrcDotNet/License.txt - true - Library for communicating via Internet Relay Chat (IRC) protocol and its extensions - Debug;Release;Debug-IPv4 - - - - TRACE;DEBUG_IPV4;NETSTANDARD1_5;IPV4 - - - - - - - - - diff --git a/IrcDotNet/IrcEventArgs.cs b/IrcDotNet/IrcEventArgs.cs deleted file mode 100644 index 99d3611..0000000 --- a/IrcDotNet/IrcEventArgs.cs +++ /dev/null @@ -1,670 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Security.Cryptography.X509Certificates; -using System.Text; -#if !SILVERLIGHT -using System.Net.Security; - -#endif - -namespace IrcDotNet -{ - /// - /// Provides data for the event. - /// - /// - public class IrcChannelListReceivedEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// A list of information about the channels that was returned by the server. - public IrcChannelListReceivedEventArgs(IList channels) - { - if (channels == null) - throw new ArgumentNullException("channels"); - - Channels = new ReadOnlyCollection(channels); - } - - /// - /// Gets the list of information about the channels that was returned by the server. - /// - /// The list of channels. - public IList Channels { get; private set; } - } - - /// - /// Provides data for the event. - /// - /// - public class IrcServerVersionInfoEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// The version of the server. - /// The debug level of the server. - /// The name of the server. - /// The comments about the server. - public IrcServerVersionInfoEventArgs(string version, string debugLevel, string serverName, string comments) - { - if (version == null) - throw new ArgumentNullException("version"); - if (debugLevel == null) - throw new ArgumentNullException("debugLevel"); - if (serverName == null) - throw new ArgumentNullException("serverName"); - if (comments == null) - throw new ArgumentNullException("comments"); - - Version = version; - DebugLevel = debugLevel; - ServerName = serverName; - Comments = comments; - } - - /// - /// Gets the version of the server. - /// - /// The version of the server. - public string Version { get; private set; } - - /// - /// Gets the debug level of the server. - /// - /// The debug level of the server. - public string DebugLevel { get; private set; } - - /// - /// Gets the name of the server to which the version information applies. - /// - /// The name of the server. - public string ServerName { get; private set; } - - /// - /// Gets the comments about the server. - /// - /// The comments about the server. - public string Comments { get; private set; } - } - - /// - /// Provides data for the event. - /// - /// - public class IrcServerTimeEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// The name of the server. - /// The local date/time received from the server. - public IrcServerTimeEventArgs(string serverName, string dateTime) - { - if (serverName == null) - throw new ArgumentNullException("serverName"); - if (dateTime == null) - throw new ArgumentNullException("dateTime"); - - ServerName = serverName; - DateTime = dateTime; - } - - /// - /// Gets the name of the server to which the version information applies. - /// - /// The name of the server. - public string ServerName { get; private set; } - - /// - /// Gets the local date/time for the server. - /// - /// The local date/time for the server. - public string DateTime { get; private set; } - } - - /// - /// Provides data for the event. - /// - /// - public class IrcServerLinksListReceivedEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// A list of information about the server links that was returned by the server. - public IrcServerLinksListReceivedEventArgs(IList links) - { - if (links == null) - throw new ArgumentNullException("links"); - - Links = new ReadOnlyCollection(links); - } - - /// - /// Gets the list of information about the server links that was returned by the server - /// - /// The list of server links. - public IList Links { get; private set; } - } - - /// - /// Provides data for the event. - /// - /// - public class IrcServerStatsReceivedEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// A list of statistical entries that was returned by the server. - public IrcServerStatsReceivedEventArgs(IList entries) - { - if (entries == null) - throw new ArgumentNullException("entries"); - - Entries = new ReadOnlyCollection(entries); - } - - /// - /// Gets the list of statistical entries that was returned by the server. - /// - /// The list of statistical entries. - public IList Entries { get; private set; } - } - - /// - /// - /// Gives the option to handle the preview event and thus stop the normal event from being raised. - /// - /// - public class IrcPreviewMessageEventArgs : IrcMessageEventArgs - { - /// - public IrcPreviewMessageEventArgs(IIrcMessageSource source, IList targets, string text, - Encoding encoding) - : base(source, targets, text, encoding) - { - Handled = false; - } - - /// - /// Gets or sets whether the event has been handled. If it is handled, the corresponding normal (non-preview) - /// event is not raised. - /// - /// if the event has been handled; , otherwise. - public bool Handled { get; set; } - } - - /// - /// Provides data for events that are raised when an IRC message or notice is sent or received. - /// - /// - public class IrcMessageEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// The source of the message. - /// A list of the targets of the message. - /// The text of the message. - /// The encoding of the message text. - /// is . - /// is . - public IrcMessageEventArgs(IIrcMessageSource source, IList targets, string text, - Encoding encoding) - { - if (targets == null) - throw new ArgumentNullException("target"); - if (text == null) - throw new ArgumentNullException("text"); - if (encoding == null) - throw new ArgumentNullException("textEncoding"); - - Source = source; - Targets = new ReadOnlyCollection(targets); - Text = text; - Encoding = encoding; - } - - /// - /// Gets the source of the message. - /// - /// The source of the message. - public IIrcMessageSource Source { get; private set; } - - /// - /// Gets a list of the targets of the message. - /// - /// The targets of the message. - public IList Targets { get; private set; } - - /// - /// Gets the text of the message. - /// - /// The text of the message. - public string Text { get; } - - /// - /// Gets the encoding of the message text. - /// - /// The encoding of the message text. - public Encoding Encoding { get; } - - /// - /// Gets the text of the message in the specified encoding. - /// - /// - /// The encoding in which to get the message text, or to use the - /// default encoding. - /// - /// The text of the message. - public string GetText(Encoding encoding = null) - { - return Text.ChangeEncoding(Encoding, encoding); - } - } - - /// - /// Provides data for the event. - /// - /// - public class IrcChannelInvitationEventArgs : IrcChannelEventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// The channel to which the recipient user is invited. - /// The user inviting the recipient user to the channel. - public IrcChannelInvitationEventArgs(IrcChannel channel, IrcUser inviter) - : base(channel) - { - if (inviter == null) - throw new ArgumentNullException("inviter"); - - Inviter = inviter; - } - - /// - /// Gets the user inviting the recipient user to the channel - /// - /// The inviter user. - public IrcUser Inviter { get; private set; } - } - - /// - /// Provides data for events that concern an . - /// - /// - public class IrcChannelUserEventArgs : IrcCommentEventArgs - { - /// - /// - /// Initializes a new instance of the class. - /// - /// The channel user that the event concerns. - public IrcChannelUserEventArgs(IrcChannelUser channelUser, string comment = null) - : base(comment) - { - if (channelUser == null) - throw new ArgumentNullException("channelUser"); - - ChannelUser = channelUser; - } - - /// - /// Gets the channel user that the event concerns. - /// - /// The channel user that the event concerns. - public IrcChannelUser ChannelUser { get; private set; } - } - - /// - /// Provides data for events that concern an . - /// - /// - public class IrcChannelEventArgs : IrcCommentEventArgs - { - /// - /// - /// Initializes a new instance of the class. - /// - /// The channel that the event concerns. - public IrcChannelEventArgs(IrcChannel channel, string comment = null) - : base(comment) - { - if (channel == null) - throw new ArgumentNullException("channel"); - - Channel = channel; - } - - /// - /// Gets the channel that the event concerns. - /// - /// The channel that the event concerns. - public IrcChannel Channel { get; private set; } - } - - /// - /// Provides data for events that concern an . - /// - /// - public class IrcUserEventArgs : IrcCommentEventArgs - { - /// - /// - /// Initializes a new instance of the class. - /// - /// The user that the event concerns, or for no user. - public IrcUserEventArgs(IrcUser user, string comment = null) - : base(comment) - { - User = user; - } - - /// - /// Gets the user that the event concerns. - /// - /// The user that the event concerns. - public IrcUser User { get; private set; } - } - - /// - /// Provides data for events that specify a comment. - /// - /// - public class IrcNameEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// The name that the event specified. - public IrcNameEventArgs(string name) - { - Name = name; - } - - /// - /// Gets the name that the event specified. - /// - /// The name that the event specified. - public string Name { get; private set; } - } - - /// - /// Provides data for events that specify a name. - /// - /// - public class IrcCommentEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// The comment that the event specified. - public IrcCommentEventArgs(string comment) - { - Comment = comment; - } - - /// - /// Gets the comment that the event specified. - /// - /// The comment that the event specified. - public string Comment { get; private set; } - } - - /// - /// Provides data for the and events. - /// - /// - public class IrcPingOrPongReceivedEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// The name of the server that is the source of the ping or pong. - public IrcPingOrPongReceivedEventArgs(string server) - { - if (server == null) - throw new ArgumentNullException("server"); - - Server = server; - } - - /// - /// Gets the name of the server that is the source of the ping or pong. - /// - /// The name of the server. - public string Server { get; private set; } - } - - /// - /// Provides data for the events. - /// - /// - public class IrcPingReceivedEventArgs : IrcPingOrPongReceivedEventArgs - { - public IrcPingReceivedEventArgs(string server) - : base(server) - { - SendPong = true; - } - - /// - /// Gets or sets if we should send a Pong back - /// - /// A value indicating sending a Pong. - public bool SendPong { get; set; } - } - - /// - /// Provides data for events that specify information about a server. - /// - /// - public class IrcServerInfoEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// The address of the server. - /// The port on which to connect to the server. - public IrcServerInfoEventArgs(string address, int port) - { - if (address == null) - throw new ArgumentNullException("address"); - if (port <= 0) - throw new ArgumentOutOfRangeException("port"); - - Address = address; - Port = port; - } - - /// - /// Gets the address of the server. - /// - /// The address of the server. - public string Address { get; private set; } - - /// - /// Gets the port on which to connect to the server. - /// - /// The port on which to connect to the server. - public int Port { get; private set; } - } - - /// - /// Provides data for the event. - /// - /// - public class IrcErrorMessageEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// The error message given by the server. - public IrcErrorMessageEventArgs(string message) - { - if (message == null) - throw new ArgumentNullException("message"); - - Message = message; - } - - /// - /// Gets the text of the error message. - /// - /// The text of the error message. - public string Message { get; private set; } - } - - /// - /// Provides data for the event. - /// - /// - public class IrcProtocolErrorEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// The code. - /// The parameters. - /// The message. - public IrcProtocolErrorEventArgs(int code, IList parameters, string message) - { - if (parameters == null) - throw new ArgumentNullException("parameters"); - if (message == null) - throw new ArgumentNullException("message"); - - Code = code; - Parameters = new ReadOnlyCollection(parameters); - Message = message; - } - - /// - /// Gets or sets the numeric code that indicates the type of error. - /// - /// The numeric code that indicates the type of error. - public int Code { get; private set; } - - /// - /// Gets a list of the parameters of the error. - /// - /// A lsit of the parameters of the error. - public IList Parameters { get; private set; } - - /// - /// Gets the text of the error message. - /// - /// The text of the error message. - public string Message { get; private set; } - } - - /// - /// Provides data for the and - /// events. - /// - /// - public class IrcRawMessageEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// The message that was sent/received. - /// The raw content of the message. - public IrcRawMessageEventArgs(IrcClient.IrcMessage message, string rawContent) - { - Message = message; - RawContent = rawContent; - } - - /// - /// Gets the message that was sent/received by the client. - /// - /// The message that was sent/received by the client. - public IrcClient.IrcMessage Message { get; private set; } - - /// - /// Gets the raw content of the message. - /// - /// The raw content of the message. - public string RawContent { get; private set; } - } - -#if !SILVERLIGHT - - /// - /// Provides data for the event. - /// - /// - public class IrcValidateSslCertificateEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// The certificate used to authenticate the remote party. - /// The chain of certificate authorities. - /// The errors associated with the remote certificate. - public IrcValidateSslCertificateEventArgs(X509Certificate certificate, X509Chain chain, - SslPolicyErrors sslPolicyErrors) - { - Certificate = certificate; - Chain = chain; - SslPolicyErrors = sslPolicyErrors; - } - - /// - /// Gets the certificate used to authenticate the remote party.. - /// - /// The certificate. - public X509Certificate Certificate { get; private set; } - - /// - /// Gets the chain of certificate authorities associated with the remote certificate. - /// - /// The chain. - public X509Chain Chain { get; private set; } - - /// - /// Gets the errors associated with the remote certificate. - /// - /// The SSL policy errors. - public SslPolicyErrors SslPolicyErrors { get; private set; } - - /// - /// Gets or sets whether the certificate given by the server is valid. - /// - /// if the certificate is valid; , otherwise. - public bool IsValid { get; set; } - } - -#endif - - /// - /// Provides data for the event. - /// - /// - public class IrcErrorEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// The error. - public IrcErrorEventArgs(Exception error) - { - if (error == null) - throw new ArgumentNullException("error"); - - Error = error; - } - - /// - /// Gets the error encountered by the client. - /// - /// The error encountered by the client. - public Exception Error { get; private set; } - } -} \ No newline at end of file diff --git a/IrcDotNet/IrcLocalUser.cs b/IrcDotNet/IrcLocalUser.cs deleted file mode 100644 index 22ded39..0000000 --- a/IrcDotNet/IrcLocalUser.cs +++ /dev/null @@ -1,505 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using IrcDotNet.Collections; - -namespace IrcDotNet -{ - /// - /// Represents the local user of a specific . - /// The local user is the user as which the client has connected and registered, and may be either a normal user or - /// service. - /// - /// - [DebuggerDisplay("{ToString(), nq} (local)")] - public class IrcLocalUser : IrcUser, IIrcMessageSendHandler, IIrcMessageReceiveHandler, IIrcMessageReceiver - { - // True if local user is service; false, if local user is normal user. - - // Collection of current modes of user. - private readonly HashSet modes; - - internal IrcLocalUser(string nickName, string distribution, string description) - : base(true, nickName, null, null) - { - IsService = true; - modes = new HashSet(); - Modes = new ReadOnlySet(modes); - ServiceDistribution = distribution; - ServiceDescription = description; - } - - internal IrcLocalUser(string nickName, string userName, string realName, IEnumerable modes = null) - : base(true, nickName, userName, realName) - { - IsService = false; - this.modes = new HashSet(); - Modes = new ReadOnlySet(this.modes); - if (modes != null) - this.modes.AddRange(modes); - } - - /// - /// Gets whether the local user is a service or normal user. - /// - /// - /// if the user is a service; , if the user is a normal - /// user. - /// - public bool IsService { get; } - - /// - /// Gets a read-only collection of the modes the user currently has. - /// - /// The current modes of the user. - public ReadOnlySet Modes { get; } - - /// - /// Gets the distribution of the service, which determines its visibility to users on specific servers. - /// - /// - /// A wildcard expression for matching against the names of servers on which the service should be - /// visible. - /// - public string ServiceDistribution { get; } - - /// - /// Gets the distribution of the service, which determines its visibility to users on specific servers. - /// - /// - /// A wildcard expression for matching against the names of servers on which the service should be - /// visible. - /// - public string ServiceDescription { get; } - - /// - /// Occurs when the local user has received a message. - /// - public event EventHandler MessageReceived; - - /// - /// Occurs when the local user has received a notice. - /// - public event EventHandler NoticeReceived; - - /// - /// Occurs when the modes of the local user have changed. - /// - public event EventHandler ModesChanged; - - /// - /// Occurs when the local user has joined a channel. - /// - public event EventHandler JoinedChannel; - - /// - /// Occurs when the local user has left a channel. - /// - public event EventHandler LeftChannel; - - /// - /// Occurs when the local user has sent a message. - /// - public event EventHandler MessageSent; - - /// - /// Occurs when the local user has received a message, before the event. - /// - public event EventHandler PreviewMessageReceived; - - /// - /// Occurs when the local user has sent a notice. - /// - public event EventHandler NoticeSent; - - /// - /// Occurs when the local user has received a notice, before the event. - /// - public event EventHandler PreviewNoticeReceived; - - /// - /// The to which to send the message. - public void SendMessage(IIrcMessageTarget target, string text) - { - if (target == null) - throw new ArgumentNullException("target"); - if (text == null) - throw new ArgumentNullException("text"); - - SendMessage(new[] {target}, text); - } - - /// - /// - /// - /// A message target may be an , , or . - /// - /// A collection of targets to which to send the message. - public void SendMessage(IEnumerable targets, string text) - { - if (targets == null) - throw new ArgumentNullException("targets"); - if (text == null) - throw new ArgumentNullException("text"); - - SendMessage(targets.Select(t => t.Name), text); - } - - /// - /// The name of the target to which to send the message. - public void SendMessage(string target, string text) - { - if (target == null) - throw new ArgumentNullException("target"); - if (text == null) - throw new ArgumentNullException("text"); - - SendMessage(new[] {target}, text); - } - - /// - /// Sends a message to the specified target. - /// - /// A collection of the names of targets to which to send the message. - /// The ASCII-encoded text of the message to send. - /// The encoding in which to send the value of . - /// is . - /// is . - public void SendMessage(IEnumerable targets, string text, Encoding encoding = null) - { - if (targets == null) - throw new ArgumentNullException("targets"); - if (text == null) - throw new ArgumentNullException("text"); - - Client.SendPrivateMessage(targets, text.ChangeEncoding(Client.TextEncoding, encoding)); - } - - /// - /// The to which to send the notice. - public void SendNotice(IIrcMessageTarget target, string text) - { - if (target == null) - throw new ArgumentNullException("target"); - if (text == null) - throw new ArgumentNullException("text"); - - SendNotice(new[] {target}, text); - } - - /// - /// - /// - /// A message target may be an , , or . - /// - /// A collection of targets to which to send the notice. - public void SendNotice(IEnumerable targets, string text) - { - if (targets == null) - throw new ArgumentNullException("targets"); - if (text == null) - throw new ArgumentNullException("text"); - - SendNotice(targets.Select(t => t.Name), text); - } - - /// - /// The name of the target to which to send the notice. - public void SendNotice(string target, string text) - { - if (target == null) - throw new ArgumentNullException("target"); - if (text == null) - throw new ArgumentNullException("text"); - - SendNotice(new[] {target}, text); - } - - /// - /// Sends a notice to the specified target. - /// - /// A collection of the names of targets to which to send the notice. - /// The ASCII-encoded text of the notice to send. - /// The encoding in which to send the value of . - /// is . - /// is . - public void SendNotice(IEnumerable targets, string text, Encoding encoding = null) - { - if (targets == null) - throw new ArgumentNullException("targets"); - if (text == null) - throw new ArgumentNullException("text"); - - Client.SendNotice(targets, text.ChangeEncoding(Client.TextEncoding, encoding)); - } - - /// - /// Sets the nick name of the local user to the specified text. - /// - /// The new nick name of the local user. - /// is . - public void SetNickName(string nickName) - { - if (nickName == null) - throw new ArgumentNullException("nickName"); - - Client.SetNickName(nickName); - } - - /// - /// Sets the local user as away, giving the specified message. - /// - /// The text of the response sent to a user when they try to message you while away. - /// is . - public void SetAway(string text) - { - if (text == null) - throw new ArgumentNullException("text"); - - Client.SetAway(text); - } - - /// - /// Sets the local user as here (no longer away). - /// - public void UnsetAway() - { - Client.UnsetAway(); - } - - /// - /// Requests a list of the current modes of the user. - /// - public void GetModes() - { - Client.GetLocalUserModes(this); - } - - /// - public void SetModes(params char[] newModes) - { - SetModes((IEnumerable) newModes); - } - - /// - /// - /// A collection of mode characters that should become the new modes. - /// Any modes in the collection that are not currently set will be set, and any nodes not in the collection that - /// are currently set will be unset. - /// - /// is . - public void SetModes(IEnumerable newModes) - { - if (newModes == null) - throw new ArgumentNullException("newModes"); - - lock (((ICollection) Modes).SyncRoot) - SetModes(newModes.Except(modes), modes.Except(newModes)); - } - - /// - /// is . - /// is . - public void SetModes(IEnumerable setModes, IEnumerable unsetModes) - { - if (setModes == null) - throw new ArgumentNullException("setModes"); - if (unsetModes == null) - throw new ArgumentNullException("unsetModes"); - - SetModes("+" + string.Join(string.Empty, setModes) + "-" + string.Join(string.Empty, unsetModes)); - } - - /// - /// Sets the specified modes on the local user. - /// - /// - /// The mode string that specifies mode changes, which takes the form - /// `( "+" / "-" ) *( mode character )`. - /// - /// is . - public void SetModes(string modes) - { - if (modes == null) - throw new ArgumentNullException("modes"); - - Client.SetLocalUserModes(this, modes); - } - - internal void HandleModesChanged(string newModes) - { - lock (((ICollection) Modes).SyncRoot) - modes.UpdateModes(newModes); - - OnModesChanged(new EventArgs()); - } - - internal void HandleJoinedChannel(IrcChannel channel) - { - OnJoinedChannel(new IrcChannelEventArgs(channel, null)); - } - - internal void HandleLeftChannel(IrcChannel channel) - { - OnLeftChannel(new IrcChannelEventArgs(channel, null)); - } - - internal void HandleMessageSent(IList targets, string text) - { - OnMessageSent(new IrcMessageEventArgs(this, targets, text, Client.TextEncoding)); - } - - internal void HandleNoticeSent(IList targets, string text) - { - OnNoticeSent(new IrcMessageEventArgs(this, targets, text, Client.TextEncoding)); - } - - internal void HandleMessageReceived(IIrcMessageSource source, IList targets, string text) - { - var previewEventArgs = new IrcPreviewMessageEventArgs(source, targets, text, Client.TextEncoding); - OnPreviewMessageReceived(previewEventArgs); - if (!previewEventArgs.Handled) - OnMessageReceived(new IrcMessageEventArgs(source, targets, text, Client.TextEncoding)); - } - - internal void HandleNoticeReceived(IIrcMessageSource source, IList targets, string text) - { - var previewEventArgs = new IrcPreviewMessageEventArgs(source, targets, text, Client.TextEncoding); - OnPreviewNoticeReceived(previewEventArgs); - if (!previewEventArgs.Handled) - OnNoticeReceived(new IrcMessageEventArgs(source, targets, text, Client.TextEncoding)); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnModesChanged(EventArgs e) - { - var handler = ModesChanged; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnJoinedChannel(IrcChannelEventArgs e) - { - var handler = JoinedChannel; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnLeftChannel(IrcChannelEventArgs e) - { - var handler = LeftChannel; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnMessageSent(IrcMessageEventArgs e) - { - var handler = MessageSent; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnMessageReceived(IrcMessageEventArgs e) - { - var handler = MessageReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnPreviewMessageReceived(IrcPreviewMessageEventArgs e) - { - var handler = PreviewMessageReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnNoticeSent(IrcMessageEventArgs e) - { - var handler = NoticeSent; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnPreviewNoticeReceived(IrcPreviewMessageEventArgs e) - { - var handler = PreviewNoticeReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnNoticeReceived(IrcMessageEventArgs e) - { - var handler = NoticeReceived; - if (handler != null) - handler(this, e); - } - - #region IIrcMessageSendHandler Members - - void IIrcMessageSendHandler.HandleMessageSent(IList targets, string text) - { - HandleMessageSent(targets, text); - } - - void IIrcMessageSendHandler.HandleNoticeSent(IList targets, string text) - { - HandleNoticeSent(targets, text); - } - - #endregion - - #region IIrcMessageReceiveHandler Members - - void IIrcMessageReceiveHandler.HandleMessageReceived(IIrcMessageSource source, IList targets, - string text) - { - HandleMessageReceived(source, targets, text); - } - - void IIrcMessageReceiveHandler.HandleNoticeReceived(IIrcMessageSource source, IList targets, - string text) - { - HandleNoticeReceived(source, targets, text); - } - - #endregion - } -} \ No newline at end of file diff --git a/IrcDotNet/IrcNetworkInfo.cs b/IrcDotNet/IrcNetworkInfo.cs deleted file mode 100644 index 7cfcde9..0000000 --- a/IrcDotNet/IrcNetworkInfo.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace IrcDotNet -{ - /// - /// Stores information about a specific IRC network. - /// - public struct IrcNetworkInfo - { - /// - /// The number of visible users on the network. - /// - public int? VisibleUsersCount; - - /// - /// The number of invisible users on the network. - /// - public int? InvisibleUsersCount; - - /// - /// The number of servers in the network. - /// - public int? ServersCount; - - /// - /// The number of operators on the network. - /// - public int? OperatorsCount; - - /// - /// The number of unknown connections to the network. - /// - public int? UnknownConnectionsCount; - - /// - /// The number of channels that currently exist on the network. - /// - public int? ChannelsCount; - - /// - /// The number of clients connected to the server. - /// - public int? ServerClientsCount; - - /// - /// The number of others servers connected to the server. - /// - public int? ServerServersCount; - - /// - /// The number of services connected to the server. - /// - public int? ServerServicesCount; - } -} \ No newline at end of file diff --git a/IrcDotNet/IrcRegistrationInfo.cs b/IrcDotNet/IrcRegistrationInfo.cs deleted file mode 100644 index af9c21f..0000000 --- a/IrcDotNet/IrcRegistrationInfo.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Collections.Generic; - -namespace IrcDotNet -{ - /// - /// Provides information used by an for registering the connection as a service. - /// - /// - public class IrcServiceRegistrationInfo : IrcRegistrationInfo - { - /// - /// Gets or sets the distribution of the service, which determines its visibility to users on specific servers. - /// - /// - /// A wildcard expression for matching against the names of servers on which the service should be - /// visible. - /// - public string Distribution { get; set; } - - /// - /// Gets or sets the description of the service to set upon registration. - /// The description cannot later be changed. - /// - /// A description of the service. - public string Description { get; set; } - } - - /// - /// Provides information used by an for registering the connection as a user. - /// - /// - public class IrcUserRegistrationInfo : IrcRegistrationInfo - { - /// - /// Gets or sets the user name of the local user to set upon registration. - /// The user name cannot later be changed. - /// - /// The user name of the local user. - public string UserName { get; set; } - - /// - /// Gets or sets the real name of the local user to set upon registration. - /// The real name cannot later be changed. - /// - /// The real name of the local user. - public string RealName { get; set; } - - /// - /// Gets or sets the modes of the local user to set initially. - /// The collection should not contain any characters except 'w' or 'i'. - /// The modes can be changed after registration. - /// - /// A collection of modes to set on the local user. - public ICollection UserModes { get; set; } - } - - /// - /// Provides information used by an for registering the connection with the server. - /// - /// - public abstract class IrcRegistrationInfo - { - /// - /// Gets or sets the password for registering with the server. - /// - /// The password for registering with the server. - public string Password { get; set; } - - /// - /// Gets or sets the nick name of the local user to set initially upon registration. - /// The nick name can be changed after registration. - /// - /// The initial nick name of the local user. - public string NickName { get; set; } - } -} \ No newline at end of file diff --git a/IrcDotNet/IrcServer.cs b/IrcDotNet/IrcServer.cs deleted file mode 100644 index dbf5aca..0000000 --- a/IrcDotNet/IrcServer.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace IrcDotNet -{ - /// - /// Represents an IRC server from the view of a particular client. - /// - /// - public class IrcServer : IIrcMessageSource - { - internal IrcServer(string hostName) - { - HostName = hostName; - } - - /// - /// Gets the host name of the server. - /// - /// The host name of the server. - public string HostName { get; } - - #region IIrcMessageSource Members - - string IIrcMessageSource.Name - { - get { return HostName; } - } - - #endregion - - /// - /// Returns a string representation of this instance. - /// - /// A string that represents this instance. - public override string ToString() - { - return HostName; - } - } -} \ No newline at end of file diff --git a/IrcDotNet/IrcServerInfo.cs b/IrcDotNet/IrcServerInfo.cs deleted file mode 100644 index 138998e..0000000 --- a/IrcDotNet/IrcServerInfo.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace IrcDotNet -{ - /// - /// Stores information about a particular server in an IRC network. - /// - /// - public struct IrcServerInfo - { - /// - /// The host name of the server. - /// - private string HostName; - - /// - /// The hop count of the server from the local server. - /// - private int? HopCount; - - /// - /// A string containing arbitrary information about the server. - /// - private string Info; - - /// - /// Initializes a new instance of the class with the specified properties. - /// - /// The host name of the server. - /// The hop count of the server from the local server. - /// A string containing arbitrary information about the server. - public IrcServerInfo(string hostName, int? hopCount, string info) - { - HostName = hostName; - HopCount = hopCount; - Info = info; - } - } -} \ No newline at end of file diff --git a/IrcDotNet/IrcServerStatisticalEntry.cs b/IrcDotNet/IrcServerStatisticalEntry.cs deleted file mode 100644 index 0f2a5c8..0000000 --- a/IrcDotNet/IrcServerStatisticalEntry.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.Collections.Generic; - -namespace IrcDotNet -{ - /// - /// Stores a statistical entry for an IRC server. - /// - public struct IrcServerStatisticalEntry - { - /// - /// The type of the statistical entry. - /// - public int Type; - - /// - /// The list of parameters of the statistical entry. - /// - public IList Parameters; - } - - /// - /// Defines the types of statistical entries for an IRC server. - /// - /// - /// These entry types correspond to the STATS replies described in the RFC for the IRC protocol. - /// - public enum IrcServerStatisticalEntryCommonType - { - /// - /// An active connection to the server. - /// - Connection, - - /// - /// A command supported by the server. - /// - Command, - - /// - /// A server to which the local server may connect. - /// - AllowedServerConnect, - - /// - /// A server from which the local server may accept connections. - /// - AllowedServerAccept, - - /// - /// A client that may connect to the server. - /// - AllowedClient, - - /// - /// A client that is banned from connecting to the server. - /// - BannedClient, - - /// - /// A connection class defined by the server. - /// - ConnectionClass, - - /// - /// The leaf depth of a server in the network. - /// - LeafDepth, - - /// - /// The uptime of the server. - /// - Uptime, - - /// - /// An operator on the server. - /// - AllowedOperator, - - /// - /// A hub server within the network. - /// - HubServer - } -} \ No newline at end of file diff --git a/IrcDotNet/IrcStandardFloodPreventer.cs b/IrcDotNet/IrcStandardFloodPreventer.cs deleted file mode 100644 index 4acfb15..0000000 --- a/IrcDotNet/IrcStandardFloodPreventer.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; - -namespace IrcDotNet -{ - /// - /// Represents a flood protector that throttles data sent by the client according to the standard rules implemented - /// by modern IRC servers. - /// - /// - /// The principle is that no message may be sent by the client once the value of an internal counter has reached - /// the value of . The counter is incremented every time a message is sent, and - /// decremented by one every duration of . Hence, messages may be sent immediately in - /// bursts so long as the high rate is not sustained, else a delay is introduced between the sending of - /// successive messages, such that the data. - /// - /// - public class IrcStandardFloodPreventer : IIrcFloodPreventer - { - private const int ticksPerMillisecond = 10000; - - // Period between each decrement of counter, in milliseconds. - - // Absolute time of last counter decrement, in milliseconds. - private long lastCounterDecrementTime; - - // Maximum number of messages that can be sent in burst. - - // Number of messages sent within current burst. - private int messageCounter; - - /// - /// Initializes a new instance of the class. - /// - /// The maximum number of messages that can be sent in a burst. - /// - /// The number of milliseconds between each decrement of the message counter. - /// - public IrcStandardFloodPreventer(int maxMessageBurst, long counterPeriod) - { - MaxMessageBurst = maxMessageBurst; - CounterPeriod = counterPeriod; - - messageCounter = 0; - lastCounterDecrementTime = 0; - } - - /// - /// Gets the maximum message number of messages that can be sent in a burst. - /// - /// The maximum message number of messages that can be sent in a burst.. - public int MaxMessageBurst { get; } - - /// - /// Gets the number of milliseconds between each decrement of the message counter. - /// - /// The period of the counter, in milliseconds. - public long CounterPeriod { get; } - - #region IIrcFloodPreventer Members - - /// - public long GetSendDelay() - { - // Subtract however many counter periods have elapsed since last decrement of counter. - var currentTime = DateTime.Now.Ticks/ticksPerMillisecond; - var elapsedMilliseconds = currentTime - lastCounterDecrementTime; - var tempMessageCounter = Math.Max(0L, messageCounter - elapsedMilliseconds/CounterPeriod); - messageCounter = tempMessageCounter > int.MaxValue ? int.MaxValue : (int) tempMessageCounter; - - // Update time of last decrement of counter to theoretical time of decrement. - lastCounterDecrementTime = currentTime - elapsedMilliseconds%CounterPeriod; - - // Return time until next message can be sent. - return Math.Max((messageCounter - MaxMessageBurst)*CounterPeriod - elapsedMilliseconds, 0); - } - - /// - public void HandleMessageSent() - { - // Increment message count. - messageCounter++; - } - - #endregion - } -} \ No newline at end of file diff --git a/IrcDotNet/IrcTargetMask.cs b/IrcDotNet/IrcTargetMask.cs deleted file mode 100644 index d05b57c..0000000 --- a/IrcDotNet/IrcTargetMask.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using IrcDotNet.Properties; - -namespace IrcDotNet -{ - /// - /// Represents a mask of an IRC server name or host name, used for specifying the targets of a message. - /// - /// - public class IrcTargetMask : IIrcMessageTarget - { - /// - /// Initializes a new instance of the class with the specified target mask - /// identifier. - /// - /// - /// A wildcard expression for matching against server names or host names. - /// If the first character is '$', the mask is a server mask; if the first character is '#', the mask is a host - /// mask. - /// - /// is - /// The length of is too short. - /// - /// does not represent a known mask type. - /// - public IrcTargetMask(string targetMask) - { - if (targetMask == null) - throw new ArgumentNullException(nameof(targetMask)); - if (Resources.MessageTargetMaskTooShort.Length < 2) - throw new ArgumentException(Resources.MessageTargetMaskTooShort, nameof(targetMask)); - - switch (targetMask[0]) - { - case '$': - Type = IrcTargetMaskType.ServerMask; - break; - case '#': - Type = IrcTargetMaskType.HostMask; - break; - default: - throw new ArgumentException(string.Format( - Resources.MessageTargetMaskInvalidType, targetMask), nameof(targetMask)); - } - Mask = Mask?.Substring(1); - } - - /// - /// Initializes a new instance of the class with the specified type and mask. - /// - /// The type. - /// The mask. - public IrcTargetMask(IrcTargetMaskType type, string mask) - { - if (!Enum.IsDefined(typeof (IrcTargetMaskType), type)) - throw new ArgumentException(nameof(type)); - if (mask == null) - throw new ArgumentNullException(nameof(mask)); - - Type = type; - Mask = mask; - } - - /// - /// Gets the type of the target mask; either a server mask or channel mask. - /// - /// The type of the mask. - public IrcTargetMaskType Type { get; } - - /// - /// Gets a wildcard expression for matching against target names. - /// The property determines the type of the mask. - /// - /// The target mask. - public string Mask { get; } - - #region IIrcMessageTarget Members - - string IIrcMessageTarget.Name - { - get - { - char maskTypeChar; - if (Type == IrcTargetMaskType.ServerMask) - maskTypeChar = '$'; - else if (Type == IrcTargetMaskType.HostMask) - maskTypeChar = '#'; - else - throw new InvalidOperationException(); - return maskTypeChar + Mask; - } - } - - #endregion - - /// - /// Returns a string representation of this instance. - /// - /// A string that represents this instance. - public override string ToString() - { - return Mask; - } - } - - /// - /// Defines the types of a target mask. - /// - public enum IrcTargetMaskType - { - /// - /// A mask of a server name. - /// - ServerMask, - - /// - /// A mask of a host name. - /// - HostMask - } -} \ No newline at end of file diff --git a/IrcDotNet/IrcUser.cs b/IrcDotNet/IrcUser.cs deleted file mode 100644 index e4d1fba..0000000 --- a/IrcDotNet/IrcUser.cs +++ /dev/null @@ -1,415 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Linq; - -namespace IrcDotNet -{ - /// - /// Represents an IRC user that exists on a specific . - /// - /// - [DebuggerDisplay("{ToString(), nq}")] - public class IrcUser : INotifyPropertyChanged, IIrcMessageSource, IIrcMessageTarget - { - private string awayMessage; - - private IrcClient client; - - private int hopCount; - - private string hostName; - - private TimeSpan idleDuration; - - private bool isAway; - private bool isOnline; - - private bool isOperator; - - private string nickName; - - private string realName; - - private string serverInfo; - - private string serverName; - - private string userName; - - internal IrcUser(bool isOnline, string nickName, string userName, string realName) - { - this.nickName = nickName; - this.userName = userName; - this.realName = realName; - serverName = null; - serverInfo = null; - isOperator = false; - isAway = false; - awayMessage = null; - idleDuration = TimeSpan.Zero; - hopCount = 0; - } - - internal IrcUser() - { - } - - /// - /// Gets whether the user is currently connected to the IRC network. This value may not be always be - /// up-to-date. - /// - /// - /// if the user is currently online; if the user is - /// currently offline. - /// - public bool IsOnline - { - get { return isOnline; } - internal set - { - isOnline = value; - OnPropertyChanged(new PropertyChangedEventArgs("IsOnline")); - } - } - - /// - /// Gets the current nick name of the user. - /// - /// The nick name of the user. - public string NickName - { - get { return nickName; } - internal set - { - nickName = value; - OnNickNameChanged(new EventArgs()); - OnPropertyChanged(new PropertyChangedEventArgs("NickName")); - } - } - - /// - /// Gets the current user name of the user. This value never changes until the user reconnects. - /// - /// The user name of the user. - public string UserName - { - get { return userName; } - internal set - { - userName = value; - OnPropertyChanged(new PropertyChangedEventArgs("UserName")); - } - } - - /// - /// Gets the real name of the user. This value never changes until the user reconnects. - /// - /// The real name of the user. - public string RealName - { - get { return realName; } - internal set - { - realName = value; - OnPropertyChanged(new PropertyChangedEventArgs("RealName")); - } - } - - /// - /// Gets the host name of the user. - /// - /// The host name of the user. - public string HostName - { - get { return hostName; } - internal set - { - hostName = value; - OnPropertyChanged(new PropertyChangedEventArgs("HostName")); - } - } - - /// - /// Gets the name of the server to which the user is connected. - /// - /// The name of the server to which the user is connected. - public string ServerName - { - get { return serverName; } - internal set - { - serverName = value; - OnPropertyChanged(new PropertyChangedEventArgs("ServerName")); - } - } - - /// - /// Gets arbitrary information about the server to which the user is connected. - /// - /// Arbitrary information about the server. - public string ServerInfo - { - get { return serverInfo; } - internal set - { - serverInfo = value; - OnPropertyChanged(new PropertyChangedEventArgs("ServerInfo")); - } - } - - /// - /// Gets whether the user is a server operator. - /// - /// if the user is a server operator; , otherwise. - public bool IsOperator - { - get { return isOperator; } - internal set - { - isOperator = value; - OnPropertyChanged(new PropertyChangedEventArgs("IsOperator")); - } - } - - /// - /// Gets whether the user has been been seen as away. This value is always up-to-date for the local user; - /// though it is only updated for remote users when a private message is sent to them or a Who Is response - /// is received for the user. - /// - /// - /// if the user is currently away; , if the user is - /// currently here. - /// - public bool IsAway - { - get { return isAway; } - internal set - { - isAway = value; - OnIsAwayChanged(new EventArgs()); - OnPropertyChanged(new PropertyChangedEventArgs("IsAway")); - } - } - - /// - /// Gets the current away message received when the user was seen as away. - /// - /// The current away message of the user. - public string AwayMessage - { - get { return awayMessage; } - internal set - { - awayMessage = value; - OnPropertyChanged(new PropertyChangedEventArgs("AwayMessage")); - } - } - - /// - /// Gets the duration for which the user has been idle. This is set when a Who Is response is received. - /// - /// The duration for which the user has been idle. - public TimeSpan IdleDuration - { - get { return idleDuration; } - internal set - { - idleDuration = value; - OnPropertyChanged(new PropertyChangedEventArgs("IdleDuration")); - } - } - - /// - /// Gets the hop count of the user, which is the number of servers between the user and the server on which the - /// client is connected, within the network. - /// - /// The hop count of the user. - public int HopCount - { - get { return hopCount; } - internal set - { - hopCount = value; - OnPropertyChanged(new PropertyChangedEventArgs("HopCount")); - } - } - - /// - /// Gets the client on which the user exists. - /// - /// The client on which the user exists. - public IrcClient Client - { - get { return client; } - internal set - { - client = value; - OnPropertyChanged(new PropertyChangedEventArgs("Client")); - } - } - - #region IIrcMessageSource Members - - string IIrcMessageSource.Name - { - get { return NickName; } - } - - #endregion - - #region IIrcMessageTarget Members - - string IIrcMessageTarget.Name - { - get { return NickName; } - } - - #endregion - - /// - /// Occurs when a property value changes. - /// - public event PropertyChangedEventHandler PropertyChanged; - - /// - /// Occurs when the nick name of the user has changed. - /// - public event EventHandler NickNameChanged; - - /// - /// Occurs when the user has been seen as away or here. - /// - public event EventHandler IsAwayChanged; - - /// - /// Occurs when an invitation to join a channel has been received. - /// - /// - /// This event should only be raised for the local user (the instance of ). - /// - public event EventHandler InviteReceived; - - /// - /// Occurs when the user has quit the network. This may not always be sent. - /// - public event EventHandler Quit; - - /// - /// Sends a Who Is query to server for the user. - /// - public void WhoIs() - { - client.QueryWhoIs(nickName); - } - - /// - /// Sends a Who Was query to server for the user. - /// - /// - /// The maximum number of entries that the server should return. A negative number - /// specifies an unlimited number of entries. - /// - public void WhoWas(int entriesCount = -1) - { - client.QueryWhoWas(new[] {nickName}, entriesCount); - } - - /// - /// Gets a collection of all channel users that correspond to the user. - /// Each represents a channel of which the user is currently a member. - /// - /// - /// A collection of all object that correspond to the . - /// - public IEnumerable GetChannelUsers() - { - // Get each channel user corresponding to this user that is member of any channel. - foreach (var channel in client.Channels) - { - foreach (var channelUser in channel.Users) - { - if (channelUser.User == this) - yield return channelUser; - } - } - } - - internal void HandleInviteReceived(IrcUser inviter, IrcChannel channel) - { - OnInviteReceived(new IrcChannelInvitationEventArgs(channel, inviter)); - } - - internal void HandeQuit(string comment) - { - foreach (var cu in GetChannelUsers().ToArray()) - cu.Channel.HandleUserQuit(cu, comment); - OnQuit(new IrcCommentEventArgs(comment)); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnNickNameChanged(EventArgs e) - { - var handler = NickNameChanged; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnIsAwayChanged(EventArgs e) - { - var handler = IsAwayChanged; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnInviteReceived(IrcChannelInvitationEventArgs e) - { - var handler = InviteReceived; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnQuit(IrcCommentEventArgs e) - { - var handler = Quit; - if (handler != null) - handler(this, e); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) - { - var handler = PropertyChanged; - if (handler != null) - handler(this, e); - } - - /// - /// Returns a string representation of this instance. - /// - /// A string that represents this instance. - public override string ToString() - { - return nickName; - } - } -} \ No newline at end of file diff --git a/IrcDotNet/IrcUserCollection.cs b/IrcDotNet/IrcUserCollection.cs deleted file mode 100644 index f92c909..0000000 --- a/IrcDotNet/IrcUserCollection.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; - -namespace IrcDotNet -{ - /// - /// Represents a collection of objects. - /// - /// - /// - public class IrcUserCollection : ReadOnlyCollection - { - internal IrcUserCollection(IrcClient client, IList list) - : base(list) - { - Client = client; - } - - /// - /// Gets the client to which the collection of users belongs. - /// - /// The client to which the collection of users belongs. - public IrcClient Client { get; } - } -} \ No newline at end of file diff --git a/IrcDotNet/IrcUtilities.cs b/IrcDotNet/IrcUtilities.cs deleted file mode 100644 index 13bdbb5..0000000 --- a/IrcDotNet/IrcUtilities.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Collections.Generic; -using IrcDotNet.Properties; - -namespace IrcDotNet -{ - // Utilities for IRC. - internal static class IrcUtilities - { - // Updates collection of modes from specified mode string. - // Mode string is of form `( "+" | "-" ) ( mode character )+`. - public static void UpdateModes(this ICollection collection, string newModes, - IEnumerable newModeParameters = null, ICollection modesWithParameters = null, - Action handleModeParameter = null) - { - if (collection == null) - throw new ArgumentNullException("collection"); - if (newModes == null) - throw new ArgumentNullException("newModes"); - if (newModeParameters != null) - { - if (modesWithParameters == null) - throw new ArgumentNullException("modesWithParameters"); - if (handleModeParameter == null) - throw new ArgumentNullException("handleModeParameter"); - } - - // Reads list of mode changes, where each group of modes is prefixed by a '+' or '-', representing - // respectively setting or unsetting of the given modes. - bool? addMode = null; - var modeParametersEnumerator = newModeParameters == null ? null : newModeParameters.GetEnumerator(); - foreach (var mode in newModes) - { - if (mode == '+') - { - addMode = true; - } - else if (mode == '-') - { - addMode = false; - } - else if (addMode.HasValue) - { - if (newModeParameters != null && modesWithParameters.Contains(mode)) - { - if (!modeParametersEnumerator.MoveNext()) - throw new ArgumentException(Resources.MessageNotEnoughModeParameters, - "newModeParameters"); - handleModeParameter(addMode.Value, mode, modeParametersEnumerator.Current); - } - else - { - if (addMode.Value) - collection.Add(mode); - else - collection.Remove(mode); - } - } - } - } - } -} \ No newline at end of file diff --git a/IrcDotNet/MessageProcessorAttribute.cs b/IrcDotNet/MessageProcessorAttribute.cs deleted file mode 100644 index a9a9ff8..0000000 --- a/IrcDotNet/MessageProcessorAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace IrcDotNet -{ - // Indicates that method processes message for some protocol. - internal class MessageProcessorAttribute : Attribute - { - public MessageProcessorAttribute(string commandName) - { - CommandName = commandName; - } - - public string CommandName { get; private set; } - } -} \ No newline at end of file diff --git a/IrcDotNet/Properties/Resources.Designer.cs b/IrcDotNet/Properties/Resources.Designer.cs deleted file mode 100644 index 30b3876..0000000 --- a/IrcDotNet/Properties/Resources.Designer.cs +++ /dev/null @@ -1,348 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.1 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -using System.Reflection; - -namespace IrcDotNet.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { -#if NETSTANDARD1_5 - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("IrcDotNet.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly); -#else - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("IrcDotNet.Properties.Resources", typeof(Resources).Assembly); -#endif - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Cannot set user mode for '{0}'.. - /// - internal static string MessageCannotSetUserMode { - get { - return ResourceManager.GetString("MessageCannotSetUserMode", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The channel type '{0}' sent by the server is invalid.. - /// - internal static string MessageInvalidChannelType { - get { - return ResourceManager.GetString("MessageInvalidChannelType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The message command '{0}' is invalid.. - /// - internal static string MessageInvalidCommand { - get { - return ResourceManager.GetString("MessageInvalidCommand", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The command definition '{0}' is invalid.. - /// - internal static string MessageInvalidCommandDefinition { - get { - return ResourceManager.GetString("MessageInvalidCommandDefinition", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The command '{0}' was not recognised.. - /// - internal static string MessageInvalidMessageCommand { - get { - return ResourceManager.GetString("MessageInvalidMessageCommand", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The non-trailing parameter '{0}' is invalid.. - /// - internal static string MessageInvalidMiddleParameter { - get { - return ResourceManager.GetString("MessageInvalidMiddleParameter", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The specified nick name is invalid.. - /// - internal static string MessageInvalidNickName { - get { - return ResourceManager.GetString("MessageInvalidNickName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The specified password is invalid.. - /// - internal static string MessageInvalidPassword { - get { - return ResourceManager.GetString("MessageInvalidPassword", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The message prefix '{0}' is invalid.. - /// - internal static string MessageInvalidPrefix { - get { - return ResourceManager.GetString("MessageInvalidPrefix", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The quoted character '{0}' was not recognised.. - /// - internal static string MessageInvalidQuotedChar { - get { - return ResourceManager.GetString("MessageInvalidQuotedChar", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The specified real name is invalid.. - /// - internal static string MessageInvalidRealName { - get { - return ResourceManager.GetString("MessageInvalidRealName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The object provided for registration info is of an unknown type.. - /// - internal static string MessageInvalidRegistrationInfoObject { - get { - return ResourceManager.GetString("MessageInvalidRegistrationInfoObject", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The registration info for a service must have a valid nick name and description.. - /// - internal static string MessageInvalidServiceRegistrationInfo { - get { - return ResourceManager.GetString("MessageInvalidServiceRegistrationInfo", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The source '{0}' of the message was not recognised as either a server or user.. - /// - internal static string MessageInvalidSource { - get { - return ResourceManager.GetString("MessageInvalidSource", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The message tag '{0}' is invalid.. - /// - internal static string MessageInvalidTag { - get { - return ResourceManager.GetString("MessageInvalidTag", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A target name may not contain any ',' character.. - /// - internal static string MessageInvalidTargetName { - get { - return ResourceManager.GetString("MessageInvalidTargetName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The trailing parameter '{0}' is invalid.. - /// - internal static string MessageInvalidTrailingParameter { - get { - return ResourceManager.GetString("MessageInvalidTrailingParameter", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The URL scheme '{0}' is not valid.. - /// - internal static string MessageInvalidUrlScheme { - get { - return ResourceManager.GetString("MessageInvalidUrlScheme", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The specified user mode is invalid.. - /// - internal static string MessageInvalidUserMode { - get { - return ResourceManager.GetString("MessageInvalidUserMode", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The specified user name is invalid.. - /// - internal static string MessageInvalidUserName { - get { - return ResourceManager.GetString("MessageInvalidUserName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The registration info for a user must have a valid nick name and user name.. - /// - internal static string MessageInvalidUserRegistrationInfo { - get { - return ResourceManager.GetString("MessageInvalidUserRegistrationInfo", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The ISUPPORT message sent by the server contains an invalid PREFIX parameter.. - /// - internal static string MessageISupportPrefixInvalid { - get { - return ResourceManager.GetString("MessageISupportPrefixInvalid", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Not enough mode parameters were specified for the given modes.. - /// - internal static string MessageNotEnoughModeParameters { - get { - return ResourceManager.GetString("MessageNotEnoughModeParameters", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The host name '{0}' does not resolve to a valid IP address.. - /// - internal static string MessageNoValidAddress { - get { - return ResourceManager.GetString("MessageNoValidAddress", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The length of a raw message must not exceed {0} characters.. - /// - internal static string MessageRawMessageTooLong { - get { - return ResourceManager.GetString("MessageRawMessageTooLong", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The message source '{0}' is not a user.. - /// - internal static string MessageSourceNotUser { - get { - return ResourceManager.GetString("MessageSourceNotUser", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The type of the given target mask '{0}' is invalid.. - /// - internal static string MessageTargetMaskInvalidType { - get { - return ResourceManager.GetString("MessageTargetMaskInvalidType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The target mask must be contain at least two characters.. - /// - internal static string MessageTargetMaskTooShort { - get { - return ResourceManager.GetString("MessageTargetMaskTooShort", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No more than 3 mode parameters may be sent per message.. - /// - internal static string MessageTooManyModeParameters { - get { - return ResourceManager.GetString("MessageTooManyModeParameters", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No more than 15 command parameters may be specified.. - /// - internal static string MessageTooManyParams { - get { - return ResourceManager.GetString("MessageTooManyParams", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The value cannot be an empty string. - /// - internal static string MessageValueCannotBeEmptyString { - get { - return ResourceManager.GetString("MessageValueCannotBeEmptyString", resourceCulture); - } - } - } -} diff --git a/IrcDotNet/Properties/Resources.resx b/IrcDotNet/Properties/Resources.resx deleted file mode 100644 index 568106b..0000000 --- a/IrcDotNet/Properties/Resources.resx +++ /dev/null @@ -1,213 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Cannot set user mode for '{0}'. - - - The channel type '{0}' sent by the server is invalid. - - - The message command '{0}' is invalid. - - - The command '{0}' was not recognised. - - - The non-trailing parameter '{0}' is invalid. - - - The specified nick name is invalid. - - - The specified password is invalid. - - - The message prefix '{0}' is invalid. - - - The specified real name is invalid. - - - The source '{0}' of the message was not recognised as either a server or user. - - - A target name may not contain any ',' character. - - - The trailing parameter '{0}' is invalid. - - - The specified user mode is invalid. - - - The specified user name is invalid. - - - The ISUPPORT message sent by the server contains an invalid PREFIX parameter. - - - The length of a raw message must not exceed {0} characters. - - - Not enough mode parameters were specified for the given modes. - - - The message source '{0}' is not a user. - - - The type of the given target mask '{0}' is invalid. - - - The target mask must be contain at least two characters. - - - No more than 3 mode parameters may be sent per message. - - - No more than 15 command parameters may be specified. - - - The value cannot be an empty string - - - The command definition '{0}' is invalid. - - - The quoted character '{0}' was not recognised. - - - The message tag '{0}' is invalid. - - - The URL scheme '{0}' is not valid. - - - The object provided for registration info is of an unknown type. - - - The registration info for a user must have a valid nick name and user name. - - - The registration info for a service must have a valid nick name and description. - - - The host name '{0}' does not resolve to a valid IP address. - - \ No newline at end of file diff --git a/IrcDotNet/ReflectionUtilities.cs b/IrcDotNet/ReflectionUtilities.cs deleted file mode 100644 index 9e837e1..0000000 --- a/IrcDotNet/ReflectionUtilities.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; - -namespace IrcDotNet -{ - // Utilities for reflection of managed entities. - internal static class ReflectionUtilities - { - public static IEnumerable> GetAttributedMethods( - this object obj) - where TAttribute : Attribute - where TDelegate : class - { - // Find all methods in class that are marked by one or more instances of given attribute. -#if NETSTANDARD1_5 - var messageProcessorsMethods = obj.GetType().GetTypeInfo().GetMethods( - BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); -#else - var messageProcessorsMethods = obj.GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic - | BindingFlags.Public); -#endif - foreach (var methodInfo in messageProcessorsMethods) - { - var methodAttributes = (TAttribute[]) methodInfo.GetCustomAttributes( - typeof (TAttribute), true); - if (methodAttributes.Length > 0) - { -#if NETSTANDARD1_5 - var methodDelegate = - (TDelegate)(object)methodInfo.CreateDelegate(typeof(TDelegate), obj); -#else - var methodDelegate = - (TDelegate) (object) Delegate.CreateDelegate(typeof (TDelegate), obj, methodInfo); -#endif - - // Get each attribute applied to method. - foreach (var attribute in methodAttributes) - yield return Tuple.Create(attribute, methodDelegate); - } - } - } - } -} \ No newline at end of file diff --git a/IrcDotNet/SafeLineReader.cs b/IrcDotNet/SafeLineReader.cs deleted file mode 100644 index e3bc18f..0000000 --- a/IrcDotNet/SafeLineReader.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.IO; -using System.Text; - -namespace IrcDotNet -{ - // Reads lines from text sources safely; non-terminated lines are not returned. - internal class SafeLineReader - { - // Current incomplete line; - private string currentLine; - // Reads characters from text source. - - public SafeLineReader(TextReader textReader) - { - TextReader = textReader; - currentLine = string.Empty; - } - - public TextReader TextReader { get; } - - // Reads line from source, ensuring that line is not returned unless it terminates with line break. - public string ReadLine() - { - var lineBuilder = new StringBuilder(); - int nextChar; - - while (true) - { - // Check whether to stop reading characters. - nextChar = TextReader.Peek(); - if (nextChar == -1) - { - currentLine = lineBuilder.ToString(); - break; - } - if (nextChar == '\r' || nextChar == '\n') - { - TextReader.Read(); - if (TextReader.Peek() == '\n') - TextReader.Read(); - - var line = currentLine + lineBuilder; - currentLine = string.Empty; - return line; - } - - // Append next character to line. - lineBuilder.Append((char) TextReader.Read()); - } - - return null; - } - } -} \ No newline at end of file diff --git a/IrcDotNet/StandardIrcClient.cs b/IrcDotNet/StandardIrcClient.cs deleted file mode 100644 index f4bf8c8..0000000 --- a/IrcDotNet/StandardIrcClient.cs +++ /dev/null @@ -1,619 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Security.Cryptography.X509Certificates; -using System.Threading; -using IrcDotNet.Properties; -#if !SILVERLIGHT -using System.Net.Security; - -#endif - -namespace IrcDotNet -{ - /// - public class StandardIrcClient : IrcClient - { - // Minimum duration of time to wait between sending successive raw messages. - private const long minimumSendWaitTime = 50; - - // Size of buffer for data received by socket, in bytes. - private const int socketReceiveBufferSize = 0xFFFF; - private Stream dataStream; - private SafeLineReader dataStreamLineReader; - private StreamReader dataStreamReader; - private AutoResetEvent disconnectedEvent; - - // Queue of pending messages and their tokens to be sent when ready. - private readonly Queue> messageSendQueue; - private CircularBufferStream receiveStream; - private Timer sendTimer; - - // Network (TCP) I/O. - private Socket socket; - - public StandardIrcClient() - { -#if IPV4 - socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); -#else - socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp); -#endif - sendTimer = new Timer(WritePendingMessages, null, - Timeout.Infinite, Timeout.Infinite); - disconnectedEvent = new AutoResetEvent(false); - - messageSendQueue = new Queue>(); - } - - public override bool IsConnected - { - get - { - CheckDisposed(); - return socket != null && socket.Connected; - } - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (disposing) - { - if (socket != null) - { - socket.Dispose(); - socket = null; - - HandleClientDisconnected(); - } - if (receiveStream != null) - { - receiveStream.Dispose(); - receiveStream = null; - } - if (dataStream != null) - { - dataStream.Dispose(); - dataStream = null; - } - if (dataStreamReader != null) - { - dataStreamReader.Dispose(); - dataStreamReader = null; - } - if (sendTimer != null) - { - sendTimer.Dispose(); - sendTimer = null; - } - if (disconnectedEvent != null) - { - disconnectedEvent.Dispose(); - disconnectedEvent = null; - } - } - } - - protected override void WriteMessage(string line, object token) - { - // Add message line to send queue. - messageSendQueue.Enqueue(Tuple.Create(line + Environment.NewLine, token)); - } - - /// - /// - /// Connects to a server using the specified URL and user information. - /// - public void Connect(Uri url, IrcRegistrationInfo registrationInfo) - { - CheckDisposed(); - - if (registrationInfo == null) - throw new ArgumentNullException("registrationInfo"); - - // Check URL scheme and decide whether to use SSL. - bool useSsl; - if (url.Scheme == "irc") - useSsl = false; - else if (url.Scheme == "ircs") - useSsl = true; - else - throw new ArgumentException(string.Format(Resources.MessageInvalidUrlScheme, - url.Scheme), "url"); - - Connect(url.Host, url.Port == -1 ? DefaultPort : url.Port, useSsl, registrationInfo); - } - - /// - public void Connect(string hostName, bool useSsl, IrcRegistrationInfo registrationInfo) - { - CheckDisposed(); - - if (registrationInfo == null) - throw new ArgumentNullException("registrationInfo"); - - Connect(hostName, DefaultPort, useSsl, registrationInfo); - } - - /// - /// The name of the remote host. - /// The port number of the remote host. - public void Connect(string hostName, int port, bool useSsl, IrcRegistrationInfo registrationInfo) - { - CheckDisposed(); - - if (registrationInfo == null) - throw new ArgumentNullException("registrationInfo"); -#if NETSTANDARD1_5 - var dnsTask = Dns.GetHostAddressesAsync(hostName); - var addresses = dnsTask.Result; - - Connect(new IPEndPoint(addresses[0], port), useSsl, registrationInfo); -#else - Connect(new DnsEndPoint(hostName, port), useSsl, registrationInfo); -#endif - } - - /// - public void Connect(IPAddress address, bool useSsl, IrcRegistrationInfo registrationInfo) - { - CheckDisposed(); - - if (registrationInfo == null) - throw new ArgumentNullException("registrationInfo"); - - Connect(address, DefaultPort, useSsl, registrationInfo); - } - - /// - /// An IP addresses that designates the remote host. - /// The port number of the remote host. - public void Connect(IPAddress address, int port, bool useSsl, IrcRegistrationInfo registrationInfo) - { - CheckDisposed(); - - if (registrationInfo == null) - throw new ArgumentNullException("registrationInfo"); - - Connect(new IPEndPoint(address, port), useSsl, registrationInfo); - } - - /// - /// Connects asynchronously to the specified server. - /// - /// - /// The network endpoint (IP address and port) of the server to which to connect. - /// - /// - /// to connect to the server via SSL; , - /// otherwise - /// - /// - /// The information used for registering the client. - /// The type of the object may be either or - /// . - /// - /// - /// is . - /// - /// - /// does not specify valid registration - /// information. - /// - /// The current instance has already been disposed. - public void Connect(EndPoint remoteEndPoint, bool useSsl, IrcRegistrationInfo registrationInfo) - { - Connect(registrationInfo); - // Connect socket to remote host. - ConnectAsync(remoteEndPoint, Tuple.Create(useSsl, string.Empty, registrationInfo)); - - HandleClientConnecting(); - } - - public override void Quit(int timeout, string comment) - { - base.Quit(timeout, comment); - if (!disconnectedEvent.WaitOne(timeout)) - Disconnect(); - } - - protected override void ResetState() - { - base.ResetState(); - - // Reset network I/O objects. - if (receiveStream != null) - receiveStream.Dispose(); - if (dataStream != null) - dataStream.Dispose(); - if (dataStreamReader != null) - dataStreamReader = null; - } - - private void WritePendingMessages(object state) - { - try - { - // Send pending messages in queue until flood preventer indicates to stop. - long sendDelay = 0; - - while (messageSendQueue.Count > 0) - { - Debug.Assert(messageSendQueue.Count < 100); - // Check that flood preventer currently permits sending of messages. - if (FloodPreventer != null) - { - sendDelay = FloodPreventer.GetSendDelay(); - if (sendDelay > 0) - break; - } - - // Send next message in queue. - var message = messageSendQueue.Dequeue(); - var line = message.Item1; - var token = message.Item2; - var lineBuffer = TextEncoding.GetBytes(line); - SendAsync(lineBuffer, token); - - // Tell flood preventer mechanism that message has just been sent. - if (FloodPreventer != null) - FloodPreventer.HandleMessageSent(); - } - - // Make timer fire when next message in send queue should be written. - sendTimer.Change((int)Math.Max(sendDelay, minimumSendWaitTime), Timeout.Infinite); - } - catch (SocketException exSocket) - { - HandleSocketError(exSocket); - } - catch (ObjectDisposedException) - { - // Ignore. - } -#if !DEBUG - catch (Exception ex) - { - OnError(new IrcErrorEventArgs(ex)); - } -#endif - finally - { - } - } - - public override void Disconnect() - { - base.Disconnect(); - - DisconnectAsync(); - } - - private void SendAsync(byte[] buffer, object token = null) - { - SendAsync(buffer, 0, buffer.Length, token); - } - - private void SendAsync(byte[] buffer, int offset, int count, object token = null) - { - // Write data from buffer to socket asynchronously. - var sendEventArgs = new SocketAsyncEventArgs(); - sendEventArgs.SetBuffer(buffer, offset, count); - sendEventArgs.UserToken = token; - sendEventArgs.Completed += SendCompleted; - - if (!socket.SendAsync(sendEventArgs)) - SendCompleted(socket, sendEventArgs); - } - - private void SendCompleted(object sender, SocketAsyncEventArgs e) - { - try - { - if (e.SocketError != SocketError.Success) - { - HandleSocketError(e.SocketError); - return; - } - - // Handle sent IRC message. - Debug.Assert(e.UserToken != null); - var messageSentEventArgs = (IrcRawMessageEventArgs) e.UserToken; - OnRawMessageSent(messageSentEventArgs); - -#if DEBUG - DebugUtilities.WriteIrcRawLine(this, "<<< " + messageSentEventArgs.RawContent); -#endif - } - catch (ObjectDisposedException) - { - // Ignore. - } -#if !DEBUG - catch (Exception ex) - { - OnError(new IrcErrorEventArgs(ex)); - } -#endif - finally - { - e.Dispose(); - } - } - - private void ReceiveAsync() - { - // Read data received from socket to buffer asynchronously. - var receiveEventArgs = new SocketAsyncEventArgs(); - Debug.Assert(receiveStream.Buffer.Length - (int) receiveStream.WritePosition > 0); - receiveEventArgs.SetBuffer(receiveStream.Buffer, (int) receiveStream.WritePosition, - receiveStream.Buffer.Length - (int) receiveStream.WritePosition); - receiveEventArgs.Completed += ReceiveCompleted; - - if (!socket.ReceiveAsync(receiveEventArgs)) - ReceiveCompleted(socket, receiveEventArgs); - } - - private void ReceiveCompleted(object sender, SocketAsyncEventArgs e) - { - try - { - if (e.SocketError != SocketError.Success) - { - HandleSocketError(e.SocketError); - return; - } - - // Check if remote host has closed connection. - if (e.BytesTransferred == 0) - { - Disconnect(); - return; - } - - // Indicate that block of data has been read into receive buffer. - receiveStream.WritePosition += e.BytesTransferred; - dataStreamReader.DiscardBufferedData(); - - // Read each terminated line of characters from data stream. - while (true) - { - // Read next line from data stream. - var line = dataStreamLineReader.ReadLine(); - if (line == null) - break; - if (line.Length == 0) - continue; - - ParseMessage(line); - } - - // Continue reading data from socket. - ReceiveAsync(); - } - catch (SocketException exSocket) - { - HandleSocketError(exSocket); - } - catch (ObjectDisposedException) - { - // Ignore. - } -#if !DEBUG - catch (Exception ex) - { - OnError(new IrcErrorEventArgs(ex)); - } -#endif - finally - { - e.Dispose(); - } - } - - private void ConnectAsync(EndPoint remoteEndPoint, object token = null) - { - // Connect socket to remote endpoint asynchronously. - var connectEventArgs = new SocketAsyncEventArgs(); - connectEventArgs.RemoteEndPoint = remoteEndPoint; - connectEventArgs.UserToken = token; - connectEventArgs.Completed += ConnectCompleted; - - if (!socket.ConnectAsync(connectEventArgs)) - ConnectCompleted(socket, connectEventArgs); - } - - private void ConnectCompleted(object sender, SocketAsyncEventArgs e) - { - try - { - if (e.SocketError != SocketError.Success) - { - HandleSocketError(e.SocketError); - return; - } - - Debug.Assert(e.UserToken != null); - var token = (Tuple) e.UserToken; - - // Create stream for received data. Use SSL stream on top of network stream, if specified. - receiveStream = new CircularBufferStream(socketReceiveBufferSize); -#if SILVERLIGHT - this.dataStream = this.receiveStream; -#else - dataStream = GetDataStream(token.Item1, token.Item2); -#endif - dataStreamReader = new StreamReader(dataStream, TextEncoding); - dataStreamLineReader = new SafeLineReader(dataStreamReader); - - // Start sending and receiving data to/from server. - sendTimer.Change(0, Timeout.Infinite); - ReceiveAsync(); - - HandleClientConnected(token.Item3); - } - catch (SocketException exSocket) - { - HandleSocketError(exSocket); - } - catch (ObjectDisposedException) - { - // Ignore. - } -#if !DEBUG - catch (Exception ex) - { - OnConnectFailed(new IrcErrorEventArgs(ex)); - } -#endif - finally - { - e.Dispose(); - } - } - - private void DisconnectAsync() - { - // Connect socket to remote endpoint asynchronously. - var disconnectEventArgs = new SocketAsyncEventArgs(); - disconnectEventArgs.Completed += DisconnectCompleted; - -#if SILVERLIGHT || NETSTANDARD1_5 - this.socket.Shutdown(SocketShutdown.Both); - disconnectEventArgs.SocketError = SocketError.Success; - DisconnectCompleted(socket, disconnectEventArgs); -#else - disconnectEventArgs.DisconnectReuseSocket = true; - if (!socket.DisconnectAsync(disconnectEventArgs)) - DisconnectCompleted(socket, disconnectEventArgs); -#endif - } - - private void DisconnectCompleted(object sender, SocketAsyncEventArgs e) - { - try - { - if (e.SocketError != SocketError.Success) - { - HandleSocketError(e.SocketError); - return; - } - - HandleClientDisconnected(); - } - catch (SocketException exSocket) - { - HandleSocketError(exSocket); - } - catch (ObjectDisposedException) - { - // Ignore. - } -#if !DEBUG - catch (Exception ex) - { - OnError(new IrcErrorEventArgs(ex)); - } -#endif - finally - { - e.Dispose(); - } - } - - protected override void HandleClientConnected(IrcRegistrationInfo regInfo) - { - DebugUtilities.WriteEvent(string.Format("Connected to server at '{0}'.", - ((IPEndPoint) socket.RemoteEndPoint).Address)); - - base.HandleClientConnected(regInfo); - } - - protected override void HandleClientDisconnected() - { - // Ensure that client has not already handled disconnection. - if (disconnectedEvent.WaitOne(0)) - return; - - DebugUtilities.WriteEvent("Disconnected from server."); - - // Stop sending messages immediately. - sendTimer.Change(Timeout.Infinite, Timeout.Infinite); - - // Set that client has disconnected. - disconnectedEvent.Set(); - - base.HandleClientDisconnected(); - } - - private void HandleSocketError(SocketError error) - { - HandleSocketError(new SocketException((int) error)); - } - - private void HandleSocketError(SocketException exception) - { - switch (exception.SocketErrorCode) - { - case SocketError.NotConnected: - case SocketError.ConnectionReset: - HandleClientDisconnected(); - return; - default: - OnError(new IrcErrorEventArgs(exception)); - return; - } - } - - /// - /// Returns a string representation of this instance. - /// - /// A string that represents this instance. - public override string ToString() - { - if (!IsDisposed && IsConnected) - return string.Format("{0}@{1}", LocalUser.UserName, - ServerName ?? socket.RemoteEndPoint.ToString()); - return "(Not connected)"; - } - -#if !SILVERLIGHT - - private Stream GetDataStream(bool useSsl, string targetHost) - { - if (useSsl) - { - // Create SSL stream over network stream to use for data transmission. - var sslStream = new SslStream(receiveStream, true, - SslUserCertificateValidationCallback); - -#if NETSTANDARD1_5 - var authTask = sslStream.AuthenticateAsClientAsync(targetHost); - authTask.Wait(); -#else - sslStream.AuthenticateAsClient(targetHost); -#endif - Debug.Assert(sslStream.IsAuthenticated); - return sslStream; - } - // Use network stream directly for data transmission. - return receiveStream; - } - - private bool SslUserCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, - SslPolicyErrors sslPolicyErrors) - { - // Raise an event to decide whether the certificate is valid. - var eventArgs = new IrcValidateSslCertificateEventArgs(certificate, chain, sslPolicyErrors); - eventArgs.IsValid = true; - OnValidateSslCertificate(eventArgs); - return eventArgs.IsValid; - } - -#endif - } -} \ No newline at end of file diff --git a/IrcDotNet/TextUtilities.cs b/IrcDotNet/TextUtilities.cs deleted file mode 100644 index e3fd559..0000000 --- a/IrcDotNet/TextUtilities.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Text.RegularExpressions; -using IrcDotNet.Properties; - -namespace IrcDotNet -{ - // Utilities for text manipulation. - internal static class TextUtilities - { - // Gets single matched value of group, if match succeeded. - public static string GetValue(this Group match) - { - if (!match.Success) - return null; - return match.Value; - } - - // Enquotes specified string given escape character and mapping for quotation characters. - public static string Quote(this string value, char escapeChar, IDictionary quotedChars) - { - var textBuilder = new StringBuilder(value.Length*2); - for (var i = 0; i < value.Length; i++) - { - var curQuotedChar = escapeChar; - if (quotedChars.TryGetValue(value[i], out curQuotedChar) || value[i] == escapeChar) - { - textBuilder.Append(escapeChar); - textBuilder.Append(curQuotedChar); - } - else - { - textBuilder.Append(value[i]); - } - } - - return textBuilder.ToString(); - } - - // Dequotes specified string given escape character and mapping for quotation characters. - public static string Dequote(this string value, char escapeChar, IDictionary dequotedChars) - { - var textBuilder = new StringBuilder(value.Length); - for (var i = 0; i < value.Length; i++) - { - if (value[i] == escapeChar) - { - i++; - var curDequotedChar = escapeChar; - if (dequotedChars.TryGetValue(value[i], out curDequotedChar) || value[i] == escapeChar) - { - textBuilder.Append(curDequotedChar); - } - else - { - throw new InvalidOperationException( - Resources.MessageInvalidQuotedChar); - } - } - else - { - textBuilder.Append(value[i]); - } - } - - return textBuilder.ToString(); - } - - // Splits specified string into pair of strings at position of first occurrence of separator. - public static Tuple SplitIntoPair(this string value, string separator) - { - var index = value.IndexOf(separator); - if (index < 0) - return Tuple.Create(value, (string) null); - return Tuple.Create(value.Substring(0, index), value.Substring(index + separator.Length)); - } - - // Change character encoding of specified string. - internal static string ChangeEncoding(this string value, Encoding currentEncoding, Encoding newEncoding) - { - if (newEncoding == null) - return value; - var buffer = currentEncoding.GetBytes(value); - return newEncoding.GetString(buffer, 0, buffer.Length); - } - } -} \ No newline at end of file diff --git a/IrcDotNet/TwitchIrcClient.cs b/IrcDotNet/TwitchIrcClient.cs deleted file mode 100644 index 3429266..0000000 --- a/IrcDotNet/TwitchIrcClient.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; - -namespace IrcDotNet -{ - public class TwitchIrcClient : StandardIrcClient - { - protected override void WriteMessage(string message, object token = null) - { - base.WriteMessage(message, - token ?? new IrcRawMessageEventArgs(new IrcMessage(this, null, null, null), message)); - } - - protected override void OnChannelModeChanged(IrcChannel channel, IrcUser source, string newModes, - IEnumerable newModeParameters) - { - // Twitch doesn't actually send JOIN messages. This means we need to add users - // to the channel when changing their mode if we haven't already. - foreach (var username in newModeParameters) - { - var user = GetUserFromNickName(username); - if (channel.GetChannelUser(user) == null) - channel.HandleUserJoined(new IrcChannelUser(user)); - } - } - - protected internal override void ProcessMessageReplyWelcome(IrcMessage message) - { - Debug.Assert(message.Parameters[0] != null); - - Debug.Assert(message.Parameters[1] != null); - WelcomeMessage = message.Parameters[1]; - - // Twitch does not send a normal welcome message, so this code is actually incorrect. - isRegistered = true; - OnRegistered(new EventArgs()); - } - - protected internal override void ProcessMessageReplyMyInfo(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - // Twitch doesn't seem to give us this information. - Debug.Assert(message.Parameters[1] == "-"); - OnClientInfoReceived(new EventArgs()); - } - - protected internal override void ProcessMessageReplyMotdStart(IrcMessage message) - { - Debug.Assert(message.Parameters[0] == localUser.NickName); - - // Looks like the motd doesn't start on the start message for twitch. - Debug.Assert(message.Parameters[1] == "-"); - motdBuilder.Clear(); - } - } -} \ No newline at end of file From f1cea23e2d7d122e57106cba77222d95df1c22e4 Mon Sep 17 00:00:00 2001 From: Evan Dixon Date: Sun, 23 Sep 2018 18:35:46 -0500 Subject: [PATCH 3/7] Cleanup and logging --- Ditto/Ditto.csproj | 1 + Ditto/Program.cs | 38 ++++++++++---------------------------- 2 files changed, 11 insertions(+), 28 deletions(-) diff --git a/Ditto/Ditto.csproj b/Ditto/Ditto.csproj index b1bb472..6542027 100644 --- a/Ditto/Ditto.csproj +++ b/Ditto/Ditto.csproj @@ -5,6 +5,7 @@ netcoreapp2.0 win-x64;linux-x64;debian.9-x64 Debug;Release;Debug-IPv4 + 7.3 diff --git a/Ditto/Program.cs b/Ditto/Program.cs index f83100b..e9e7bc4 100644 --- a/Ditto/Program.cs +++ b/Ditto/Program.cs @@ -16,37 +16,21 @@ public class Program private static List Pairs; private static bool _listen; - private static int _exceptionCount; - public static void Main(string[] args) + public static async Task Main(string[] args) { - _exceptionCount = 0; - _listen = true; - while (_listen && _exceptionCount < 10) - { - try - { - MainAsync(args).Wait(); - } - catch (Exception ex) - { - File.WriteAllText(Path.Combine(Directory.GetCurrentDirectory(), $"Exception-{DateTime.Now.ToString("YYYY-MM-DD-hh-mm-ss")}.txt"), ex.ToString()); - _exceptionCount += 1; - } - Task.Delay(30000).Wait(); - } - } - - public static async Task MainAsync(string[] args) - { + Console.WriteLine("Starting..."); Pairs = new List(); var discordFilenames = Directory.GetFiles(".", "*.discord.json"); + Console.WriteLine("Found " + discordFilenames.Length.ToString() + " Discord settings"); foreach (var discordFilename in discordFilenames) { + Console.WriteLine(discordFilename); var ircFilename = discordFilename.Replace(".discord.json", ".irc.json"); if (File.Exists(ircFilename)) { + Console.Write(ircFilename); var discordInfo = JsonConvert.DeserializeObject(File.ReadAllText(discordFilename)); var ircInfo = JsonConvert.DeserializeObject(File.ReadAllText(ircFilename)); @@ -56,16 +40,14 @@ public static async Task MainAsync(string[] args) pair.EnableConsoleLogging = false; } Console.WriteLine("Connecting..."); - pair.Connect().Wait(); + await pair.Connect(); Console.WriteLine("Ready."); Pairs.Add(pair); } - } - - if (_exceptionCount > 0) - { - await Task.Delay(30000); - await Pairs[0].SendDiscordMessage("Ditto just recovered from a fatal exception."); + else + { + Console.Write("Did not find IRC settings. Not creating pair."); + } } // Listen for mannual commands From 6f4f3a9fe81ac46472540c70ff8d0fc677780956 Mon Sep 17 00:00:00 2001 From: Evan Dixon Date: Sun, 23 Sep 2018 19:04:41 -0500 Subject: [PATCH 4/7] Readd custom IrcDotNet for IP6 support --- Ditto.sln | 8 + Ditto/Ditto.csproj | 5 +- IrcDotNet/CircularBufferStream.cs | 141 ++ IrcDotNet/ClassDiagram.cd | 255 ++ IrcDotNet/CollectionUtilities.cs | 13 + IrcDotNet/Collections/CollectionsUtilities.cs | 84 + IrcDotNet/Collections/ReadOnlyDictionary.cs | 298 +++ IrcDotNet/Collections/ReadOnlySet.cs | 327 +++ IrcDotNet/Ctcp/CtcpClient.cs | 599 +++++ IrcDotNet/Ctcp/CtcpClientMessageProcessing.cs | 117 + IrcDotNet/Ctcp/CtcpClientMessageSending.cs | 76 + IrcDotNet/Ctcp/CtcpEventArgs.cs | 222 ++ IrcDotNet/DebugUtilities.cs | 24 + IrcDotNet/IIrcFloodPreventer.cs | 19 + IrcDotNet/IIrcMessageReceiveHandler.cs | 26 + IrcDotNet/IIrcMessageReceiver.cs | 20 + IrcDotNet/IIrcMessageSendHandler.cs | 24 + IrcDotNet/IIrcMessageSource.cs | 14 + IrcDotNet/IIrcMessageTarget.cs | 14 + IrcDotNet/IrcChannel.cs | 633 +++++ IrcDotNet/IrcChannelCollection.cs | 73 + IrcDotNet/IrcChannelInfo.cs | 36 + IrcDotNet/IrcChannelUser.cs | 154 ++ IrcDotNet/IrcChannelUserCollection.cs | 35 + IrcDotNet/IrcClient.cs | 2064 +++++++++++++++++ IrcDotNet/IrcClientMessageProcessing.cs | 1214 ++++++++++ IrcDotNet/IrcClientMessageSending.cs | 590 +++++ IrcDotNet/IrcDotNet.csproj | 28 + IrcDotNet/IrcEventArgs.cs | 670 ++++++ IrcDotNet/IrcLocalUser.cs | 505 ++++ IrcDotNet/IrcNetworkInfo.cs | 53 + IrcDotNet/IrcRegistrationInfo.cs | 76 + IrcDotNet/IrcServer.cs | 38 + IrcDotNet/IrcServerInfo.cs | 37 + IrcDotNet/IrcServerStatisticalEntry.cs | 84 + IrcDotNet/IrcStandardFloodPreventer.cs | 86 + IrcDotNet/IrcTargetMask.cs | 121 + IrcDotNet/IrcUser.cs | 415 ++++ IrcDotNet/IrcUserCollection.cs | 25 + IrcDotNet/IrcUtilities.cs | 62 + IrcDotNet/MessageProcessorAttribute.cs | 15 + IrcDotNet/Properties/Resources.Designer.cs | 348 +++ IrcDotNet/Properties/Resources.resx | 213 ++ IrcDotNet/ReflectionUtilities.cs | 44 + IrcDotNet/SafeLineReader.cs | 54 + IrcDotNet/StandardIrcClient.cs | 619 +++++ IrcDotNet/TextUtilities.cs | 88 + IrcDotNet/TwitchIrcClient.cs | 58 + 48 files changed, 10723 insertions(+), 1 deletion(-) create mode 100644 IrcDotNet/CircularBufferStream.cs create mode 100644 IrcDotNet/ClassDiagram.cd create mode 100644 IrcDotNet/CollectionUtilities.cs create mode 100644 IrcDotNet/Collections/CollectionsUtilities.cs create mode 100644 IrcDotNet/Collections/ReadOnlyDictionary.cs create mode 100644 IrcDotNet/Collections/ReadOnlySet.cs create mode 100644 IrcDotNet/Ctcp/CtcpClient.cs create mode 100644 IrcDotNet/Ctcp/CtcpClientMessageProcessing.cs create mode 100644 IrcDotNet/Ctcp/CtcpClientMessageSending.cs create mode 100644 IrcDotNet/Ctcp/CtcpEventArgs.cs create mode 100644 IrcDotNet/DebugUtilities.cs create mode 100644 IrcDotNet/IIrcFloodPreventer.cs create mode 100644 IrcDotNet/IIrcMessageReceiveHandler.cs create mode 100644 IrcDotNet/IIrcMessageReceiver.cs create mode 100644 IrcDotNet/IIrcMessageSendHandler.cs create mode 100644 IrcDotNet/IIrcMessageSource.cs create mode 100644 IrcDotNet/IIrcMessageTarget.cs create mode 100644 IrcDotNet/IrcChannel.cs create mode 100644 IrcDotNet/IrcChannelCollection.cs create mode 100644 IrcDotNet/IrcChannelInfo.cs create mode 100644 IrcDotNet/IrcChannelUser.cs create mode 100644 IrcDotNet/IrcChannelUserCollection.cs create mode 100644 IrcDotNet/IrcClient.cs create mode 100644 IrcDotNet/IrcClientMessageProcessing.cs create mode 100644 IrcDotNet/IrcClientMessageSending.cs create mode 100644 IrcDotNet/IrcDotNet.csproj create mode 100644 IrcDotNet/IrcEventArgs.cs create mode 100644 IrcDotNet/IrcLocalUser.cs create mode 100644 IrcDotNet/IrcNetworkInfo.cs create mode 100644 IrcDotNet/IrcRegistrationInfo.cs create mode 100644 IrcDotNet/IrcServer.cs create mode 100644 IrcDotNet/IrcServerInfo.cs create mode 100644 IrcDotNet/IrcServerStatisticalEntry.cs create mode 100644 IrcDotNet/IrcStandardFloodPreventer.cs create mode 100644 IrcDotNet/IrcTargetMask.cs create mode 100644 IrcDotNet/IrcUser.cs create mode 100644 IrcDotNet/IrcUserCollection.cs create mode 100644 IrcDotNet/IrcUtilities.cs create mode 100644 IrcDotNet/MessageProcessorAttribute.cs create mode 100644 IrcDotNet/Properties/Resources.Designer.cs create mode 100644 IrcDotNet/Properties/Resources.resx create mode 100644 IrcDotNet/ReflectionUtilities.cs create mode 100644 IrcDotNet/SafeLineReader.cs create mode 100644 IrcDotNet/StandardIrcClient.cs create mode 100644 IrcDotNet/TextUtilities.cs create mode 100644 IrcDotNet/TwitchIrcClient.cs diff --git a/Ditto.sln b/Ditto.sln index 8e779f4..68efe10 100644 --- a/Ditto.sln +++ b/Ditto.sln @@ -10,6 +10,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.MD = README.MD EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IrcDotNet", "IrcDotNet\IrcDotNet.csproj", "{2E1632CE-3048-4EE7-B7A8-318F3007990D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -23,6 +25,12 @@ Global {B4D93EE6-97DA-43DD-84CB-DCDC3E843BC8}.Debug-IPv4|Any CPU.Build.0 = Debug-IPv4|Any CPU {B4D93EE6-97DA-43DD-84CB-DCDC3E843BC8}.Release|Any CPU.ActiveCfg = Release|Any CPU {B4D93EE6-97DA-43DD-84CB-DCDC3E843BC8}.Release|Any CPU.Build.0 = Release|Any CPU + {2E1632CE-3048-4EE7-B7A8-318F3007990D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E1632CE-3048-4EE7-B7A8-318F3007990D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E1632CE-3048-4EE7-B7A8-318F3007990D}.Debug-IPv4|Any CPU.ActiveCfg = Debug-IPv4|Any CPU + {2E1632CE-3048-4EE7-B7A8-318F3007990D}.Debug-IPv4|Any CPU.Build.0 = Debug-IPv4|Any CPU + {2E1632CE-3048-4EE7-B7A8-318F3007990D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E1632CE-3048-4EE7-B7A8-318F3007990D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Ditto/Ditto.csproj b/Ditto/Ditto.csproj index 6542027..6953031 100644 --- a/Ditto/Ditto.csproj +++ b/Ditto/Ditto.csproj @@ -10,8 +10,11 @@ - + + + + \ No newline at end of file diff --git a/IrcDotNet/CircularBufferStream.cs b/IrcDotNet/CircularBufferStream.cs new file mode 100644 index 0000000..efb1003 --- /dev/null +++ b/IrcDotNet/CircularBufferStream.cs @@ -0,0 +1,141 @@ +using System; +using System.IO; + +namespace IrcDotNet +{ + // Allows reading and writing to circular buffer as stream. + // Note: Stream is non-blocking and non-thread-safe. + internal class CircularBufferStream : Stream + { + // Buffer for storing data. + private long readPosition; + + // Current index within buffer for writing and reading. + private long writePosition; + + public CircularBufferStream(int length) + : this(new byte[length]) + { + } + + public CircularBufferStream(byte[] buffer) + { + Buffer = buffer; + writePosition = 0; + readPosition = 0; + } + + public byte[] Buffer { get; } + + public long WritePosition + { + get { return writePosition; } + set { writePosition = value%Buffer.Length; } + } + + public override long Position + { + get { return readPosition; } + set { readPosition = value%Buffer.Length; } + } + + public override long Length + { + get + { + var length = writePosition - readPosition; + return length < 0 ? Buffer.Length + length : length; + } + } + + public override bool CanSeek + { + get { return true; } + } + + public override bool CanWrite + { + get { return true; } + } + + public override bool CanRead + { + get { return true; } + } + + public override void Flush() + { + // + } + + public override long Seek(long offset, SeekOrigin origin) + { + switch (origin) + { + case SeekOrigin.Begin: + readPosition = offset%Buffer.Length; + break; + case SeekOrigin.End: + readPosition = (Buffer.Length - offset)%Buffer.Length; + break; + case SeekOrigin.Current: + readPosition = (readPosition + offset)%Buffer.Length; + break; + default: + throw new NotSupportedException(); + } + + return readPosition; + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + // Write block of bytes from given buffer into circular buffer, wrapping around when necessary. + int writeCount; + while ((writeCount = Math.Min(count, (int) (Buffer.Length - writePosition))) > 0) + { + var oldWritePosition = writePosition; + var newWritePosition = (writePosition + writeCount)%Buffer.Length; + if (newWritePosition > readPosition && oldWritePosition < readPosition) + { +#if !SILVERLIGHT && !NETSTANDARD1_5 + throw new InternalBufferOverflowException("The CircularBuffer was overflowed!"); +#else + throw new IOException("The CircularBuffer was overflowed!"); +#endif + } + System.Buffer.BlockCopy(buffer, offset, Buffer, (int) writePosition, writeCount); + writePosition = newWritePosition; + + offset += writeCount; + count -= writeCount; //writeCount <= count => now is count >=0 + } + } + + public override int Read(byte[] buffer, int offset, int count) + { + // Read block of bytes from circular buffer, wrapping around when necessary. + var totalReadCount = 0; + int readCount; + count = Math.Min(buffer.Length - offset, count); + while ((readCount = Math.Min(count, (int) Length)) > 0) + { + if (readCount > Buffer.Length - readPosition) + { + readCount = (int) (Buffer.Length - readPosition); + } + System.Buffer.BlockCopy(Buffer, (int) readPosition, buffer, offset, readCount); + readPosition = (readPosition + readCount)%Buffer.Length; + offset += readCount; + count = Math.Min(buffer.Length - offset, count); + totalReadCount += readCount; + } + return totalReadCount; + } + } +} \ No newline at end of file diff --git a/IrcDotNet/ClassDiagram.cd b/IrcDotNet/ClassDiagram.cd new file mode 100644 index 0000000..8db9736 --- /dev/null +++ b/IrcDotNet/ClassDiagram.cd @@ -0,0 +1,255 @@ + + + + + + + + + KowQMyEAQIALCFAEoBQIGI0BwUEFIkAEIQmCBSCBAwQ= + IrcChannel.cs + + + + + + + + + + AAEBAAAgAAACIAAEIBAIAAAA4AAAAEAAAIAAAAAACQg= + IrcChannelUser.cs + + + + + + + + + + AAAAAAAAAAAAAAgAAAAAAAAAAAEAAAAAAAAABAAAAAQ= + IrcChannelCollection.cs + + + + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQ= + IrcUserCollection.cs + + + + + + + + + gAiAAIIAAICIJASEKCRUEIBIgQCIwEMUAQAABAEIgQQ= + IrcUser.cs + + + + + + + + + + Pkfc0nZ3LnOvU/rVuc/IW/PVv+/6M1496f9ev05r2/s= + IrcClient.cs + + + + + + + + + + IAAAAAAgAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + IrcChannelUserCollection.cs + + + + + + + + + CAgBggEAREADCGwAIDUMAIAEQUJAgUAMAAEAAAiBDwA= + IrcLocalUser.cs + + + + + + + gAAAAIAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAA= + IrcServer.cs + + + + + + + + + + AAAAAAAAAAAAAAAEAAAAEAAAAAABAAAAAQCAAAAAgAA= + IrcTargetMask.cs + + + + + + + + + + AABAFAAAAAAAAAAAAAAAAAAAAAAAAAAAgIAAAACCSAA= + IrcStandardFloodPreventer.cs + + + + + + + AAAAAAAAAAAgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + IrcRegistrationInfo.cs + + + + + + AAAAAAAAAAAQAAAAACAAAAAAAAAAAAAAAQAAAAAAAAA= + IrcRegistrationInfo.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAECAAAAAAAAAAA= + IrcRegistrationInfo.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAA= + IrcUtilities.cs + + + + + + AAAAAAAAIgAAAAAAAAAAAAAAgBAAAAAAAAAAAAACAAA= + TextUtilities.cs + + + + + + AAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAA= + ReflectionUtilities.cs + + + + + + + + + AQUICWQMCwGRQUCFYdACARBIACkcAAAQQgkAiACSmEA= + Ctcp\CtcpClient.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAQAAAAAAAIAAAAAACAAAAA= + IrcChannelInfo.cs + + + + + + AAAAAAAAAAAAAAQAAAAAAgEIAAAACAYBAAAAAAAAAAA= + IrcNetworkInfo.cs + + + + + + gAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAEAAAAA= + IrcServerInfo.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAA= + IrcServerStatisticalEntry.cs + + + + + + CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAA= + IIrcMessageReceiveHandler.cs + + + + + + AAAAAAAAQAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + IIrcMessageReceiver.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAA= + IIrcMessageSource.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAA= + IIrcMessageTarget.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAA= + IIrcMessageSendHandler.cs + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAACAA= + IIrcFloodPreventer.cs + + + + + + AAAAAAAAAAAAAAAAQAAABAAAAAAAAAAAAAAAAAAAAAA= + IrcTargetMask.cs + + + + + + AAAAAAABAAACAAEAAIQABAAAAAAAAAgAQAAAAACQAEA= + IrcServerStatisticalEntry.cs + + + + \ No newline at end of file diff --git a/IrcDotNet/CollectionUtilities.cs b/IrcDotNet/CollectionUtilities.cs new file mode 100644 index 0000000..0d08286 --- /dev/null +++ b/IrcDotNet/CollectionUtilities.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Linq; + +namespace IrcDotNet +{ + internal static class CollectionUtilities + { + public static IDictionary Invert(this IDictionary dictionary) + { + return dictionary.ToDictionary(pair => pair.Value, pair => pair.Key); + } + } +} \ No newline at end of file diff --git a/IrcDotNet/Collections/CollectionsUtilities.cs b/IrcDotNet/Collections/CollectionsUtilities.cs new file mode 100644 index 0000000..9778b74 --- /dev/null +++ b/IrcDotNet/Collections/CollectionsUtilities.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace IrcDotNet.Collections +{ + /// + /// Contains common utilities for functionality relating to collections. + /// + public static class CollectionsUtilities + { + /// + /// Sets the value for the specified key in a dictionary. + /// If the given key already exists, overwrite its value; otherwise, add a new key/value pair. + /// + /// The type of keys in the dictionary. + /// The type of values in the dictionary.. + /// The dictionary in which to set the value. + /// The object to use as the key of the element to add/update. + /// The object to use as the value of the element to add/update. + public static void Set(this IDictionary dictionary, TKey key, TValue value) + { + if (dictionary == null) + throw new ArgumentNullException("collection"); + + if (dictionary.ContainsKey(key)) + dictionary[key] = value; + else + dictionary.Add(key, value); + } + + /// + /// Adds the specified items to the collection. + /// + /// The type of the items in the collection. + /// The collection to which to add the items. + /// A collection of items to add to . + public static void AddRange(this ICollection collection, IEnumerable range) + { + if (collection == null) + throw new ArgumentNullException("collection"); + if (range == null) + throw new ArgumentNullException("range"); + + foreach (var item in range) + collection.Add(item); + } + + /// + /// Removes the specified items from the collection. + /// + /// The type of the items in the collection. + /// The collection fom which to remove the items. + /// A collection of items to remove from . + public static void RemoveRange(this ICollection collection, IEnumerable range) + { + if (collection == null) + throw new ArgumentNullException("collection"); + if (range == null) + throw new ArgumentNullException("range"); + + foreach (var item in range) + collection.Remove(item); + } + + /// + /// Performs the specified action on each item in the collection. + /// + /// The type of the items in the collection. + /// The collection on whose items to perform the action. + /// The action to perform on each item of the collection. + public static void ForEach(this IEnumerable source, Action action) + { + if (source == null) + throw new ArgumentNullException("source"); + if (action == null) + throw new ArgumentNullException("action"); + + foreach (var item in source) + action(item); + } + } +} diff --git a/IrcDotNet/Collections/ReadOnlyDictionary.cs b/IrcDotNet/Collections/ReadOnlyDictionary.cs new file mode 100644 index 0000000..b45ee27 --- /dev/null +++ b/IrcDotNet/Collections/ReadOnlyDictionary.cs @@ -0,0 +1,298 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; + +#if !NETSTANDARD1_5 +using System.Runtime.Serialization; +#endif + +namespace IrcDotNet.Collections +{ + /// + /// Represents a read-only collection of keys and values. + /// + /// The type of the keys in the dictionary. + /// The type of the values in the dictionary. +#if !SILVERLIGHT && !NETSTANDARD1_5 + [Serializable()] +#endif + [DebuggerDisplay("Count = {Count}")] + public class ReadOnlyDictionary : IDictionary, ICollection>, + IEnumerable>, IDictionary, ICollection, IEnumerable +#if !SILVERLIGHT && !NETSTANDARD1_5 + , ISerializable, IDeserializationCallback +#endif + { + // Dictionary to expose as read-only. + private IDictionary dictionary; + + /// + /// Initializes a new instance of the class. + /// + /// The dictionary to wrap. + /// is . + public ReadOnlyDictionary(IDictionary dictionary) + { + if (dictionary == null) + throw new ArgumentNullException("dictionary"); + + this.dictionary = dictionary; + } + +#region IDictionary Members + + /// + /// Gets a collection containing the keys in the dictionary. + /// + /// A collection containing the keys in the dictionary. + public ICollection Keys + { + get { return this.dictionary.Keys; } + } + + /// + /// Gets a collection containing the values in the dictionary. + /// + /// A collection containing the values in the dictionary. + public ICollection Values + { + get { return this.dictionary.Values; } + } + + /// + /// Gets or sets the element with the specified key. + /// + /// The element with the specified key. + /// This operation is not supported on a read-only dictionary. + /// + public TValue this[TKey key] + { + get + { + return this.dictionary[key]; + } + set + { + throw new NotSupportedException(); + } + } + + /// + /// Determines whether the dictionary contains the specified key. + /// + /// The key to locate in the dictionary. + /// if the dictionary contains an element with the specified key; + /// , otherwise. + /// is . + public bool ContainsKey(TKey key) + { + if (key == null) + throw new ArgumentNullException("key"); + + return this.dictionary.ContainsKey(key); + } + + /// + /// Gets the value associated with the specified key. + /// + /// The key of the value to get. + /// When this method returns, contains the value associated with the specified key, if the + /// key is found; otherwise, the default value for the type of the value parameter. This parameter is passed + /// uninitialized. + /// if the dictionary contains an element with the specified key; + /// , otherwise. + /// is . + public bool TryGetValue(TKey key, out TValue value) + { + if (key == null) + throw new ArgumentNullException("key"); + + return this.dictionary.TryGetValue(key, out value); + } + + void IDictionary.Add(TKey key, TValue value) + { + throw new NotSupportedException(); + } + + bool IDictionary.Remove(TKey key) + { + throw new NotSupportedException(); + } + +#endregion + +#region ICollection> Members + + /// + /// Gets the number of key/value pairs contained in the dictionary. + /// + /// The number of key/value pairs contained in the dictionary. + public int Count + { + get { return this.dictionary.Count; } + } + + bool ICollection>.IsReadOnly + { + get { return true; } + } + + void ICollection>.Add(KeyValuePair item) + { + throw new NotSupportedException(); + } + + bool ICollection>.Remove(KeyValuePair item) + { + throw new NotSupportedException(); + } + + void ICollection>.Clear() + { + throw new NotSupportedException(); + } + + bool ICollection>.Contains(KeyValuePair item) + { + return this.dictionary.Contains(item); + } + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + this.dictionary.CopyTo(array, arrayIndex); + } + +#endregion + +#region IEnumerable> Members + + /// + /// Returns an enumerator that iterates through the dictionary. + /// + /// An enumerator for the dictionary. + public IEnumerator> GetEnumerator() + { + return ((IEnumerable>)this.dictionary).GetEnumerator(); + } + +#endregion + +#region IDictionary Members + + ICollection IDictionary.Keys + { + get { return ((IDictionary)this.dictionary).Keys; } + } + + ICollection IDictionary.Values + { + get { return ((IDictionary)this.dictionary).Values; } + } + + bool IDictionary.IsFixedSize + { + get { return ((IDictionary)this.dictionary).IsFixedSize; } + } + + bool IDictionary.IsReadOnly + { + get { return true; } + } + + object IDictionary.this[object key] + { + get + { + return ((IDictionary)this.dictionary)[key]; + } + set + { + throw new NotSupportedException(); + } + } + + void IDictionary.Add(object key, object value) + { + throw new NotSupportedException(); + } + + void IDictionary.Remove(object key) + { + throw new NotSupportedException(); + } + + void IDictionary.Clear() + { + throw new NotSupportedException(); + } + + bool IDictionary.Contains(object key) + { + return ((IDictionary)this.dictionary).Contains(key); + } + + IDictionaryEnumerator IDictionary.GetEnumerator() + { + return ((IDictionary)this.dictionary).GetEnumerator(); + } + +#endregion + +#region ICollection Members + + void ICollection.CopyTo(Array array, int index) + { + ((ICollection)this.dictionary).CopyTo(array, index); + } + + int ICollection.Count + { + get { return ((ICollection)this.dictionary).Count; } + } + + bool ICollection.IsSynchronized + { + get { return ((ICollection)this.dictionary).IsSynchronized; } + } + + object ICollection.SyncRoot + { + get { return ((ICollection)this.dictionary).SyncRoot; } + } + +#endregion + +#region IEnumerable Members + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)this.dictionary).GetEnumerator(); + } + +#endregion + +#if !SILVERLIGHT && !NETSTANDARD1_5 + +#region ISerializable Members + + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + ((ISerializable)this.dictionary).GetObjectData(info, context); + } + +#endregion + +#region IDeserializationCallback Members + + void IDeserializationCallback.OnDeserialization(object sender) + { + ((IDeserializationCallback)this.dictionary).OnDeserialization(sender); + } + +#endregion + +#endif + } +} diff --git a/IrcDotNet/Collections/ReadOnlySet.cs b/IrcDotNet/Collections/ReadOnlySet.cs new file mode 100644 index 0000000..ae812ff --- /dev/null +++ b/IrcDotNet/Collections/ReadOnlySet.cs @@ -0,0 +1,327 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +#if !NETSTANDARD1_5 +using System.Runtime.Serialization; +#endif + +namespace IrcDotNet.Collections +{ + /// + /// Represents a read-only set of values. + /// + /// The type of elements in the set. +#if !SILVERLIGHT && !NETSTANDARD1_5 + [Serializable()] +#endif + [DebuggerDisplay("Count = {Count}")] + public class ReadOnlySet : ISet, ICollection, IEnumerable, ICollection, IEnumerable +#if !SILVERLIGHT && !NETSTANDARD1_5 + , ISerializable, IDeserializationCallback +#endif + { + // Set to expose as read-only. + private ISet set; + + private object syncRoot = new object(); + + /// + /// Initializes a new instance of the class. + /// + /// The set to wrap. + /// is . + public ReadOnlySet(ISet set) + { + if (set == null) + throw new ArgumentNullException("set"); + + this.set = set; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + + bool first = true; + foreach (T t in this) + { + if (!first) + sb.Append(' '); + + first = false; + sb.Append(t.ToString()); + } + + return sb.ToString(); + } + +#region ISet Members + + bool ISet.Add(T item) + { + throw new NotSupportedException(); + } + + void ISet.ExceptWith(IEnumerable other) + { + throw new NotSupportedException(); + } + + void ISet.SymmetricExceptWith(IEnumerable other) + { + throw new NotSupportedException(); + } + + void ISet.IntersectWith(IEnumerable other) + { + throw new NotSupportedException(); + } + + void ISet.UnionWith(IEnumerable other) + { + throw new NotSupportedException(); + } + + /// + /// Determines whether the set is a proper subset of the specified collection. + /// + /// The collection to compare to the current set. + /// + /// if the set is a proper subset of ; + /// , otherwise. + /// + /// is . + public bool IsProperSubsetOf(IEnumerable other) + { + if (other == null) + throw new ArgumentNullException("other"); + + return this.set.IsProperSubsetOf(other); + } + + /// + /// Determines whether the set is a proper superset of the specified collection. + /// + /// The collection to compare to the current set. + /// + /// if the set is a proper superset of ; + /// , otherwise. + /// + /// is . + public bool IsProperSupersetOf(IEnumerable other) + { + if (other == null) + throw new ArgumentNullException("other"); + + return this.set.IsProperSupersetOf(other); + } + + /// + /// Determines whether the set is a subset of the specified collection. + /// + /// The collection to compare to the current set. + /// + /// if the set is a subset of ; + /// , otherwise. + /// + /// is . + public bool IsSubsetOf(IEnumerable other) + { + if (other == null) + throw new ArgumentNullException("other"); + + return this.set.IsSubsetOf(other); + } + + /// + /// Determines whether the set is a superset of the specified collection. + /// + /// The collection to compare to the current set. + /// + /// if the set is a superset of ; + /// , otherwise. + /// + /// is . + public bool IsSupersetOf(IEnumerable other) + { + if (other == null) + throw new ArgumentNullException("other"); + + return this.set.IsSupersetOf(other); + } + + /// + /// Determines whether the set and the specified collection share common elements. + /// + /// The collection to compare to the current set. + /// + /// if the set and share at least one common element; + /// , otherwise. + /// + /// is . + public bool Overlaps(IEnumerable other) + { + if (other == null) + throw new ArgumentNullException("other"); + + return this.set.Overlaps(other); + } + + /// + /// Determines whether the set and the specified collection contain the same elements. + /// + /// The collection to compare to the current set. + /// + /// if the set and are equal; + /// , otherwise. + /// + /// is . + public bool SetEquals(IEnumerable other) + { + if (other == null) + throw new ArgumentNullException("other"); + + return this.set.SetEquals(other); + } + +#endregion + +#region ICollection Members + + /// + /// Gets the number of elements that are contained in the set. + /// + /// The number of elements that are contained in the set. + public int Count + { + get { return this.set.Count; } + } + + bool ICollection.IsReadOnly + { + get { return true; } + } + + void ICollection.Add(T item) + { + throw new NotSupportedException(); + } + + bool ICollection.Remove(T item) + { + throw new NotSupportedException(); + } + + void ICollection.Clear() + { + throw new NotSupportedException(); + } + + /// + /// Determines whether the set contains the specified element. + /// + /// The element to locate in the set. + /// if the set contains the specified element; + /// , otherwise. + /// is . + public bool Contains(T item) + { + if (item == null) + throw new ArgumentNullException("item"); + + return this.set.Contains(item); + } + + /// + public void CopyTo(T[] array) + { + CopyTo(array, 0); + } + + /// + /// Copies the elements of the set to an array. + /// + /// The one-dimensional array that is the destination of the elements copied from the + /// set. The array must have zero-based indexing. + /// The zero-based index in at which copying begins. + /// is . + /// is less than 0. + /// is greater than the length of the + /// destination array. + public void CopyTo(T[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException("item"); + + this.set.CopyTo(array, arrayIndex); + } + +#endregion + +#region IEnumerable Members + + /// + /// Returns an enumerator that iterates through the set. + /// + /// An enumerator for the set. + public IEnumerator GetEnumerator() + { + return ((IEnumerable)this.set).GetEnumerator(); + } + +#endregion + +#region ICollection Members + + void ICollection.CopyTo(Array array, int index) + { + this.set.CopyTo((T[])array, index); + } + + bool ICollection.IsSynchronized + { + get { return true; } + } + + object ICollection.SyncRoot + { + get { return this.syncRoot; } + } + +#endregion + +#region IEnumerable Members + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)this.set).GetEnumerator(); + } + +#endregion + +#if !SILVERLIGHT && !NETSTANDARD1_5 + +#region ISerializable Members + + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + ((ISerializable)this.set).GetObjectData(info, context); + } + +#endregion + +#region IDeserializationCallback Members + + void IDeserializationCallback.OnDeserialization(object sender) + { + ((IDeserializationCallback)this.set).OnDeserialization(sender); + } + +#endregion + +#endif + } +} diff --git a/IrcDotNet/Ctcp/CtcpClient.cs b/IrcDotNet/Ctcp/CtcpClient.cs new file mode 100644 index 0000000..67f813a --- /dev/null +++ b/IrcDotNet/Ctcp/CtcpClient.cs @@ -0,0 +1,599 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using IrcDotNet.Collections; +using IrcDotNet.Properties; + +namespace IrcDotNet.Ctcp +{ + /// + /// Represents a client that communicates with a server using CTCP (Client to Client Protocol), operating over an + /// IRC connection. + /// Do not inherit this class unless the protocol itself is being extended. + /// + /// + /// All collection objects must be locked on the object for thread-safety. + /// They can however be used safely without locking within event handlers. + /// + /// + /// + [DebuggerDisplay("{ToString(), nq}")] + public partial class CtcpClient + { + // Message indicating that no error occurred. + private const string messageNoError = "no error"; + + // Tag used for checking whether no error occurred for remote user. + private const string noErrorTag = "NO_ERROR"; + + // Character that marks start and end of tagged data. + private const char taggedDataDelimeterChar = '\x001'; + + // Information for low-level quoting of messages. + private const char lowLevelQuotingEscapeChar = '\x10'; + + // Information for CTCP-quoting of messages. + private const char ctcpQuotingEscapeChar = '\x5C'; + + private static readonly IDictionary lowLevelQuotedChars = new Dictionary + { + {'\0', '0'}, + {'\n', 'n'}, + {'\r', 'r'} + }; + + private static readonly IDictionary lowLevelDequotedChars = lowLevelQuotedChars.Invert(); + + private static readonly IDictionary ctcpQuotedChars = new Dictionary + { + {taggedDataDelimeterChar, 'a'} + }; + + private static readonly IDictionary ctcpDequotedChars = ctcpQuotedChars.Invert(); + + // IRC client for communication. + + // Dictionary of message processor routines, keyed by their command names. + private readonly Dictionary messageProcessors; + + /// + /// Initializes a new instance of the class. + /// + /// The IRC client by which the CTCP client should communicate. + public CtcpClient(IrcClient ircClient) + { + if (ircClient == null) + throw new ArgumentNullException("ircClient"); + + IrcClient = ircClient; + messageProcessors = new Dictionary( + StringComparer.OrdinalIgnoreCase); + + InitializeMessageProcessors(); + + IrcClient.Connected += ircClient_Connected; + IrcClient.Disconnected += ircClient_Disconnected; + } + + /// + /// Gets or sets information about the client version. + /// + /// Information about the client version. + public string ClientVersion { get; set; } + + /// + /// Gets or sets the IRC client by which the CTCP client should communicate. + /// + /// The IRC client. + public IrcClient IrcClient { get; } + + /// + /// Occurs when an action has been sent to a user. + /// + public event EventHandler ActionSent; + + /// + /// Occurs when an action has been received from a user. + /// + public event EventHandler ActionReceived; + + /// + /// Occurs when a response to a date/time request has been received from a user. + /// + public event EventHandler TimeResponseReceived; + + /// + /// Occurs when a response to a version request has been received from a user. + /// + public event EventHandler VersionResponseReceived; + + /// + /// Occurs when an error message has been received from a user. + /// + public event EventHandler ErrorMessageReceived; + + /// + /// Occurs when a ping response has been received from a user. + /// + public event EventHandler PingResponseReceived; + + /// + /// Occurs when a raw message has been sent to a user. + /// + public event EventHandler RawMessageSent; + + /// + /// Occurs when a raw message has been received from a user. + /// + public event EventHandler RawMessageReceived; + + /// + /// Occurs when the client encounters an error during execution. + /// + public event EventHandler Error; + + /// + /// + /// Sends an action message to the specified list of users. + /// + /// The user to which to send the request. + public void SendAction(IIrcMessageTarget user, string text) + { + SendMessageAction(new[] {user}, text); + } + + /// + /// Sends an action message to the specified list of users. + /// + /// A list of users to which to send the request. + /// The text of the message. + public void SendAction(IList users, string text) + { + SendMessageAction(users, text); + } + + /// + /// + /// Gets the local date/time of the specified user. + /// + /// The user to which to send the request. + public void GetTime(IIrcMessageTarget user) + { + GetTime(new[] {user}); + } + + /// + /// Gets the local date/time of the specified list of users. + /// + /// A list of users to which to send the request. + public void GetTime(IList users) + { + SendMessageTime(users, null, false); + } + + /// + /// + /// Gets the client version of the specified user. + /// + /// The user to which to send the request. + public void GetVersion(IIrcMessageTarget user) + { + GetVersion(new[] {user}); + } + + /// + /// Gets the client version of the specified list of users. + /// + /// A list of users to which to send the request. + public void GetVersion(IList users) + { + SendMessageVersion(users, null, false); + } + + /// + /// + /// Asks the specified user whether an error just occurred. + /// + /// The user to which to send the request. + public void CheckErrorOccurred(IIrcMessageTarget user) + { + CheckErrorOccurred(new[] {user}); + } + + /// + /// Asks the specified list of users whether an error just occurred. + /// + /// A list of users to which to send the request. + public void CheckErrorOccurred(IList users) + { + SendMessageErrMsg(users, noErrorTag, false); + } + + /// + /// + /// Pings the specified user. + /// + /// The user to which to send the request. + public void Ping(IIrcMessageTarget user) + { + Ping(new[] {user}); + } + + /// + /// Pings the specified list of users. + /// + /// A list of users to which to send the request. + public void Ping(IList users) + { + SendMessagePing(users, DateTime.Now.Ticks.ToString(), false); + } + + private void ircClient_Connected(object sender, EventArgs e) + { + if (IrcClient.LocalUser != null) + { + IrcClient.LocalUser.PreviewMessageReceived += ircClient_LocalUser_PreviewMessageReceived; + IrcClient.LocalUser.PreviewNoticeReceived += ircClient_LocalUser_PreviewNoticeReceived; + } + } + + private void ircClient_Disconnected(object sender, EventArgs e) + { + if (IrcClient.LocalUser != null) + { + IrcClient.LocalUser.PreviewMessageReceived -= ircClient_LocalUser_PreviewMessageReceived; + IrcClient.LocalUser.PreviewNoticeReceived -= ircClient_LocalUser_PreviewNoticeReceived; + } + } + + private void ircClient_LocalUser_PreviewMessageReceived(object sender, IrcPreviewMessageEventArgs e) + { + ReadMessage(e, false); + } + + private void ircClient_LocalUser_PreviewNoticeReceived(object sender, IrcPreviewMessageEventArgs e) + { + ReadMessage(e, true); + } + + private void InitializeMessageProcessors() + { + // Find each method defined as processor for CTCP message. + this.GetAttributedMethods().ForEach(item => + { + var attribute = item.Item1; + var methodDelegate = item.Item2; + + messageProcessors.Add(attribute.CommandName, methodDelegate); + }); + } + + private void ReadMessage(IrcPreviewMessageEventArgs previewMessageEventArgs, bool isNotice) + { + if (!(previewMessageEventArgs.Source is IrcUser)) + return; + + // Check if message represents tagged data. + if (previewMessageEventArgs.Text.First() == taggedDataDelimeterChar && + previewMessageEventArgs.Text.Last() == taggedDataDelimeterChar) + { + if (previewMessageEventArgs.Source is IrcUser) + { + var message = new CtcpMessage(); + message.Source = (IrcUser) previewMessageEventArgs.Source; + message.Targets = previewMessageEventArgs.Targets; + message.IsResponse = isNotice; + + // Parse tagged data into message. + var dequotedText = LowLevelDequote(CtcpDequote(previewMessageEventArgs.Text.Substring( + 1, previewMessageEventArgs.Text.Length - 2))); + var firstSpaceIndex = dequotedText.IndexOf(' '); + if (firstSpaceIndex == -1) + { + message.Tag = dequotedText; + message.Data = null; + } + else + { + message.Tag = dequotedText.Substring(0, firstSpaceIndex); + message.Data = dequotedText.Substring(firstSpaceIndex + 1).TrimStart(':'); + } + + ReadMessage(message); + previewMessageEventArgs.Handled = true; + } + } + } + + private void ReadMessage(CtcpMessage message) + { + OnRawMessageReceived(new CtcpRawMessageEventArgs(message)); + + // Try to find corresponding message processor for command of given message. + MessageProcessor messageProcessor; + if (messageProcessors.TryGetValue(message.Tag, out messageProcessor)) + { + try + { + messageProcessor(message); + } +#if !DEBUG + catch (Exception ex) + { + OnError(new IrcErrorEventArgs(ex)); + } +#endif + finally + { + } + } + else + { + // Command is unknown. + DebugUtilities.WriteEvent("Unknown CTCP message tag '{0}'.", message.Tag); + } + } + + /// + /// The tag of the message. + /// The data contained by the message. + /// + /// if the message is a response to another message; + /// , otherwise. + /// + protected void WriteMessage(IList targets, string tag, string data = null, + bool isResponse = false) + { + WriteMessage(targets, new CtcpMessage(IrcClient.LocalUser, targets, tag, data, isResponse)); + } + + /// + /// The message to write. + /// + /// contains more than 15 many parameters. + /// + /// + /// The value of of + /// is invalid. + /// + protected void WriteMessage(IList targets, CtcpMessage message) + { + if (message.Tag == null) + throw new ArgumentException(Resources.MessageInvalidTag, "message"); + + var tag = message.Tag.ToUpper(); + var taggedData = message.Data == null ? tag : tag + " :" + message.Data; + WriteMessage(targets, taggedData, message.IsResponse); + OnRawMessageSent(new CtcpRawMessageEventArgs(message)); + } + + /// + /// Writes the specified message to a target. + /// + /// A list of the targets to which to write the message. + /// The tagged data to write. + /// + /// if the message is a response to another message; + /// , otherwise. + /// + private void WriteMessage(IList targets, string taggedData, bool isResponse) + { + Debug.Assert(taggedData != null); + var text = taggedDataDelimeterChar + LowLevelQuote(CtcpQuote(taggedData)) + taggedDataDelimeterChar; + + if (isResponse) + IrcClient.LocalUser.SendNotice(targets, text); + else + IrcClient.LocalUser.SendMessage(targets, text); + } + + private string LowLevelQuote(string value) + { + return value.Quote(lowLevelQuotingEscapeChar, lowLevelQuotedChars); + } + + private string LowLevelDequote(string value) + { + return value.Dequote(lowLevelQuotingEscapeChar, lowLevelDequotedChars); + } + + private string CtcpQuote(string value) + { + return value.Quote(ctcpQuotingEscapeChar, ctcpQuotedChars); + } + + private string CtcpDequote(string value) + { + return value.Dequote(ctcpQuotingEscapeChar, ctcpDequotedChars); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnActionSent(CtcpMessageEventArgs e) + { + var handler = ActionSent; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnActionReceived(CtcpMessageEventArgs e) + { + var handler = ActionReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// + /// The instance containing the event data. + /// + protected virtual void OnTimeResponseReceived(CtcpTimeResponseReceivedEventArgs e) + { + var handler = TimeResponseReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// + /// The instance containing the event data. + /// + protected virtual void OnVersionResponseReceived(CtcpVersionResponseReceivedEventArgs e) + { + var handler = VersionResponseReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// + /// The instance containing the event + /// data. + /// + protected virtual void OnErrorMessageResponseReceived(CtcpErrorMessageReceivedEventArgs e) + { + var handler = ErrorMessageReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// + /// The instance containing the event data. + /// + protected virtual void OnPingResponseReceived(CtcpPingResponseReceivedEventArgs e) + { + var handler = PingResponseReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnRawMessageSent(CtcpRawMessageEventArgs e) + { + var handler = RawMessageSent; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnRawMessageReceived(CtcpRawMessageEventArgs e) + { + var handler = RawMessageReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnError(IrcErrorEventArgs e) + { + var handler = Error; + if (handler != null) + handler(this, e); + } + + /// + /// Returns a string representation of this instance. + /// + /// A string that represents this instance. + public override string ToString() + { + return string.Format("CTCP / {0}", IrcClient); + } + + /// + /// Represents a method that processes objects. + /// + /// The message to be processed. + protected delegate void MessageProcessor(CtcpMessage message); + + /// + /// Represents a raw CTCP message that is sent/received by . + /// + /// + [DebuggerDisplay("{ToString(), nq}")] + public struct CtcpMessage + { + /// + /// The user that sent the message. + /// + public IrcUser Source; + + /// + /// A list of users to which to send the message. + /// + public IList Targets; + + /// + /// The tag of the message, that specifies the kind of data it contains or the type of the request. + /// + public string Tag; + + /// + /// The data contained by the message. + /// + public string Data; + + /// + /// if this message is a response to another message; , + /// otherwise. + /// + public bool IsResponse; + + /// + /// Initializes a new instance of the structure. + /// + /// The source of the message. + /// A list of the targets of the message. + /// The tag of the message. + /// The data contained by the message, or for no data. + /// + /// if the message is a response to another message; + /// , otherwise. + /// + public CtcpMessage(IrcUser source, IList targets, string tag, string data, + bool isResponse) + { + Source = source; + Targets = targets; + Tag = tag; + Data = data; + IsResponse = isResponse; + } + + /// + /// Returns a string representation of this instance. + /// + /// A string that represents this instance. + public override string ToString() + { + return string.Format("{0} {1}", Tag, Data); + } + } + } +} \ No newline at end of file diff --git a/IrcDotNet/Ctcp/CtcpClientMessageProcessing.cs b/IrcDotNet/Ctcp/CtcpClientMessageProcessing.cs new file mode 100644 index 0000000..ed9ce81 --- /dev/null +++ b/IrcDotNet/Ctcp/CtcpClientMessageProcessing.cs @@ -0,0 +1,117 @@ +using System; +using System.Diagnostics; + +namespace IrcDotNet.Ctcp +{ + // Defines all message processors for the client. + partial class CtcpClient + { + /// + /// Process ACTION messages received from a user. + /// + /// The message received from the user. + [MessageProcessor("action")] + protected internal void ProcessMessageAction(CtcpMessage message) + { + Debug.Assert(message.Data != null); + + if (!message.IsResponse) + { + var text = message.Data; + + OnActionReceived(new CtcpMessageEventArgs(message.Source, message.Targets, text)); + } + } + + /// + /// Process TIME messages received from a user. + /// + /// The message received from the user. + [MessageProcessor("time")] + protected internal void ProcessMessageTime(CtcpMessage message) + { + if (message.IsResponse) + { + var dateTime = message.Data; + + OnTimeResponseReceived(new CtcpTimeResponseReceivedEventArgs(message.Source, dateTime)); + } + else + { + var localDateTime = DateTimeOffset.Now.ToString("o"); + + SendMessageTime(new[] {message.Source}, localDateTime, true); + } + } + + /// + /// Process VERSION messages received from a user. + /// + /// The message received from the user. + [MessageProcessor("version")] + protected internal void ProcessMessageVersion(CtcpMessage message) + { + if (message.IsResponse) + { + var versionInfo = message.Data; + + OnVersionResponseReceived(new CtcpVersionResponseReceivedEventArgs(message.Source, versionInfo)); + } + else + { + if (ClientVersion != null) + { + SendMessageVersion(new[] {message.Source}, ClientVersion, true); + } + } + } + + /// + /// Process ERRMSG messages received from a user. + /// + /// The message received from the user. + [MessageProcessor("errmsg")] + protected internal void ProcessMessageErrMsg(CtcpMessage message) + { + Debug.Assert(message.Data != null); + + if (message.IsResponse) + { + // Get failed query and error message from data. + var parts = message.Data.SplitIntoPair(" :"); + var failedQuery = parts.Item1; + var errorMessage = parts.Item2; + + OnErrorMessageResponseReceived(new CtcpErrorMessageReceivedEventArgs(message.Source, + failedQuery, errorMessage)); + } + else + { + SendMessageErrMsg(new[] {message.Source}, message.Data + " :" + messageNoError, true); + } + } + + /// + /// Process PING messages received from a user. + /// + /// The message received from the user. + [MessageProcessor("ping")] + protected internal void ProcessMessagePing(CtcpMessage message) + { + Debug.Assert(message.Data != null); + + if (message.IsResponse) + { + // Calculate time elapsed since the ping request was sent. + var sendTime = new DateTime(long.Parse(message.Data)); + var pingTime = DateTime.Now - sendTime; + + OnPingResponseReceived(new CtcpPingResponseReceivedEventArgs(message.Source, pingTime)); + } + else + { + SendMessagePing(new[] {message.Source}, message.Data, true); + } + } + } +} \ No newline at end of file diff --git a/IrcDotNet/Ctcp/CtcpClientMessageSending.cs b/IrcDotNet/Ctcp/CtcpClientMessageSending.cs new file mode 100644 index 0000000..0736736 --- /dev/null +++ b/IrcDotNet/Ctcp/CtcpClientMessageSending.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; + +namespace IrcDotNet.Ctcp +{ + // Defines all message senders for the client. + partial class CtcpClient + { + /// + /// Sends an action message to the specified target. + /// + /// A list of the targets of the message. + /// The message text. + protected void SendMessageAction(IList targets, string text) + { + WriteMessage(targets, "action", text); + + OnActionSent(new CtcpMessageEventArgs(IrcClient.LocalUser, targets, text)); + } + + /// + /// Sends a request for the local date/time to the specified target. + /// + /// A list of the targets of the message. + /// The information to send. + /// + /// if the message is a response; , + /// otherwise. + /// + protected void SendMessageTime(IList targets, string info, bool isResponse) + { + WriteMessage(targets, "time", info, isResponse); + } + + /// + /// Sends a request or response for information about the version of the client. + /// + /// A list of the targets of the message. + /// The information to send. + /// + /// if the message is a response; , + /// otherwise. + /// + protected void SendMessageVersion(IList targets, string info, bool isResponse) + { + WriteMessage(targets, "version", info, isResponse); + } + + /// + /// Sends a request for confirming that no error has occurred. + /// + /// A list of the targets of the message. + /// A tag that can be used for tracking the response. + /// + /// if the message is a response; , + /// otherwise. + /// + protected void SendMessageErrMsg(IList targets, string tag, bool isResponse) + { + WriteMessage(targets, "errmsg", tag, isResponse); + } + + /// + /// Sends a ping request or response to the specified target. + /// + /// A list of the targets of the message. + /// The information to send. + /// + /// if the message is a response; , + /// otherwise. + /// + protected void SendMessagePing(IList targets, string info, bool isResponse) + { + WriteMessage(targets, "ping", info, isResponse); + } + } +} \ No newline at end of file diff --git a/IrcDotNet/Ctcp/CtcpEventArgs.cs b/IrcDotNet/Ctcp/CtcpEventArgs.cs new file mode 100644 index 0000000..af78537 --- /dev/null +++ b/IrcDotNet/Ctcp/CtcpEventArgs.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace IrcDotNet.Ctcp +{ + /// + /// Provides data for events that are raised when a CTCP message or notice is sent or received. + /// + /// + public class CtcpMessageEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The source of the message. + /// A list of the targets of the message. + /// The text of the message. + /// is . + /// is . + public CtcpMessageEventArgs(IrcUser source, IList targets, string text) + { + if (targets == null) + throw new ArgumentNullException("target"); + if (text == null) + throw new ArgumentNullException("text"); + + Source = source; + Targets = new ReadOnlyCollection(targets); + Text = text; + } + + /// + /// Gets the source of the message. + /// + /// The source of the message. + public IrcUser Source { get; private set; } + + /// + /// Gets a list of the targets of the message. + /// + /// The targets of the message. + public IList Targets { get; private set; } + + /// + /// Gets the text of the message. + /// + /// The text of the message. + public string Text { get; private set; } + } + + /// + /// Provides data for the event. + /// + /// + public class CtcpTimeResponseReceivedEventArgs : CtcpResponseReceivedEventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The local date/time received from the user. + public CtcpTimeResponseReceivedEventArgs(IrcUser user, string dateTime) + : base(user) + { + DateTime = dateTime; + } + + /// + /// Gets the local date/time for the user. + /// + /// The local date/time for the user. + public string DateTime { get; private set; } + } + + /// + /// Provides data for the event. + /// + /// + public class CtcpVersionResponseReceivedEventArgs : CtcpResponseReceivedEventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The information about the client version. + public CtcpVersionResponseReceivedEventArgs(IrcUser user, string versionInfo) + : base(user) + { + VersionInfo = versionInfo; + } + + /// + /// Gets the information about the client version of the user. + /// + /// The ping time. + public string VersionInfo { get; private set; } + } + + /// + /// Provides data for the event. + /// + /// + public class CtcpErrorMessageReceivedEventArgs : CtcpResponseReceivedEventArgs + { + /// + /// Initializes a new instance of the class, + /// specifying that no error occurred. + /// + /// The message indicating that no error occurred. + public CtcpErrorMessageReceivedEventArgs(IrcUser user, string noErrorMessage) + : base(user) + { + ErrorOccurred = false; + FailedQuery = null; + ErrorMessage = noErrorMessage; + } + + /// + /// Initializes a new instance of the class, + /// specifying the query that failed with an error message. + /// + /// A string containing the query that failed. + /// The message describing the error that occurred for the remote user. + public CtcpErrorMessageReceivedEventArgs(IrcUser user, string failedQuery, string errorMessage) + : base(user) + { + ErrorOccurred = true; + FailedQuery = failedQuery; + ErrorMessage = errorMessage; + } + + /// + /// Gets a value indicating whether an error occurred or the user confirmed that no error occurred. + /// + /// + /// if an error occurred; if the remote user confirmed + /// that no error occurred. + /// + public bool ErrorOccurred { get; private set; } + + /// + /// Gets a string containing the query that failed + /// + /// The failed query. + public string FailedQuery { get; private set; } + + /// + /// Gets message describing the error that occurred for the remote user. + /// + /// The error message. + public string ErrorMessage { get; private set; } + } + + /// + /// Provides data for the event. + /// + /// + public class CtcpPingResponseReceivedEventArgs : CtcpResponseReceivedEventArgs + { + /// + /// + /// Initializes a new instance of the class. + /// + /// The ping time. + public CtcpPingResponseReceivedEventArgs(IrcUser user, TimeSpan pingTime) + : base(user) + { + PingTime = pingTime; + } + + /// + /// Gets the duration of time elapsed between the sending of the ping request and the receiving of the ping + /// response. + /// + /// The ping time. + public TimeSpan PingTime { get; private set; } + } + + /// + /// Provides data for events that indicate a response to a CTCP request. + /// + /// + public class CtcpResponseReceivedEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The user from which the response was received. + public CtcpResponseReceivedEventArgs(IrcUser user) + { + User = user; + } + + /// + /// Gets the user from which the response was received. + /// + /// The user from which the request was received. + public IrcUser User { get; private set; } + } + + /// + /// Provides data for the and + /// events. + /// + /// + public class CtcpRawMessageEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The message that was sent/received. + public CtcpRawMessageEventArgs(CtcpClient.CtcpMessage message) + { + Message = message; + } + + /// + /// Gets the message that was sent/received by the client. + /// + /// The message that was sent/received by the client. + public CtcpClient.CtcpMessage Message { get; private set; } + } +} \ No newline at end of file diff --git a/IrcDotNet/DebugUtilities.cs b/IrcDotNet/DebugUtilities.cs new file mode 100644 index 0000000..11ff7c0 --- /dev/null +++ b/IrcDotNet/DebugUtilities.cs @@ -0,0 +1,24 @@ +using System; +using System.Diagnostics; + +namespace IrcDotNet +{ + // Utilities for debugging execution. + // TODO: Use TraceSource here and configure trace listeners in test project. + internal static class DebugUtilities + { + [Conditional("DEBUG")] + public static void WriteIrcRawLine(IrcClient client, string line) + { +#if DEBUG + WriteEvent("({0}) {1}", client.ClientId, line); +#endif + } + + [Conditional("DEBUG")] + public static void WriteEvent(string message, params object[] args) + { + Debug.WriteLine("{0:HH:mm:ss} {1}", DateTime.Now, string.Format(message, args)); + } + } +} \ No newline at end of file diff --git a/IrcDotNet/IIrcFloodPreventer.cs b/IrcDotNet/IIrcFloodPreventer.cs new file mode 100644 index 0000000..d1e3e7a --- /dev/null +++ b/IrcDotNet/IIrcFloodPreventer.cs @@ -0,0 +1,19 @@ +namespace IrcDotNet +{ + /// + /// Defines a mechanism for preventing server floods by limiting the rate of outgoing raw messages from the client. + /// + public interface IIrcFloodPreventer + { + /// + /// Gets the time delay before which the client may currently send the next message. + /// + /// The time delay before the next message may be sent, in milliseconds. + long GetSendDelay(); + + /// + /// Notifies the flood preventer that a message has just been send by the client. + /// + void HandleMessageSent(); + } +} \ No newline at end of file diff --git a/IrcDotNet/IIrcMessageReceiveHandler.cs b/IrcDotNet/IIrcMessageReceiveHandler.cs new file mode 100644 index 0000000..31801df --- /dev/null +++ b/IrcDotNet/IIrcMessageReceiveHandler.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace IrcDotNet +{ + /// + /// Represents an object that handles messages and notices received by an IRC client. + /// + internal interface IIrcMessageReceiveHandler + { + /// + /// Handles the specified message that was received by the client. + /// + /// The source of the message. + /// A collection of the target of the message. + /// The text of the message. + void HandleMessageReceived(IIrcMessageSource source, IList targets, string text); + + /// + /// Handles the specified notice that was received by the client. + /// + /// The source of the notice. + /// A collection of the target of the notice. + /// The text of the message. + void HandleNoticeReceived(IIrcMessageSource source, IList targets, string text); + } +} \ No newline at end of file diff --git a/IrcDotNet/IIrcMessageReceiver.cs b/IrcDotNet/IIrcMessageReceiver.cs new file mode 100644 index 0000000..2eaecbc --- /dev/null +++ b/IrcDotNet/IIrcMessageReceiver.cs @@ -0,0 +1,20 @@ +using System; + +namespace IrcDotNet +{ + /// + /// Represents an object that raises an event when a message or notice has been received. + /// + public interface IIrcMessageReceiver + { + /// + /// Occurs when a message has been received by the object. + /// + event EventHandler MessageReceived; + + /// + /// Occurs when a notice has been received by the object. + /// + event EventHandler NoticeReceived; + } +} \ No newline at end of file diff --git a/IrcDotNet/IIrcMessageSendHandler.cs b/IrcDotNet/IIrcMessageSendHandler.cs new file mode 100644 index 0000000..b829862 --- /dev/null +++ b/IrcDotNet/IIrcMessageSendHandler.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace IrcDotNet +{ + /// + /// Represents an object that handles messages and notices sent by an IRC client. + /// + internal interface IIrcMessageSendHandler + { + /// + /// Handles the specified message that was sent by the client. + /// + /// A collection of the target of the message. + /// The text of the message. + void HandleMessageSent(IList targets, string text); + + /// + /// Handles the specified notice that was sent by the client. + /// + /// A collection of the target of the notice. + /// The text of the message. + void HandleNoticeSent(IList targets, string text); + } +} \ No newline at end of file diff --git a/IrcDotNet/IIrcMessageSource.cs b/IrcDotNet/IIrcMessageSource.cs new file mode 100644 index 0000000..085501c --- /dev/null +++ b/IrcDotNet/IIrcMessageSource.cs @@ -0,0 +1,14 @@ +namespace IrcDotNet +{ + /// + /// Represents the source of a message or notice sent by an IRC client. + /// + public interface IIrcMessageSource + { + /// + /// Gets the name of the source, as understood by the IRC protocol. + /// + /// The name of the source. + string Name { get; } + } +} \ No newline at end of file diff --git a/IrcDotNet/IIrcMessageTarget.cs b/IrcDotNet/IIrcMessageTarget.cs new file mode 100644 index 0000000..a068cbc --- /dev/null +++ b/IrcDotNet/IIrcMessageTarget.cs @@ -0,0 +1,14 @@ +namespace IrcDotNet +{ + /// + /// Represents the target of a message or notice sent by an IRC client. + /// + public interface IIrcMessageTarget + { + /// + /// Gets the name of the source, as understood by the IRC protocol. + /// + /// The name of the target. + string Name { get; } + } +} \ No newline at end of file diff --git a/IrcDotNet/IrcChannel.cs b/IrcDotNet/IrcChannel.cs new file mode 100644 index 0000000..ec18cdd --- /dev/null +++ b/IrcDotNet/IrcChannel.cs @@ -0,0 +1,633 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using IrcDotNet.Collections; + +namespace IrcDotNet +{ + /// + /// Represents an IRC channel that exists on a specific . + /// + /// + [DebuggerDisplay("{ToString(), nq}")] + public class IrcChannel : INotifyPropertyChanged, IIrcMessageTarget, IIrcMessageReceiveHandler, IIrcMessageReceiver + { + private IrcClient client; + + // Collection of current modes of channel. + private readonly HashSet modes; + + // Current topic of channel. + private string topic; + + private IrcChannelType type; + + // Collection of users that are currently members of this channel. + private readonly Collection users; + + internal IrcChannel(string name) + { + Name = name; + type = IrcChannelType.Unspecified; + modes = new HashSet(); + Modes = new ReadOnlySet(modes); + users = new Collection(); + Users = new IrcChannelUserCollection(this, users); + } + + /// + /// Gets the name of the channel. + /// + /// The name of the channel. + public string Name { get; } + + /// + /// Gets the type of the channel. + /// + /// The type of the channel. + public IrcChannelType Type + { + get { return type; } + private set + { + type = value; + OnPropertyChanged(new PropertyChangedEventArgs("Type")); + } + } + + /// + /// Gets the current topic of the channel. + /// + /// The current topic of the channel. + public string Topic + { + get { return topic; } + private set + { + topic = value; + OnPropertyChanged(new PropertyChangedEventArgs("Topic")); + } + } + + /// + /// Gets a read-only collection of the modes the channel currently has. + /// + /// The current modes of the channel. + public ReadOnlySet Modes { get; } + + /// + /// Gets a collection of all channel users currently in the channel. + /// + /// A collection of all users currently in the channel. + public IrcChannelUserCollection Users { get; } + + /// + /// Gets the client to which the channel belongs. + /// + /// The client to which the channel belongs. + public IrcClient Client + { + get { return client; } + internal set + { + client = value; + OnPropertyChanged(new PropertyChangedEventArgs("Client")); + } + } + + /// + /// Occurs when the channel has received a message. + /// + public event EventHandler MessageReceived; + + /// + /// Occurs when the channel has received a notice. + /// + public event EventHandler NoticeReceived; + + #region IIrcMessageTarget Members + + string IIrcMessageTarget.Name + { + get { return Name; } + } + + #endregion + + /// + /// Occurs when a property value changes. + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Occurs when the list of users in the channel has been received. + /// The list of users is sent initially upon joining the channel, or on the request of the client. + /// + public event EventHandler UsersListReceived; + + /// + /// Occurs when any of the modes of the channel have changed. + /// + public event EventHandler ModesChanged; + + /// + /// Occurs when the topic of the channel has changed. + /// + public event EventHandler TopicChanged; + + /// + /// Occurs when a user has joined the channel. + /// + public event EventHandler UserJoined; + + /// + /// Occurs when a user has left the channel. + /// + public event EventHandler UserLeft; + + /// + /// Occurs when a user is kicked from the channel. + /// + public event EventHandler UserKicked; + + /// + /// Occurs when a user is invited to join the channel. + /// + public event EventHandler UserInvited; + + /// + /// Occurs when the channel has received a message, before the event. + /// + public event EventHandler PreviewMessageReceived; + + /// + /// Occurs when the channel has received a notice, before the event. + /// + public event EventHandler PreviewNoticeReceived; + + /// + /// Gets the in the channel that corresponds to the specified + /// , or if none is found. + /// + /// The for which to look. + /// + /// The in the channel that corresponds to the specified + /// , or if none is found. + /// + /// is . + public IrcChannelUser GetChannelUser(IrcUser user) + { + if (user == null) + throw new ArgumentNullException("user"); + + return users.SingleOrDefault(cu => cu.User == user); + } + + /// + /// The user to invite to the channel + public void Invite(IrcUser user) + { + Invite(user.NickName); + } + + /// + /// Invites the the specified user to the channel. + /// + /// The nick name of the user to invite. + public void Invite(string userNickName) + { + client.Invite(this, userNickName); + } + + /// + /// Kicks the specified user from the channel, giving the specified comment. + /// + /// The nick name of the user to kick from the channel. + /// The comment to give for the kick, or for none. + public void Kick(string userNickName, string comment = null) + { + client.Kick(this, new[] {userNickName}, comment); + } + + /// + /// Requests the current topic of the channel. + /// + public void GetTopic() + { + client.SetTopic(Name); + } + + /// + /// Sets the topic of the channel to the specified text. + /// + /// The new topic to set. + public void SetTopic(string newTopic) + { + client.SetTopic(Name, newTopic); + } + + /// + /// Requests a list of the current modes of the channel, or if is specified, the + /// settings for the specified modes. + /// + /// + /// The modes for which to get the current settings, or for all + /// current channel modes. + /// + public void GetModes(string modes = null) + { + client.GetChannelModes(this, modes); + } + + /// + public void SetModes(params char[] newModes) + { + SetModes((IEnumerable) newModes); + } + + /// + /// + /// A collection of mode characters that should become the new modes. + /// Any modes in the collection that are not currently set will be set, and any nodes not in the collection that + /// are currently set will be unset. + /// + /// is . + public void SetModes(IEnumerable newModes) + { + if (newModes == null) + throw new ArgumentNullException("newModes"); + + lock (((ICollection) Modes).SyncRoot) + SetModes(newModes.Except(modes), modes.Except(newModes)); + } + + /// + /// is . + /// is . + public void SetModes(IEnumerable setModes, IEnumerable unsetModes, + IEnumerable modeParameters = null) + { + if (setModes == null) + throw new ArgumentNullException("setModes"); + if (unsetModes == null) + throw new ArgumentNullException("unsetModes"); + + SetModes("+" + string.Join(string.Empty, setModes) + "-" + string.Join(string.Empty, unsetModes), + modeParameters); + } + + /// + public void SetModes(string modes, params string[] modeParameters) + { + if (modes == null) + throw new ArgumentNullException("modes"); + + SetModes(modes, (IEnumerable) modeParameters); + } + + /// + /// Sets the specified modes on the channel. + /// + /// + /// The mode string that specifies mode changes, which takes the form + /// `( "+" / "-" ) *( mode character )`. + /// + /// + /// A collection of parameters to he modes, or for no + /// parameters. + /// + /// is . + public void SetModes(string modes, IEnumerable modeParameters = null) + { + if (modes == null) + throw new ArgumentNullException("modes"); + + client.SetChannelModes(this, modes, modeParameters); + } + + /// + /// Leaves the channel, giving the specified comment. + /// + /// + /// The comment to send the server upon leaving the channel, or for + /// no comment. + /// + public void Leave(string comment = null) + { + client.Leave(new[] {Name}, comment); + } + + internal void HandleUserNameReply(IrcChannelUser channelUser) + { + lock (((ICollection) Modes).SyncRoot) + { + if (users.Contains(channelUser)) + { +#if SILVERLIGHT + Debug.Assert(false, "User already in channel."); +#else + Debug.Fail("User already in channel."); +#endif + return; + } + } + + channelUser.Channel = this; + lock (((ICollection) Users).SyncRoot) + users.Add(channelUser); + } + + internal void HandleTypeChanged(IrcChannelType type) + { + Type = type; + } + + internal void HandleUsersListReceived() + { + OnUsersListReceived(new EventArgs()); + } + + internal void HandleTopicChanged(IrcUser source, string newTopic) + { + Topic = newTopic; + + OnTopicChanged(new IrcUserEventArgs(source)); + } + + internal void HandleModesChanged(IrcUser source, string newModes, IEnumerable newModeParameters) + { + lock (((ICollection) Modes).SyncRoot) + modes.UpdateModes(newModes, newModeParameters, client.ChannelUserModes, + (add, mode, modeParameter) => users.Single( + cu => cu.User.NickName == modeParameter).HandleModeChanged(add, mode)); + + OnModesChanged(new IrcUserEventArgs(source)); + } + + internal void HandleUserJoined(IrcChannelUser channelUser) + { + lock (((ICollection) Modes).SyncRoot) + { + if (users.Contains(channelUser)) + { +#if SILVERLIGHT + Debug.Assert(false, "User already in channel."); +#else + Debug.Fail("User already in channel."); +#endif + return; + } + } + + channelUser.Channel = this; + lock (((ICollection) Users).SyncRoot) + users.Add(channelUser); + + OnUserJoined(new IrcChannelUserEventArgs(channelUser, null)); + } + + internal void HandleUserLeft(IrcUser user, string comment) + { + lock (((ICollection) Modes).SyncRoot) + HandleUserLeft(users.Single(u => u.User == user), comment); + } + + internal void HandleUserLeft(IrcChannelUser channelUser, string comment) + { + lock (((ICollection) Users).SyncRoot) + users.Remove(channelUser); + + OnUserLeft(new IrcChannelUserEventArgs(channelUser, comment)); + } + + internal void HandleUserKicked(IrcUser user, string comment) + { + lock (((ICollection) Modes).SyncRoot) + HandleUserKicked(users.Single(u => u.User == user), comment); + } + + internal void HandleUserKicked(IrcChannelUser channelUser, string comment) + { + lock (((ICollection) Users).SyncRoot) + users.Remove(channelUser); + + OnUserKicked(new IrcChannelUserEventArgs(channelUser, comment)); + } + + internal void HandleUserInvited(IrcUser user) + { + lock (((ICollection) Modes).SyncRoot) + OnUserInvited(new IrcUserEventArgs(user)); + } + + internal void HandleUserQuit(IrcChannelUser channelUser, string comment) + { + lock (((ICollection) Users).SyncRoot) + users.Remove(channelUser); + } + + internal void HandleMessageReceived(IIrcMessageSource source, IList targets, string text) + { + var previewEventArgs = new IrcPreviewMessageEventArgs(source, targets, text, Client.TextEncoding); + OnPreviewMessageReceived(previewEventArgs); + if (!previewEventArgs.Handled) + OnMessageReceived(new IrcMessageEventArgs(source, targets, text, Client.TextEncoding)); + } + + internal void HandleNoticeReceived(IIrcMessageSource source, IList targets, string text) + { + var previewEventArgs = new IrcPreviewMessageEventArgs(source, targets, text, Client.TextEncoding); + OnPreviewNoticeReceived(previewEventArgs); + if (!previewEventArgs.Handled) + OnNoticeReceived(new IrcMessageEventArgs(source, targets, text, Client.TextEncoding)); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnUsersListReceived(EventArgs e) + { + var handler = UsersListReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnModesChanged(IrcUserEventArgs e) + { + var handler = ModesChanged; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnTopicChanged(IrcUserEventArgs e) + { + var handler = TopicChanged; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnUserJoined(IrcChannelUserEventArgs e) + { + var handler = UserJoined; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnUserLeft(IrcChannelUserEventArgs e) + { + var handler = UserLeft; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnUserKicked(IrcChannelUserEventArgs e) + { + var handler = UserKicked; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnUserInvited(IrcUserEventArgs e) + { + var handler = UserInvited; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnMessageReceived(IrcMessageEventArgs e) + { + var handler = MessageReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnPreviewMessageReceived(IrcPreviewMessageEventArgs e) + { + var handler = PreviewMessageReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnNoticeReceived(IrcMessageEventArgs e) + { + var handler = NoticeReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnPreviewNoticeReceived(IrcPreviewMessageEventArgs e) + { + var handler = PreviewNoticeReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, e); + } + + /// + /// Returns a string representation of this instance. + /// + /// A string that represents this instance. + public override string ToString() + { + return Name; + } + + #region IIrcMessageReceiveHandler Members + + void IIrcMessageReceiveHandler.HandleMessageReceived(IIrcMessageSource source, IList targets, + string text) + { + HandleMessageReceived(source, targets, text); + } + + void IIrcMessageReceiveHandler.HandleNoticeReceived(IIrcMessageSource source, IList targets, + string text) + { + HandleNoticeReceived(source, targets, text); + } + + #endregion + } + + /// + /// Defines the types of channels. Each channel may only be of a single type at any one time. + /// + public enum IrcChannelType + { + /// + /// The channel type is unspecified. + /// + Unspecified, + + /// + /// The channel is public. The server always lists this channel. + /// + Public, + + /// + /// The channel is private. The server never lists this channel. + /// + Private, + + /// + /// The channel is secret. The server never lists this channel and pretends it does not exist when responding to + /// queries. + /// + Secret + } +} \ No newline at end of file diff --git a/IrcDotNet/IrcChannelCollection.cs b/IrcDotNet/IrcChannelCollection.cs new file mode 100644 index 0000000..d6b9385 --- /dev/null +++ b/IrcDotNet/IrcChannelCollection.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace IrcDotNet +{ + /// + /// Represents a collection of objects. + /// + /// + /// + public class IrcChannelCollection : ReadOnlyCollection + { + internal IrcChannelCollection(IrcClient client, IList list) + : base(list) + { + Client = client; + } + + /// + /// Gets the client to which the collection of channels belongs. + /// + /// The client to which the collection of channels belongs. + public IrcClient Client { get; } + + /// + public void Join(params string[] channels) + { + Join((IEnumerable) channels); + } + + /// + /// A collection of the names of channels to join. + public void Join(IEnumerable channels) + { + Client.Join(channels); + } + + /// + public void Join(params Tuple[] channels) + { + Join((IEnumerable>) channels); + } + + /// + /// Joins the specified channels. + /// + /// A collection of 2-tuples of the names of channels to join and their keys. + public void Join(IEnumerable> channels) + { + Client.Join(channels); + } + + /// + public void Leave(params string[] channels) + { + Leave((IEnumerable) channels); + } + + /// + /// Leaves the specified channels, giving the specified comment. + /// + /// A collection of the names of channels to leave. + /// + /// The comment to send the server upon leaving the channel, or for + /// no comment. + /// + public void Leave(IEnumerable channels, string comment = null) + { + Client.Leave(channels, comment); + } + } +} \ No newline at end of file diff --git a/IrcDotNet/IrcChannelInfo.cs b/IrcDotNet/IrcChannelInfo.cs new file mode 100644 index 0000000..765f281 --- /dev/null +++ b/IrcDotNet/IrcChannelInfo.cs @@ -0,0 +1,36 @@ +namespace IrcDotNet +{ + /// + /// Stores information about a particular channel on an IRC network. + /// + public struct IrcChannelInfo + { + /// + /// The name of the channel. + /// + public string Name; + + /// + /// The number of visible users in the channel. + /// + public int? VisibleUsersCount; + + /// + /// The current topic of the channel. + /// + public string Topic; + + /// + /// Initializes a new instance of the structure with the specified properties. + /// + /// The name of the channel. + /// The number of visible users in the channel. + /// The current topic of the channel. + public IrcChannelInfo(string name, int? visibleUsersCount, string topic) + { + Name = name; + VisibleUsersCount = visibleUsersCount; + Topic = topic; + } + } +} \ No newline at end of file diff --git a/IrcDotNet/IrcChannelUser.cs b/IrcDotNet/IrcChannelUser.cs new file mode 100644 index 0000000..16a816f --- /dev/null +++ b/IrcDotNet/IrcChannelUser.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using IrcDotNet.Collections; + +namespace IrcDotNet +{ + /// + /// Represents an IRC user that exists on a specific channel on a specific . + /// + /// + /// + /// + [DebuggerDisplay("{ToString(), nq}")] + public class IrcChannelUser : INotifyPropertyChanged + { + private IrcChannel channel; + // Collection of channel modes currently active on user. + private readonly HashSet modes; + + internal IrcChannelUser(IrcUser user, IEnumerable modes = null) + { + User = user; + + this.modes = new HashSet(); + Modes = new ReadOnlySet(this.modes); + if (modes != null) + this.modes.AddRange(modes); + } + + /// + /// A read-only collection of the channel modes the user currently has. + /// + /// The current channel modes of the user. + public ReadOnlySet Modes { get; } + + /// + /// Gets or sets the channel. + /// + /// The channel. + public IrcChannel Channel + { + get { return channel; } + internal set + { + channel = value; + OnPropertyChanged(new PropertyChangedEventArgs("Channel")); + } + } + + /// + /// Gets the that is represented by the . + /// + /// The that is represented by the . + public IrcUser User { get; } + + /// + /// Occurs when a property value changes. + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Occurs when the channel modes of the user have changed. + /// + public event EventHandler ModesChanged; + + /// + /// Kicks the user from the channel, giving the specified comment. + /// + /// The comment to give for the kick, or for none. + public void Kick(string comment = null) + { + channel.Kick(User.NickName, comment); + } + + /// + /// Gives the user operator privileges in the channel. + /// + public void Op() + { + channel.SetModes("+o", User.NickName); + } + + /// + /// Removes operator privileges from the user in the channel. + /// + public void DeOp() + { + channel.SetModes("-o", User.NickName); + } + + /// + /// Voices the user in the channel. + /// + public void Voice() + { + channel.SetModes("+v", User.NickName); + } + + /// + /// Devoices the user in the channel + /// + public void DeVoice() + { + channel.SetModes("-v", User.NickName); + } + + internal void HandleModeChanged(bool add, char mode) + { + lock (((ICollection) Modes).SyncRoot) + { + if (add) + modes.Add(mode); + else + modes.Remove(mode); + } + + OnModesChanged(new EventArgs()); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnModesChanged(EventArgs e) + { + var handler = ModesChanged; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, e); + } + + /// + /// Returns a string representation of this instance. + /// + /// A string that represents this instance. + public override string ToString() + { + return string.Format("{0}/{1}", channel.Name, User.NickName); + } + } +} \ No newline at end of file diff --git a/IrcDotNet/IrcChannelUserCollection.cs b/IrcDotNet/IrcChannelUserCollection.cs new file mode 100644 index 0000000..24ff6aa --- /dev/null +++ b/IrcDotNet/IrcChannelUserCollection.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace IrcDotNet +{ + /// + /// Represents a collection of objects. + /// + /// + /// + public class IrcChannelUserCollection : ReadOnlyCollection + { + internal IrcChannelUserCollection(IrcChannel channel, IList list) + : base(list) + { + Channel = channel; + } + + /// + /// Gets the channel to which the collection of channel users belongs. + /// + /// The channel to which the collection of channel users belongs.. + public IrcChannel Channel { get; } + + /// + /// Gets a collection of all users that correspond to the channel users in the collection. + /// + /// A collection of users. + public IEnumerable GetUsers() + { + return Items.Select(channelUser => channelUser.User); + } + } +} \ No newline at end of file diff --git a/IrcDotNet/IrcClient.cs b/IrcDotNet/IrcClient.cs new file mode 100644 index 0000000..e97ea99 --- /dev/null +++ b/IrcDotNet/IrcClient.cs @@ -0,0 +1,2064 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using IrcDotNet.Collections; +using IrcDotNet.Properties; + +namespace IrcDotNet +{ + /// + /// Represents a client that communicates with a server using the IRC (Internet Relay Chat) protocol. + /// Do not inherit this class unless the protocol itself is being extended. + /// + /// + /// All collection objects must be locked on the object for thread-safety. + /// They can however be used safely without locking within event handlers. + /// + /// + [DebuggerDisplay("{ToString(), nq}")] + public abstract partial class IrcClient : IDisposable + { + // Maximum number of parameters that can be sent in single raw message. + private const int maxParamsCount = 15; + + // Default port on which to connect to IRC server. + public static readonly int DefaultPort = 6667; + + // Regular expressions used for extracting information from protocol messages. + protected static readonly string regexNickName; + protected static readonly string regexUserName; + protected static readonly string regexHostName; + protected static readonly string regexChannelName; + protected static readonly string regexTargetMask; + protected static readonly string regexServerName; + protected static readonly string regexNickNameId; + protected static readonly string regexUserNameId; + protected static readonly string regexMessagePrefix; + protected static readonly string regexMessageTarget; + + protected static readonly string isupportPrefix; + + // Non-zero if object has been disposed or is currently being disposed. + private int disposedFlag; + + // Prevents client from flooding server with messages by limiting send rate. + + // Dictionary of message processor routines, keyed by their command names. + private readonly Dictionary messageProcessors; + + // Dictionary of message processor routines, keyed by their numeric codes (000 to 999). + private readonly Dictionary numericMessageProcessors; + + static IrcClient() + { + regexNickName = @"(?[^!@]+)"; + regexUserName = @"(?[^!@]+)"; + regexHostName = @"(?[^%@]+)"; + regexChannelName = @"(?[#+!&].+)"; + regexTargetMask = @"(?[$#].+)"; + regexServerName = @"(?[^%@]+?\.[^%@]*)"; + regexNickNameId = string.Format(@"{0}(?:(?:!{1})?@{2})?", regexNickName, regexUserName, regexHostName); + regexUserNameId = string.Format(@"{0}(?:(?:%{1})?@{2}|%{1})", regexUserName, regexHostName, + regexServerName); + regexMessagePrefix = string.Format(@"^(?:{0}|{1})$", regexServerName, regexNickNameId); + regexMessageTarget = string.Format(@"^(?:{0}|{1}|{2}|{3})$", regexChannelName, regexUserNameId, + regexTargetMask, regexNickNameId); + + isupportPrefix = @"\((?.*)\)(?.*)"; + } + + /// + /// Initializes a new instance of the class. + /// + public IrcClient() + { + TextEncoding = Encoding.UTF8; + messageProcessors = new Dictionary( + StringComparer.OrdinalIgnoreCase); + numericMessageProcessors = new Dictionary(1000); + FloodPreventer = null; + + InitializeMessageProcessors(); + } + +#if DEBUG + public string ClientId { get; set; } +#endif + + /// + /// Gets whether the client connection has been registered with the server. + /// + /// + /// if the connection has been registered; , otherwise. + /// + public bool IsRegistered + { + get { return isRegistered; } + } + + /// + /// Gets the local user. The local user is the user managed by this client connection. + /// + /// The local user. + public IrcLocalUser LocalUser + { + get { return localUser; } + } + + /// + /// Gets the 'Welcome' message sent by the server. + /// This value is set after successful registration of the connection. + /// + /// The 'Welcome' message received from the server.. + public string WelcomeMessage { get; protected set; } + + /// + /// Gets the 'Your Host' message sent by the server. + /// This value is set after successful registration of the connection. + /// + /// The 'Your Host' message received from the server. + public string YourHostMessage { get; private set; } + + /// + /// Gets the 'Created' message sent by the server. + /// This value is set after successful registration of the connection. + /// + /// The 'Created' message received from the server. + public string ServerCreatedMessage { get; private set; } + + /// + /// Gets the host name of the server. + /// This value is set after successful registration of the connection. + /// + /// The host name given received from the server. + public string ServerName { get; private set; } + + /// + /// Gets the version of the server. + /// This value is set after successful registration of the connection. + /// + /// The version given received from the server. + public string ServerVersion { get; private set; } + + /// + /// Gets a collection of the user modes available on the server. + /// This value is set after successful registration of the connection. + /// + /// A list of user modes available on the server. + public IEnumerable ServerAvailableUserModes { get; private set; } + + /// + /// Gets a collection of the channel modes available on the server. + /// This value is set after successful registration of the connection. + /// + /// A list of channel modes available on the server. + public IEnumerable ServerAvailableChannelModes { get; private set; } + + /// + /// Gets a dictionary of the features supported by the server, keyed by feature name, as returned by the + /// ISUPPORT message. + /// This value is set after successful registration of the connection. + /// + /// A dictionary of features supported by the server. + public Collections.ReadOnlyDictionary ServerSupportedFeatures { get; private set; } + + /// + /// Gets a collection of channel modes that apply to users in a channel. + /// + /// A collection of channel modes that apply to users. + public ReadOnlyCollection ChannelUserModes { get; private set; } + + /// + /// Gets the Message of the Day (MOTD) sent by the server. + /// This value is set after successful registration of the connection. + /// + /// The Message of the Day sent by the server. + public string MessageOfTheDay + { + get { return motdBuilder.ToString(); } + } + + /// + /// Gets information about the IRC network that is given by the server. + /// This value is set after successful registration of the connection. + /// + /// The Message of the Day sent by the server. + public IrcNetworkInfo? NetworkInformation + { + get { return networkInformation; } + } + + /// + /// Gets a collection of all channels known to the client. + /// + /// A collection of known channels. + public IrcChannelCollection Channels { get; private set; } + + /// + /// Gets a collection of all users known to the client, including the local user. + /// + /// A collection of known users. + public IrcUserCollection Users { get; private set; } + + /// + /// Gets or sets an object that limits the rate of outgoing messages in order to prevent flooding the server. + /// The value is by default, which indicates that no flood prevention should be + /// performed. + /// + /// A flood preventer object. + public IIrcFloodPreventer FloodPreventer { get; set; } + + /// + /// Gets or sets the text encoding to use for reading from and writing to the network data stream. + /// + /// The text encoding of the network stream. + public Encoding TextEncoding { get; set; } + + /// + /// Gets whether the client is currently connected to a server. + /// + /// if the client is connected; , otherwise. + public abstract bool IsConnected { get; } + + /// + /// Gets whether the object has been disposed. + /// + /// + /// if the object has been disposed; + /// , otherwise. + /// + protected bool IsDisposed + { + get { return Interlocked.CompareExchange(ref disposedFlag, 0, 0) > 0; } + } + + /// + /// Releases all resources used by the object. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Finalizes an instance of the class. + /// + ~IrcClient() + { + Dispose(false); + } + + /// + /// Releases all resources used by the . + /// + /// + /// if the consumer is actively disposing the object; + /// if the garbage collector is finalizing the object. + /// + protected virtual void Dispose(bool disposing) + { + if (Interlocked.CompareExchange(ref disposedFlag, 1, 0) > 0) + return; + } + + /// + /// Occurs when the client has connected to the server. + /// + /// + /// Note that the object is not yet set when this event occurs, but is only accessible + /// when the event is raised. + /// + public event EventHandler Connected; + + /// + /// Occurs when the client has failed to connect to the server. + /// + public event EventHandler ConnectFailed; + + /// + /// Occurs when the client has disconnected from the server. + /// + public event EventHandler Disconnected; + + /// + /// Occurs when the client encounters an error during execution, while connected. + /// + public event EventHandler Error; + +#if !SILVERLIGHT + + /// + /// Occurs when the SSL certificate received from the server should be validated. + /// The certificate is automatically validated if this event is not handled. + /// + public event EventHandler ValidateSslCertificate; + +#endif + + /// + /// Occurs when a raw message has been sent to the server. + /// + public event EventHandler RawMessageSent; + + /// + /// Occurs when a raw message has been received from the server. + /// + public event EventHandler RawMessageReceived; + + /// + /// Occurs when a protocol (numeric) error is received from the server. + /// + public event EventHandler ProtocolError; + + /// + /// Occurs when an error message (ERROR command) is received from the server. + /// + public event EventHandler ErrorMessageReceived; + + /// + /// Occurs when the connection has been registered. + /// + /// + /// The object is set when this event occurs. + /// + public event EventHandler Registered; + + /// + /// Occurs when the client information has been received from the server, following registration. + /// + /// + /// Client information is accessible via , , + /// , , , + /// , and . + /// + public event EventHandler ClientInfoReceived; + + /// + /// Occurs when a bounce message is received from the server, telling the client to connect to a new server. + /// + public event EventHandler ServerBounce; + + /// + /// Occurs when a list of features supported by the server (ISUPPORT) has been received. + /// This event may be raised more than once after registration, depending on the size of the list received. + /// + public event EventHandler ServerSupportedFeaturesReceived; + + /// + /// Occurs when a ping query is received from the server. + /// The client automatically replies to pings from the server; this event is only a notification. + /// + public event EventHandler PingReceived; + + /// + /// Occurs when a pong reply is received from the server. + /// + public event EventHandler PongReceived; + + /// + /// Occurs when the Message of the Day (MOTD) has been received from the server. + /// + public event EventHandler MotdReceived; + + /// + /// Occurs when information about the IRC network has been received from the server. + /// + public event EventHandler NetworkInformationReceived; + + /// + /// Occurs when information about a specific server on the IRC network has been received from the server. + /// + public event EventHandler ServerVersionInfoReceived; + + /// + /// Occurs when the local date/time for a specific server has been received from the server. + /// + public event EventHandler ServerTimeReceived; + + /// + /// Occurs when a list of server links has been received from the server. + /// + public event EventHandler ServerLinksListReceived; + + /// + /// Occurs when server statistics have been received from the server. + /// + public event EventHandler ServerStatsReceived; + + /// + /// Occurs when a reply to a Who query has been received from the server. + /// + public event EventHandler WhoReplyReceived; + + /// + /// Occurs when a reply to a Who Is query has been received from the server. + /// + public event EventHandler WhoIsReplyReceived; + + /// + /// Occurs when a reply to a Who Was query has been received from the server. + /// + public event EventHandler WhoWasReplyReceived; + + /// + /// Occurs when a list of channels has been received from the server in response to a query. + /// + public event EventHandler ChannelListReceived; + + /// + public void ListChannels(params string[] channelNames) + { + CheckDisposed(); + + if (channelNames == null) + throw new ArgumentNullException("channelNames"); + + SendMessageList(channelNames); + } + + /// + /// Requests a list of information about the specified (or all) channels on the network. + /// + /// + /// The names of the channels to list, or to list all channels + /// on the network. + /// + public void ListChannels(IEnumerable channelNames = null) + { + CheckDisposed(); + + SendMessageList(channelNames); + } + + /// + /// Requests the Message of the Day (MOTD) from the specified server. + /// + /// + /// The name of the server from which to request the MOTD, or + /// for the current server. + /// + /// The current instance has already been disposed. + public void GetMessageOfTheDay(string targetServer = null) + { + CheckDisposed(); + + SendMessageMotd(targetServer); + } + + /// + /// Requests statistics about the connected IRC network. + /// If is specified, then the server only returns information about the part of + /// the network formed by the servers whose names match the mask; otherwise, the information concerns the whole + /// network + /// + /// + /// A wildcard expression for matching against server names, or + /// to match the entire network. + /// + /// + /// The name of the server to which to forward the message, or + /// for the current server. + /// + /// The current instance has already been disposed. + public void GetNetworkInfo(string serverMask = null, string targetServer = null) + { + CheckDisposed(); + + SendMessageLUsers(serverMask, targetServer); + } + + /// + /// Requests the version of the specified server. + /// + /// The name of the server whose version to request. + /// The current instance has already been disposed. + public void GetServerVersion(string targetServer = null) + { + CheckDisposed(); + + SendMessageVersion(targetServer); + } + + /// + /// Requests statistics about the specified server. + /// + /// + /// The query character that indicates which server statistics to return. + /// The set of valid query characters is dependent on the implementation of the particular IRC server. + /// + /// The name of the server whose statistics to request. + /// + /// The server may not accept the command if is unspecified. + /// + /// The current instance has already been disposed. + public void GetServerStatistics(char? query = null, string targetServer = null) + { + CheckDisposed(); + + SendMessageStats(query == null ? null : query.Value.ToString(), targetServer); + } + + /// + /// Requests a list of all servers known by the target server. + /// If is specified, then the server only returns information about the part of + /// the network formed by the servers whose names match the mask; otherwise, the information concerns the whole + /// network. + /// + /// + /// A wildcard expression for matching against server names, or + /// to match the entire network. + /// + /// + /// The name of the server to which to forward the request, or + /// for the current server. + /// + /// The current instance has already been disposed. + public void GetServerLinks(string serverMask = null, string targetServer = null) + { + CheckDisposed(); + + SendMessageLinks(serverMask, targetServer); + } + + /// + /// Requests the local time on the specified server. + /// + /// The name of the server whose local time to request. + /// The current instance has already been disposed. + public void GetServerTime(string targetServer = null) + { + CheckDisposed(); + + SendMessageTime(targetServer); + } + + /// + /// Sends a ping to the specified server. + /// + /// The name of the server to ping. + /// The current instance has already been disposed. + public void Ping(string targetServer = null) + { + CheckDisposed(); + + SendMessagePing(localUser.NickName, targetServer); + } + + /// + /// Sends a Who query to the server targeting the specified channel or user masks. + /// + /// + /// A wildcard expression for matching against channel names; or if none can be found, + /// host names, server names, real names, and nick names of users. If the value is , + /// all users are matched. + /// + /// + /// to match only server operators; + /// to match all users. + /// + /// The current instance has already been disposed. + public void QueryWho(string mask = null, bool onlyOperators = false) + { + CheckDisposed(); + + SendMessageWho(mask, onlyOperators); + } + + /// + public void QueryWhoIs(params string[] nickNameMasks) + { + CheckDisposed(); + + if (nickNameMasks == null) + throw new ArgumentNullException("nickNames"); + + QueryWhoIs((IEnumerable) nickNameMasks); + } + + /// Sends a Who Is query to the server. + /// + /// Sends a Who Is query to server targeting the specified nick name masks. + /// + /// + /// A collection of wildcard expressions for matching against nick names of users. + /// + /// The current instance has already been disposed. + /// is . + public void QueryWhoIs(IEnumerable nickNameMasks) + { + CheckDisposed(); + + if (nickNameMasks == null) + throw new ArgumentNullException("nickNames"); + + SendMessageWhoIs(nickNameMasks); + } + + /// + public void QueryWhoWas(params string[] nickNames) + { + CheckDisposed(); + + if (nickNames == null) + throw new ArgumentNullException("nickNames"); + + QueryWhoWas((IEnumerable) nickNames); + } + + /// + /// Sends a Who Was query to server targeting the specified nick names. + /// + /// The nick names of the users to query. + /// + /// The maximum number of entries to return from the query. A negative value + /// specifies to return an unlimited number of entries. + /// + /// The current instance has already been disposed. + /// is . + public void QueryWhoWas(IEnumerable nickNames, int entriesCount = -1) + { + CheckDisposed(); + + if (nickNames == null) + throw new ArgumentNullException("nickNames"); + + SendMessageWhoWas(nickNames, entriesCount); + } + + /// + /// + /// Quits the server, giving the specified comment. Waits the specified duration of time before forcibly + /// disconnecting. + /// + /// The number of milliseconds to wait before forcibly disconnecting. + /// + /// + /// The current instance has already been disposed. + public virtual void Quit(int timeout, string comment = null) + { + SendMessageQuit(comment); + } + + /// + /// Quits the server, giving the specified comment. + /// + /// The comment to send to the server. + /// + /// Note that because this message is not sent immediately, calling immediately after + /// this will likely disconnect the client before it has a chance to quit the server properly. + /// Quitting the server should automatically disconnect the client. + /// + /// The current instance has already been disposed. + public void Quit(string comment = null) + { + Quit(0, comment); + } + + /// + /// Sends the specified raw message to the server. + /// + /// The text (single line) of the message to send the server. + /// The current instance has already been disposed. + /// is . + public void SendRawMessage(string message) + { + CheckDisposed(); + + if (message == null) + throw new ArgumentNullException("message"); + + WriteMessage(message); + } + + /// + /// Handles the specified statistical entry for the server, received in response to a STATS message. + /// + /// The type of the statistical entry for the server. + /// The message that contains the statistical entry. + protected void HandleStatsEntryReceived(int type, IrcMessage message) + { + // Add statistical entry to temporary list. + listedStatsEntries.Add(new IrcServerStatisticalEntry + { + Type = type, + Parameters = message.Parameters.Skip(1).ToArray() + }); + } + + /// + /// Handles the specified parameter value of an ISUPPORT message, received from the server upon registration. + /// + /// The name of the parameter. + /// + /// The value of the parameter, or if it does not have a value. + /// + protected bool HandleISupportParameter(string paramName, string paramValue) + { + if (paramName == null) + throw new ArgumentNullException("paramName"); + if (paramName.Length == 0) + throw new ArgumentException(Resources.MessageValueCannotBeEmptyString, "paramName"); + + // Check name of parameter. + switch (paramName.ToLowerInvariant()) + { + case "prefix": + var prefixValueMatch = Regex.Match(paramValue, isupportPrefix); + ; + var prefixes = prefixValueMatch.Groups["prefixes"].GetValue(); + var modes = prefixValueMatch.Groups["modes"].GetValue(); + + if (prefixes.Length != modes.Length) + throw new ProtocolViolationException(Resources.MessageISupportPrefixInvalid); + + lock (((ICollection) ChannelUserModes).SyncRoot) + { + channelUserModes.Clear(); + channelUserModes.AddRange(modes); + } + + channelUserModesPrefixes.Clear(); + for (var i = 0; i < prefixes.Length; i++) + channelUserModesPrefixes.Add(prefixes[i], modes[i]); + + return true; + default: + return false; + } + } + + /// + /// Extracts the the mode and nick name of a user from the specified value. + /// + /// The input value, containing a nick name optionally prefixed by a mode character. + /// A 2-tuple of the nick name and user mode. + protected Tuple GetUserModeAndNickName(string input) + { + if (input == null) + throw new ArgumentNullException("input"); + if (input.Length == 0) + throw new ArgumentException(Resources.MessageValueCannotBeEmptyString, "input"); + + char mode; + if (channelUserModesPrefixes.TryGetValue(input[0], out mode)) + return Tuple.Create(input.Substring(1), mode.ToString()); + return Tuple.Create(input, string.Empty); + } + + /// + /// Gets a collection of mode characters and mode parameters from the specified mode parameters. + /// Combines multiple mode strings into a single mode string. + /// + /// + /// A collection of message parameters, which consists of mode strings and mode + /// parameters. A mode string is of the form `( "+" / "-" ) *( mode character )`, and specifies mode changes. + /// A mode parameter is arbitrary text associated with a certain mode. + /// + /// + /// A 2-tuple of a single mode string and a collection of mode parameters. + /// Each mode parameter corresponds to a single mode character, in the same order. + /// + protected Tuple> GetModeAndParameters(IEnumerable messageParameters) + { + if (messageParameters == null) + throw new ArgumentNullException("messageParameters"); + + var modes = new StringBuilder(); + var modeParameters = new List(); + foreach (var p in messageParameters) + { + if (p == null) + break; + if (p.Length == 0) + continue; + if (p[0] == '+' || p[0] == '-') + modes.Append(p); + else + modeParameters.Add(p); + } + return Tuple.Create(modes.ToString(), (IEnumerable) modeParameters.AsReadOnly()); + } + + /// + /// Gets a list of channel objects from the specified comma-separated list of channel names. + /// + /// A value that contains a comma-separated list of names of channels. + /// A list of channel objects that corresponds to the given list of channel names. + protected IEnumerable GetChannelsFromList(string namesList) + { + if (namesList == null) + throw new ArgumentNullException("namesList"); + + return namesList.Split(',').Select(n => GetChannelFromName(n)); + } + + /// + /// Gets a list of user objects from the specified comma-separated list of nick names. + /// + /// A value that contains a comma-separated list of nick names of users. + /// A list of user objects that corresponds to the given list of nick names. + protected IEnumerable GetUsersFromList(string nickNamesList) + { + if (nickNamesList == null) + throw new ArgumentNullException("nickNamesList"); + + lock (((ICollection) Users).SyncRoot) + return nickNamesList.Split(',').Select(n => users.Single(u => u.NickName == n)); + } + + /// + /// Determines whether the specified name refers to a channel. + /// + /// The name to check. + /// + /// if the specified name represents a channel; , + /// otherwise. + /// + protected bool IsChannelName(string name) + { + if (name == null) + throw new ArgumentNullException("name"); + + return Regex.IsMatch(name, regexChannelName); + } + + /// + /// Gets the type of the channel from the specified character. + /// + /// + /// A character that represents the type of the channel. + /// The character may be one of the following: + /// + /// + /// Character + /// Channel type + /// + /// + /// = + /// Public channel + /// + /// + /// * + /// Private channel + /// + /// + /// @ + /// Secret channel + /// + /// + /// + /// The channel type that corresponds to the specified character. + /// + /// does not correspond to any known channel type. + /// + protected IrcChannelType GetChannelType(char type) + { + switch (type) + { + case '=': + return IrcChannelType.Public; + case '*': + return IrcChannelType.Private; + case '@': + return IrcChannelType.Secret; + default: + throw new ArgumentException(string.Format( + Resources.MessageInvalidChannelType, type), "type"); + } + } + + /// + /// Gets the target of a message from the specified name. + /// A message target may be an , , or . + /// + /// The name of the target. + /// The target object that corresponds to the given name. + /// + /// does not represent a valid message target. + /// + protected IIrcMessageTarget GetMessageTarget(string targetName) + { + if (targetName == null) + throw new ArgumentNullException("targetName"); + if (targetName.Length == 0) + throw new ArgumentException(Resources.MessageValueCannotBeEmptyString, "targetName"); + + // Check whether target name represents channel, user, or target mask. + var targetNameMatch = Regex.Match(targetName, regexMessageTarget); + var channelName = targetNameMatch.Groups["channel"].GetValue(); + var nickName = targetNameMatch.Groups["nick"].GetValue(); + var userName = targetNameMatch.Groups["user"].GetValue(); + var hostName = targetNameMatch.Groups["host"].GetValue(); + var serverName = targetNameMatch.Groups["server"].GetValue(); + var targetMask = targetNameMatch.Groups["targetMask"].GetValue(); + if (channelName != null) + { + return GetChannelFromName(channelName); + } + if (nickName != null) + { + // Find user by nick name. If no user exists in list, create it and set its properties. + var user = GetUserFromNickName(nickName, true); + if (user.UserName == null) + user.UserName = userName; + if (user.HostName == null) + user.HostName = hostName; + + return user; + } + if (userName != null) + { + // Find user by user name. If no user exists in list, create it and set its properties. + var user = GetUserFromNickName(nickName, true); + if (user.HostName == null) + user.HostName = hostName; + + return user; + } + if (targetMask != null) + { + return new IrcTargetMask(targetMask); + } + throw new ArgumentException(string.Format( + Resources.MessageInvalidSource, targetName), "targetName"); + } + + /// + /// Gets the source of a message from the specified prefix. + /// A message source may be a or . + /// + /// The raw prefix of the message. + /// + /// The message source that corresponds to the specified prefix. The object is an instance of + /// or . + /// + /// + /// does not represent a valid message source. + /// + protected IIrcMessageSource GetSourceFromPrefix(string prefix) + { + if (prefix == null) + return null; + if (prefix.Length == 0) + throw new ArgumentException(Resources.MessageValueCannotBeEmptyString, "prefix"); + + // Check whether prefix represents server or user. + var prefixMatch = Regex.Match(prefix, regexMessagePrefix); + var serverName = prefixMatch.Groups["server"].GetValue(); + var nickName = prefixMatch.Groups["nick"].GetValue(); + var userName = prefixMatch.Groups["user"].GetValue(); + var hostName = prefixMatch.Groups["host"].GetValue(); + if (serverName != null) + { + return GetServerFromHostName(serverName); + } + if (nickName != null) + { + // Find user by nick name. If no user exists in list, create it and set its properties. + var user = GetUserFromNickName(nickName, true); + if (user.UserName == null) + user.UserName = userName; + if (user.HostName == null) + user.HostName = hostName; + + return user; + } + throw new ArgumentException(string.Format( + Resources.MessageInvalidSource, prefix), "prefix"); + } + + /// + protected IrcServer GetServerFromHostName(string hostName) + { + bool createdNew; + return GetServerFromHostName(hostName, out createdNew); + } + + /// + /// Gets the server with the specified host name, creating it if necessary. + /// + /// The host name of the server. + /// + /// if the server object was created during the call; + /// , otherwise. + /// + /// The server object that corresponds to the specified host name. + protected IrcServer GetServerFromHostName(string hostName, out bool createdNew) + { + if (hostName == null) + throw new ArgumentNullException("hostName"); + if (hostName.Length == 0) + throw new ArgumentException(Resources.MessageValueCannotBeEmptyString, "hostName"); + + // Search for server with given name in list of known servers. If it does not exist, add it. + var server = servers.SingleOrDefault(s => s.HostName == hostName); + if (server == null) + { + server = new IrcServer(hostName); + servers.Add(server); + + createdNew = true; + } + else + { + createdNew = false; + } + return server; + } + + /// + protected IrcChannel GetChannelFromName(string channelName) + { + bool createdNew; + return GetChannelFromName(channelName, out createdNew); + } + + /// + /// Gets the channel with the specified name, creating it if necessary. + /// + /// The name of the channel. + /// + /// if the channel object was created during the call; + /// , otherwise. + /// + /// The channel object that corresponds to the specified name. + protected IrcChannel GetChannelFromName(string channelName, out bool createdNew) + { + if (channelName == null) + throw new ArgumentNullException("channelName"); + if (channelName.Length == 0) + throw new ArgumentException(Resources.MessageValueCannotBeEmptyString, "channelName"); + + // Search for channel with given name in list of known channel. If it does not exist, add it. + lock (((ICollection) Channels).SyncRoot) + { + var channel = channels.SingleOrDefault(c => c.Name == channelName); + if (channel == null) + { + channel = new IrcChannel(channelName); + channel.Client = this; + channels.Add(channel); + createdNew = true; + } + else + { + createdNew = false; + } + + return channel; + } + } + + /// + protected IrcUser GetUserFromNickName(string nickName, bool isOnline = true) + { + bool createdNew; + return GetUserFromNickName(nickName, isOnline, out createdNew); + } + + /// + /// Gets the user with the specified nick name, creating it if necessary. + /// + /// The nick name of the user. + /// + /// if the user is currently online; + /// , if the user is currently offline. + /// The property of the user object is set to this value. + /// + /// + /// if the user object was created during the call; + /// , otherwise. + /// + /// The user object that corresponds to the specified nick name. + protected IrcUser GetUserFromNickName(string nickName, bool isOnline, out bool createdNew) + { + if (nickName == null) + throw new ArgumentNullException("nickName"); + if (nickName.Length == 0) + throw new ArgumentException(Resources.MessageValueCannotBeEmptyString, "nickName"); + + // Search for user with given nick name in list of known users. If it does not exist, add it. + IrcUser user; + lock (((ICollection) Users).SyncRoot) + { + user = users.SingleOrDefault(u => u.NickName == nickName); + if (user == null) + { + user = new IrcUser + { + Client = this, + NickName = nickName + }; + users.Add(user); + createdNew = true; + } + else + { + createdNew = false; + } + } + user.IsOnline = isOnline; + return user; + } + + /// + protected IrcUser GetUserFromUserName(string userName) + { + bool createdNew; + return GetUserFromUserName(userName, out createdNew); + } + + /// + /// Gets the user with the specified user name, creating it if necessary. + /// + /// The user name of the user. + /// + /// if the user object was created during the call; + /// , otherwise. + /// + /// The user object that corresponds to the specified user name. + protected IrcUser GetUserFromUserName(string userName, out bool createdNew) + { + if (userName == null) + throw new ArgumentNullException("userName"); + if (userName.Length == 0) + throw new ArgumentException(Resources.MessageValueCannotBeEmptyString, "userName"); + + // Search for user with given nick name in list of known users. If it does not exist, add it. + lock (((ICollection) Users).SyncRoot) + { + var user = users.SingleOrDefault(u => u.UserName == userName); + if (user == null) + { + user = new IrcUser(); + user.Client = this; + user.UserName = userName; + users.Add(user); + + createdNew = true; + } + else + { + createdNew = false; + } + + return user; + } + } + + protected int GetNumericUserMode(ICollection modes) + { + var value = 0; + if (modes == null) + return value; + if (modes.Contains('w')) + value |= 0x02; + if (modes.Contains('i')) + value |= 0x04; + return value; + } + + protected virtual void ResetState() + { + // Reset fully state of client. + servers = new Collection(); + isRegistered = false; + localUser = null; + serverSupportedFeatures = new Dictionary(); + ServerSupportedFeatures = new Collections.ReadOnlyDictionary(serverSupportedFeatures); + channelUserModes = new Collection + { + 'o', + 'v' + }; + ChannelUserModes = new ReadOnlyCollection(channelUserModes); + channelUserModesPrefixes = new Dictionary + { + {'@', 'o'}, + {'+', 'v'} + }; + motdBuilder = new StringBuilder(); + networkInformation = new IrcNetworkInfo(); + channels = new Collection(); + Channels = new IrcChannelCollection(this, channels); + users = new Collection(); + Users = new IrcUserCollection(this, users); + listedChannels = new List(); + listedServerLinks = new List(); + listedStatsEntries = new List(); + } + + protected void InitializeMessageProcessors() + { + // Find each method defined as processor for IRC message. + foreach (var method in this.GetAttributedMethods()) + { + var attribute = method.Item1; + var methodDelegate = method.Item2; + + var commandRangeParts = attribute.CommandName.Split('-'); + if (commandRangeParts.Length == 2) + { + // Numeric command range was defined. + var commandRangeStart = int.Parse(commandRangeParts[0]); + var commandRangeEnd = int.Parse(commandRangeParts[1]); + for (var code = commandRangeStart; code <= commandRangeEnd; code++) + numericMessageProcessors.Add(code, methodDelegate); + } + else if (commandRangeParts.Length == 1) + { + // Single command name was defined. Check whether it is numeric or alphabetic. + int commandCode; + if (int.TryParse(attribute.CommandName, out commandCode)) + // Command is numeric. + numericMessageProcessors.Add(commandCode, methodDelegate); + else + // Command is alphabetic. + messageProcessors.Add(attribute.CommandName, methodDelegate); + } + else + { + throw new ProtocolViolationException(string.Format( + Resources.MessageInvalidCommandDefinition, attribute.CommandName)); + } + } + ; + } + + /// + /// The current instance has already been disposed. + protected void WriteMessage(string prefix, string command, IEnumerable parameters) + { + CheckDisposed(); + + WriteMessage(prefix, command, parameters.ToArray()); + } + + /// + /// The message prefix that represents the source of the message. + /// The name of the command. + /// A collection of the parameters to the command. + /// The current instance has already been disposed. + protected void WriteMessage(string prefix, string command, params string[] parameters) + { + CheckDisposed(); + + var message = new IrcMessage(this, prefix, command, parameters.ToArray()); + if (message.Source == null) + message.Source = localUser; + WriteMessage(message); + } + + /// + /// + /// Writes the specified message (prefix, command, and parameters) to the network stream. + /// + /// The message to write. + /// + /// contains more than 15 many parameters. + /// + /// + /// The value of of + /// is invalid. + /// + /// + /// The value of one of the items of of + /// is invalid. + /// + /// The current instance has already been disposed. + protected void WriteMessage(IrcMessage message) + { + CheckDisposed(); + + if (message.Command == null) + throw new ArgumentException(Resources.MessageInvalidCommand, "message"); + if (message.Parameters.Count > maxParamsCount) + throw new ArgumentException(Resources.MessageTooManyParams, "parameters"); + + var lineBuilder = new StringBuilder(); + + // Append prefix to line, if specified. + if (message.Prefix != null) + lineBuilder.Append(":" + CheckPrefix(message.Prefix) + " "); + + // Append command name to line. + lineBuilder.Append(CheckCommand(message.Command).ToUpper()); + + // Append each parameter to line, adding ':' character before last parameter. + for (var i = 0; i < message.Parameters.Count - 1; i++) + { + if (message.Parameters[i] != null) + lineBuilder.Append(" " + CheckMiddleParameter(message.Parameters[i])); + } + if (message.Parameters.Count > 0) + { + var lastParameter = message.Parameters[message.Parameters.Count - 1]; + if (lastParameter != null) + lineBuilder.Append(" :" + CheckTrailingParameter(lastParameter)); + } + + // Send raw message as line of text. + var line = lineBuilder.ToString(); + var messageSentEventArgs = new IrcRawMessageEventArgs(message, line); + WriteMessage(line, messageSentEventArgs); + } + + protected virtual void WriteMessage(string line, object token = null) + { + CheckDisposed(); + + Debug.Assert(line != null); + } + + private void ReadMessage(IrcMessage message, string line) + { + // Try to find corresponding message processor for command of given message. + MessageProcessor messageProcessor; + int commandCode; + if (messageProcessors.TryGetValue(message.Command, out messageProcessor) || + (int.TryParse(message.Command, out commandCode) && + numericMessageProcessors.TryGetValue(commandCode, out messageProcessor))) + { + try + { + messageProcessor(message); + } +#if !DEBUG + catch (Exception ex) + { + OnError(new IrcErrorEventArgs(ex)); + } +#endif + finally + { + } + } + else + { + // Unknown command. + DebugUtilities.WriteEvent("Unknown IRC message command '{0}'.", message.Command); + } + } + + private string CheckPrefix(string value) + { + Debug.Assert(value != null); + + if (value.Length == 0 || value.Any(IsInvalidMessageChar)) + { + throw new ArgumentException(string.Format( + Resources.MessageInvalidPrefix, value), "value"); + } + + return value; + } + + private string CheckCommand(string value) + { + Debug.Assert(value != null); + + if (value.Length == 0 || value.Any(IsInvalidMessageChar)) + { + throw new ArgumentException(string.Format( + Resources.MessageInvalidCommand, value), "value"); + } + + return value; + } + + private string CheckMiddleParameter(string value) + { + Debug.Assert(value != null); + + if (value.Length == 0 || value.Any(c => IsInvalidMessageChar(c) || c == ' ') || value[0] == ':') + { + throw new ArgumentException(string.Format( + Resources.MessageInvalidMiddleParameter, value), "value"); + } + + return value; + } + + private string CheckTrailingParameter(string value) + { + Debug.Assert(value != null); + + if (value.Any(c => IsInvalidMessageChar(c))) + { + throw new ArgumentException(string.Format( + Resources.MessageInvalidMiddleParameter, value), "value"); + } + + return value; + } + + private bool IsInvalidMessageChar(char value) + { + return value == '\0' || value == '\r' || value == '\n'; + } + + protected void Connect(IrcRegistrationInfo registrationInfo) + { + CheckDisposed(); + + if (registrationInfo == null) + throw new ArgumentNullException("registrationInfo"); + + CheckRegistrationInfo(registrationInfo, "registrationInfo"); + ResetState(); + } + + protected void CheckRegistrationInfo(IrcRegistrationInfo registrationInfo, string registrationInfoParamName) + { + // Check that given registration info is valid. + if (registrationInfo is IrcUserRegistrationInfo) + { + if (registrationInfo.NickName == null || + ((IrcUserRegistrationInfo) registrationInfo).UserName == null) + throw new ArgumentException(Resources.MessageInvalidUserRegistrationInfo, + registrationInfoParamName); + } + else if (registrationInfo is IrcServiceRegistrationInfo) + { + if (registrationInfo.NickName == null || + ((IrcServiceRegistrationInfo) registrationInfo).Description == null) + throw new ArgumentException(Resources.MessageInvalidServiceRegistrationInfo, + registrationInfoParamName); + } + else + { + throw new ArgumentException(Resources.MessageInvalidRegistrationInfoObject, + registrationInfoParamName); + } + } + + /// + /// Disconnects asynchronously from the server. + /// + /// The current instance has already been disposed. + /// + /// This method closes the client connection immediately and forcibly, and does not send a quit message to the + /// server. To disconnect from the IRC server gracefully, call and wait for the + /// connection to be closed. + /// + public virtual void Disconnect() + { + CheckDisposed(); + } + + protected void ParseMessage(string line) + { + string prefix = null; + string lineAfterPrefix = null; + + // Extract prefix from message line, if it contains one. + if (line[0] == ':') + { + var firstSpaceIndex = line.IndexOf(' '); + Debug.Assert(firstSpaceIndex != -1); + prefix = line.Substring(1, firstSpaceIndex - 1); + lineAfterPrefix = line.Substring(firstSpaceIndex + 1); + } + else + { + lineAfterPrefix = line; + } + + // Extract command from message. + var spaceIndex = lineAfterPrefix.IndexOf(' '); + Debug.Assert(spaceIndex != -1); + var command = lineAfterPrefix.Substring(0, spaceIndex); + var paramsLine = lineAfterPrefix.Substring(command.Length + 1); + + // Extract parameters from message. + // Each parameter is separated by single space, except last one, which may contain spaces if it + // is prefixed by colon. + var parameters = new string[maxParamsCount]; + int paramStartIndex, paramEndIndex = -1; + var lineColonIndex = paramsLine.IndexOf(" :"); + if (lineColonIndex == -1 && !paramsLine.StartsWith(":")) + lineColonIndex = paramsLine.Length; + for (var i = 0; i < parameters.Length; i++) + { + paramStartIndex = paramEndIndex + 1; + paramEndIndex = paramsLine.IndexOf(' ', paramStartIndex); + if (paramEndIndex == -1) + paramEndIndex = paramsLine.Length; + if (paramEndIndex > lineColonIndex) + { + paramStartIndex++; + paramEndIndex = paramsLine.Length; + } + parameters[i] = paramsLine.Substring(paramStartIndex, paramEndIndex - paramStartIndex); + if (paramEndIndex == paramsLine.Length) + break; + } + + // Parse received IRC message. + var message = new IrcMessage(this, prefix, command, parameters); + var messageReceivedEventArgs = new IrcRawMessageEventArgs(message, line); + OnRawMessageReceived(messageReceivedEventArgs); + ReadMessage(message, line); + +#if DEBUG + DebugUtilities.WriteIrcRawLine(this, ">>> " + messageReceivedEventArgs.RawContent); +#endif + } + + protected void HandleClientConnecting() + { + DebugUtilities.WriteEvent("Connecting to server..."); + } + + protected virtual void HandleClientConnected(IrcRegistrationInfo regInfo) + { + if (regInfo.Password != null) + // Authenticate with server using password. + SendMessagePassword(regInfo.Password); + + // Check if client is registering as service or normal user. + if (regInfo is IrcServiceRegistrationInfo) + { + // Register client as service. + var serviceRegInfo = (IrcServiceRegistrationInfo) regInfo; + SendMessageService(serviceRegInfo.NickName, serviceRegInfo.Distribution, + serviceRegInfo.Description); + + localUser = new IrcLocalUser(serviceRegInfo.NickName, serviceRegInfo.Distribution, + serviceRegInfo.Description); + } + else + { + // Register client as normal user. + var userRegInfo = (IrcUserRegistrationInfo) regInfo; + SendMessageNick(userRegInfo.NickName); + SendMessageUser(userRegInfo.UserName, GetNumericUserMode(userRegInfo.UserModes), + userRegInfo.RealName); + + localUser = new IrcLocalUser(userRegInfo.NickName, userRegInfo.UserName, userRegInfo.RealName, + userRegInfo.UserModes); + } + localUser.Client = this; + + // Add local user to list of known users. + lock (((ICollection) Users).SyncRoot) + users.Add(localUser); + + OnConnected(new EventArgs()); + } + + protected virtual void HandleClientDisconnected() + { + OnDisconnected(new EventArgs()); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnConnected(EventArgs e) + { + var handler = Connected; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnConnectFailed(IrcErrorEventArgs e) + { + var handler = ConnectFailed; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnDisconnected(EventArgs e) + { + var handler = Disconnected; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnError(IrcErrorEventArgs e) + { + var handler = Error; + if (handler != null) + handler(this, e); + } + +#if !SILVERLIGHT + + /// + /// Raises the event. + /// + /// + /// The instance containing the event data. + /// + protected virtual void OnValidateSslCertificate(IrcValidateSslCertificateEventArgs e) + { + var handler = ValidateSslCertificate; + if (handler != null) + handler(this, e); + } + +#endif + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnRawMessageSent(IrcRawMessageEventArgs e) + { + var handler = RawMessageSent; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnRawMessageReceived(IrcRawMessageEventArgs e) + { + var handler = RawMessageReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnProtocolError(IrcProtocolErrorEventArgs e) + { + var handler = ProtocolError; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnErrorMessageReceived(IrcErrorMessageEventArgs e) + { + var handler = ErrorMessageReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnClientInfoReceived(EventArgs e) + { + var handler = ClientInfoReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnRegistered(EventArgs e) + { + var handler = Registered; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnServerBounce(IrcServerInfoEventArgs e) + { + var handler = ServerBounce; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnServerSupportedFeaturesReceived(EventArgs e) + { + var handler = ServerSupportedFeaturesReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnPingReceived(IrcPingOrPongReceivedEventArgs e) + { + var handler = PingReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnPongReceived(IrcPingOrPongReceivedEventArgs e) + { + var handler = PongReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnMotdReceived(EventArgs e) + { + var handler = MotdReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnNetworkInformationReceived(IrcCommentEventArgs e) + { + var handler = NetworkInformationReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnServerVersionInfoReceived(IrcServerVersionInfoEventArgs e) + { + var handler = ServerVersionInfoReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnServerTimeReceived(IrcServerTimeEventArgs e) + { + var handler = ServerTimeReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// + /// The instance containing the event data. + /// + protected virtual void OnServerLinksListReceived(IrcServerLinksListReceivedEventArgs e) + { + var handler = ServerLinksListReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// + /// The instance containing the event data. + /// + protected virtual void OnServerStatsReceived(IrcServerStatsReceivedEventArgs e) + { + var handler = ServerStatsReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnWhoReplyReceived(IrcNameEventArgs e) + { + var handler = WhoReplyReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnWhoIsReplyReceived(IrcUserEventArgs e) + { + var handler = WhoIsReplyReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnWhoWasReplyReceived(IrcUserEventArgs e) + { + var handler = WhoWasReplyReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// + /// The instance containing the event data. + /// + protected virtual void OnChannelListReceived(IrcChannelListReceivedEventArgs e) + { + var handler = ChannelListReceived; + if (handler != null) + handler(this, e); + } + + protected void CheckDisposed() + { + if (IsDisposed) + throw new ObjectDisposedException(GetType().FullName); + } + + /// + /// Returns a string representation of this instance. + /// + /// A string that represents this instance. + public override string ToString() + { + if (!IsDisposed && IsConnected) + return string.Format("{0}@{1}", localUser.UserName, + ServerName); + return "(Not connected)"; + } + + /// + /// Represents a method that processes objects. + /// + /// The message to be processed. + protected delegate void MessageProcessor(IrcMessage message); + + /// + /// Represents a raw IRC message that is sent/received by . + /// A message contains a prefix (representing the source), a command name (a word or three-digit number), + /// and any number of parameters (up to a maximum of 15). + /// + /// + [DebuggerDisplay("{ToString(), nq}")] + public struct IrcMessage + { + /// + /// The source of the message, which is the object represented by the value of . + /// + public IIrcMessageSource Source; + + /// + /// The message prefix. + /// + public string Prefix; + + /// + /// The name of the command. + /// + public string Command; + + /// + /// A list of the parameters to the message. + /// + public IList Parameters; + + /// + /// Initializes a new instance of the structure. + /// + /// A client object that has sent/will receive the message. + /// The message prefix that represents the source of the message. + /// The command name; either an alphabetic word or 3-digit number. + /// + /// A list of the parameters to the message. Can contain a maximum of 15 items. + /// + public IrcMessage(IrcClient client, string prefix, string command, IList parameters) + { + Prefix = prefix; + Command = command; + Parameters = parameters; + + Source = client.GetSourceFromPrefix(prefix); + } + + /// + /// Returns a string representation of this instance. + /// + /// A string that represents this instance. + public override string ToString() + { + return string.Format("{0} ({1} parameters)", Command, Parameters.Count); + } + } + + #region Protocol Data + + // Internal collection of all known servers. + private Collection servers; + + // True if connection has been registered with server; + protected bool isRegistered; + + // Stores information about local user. + protected IrcLocalUser localUser; + + // Dictionary of protocol features supported by server. + private Dictionary serverSupportedFeatures; + + // Collection of channel modes that apply to users in a channel. + private Collection channelUserModes; + + // Dictionary of nick name prefixes (keys) and their corresponding channel modes. + private Dictionary channelUserModesPrefixes; + + // Builds MOTD (message of the day) string as it is received from server. + protected StringBuilder motdBuilder; + + // Information about the IRC network given by the server. + private IrcNetworkInfo networkInformation; + + // Collection of all currently joined channels. + private Collection channels; + + // Collection of all known users. + private Collection users; + + // List of information about channels, returned by server in response to last LIST message. + private List listedChannels; + + // List of other servers to which server links, returned by server in response to last LINKS message. + private List listedServerLinks; + + // List of statistical entries, returned by server in response to last STATS message. + private List listedStatsEntries; + + #endregion + + #region Proxy Methods + + internal void SetTopic(string channel, string topic = null) + { + SendMessageTopic(channel, topic); + } + + internal void GetChannelModes(IrcChannel channel, string modes = null) + { + SendMessageChannelMode(channel.Name, modes); + } + + internal void SetChannelModes(IrcChannel channel, string modes, IEnumerable modeParameters = null) + { + SendMessageChannelMode(channel.Name, modes, modeParameters); + } + + internal void Invite(IrcChannel channel, string userNickName) + { + SendMessageInvite(channel.Name, userNickName); + } + + internal void Kick(IrcChannel channel, IEnumerable usersNickNames, string comment = null) + { + SendMessageKick(channel.Name, usersNickNames, comment); + } + + internal void Kick(IEnumerable channelUsers, string comment = null) + { + SendMessageKick(channelUsers.Select(cu => Tuple.Create(cu.Channel.Name, cu.User.NickName)), comment); + } + + internal void Join(IEnumerable channels) + { + SendMessageJoin(channels); + } + + internal void Join(IEnumerable> channels) + { + SendMessageJoin(channels); + } + + internal void Leave(IEnumerable channels, string comment = null) + { + SendMessagePart(channels, comment); + } + + internal void SendPrivateMessage(IEnumerable targetsNames, string text) + { + var targetsNamesArray = targetsNames.ToArray(); + var targets = targetsNamesArray.Select(n => GetMessageTarget(n)).ToArray(); + SendMessagePrivateMessage(targetsNamesArray, text); + localUser.HandleMessageSent(targets, text); + } + + internal void SendNotice(IEnumerable targetsNames, string text) + { + var targetsNamesArray = targetsNames.ToArray(); + var targets = targetsNamesArray.Select(n => GetMessageTarget(n)).ToArray(); + SendMessageNotice(targetsNamesArray, text); + localUser.HandleNoticeSent(targets, text); + } + + internal void SetAway(string text) + { + SendMessageAway(text); + } + + internal void UnsetAway() + { + SendMessageAway(); + } + + internal void SetNickName(string nickName) + { + SendMessageNick(nickName); + } + + internal void GetLocalUserModes(IrcLocalUser user) + { + SendMessageUserMode(user.NickName); + } + + internal void SetLocalUserModes(IrcLocalUser user, string modes) + { + SendMessageUserMode(user.NickName, modes); + } + + #endregion + } +} \ No newline at end of file diff --git a/IrcDotNet/IrcClientMessageProcessing.cs b/IrcDotNet/IrcClientMessageProcessing.cs new file mode 100644 index 0000000..050f56f --- /dev/null +++ b/IrcDotNet/IrcClientMessageProcessing.cs @@ -0,0 +1,1214 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Text.RegularExpressions; +using IrcDotNet.Collections; +using IrcDotNet.Properties; + +namespace IrcDotNet +{ + // Defines all message processors for the client. + partial class IrcClient + { + /// + /// Process NICK messages received from the server. + /// + /// The message received from the server. + [MessageProcessor("nick")] + protected internal void ProcessMessageNick(IrcMessage message) + { + var sourceUser = message.Source as IrcUser; + if (sourceUser == null) + throw new ProtocolViolationException(string.Format( + Resources.MessageSourceNotUser, message.Source.Name)); + + // Local or remote user has changed nick name. + Debug.Assert(message.Parameters[0] != null); + sourceUser.NickName = message.Parameters[0]; + } + + /// + /// Process QUIT messages received from the server. + /// + /// The message received from the server. + [MessageProcessor("quit")] + protected internal void ProcessMessageQuit(IrcMessage message) + { + var sourceUser = message.Source as IrcUser; + if (sourceUser == null) + throw new ProtocolViolationException(string.Format( + Resources.MessageSourceNotUser, message.Source.Name)); + + // Remote user has quit server. + Debug.Assert(message.Parameters[0] != null); + sourceUser.HandeQuit(message.Parameters[0]); + + lock (((ICollection) Users).SyncRoot) + users.Remove(sourceUser); + } + + /// + /// Process JOIN messages received from the server. + /// + /// The message received from the server. + [MessageProcessor("join")] + protected internal void ProcessMessageJoin(IrcMessage message) + { + var sourceUser = message.Source as IrcUser; + if (sourceUser == null) + throw new ProtocolViolationException(string.Format( + Resources.MessageSourceNotUser, message.Source.Name)); + + // Local or remote user has joined one or more channels. + Debug.Assert(message.Parameters[0] != null); + var chans = GetChannelsFromList(message.Parameters[0]).ToArray(); + if (sourceUser == localUser) + chans.ForEach(c => localUser.HandleJoinedChannel(c)); + else + chans.ForEach(c => c.HandleUserJoined(new IrcChannelUser(sourceUser))); + } + + /// + /// Process PART messages received from the server. + /// + /// The message received from the server. + [MessageProcessor("part")] + protected internal void ProcessMessagePart(IrcMessage message) + { + var sourceUser = message.Source as IrcUser; + if (sourceUser == null) + throw new ProtocolViolationException(string.Format( + Resources.MessageSourceNotUser, message.Source.Name)); + + // Local or remote user has left one or more channels. + Debug.Assert(message.Parameters[0] != null); + var comment = message.Parameters[1]; + var chans = GetChannelsFromList(message.Parameters[0]).ToArray(); + if (sourceUser == localUser) + chans.ForEach(c => localUser.HandleLeftChannel(c)); + else + chans.ForEach(c => c.HandleUserLeft(sourceUser, comment)); + } + + /// + /// Process MODE messages received from the server. + /// + /// The message received from the server. + [MessageProcessor("mode")] + protected internal void ProcessMessageMode(IrcMessage message) + { + // Check if mode applies to channel or user. + Debug.Assert(message.Parameters[0] != null); + if (IsChannelName(message.Parameters[0])) + { + var channel = GetChannelFromName(message.Parameters[0]); + + // Get channel modes and list of mode parameters from message parameters. + Debug.Assert(message.Parameters[1] != null); + var modesAndParameters = GetModeAndParameters(message.Parameters.Skip(1)); + var source = message.Source as IrcUser; + OnChannelModeChanged(channel, source, modesAndParameters.Item1, modesAndParameters.Item2); + channel.HandleModesChanged(source, modesAndParameters.Item1, + modesAndParameters.Item2); + } + else if (message.Parameters[0] == localUser.NickName) + { + Debug.Assert(message.Parameters[1] != null); + localUser.HandleModesChanged(message.Parameters[1]); + } + else + { + throw new ProtocolViolationException(string.Format(Resources.MessageCannotSetUserMode, + message.Parameters[0])); + } + } + + protected virtual void OnChannelModeChanged(IrcChannel channel, IrcUser source, string newModes, + IEnumerable newModeParameters) + { + } + + /// + /// Process TOPIC messages received from the server. + /// + /// The message received from the server. + [MessageProcessor("topic")] + protected internal void ProcessMessageTopic(IrcMessage message) + { + Debug.Assert(message.Parameters[0] != null); + var channel = GetChannelFromName(message.Parameters[0]); + Debug.Assert(message.Parameters[1] != null); + channel.HandleTopicChanged(message.Source as IrcUser, message.Parameters[1]); + } + + /// + /// Process KICK messages received from the server. + /// + /// The message received from the server. + [MessageProcessor("kick")] + protected internal void ProcessMessageKick(IrcMessage message) + { + Debug.Assert(message.Parameters[0] != null); + var channels = GetChannelsFromList(message.Parameters[0]); + Debug.Assert(message.Parameters[1] != null); + var users = GetUsersFromList(message.Parameters[1]).ToArray(); + var comment = message.Parameters[2]; + + // Handle kick command for each user given in message. + foreach (var channelUser in channels.Zip(users, + (channel, user) => channel.GetChannelUser(user))) + { + if (channelUser.User == localUser) + { + // Local user was kicked from channel. + var channel = channelUser.Channel; + lock (((ICollection) Channels).SyncRoot) + this.channels.Remove(channel); + + channelUser.Channel.HandleUserKicked(channelUser, comment); + localUser.HandleLeftChannel(channel); + + // Local user has left channel. Do not process kicks of remote users. + break; + } + // Remote user was kicked from channel. + channelUser.Channel.HandleUserKicked(channelUser, comment); + } + } + + /// + /// Process INVITE messages received from the server. + /// + /// The message received from the server. + [MessageProcessor("invite")] + protected internal void ProcessMessageInvite(IrcMessage message) + { + Debug.Assert(message.Parameters[0] != null); + var user = GetUserFromNickName(message.Parameters[0]); + Debug.Assert(message.Parameters[1] != null); + var channel = GetChannelFromName(message.Parameters[1]); + + Debug.Assert(message.Source is IrcUser); + if (message.Source is IrcUser) + user.HandleInviteReceived((IrcUser) message.Source, channel); + } + + /// + /// Process PRIVMSG messages received from the server. + /// + /// The message received from the server. + [MessageProcessor("privmsg")] + protected internal void ProcessMessagePrivateMessage(IrcMessage message) + { + // Get list of message targets. + Debug.Assert(message.Parameters[0] != null); + var targets = message.Parameters[0].Split(',').Select(n => GetMessageTarget(n)).ToArray(); + + // Get message text. + Debug.Assert(message.Parameters[1] != null); + var text = message.Parameters[1]; + + // Process message for each given target. + foreach (var curTarget in targets) + { + Debug.Assert(curTarget != null); + var messageHandler = curTarget as IIrcMessageReceiveHandler ?? localUser; + messageHandler.HandleMessageReceived(message.Source, targets, text); + } + } + + /// + /// Process NOTICE messages received from the server. + /// + /// The message received from the server. + [MessageProcessor("notice")] + protected internal void ProcessMessageNotice(IrcMessage message) + { + // Get list of message targets. + Debug.Assert(message.Parameters[0] != null); + var targets = message.Parameters[0].Split(',').Select(n => GetMessageTarget(n)).ToArray(); + + // Get message text. + Debug.Assert(message.Parameters[1] != null); + var text = message.Parameters[1]; + + // Process notice for each given target. + foreach (var curTarget in targets) + { + Debug.Assert(curTarget != null); + var messageHandler = curTarget as IIrcMessageReceiveHandler ?? localUser; + if (messageHandler != null) + messageHandler.HandleNoticeReceived(message.Source, targets, text); + } + } + + /// + /// Process PING messages received from the server. + /// + /// The message received from the server. + [MessageProcessor("ping")] + protected internal void ProcessMessagePing(IrcMessage message) + { + Debug.Assert(message.Parameters[0] != null); + var server = message.Parameters[0]; + var target = message.Parameters[1]; + var ircPingReceivedEventArgs = new IrcPingReceivedEventArgs(server); + try + { + OnPingReceived(ircPingReceivedEventArgs); + } + finally + { + if (ircPingReceivedEventArgs.SendPong) + { + SendMessagePong(server, target); + } + } + } + + /// + /// Process PONG messages received from the server. + /// + /// The message received from the server. + [MessageProcessor("pong")] + protected internal void ProcessMessagePong(IrcMessage message) + { + Debug.Assert(message.Parameters[0] != null); + var server = message.Parameters[0]; + OnPongReceived(new IrcPingOrPongReceivedEventArgs(server)); + } + + /// + /// Process ERROR messages received from the server. + /// + /// The message received from the server. + [MessageProcessor("error")] + protected internal void ProcessMessageError(IrcMessage message) + { + Debug.Assert(message.Parameters[0] != null); + var errorMessage = message.Parameters[0]; + OnErrorMessageReceived(new IrcErrorMessageEventArgs(errorMessage)); + } + + /// + /// Process RPL_WELCOME responses from the server. + /// + /// The message received from the server. + [MessageProcessor("001")] + protected internal virtual void ProcessMessageReplyWelcome(IrcMessage message) + { + Debug.Assert(message.Parameters[0] != null); + + Debug.Assert(message.Parameters[1] != null); + WelcomeMessage = message.Parameters[1]; + + // Extract nick name, user name, and host name from welcome message. Use fallback info if not present. + var nickNameIdMatch = Regex.Match(WelcomeMessage.Split(' ').Last(), regexNickNameId); + localUser.NickName = nickNameIdMatch.Groups["nick"].GetValue() ?? localUser.NickName; + localUser.UserName = nickNameIdMatch.Groups["user"].GetValue() ?? localUser.UserName; + localUser.HostName = nickNameIdMatch.Groups["host"].GetValue() ?? localUser.HostName; + + isRegistered = true; + OnRegistered(new EventArgs()); + } + + /// + /// Process RPL_YOURHOST responses from the server. + /// + /// The message received from the server. + [MessageProcessor("002")] + protected internal void ProcessMessageReplyYourHost(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + YourHostMessage = message.Parameters[1]; + } + + /// + /// Process RPL_CREATED responses from the server. + /// + /// The message received from the server. + [MessageProcessor("003")] + protected internal void ProcessMessageReplyCreated(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + ServerCreatedMessage = message.Parameters[1]; + } + + /// + /// Process RPL_MYINFO responses from the server. + /// + /// The message received from the server. + [MessageProcessor("004")] + protected internal virtual void ProcessMessageReplyMyInfo(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + ServerName = message.Parameters[1]; + Debug.Assert(message.Parameters[2] != null); + ServerVersion = message.Parameters[2]; + Debug.Assert(message.Parameters[3] != null); + ServerAvailableUserModes = message.Parameters[3]; + Debug.Assert(message.Parameters[4] != null); + ServerAvailableChannelModes = message.Parameters[4]; + + // All initial information about client has now been received. + OnClientInfoReceived(new EventArgs()); + } + + /// + /// Process RPL_BOUNCE and RPL_ISUPPORT responses from the server. + /// + /// The message received from the server. + [MessageProcessor("005")] + protected internal void ProcessMessageReplyBounceOrISupport(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + // Check if message is RPL_BOUNCE or RPL_ISUPPORT. + Debug.Assert(message.Parameters[1] != null); + if (message.Parameters[1].StartsWith("Try server")) + { + // Message is RPL_BOUNCE. + // Current server is redirecting client to new server. + var textParts = message.Parameters[0].Split(' ', ','); + var serverAddress = textParts[2]; + var serverPort = int.Parse(textParts[6]); + + OnServerBounce(new IrcServerInfoEventArgs(serverAddress, serverPort)); + } + else + { + // Message is RPL_ISUPPORT. + // Add key/value pairs to dictionary of supported server features. + for (var i = 1; i < message.Parameters.Count - 1; i++) + { + if (message.Parameters[i + 1] == null) + break; + + var paramParts = message.Parameters[i].Split('='); + var paramName = paramParts[0]; + var paramValue = paramParts.Length == 1 ? null : paramParts[1]; + HandleISupportParameter(paramName, paramValue); + serverSupportedFeatures.Set(paramName, paramValue); + } + + OnServerSupportedFeaturesReceived(new EventArgs()); + } + } + + /// + /// Process RPL_STATSLINKINFO responses from the server. + /// + /// The message received from the server. + [MessageProcessor("211")] + protected internal void ProcessMessageStatsLinkInfo(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + HandleStatsEntryReceived((int) IrcServerStatisticalEntryCommonType.Connection, message); + } + + /// + /// Process RPL_STATSCOMMANDS responses from the server. + /// + /// The message received from the server. + [MessageProcessor("212")] + protected internal void ProcessMessageStatsCommands(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + HandleStatsEntryReceived((int) IrcServerStatisticalEntryCommonType.Command, message); + } + + /// + /// Process RPL_STATSCLINE responses from the server. + /// + /// The message received from the server. + [MessageProcessor("213")] + protected internal void ProcessMessageStatsCLine(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + HandleStatsEntryReceived((int) IrcServerStatisticalEntryCommonType.AllowedServerConnect, message); + } + + /// + /// Process RPL_STATSNLINE responses from the server. + /// + /// The message received from the server. + [MessageProcessor("214")] + protected internal void ProcessMessageStatsNLine(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + HandleStatsEntryReceived((int) IrcServerStatisticalEntryCommonType.AllowedServerAccept, message); + } + + /// + /// Process RPL_STATSILINE responses from the server. + /// + /// The message received from the server. + [MessageProcessor("215")] + protected internal void ProcessMessageStatsILine(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + HandleStatsEntryReceived((int) IrcServerStatisticalEntryCommonType.AllowedClient, message); + } + + /// + /// Process RPL_STATSKLINE responses from the server. + /// + /// The message received from the server. + [MessageProcessor("216")] + protected internal void ProcessMessageStatsKLine(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + HandleStatsEntryReceived((int) IrcServerStatisticalEntryCommonType.BannedClient, message); + } + + /// + /// Process RPL_STATSYLINE responses from the server. + /// + /// The message received from the server. + [MessageProcessor("218")] + protected internal void ProcessMessageStatsYLine(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + HandleStatsEntryReceived((int) IrcServerStatisticalEntryCommonType.ConnectionClass, message); + } + + /// + /// Process RPL_ENDOFSTATS responses from the server. + /// + /// The message received from the server. + [MessageProcessor("219")] + protected internal void ProcessMessageEndOfStats(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + OnServerStatsReceived(new IrcServerStatsReceivedEventArgs(listedStatsEntries)); + listedStatsEntries = new List(); + } + + /// + /// Process RPL_STATSLLINE responses from the server. + /// + /// The message received from the server. + [MessageProcessor("241")] + protected internal void ProcessMessageStatsLLine(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + HandleStatsEntryReceived((int) IrcServerStatisticalEntryCommonType.LeafDepth, message); + } + + /// + /// Process RPL_STATSUPTIME responses from the server. + /// + /// The message received from the server. + [MessageProcessor("242")] + protected internal void ProcessMessageStatsUpTime(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + HandleStatsEntryReceived((int) IrcServerStatisticalEntryCommonType.Uptime, message); + } + + /// + /// Process RPL_STATSOLINE responses from the server. + /// + /// The message received from the server. + [MessageProcessor("243")] + protected internal void ProcessMessageStatsOLine(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + HandleStatsEntryReceived((int) IrcServerStatisticalEntryCommonType.AllowedOperator, message); + } + + /// + /// Process RPL_STATSHLINE responses from the server. + /// + /// The message received from the server. + [MessageProcessor("244")] + protected internal void ProcessMessageStatsHLine(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + HandleStatsEntryReceived((int) IrcServerStatisticalEntryCommonType.HubServer, message); + } + + /// + /// Process RPL_LUSERCLIENT responses from the server. + /// + /// The message received from the server. + [MessageProcessor("251")] + protected internal void ProcessMessageLUserClient(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + // Extract network information from text. + Debug.Assert(message.Parameters[1] != null); + var info = message.Parameters[1]; + var infoParts = info.Split(' '); + Debug.Assert(infoParts.Length == 10); + networkInformation.VisibleUsersCount = int.Parse(infoParts[2]); + networkInformation.InvisibleUsersCount = int.Parse(infoParts[5]); + networkInformation.ServersCount = int.Parse(infoParts[8]); + + OnNetworkInformationReceived(new IrcCommentEventArgs(info)); + } + + /// + /// Process RPL_LUSEROP responses from the server. + /// + /// The message received from the server. + [MessageProcessor("252")] + protected internal void ProcessMessageLUserOp(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + // Extract network information from text. + Debug.Assert(message.Parameters[1] != null); + var info = message.Parameters[1]; + networkInformation.OperatorsCount = int.Parse(info); + + OnNetworkInformationReceived(new IrcCommentEventArgs(info)); + } + + /// + /// Process RPL_LUSERUNKNOWN responses from the server. + /// + /// The message received from the server. + [MessageProcessor("253")] + protected internal void ProcessMessageLUserUnknown(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + // Extract network information from text. + Debug.Assert(message.Parameters[1] != null); + var info = message.Parameters[1]; + networkInformation.UnknownConnectionsCount = int.Parse(info); + + OnNetworkInformationReceived(new IrcCommentEventArgs(info)); + } + + /// + /// Process RPL_LUSERCHANNELS responses from the server. + /// + /// The message received from the server. + [MessageProcessor("254")] + protected internal void ProcessMessageLUserChannels(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + // Extract network information from text. + Debug.Assert(message.Parameters[1] != null); + var info = message.Parameters[1]; + networkInformation.ChannelsCount = int.Parse(info); + + OnNetworkInformationReceived(new IrcCommentEventArgs(info)); + } + + /// + /// Process RPL_LUSERME responses from the server. + /// + /// The message received from the server. + [MessageProcessor("255")] + protected internal void ProcessMessageLUserMe(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + // Extract network information from text. + Debug.Assert(message.Parameters[1] != null); + var info = message.Parameters[1]; + var infoParts = info.Split(' '); + for (var i = 0; i < infoParts.Length; i++) + { + switch (infoParts[i].ToLowerInvariant()) + { + case "user": + case "users": + networkInformation.ServerClientsCount = int.Parse(infoParts[i - 1]); + break; + case "server": + case "servers": + networkInformation.ServerServersCount = int.Parse(infoParts[i - 1]); + break; + case "service": + case "services": + networkInformation.ServerClientsCount = int.Parse(infoParts[i - 1]); + break; + } + } + + OnNetworkInformationReceived(new IrcCommentEventArgs(info)); + } + + /// + /// Process RPL_AWAY responses from the server. + /// + /// The message received from the server. + [MessageProcessor("301")] + protected internal void ProcessMessageReplyAway(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + var user = GetUserFromNickName(message.Parameters[1]); + Debug.Assert(message.Parameters[2] != null); + user.AwayMessage = message.Parameters[2]; + user.IsAway = true; + } + + /// + /// Process RPL_ISON responses from the server. + /// + /// The message received from the server. + [MessageProcessor("303")] + protected internal void ProcessMessageReplyIsOn(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + // Set each user listed in reply as online. + Debug.Assert(message.Parameters[1] != null); + var onlineUsers = message.Parameters[1].Split(' ').Select(n => GetUserFromNickName(n)); + onlineUsers.ForEach(u => u.IsOnline = true); + } + + /// + /// Process RPL_UNAWAY responses from the server. + /// + /// The message received from the server. + [MessageProcessor("305")] + protected internal void ProcessMessageReplyUnAway(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + localUser.IsAway = false; + } + + /// + /// Process RPL_NOWAWAY responses from the server. + /// + /// The message received from the server. + [MessageProcessor("306")] + protected internal void ProcessMessageReplyNowAway(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + localUser.IsAway = true; + } + + /// + /// Process RPL_WHOISUSER responses from the server. + /// + /// The message received from the server. + [MessageProcessor("311")] + protected internal void ProcessMessageReplyWhoIsUser(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + var user = GetUserFromNickName(message.Parameters[1]); + Debug.Assert(message.Parameters[2] != null); + user.UserName = message.Parameters[2]; + Debug.Assert(message.Parameters[3] != null); + user.HostName = message.Parameters[3]; + Debug.Assert(message.Parameters[4] != null); + Debug.Assert(message.Parameters[5] != null); + user.RealName = message.Parameters[5]; + } + + /// + /// Process RPL_WHOISSERVER responses from the server. + /// + /// The message received from the server. + [MessageProcessor("312")] + protected internal void ProcessMessageReplyWhoIsServer(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + var user = GetUserFromNickName(message.Parameters[1]); + Debug.Assert(message.Parameters[2] != null); + user.ServerName = message.Parameters[2]; + Debug.Assert(message.Parameters[3] != null); + user.ServerInfo = message.Parameters[3]; + } + + /// + /// Process RPL_WHOISOPERATOR responses from the server. + /// + /// The message received from the server. + [MessageProcessor("313")] + protected internal void ProcessMessageReplyWhoIsOperator(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + var user = GetUserFromNickName(message.Parameters[1]); + user.IsOperator = true; + } + + /// + /// Process RPL_WHOWASUSER responses from the server. + /// + /// The message received from the server. + [MessageProcessor("314")] + protected internal void ProcessMessageReplyWhoWasUser(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + var user = GetUserFromNickName(message.Parameters[1], false); + Debug.Assert(message.Parameters[2] != null); + user.UserName = message.Parameters[2]; + Debug.Assert(message.Parameters[3] != null); + user.HostName = message.Parameters[3]; + Debug.Assert(message.Parameters[4] != null); + Debug.Assert(message.Parameters[5] != null); + user.RealName = message.Parameters[5]; + } + + /// + /// Process RPL_ENDOFWHO responses from the server. + /// + /// The message received from the server. + [MessageProcessor("315")] + protected internal void ProcessMessageReplyEndOfWho(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + var mask = message.Parameters[1]; + OnWhoReplyReceived(new IrcNameEventArgs(mask)); + } + + /// + /// Process RPL_WHOISIDLE responses from the server. + /// + /// The message received from the server. + [MessageProcessor("317")] + protected internal void ProcessMessageReplyWhoIsIdle(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + var user = GetUserFromNickName(message.Parameters[1]); + Debug.Assert(message.Parameters[2] != null); + user.IdleDuration = TimeSpan.FromSeconds(int.Parse(message.Parameters[2])); + } + + /// + /// Process 318 responses from the server. + /// + /// The message received from the server. + [MessageProcessor("318")] + protected internal void ProcessMessageReplyEndOfWhoIs(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + var user = GetUserFromNickName(message.Parameters[1]); + OnWhoIsReplyReceived(new IrcUserEventArgs(user, null)); + } + + /// + /// Process RPL_WHOISCHANNELS responses from the server. + /// + /// The message received from the server. + [MessageProcessor("319")] + protected internal void ProcessMessageReplyWhoIsChannels(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + var user = GetUserFromNickName(message.Parameters[1]); + + Debug.Assert(message.Parameters[2] != null); + foreach (var channelId in message.Parameters[2].Split(' ')) + { + if (channelId.Length == 0) + return; + + // Find user by nick name and add it to collection of channel users. + var channelNameAndUserMode = GetUserModeAndNickName(channelId); + var channel = GetChannelFromName(channelNameAndUserMode.Item1); + if (channel.GetChannelUser(user) == null) + channel.HandleUserJoined(new IrcChannelUser(user, channelNameAndUserMode.Item2)); + } + } + + /// + /// Process RPL_LIST responses from the server. + /// + /// The message received from the server. + [MessageProcessor("322")] + protected internal void ProcessMessageReplyList(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + var channelName = message.Parameters[1]; + Debug.Assert(message.Parameters[2] != null); + var visibleUsersCount = int.Parse(message.Parameters[2]); + Debug.Assert(message.Parameters[3] != null); + var topic = message.Parameters[3]; + + // Add channel information to temporary list. + listedChannels.Add(new IrcChannelInfo(channelName, visibleUsersCount, topic)); + } + + /// + /// Process RPL_LISTEND responses from the server. + /// + /// The message received from the server. + [MessageProcessor("323")] + protected internal void ProcessMessageReplyListEnd(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + OnChannelListReceived(new IrcChannelListReceivedEventArgs(listedChannels)); + listedChannels = new List(); + } + + /// + /// Process RPL_NOTOPIC responses from the server. + /// + /// The message received from the server. + [MessageProcessor("331")] + protected internal void ProcessMessageReplyNoTopic(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + var channel = GetChannelFromName(message.Parameters[1]); + channel.HandleTopicChanged(null, null); + } + + /// + /// Process RPL_TOPIC responses from the server. + /// + /// The message received from the server. + [MessageProcessor("332")] + protected internal void ProcessMessageReplyTopic(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + var channel = GetChannelFromName(message.Parameters[1]); + Debug.Assert(message.Parameters[2] != null); + channel.HandleTopicChanged(null, message.Parameters[2]); + } + + /// + /// Process RPL_INVITING responses from the server. + /// + /// The message received from the server. + [MessageProcessor("341")] + protected internal void ProcessMessageReplyInviting(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + var invitedUser = GetUserFromNickName(message.Parameters[1]); + Debug.Assert(message.Parameters[2] != null); + var channel = GetChannelFromName(message.Parameters[2]); + + channel.HandleUserInvited(invitedUser); + } + + /// + /// Process RPL_VERSION responses from the server. + /// + /// The message received from the server. + [MessageProcessor("351")] + protected internal void ProcessMessageReplyVersion(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + var versionInfo = message.Parameters[1]; + var versionSplitIndex = versionInfo.LastIndexOf('.'); + var version = versionInfo.Substring(0, versionSplitIndex); + var debugLevel = versionInfo.Substring(versionSplitIndex + 1); + Debug.Assert(message.Parameters[2] != null); + var server = message.Parameters[2]; + Debug.Assert(message.Parameters[3] != null); + var comments = message.Parameters[3]; + + OnServerVersionInfoReceived(new IrcServerVersionInfoEventArgs(version, debugLevel, server, comments)); + } + + /// + /// Process RPL_WHOREPLY responses from the server. + /// + /// The message received from the server. + [MessageProcessor("352")] + protected internal void ProcessMessageReplyWhoReply(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + var channel = message.Parameters[1] == "*" ? null : GetChannelFromName(message.Parameters[1]); + + Debug.Assert(message.Parameters[5] != null); + var user = GetUserFromNickName(message.Parameters[5]); + + Debug.Assert(message.Parameters[2] != null); + var userName = message.Parameters[2]; + Debug.Assert(message.Parameters[3] != null); + user.HostName = message.Parameters[3]; + Debug.Assert(message.Parameters[4] != null); + user.ServerName = message.Parameters[4]; + + Debug.Assert(message.Parameters[6] != null); + var userModeFlags = message.Parameters[6]; + Debug.Assert(userModeFlags.Length > 0); + if (userModeFlags.Contains('H')) + user.IsAway = false; + else if (userModeFlags.Contains('G')) + user.IsAway = true; + user.IsOperator = userModeFlags.Contains('*'); + if (channel != null) + { + // Add user to channel if it does not already exist in it. + var channelUser = channel.GetChannelUser(user); + if (channelUser == null) + { + channelUser = new IrcChannelUser(user); + channel.HandleUserJoined(channelUser); + } + + // Set modes on user corresponding to given mode flags (prefix characters). + foreach (var c in userModeFlags) + { + char mode; + if (channelUserModesPrefixes.TryGetValue(c, out mode)) + channelUser.HandleModeChanged(true, mode); + else + break; + } + } + + Debug.Assert(message.Parameters[7] != null); + var lastParamParts = message.Parameters[7].SplitIntoPair(" "); + user.HopCount = int.Parse(lastParamParts.Item1); + if (lastParamParts.Item2 != null) + user.RealName = lastParamParts.Item2; + } + + /// + /// Process RPL_NAMEREPLY responses from the server. + /// + /// The message received from the server. + [MessageProcessor("353")] + protected internal void ProcessMessageReplyNameReply(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[2] != null); + var channel = GetChannelFromName(message.Parameters[2]); + if (channel != null) + { + Debug.Assert(message.Parameters[1] != null); + Debug.Assert(message.Parameters[1].Length == 1); + channel.HandleTypeChanged(GetChannelType(message.Parameters[1][0])); + + Debug.Assert(message.Parameters[3] != null); + foreach (var userId in message.Parameters[3].Split(' ')) + { + if (userId.Length == 0) + return; + + // Find user by nick name and add it to collection of channel users. + var userNickNameAndMode = GetUserModeAndNickName(userId); + var user = GetUserFromNickName(userNickNameAndMode.Item1); + channel.HandleUserNameReply(new IrcChannelUser(user, userNickNameAndMode.Item2)); + } + } + } + + /// + /// Process RPL_LINKS responses from the server. + /// + /// The message received from the server. + [MessageProcessor("364")] + protected internal void ProcessMessageReplyLinks(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + var hostName = message.Parameters[1]; + Debug.Assert(message.Parameters[2] != null); + var clientServerHostName = message.Parameters[2]; + Debug.Assert(ServerName == null || clientServerHostName == ServerName); + Debug.Assert(message.Parameters[3] != null); + var infoParts = message.Parameters[3].SplitIntoPair(" "); + Debug.Assert(infoParts.Item2 != null); + var hopCount = int.Parse(infoParts.Item1); + var info = infoParts.Item2; + + // Add server information to temporary list. + listedServerLinks.Add(new IrcServerInfo(hostName, hopCount, info)); + } + + /// + /// Process RPL_ENDOFLINKS responses from the server. + /// + /// The message received from the server. + [MessageProcessor("365")] + protected internal void ProcessMessageReplyEndOfLinks(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + var mask = message.Parameters[1]; + + OnServerLinksListReceived(new IrcServerLinksListReceivedEventArgs(listedServerLinks)); + listedServerLinks = new List(); + } + + /// + /// Process RPL_ENDOFNAMES responses from the server. + /// + /// The message received from the server. + [MessageProcessor("366")] + protected internal void ProcessMessageReplyEndOfNames(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + var channel = GetChannelFromName(message.Parameters[1]); + channel.HandleUsersListReceived(); + } + + /// + /// Process RPL_ENDOFWHOWAS responses from the server. + /// + /// The message received from the server. + [MessageProcessor("369")] + protected internal void ProcessMessageReplyEndOfWhoWas(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + var user = GetUserFromNickName(message.Parameters[1], false); + OnWhoWasReplyReceived(new IrcUserEventArgs(user, null)); + } + + /// + /// Process RPL_MOTD responses from the server. + /// + /// The message received from the server. + [MessageProcessor("372")] + protected internal void ProcessMessageReplyMotd(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + motdBuilder.AppendLine(message.Parameters[1]); + } + + /// + /// Process RPL_MOTDSTART responses from the server. + /// + /// The message received from the server. + [MessageProcessor("375")] + protected internal virtual void ProcessMessageReplyMotdStart(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + motdBuilder.Clear(); + motdBuilder.AppendLine(message.Parameters[1]); + } + + /// + /// Process RPL_ENDOFMOTD responses from the server. + /// + /// The message received from the server. + [MessageProcessor("376")] + protected internal void ProcessMessageReplyMotdEnd(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + Debug.Assert(message.Parameters[1] != null); + motdBuilder.AppendLine(message.Parameters[1]); + + OnMotdReceived(new EventArgs()); + } + + /// + /// Process RPL_YOURESERVICE responses from the server. + /// + /// The message received from the server. + [MessageProcessor("383")] + protected internal void ProcessMessageReplyYouAreService(IrcMessage message) + { + Debug.Assert(message.Parameters[0] != null); + + Debug.Assert(message.Parameters[1] != null); + localUser.NickName = message.Parameters[1].Split(' ')[3]; + + isRegistered = true; + OnRegistered(new EventArgs()); + } + + /// + /// Process RPL_TIME responses from the server. + /// + /// The message received from the server. + [MessageProcessor("391")] + protected internal void ProcessMessageReplyTime(IrcMessage message) + { + Debug.Assert(message.Parameters[0] != null); + + Debug.Assert(message.Parameters[1] != null); + var server = message.Parameters[1]; + Debug.Assert(message.Parameters[2] != null); + var dateTime = message.Parameters[2]; + + OnServerTimeReceived(new IrcServerTimeEventArgs(server, dateTime)); + } + + /// + /// Process numeric error (from 400 to 599) responses from the server. + /// + /// The message received from the server. + [MessageProcessor("400-599")] + protected internal void ProcessMessageNumericError(IrcMessage message) + { + Debug.Assert(message.Parameters[0] != null); + + // Extract error parameters and message text from message parameters. + Debug.Assert(message.Parameters[1] != null); + var errorParameters = new List(); + string errorMessage = null; + for (var i = 1; i < message.Parameters.Count; i++) + { + if (i + 1 == message.Parameters.Count || message.Parameters[i + 1] == null) + { + errorMessage = message.Parameters[i]; + break; + } + errorParameters.Add(message.Parameters[i]); + } + + Debug.Assert(errorMessage != null); + OnProtocolError(new IrcProtocolErrorEventArgs(int.Parse(message.Command), errorParameters, errorMessage)); + } + } +} \ No newline at end of file diff --git a/IrcDotNet/IrcClientMessageSending.cs b/IrcDotNet/IrcClientMessageSending.cs new file mode 100644 index 0000000..896f1eb --- /dev/null +++ b/IrcDotNet/IrcClientMessageSending.cs @@ -0,0 +1,590 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using IrcDotNet.Properties; + +namespace IrcDotNet +{ + // Defines all message senders for the client. + partial class IrcClient + { + private void EnsureChannelName(string c) + { + // NOTE: we are missing the Control G (Asci 7) restriction + if (!IsChannelName(c) || c.Length > 50 || c.IndexOfAny(new[] {' ', ',', ':'}) != -1) + { + var t = new ArgumentException(string.Format("collection contains an invalid Channelname ({0})!", c)); + + t.Data["ChannelNameRequirements"] = "http://www.irchelp.org/irchelp/rfc/rfc2811.txt"; + throw t; + } + } + + /// + /// Sends the password for registering the connection. + /// This message must only be sent before the actual registration, which is done by + /// (for normal users) or (for services). + /// + /// The connection password. + protected void SendMessagePassword(string password) + { + WriteMessage(null, "pass", password); + } + + /// + /// Sends the nick name of the local user to the server. This command may be used either for intitially setting + /// the nick name or changing it at any point. + /// + /// The nick name to set. + protected void SendMessageNick(string nickName) + { + WriteMessage(null, "nick", nickName); + } + + /// + /// Sends a request to register the client as a user on the server. + /// + /// The user name of the user. + /// The initial mode of the user. + /// The real name of the user. + protected void SendMessageUser(string userName, int userMode, string realName) + { + WriteMessage(null, "user", userName, userMode.ToString(), "*", realName); + } + + /// + /// Sends a request to register the client as a service on the server. + /// + /// The nick name of the service. + /// + /// A wildcard expression for matching against server names, which determines where + /// the service is visible. + /// + /// A description of the service. + protected void SendMessageService(string nickName, string distribution, string description = "") + { + WriteMessage(null, "service", nickName, distribution, "0", "0", description); + } + + /// + /// Sends a request for server operator privileges. + /// + /// The user name with which to register. + /// The password with which to register. + protected void SendMessageOper(string userName, string password) + { + WriteMessage(null, "oper", userName, password); + } + + /// + /// Sends an update or request for the current modes of the specified user. + /// + /// The nick name of the user whose modes to update/request. + /// The mode string that indicates the user modes to change. + protected void SendMessageUserMode(string nickName, string modes = null) + { + WriteMessage(null, "mode", nickName, modes); + } + + /// + /// Sends a notification to the server indicating that the client is quitting the network. + /// + /// The comment to send the server, or for none. + protected void SendMessageQuit(string comment = null) + { + WriteMessage(null, "quit", comment); + } + + /// + /// Sends a request to disconnect the specified server from the network. + /// This command is only available to oeprators. + /// + /// The name of the server to disconnected from the network. + /// The comment to send the server. + protected void SendMessageSquit(string targetServer, string comment) + { + WriteMessage(null, "squit", targetServer, comment); + } + + /// + /// Sends a request to leave all channels in which the user is currently present. + /// + protected void SendMessageLeaveAll() + { + WriteMessage(null, "join", "0"); + } + + /// + /// A collection of 2-tuples of the names and keys of the channels to join. + protected void SendMessageJoin(IEnumerable> channels) + { + var secureChannels = channels.Select(c => + { + EnsureChannelName(c.Item1); + return c; + }).ToList(); + WriteMessage(null, "join", string.Join(",", secureChannels.Select(c => c.Item1)), + string.Join(",", secureChannels.Select(c => c.Item2))); + } + + /// + /// Sends a request to join the specified channels. + /// + /// A collection of the names of the channels to join. + protected void SendMessageJoin(IEnumerable channels) + { + var secureChannels = channels.Select(c => + { + EnsureChannelName(c); + return c; + }).ToList(); + WriteMessage(null, "join", string.Join(",", secureChannels)); + } + + /// + /// Sends a request to leave the specified channels. + /// + /// A collection of the names of the channels to leave. + /// The comment to send the server, or for none. + protected void SendMessagePart(IEnumerable channels, string comment = null) + { + WriteMessage(null, "part", string.Join(",", channels), comment); + } + + /// + /// Sends an update for the modes of the specified channel. + /// + /// The channel whose modes to update. + /// The mode string that indicates the channel modes to change. + /// A collection of parameters to the specified . + protected void SendMessageChannelMode(string channel, string modes = null, + IEnumerable modeParameters = null) + { + string modeParametersList = null; + if (modeParameters != null) + { + var modeParametersArray = modeParameters.ToArray(); + if (modeParametersArray.Length > 3) + throw new ArgumentException(Resources.MessageTooManyModeParameters); + modeParametersList = string.Join(",", modeParametersArray); + } + WriteMessage(null, "mode", channel, modes, modeParametersList); + } + + /// + /// Sends an update or request for the topic of the specified channel. + /// + /// The name of the channel whose topic to change. + /// The new topic to set, or to request the current topic. + protected void SendMessageTopic(string channel, string topic = null) + { + WriteMessage(null, "topic", channel, topic); + } + + /// + /// Sends a request to list all names visible to the client. + /// + /// + /// A collection of the names of channels for which to list users, or + /// for all channels. + /// + /// + /// The name of the server to which to forward the message, or + /// for the current server. + /// + protected void SendMessageNames(IEnumerable channels = null, string targetServer = null) + { + WriteMessage(null, "names", channels == null ? null : string.Join(",", channels), targetServer); + } + + /// + /// Sends a request to list channels and their topics. + /// + /// + /// A collection of the names of channels to list, or for all + /// channels. + /// + /// + /// The name of the server to which to forward the message, or + /// for the current server. + /// + protected void SendMessageList(IEnumerable channels = null, string targetServer = null) + { + WriteMessage(null, "list", channels == null ? null : string.Join(",", channels), targetServer); + } + + /// + /// Sends a request to invite the specified user to the specified channel. + /// + /// The name of the channel to which to invite the user. + /// The nick name of the user to invite. + protected void SendMessageInvite(string channel, string nickName) + { + WriteMessage(null, "invite", nickName, channel); + } + + /// + /// The name of the channel from which to kick the users. + /// A collection of the nick names of the users to kick from the channel. + protected void SendMessageKick(string channel, IEnumerable nickNames, string comment = null) + { + WriteMessage(null, "kick", channel, string.Join(",", nickNames), comment); + } + + /// + /// Sends a request to kick the specifier users from the specified channel. + /// + /// + /// A collection of 2-tuples of channel names and the nick names of the users to + /// kick from the channel. + /// + /// The comment to send the server, or for none. + protected void SendMessageKick(IEnumerable> channelsUsers, string comment = null) + { + WriteMessage(null, "kick", string.Join(",", channelsUsers.Select(user => user.Item1)), + string.Join(",", channelsUsers.Select(user => user.Item2)), comment); + } + + /// + /// Sends a private message to the specified targets. + /// + /// A collection of the targets to which to send the message. + /// The text of the message to send. + protected void SendMessagePrivateMessage(IEnumerable targets, string text) + { + var targetsArray = targets.ToArray(); + foreach (var target in targetsArray) + { + if (target.Contains(",")) + throw new ArgumentException(Resources.MessageInvalidTargetName, "arguments"); + } + WriteMessage(null, "privmsg", string.Join(",", targetsArray), text); + } + + /// + /// Sends a notice to the specified targets. + /// + /// A collection of the targets to which to send the message. + /// The text of the message to send. + protected void SendMessageNotice(IEnumerable targets, string text) + { + var targetsArray = targets.ToArray(); + foreach (var target in targetsArray) + { + if (target.Contains(",")) + throw new ArgumentException(Resources.MessageInvalidTargetName, "arguments"); + } + WriteMessage(null, "notice", string.Join(",", targetsArray), text); + } + + /// + /// Sends a request to receive the Message of the Day (MOTD) from the server. + /// + /// + /// The name of the server to which to forward the message, or for + /// the current server. + /// + protected void SendMessageMotd(string targetServer = null) + { + WriteMessage(null, "motd", targetServer); + } + + /// + /// Sends a request to get statistics about the size of the IRC network. + /// + /// + /// A wildcard expression for matching against the names of servers, or + /// to match the entire network. + /// + /// + /// The name of the server to which to forward the message, or + /// for the current server. + /// + protected void SendMessageLUsers(string serverMask = null, string targetServer = null) + { + WriteMessage(null, "lusers", serverMask, targetServer); + } + + /// + /// Sends a request for the version of the server program. + /// + /// + /// The name of the server to which to forward the message, or + /// for the current server. + /// + protected void SendMessageVersion(string targetServer = null) + { + WriteMessage(null, "version", targetServer); + } + + /// + /// Sends a request to query statistics for the server. + /// + /// The query to send the server. + /// + /// The name of the server to which to forward the message, or + /// for the current server. + /// + protected void SendMessageStats(string query = null, string targetServer = null) + { + WriteMessage(null, "stats", query, targetServer); + } + + /// + /// Sends a request to list all other servers linked to the server. + /// + /// A wildcard expression for matching the names of servers to list. + /// + /// The name of the server to which to forward the message, or + /// for the current server. + /// + protected void SendMessageLinks(string serverMask = null, string targetServer = null) + { + WriteMessage(null, "links", targetServer, serverMask); + } + + /// + /// Sends a request to query the local time on the server. + /// + /// + /// The name of the server to which to forward the message, or + /// for the current server. + /// + protected void SendMessageTime(string targetServer = null) + { + WriteMessage(null, "time", targetServer); + } + + /// + /// Sends a request for the server to try to connect to another server. + /// + /// The host name of the other server to which the server should connect. + /// The port on the other server to which the server should connect. + /// + /// The name of the server to which to forward the message, or + /// for the current server. + /// + protected void SendMessageConnect(string hostName, int port, string targetServer = null) + { + WriteMessage(null, "connect", hostName, port.ToString(), targetServer); + } + + /// + /// Sends a query to trace the route to the server. + /// + /// + /// The name of the server to which to forward the message, or + /// for the current server. + /// + protected void SendMessageTrace(string targetServer = null) + { + WriteMessage(null, "trace", targetServer); + } + + /// + /// Sends a request for information about the administrator of the server. + /// + /// + /// The name of the server to which to forward the message, or + /// for the current server. + /// + protected void SendMessageAdmin(string targetServer = null) + { + WriteMessage(null, "admin", targetServer); + } + + /// + /// Sends a request for general information about the server program. + /// + /// + /// The name of the server to which to forward the message, or + /// for the current server. + /// + protected void SendMessageInfo(string targetServer = null) + { + WriteMessage(null, "info", targetServer); + } + + /// + /// Sends a request to list services currently connected to the netwrok/ + /// + /// A wildcard expression for matching against the names of services. + /// The type of services to list. + protected void SendMessageServlist(string mask = null, string type = null) + { + WriteMessage(null, "servlist", mask, type); + } + + /// + /// Sends a query message to a service. + /// + /// The name of the service. + /// The text of the message to send. + protected void SendMessageSquery(string serviceName, string text) + { + WriteMessage(null, "squery", serviceName, text); + } + + /// + /// Sends a request to perform a Who query on users. + /// + /// + /// A wildcard expression for matching against channel names; or if none can be found, + /// host names, server names, real names, and nick names of users. If the value is , + /// all users are matched. + /// + /// + /// to match only server operators; + /// to match all users. + /// + protected void SendMessageWho(string mask = null, bool onlyOperators = false) + { + WriteMessage(null, "who", mask, onlyOperators ? "o" : null); + } + + /// + /// Sends a request to perform a WhoIs query on users. + /// + /// + /// A collection of wildcard expressions for matching against the nick names of + /// users. + /// + /// + /// The name of the server to which to forward the message, or + /// for the current server. + /// + protected void SendMessageWhoIs(IEnumerable nickNameMasks, string targetServer = null) + { + WriteMessage(null, "whois", targetServer, string.Join(",", nickNameMasks)); + } + + /// + /// Sends a request to perform a WhoWas query on users. + /// + /// + /// A collection of wildcard expressions for matching against the nick names of + /// users. + /// + /// The maximum number of (most recent) entries to return. + /// + /// The name of the server to which to forward the message, or + /// for the current server. + /// + protected void SendMessageWhoWas(IEnumerable nickNames, int entriesCount = -1, + string targetServer = null) + { + WriteMessage(null, "whowas", string.Join(",", nickNames), entriesCount.ToString(), targetServer); + } + + /// + /// Sends a request to disconnect the specified user from the server. + /// + /// The nick name of the user to disconnect. + /// The comment to send the server. + protected void SendMessageKill(string nickName, string comment) + { + WriteMessage(null, "kill", nickName, comment); + } + + /// + /// Sends a ping request to the server. + /// + /// The name of the server to which to send the request. + /// + /// The name of the server to which to forward the message, or + /// for the current server. + /// + protected void SendMessagePing(string server, string targetServer = null) + { + WriteMessage(null, "ping", server, targetServer); + } + + /// + /// Sends a pong response (to a ping) to the server. + /// + /// The name of the server to which to send the response. + /// + /// The name of the server to which to forward the message, or + /// for the current server. + /// + protected void SendMessagePong(string server, string targetServer = null) + { + WriteMessage(null, "pong", server, targetServer); + } + + /// + /// Sends an update to the server indicating that the local user is away. + /// + /// + /// The text of the away message. The away message is sent to any user that tries to contact + /// the local user while it is away. + /// + protected void SendMessageAway(string text = null) + { + WriteMessage(null, "away", text); + } + + /// + /// Sends a request to the server telling it to reprocess its configuration settings. + /// + protected void SendMessageRehash() + { + WriteMessage(null, "rehash"); + } + + /// + /// Sends a request to the server telling it to shut down. + /// + protected void SendMessageDie() + { + WriteMessage(null, "die"); + } + + /// + /// Sends a message to the server telling it to restart. + /// + protected void SendMessageRestart() + { + WriteMessage(null, "restart"); + } + + /// + /// Sends a request to return a list of information about all users currently registered on the server. + /// + /// + /// The name of the server to which to forward the message, or + /// for the current server. + /// + protected void SendMessageUsers(string targetServer = null) + { + WriteMessage(null, "users", targetServer); + } + + /// + /// Sends a message to all connected users that have the 'w' mode set. + /// + /// The text of the message to send. + protected void SendMessageWallops(string text) + { + WriteMessage(null, "wallops", text); + } + + /// + /// Sends a request to return the host names of the specified users. + /// + /// A collection of the nick names of the users to query. + protected void SendMessageUserHost(IEnumerable nickNames) + { + WriteMessage(null, "userhost", nickNames); + } + + /// + /// Sends a request to check whether the specified users are currently online. + /// + /// A collection of the nick names of the users to query. + protected void SendMessageIsOn(IEnumerable nickNames) + { + WriteMessage(null, "ison", nickNames); + } + } +} \ No newline at end of file diff --git a/IrcDotNet/IrcDotNet.csproj b/IrcDotNet/IrcDotNet.csproj new file mode 100644 index 0000000..3c4cf37 --- /dev/null +++ b/IrcDotNet/IrcDotNet.csproj @@ -0,0 +1,28 @@ + + + + 0.7.0 + netstandard1.5 + IrcDotNet + IrcDotNet + communication;irc;networking;ctcp + https://github.com/IrcDotNet/IrcDotNet/releases/tag/0.6.0 + https://raw.githubusercontent.com/IrcDotNet/IrcDotNet/master/doc/content/img/logo.png + https://IrcDotNet.github.io/IrcDotNet/ + https://github.com/IrcDotNet/IrcDotNet/License.txt + true + Library for communicating via Internet Relay Chat (IRC) protocol and its extensions + Debug;Release;Debug-IPv4 + + + + TRACE;DEBUG_IPV4;NETSTANDARD1_5;IPV4 + + + + + + + + + diff --git a/IrcDotNet/IrcEventArgs.cs b/IrcDotNet/IrcEventArgs.cs new file mode 100644 index 0000000..99d3611 --- /dev/null +++ b/IrcDotNet/IrcEventArgs.cs @@ -0,0 +1,670 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Security.Cryptography.X509Certificates; +using System.Text; +#if !SILVERLIGHT +using System.Net.Security; + +#endif + +namespace IrcDotNet +{ + /// + /// Provides data for the event. + /// + /// + public class IrcChannelListReceivedEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// A list of information about the channels that was returned by the server. + public IrcChannelListReceivedEventArgs(IList channels) + { + if (channels == null) + throw new ArgumentNullException("channels"); + + Channels = new ReadOnlyCollection(channels); + } + + /// + /// Gets the list of information about the channels that was returned by the server. + /// + /// The list of channels. + public IList Channels { get; private set; } + } + + /// + /// Provides data for the event. + /// + /// + public class IrcServerVersionInfoEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The version of the server. + /// The debug level of the server. + /// The name of the server. + /// The comments about the server. + public IrcServerVersionInfoEventArgs(string version, string debugLevel, string serverName, string comments) + { + if (version == null) + throw new ArgumentNullException("version"); + if (debugLevel == null) + throw new ArgumentNullException("debugLevel"); + if (serverName == null) + throw new ArgumentNullException("serverName"); + if (comments == null) + throw new ArgumentNullException("comments"); + + Version = version; + DebugLevel = debugLevel; + ServerName = serverName; + Comments = comments; + } + + /// + /// Gets the version of the server. + /// + /// The version of the server. + public string Version { get; private set; } + + /// + /// Gets the debug level of the server. + /// + /// The debug level of the server. + public string DebugLevel { get; private set; } + + /// + /// Gets the name of the server to which the version information applies. + /// + /// The name of the server. + public string ServerName { get; private set; } + + /// + /// Gets the comments about the server. + /// + /// The comments about the server. + public string Comments { get; private set; } + } + + /// + /// Provides data for the event. + /// + /// + public class IrcServerTimeEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The name of the server. + /// The local date/time received from the server. + public IrcServerTimeEventArgs(string serverName, string dateTime) + { + if (serverName == null) + throw new ArgumentNullException("serverName"); + if (dateTime == null) + throw new ArgumentNullException("dateTime"); + + ServerName = serverName; + DateTime = dateTime; + } + + /// + /// Gets the name of the server to which the version information applies. + /// + /// The name of the server. + public string ServerName { get; private set; } + + /// + /// Gets the local date/time for the server. + /// + /// The local date/time for the server. + public string DateTime { get; private set; } + } + + /// + /// Provides data for the event. + /// + /// + public class IrcServerLinksListReceivedEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// A list of information about the server links that was returned by the server. + public IrcServerLinksListReceivedEventArgs(IList links) + { + if (links == null) + throw new ArgumentNullException("links"); + + Links = new ReadOnlyCollection(links); + } + + /// + /// Gets the list of information about the server links that was returned by the server + /// + /// The list of server links. + public IList Links { get; private set; } + } + + /// + /// Provides data for the event. + /// + /// + public class IrcServerStatsReceivedEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// A list of statistical entries that was returned by the server. + public IrcServerStatsReceivedEventArgs(IList entries) + { + if (entries == null) + throw new ArgumentNullException("entries"); + + Entries = new ReadOnlyCollection(entries); + } + + /// + /// Gets the list of statistical entries that was returned by the server. + /// + /// The list of statistical entries. + public IList Entries { get; private set; } + } + + /// + /// + /// Gives the option to handle the preview event and thus stop the normal event from being raised. + /// + /// + public class IrcPreviewMessageEventArgs : IrcMessageEventArgs + { + /// + public IrcPreviewMessageEventArgs(IIrcMessageSource source, IList targets, string text, + Encoding encoding) + : base(source, targets, text, encoding) + { + Handled = false; + } + + /// + /// Gets or sets whether the event has been handled. If it is handled, the corresponding normal (non-preview) + /// event is not raised. + /// + /// if the event has been handled; , otherwise. + public bool Handled { get; set; } + } + + /// + /// Provides data for events that are raised when an IRC message or notice is sent or received. + /// + /// + public class IrcMessageEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The source of the message. + /// A list of the targets of the message. + /// The text of the message. + /// The encoding of the message text. + /// is . + /// is . + public IrcMessageEventArgs(IIrcMessageSource source, IList targets, string text, + Encoding encoding) + { + if (targets == null) + throw new ArgumentNullException("target"); + if (text == null) + throw new ArgumentNullException("text"); + if (encoding == null) + throw new ArgumentNullException("textEncoding"); + + Source = source; + Targets = new ReadOnlyCollection(targets); + Text = text; + Encoding = encoding; + } + + /// + /// Gets the source of the message. + /// + /// The source of the message. + public IIrcMessageSource Source { get; private set; } + + /// + /// Gets a list of the targets of the message. + /// + /// The targets of the message. + public IList Targets { get; private set; } + + /// + /// Gets the text of the message. + /// + /// The text of the message. + public string Text { get; } + + /// + /// Gets the encoding of the message text. + /// + /// The encoding of the message text. + public Encoding Encoding { get; } + + /// + /// Gets the text of the message in the specified encoding. + /// + /// + /// The encoding in which to get the message text, or to use the + /// default encoding. + /// + /// The text of the message. + public string GetText(Encoding encoding = null) + { + return Text.ChangeEncoding(Encoding, encoding); + } + } + + /// + /// Provides data for the event. + /// + /// + public class IrcChannelInvitationEventArgs : IrcChannelEventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The channel to which the recipient user is invited. + /// The user inviting the recipient user to the channel. + public IrcChannelInvitationEventArgs(IrcChannel channel, IrcUser inviter) + : base(channel) + { + if (inviter == null) + throw new ArgumentNullException("inviter"); + + Inviter = inviter; + } + + /// + /// Gets the user inviting the recipient user to the channel + /// + /// The inviter user. + public IrcUser Inviter { get; private set; } + } + + /// + /// Provides data for events that concern an . + /// + /// + public class IrcChannelUserEventArgs : IrcCommentEventArgs + { + /// + /// + /// Initializes a new instance of the class. + /// + /// The channel user that the event concerns. + public IrcChannelUserEventArgs(IrcChannelUser channelUser, string comment = null) + : base(comment) + { + if (channelUser == null) + throw new ArgumentNullException("channelUser"); + + ChannelUser = channelUser; + } + + /// + /// Gets the channel user that the event concerns. + /// + /// The channel user that the event concerns. + public IrcChannelUser ChannelUser { get; private set; } + } + + /// + /// Provides data for events that concern an . + /// + /// + public class IrcChannelEventArgs : IrcCommentEventArgs + { + /// + /// + /// Initializes a new instance of the class. + /// + /// The channel that the event concerns. + public IrcChannelEventArgs(IrcChannel channel, string comment = null) + : base(comment) + { + if (channel == null) + throw new ArgumentNullException("channel"); + + Channel = channel; + } + + /// + /// Gets the channel that the event concerns. + /// + /// The channel that the event concerns. + public IrcChannel Channel { get; private set; } + } + + /// + /// Provides data for events that concern an . + /// + /// + public class IrcUserEventArgs : IrcCommentEventArgs + { + /// + /// + /// Initializes a new instance of the class. + /// + /// The user that the event concerns, or for no user. + public IrcUserEventArgs(IrcUser user, string comment = null) + : base(comment) + { + User = user; + } + + /// + /// Gets the user that the event concerns. + /// + /// The user that the event concerns. + public IrcUser User { get; private set; } + } + + /// + /// Provides data for events that specify a comment. + /// + /// + public class IrcNameEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The name that the event specified. + public IrcNameEventArgs(string name) + { + Name = name; + } + + /// + /// Gets the name that the event specified. + /// + /// The name that the event specified. + public string Name { get; private set; } + } + + /// + /// Provides data for events that specify a name. + /// + /// + public class IrcCommentEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The comment that the event specified. + public IrcCommentEventArgs(string comment) + { + Comment = comment; + } + + /// + /// Gets the comment that the event specified. + /// + /// The comment that the event specified. + public string Comment { get; private set; } + } + + /// + /// Provides data for the and events. + /// + /// + public class IrcPingOrPongReceivedEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The name of the server that is the source of the ping or pong. + public IrcPingOrPongReceivedEventArgs(string server) + { + if (server == null) + throw new ArgumentNullException("server"); + + Server = server; + } + + /// + /// Gets the name of the server that is the source of the ping or pong. + /// + /// The name of the server. + public string Server { get; private set; } + } + + /// + /// Provides data for the events. + /// + /// + public class IrcPingReceivedEventArgs : IrcPingOrPongReceivedEventArgs + { + public IrcPingReceivedEventArgs(string server) + : base(server) + { + SendPong = true; + } + + /// + /// Gets or sets if we should send a Pong back + /// + /// A value indicating sending a Pong. + public bool SendPong { get; set; } + } + + /// + /// Provides data for events that specify information about a server. + /// + /// + public class IrcServerInfoEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The address of the server. + /// The port on which to connect to the server. + public IrcServerInfoEventArgs(string address, int port) + { + if (address == null) + throw new ArgumentNullException("address"); + if (port <= 0) + throw new ArgumentOutOfRangeException("port"); + + Address = address; + Port = port; + } + + /// + /// Gets the address of the server. + /// + /// The address of the server. + public string Address { get; private set; } + + /// + /// Gets the port on which to connect to the server. + /// + /// The port on which to connect to the server. + public int Port { get; private set; } + } + + /// + /// Provides data for the event. + /// + /// + public class IrcErrorMessageEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The error message given by the server. + public IrcErrorMessageEventArgs(string message) + { + if (message == null) + throw new ArgumentNullException("message"); + + Message = message; + } + + /// + /// Gets the text of the error message. + /// + /// The text of the error message. + public string Message { get; private set; } + } + + /// + /// Provides data for the event. + /// + /// + public class IrcProtocolErrorEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The code. + /// The parameters. + /// The message. + public IrcProtocolErrorEventArgs(int code, IList parameters, string message) + { + if (parameters == null) + throw new ArgumentNullException("parameters"); + if (message == null) + throw new ArgumentNullException("message"); + + Code = code; + Parameters = new ReadOnlyCollection(parameters); + Message = message; + } + + /// + /// Gets or sets the numeric code that indicates the type of error. + /// + /// The numeric code that indicates the type of error. + public int Code { get; private set; } + + /// + /// Gets a list of the parameters of the error. + /// + /// A lsit of the parameters of the error. + public IList Parameters { get; private set; } + + /// + /// Gets the text of the error message. + /// + /// The text of the error message. + public string Message { get; private set; } + } + + /// + /// Provides data for the and + /// events. + /// + /// + public class IrcRawMessageEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The message that was sent/received. + /// The raw content of the message. + public IrcRawMessageEventArgs(IrcClient.IrcMessage message, string rawContent) + { + Message = message; + RawContent = rawContent; + } + + /// + /// Gets the message that was sent/received by the client. + /// + /// The message that was sent/received by the client. + public IrcClient.IrcMessage Message { get; private set; } + + /// + /// Gets the raw content of the message. + /// + /// The raw content of the message. + public string RawContent { get; private set; } + } + +#if !SILVERLIGHT + + /// + /// Provides data for the event. + /// + /// + public class IrcValidateSslCertificateEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The certificate used to authenticate the remote party. + /// The chain of certificate authorities. + /// The errors associated with the remote certificate. + public IrcValidateSslCertificateEventArgs(X509Certificate certificate, X509Chain chain, + SslPolicyErrors sslPolicyErrors) + { + Certificate = certificate; + Chain = chain; + SslPolicyErrors = sslPolicyErrors; + } + + /// + /// Gets the certificate used to authenticate the remote party.. + /// + /// The certificate. + public X509Certificate Certificate { get; private set; } + + /// + /// Gets the chain of certificate authorities associated with the remote certificate. + /// + /// The chain. + public X509Chain Chain { get; private set; } + + /// + /// Gets the errors associated with the remote certificate. + /// + /// The SSL policy errors. + public SslPolicyErrors SslPolicyErrors { get; private set; } + + /// + /// Gets or sets whether the certificate given by the server is valid. + /// + /// if the certificate is valid; , otherwise. + public bool IsValid { get; set; } + } + +#endif + + /// + /// Provides data for the event. + /// + /// + public class IrcErrorEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The error. + public IrcErrorEventArgs(Exception error) + { + if (error == null) + throw new ArgumentNullException("error"); + + Error = error; + } + + /// + /// Gets the error encountered by the client. + /// + /// The error encountered by the client. + public Exception Error { get; private set; } + } +} \ No newline at end of file diff --git a/IrcDotNet/IrcLocalUser.cs b/IrcDotNet/IrcLocalUser.cs new file mode 100644 index 0000000..22ded39 --- /dev/null +++ b/IrcDotNet/IrcLocalUser.cs @@ -0,0 +1,505 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using IrcDotNet.Collections; + +namespace IrcDotNet +{ + /// + /// Represents the local user of a specific . + /// The local user is the user as which the client has connected and registered, and may be either a normal user or + /// service. + /// + /// + [DebuggerDisplay("{ToString(), nq} (local)")] + public class IrcLocalUser : IrcUser, IIrcMessageSendHandler, IIrcMessageReceiveHandler, IIrcMessageReceiver + { + // True if local user is service; false, if local user is normal user. + + // Collection of current modes of user. + private readonly HashSet modes; + + internal IrcLocalUser(string nickName, string distribution, string description) + : base(true, nickName, null, null) + { + IsService = true; + modes = new HashSet(); + Modes = new ReadOnlySet(modes); + ServiceDistribution = distribution; + ServiceDescription = description; + } + + internal IrcLocalUser(string nickName, string userName, string realName, IEnumerable modes = null) + : base(true, nickName, userName, realName) + { + IsService = false; + this.modes = new HashSet(); + Modes = new ReadOnlySet(this.modes); + if (modes != null) + this.modes.AddRange(modes); + } + + /// + /// Gets whether the local user is a service or normal user. + /// + /// + /// if the user is a service; , if the user is a normal + /// user. + /// + public bool IsService { get; } + + /// + /// Gets a read-only collection of the modes the user currently has. + /// + /// The current modes of the user. + public ReadOnlySet Modes { get; } + + /// + /// Gets the distribution of the service, which determines its visibility to users on specific servers. + /// + /// + /// A wildcard expression for matching against the names of servers on which the service should be + /// visible. + /// + public string ServiceDistribution { get; } + + /// + /// Gets the distribution of the service, which determines its visibility to users on specific servers. + /// + /// + /// A wildcard expression for matching against the names of servers on which the service should be + /// visible. + /// + public string ServiceDescription { get; } + + /// + /// Occurs when the local user has received a message. + /// + public event EventHandler MessageReceived; + + /// + /// Occurs when the local user has received a notice. + /// + public event EventHandler NoticeReceived; + + /// + /// Occurs when the modes of the local user have changed. + /// + public event EventHandler ModesChanged; + + /// + /// Occurs when the local user has joined a channel. + /// + public event EventHandler JoinedChannel; + + /// + /// Occurs when the local user has left a channel. + /// + public event EventHandler LeftChannel; + + /// + /// Occurs when the local user has sent a message. + /// + public event EventHandler MessageSent; + + /// + /// Occurs when the local user has received a message, before the event. + /// + public event EventHandler PreviewMessageReceived; + + /// + /// Occurs when the local user has sent a notice. + /// + public event EventHandler NoticeSent; + + /// + /// Occurs when the local user has received a notice, before the event. + /// + public event EventHandler PreviewNoticeReceived; + + /// + /// The to which to send the message. + public void SendMessage(IIrcMessageTarget target, string text) + { + if (target == null) + throw new ArgumentNullException("target"); + if (text == null) + throw new ArgumentNullException("text"); + + SendMessage(new[] {target}, text); + } + + /// + /// + /// + /// A message target may be an , , or . + /// + /// A collection of targets to which to send the message. + public void SendMessage(IEnumerable targets, string text) + { + if (targets == null) + throw new ArgumentNullException("targets"); + if (text == null) + throw new ArgumentNullException("text"); + + SendMessage(targets.Select(t => t.Name), text); + } + + /// + /// The name of the target to which to send the message. + public void SendMessage(string target, string text) + { + if (target == null) + throw new ArgumentNullException("target"); + if (text == null) + throw new ArgumentNullException("text"); + + SendMessage(new[] {target}, text); + } + + /// + /// Sends a message to the specified target. + /// + /// A collection of the names of targets to which to send the message. + /// The ASCII-encoded text of the message to send. + /// The encoding in which to send the value of . + /// is . + /// is . + public void SendMessage(IEnumerable targets, string text, Encoding encoding = null) + { + if (targets == null) + throw new ArgumentNullException("targets"); + if (text == null) + throw new ArgumentNullException("text"); + + Client.SendPrivateMessage(targets, text.ChangeEncoding(Client.TextEncoding, encoding)); + } + + /// + /// The to which to send the notice. + public void SendNotice(IIrcMessageTarget target, string text) + { + if (target == null) + throw new ArgumentNullException("target"); + if (text == null) + throw new ArgumentNullException("text"); + + SendNotice(new[] {target}, text); + } + + /// + /// + /// + /// A message target may be an , , or . + /// + /// A collection of targets to which to send the notice. + public void SendNotice(IEnumerable targets, string text) + { + if (targets == null) + throw new ArgumentNullException("targets"); + if (text == null) + throw new ArgumentNullException("text"); + + SendNotice(targets.Select(t => t.Name), text); + } + + /// + /// The name of the target to which to send the notice. + public void SendNotice(string target, string text) + { + if (target == null) + throw new ArgumentNullException("target"); + if (text == null) + throw new ArgumentNullException("text"); + + SendNotice(new[] {target}, text); + } + + /// + /// Sends a notice to the specified target. + /// + /// A collection of the names of targets to which to send the notice. + /// The ASCII-encoded text of the notice to send. + /// The encoding in which to send the value of . + /// is . + /// is . + public void SendNotice(IEnumerable targets, string text, Encoding encoding = null) + { + if (targets == null) + throw new ArgumentNullException("targets"); + if (text == null) + throw new ArgumentNullException("text"); + + Client.SendNotice(targets, text.ChangeEncoding(Client.TextEncoding, encoding)); + } + + /// + /// Sets the nick name of the local user to the specified text. + /// + /// The new nick name of the local user. + /// is . + public void SetNickName(string nickName) + { + if (nickName == null) + throw new ArgumentNullException("nickName"); + + Client.SetNickName(nickName); + } + + /// + /// Sets the local user as away, giving the specified message. + /// + /// The text of the response sent to a user when they try to message you while away. + /// is . + public void SetAway(string text) + { + if (text == null) + throw new ArgumentNullException("text"); + + Client.SetAway(text); + } + + /// + /// Sets the local user as here (no longer away). + /// + public void UnsetAway() + { + Client.UnsetAway(); + } + + /// + /// Requests a list of the current modes of the user. + /// + public void GetModes() + { + Client.GetLocalUserModes(this); + } + + /// + public void SetModes(params char[] newModes) + { + SetModes((IEnumerable) newModes); + } + + /// + /// + /// A collection of mode characters that should become the new modes. + /// Any modes in the collection that are not currently set will be set, and any nodes not in the collection that + /// are currently set will be unset. + /// + /// is . + public void SetModes(IEnumerable newModes) + { + if (newModes == null) + throw new ArgumentNullException("newModes"); + + lock (((ICollection) Modes).SyncRoot) + SetModes(newModes.Except(modes), modes.Except(newModes)); + } + + /// + /// is . + /// is . + public void SetModes(IEnumerable setModes, IEnumerable unsetModes) + { + if (setModes == null) + throw new ArgumentNullException("setModes"); + if (unsetModes == null) + throw new ArgumentNullException("unsetModes"); + + SetModes("+" + string.Join(string.Empty, setModes) + "-" + string.Join(string.Empty, unsetModes)); + } + + /// + /// Sets the specified modes on the local user. + /// + /// + /// The mode string that specifies mode changes, which takes the form + /// `( "+" / "-" ) *( mode character )`. + /// + /// is . + public void SetModes(string modes) + { + if (modes == null) + throw new ArgumentNullException("modes"); + + Client.SetLocalUserModes(this, modes); + } + + internal void HandleModesChanged(string newModes) + { + lock (((ICollection) Modes).SyncRoot) + modes.UpdateModes(newModes); + + OnModesChanged(new EventArgs()); + } + + internal void HandleJoinedChannel(IrcChannel channel) + { + OnJoinedChannel(new IrcChannelEventArgs(channel, null)); + } + + internal void HandleLeftChannel(IrcChannel channel) + { + OnLeftChannel(new IrcChannelEventArgs(channel, null)); + } + + internal void HandleMessageSent(IList targets, string text) + { + OnMessageSent(new IrcMessageEventArgs(this, targets, text, Client.TextEncoding)); + } + + internal void HandleNoticeSent(IList targets, string text) + { + OnNoticeSent(new IrcMessageEventArgs(this, targets, text, Client.TextEncoding)); + } + + internal void HandleMessageReceived(IIrcMessageSource source, IList targets, string text) + { + var previewEventArgs = new IrcPreviewMessageEventArgs(source, targets, text, Client.TextEncoding); + OnPreviewMessageReceived(previewEventArgs); + if (!previewEventArgs.Handled) + OnMessageReceived(new IrcMessageEventArgs(source, targets, text, Client.TextEncoding)); + } + + internal void HandleNoticeReceived(IIrcMessageSource source, IList targets, string text) + { + var previewEventArgs = new IrcPreviewMessageEventArgs(source, targets, text, Client.TextEncoding); + OnPreviewNoticeReceived(previewEventArgs); + if (!previewEventArgs.Handled) + OnNoticeReceived(new IrcMessageEventArgs(source, targets, text, Client.TextEncoding)); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnModesChanged(EventArgs e) + { + var handler = ModesChanged; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnJoinedChannel(IrcChannelEventArgs e) + { + var handler = JoinedChannel; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnLeftChannel(IrcChannelEventArgs e) + { + var handler = LeftChannel; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnMessageSent(IrcMessageEventArgs e) + { + var handler = MessageSent; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnMessageReceived(IrcMessageEventArgs e) + { + var handler = MessageReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnPreviewMessageReceived(IrcPreviewMessageEventArgs e) + { + var handler = PreviewMessageReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnNoticeSent(IrcMessageEventArgs e) + { + var handler = NoticeSent; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnPreviewNoticeReceived(IrcPreviewMessageEventArgs e) + { + var handler = PreviewNoticeReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnNoticeReceived(IrcMessageEventArgs e) + { + var handler = NoticeReceived; + if (handler != null) + handler(this, e); + } + + #region IIrcMessageSendHandler Members + + void IIrcMessageSendHandler.HandleMessageSent(IList targets, string text) + { + HandleMessageSent(targets, text); + } + + void IIrcMessageSendHandler.HandleNoticeSent(IList targets, string text) + { + HandleNoticeSent(targets, text); + } + + #endregion + + #region IIrcMessageReceiveHandler Members + + void IIrcMessageReceiveHandler.HandleMessageReceived(IIrcMessageSource source, IList targets, + string text) + { + HandleMessageReceived(source, targets, text); + } + + void IIrcMessageReceiveHandler.HandleNoticeReceived(IIrcMessageSource source, IList targets, + string text) + { + HandleNoticeReceived(source, targets, text); + } + + #endregion + } +} \ No newline at end of file diff --git a/IrcDotNet/IrcNetworkInfo.cs b/IrcDotNet/IrcNetworkInfo.cs new file mode 100644 index 0000000..7cfcde9 --- /dev/null +++ b/IrcDotNet/IrcNetworkInfo.cs @@ -0,0 +1,53 @@ +namespace IrcDotNet +{ + /// + /// Stores information about a specific IRC network. + /// + public struct IrcNetworkInfo + { + /// + /// The number of visible users on the network. + /// + public int? VisibleUsersCount; + + /// + /// The number of invisible users on the network. + /// + public int? InvisibleUsersCount; + + /// + /// The number of servers in the network. + /// + public int? ServersCount; + + /// + /// The number of operators on the network. + /// + public int? OperatorsCount; + + /// + /// The number of unknown connections to the network. + /// + public int? UnknownConnectionsCount; + + /// + /// The number of channels that currently exist on the network. + /// + public int? ChannelsCount; + + /// + /// The number of clients connected to the server. + /// + public int? ServerClientsCount; + + /// + /// The number of others servers connected to the server. + /// + public int? ServerServersCount; + + /// + /// The number of services connected to the server. + /// + public int? ServerServicesCount; + } +} \ No newline at end of file diff --git a/IrcDotNet/IrcRegistrationInfo.cs b/IrcDotNet/IrcRegistrationInfo.cs new file mode 100644 index 0000000..af9c21f --- /dev/null +++ b/IrcDotNet/IrcRegistrationInfo.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; + +namespace IrcDotNet +{ + /// + /// Provides information used by an for registering the connection as a service. + /// + /// + public class IrcServiceRegistrationInfo : IrcRegistrationInfo + { + /// + /// Gets or sets the distribution of the service, which determines its visibility to users on specific servers. + /// + /// + /// A wildcard expression for matching against the names of servers on which the service should be + /// visible. + /// + public string Distribution { get; set; } + + /// + /// Gets or sets the description of the service to set upon registration. + /// The description cannot later be changed. + /// + /// A description of the service. + public string Description { get; set; } + } + + /// + /// Provides information used by an for registering the connection as a user. + /// + /// + public class IrcUserRegistrationInfo : IrcRegistrationInfo + { + /// + /// Gets or sets the user name of the local user to set upon registration. + /// The user name cannot later be changed. + /// + /// The user name of the local user. + public string UserName { get; set; } + + /// + /// Gets or sets the real name of the local user to set upon registration. + /// The real name cannot later be changed. + /// + /// The real name of the local user. + public string RealName { get; set; } + + /// + /// Gets or sets the modes of the local user to set initially. + /// The collection should not contain any characters except 'w' or 'i'. + /// The modes can be changed after registration. + /// + /// A collection of modes to set on the local user. + public ICollection UserModes { get; set; } + } + + /// + /// Provides information used by an for registering the connection with the server. + /// + /// + public abstract class IrcRegistrationInfo + { + /// + /// Gets or sets the password for registering with the server. + /// + /// The password for registering with the server. + public string Password { get; set; } + + /// + /// Gets or sets the nick name of the local user to set initially upon registration. + /// The nick name can be changed after registration. + /// + /// The initial nick name of the local user. + public string NickName { get; set; } + } +} \ No newline at end of file diff --git a/IrcDotNet/IrcServer.cs b/IrcDotNet/IrcServer.cs new file mode 100644 index 0000000..dbf5aca --- /dev/null +++ b/IrcDotNet/IrcServer.cs @@ -0,0 +1,38 @@ +namespace IrcDotNet +{ + /// + /// Represents an IRC server from the view of a particular client. + /// + /// + public class IrcServer : IIrcMessageSource + { + internal IrcServer(string hostName) + { + HostName = hostName; + } + + /// + /// Gets the host name of the server. + /// + /// The host name of the server. + public string HostName { get; } + + #region IIrcMessageSource Members + + string IIrcMessageSource.Name + { + get { return HostName; } + } + + #endregion + + /// + /// Returns a string representation of this instance. + /// + /// A string that represents this instance. + public override string ToString() + { + return HostName; + } + } +} \ No newline at end of file diff --git a/IrcDotNet/IrcServerInfo.cs b/IrcDotNet/IrcServerInfo.cs new file mode 100644 index 0000000..138998e --- /dev/null +++ b/IrcDotNet/IrcServerInfo.cs @@ -0,0 +1,37 @@ +namespace IrcDotNet +{ + /// + /// Stores information about a particular server in an IRC network. + /// + /// + public struct IrcServerInfo + { + /// + /// The host name of the server. + /// + private string HostName; + + /// + /// The hop count of the server from the local server. + /// + private int? HopCount; + + /// + /// A string containing arbitrary information about the server. + /// + private string Info; + + /// + /// Initializes a new instance of the class with the specified properties. + /// + /// The host name of the server. + /// The hop count of the server from the local server. + /// A string containing arbitrary information about the server. + public IrcServerInfo(string hostName, int? hopCount, string info) + { + HostName = hostName; + HopCount = hopCount; + Info = info; + } + } +} \ No newline at end of file diff --git a/IrcDotNet/IrcServerStatisticalEntry.cs b/IrcDotNet/IrcServerStatisticalEntry.cs new file mode 100644 index 0000000..0f2a5c8 --- /dev/null +++ b/IrcDotNet/IrcServerStatisticalEntry.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; + +namespace IrcDotNet +{ + /// + /// Stores a statistical entry for an IRC server. + /// + public struct IrcServerStatisticalEntry + { + /// + /// The type of the statistical entry. + /// + public int Type; + + /// + /// The list of parameters of the statistical entry. + /// + public IList Parameters; + } + + /// + /// Defines the types of statistical entries for an IRC server. + /// + /// + /// These entry types correspond to the STATS replies described in the RFC for the IRC protocol. + /// + public enum IrcServerStatisticalEntryCommonType + { + /// + /// An active connection to the server. + /// + Connection, + + /// + /// A command supported by the server. + /// + Command, + + /// + /// A server to which the local server may connect. + /// + AllowedServerConnect, + + /// + /// A server from which the local server may accept connections. + /// + AllowedServerAccept, + + /// + /// A client that may connect to the server. + /// + AllowedClient, + + /// + /// A client that is banned from connecting to the server. + /// + BannedClient, + + /// + /// A connection class defined by the server. + /// + ConnectionClass, + + /// + /// The leaf depth of a server in the network. + /// + LeafDepth, + + /// + /// The uptime of the server. + /// + Uptime, + + /// + /// An operator on the server. + /// + AllowedOperator, + + /// + /// A hub server within the network. + /// + HubServer + } +} \ No newline at end of file diff --git a/IrcDotNet/IrcStandardFloodPreventer.cs b/IrcDotNet/IrcStandardFloodPreventer.cs new file mode 100644 index 0000000..4acfb15 --- /dev/null +++ b/IrcDotNet/IrcStandardFloodPreventer.cs @@ -0,0 +1,86 @@ +using System; + +namespace IrcDotNet +{ + /// + /// Represents a flood protector that throttles data sent by the client according to the standard rules implemented + /// by modern IRC servers. + /// + /// + /// The principle is that no message may be sent by the client once the value of an internal counter has reached + /// the value of . The counter is incremented every time a message is sent, and + /// decremented by one every duration of . Hence, messages may be sent immediately in + /// bursts so long as the high rate is not sustained, else a delay is introduced between the sending of + /// successive messages, such that the data. + /// + /// + public class IrcStandardFloodPreventer : IIrcFloodPreventer + { + private const int ticksPerMillisecond = 10000; + + // Period between each decrement of counter, in milliseconds. + + // Absolute time of last counter decrement, in milliseconds. + private long lastCounterDecrementTime; + + // Maximum number of messages that can be sent in burst. + + // Number of messages sent within current burst. + private int messageCounter; + + /// + /// Initializes a new instance of the class. + /// + /// The maximum number of messages that can be sent in a burst. + /// + /// The number of milliseconds between each decrement of the message counter. + /// + public IrcStandardFloodPreventer(int maxMessageBurst, long counterPeriod) + { + MaxMessageBurst = maxMessageBurst; + CounterPeriod = counterPeriod; + + messageCounter = 0; + lastCounterDecrementTime = 0; + } + + /// + /// Gets the maximum message number of messages that can be sent in a burst. + /// + /// The maximum message number of messages that can be sent in a burst.. + public int MaxMessageBurst { get; } + + /// + /// Gets the number of milliseconds between each decrement of the message counter. + /// + /// The period of the counter, in milliseconds. + public long CounterPeriod { get; } + + #region IIrcFloodPreventer Members + + /// + public long GetSendDelay() + { + // Subtract however many counter periods have elapsed since last decrement of counter. + var currentTime = DateTime.Now.Ticks/ticksPerMillisecond; + var elapsedMilliseconds = currentTime - lastCounterDecrementTime; + var tempMessageCounter = Math.Max(0L, messageCounter - elapsedMilliseconds/CounterPeriod); + messageCounter = tempMessageCounter > int.MaxValue ? int.MaxValue : (int) tempMessageCounter; + + // Update time of last decrement of counter to theoretical time of decrement. + lastCounterDecrementTime = currentTime - elapsedMilliseconds%CounterPeriod; + + // Return time until next message can be sent. + return Math.Max((messageCounter - MaxMessageBurst)*CounterPeriod - elapsedMilliseconds, 0); + } + + /// + public void HandleMessageSent() + { + // Increment message count. + messageCounter++; + } + + #endregion + } +} \ No newline at end of file diff --git a/IrcDotNet/IrcTargetMask.cs b/IrcDotNet/IrcTargetMask.cs new file mode 100644 index 0000000..d05b57c --- /dev/null +++ b/IrcDotNet/IrcTargetMask.cs @@ -0,0 +1,121 @@ +using System; +using IrcDotNet.Properties; + +namespace IrcDotNet +{ + /// + /// Represents a mask of an IRC server name or host name, used for specifying the targets of a message. + /// + /// + public class IrcTargetMask : IIrcMessageTarget + { + /// + /// Initializes a new instance of the class with the specified target mask + /// identifier. + /// + /// + /// A wildcard expression for matching against server names or host names. + /// If the first character is '$', the mask is a server mask; if the first character is '#', the mask is a host + /// mask. + /// + /// is + /// The length of is too short. + /// + /// does not represent a known mask type. + /// + public IrcTargetMask(string targetMask) + { + if (targetMask == null) + throw new ArgumentNullException(nameof(targetMask)); + if (Resources.MessageTargetMaskTooShort.Length < 2) + throw new ArgumentException(Resources.MessageTargetMaskTooShort, nameof(targetMask)); + + switch (targetMask[0]) + { + case '$': + Type = IrcTargetMaskType.ServerMask; + break; + case '#': + Type = IrcTargetMaskType.HostMask; + break; + default: + throw new ArgumentException(string.Format( + Resources.MessageTargetMaskInvalidType, targetMask), nameof(targetMask)); + } + Mask = Mask?.Substring(1); + } + + /// + /// Initializes a new instance of the class with the specified type and mask. + /// + /// The type. + /// The mask. + public IrcTargetMask(IrcTargetMaskType type, string mask) + { + if (!Enum.IsDefined(typeof (IrcTargetMaskType), type)) + throw new ArgumentException(nameof(type)); + if (mask == null) + throw new ArgumentNullException(nameof(mask)); + + Type = type; + Mask = mask; + } + + /// + /// Gets the type of the target mask; either a server mask or channel mask. + /// + /// The type of the mask. + public IrcTargetMaskType Type { get; } + + /// + /// Gets a wildcard expression for matching against target names. + /// The property determines the type of the mask. + /// + /// The target mask. + public string Mask { get; } + + #region IIrcMessageTarget Members + + string IIrcMessageTarget.Name + { + get + { + char maskTypeChar; + if (Type == IrcTargetMaskType.ServerMask) + maskTypeChar = '$'; + else if (Type == IrcTargetMaskType.HostMask) + maskTypeChar = '#'; + else + throw new InvalidOperationException(); + return maskTypeChar + Mask; + } + } + + #endregion + + /// + /// Returns a string representation of this instance. + /// + /// A string that represents this instance. + public override string ToString() + { + return Mask; + } + } + + /// + /// Defines the types of a target mask. + /// + public enum IrcTargetMaskType + { + /// + /// A mask of a server name. + /// + ServerMask, + + /// + /// A mask of a host name. + /// + HostMask + } +} \ No newline at end of file diff --git a/IrcDotNet/IrcUser.cs b/IrcDotNet/IrcUser.cs new file mode 100644 index 0000000..e4d1fba --- /dev/null +++ b/IrcDotNet/IrcUser.cs @@ -0,0 +1,415 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; + +namespace IrcDotNet +{ + /// + /// Represents an IRC user that exists on a specific . + /// + /// + [DebuggerDisplay("{ToString(), nq}")] + public class IrcUser : INotifyPropertyChanged, IIrcMessageSource, IIrcMessageTarget + { + private string awayMessage; + + private IrcClient client; + + private int hopCount; + + private string hostName; + + private TimeSpan idleDuration; + + private bool isAway; + private bool isOnline; + + private bool isOperator; + + private string nickName; + + private string realName; + + private string serverInfo; + + private string serverName; + + private string userName; + + internal IrcUser(bool isOnline, string nickName, string userName, string realName) + { + this.nickName = nickName; + this.userName = userName; + this.realName = realName; + serverName = null; + serverInfo = null; + isOperator = false; + isAway = false; + awayMessage = null; + idleDuration = TimeSpan.Zero; + hopCount = 0; + } + + internal IrcUser() + { + } + + /// + /// Gets whether the user is currently connected to the IRC network. This value may not be always be + /// up-to-date. + /// + /// + /// if the user is currently online; if the user is + /// currently offline. + /// + public bool IsOnline + { + get { return isOnline; } + internal set + { + isOnline = value; + OnPropertyChanged(new PropertyChangedEventArgs("IsOnline")); + } + } + + /// + /// Gets the current nick name of the user. + /// + /// The nick name of the user. + public string NickName + { + get { return nickName; } + internal set + { + nickName = value; + OnNickNameChanged(new EventArgs()); + OnPropertyChanged(new PropertyChangedEventArgs("NickName")); + } + } + + /// + /// Gets the current user name of the user. This value never changes until the user reconnects. + /// + /// The user name of the user. + public string UserName + { + get { return userName; } + internal set + { + userName = value; + OnPropertyChanged(new PropertyChangedEventArgs("UserName")); + } + } + + /// + /// Gets the real name of the user. This value never changes until the user reconnects. + /// + /// The real name of the user. + public string RealName + { + get { return realName; } + internal set + { + realName = value; + OnPropertyChanged(new PropertyChangedEventArgs("RealName")); + } + } + + /// + /// Gets the host name of the user. + /// + /// The host name of the user. + public string HostName + { + get { return hostName; } + internal set + { + hostName = value; + OnPropertyChanged(new PropertyChangedEventArgs("HostName")); + } + } + + /// + /// Gets the name of the server to which the user is connected. + /// + /// The name of the server to which the user is connected. + public string ServerName + { + get { return serverName; } + internal set + { + serverName = value; + OnPropertyChanged(new PropertyChangedEventArgs("ServerName")); + } + } + + /// + /// Gets arbitrary information about the server to which the user is connected. + /// + /// Arbitrary information about the server. + public string ServerInfo + { + get { return serverInfo; } + internal set + { + serverInfo = value; + OnPropertyChanged(new PropertyChangedEventArgs("ServerInfo")); + } + } + + /// + /// Gets whether the user is a server operator. + /// + /// if the user is a server operator; , otherwise. + public bool IsOperator + { + get { return isOperator; } + internal set + { + isOperator = value; + OnPropertyChanged(new PropertyChangedEventArgs("IsOperator")); + } + } + + /// + /// Gets whether the user has been been seen as away. This value is always up-to-date for the local user; + /// though it is only updated for remote users when a private message is sent to them or a Who Is response + /// is received for the user. + /// + /// + /// if the user is currently away; , if the user is + /// currently here. + /// + public bool IsAway + { + get { return isAway; } + internal set + { + isAway = value; + OnIsAwayChanged(new EventArgs()); + OnPropertyChanged(new PropertyChangedEventArgs("IsAway")); + } + } + + /// + /// Gets the current away message received when the user was seen as away. + /// + /// The current away message of the user. + public string AwayMessage + { + get { return awayMessage; } + internal set + { + awayMessage = value; + OnPropertyChanged(new PropertyChangedEventArgs("AwayMessage")); + } + } + + /// + /// Gets the duration for which the user has been idle. This is set when a Who Is response is received. + /// + /// The duration for which the user has been idle. + public TimeSpan IdleDuration + { + get { return idleDuration; } + internal set + { + idleDuration = value; + OnPropertyChanged(new PropertyChangedEventArgs("IdleDuration")); + } + } + + /// + /// Gets the hop count of the user, which is the number of servers between the user and the server on which the + /// client is connected, within the network. + /// + /// The hop count of the user. + public int HopCount + { + get { return hopCount; } + internal set + { + hopCount = value; + OnPropertyChanged(new PropertyChangedEventArgs("HopCount")); + } + } + + /// + /// Gets the client on which the user exists. + /// + /// The client on which the user exists. + public IrcClient Client + { + get { return client; } + internal set + { + client = value; + OnPropertyChanged(new PropertyChangedEventArgs("Client")); + } + } + + #region IIrcMessageSource Members + + string IIrcMessageSource.Name + { + get { return NickName; } + } + + #endregion + + #region IIrcMessageTarget Members + + string IIrcMessageTarget.Name + { + get { return NickName; } + } + + #endregion + + /// + /// Occurs when a property value changes. + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Occurs when the nick name of the user has changed. + /// + public event EventHandler NickNameChanged; + + /// + /// Occurs when the user has been seen as away or here. + /// + public event EventHandler IsAwayChanged; + + /// + /// Occurs when an invitation to join a channel has been received. + /// + /// + /// This event should only be raised for the local user (the instance of ). + /// + public event EventHandler InviteReceived; + + /// + /// Occurs when the user has quit the network. This may not always be sent. + /// + public event EventHandler Quit; + + /// + /// Sends a Who Is query to server for the user. + /// + public void WhoIs() + { + client.QueryWhoIs(nickName); + } + + /// + /// Sends a Who Was query to server for the user. + /// + /// + /// The maximum number of entries that the server should return. A negative number + /// specifies an unlimited number of entries. + /// + public void WhoWas(int entriesCount = -1) + { + client.QueryWhoWas(new[] {nickName}, entriesCount); + } + + /// + /// Gets a collection of all channel users that correspond to the user. + /// Each represents a channel of which the user is currently a member. + /// + /// + /// A collection of all object that correspond to the . + /// + public IEnumerable GetChannelUsers() + { + // Get each channel user corresponding to this user that is member of any channel. + foreach (var channel in client.Channels) + { + foreach (var channelUser in channel.Users) + { + if (channelUser.User == this) + yield return channelUser; + } + } + } + + internal void HandleInviteReceived(IrcUser inviter, IrcChannel channel) + { + OnInviteReceived(new IrcChannelInvitationEventArgs(channel, inviter)); + } + + internal void HandeQuit(string comment) + { + foreach (var cu in GetChannelUsers().ToArray()) + cu.Channel.HandleUserQuit(cu, comment); + OnQuit(new IrcCommentEventArgs(comment)); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnNickNameChanged(EventArgs e) + { + var handler = NickNameChanged; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnIsAwayChanged(EventArgs e) + { + var handler = IsAwayChanged; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnInviteReceived(IrcChannelInvitationEventArgs e) + { + var handler = InviteReceived; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnQuit(IrcCommentEventArgs e) + { + var handler = Quit; + if (handler != null) + handler(this, e); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, e); + } + + /// + /// Returns a string representation of this instance. + /// + /// A string that represents this instance. + public override string ToString() + { + return nickName; + } + } +} \ No newline at end of file diff --git a/IrcDotNet/IrcUserCollection.cs b/IrcDotNet/IrcUserCollection.cs new file mode 100644 index 0000000..f92c909 --- /dev/null +++ b/IrcDotNet/IrcUserCollection.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace IrcDotNet +{ + /// + /// Represents a collection of objects. + /// + /// + /// + public class IrcUserCollection : ReadOnlyCollection + { + internal IrcUserCollection(IrcClient client, IList list) + : base(list) + { + Client = client; + } + + /// + /// Gets the client to which the collection of users belongs. + /// + /// The client to which the collection of users belongs. + public IrcClient Client { get; } + } +} \ No newline at end of file diff --git a/IrcDotNet/IrcUtilities.cs b/IrcDotNet/IrcUtilities.cs new file mode 100644 index 0000000..13bdbb5 --- /dev/null +++ b/IrcDotNet/IrcUtilities.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using IrcDotNet.Properties; + +namespace IrcDotNet +{ + // Utilities for IRC. + internal static class IrcUtilities + { + // Updates collection of modes from specified mode string. + // Mode string is of form `( "+" | "-" ) ( mode character )+`. + public static void UpdateModes(this ICollection collection, string newModes, + IEnumerable newModeParameters = null, ICollection modesWithParameters = null, + Action handleModeParameter = null) + { + if (collection == null) + throw new ArgumentNullException("collection"); + if (newModes == null) + throw new ArgumentNullException("newModes"); + if (newModeParameters != null) + { + if (modesWithParameters == null) + throw new ArgumentNullException("modesWithParameters"); + if (handleModeParameter == null) + throw new ArgumentNullException("handleModeParameter"); + } + + // Reads list of mode changes, where each group of modes is prefixed by a '+' or '-', representing + // respectively setting or unsetting of the given modes. + bool? addMode = null; + var modeParametersEnumerator = newModeParameters == null ? null : newModeParameters.GetEnumerator(); + foreach (var mode in newModes) + { + if (mode == '+') + { + addMode = true; + } + else if (mode == '-') + { + addMode = false; + } + else if (addMode.HasValue) + { + if (newModeParameters != null && modesWithParameters.Contains(mode)) + { + if (!modeParametersEnumerator.MoveNext()) + throw new ArgumentException(Resources.MessageNotEnoughModeParameters, + "newModeParameters"); + handleModeParameter(addMode.Value, mode, modeParametersEnumerator.Current); + } + else + { + if (addMode.Value) + collection.Add(mode); + else + collection.Remove(mode); + } + } + } + } + } +} \ No newline at end of file diff --git a/IrcDotNet/MessageProcessorAttribute.cs b/IrcDotNet/MessageProcessorAttribute.cs new file mode 100644 index 0000000..a9a9ff8 --- /dev/null +++ b/IrcDotNet/MessageProcessorAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace IrcDotNet +{ + // Indicates that method processes message for some protocol. + internal class MessageProcessorAttribute : Attribute + { + public MessageProcessorAttribute(string commandName) + { + CommandName = commandName; + } + + public string CommandName { get; private set; } + } +} \ No newline at end of file diff --git a/IrcDotNet/Properties/Resources.Designer.cs b/IrcDotNet/Properties/Resources.Designer.cs new file mode 100644 index 0000000..30b3876 --- /dev/null +++ b/IrcDotNet/Properties/Resources.Designer.cs @@ -0,0 +1,348 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.1 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System.Reflection; + +namespace IrcDotNet.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { +#if NETSTANDARD1_5 + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("IrcDotNet.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly); +#else + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("IrcDotNet.Properties.Resources", typeof(Resources).Assembly); +#endif + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Cannot set user mode for '{0}'.. + /// + internal static string MessageCannotSetUserMode { + get { + return ResourceManager.GetString("MessageCannotSetUserMode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The channel type '{0}' sent by the server is invalid.. + /// + internal static string MessageInvalidChannelType { + get { + return ResourceManager.GetString("MessageInvalidChannelType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The message command '{0}' is invalid.. + /// + internal static string MessageInvalidCommand { + get { + return ResourceManager.GetString("MessageInvalidCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The command definition '{0}' is invalid.. + /// + internal static string MessageInvalidCommandDefinition { + get { + return ResourceManager.GetString("MessageInvalidCommandDefinition", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The command '{0}' was not recognised.. + /// + internal static string MessageInvalidMessageCommand { + get { + return ResourceManager.GetString("MessageInvalidMessageCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The non-trailing parameter '{0}' is invalid.. + /// + internal static string MessageInvalidMiddleParameter { + get { + return ResourceManager.GetString("MessageInvalidMiddleParameter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified nick name is invalid.. + /// + internal static string MessageInvalidNickName { + get { + return ResourceManager.GetString("MessageInvalidNickName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified password is invalid.. + /// + internal static string MessageInvalidPassword { + get { + return ResourceManager.GetString("MessageInvalidPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The message prefix '{0}' is invalid.. + /// + internal static string MessageInvalidPrefix { + get { + return ResourceManager.GetString("MessageInvalidPrefix", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The quoted character '{0}' was not recognised.. + /// + internal static string MessageInvalidQuotedChar { + get { + return ResourceManager.GetString("MessageInvalidQuotedChar", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified real name is invalid.. + /// + internal static string MessageInvalidRealName { + get { + return ResourceManager.GetString("MessageInvalidRealName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The object provided for registration info is of an unknown type.. + /// + internal static string MessageInvalidRegistrationInfoObject { + get { + return ResourceManager.GetString("MessageInvalidRegistrationInfoObject", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The registration info for a service must have a valid nick name and description.. + /// + internal static string MessageInvalidServiceRegistrationInfo { + get { + return ResourceManager.GetString("MessageInvalidServiceRegistrationInfo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The source '{0}' of the message was not recognised as either a server or user.. + /// + internal static string MessageInvalidSource { + get { + return ResourceManager.GetString("MessageInvalidSource", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The message tag '{0}' is invalid.. + /// + internal static string MessageInvalidTag { + get { + return ResourceManager.GetString("MessageInvalidTag", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A target name may not contain any ',' character.. + /// + internal static string MessageInvalidTargetName { + get { + return ResourceManager.GetString("MessageInvalidTargetName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The trailing parameter '{0}' is invalid.. + /// + internal static string MessageInvalidTrailingParameter { + get { + return ResourceManager.GetString("MessageInvalidTrailingParameter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The URL scheme '{0}' is not valid.. + /// + internal static string MessageInvalidUrlScheme { + get { + return ResourceManager.GetString("MessageInvalidUrlScheme", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified user mode is invalid.. + /// + internal static string MessageInvalidUserMode { + get { + return ResourceManager.GetString("MessageInvalidUserMode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified user name is invalid.. + /// + internal static string MessageInvalidUserName { + get { + return ResourceManager.GetString("MessageInvalidUserName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The registration info for a user must have a valid nick name and user name.. + /// + internal static string MessageInvalidUserRegistrationInfo { + get { + return ResourceManager.GetString("MessageInvalidUserRegistrationInfo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The ISUPPORT message sent by the server contains an invalid PREFIX parameter.. + /// + internal static string MessageISupportPrefixInvalid { + get { + return ResourceManager.GetString("MessageISupportPrefixInvalid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Not enough mode parameters were specified for the given modes.. + /// + internal static string MessageNotEnoughModeParameters { + get { + return ResourceManager.GetString("MessageNotEnoughModeParameters", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The host name '{0}' does not resolve to a valid IP address.. + /// + internal static string MessageNoValidAddress { + get { + return ResourceManager.GetString("MessageNoValidAddress", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The length of a raw message must not exceed {0} characters.. + /// + internal static string MessageRawMessageTooLong { + get { + return ResourceManager.GetString("MessageRawMessageTooLong", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The message source '{0}' is not a user.. + /// + internal static string MessageSourceNotUser { + get { + return ResourceManager.GetString("MessageSourceNotUser", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type of the given target mask '{0}' is invalid.. + /// + internal static string MessageTargetMaskInvalidType { + get { + return ResourceManager.GetString("MessageTargetMaskInvalidType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The target mask must be contain at least two characters.. + /// + internal static string MessageTargetMaskTooShort { + get { + return ResourceManager.GetString("MessageTargetMaskTooShort", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No more than 3 mode parameters may be sent per message.. + /// + internal static string MessageTooManyModeParameters { + get { + return ResourceManager.GetString("MessageTooManyModeParameters", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No more than 15 command parameters may be specified.. + /// + internal static string MessageTooManyParams { + get { + return ResourceManager.GetString("MessageTooManyParams", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The value cannot be an empty string. + /// + internal static string MessageValueCannotBeEmptyString { + get { + return ResourceManager.GetString("MessageValueCannotBeEmptyString", resourceCulture); + } + } + } +} diff --git a/IrcDotNet/Properties/Resources.resx b/IrcDotNet/Properties/Resources.resx new file mode 100644 index 0000000..568106b --- /dev/null +++ b/IrcDotNet/Properties/Resources.resx @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cannot set user mode for '{0}'. + + + The channel type '{0}' sent by the server is invalid. + + + The message command '{0}' is invalid. + + + The command '{0}' was not recognised. + + + The non-trailing parameter '{0}' is invalid. + + + The specified nick name is invalid. + + + The specified password is invalid. + + + The message prefix '{0}' is invalid. + + + The specified real name is invalid. + + + The source '{0}' of the message was not recognised as either a server or user. + + + A target name may not contain any ',' character. + + + The trailing parameter '{0}' is invalid. + + + The specified user mode is invalid. + + + The specified user name is invalid. + + + The ISUPPORT message sent by the server contains an invalid PREFIX parameter. + + + The length of a raw message must not exceed {0} characters. + + + Not enough mode parameters were specified for the given modes. + + + The message source '{0}' is not a user. + + + The type of the given target mask '{0}' is invalid. + + + The target mask must be contain at least two characters. + + + No more than 3 mode parameters may be sent per message. + + + No more than 15 command parameters may be specified. + + + The value cannot be an empty string + + + The command definition '{0}' is invalid. + + + The quoted character '{0}' was not recognised. + + + The message tag '{0}' is invalid. + + + The URL scheme '{0}' is not valid. + + + The object provided for registration info is of an unknown type. + + + The registration info for a user must have a valid nick name and user name. + + + The registration info for a service must have a valid nick name and description. + + + The host name '{0}' does not resolve to a valid IP address. + + \ No newline at end of file diff --git a/IrcDotNet/ReflectionUtilities.cs b/IrcDotNet/ReflectionUtilities.cs new file mode 100644 index 0000000..9e837e1 --- /dev/null +++ b/IrcDotNet/ReflectionUtilities.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace IrcDotNet +{ + // Utilities for reflection of managed entities. + internal static class ReflectionUtilities + { + public static IEnumerable> GetAttributedMethods( + this object obj) + where TAttribute : Attribute + where TDelegate : class + { + // Find all methods in class that are marked by one or more instances of given attribute. +#if NETSTANDARD1_5 + var messageProcessorsMethods = obj.GetType().GetTypeInfo().GetMethods( + BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); +#else + var messageProcessorsMethods = obj.GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic + | BindingFlags.Public); +#endif + foreach (var methodInfo in messageProcessorsMethods) + { + var methodAttributes = (TAttribute[]) methodInfo.GetCustomAttributes( + typeof (TAttribute), true); + if (methodAttributes.Length > 0) + { +#if NETSTANDARD1_5 + var methodDelegate = + (TDelegate)(object)methodInfo.CreateDelegate(typeof(TDelegate), obj); +#else + var methodDelegate = + (TDelegate) (object) Delegate.CreateDelegate(typeof (TDelegate), obj, methodInfo); +#endif + + // Get each attribute applied to method. + foreach (var attribute in methodAttributes) + yield return Tuple.Create(attribute, methodDelegate); + } + } + } + } +} \ No newline at end of file diff --git a/IrcDotNet/SafeLineReader.cs b/IrcDotNet/SafeLineReader.cs new file mode 100644 index 0000000..e3bc18f --- /dev/null +++ b/IrcDotNet/SafeLineReader.cs @@ -0,0 +1,54 @@ +using System.IO; +using System.Text; + +namespace IrcDotNet +{ + // Reads lines from text sources safely; non-terminated lines are not returned. + internal class SafeLineReader + { + // Current incomplete line; + private string currentLine; + // Reads characters from text source. + + public SafeLineReader(TextReader textReader) + { + TextReader = textReader; + currentLine = string.Empty; + } + + public TextReader TextReader { get; } + + // Reads line from source, ensuring that line is not returned unless it terminates with line break. + public string ReadLine() + { + var lineBuilder = new StringBuilder(); + int nextChar; + + while (true) + { + // Check whether to stop reading characters. + nextChar = TextReader.Peek(); + if (nextChar == -1) + { + currentLine = lineBuilder.ToString(); + break; + } + if (nextChar == '\r' || nextChar == '\n') + { + TextReader.Read(); + if (TextReader.Peek() == '\n') + TextReader.Read(); + + var line = currentLine + lineBuilder; + currentLine = string.Empty; + return line; + } + + // Append next character to line. + lineBuilder.Append((char) TextReader.Read()); + } + + return null; + } + } +} \ No newline at end of file diff --git a/IrcDotNet/StandardIrcClient.cs b/IrcDotNet/StandardIrcClient.cs new file mode 100644 index 0000000..f4bf8c8 --- /dev/null +++ b/IrcDotNet/StandardIrcClient.cs @@ -0,0 +1,619 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using IrcDotNet.Properties; +#if !SILVERLIGHT +using System.Net.Security; + +#endif + +namespace IrcDotNet +{ + /// + public class StandardIrcClient : IrcClient + { + // Minimum duration of time to wait between sending successive raw messages. + private const long minimumSendWaitTime = 50; + + // Size of buffer for data received by socket, in bytes. + private const int socketReceiveBufferSize = 0xFFFF; + private Stream dataStream; + private SafeLineReader dataStreamLineReader; + private StreamReader dataStreamReader; + private AutoResetEvent disconnectedEvent; + + // Queue of pending messages and their tokens to be sent when ready. + private readonly Queue> messageSendQueue; + private CircularBufferStream receiveStream; + private Timer sendTimer; + + // Network (TCP) I/O. + private Socket socket; + + public StandardIrcClient() + { +#if IPV4 + socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); +#else + socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp); +#endif + sendTimer = new Timer(WritePendingMessages, null, + Timeout.Infinite, Timeout.Infinite); + disconnectedEvent = new AutoResetEvent(false); + + messageSendQueue = new Queue>(); + } + + public override bool IsConnected + { + get + { + CheckDisposed(); + return socket != null && socket.Connected; + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (disposing) + { + if (socket != null) + { + socket.Dispose(); + socket = null; + + HandleClientDisconnected(); + } + if (receiveStream != null) + { + receiveStream.Dispose(); + receiveStream = null; + } + if (dataStream != null) + { + dataStream.Dispose(); + dataStream = null; + } + if (dataStreamReader != null) + { + dataStreamReader.Dispose(); + dataStreamReader = null; + } + if (sendTimer != null) + { + sendTimer.Dispose(); + sendTimer = null; + } + if (disconnectedEvent != null) + { + disconnectedEvent.Dispose(); + disconnectedEvent = null; + } + } + } + + protected override void WriteMessage(string line, object token) + { + // Add message line to send queue. + messageSendQueue.Enqueue(Tuple.Create(line + Environment.NewLine, token)); + } + + /// + /// + /// Connects to a server using the specified URL and user information. + /// + public void Connect(Uri url, IrcRegistrationInfo registrationInfo) + { + CheckDisposed(); + + if (registrationInfo == null) + throw new ArgumentNullException("registrationInfo"); + + // Check URL scheme and decide whether to use SSL. + bool useSsl; + if (url.Scheme == "irc") + useSsl = false; + else if (url.Scheme == "ircs") + useSsl = true; + else + throw new ArgumentException(string.Format(Resources.MessageInvalidUrlScheme, + url.Scheme), "url"); + + Connect(url.Host, url.Port == -1 ? DefaultPort : url.Port, useSsl, registrationInfo); + } + + /// + public void Connect(string hostName, bool useSsl, IrcRegistrationInfo registrationInfo) + { + CheckDisposed(); + + if (registrationInfo == null) + throw new ArgumentNullException("registrationInfo"); + + Connect(hostName, DefaultPort, useSsl, registrationInfo); + } + + /// + /// The name of the remote host. + /// The port number of the remote host. + public void Connect(string hostName, int port, bool useSsl, IrcRegistrationInfo registrationInfo) + { + CheckDisposed(); + + if (registrationInfo == null) + throw new ArgumentNullException("registrationInfo"); +#if NETSTANDARD1_5 + var dnsTask = Dns.GetHostAddressesAsync(hostName); + var addresses = dnsTask.Result; + + Connect(new IPEndPoint(addresses[0], port), useSsl, registrationInfo); +#else + Connect(new DnsEndPoint(hostName, port), useSsl, registrationInfo); +#endif + } + + /// + public void Connect(IPAddress address, bool useSsl, IrcRegistrationInfo registrationInfo) + { + CheckDisposed(); + + if (registrationInfo == null) + throw new ArgumentNullException("registrationInfo"); + + Connect(address, DefaultPort, useSsl, registrationInfo); + } + + /// + /// An IP addresses that designates the remote host. + /// The port number of the remote host. + public void Connect(IPAddress address, int port, bool useSsl, IrcRegistrationInfo registrationInfo) + { + CheckDisposed(); + + if (registrationInfo == null) + throw new ArgumentNullException("registrationInfo"); + + Connect(new IPEndPoint(address, port), useSsl, registrationInfo); + } + + /// + /// Connects asynchronously to the specified server. + /// + /// + /// The network endpoint (IP address and port) of the server to which to connect. + /// + /// + /// to connect to the server via SSL; , + /// otherwise + /// + /// + /// The information used for registering the client. + /// The type of the object may be either or + /// . + /// + /// + /// is . + /// + /// + /// does not specify valid registration + /// information. + /// + /// The current instance has already been disposed. + public void Connect(EndPoint remoteEndPoint, bool useSsl, IrcRegistrationInfo registrationInfo) + { + Connect(registrationInfo); + // Connect socket to remote host. + ConnectAsync(remoteEndPoint, Tuple.Create(useSsl, string.Empty, registrationInfo)); + + HandleClientConnecting(); + } + + public override void Quit(int timeout, string comment) + { + base.Quit(timeout, comment); + if (!disconnectedEvent.WaitOne(timeout)) + Disconnect(); + } + + protected override void ResetState() + { + base.ResetState(); + + // Reset network I/O objects. + if (receiveStream != null) + receiveStream.Dispose(); + if (dataStream != null) + dataStream.Dispose(); + if (dataStreamReader != null) + dataStreamReader = null; + } + + private void WritePendingMessages(object state) + { + try + { + // Send pending messages in queue until flood preventer indicates to stop. + long sendDelay = 0; + + while (messageSendQueue.Count > 0) + { + Debug.Assert(messageSendQueue.Count < 100); + // Check that flood preventer currently permits sending of messages. + if (FloodPreventer != null) + { + sendDelay = FloodPreventer.GetSendDelay(); + if (sendDelay > 0) + break; + } + + // Send next message in queue. + var message = messageSendQueue.Dequeue(); + var line = message.Item1; + var token = message.Item2; + var lineBuffer = TextEncoding.GetBytes(line); + SendAsync(lineBuffer, token); + + // Tell flood preventer mechanism that message has just been sent. + if (FloodPreventer != null) + FloodPreventer.HandleMessageSent(); + } + + // Make timer fire when next message in send queue should be written. + sendTimer.Change((int)Math.Max(sendDelay, minimumSendWaitTime), Timeout.Infinite); + } + catch (SocketException exSocket) + { + HandleSocketError(exSocket); + } + catch (ObjectDisposedException) + { + // Ignore. + } +#if !DEBUG + catch (Exception ex) + { + OnError(new IrcErrorEventArgs(ex)); + } +#endif + finally + { + } + } + + public override void Disconnect() + { + base.Disconnect(); + + DisconnectAsync(); + } + + private void SendAsync(byte[] buffer, object token = null) + { + SendAsync(buffer, 0, buffer.Length, token); + } + + private void SendAsync(byte[] buffer, int offset, int count, object token = null) + { + // Write data from buffer to socket asynchronously. + var sendEventArgs = new SocketAsyncEventArgs(); + sendEventArgs.SetBuffer(buffer, offset, count); + sendEventArgs.UserToken = token; + sendEventArgs.Completed += SendCompleted; + + if (!socket.SendAsync(sendEventArgs)) + SendCompleted(socket, sendEventArgs); + } + + private void SendCompleted(object sender, SocketAsyncEventArgs e) + { + try + { + if (e.SocketError != SocketError.Success) + { + HandleSocketError(e.SocketError); + return; + } + + // Handle sent IRC message. + Debug.Assert(e.UserToken != null); + var messageSentEventArgs = (IrcRawMessageEventArgs) e.UserToken; + OnRawMessageSent(messageSentEventArgs); + +#if DEBUG + DebugUtilities.WriteIrcRawLine(this, "<<< " + messageSentEventArgs.RawContent); +#endif + } + catch (ObjectDisposedException) + { + // Ignore. + } +#if !DEBUG + catch (Exception ex) + { + OnError(new IrcErrorEventArgs(ex)); + } +#endif + finally + { + e.Dispose(); + } + } + + private void ReceiveAsync() + { + // Read data received from socket to buffer asynchronously. + var receiveEventArgs = new SocketAsyncEventArgs(); + Debug.Assert(receiveStream.Buffer.Length - (int) receiveStream.WritePosition > 0); + receiveEventArgs.SetBuffer(receiveStream.Buffer, (int) receiveStream.WritePosition, + receiveStream.Buffer.Length - (int) receiveStream.WritePosition); + receiveEventArgs.Completed += ReceiveCompleted; + + if (!socket.ReceiveAsync(receiveEventArgs)) + ReceiveCompleted(socket, receiveEventArgs); + } + + private void ReceiveCompleted(object sender, SocketAsyncEventArgs e) + { + try + { + if (e.SocketError != SocketError.Success) + { + HandleSocketError(e.SocketError); + return; + } + + // Check if remote host has closed connection. + if (e.BytesTransferred == 0) + { + Disconnect(); + return; + } + + // Indicate that block of data has been read into receive buffer. + receiveStream.WritePosition += e.BytesTransferred; + dataStreamReader.DiscardBufferedData(); + + // Read each terminated line of characters from data stream. + while (true) + { + // Read next line from data stream. + var line = dataStreamLineReader.ReadLine(); + if (line == null) + break; + if (line.Length == 0) + continue; + + ParseMessage(line); + } + + // Continue reading data from socket. + ReceiveAsync(); + } + catch (SocketException exSocket) + { + HandleSocketError(exSocket); + } + catch (ObjectDisposedException) + { + // Ignore. + } +#if !DEBUG + catch (Exception ex) + { + OnError(new IrcErrorEventArgs(ex)); + } +#endif + finally + { + e.Dispose(); + } + } + + private void ConnectAsync(EndPoint remoteEndPoint, object token = null) + { + // Connect socket to remote endpoint asynchronously. + var connectEventArgs = new SocketAsyncEventArgs(); + connectEventArgs.RemoteEndPoint = remoteEndPoint; + connectEventArgs.UserToken = token; + connectEventArgs.Completed += ConnectCompleted; + + if (!socket.ConnectAsync(connectEventArgs)) + ConnectCompleted(socket, connectEventArgs); + } + + private void ConnectCompleted(object sender, SocketAsyncEventArgs e) + { + try + { + if (e.SocketError != SocketError.Success) + { + HandleSocketError(e.SocketError); + return; + } + + Debug.Assert(e.UserToken != null); + var token = (Tuple) e.UserToken; + + // Create stream for received data. Use SSL stream on top of network stream, if specified. + receiveStream = new CircularBufferStream(socketReceiveBufferSize); +#if SILVERLIGHT + this.dataStream = this.receiveStream; +#else + dataStream = GetDataStream(token.Item1, token.Item2); +#endif + dataStreamReader = new StreamReader(dataStream, TextEncoding); + dataStreamLineReader = new SafeLineReader(dataStreamReader); + + // Start sending and receiving data to/from server. + sendTimer.Change(0, Timeout.Infinite); + ReceiveAsync(); + + HandleClientConnected(token.Item3); + } + catch (SocketException exSocket) + { + HandleSocketError(exSocket); + } + catch (ObjectDisposedException) + { + // Ignore. + } +#if !DEBUG + catch (Exception ex) + { + OnConnectFailed(new IrcErrorEventArgs(ex)); + } +#endif + finally + { + e.Dispose(); + } + } + + private void DisconnectAsync() + { + // Connect socket to remote endpoint asynchronously. + var disconnectEventArgs = new SocketAsyncEventArgs(); + disconnectEventArgs.Completed += DisconnectCompleted; + +#if SILVERLIGHT || NETSTANDARD1_5 + this.socket.Shutdown(SocketShutdown.Both); + disconnectEventArgs.SocketError = SocketError.Success; + DisconnectCompleted(socket, disconnectEventArgs); +#else + disconnectEventArgs.DisconnectReuseSocket = true; + if (!socket.DisconnectAsync(disconnectEventArgs)) + DisconnectCompleted(socket, disconnectEventArgs); +#endif + } + + private void DisconnectCompleted(object sender, SocketAsyncEventArgs e) + { + try + { + if (e.SocketError != SocketError.Success) + { + HandleSocketError(e.SocketError); + return; + } + + HandleClientDisconnected(); + } + catch (SocketException exSocket) + { + HandleSocketError(exSocket); + } + catch (ObjectDisposedException) + { + // Ignore. + } +#if !DEBUG + catch (Exception ex) + { + OnError(new IrcErrorEventArgs(ex)); + } +#endif + finally + { + e.Dispose(); + } + } + + protected override void HandleClientConnected(IrcRegistrationInfo regInfo) + { + DebugUtilities.WriteEvent(string.Format("Connected to server at '{0}'.", + ((IPEndPoint) socket.RemoteEndPoint).Address)); + + base.HandleClientConnected(regInfo); + } + + protected override void HandleClientDisconnected() + { + // Ensure that client has not already handled disconnection. + if (disconnectedEvent.WaitOne(0)) + return; + + DebugUtilities.WriteEvent("Disconnected from server."); + + // Stop sending messages immediately. + sendTimer.Change(Timeout.Infinite, Timeout.Infinite); + + // Set that client has disconnected. + disconnectedEvent.Set(); + + base.HandleClientDisconnected(); + } + + private void HandleSocketError(SocketError error) + { + HandleSocketError(new SocketException((int) error)); + } + + private void HandleSocketError(SocketException exception) + { + switch (exception.SocketErrorCode) + { + case SocketError.NotConnected: + case SocketError.ConnectionReset: + HandleClientDisconnected(); + return; + default: + OnError(new IrcErrorEventArgs(exception)); + return; + } + } + + /// + /// Returns a string representation of this instance. + /// + /// A string that represents this instance. + public override string ToString() + { + if (!IsDisposed && IsConnected) + return string.Format("{0}@{1}", LocalUser.UserName, + ServerName ?? socket.RemoteEndPoint.ToString()); + return "(Not connected)"; + } + +#if !SILVERLIGHT + + private Stream GetDataStream(bool useSsl, string targetHost) + { + if (useSsl) + { + // Create SSL stream over network stream to use for data transmission. + var sslStream = new SslStream(receiveStream, true, + SslUserCertificateValidationCallback); + +#if NETSTANDARD1_5 + var authTask = sslStream.AuthenticateAsClientAsync(targetHost); + authTask.Wait(); +#else + sslStream.AuthenticateAsClient(targetHost); +#endif + Debug.Assert(sslStream.IsAuthenticated); + return sslStream; + } + // Use network stream directly for data transmission. + return receiveStream; + } + + private bool SslUserCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, + SslPolicyErrors sslPolicyErrors) + { + // Raise an event to decide whether the certificate is valid. + var eventArgs = new IrcValidateSslCertificateEventArgs(certificate, chain, sslPolicyErrors); + eventArgs.IsValid = true; + OnValidateSslCertificate(eventArgs); + return eventArgs.IsValid; + } + +#endif + } +} \ No newline at end of file diff --git a/IrcDotNet/TextUtilities.cs b/IrcDotNet/TextUtilities.cs new file mode 100644 index 0000000..e3fd559 --- /dev/null +++ b/IrcDotNet/TextUtilities.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using IrcDotNet.Properties; + +namespace IrcDotNet +{ + // Utilities for text manipulation. + internal static class TextUtilities + { + // Gets single matched value of group, if match succeeded. + public static string GetValue(this Group match) + { + if (!match.Success) + return null; + return match.Value; + } + + // Enquotes specified string given escape character and mapping for quotation characters. + public static string Quote(this string value, char escapeChar, IDictionary quotedChars) + { + var textBuilder = new StringBuilder(value.Length*2); + for (var i = 0; i < value.Length; i++) + { + var curQuotedChar = escapeChar; + if (quotedChars.TryGetValue(value[i], out curQuotedChar) || value[i] == escapeChar) + { + textBuilder.Append(escapeChar); + textBuilder.Append(curQuotedChar); + } + else + { + textBuilder.Append(value[i]); + } + } + + return textBuilder.ToString(); + } + + // Dequotes specified string given escape character and mapping for quotation characters. + public static string Dequote(this string value, char escapeChar, IDictionary dequotedChars) + { + var textBuilder = new StringBuilder(value.Length); + for (var i = 0; i < value.Length; i++) + { + if (value[i] == escapeChar) + { + i++; + var curDequotedChar = escapeChar; + if (dequotedChars.TryGetValue(value[i], out curDequotedChar) || value[i] == escapeChar) + { + textBuilder.Append(curDequotedChar); + } + else + { + throw new InvalidOperationException( + Resources.MessageInvalidQuotedChar); + } + } + else + { + textBuilder.Append(value[i]); + } + } + + return textBuilder.ToString(); + } + + // Splits specified string into pair of strings at position of first occurrence of separator. + public static Tuple SplitIntoPair(this string value, string separator) + { + var index = value.IndexOf(separator); + if (index < 0) + return Tuple.Create(value, (string) null); + return Tuple.Create(value.Substring(0, index), value.Substring(index + separator.Length)); + } + + // Change character encoding of specified string. + internal static string ChangeEncoding(this string value, Encoding currentEncoding, Encoding newEncoding) + { + if (newEncoding == null) + return value; + var buffer = currentEncoding.GetBytes(value); + return newEncoding.GetString(buffer, 0, buffer.Length); + } + } +} \ No newline at end of file diff --git a/IrcDotNet/TwitchIrcClient.cs b/IrcDotNet/TwitchIrcClient.cs new file mode 100644 index 0000000..3429266 --- /dev/null +++ b/IrcDotNet/TwitchIrcClient.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace IrcDotNet +{ + public class TwitchIrcClient : StandardIrcClient + { + protected override void WriteMessage(string message, object token = null) + { + base.WriteMessage(message, + token ?? new IrcRawMessageEventArgs(new IrcMessage(this, null, null, null), message)); + } + + protected override void OnChannelModeChanged(IrcChannel channel, IrcUser source, string newModes, + IEnumerable newModeParameters) + { + // Twitch doesn't actually send JOIN messages. This means we need to add users + // to the channel when changing their mode if we haven't already. + foreach (var username in newModeParameters) + { + var user = GetUserFromNickName(username); + if (channel.GetChannelUser(user) == null) + channel.HandleUserJoined(new IrcChannelUser(user)); + } + } + + protected internal override void ProcessMessageReplyWelcome(IrcMessage message) + { + Debug.Assert(message.Parameters[0] != null); + + Debug.Assert(message.Parameters[1] != null); + WelcomeMessage = message.Parameters[1]; + + // Twitch does not send a normal welcome message, so this code is actually incorrect. + isRegistered = true; + OnRegistered(new EventArgs()); + } + + protected internal override void ProcessMessageReplyMyInfo(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + // Twitch doesn't seem to give us this information. + Debug.Assert(message.Parameters[1] == "-"); + OnClientInfoReceived(new EventArgs()); + } + + protected internal override void ProcessMessageReplyMotdStart(IrcMessage message) + { + Debug.Assert(message.Parameters[0] == localUser.NickName); + + // Looks like the motd doesn't start on the start message for twitch. + Debug.Assert(message.Parameters[1] == "-"); + motdBuilder.Clear(); + } + } +} \ No newline at end of file From 1fa6e58a027ab6bf787b0a8ef05051c8bc41df73 Mon Sep 17 00:00:00 2001 From: Evan Dixon Date: Sun, 23 Sep 2018 19:26:48 -0500 Subject: [PATCH 5/7] Add IPv4 Release configuration --- Ditto.sln | 5 +++++ Ditto/Ditto.csproj | 2 +- IrcDotNet/IrcDotNet.csproj | 7 +++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Ditto.sln b/Ditto.sln index 68efe10..dcb27b4 100644 --- a/Ditto.sln +++ b/Ditto.sln @@ -17,6 +17,7 @@ Global Debug|Any CPU = Debug|Any CPU Debug-IPv4|Any CPU = Debug-IPv4|Any CPU Release|Any CPU = Release|Any CPU + Release-IPv4|Any CPU = Release-IPv4|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {B4D93EE6-97DA-43DD-84CB-DCDC3E843BC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -25,12 +26,16 @@ Global {B4D93EE6-97DA-43DD-84CB-DCDC3E843BC8}.Debug-IPv4|Any CPU.Build.0 = Debug-IPv4|Any CPU {B4D93EE6-97DA-43DD-84CB-DCDC3E843BC8}.Release|Any CPU.ActiveCfg = Release|Any CPU {B4D93EE6-97DA-43DD-84CB-DCDC3E843BC8}.Release|Any CPU.Build.0 = Release|Any CPU + {B4D93EE6-97DA-43DD-84CB-DCDC3E843BC8}.Release-IPv4|Any CPU.ActiveCfg = Release-IPv4|Any CPU + {B4D93EE6-97DA-43DD-84CB-DCDC3E843BC8}.Release-IPv4|Any CPU.Build.0 = Release-IPv4|Any CPU {2E1632CE-3048-4EE7-B7A8-318F3007990D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2E1632CE-3048-4EE7-B7A8-318F3007990D}.Debug|Any CPU.Build.0 = Debug|Any CPU {2E1632CE-3048-4EE7-B7A8-318F3007990D}.Debug-IPv4|Any CPU.ActiveCfg = Debug-IPv4|Any CPU {2E1632CE-3048-4EE7-B7A8-318F3007990D}.Debug-IPv4|Any CPU.Build.0 = Debug-IPv4|Any CPU {2E1632CE-3048-4EE7-B7A8-318F3007990D}.Release|Any CPU.ActiveCfg = Release|Any CPU {2E1632CE-3048-4EE7-B7A8-318F3007990D}.Release|Any CPU.Build.0 = Release|Any CPU + {2E1632CE-3048-4EE7-B7A8-318F3007990D}.Release-IPv4|Any CPU.ActiveCfg = Release-IPv4|Any CPU + {2E1632CE-3048-4EE7-B7A8-318F3007990D}.Release-IPv4|Any CPU.Build.0 = Release-IPv4|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Ditto/Ditto.csproj b/Ditto/Ditto.csproj index 6953031..4f6e1f0 100644 --- a/Ditto/Ditto.csproj +++ b/Ditto/Ditto.csproj @@ -4,7 +4,7 @@ Exe netcoreapp2.0 win-x64;linux-x64;debian.9-x64 - Debug;Release;Debug-IPv4 + Debug;Release;Debug-IPv4;Release-IPv4 7.3 diff --git a/IrcDotNet/IrcDotNet.csproj b/IrcDotNet/IrcDotNet.csproj index 3c4cf37..6272d5d 100644 --- a/IrcDotNet/IrcDotNet.csproj +++ b/IrcDotNet/IrcDotNet.csproj @@ -12,10 +12,13 @@ https://github.com/IrcDotNet/IrcDotNet/License.txt true Library for communicating via Internet Relay Chat (IRC) protocol and its extensions - Debug;Release;Debug-IPv4 + Debug;Release;Debug-IPv4;Release-IPv4 - + + TRACE;DEBUG_IPV4;NETSTANDARD1_5;IPV4 + + TRACE;DEBUG_IPV4;NETSTANDARD1_5;IPV4 From 3220bfb9a89a5b39c9b62d19660aa3ea8e5ef28d Mon Sep 17 00:00:00 2001 From: Evan Dixon Date: Sun, 23 Sep 2018 19:27:36 -0500 Subject: [PATCH 6/7] Add global exception handler --- Ditto/Program.cs | 126 +++++++++++++++++++++++++---------------------- 1 file changed, 67 insertions(+), 59 deletions(-) diff --git a/Ditto/Program.cs b/Ditto/Program.cs index e9e7bc4..e953f22 100644 --- a/Ditto/Program.cs +++ b/Ditto/Program.cs @@ -19,79 +19,87 @@ public class Program public static async Task Main(string[] args) { - Console.WriteLine("Starting..."); - Pairs = new List(); - - var discordFilenames = Directory.GetFiles(".", "*.discord.json"); - Console.WriteLine("Found " + discordFilenames.Length.ToString() + " Discord settings"); - foreach (var discordFilename in discordFilenames) + try { - Console.WriteLine(discordFilename); - var ircFilename = discordFilename.Replace(".discord.json", ".irc.json"); - if (File.Exists(ircFilename)) + Console.WriteLine("Starting..."); + Pairs = new List(); + + var discordFilenames = Directory.GetFiles(".", "*.discord.json"); + Console.WriteLine("Found " + discordFilenames.Length.ToString() + " Discord settings"); + foreach (var discordFilename in discordFilenames) { - Console.Write(ircFilename); - var discordInfo = JsonConvert.DeserializeObject(File.ReadAllText(discordFilename)); - var ircInfo = JsonConvert.DeserializeObject(File.ReadAllText(ircFilename)); + Console.WriteLine(discordFilename); + var ircFilename = discordFilename.Replace(".discord.json", ".irc.json"); + if (File.Exists(ircFilename)) + { + Console.Write(ircFilename); + var discordInfo = JsonConvert.DeserializeObject(File.ReadAllText(discordFilename)); + var ircInfo = JsonConvert.DeserializeObject(File.ReadAllText(ircFilename)); - var pair = new ChannelPair(new IrcConnection(ircInfo), discordInfo); - if (args.Contains("noprompt")) + var pair = new ChannelPair(new IrcConnection(ircInfo), discordInfo); + if (args.Contains("noprompt")) + { + pair.EnableConsoleLogging = false; + } + Console.WriteLine("Connecting..."); + await pair.Connect(); + Console.WriteLine("Ready."); + Pairs.Add(pair); + } + else { - pair.EnableConsoleLogging = false; + Console.Write("Did not find IRC settings. Not creating pair."); } - Console.WriteLine("Connecting..."); - await pair.Connect(); - Console.WriteLine("Ready."); - Pairs.Add(pair); - } - else - { - Console.Write("Did not find IRC settings. Not creating pair."); } - } - // Listen for mannual commands - if (args.Contains("noprompt")) - { - while (true) + // Listen for mannual commands + if (args.Contains("noprompt")) { - // Block until process is manually stopped + while (true) + { + // Block until process is manually stopped + } } - } - else - { - while (_listen) + else { - var input = Console.ReadLine(); - var cmd = input.Split(" ".ToCharArray(), 2); - switch (cmd[0].ToLower()) + while (_listen) { - case "say": - if (Pairs.Count > 1) - { - Console.WriteLine("There is currently more than 1 channel pair active. Manual input is not currently supported."); - break; - } + var input = Console.ReadLine(); + var cmd = input.Split(" ".ToCharArray(), 2); + switch (cmd[0].ToLower()) + { + case "say": + if (Pairs.Count > 1) + { + Console.WriteLine("There is currently more than 1 channel pair active. Manual input is not currently supported."); + break; + } - if (cmd.Length > 1) - { - await Pairs[0].SendDiscordMessage(cmd[1]); - Pairs[0].SendIrcMessage(cmd[1]); - } - else - { - Console.WriteLine("Usage: say "); - } - break; - case "exit": - case "quit": - _listen = false; - break; - default: - break; + if (cmd.Length > 1) + { + await Pairs[0].SendDiscordMessage(cmd[1]); + Pairs[0].SendIrcMessage(cmd[1]); + } + else + { + Console.WriteLine("Usage: say "); + } + break; + case "exit": + case "quit": + _listen = false; + break; + default: + break; + } } } - } + } + catch (Exception ex) + { + Console.Write(ex.ToString()); + throw; + } } } } From 5d341706ad701a01b4445e85e6a168eb103e36f7 Mon Sep 17 00:00:00 2001 From: Evan Dixon Date: Mon, 24 Sep 2018 17:04:21 -0500 Subject: [PATCH 7/7] Update readme --- README.MD | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.MD b/README.MD index 3267d59..857fdfd 100644 --- a/README.MD +++ b/README.MD @@ -8,6 +8,9 @@ * Watchog - Starts Ditto and watches to make sure it's online, restarting it if necessary. This is needed because on some systems, Ditto dies so hard its error handling doesn't kick in, whenever there's a Discord outage for any length. ## Usage -1. Create the json settings files. Instructions coming eventually. -2. Run startup.sh -3. Run shutdown.sh to shutdown +1. Create the json settings files. You need two of them that deserialize into each of these classes: + - [https://github.com/projectpokemon/Ditto/blob/master/Ditto/DiscordConnectionInfo.cs](DiscordConnectionInfo) + - [https://github.com/projectpokemon/Ditto/blob/master/Ditto/IrcConnectionInfo.cs](IrcConnectionInfo) + You can create as many of these as you want, and you can link them by naming them accordingly. For example, Server1.discord.json and Server1.irc.json are a single linked pair, and Server2.discord.json and Server2.irc.json are another linked pair. +2. Place them in the working directory +3. Run Ditto.