commit
d1424ba162
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
|
||||
namespace ChatSharp.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the response to a WHO (WHOX protocol) query. Note that ChatSharp may generate WHO
|
||||
/// queries that the user did not ask for.
|
||||
/// </summary>
|
||||
public class WhoxReceivedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The WHOIS response from the server.
|
||||
/// </summary>
|
||||
public ExtendedWho[] WhoxResponse { get; set; }
|
||||
|
||||
internal WhoxReceivedEventArgs(ExtendedWho[] whoxResponse)
|
||||
{
|
||||
WhoxResponse = whoxResponse;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -41,10 +41,12 @@ namespace ChatSharp.Handlers
|
|||
client.SetHandler("311", UserHandlers.HandleWhoIsUser);
|
||||
client.SetHandler("312", UserHandlers.HandleWhoIsServer);
|
||||
client.SetHandler("313", UserHandlers.HandleWhoIsOperator);
|
||||
client.SetHandler("315", UserHandlers.HandleWhoEnd);
|
||||
client.SetHandler("317", UserHandlers.HandleWhoIsIdle);
|
||||
client.SetHandler("318", UserHandlers.HandleWhoIsEnd);
|
||||
client.SetHandler("319", UserHandlers.HandleWhoIsChannels);
|
||||
client.SetHandler("330", UserHandlers.HandleWhoIsLoggedInAs);
|
||||
client.SetHandler("354", UserHandlers.HandleWhox);
|
||||
|
||||
// Listing handlers
|
||||
client.SetHandler("367", ListingHandlers.HandleBanListPart);
|
||||
|
|
|
@ -86,6 +86,15 @@ namespace ChatSharp.Handlers
|
|||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (key.ToUpper())
|
||||
{
|
||||
case "WHOX":
|
||||
client.ServerInfo.ExtendedWho = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
client.OnServerInfoRecieved(new SupportsEventArgs(client.ServerInfo));
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ChatSharp.Handlers
|
||||
{
|
||||
|
@ -70,5 +71,132 @@ namespace ChatSharp.Handlers
|
|||
request.Callback(request);
|
||||
client.OnWhoIsReceived(new Events.WhoIsReceivedEventArgs(whois));
|
||||
}
|
||||
|
||||
public static void HandleWhox(IrcClient client, IrcMessage message)
|
||||
{
|
||||
int msgQueryType = int.Parse(message.Parameters[1]);
|
||||
var whoxQuery = new KeyValuePair<string, RequestOperation>();
|
||||
|
||||
foreach (var query in client.RequestManager.PendingOperations.Where(kvp => kvp.Key.StartsWith("WHO ")))
|
||||
{
|
||||
// Parse query to retrieve querytype
|
||||
string key = query.Key;
|
||||
string[] queryParts = key.Split(new[] { ' ' });
|
||||
|
||||
int queryType = int.Parse(queryParts[2]);
|
||||
|
||||
// Check querytype against message querytype
|
||||
if (queryType == msgQueryType) whoxQuery = query;
|
||||
}
|
||||
|
||||
if (whoxQuery.Key != string.Empty && whoxQuery.Value != null)
|
||||
{
|
||||
var whoxList = (List<ExtendedWho>)client.RequestManager.PeekOperation(whoxQuery.Key).State;
|
||||
var whox = new ExtendedWho();
|
||||
|
||||
string key = whoxQuery.Key;
|
||||
string[] queryParts = key.Split(new[] { ' ' });
|
||||
|
||||
// Handle what fields were included in the WHOX request
|
||||
WhoxField fields = (WhoxField)int.Parse(queryParts[3]);
|
||||
int fieldIdx = 1;
|
||||
do
|
||||
{
|
||||
if ((fields & WhoxField.QueryType) != 0)
|
||||
{
|
||||
whox.QueryType = msgQueryType;
|
||||
fieldIdx++;
|
||||
}
|
||||
|
||||
if ((fields & WhoxField.Channel) != 0)
|
||||
{
|
||||
whox.Channel = message.Parameters[fieldIdx];
|
||||
fieldIdx++;
|
||||
}
|
||||
|
||||
if ((fields & WhoxField.Username) != 0)
|
||||
{
|
||||
whox.User.User = message.Parameters[fieldIdx];
|
||||
fieldIdx++;
|
||||
}
|
||||
|
||||
if ((fields & WhoxField.UserIp) != 0)
|
||||
{
|
||||
whox.IP = message.Parameters[fieldIdx];
|
||||
fieldIdx++;
|
||||
}
|
||||
|
||||
if ((fields & WhoxField.Hostname) != 0)
|
||||
{
|
||||
whox.User.Hostname = message.Parameters[fieldIdx];
|
||||
fieldIdx++;
|
||||
}
|
||||
|
||||
if ((fields & WhoxField.ServerName) != 0)
|
||||
{
|
||||
whox.Server = message.Parameters[fieldIdx];
|
||||
fieldIdx++;
|
||||
}
|
||||
|
||||
if ((fields & WhoxField.Nick) != 0)
|
||||
{
|
||||
whox.User.Nick = message.Parameters[fieldIdx];
|
||||
fieldIdx++;
|
||||
}
|
||||
|
||||
if ((fields & WhoxField.Flags) != 0)
|
||||
{
|
||||
whox.Flags = message.Parameters[fieldIdx];
|
||||
fieldIdx++;
|
||||
}
|
||||
|
||||
if ((fields & WhoxField.Hops) != 0)
|
||||
{
|
||||
whox.Hops = int.Parse(message.Parameters[fieldIdx]);
|
||||
fieldIdx++;
|
||||
}
|
||||
|
||||
if ((fields & WhoxField.TimeIdle) != 0)
|
||||
{
|
||||
whox.TimeIdle = int.Parse(message.Parameters[fieldIdx]);
|
||||
fieldIdx++;
|
||||
}
|
||||
|
||||
if ((fields & WhoxField.AccountName) != 0)
|
||||
{
|
||||
whox.Account = message.Parameters[fieldIdx];
|
||||
fieldIdx++;
|
||||
}
|
||||
|
||||
if ((fields & WhoxField.OpLevel) != 0)
|
||||
{
|
||||
whox.OpLevel = message.Parameters[fieldIdx];
|
||||
fieldIdx++;
|
||||
}
|
||||
|
||||
if ((fields & WhoxField.RealName) != 0)
|
||||
{
|
||||
whox.User.RealName = message.Parameters[fieldIdx];
|
||||
fieldIdx++;
|
||||
}
|
||||
}
|
||||
while (fieldIdx < message.Parameters.Length - 1);
|
||||
whoxList.Add(whox);
|
||||
}
|
||||
}
|
||||
|
||||
public static void HandleWhoEnd(IrcClient client, IrcMessage message)
|
||||
{
|
||||
var query = client.RequestManager.PendingOperations.Where(kvp => kvp.Key.StartsWith("WHO " + message.Parameters[1])).FirstOrDefault();
|
||||
var request = client.RequestManager.DequeueOperation(query.Key);
|
||||
var whoxList = (List<ExtendedWho>)request.State;
|
||||
|
||||
foreach (var whox in whoxList)
|
||||
if (!client.Users.Contains(whox.User.Nick))
|
||||
client.Users.Add(whox.User);
|
||||
|
||||
request.Callback?.Invoke(request);
|
||||
client.OnWhoxReceived(new Events.WhoxReceivedEventArgs(whoxList.ToArray()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace ChatSharp
|
||||
|
@ -145,6 +146,35 @@ namespace ChatSharp
|
|||
SendRawMessage("WHOIS {0}", nick);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an extended WHO query asking for specific information about a single user
|
||||
/// or the users in a channel, and runs a callback when we have received the response.
|
||||
/// </summary>
|
||||
public void Who(string target, WhoxFlag flags, WhoxField fields, Action<ExtendedWho> callback)
|
||||
{
|
||||
if (ServerInfo.ExtendedWho)
|
||||
{
|
||||
var whox = new List<ExtendedWho>();
|
||||
|
||||
// Generate random querytype for WHO query
|
||||
int queryType = RandomNumber.Next(0, 999);
|
||||
|
||||
// Add the querytype field if it wasn't defined
|
||||
var _fields = fields;
|
||||
if ((fields & WhoxField.QueryType) == 0)
|
||||
_fields |= WhoxField.QueryType;
|
||||
|
||||
string whoQuery = string.Format("WHO {0} {1}%{2},{3}", target, flags.AsString(), _fields.AsString(), queryType);
|
||||
string queryKey = string.Format("WHO {0} {1} {2:D}", target, queryType, _fields);
|
||||
|
||||
RequestManager.QueueOperation(queryKey, new RequestOperation(whox, ro =>
|
||||
{
|
||||
callback?.Invoke((ExtendedWho)ro.State);
|
||||
}));
|
||||
SendRawMessage(whoQuery);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests the mode of a channel from the server.
|
||||
/// </summary>
|
||||
|
|
|
@ -43,6 +43,8 @@ namespace ChatSharp
|
|||
return new DateTime(1970, 1, 1).AddSeconds(time);
|
||||
}
|
||||
|
||||
internal static Random RandomNumber { get; private set; }
|
||||
|
||||
private const int ReadBufferLength = 1024;
|
||||
|
||||
private byte[] ReadBuffer { get; set; }
|
||||
|
@ -170,6 +172,8 @@ namespace ChatSharp
|
|||
});
|
||||
|
||||
IsNegotiatingCapabilities = false;
|
||||
|
||||
RandomNumber = new Random();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -571,5 +575,13 @@ namespace ChatSharp
|
|||
{
|
||||
if (UserQuit != null) UserQuit(this, e);
|
||||
}
|
||||
/// <summary>
|
||||
/// Occurs when a WHO (WHOX protocol) is received.
|
||||
/// </summary>
|
||||
public event EventHandler<WhoxReceivedEventArgs> WhoxReceived;
|
||||
internal void OnWhoxReceived(WhoxReceivedEventArgs e)
|
||||
{
|
||||
WhoxReceived?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace ChatSharp
|
|||
PendingOperations = new Dictionary<string, RequestOperation>();
|
||||
}
|
||||
|
||||
private Dictionary<string, RequestOperation> PendingOperations { get; set; }
|
||||
internal Dictionary<string, RequestOperation> PendingOperations { get; private set; }
|
||||
|
||||
public void QueueOperation(string key, RequestOperation operation)
|
||||
{
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace ChatSharp
|
|||
Prefixes = new[] { "ovhaq", "@+%&~" };
|
||||
SupportedChannelModes = new ChannelModes();
|
||||
IsGuess = true;
|
||||
ExtendedWho = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -116,6 +117,10 @@ namespace ChatSharp
|
|||
/// Set to the maximum length of an away message
|
||||
/// </summary>
|
||||
public int? MaxAwayLength { get; set; }
|
||||
/// <summary>
|
||||
/// Server supports WHOX (WHO extension)
|
||||
/// </summary>
|
||||
public bool ExtendedWho { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Modes a server supports that are applicable to channels.
|
||||
|
|
|
@ -0,0 +1,267 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
namespace ChatSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// The results of an IRC WHO (WHOX protocol) query. Depending on what information you request,
|
||||
/// some of these fields may be null.
|
||||
/// </summary>
|
||||
public class ExtendedWho
|
||||
{
|
||||
internal ExtendedWho()
|
||||
{
|
||||
QueryType = -1;
|
||||
Channel = "*";
|
||||
User = new IrcUser();
|
||||
IP = string.Empty;
|
||||
Server = string.Empty;
|
||||
Flags = string.Empty;
|
||||
Hops = -1;
|
||||
TimeIdle = -1;
|
||||
Account = "*";
|
||||
OpLevel = "n/a";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of the query. Defaults to a randomly generated number so ChatSharp can keep
|
||||
/// track of WHOX queries it issues.
|
||||
/// </summary>
|
||||
public int QueryType { get; internal set; }
|
||||
/// <summary>
|
||||
/// Channel name
|
||||
/// </summary>
|
||||
public string Channel { get; internal set; }
|
||||
/// <summary>
|
||||
/// User
|
||||
/// </summary>
|
||||
public IrcUser User { get; internal set; }
|
||||
/// <summary>
|
||||
/// Numeric IP address of the user (unresolved hostname)
|
||||
/// </summary>
|
||||
public string IP { get; internal set; }
|
||||
/// <summary>
|
||||
/// Server name
|
||||
/// </summary>
|
||||
public string Server { get; internal set; }
|
||||
/// <summary>
|
||||
/// User flags
|
||||
/// </summary>
|
||||
public string Flags { get; internal set; }
|
||||
/// <summary>
|
||||
/// Distance, in hops
|
||||
/// </summary>
|
||||
public int Hops { get; internal set; }
|
||||
/// <summary>
|
||||
/// Time the user has been idle for
|
||||
/// </summary>
|
||||
public int TimeIdle { get; internal set; }
|
||||
/// <summary>
|
||||
/// User account name (NickServ or similar)
|
||||
/// </summary>
|
||||
public string Account { get; internal set; }
|
||||
/// <summary>
|
||||
/// OP level of the user in the channel
|
||||
/// </summary>
|
||||
public string OpLevel { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Field matching flags for WHOX protocol.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum WhoxFlag
|
||||
{
|
||||
/// <summary>
|
||||
/// Do not match any flag at all. By doing so, ircds defaults to 'nuhsr'
|
||||
/// (everything except the numeric IP).
|
||||
/// </summary>
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// Matches nick (in nick!user@host)
|
||||
/// </summary>
|
||||
Nick = 1,
|
||||
/// <summary>
|
||||
/// Matches username (in nick!user@host)
|
||||
/// </summary>
|
||||
Username = 2,
|
||||
/// <summary>
|
||||
/// Matches hostname (in nick!user@host)
|
||||
/// </summary>
|
||||
Hostname = 4,
|
||||
/// <summary>
|
||||
/// Matches numeric IPs
|
||||
/// </summary>
|
||||
NumericIp = 8,
|
||||
/// <summary>
|
||||
/// Matches server name
|
||||
/// </summary>
|
||||
ServerName = 16,
|
||||
/// <summary>
|
||||
/// Matches informational text
|
||||
/// </summary>
|
||||
Info = 32,
|
||||
/// <summary>
|
||||
/// Matches account name
|
||||
/// </summary>
|
||||
AccountName = 64,
|
||||
/// <summary>
|
||||
/// Matches visible and invisble users in a channel
|
||||
/// </summary>
|
||||
DelayedChanMembers = 128,
|
||||
/// <summary>
|
||||
/// Matches IRC operators
|
||||
/// </summary>
|
||||
IrcOp = 256,
|
||||
/// <summary>
|
||||
/// Special purpose flag, normally only IRC ops have access to it.
|
||||
/// </summary>
|
||||
Special = 512,
|
||||
/// <summary>
|
||||
/// Matches all of the flags defined.
|
||||
/// </summary>
|
||||
All = ~0
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information fields for WHOX protocol.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum WhoxField
|
||||
{
|
||||
/// <summary>
|
||||
/// Do not include any field at all.
|
||||
/// By doing so, ircds defaults to sending a normal WHO reply.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// Includes the querytype in the reply
|
||||
/// </summary>
|
||||
QueryType = 1,
|
||||
/// <summary>
|
||||
/// Includes the first channel name
|
||||
/// </summary>
|
||||
Channel = 2,
|
||||
/// <summary>
|
||||
/// Includes the userID (username)
|
||||
/// </summary>
|
||||
Username = 4,
|
||||
/// <summary>
|
||||
/// Includes the IP
|
||||
/// </summary>
|
||||
UserIp = 8,
|
||||
/// <summary>
|
||||
/// Includes the user's hostname
|
||||
/// </summary>
|
||||
Hostname = 16,
|
||||
/// <summary>
|
||||
/// Includes the server name
|
||||
/// </summary>
|
||||
ServerName = 32,
|
||||
/// <summary>
|
||||
/// Includes the user's nick
|
||||
/// </summary>
|
||||
Nick = 64,
|
||||
/// <summary>
|
||||
/// Includes all flags a user has
|
||||
/// </summary>
|
||||
Flags = 128,
|
||||
/// <summary>
|
||||
/// Includes the "distance" in hops
|
||||
/// </summary>
|
||||
Hops = 256,
|
||||
/// <summary>
|
||||
/// Includes the idle time (0 for remote users)
|
||||
/// </summary>
|
||||
TimeIdle = 512,
|
||||
/// <summary>
|
||||
/// Includes the user's account name
|
||||
/// </summary>
|
||||
AccountName = 1024,
|
||||
/// <summary>
|
||||
/// Includes the user's op level in the channel
|
||||
/// </summary>
|
||||
OpLevel = 2048,
|
||||
/// <summary>
|
||||
/// Includes the user's real name
|
||||
/// </summary>
|
||||
RealName = 4096,
|
||||
/// <summary>
|
||||
/// Includes all fields defined
|
||||
/// </summary>
|
||||
All = ~0
|
||||
}
|
||||
|
||||
internal static class WhoxEnumExtensions
|
||||
{
|
||||
public static string AsString(this WhoxFlag flag)
|
||||
{
|
||||
// nuhisradox
|
||||
var result = string.Empty;
|
||||
if ((flag & WhoxFlag.Nick) != 0)
|
||||
result += 'n';
|
||||
if ((flag & WhoxFlag.Username) != 0)
|
||||
result += 'u';
|
||||
if ((flag & WhoxFlag.Hostname) != 0)
|
||||
result += 'h';
|
||||
if ((flag & WhoxFlag.NumericIp) != 0)
|
||||
result += 'i';
|
||||
if ((flag & WhoxFlag.ServerName) != 0)
|
||||
result += 's';
|
||||
if ((flag & WhoxFlag.Info) != 0)
|
||||
result += 'r';
|
||||
if ((flag & WhoxFlag.AccountName) != 0)
|
||||
result += 'a';
|
||||
if ((flag & WhoxFlag.DelayedChanMembers) != 0)
|
||||
result += 'd';
|
||||
if ((flag & WhoxFlag.IrcOp) != 0)
|
||||
result += 'o';
|
||||
if ((flag & WhoxFlag.Special) != 0)
|
||||
result += 'x';
|
||||
|
||||
if (flag == WhoxFlag.None)
|
||||
result = string.Empty;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string AsString(this WhoxField field)
|
||||
{
|
||||
// cdfhilnrstuao
|
||||
var result = string.Empty;
|
||||
|
||||
if ((field & WhoxField.Channel) != 0)
|
||||
result += 'c';
|
||||
if ((field & WhoxField.Hops) != 0)
|
||||
result += 'd';
|
||||
if ((field & WhoxField.Flags) != 0)
|
||||
result += 'f';
|
||||
if ((field & WhoxField.Hostname) != 0)
|
||||
result += 'h';
|
||||
if ((field & WhoxField.UserIp) != 0)
|
||||
result += 'i';
|
||||
if ((field & WhoxField.TimeIdle) != 0)
|
||||
result += 'l';
|
||||
if ((field & WhoxField.Nick) != 0)
|
||||
result += 'n';
|
||||
if ((field & WhoxField.RealName) != 0)
|
||||
result += 'r';
|
||||
if ((field & WhoxField.ServerName) != 0)
|
||||
result += 's';
|
||||
if ((field & WhoxField.QueryType) != 0)
|
||||
result += 't';
|
||||
if ((field & WhoxField.Username) != 0)
|
||||
result += 'u';
|
||||
if ((field & WhoxField.AccountName) != 0)
|
||||
result += 'a';
|
||||
if ((field & WhoxField.OpLevel) != 0)
|
||||
result += 'o';
|
||||
|
||||
if (field == WhoxField.None)
|
||||
result = string.Empty;
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue