Merge pull request #79 from RockyTV/caps

Add basic capability support
This commit is contained in:
Drew DeVault 2017-08-19 12:44:10 -04:00 committed by GitHub
commit cdd7c7664e
5 changed files with 258 additions and 0 deletions

140
ChatSharp/CapabilityPool.cs Normal file
View File

@ -0,0 +1,140 @@
using System.Linq;
using System.Collections.Generic;
namespace ChatSharp
{
/// <summary>
/// A list of capabilities supported by the library, along with the enabled and disabled
/// capabilities after negotiating with the server.
/// </summary>
public class CapabilityPool : IEnumerable<IrcCapability>
{
private List<IrcCapability> Capabilities { get; set; }
internal CapabilityPool()
{
Capabilities = new List<IrcCapability>();
}
/// <summary>
/// Gets the IrcCapability with the specified name.
/// </summary>
public IrcCapability this[string name]
{
get
{
var cap = Capabilities.FirstOrDefault(c => c.Name == name);
if (cap == null)
throw new KeyNotFoundException();
return cap;
}
}
internal void Add(string name)
{
if (Capabilities.Any(cap => cap.Name == name))
return;
Capabilities.Add(new IrcCapability(name));
}
internal void AddRange(IEnumerable<string> range)
{
foreach (string item in range)
Add(item);
}
internal void Remove(string name)
{
Capabilities.Remove(this[name]);
}
/// <summary>
/// Enables the specified capability.
/// </summary>
internal void Enable(string name)
{
if (Capabilities.Any(cap => cap.Name == name && cap.IsEnabled))
return;
this[name].IsEnabled = true;
}
/// <summary>
/// Disables the specified capability.
/// </summary>
internal void Disable(string name)
{
if (Capabilities.Any(cap => cap.Name == name && !cap.IsEnabled))
return;
this[name].IsEnabled = false;
}
/// <summary>
/// Checks if the specified capability is enabled.
/// </summary>
internal bool IsEnabled(string name)
{
return Capabilities.Any(cap => cap.Name == name && cap.IsEnabled);
}
/// <summary>
/// Gets a list of enabled capabilities after negotiating capabilities with the server.
/// </summary>
/// <returns></returns>
internal IEnumerable<string> Enabled
{
get
{
return Capabilities.Where(cap => cap.IsEnabled).Select(x => x.Name);
}
}
/// <summary>
/// Gets a list of disabled capabilities after negotiating capabilities with the server.
/// </summary>
/// <returns></returns>
internal IEnumerable<string> Disabled
{
get
{
return Capabilities.Where(cap => cap.IsEnabled).Select(x => x.Name);
}
}
internal bool Contains(string name)
{
return Capabilities.Any(cap => cap.Name == name);
}
internal IrcCapability Get(string name)
{
if (Contains(name))
return this[name];
throw new KeyNotFoundException();
}
internal IrcCapability GetOrAdd(string name)
{
if (Contains(name))
return this[name];
Add(name);
return this[name];
}
/// <summary>
/// Enumerates over the capabilities in this collection.
/// </summary>
public IEnumerator<IrcCapability> GetEnumerator()
{
return Capabilities.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace ChatSharp.Handlers
{
internal static class CapabilityHandlers
{
public static void HandleCapability(IrcClient client, IrcMessage message)
{
var serverCaps = new List<string>();
var supportedCaps = client.Capabilities.ToArray();
var requestedCaps = new List<string>();
switch (message.Parameters[1])
{
case "LS":
// Parse server capabilities
var serverCapsString = (message.Parameters[2] == "*" ? message.Parameters[3] : message.Parameters[2]);
serverCaps.AddRange(serverCapsString.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
// CAP 3.2 multiline support. Send CAP requests on the last CAP LS line.
// The last CAP LS line doesn't have * set as Parameters[2]
if (message.Parameters[2] != "*")
{
// Check which capabilities we support that the server supports
requestedCaps.AddRange(supportedCaps.Select(cap => cap.Name).Intersect(serverCaps));
// Check if we have to request any capability to be enabled.
// If not, end the capability negotiation.
if (requestedCaps.Count > 0)
client.SendRawMessage("CAP REQ :{0}", string.Join(" ", requestedCaps));
else
client.SendRawMessage("CAP END");
}
break;
case "ACK":
// Get the accepted capabilities
var acceptedCaps = message.Parameters[2].Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string acceptedCap in acceptedCaps)
client.Capabilities.Enable(acceptedCap);
// Check if the enabled capabilities count is the same as the ones
// acknowledged by the server.
if (client.Capabilities.Enabled.Count() == acceptedCaps.Count())
client.SendRawMessage("CAP END");
break;
case "NAK":
// Get the rejected capabilities
var rejectedCaps = message.Parameters[2].Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string acceptedCap in rejectedCaps)
client.Capabilities.Disable(acceptedCap);
// Check if the disabled capabilities count is the same as the ones
// rejected by the server.
if (client.Capabilities.Disabled.Count() == rejectedCaps.Count())
client.SendRawMessage("CAP END");
break;
case "LIST":
// Not implemented yet
break;
case "NEW":
// Not implemented yet
break;
case "DEL":
// Not implemented yet
break;
default:
break;
}
}
}
}

View File

@ -67,6 +67,9 @@ namespace ChatSharp.Handlers
client.SetHandler("405", ErrorHandlers.HandleError);//ERR_TOOMANYCHANNELS "<channel name> :You have joined too many \ channels"
client.SetHandler("406", ErrorHandlers.HandleError);//ERR_WASNOSUCHNICK "<nickname> :There was no such nickname"
client.SetHandler("407", ErrorHandlers.HandleError);//ERR_TOOMANYTARGETS "<target> :Duplicate recipients. No message \
// Capability handlers
client.SetHandler("CAP", CapabilityHandlers.HandleCapability);
}
public static void HandleNick(IrcClient client, IrcMessage message)

View File

@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Linq;
namespace ChatSharp
{
/// <summary>
/// A IRC capability.
/// </summary>
public class IrcCapability
{
/// <summary>
/// The capability identifier.
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// The state of the capability after negotiating with the server.
/// Disabled by default when the capability is created.
/// </summary>
public bool IsEnabled { get; internal set; }
/// <summary>
/// Constructs a capability given a name.
/// </summary>
/// <param name="name"></param>
public IrcCapability(string name)
{
Name = name;
IsEnabled = false;
}
}
}

View File

@ -126,6 +126,11 @@ namespace ChatSharp
/// A list of users on this network that we are aware of.
/// </summary>
public UserPool Users { get; set; }
/// <summary>
/// A list of capabilities supported by the library, along with enabled and disabled capabilities
/// after negotiating with the server.
/// </summary>
public CapabilityPool Capabilities { get; set; }
/// <summary>
/// Creates a new IRC client, but will not connect until ConnectAsync is called.
@ -152,6 +157,8 @@ namespace ChatSharp
PrivmsgPrefix = "";
Users = new UserPool();
Users.Add(User); // Add self to user pool
Capabilities = new CapabilityPool();
Capabilities.AddRange(new string[] { "server-time" }); // List of supported capabilities
}
/// <summary>
@ -225,6 +232,8 @@ namespace ChatSharp
}
NetworkStream.BeginRead(ReadBuffer, ReadBufferIndex, ReadBuffer.Length, DataRecieved, null);
// Begin capability negotiation
SendRawMessage("CAP LS 302");
// Write login info
if (!string.IsNullOrEmpty(User.Password))
SendRawMessage("PASS {0}", User.Password);