mirror of
https://github.com/deavmi/birchwood
synced 2024-09-20 14:03:05 +02:00
Merge branch 'deavmi:master' into master
This commit is contained in:
commit
622d6e509d
@ -40,8 +40,10 @@ You can take a look at the `Client` API documentation on [DUB](https://birchwood
|
||||
|
||||
## Compatibility
|
||||
|
||||
- [ ] rfc1459
|
||||
* Should be more or less stable in supporting this standard
|
||||
- [x] [rfc1459](https://www.rfc-editor.org/rfc/rfc1459)
|
||||
* Supports all the numeric codes
|
||||
- [x] [rfc2812](https://www.rfc-editor.org/rfc/rfc2812)
|
||||
* Supports all the numeric codes
|
||||
|
||||
More standards will be added within the next month or so, mostly relating to new response codes that just need to be added.
|
||||
|
||||
|
@ -71,42 +71,103 @@ public class Client : Thread
|
||||
//TODO: Do something here, tare downs
|
||||
}
|
||||
|
||||
// TODO: Investigate
|
||||
public ConnectionInfo getConnInfo()
|
||||
{
|
||||
return connInfo;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* User overridable handler functions below
|
||||
*/
|
||||
* Called on reception of a channel message
|
||||
*
|
||||
* Params:
|
||||
* fullMessage = the channel message in its entirety
|
||||
* channel = the channel
|
||||
* msgBody = the body of the message
|
||||
*/
|
||||
public void onChannelMessage(Message fullMessage, string channel, string msgBody)
|
||||
{
|
||||
/* Default implementation */
|
||||
logger.log("Channel("~channel~"): "~msgBody);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on reception of a direct message
|
||||
*
|
||||
* Params:
|
||||
* fullMessage = the direct message in its entirety
|
||||
* nickname = the sender
|
||||
* msgBody = the body of the message
|
||||
*/
|
||||
public void onDirectMessage(Message fullMessage, string nickname, string msgBody)
|
||||
{
|
||||
/* Default implementation */
|
||||
logger.log("DirectMessage("~nickname~"): "~msgBody);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on generic commands
|
||||
*
|
||||
* Params:
|
||||
* commandReply = the generic message
|
||||
*/
|
||||
public void onGenericCommand(Message message)
|
||||
{
|
||||
/* Default implementation */
|
||||
logger.log("Generic("~message.getCommand()~", "~message.getFrom()~"): "~message.getParams());
|
||||
}
|
||||
|
||||
|
||||
// TODO: Hook certain ones default style with an implemenation
|
||||
// ... for things that the client can learn from
|
||||
// TODO: comment
|
||||
/**
|
||||
* Called on command replies
|
||||
*
|
||||
* Params:
|
||||
* commandReply = the command's reply
|
||||
*/
|
||||
public void onCommandReply(Message commandReply)
|
||||
{
|
||||
// TODO: Add numeric response check here for CERTAIN ones which add to client
|
||||
// ... state
|
||||
|
||||
/* Default implementation */
|
||||
logger.log("Response("~to!(string)(commandReply.getReplyType())~", "~commandReply.getFrom()~"): "~commandReply.toString());
|
||||
|
||||
import birchwood.protocol.constants : ReplyType;
|
||||
|
||||
if(commandReply.getReplyType() == ReplyType.RPL_BOUNCE)
|
||||
{
|
||||
// TODO: Testing code was here
|
||||
// logger.log();
|
||||
// logger.log("<<<>>>");
|
||||
|
||||
// logger.log("Take a look:\n\n"~commandReply.getParams());
|
||||
|
||||
// logger.log("And here is key-value pairs: ", commandReply.getKVPairs());
|
||||
// logger.log("And here is array: ", commandReply.getPairs());
|
||||
|
||||
// // TODO: DLog bug, this prints nothing
|
||||
// logger.log("And here is trailing: ", commandReply.getTrailing());
|
||||
|
||||
// import std.stdio;
|
||||
// writeln("Trailer: "~commandReply.getTrailing());
|
||||
|
||||
// writeln(cast(ubyte[])commandReply.getTrailing());
|
||||
|
||||
// logger.log("<<<>>>");
|
||||
// logger.log();
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* User operations (request-response type)
|
||||
*/
|
||||
|
||||
// TODO: Add joinChannels(strung[])
|
||||
|
||||
/**
|
||||
* Joins the requested channel
|
||||
*
|
||||
@ -138,6 +199,75 @@ public class Client : Thread
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins the requested channels
|
||||
*
|
||||
* Params:
|
||||
* channels = the channels to join
|
||||
* Throws:
|
||||
* BirchwoodException on invalid channel name
|
||||
*/
|
||||
public void joinChannel(string[] channels)
|
||||
{
|
||||
/* If single channel */
|
||||
if(channels.length == 1)
|
||||
{
|
||||
/* Join the channel */
|
||||
joinChannel(channels[0]);
|
||||
}
|
||||
/* If multiple channels */
|
||||
else if(channels.length > 1)
|
||||
{
|
||||
string channelLine = channels[0];
|
||||
|
||||
/* Ensure valid characters in first channel */
|
||||
if(isValidText(channelLine))
|
||||
{
|
||||
//TODO: Add check for #
|
||||
|
||||
/* Append on a trailing `,` */
|
||||
channelLine ~= ",";
|
||||
|
||||
for(ulong i = 1; i < channels.length; i++)
|
||||
{
|
||||
string currentChannel = channels[i];
|
||||
|
||||
/* Ensure the character channel is valid */
|
||||
if(isValidText(currentChannel))
|
||||
{
|
||||
//TODO: Add check for #
|
||||
|
||||
if(i == channels.length-1)
|
||||
{
|
||||
channelLine~=currentChannel;
|
||||
}
|
||||
else
|
||||
{
|
||||
channelLine~=currentChannel~",";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BirchwoodException(BirchwoodException.ErrorType.ILLEGAL_CHARACTERS);
|
||||
}
|
||||
}
|
||||
|
||||
/* Join multiple channels */
|
||||
Message joinMessage = new Message("", "JOIN", channelLine);
|
||||
sendMessage(joinMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BirchwoodException(BirchwoodException.ErrorType.ILLEGAL_CHARACTERS);
|
||||
}
|
||||
}
|
||||
/* If no channels provided at all (error) */
|
||||
else
|
||||
{
|
||||
throw new BirchwoodException(BirchwoodException.ErrorType.EMPTY_PARAMS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parts from a list of channel(s) in one go
|
||||
*
|
||||
@ -500,6 +630,14 @@ public class Client : Thread
|
||||
|
||||
if(cmp(command, "PRIVMSG") == 0)
|
||||
{
|
||||
// TODO: We will need a non kv pair thing as well to see (in the
|
||||
// ... case of channel messages) the singular pair <channel>
|
||||
// ... name.
|
||||
//
|
||||
// Then our message will be in `getTrailing()`
|
||||
logger.debug_("PrivMessage parser (kv-pairs): ", ircMessage.getKVPairs());
|
||||
logger.debug_("PrivMessage parser (trailing): ", ircMessage.getTrailing());
|
||||
|
||||
/* Split up into (channel/nick) and (message)*/
|
||||
long firstSpaceIdx = indexOf(params, " "); //TODO: validity check;
|
||||
string chanNick = params[0..firstSpaceIdx];
|
||||
@ -524,6 +662,9 @@ public class Client : Thread
|
||||
// If the command is numeric then it is a reply of some sorts
|
||||
else if(ircMessage.isResponseMessage())
|
||||
{
|
||||
// TODO: Add numeric response check here for CERTAIN ones which add to client
|
||||
// ... state
|
||||
|
||||
/* Call the command reply handler */
|
||||
onCommandReply(ircMessage);
|
||||
}
|
||||
@ -632,25 +773,6 @@ public class Client : Thread
|
||||
receiver.rq(message);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Sends a message to the server by enqueuing it on
|
||||
// * the client-side send queue
|
||||
// *
|
||||
// * Params:
|
||||
// * messageOut = the message to send
|
||||
// */
|
||||
// private void sendMessage(string messageOut)
|
||||
// {
|
||||
// // TODO: Do message splits here
|
||||
|
||||
|
||||
// /* Encode the mesage */
|
||||
// ubyte[] encodedMessage = encodeMessage(messageOut);
|
||||
|
||||
// /* Enqueue the message to the send queue */
|
||||
// sender.sq(encodedMessage);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Sends a message to the server by enqueuing it on
|
||||
* the client-side send queue.
|
||||
@ -900,9 +1022,11 @@ public class Client : Thread
|
||||
client.joinChannel("#birchwood");
|
||||
// TODO: Add a joinChannels(string[])
|
||||
client.joinChannel("#birchwood2");
|
||||
client.joinChannel("#birchwoodLeave1");
|
||||
client.joinChannel("#birchwoodLeave2");
|
||||
client.joinChannel("#birchwoodLeave3");
|
||||
|
||||
client.joinChannel(["#birchwoodLeave1", "#birchwoodLeave2", "#birchwoodLeave3"]);
|
||||
// client.joinChannel("#birchwoodLeave1");
|
||||
// client.joinChannel("#birchwoodLeave2");
|
||||
// client.joinChannel("#birchwoodLeave3");
|
||||
|
||||
Thread.sleep(dur!("seconds")(2));
|
||||
client.command(new Message("", "NAMES", "")); // TODO: add names commdn
|
||||
@ -978,4 +1102,6 @@ public class Client : Thread
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -53,6 +53,13 @@ public final class ReceiverThread : Thread
|
||||
}
|
||||
|
||||
// TODO: Rename to `receiveQ`
|
||||
/**
|
||||
* Enqueues the raw message into the receieve queue
|
||||
* for eventual processing
|
||||
*
|
||||
* Params:
|
||||
* encodedMessage = the message to enqueue
|
||||
*/
|
||||
public void rq(ubyte[] encodedMessage)
|
||||
{
|
||||
/* Lock queue */
|
||||
@ -117,29 +124,62 @@ public final class ReceiverThread : Thread
|
||||
/* Lock the receieve queue */
|
||||
recvQueueLock.lock();
|
||||
|
||||
/* Message being analysed */
|
||||
Message curMsg;
|
||||
/* Parsed messages */
|
||||
SList!(Message) currentMessageQueue;
|
||||
|
||||
/* Search for a PING */
|
||||
ubyte[] pingMessage;
|
||||
|
||||
ulong pos = 0;
|
||||
/**
|
||||
* Parse all messages and save them
|
||||
* into the above array
|
||||
*/
|
||||
foreach(ubyte[] message; recvQueue[])
|
||||
{
|
||||
// FIXME: Holy shit this is funny (see https://github.com/deavmi/birchwood/issues/13)
|
||||
if(indexOf(cast(string)message, "PING") > -1)
|
||||
{
|
||||
pingMessage = message;
|
||||
recvQueue.linearRemoveElement(message);
|
||||
/* Decode the message */
|
||||
string decodedMessage = decodeMessage(message);
|
||||
|
||||
import std.stdio;
|
||||
writeln("\n\nHOLY SHIT\n: "~cast(string)(message)~"\n\n");
|
||||
/* Parse the message */
|
||||
Message parsedMessage = Message.parseReceivedMessage(decodedMessage);
|
||||
|
||||
/* Save it */
|
||||
currentMessageQueue.insertAfter(currentMessageQueue[], parsedMessage);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Search for any PING messages, then store it if so
|
||||
* and remove it so it isn't processed again later
|
||||
*/
|
||||
Message pingMessage;
|
||||
foreach(Message curMsg; currentMessageQueue[])
|
||||
{
|
||||
import std.string : cmp;
|
||||
if(cmp(curMsg.getCommand(), "PING") == 0)
|
||||
{
|
||||
currentMessageQueue.linearRemoveElement(curMsg);
|
||||
pingMessage = curMsg;
|
||||
break;
|
||||
}
|
||||
|
||||
pos++;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we have a PING then respond with a PONG
|
||||
*/
|
||||
if(pingMessage !is null)
|
||||
{
|
||||
logger.log("Found a ping: "~pingMessage.toString());
|
||||
|
||||
/* Extract the PING ID */
|
||||
string pingID = pingMessage.getParams();
|
||||
|
||||
/* Spawn a PONG event */
|
||||
EventyEvent pongEvent = new PongEvent(pingID);
|
||||
client.engine.push(pongEvent);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* TODO: Plan of action
|
||||
@ -156,62 +196,26 @@ public final class ReceiverThread : Thread
|
||||
* - we can cache or remember stuff when we get 353
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
/* If we found a PING */
|
||||
if(pingMessage.length > 0)
|
||||
{
|
||||
/* Decode the message and parse it */
|
||||
curMsg = Message.parseReceivedMessage(decodeMessage(pingMessage));
|
||||
logger.log("Found a ping: "~curMsg.toString());
|
||||
|
||||
// string ogMessage = cast(string)pingMessage;
|
||||
// long idxSigStart = indexOf(ogMessage, ":")+1;
|
||||
// long idxSigEnd = lastIndexOf(ogMessage, '\r');
|
||||
|
||||
// string pingID = ogMessage[idxSigStart..idxSigEnd];
|
||||
string pingID = curMsg.getParams();
|
||||
|
||||
|
||||
// this.socket.send(encodeMessage("PONG "~pingID));
|
||||
// string messageToSend = "PONG "~pingID;
|
||||
|
||||
// sendMessage(messageToSend);
|
||||
|
||||
// logger.log("Ponged");
|
||||
|
||||
/* TODO: Implement */
|
||||
// TODO: Remove the Eventy push and replace with a handler call (on second thought no)
|
||||
EventyEvent pongEvent = new PongEvent(pingID);
|
||||
client.engine.push(pongEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process each message remaining in the queue now
|
||||
* till it is empty
|
||||
*/
|
||||
while(!recvQueue.empty())
|
||||
while(!currentMessageQueue.empty())
|
||||
{
|
||||
ubyte[] message = recvQueue.front();
|
||||
|
||||
/* Decode message */
|
||||
string messageNormal = decodeMessage(message);
|
||||
|
||||
recvQueue.linearRemoveElement(recvQueue.front());
|
||||
|
||||
// writeln("Normal message: "~messageNormal);
|
||||
|
||||
|
||||
|
||||
/* TODO: Parse message and call correct handler */
|
||||
curMsg = Message.parseReceivedMessage(messageNormal);
|
||||
/* Get the frontmost Message */
|
||||
Message curMsg = currentMessageQueue.front();
|
||||
|
||||
// TODO: Remove the Eventy push and replace with a handler call (on second thought no)
|
||||
EventyEvent ircEvent = new IRCEvent(curMsg);
|
||||
client.engine.push(ircEvent);
|
||||
|
||||
/* Remove the message from the queue */
|
||||
currentMessageQueue.linearRemoveElement(curMsg);
|
||||
}
|
||||
|
||||
/* Clear the receive queue */
|
||||
recvQueue.clear();
|
||||
|
||||
/* Unlock the receive queue */
|
||||
recvQueueLock.unlock();
|
||||
}
|
||||
|
@ -48,6 +48,13 @@ public final class SenderThread : Thread
|
||||
}
|
||||
|
||||
// TODO: Rename to `sendQ`
|
||||
/**
|
||||
* Enqueues the raw message into the send queue
|
||||
* for eventual sending
|
||||
*
|
||||
* Params:
|
||||
* encodedMessage = the message to enqueue
|
||||
*/
|
||||
public void sq(ubyte[] encodedMessage)
|
||||
{
|
||||
/* Lock queue */
|
||||
|
@ -1,8 +1,15 @@
|
||||
/**
|
||||
* COnfiguration-related types
|
||||
*/
|
||||
module birchwood.config.conninfo;
|
||||
|
||||
import std.socket : SocketException, Address, getAddress;
|
||||
import birchwood.client.exceptions : BirchwoodException;
|
||||
|
||||
/**
|
||||
* Represents the connection details for a server
|
||||
* to connect to
|
||||
*/
|
||||
public struct ConnectionInfo
|
||||
{
|
||||
/* Server address information */
|
||||
@ -36,16 +43,32 @@ public struct ConnectionInfo
|
||||
return this.bulkReadSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the address of the endpoint server
|
||||
*
|
||||
* Returns: the server's address
|
||||
*/
|
||||
public Address getAddr()
|
||||
{
|
||||
return addrInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the chosen fake lag
|
||||
*
|
||||
* Returns: the fake lag in seconds
|
||||
*/
|
||||
public ulong getFakeLag()
|
||||
{
|
||||
return fakeLag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the fake lag in seconds
|
||||
*
|
||||
* Params:
|
||||
* fakeLag = the fake lag to use
|
||||
*/
|
||||
public void setFakeLag(ulong fakeLag)
|
||||
{
|
||||
this.fakeLag = fakeLag;
|
||||
|
@ -1,148 +1,169 @@
|
||||
module birchwood.protocol.constants;
|
||||
|
||||
/* Reply object */
|
||||
public enum ReplyType : ulong
|
||||
{
|
||||
/* Error replies */
|
||||
ERR_NOSUCHNICK = 401,
|
||||
ERR_NOSUCHSERVER = 402,
|
||||
ERR_NOSUCHCHANNEL = 403,
|
||||
ERR_CANNOTSENDTOCHAN = 404,
|
||||
ERR_TOOMANYCHANNELS = 405,
|
||||
ERR_WASNOSUCHNICK = 406,
|
||||
ERR_TOOMANYTARGETS = 407,
|
||||
ERR_NOORIGIN = 409,
|
||||
ERR_NORECIPIENT = 411,
|
||||
ERR_NOTEXTTOSEND = 412,
|
||||
ERR_NOTOPLEVEL = 413,
|
||||
ERR_WILDTOPLEVEL = 414,
|
||||
ERR_UNKNOWNCOMMAND = 421,
|
||||
ERR_NOMOTD = 422,
|
||||
ERR_NOADMININFO = 423,
|
||||
ERR_FILEERROR = 424,
|
||||
ERR_NONICKNAMEGIVEN = 431,
|
||||
ERR_ERRONEUSNICKNAME = 432,
|
||||
ERR_NICKNAMEINUSE = 433,
|
||||
ERR_NICKCOLLISION = 436,
|
||||
ERR_USERNOTINCHANNEL = 441,
|
||||
ERR_NOTONCHANNEL = 442,
|
||||
ERR_USERONCHANNEL = 443,
|
||||
ERR_NOLOGIN = 444,
|
||||
ERR_SUMMONDISABLED = 445,
|
||||
ERR_USERSDISABLED = 446,
|
||||
ERR_NOTREGISTERED = 451,
|
||||
ERR_NEEDMOREPARAMS = 461,
|
||||
ERR_ALREADYREGISTRED = 462,
|
||||
ERR_NOPERMFORHOST = 463,
|
||||
ERR_PASSWDMISMATCH = 464,
|
||||
ERR_YOUREBANNEDCREEP = 465,
|
||||
ERR_KEYSET = 467,
|
||||
ERR_CHANNELISFULL = 471,
|
||||
ERR_UNKNOWNMODE = 472,
|
||||
ERR_INVITEONLYCHAN = 473,
|
||||
ERR_BANNEDFROMCHAN = 474,
|
||||
ERR_BADCHANNELKEY = 475,
|
||||
ERR_NOPRIVILEGES = 481,
|
||||
ERR_CHANOPRIVSNEEDED = 482,
|
||||
ERR_CANTKILLSERVER = 483,
|
||||
ERR_NOOPERHOST = 491,
|
||||
ERR_UMODEUNKNOWNFLAG = 501,
|
||||
ERR_USERSDONTMATCH = 502,
|
||||
/**
|
||||
* The type of numeric response
|
||||
*/
|
||||
public enum ReplyType : ulong
|
||||
{
|
||||
/**
|
||||
* rfc 1459
|
||||
*/
|
||||
|
||||
/* Command responses */
|
||||
RPL_NONE = 300,
|
||||
RPL_USERHOST = 302,
|
||||
RPL_ISON = 303,
|
||||
RPL_AWAY = 301,
|
||||
RPL_UNAWAY = 305,
|
||||
RPL_NOWAWAY = 306,
|
||||
RPL_WHOISUSER = 311,
|
||||
RPL_WHOISSERVER = 312,
|
||||
RPL_WHOISOPERATOR = 313,
|
||||
RPL_WHOISIDLE = 317,
|
||||
RPL_ENDOFWHOIS = 318,
|
||||
RPL_WHOISCHANNELS = 319,
|
||||
RPL_WHOWASUSER = 314,
|
||||
RPL_ENDOFWHOWAS = 369,
|
||||
RPL_LISTSTART = 321,
|
||||
RPL_LIST = 322,
|
||||
RPL_LISTEND = 323,
|
||||
RPL_CHANNELMODEIS = 324,
|
||||
RPL_NOTOPIC = 331,
|
||||
RPL_TOPIC = 332,
|
||||
RPL_INVITING = 341,
|
||||
RPL_SUMMONING = 342,
|
||||
RPL_VERSION = 351,
|
||||
RPL_WHOREPLY = 352,
|
||||
RPL_ENDOFWHO = 315,
|
||||
RPL_NAMREPLY = 353,
|
||||
RPL_ENDOFNAMES = 366,
|
||||
RPL_LINKS = 364,
|
||||
RPL_ENDOFLINKS = 365,
|
||||
RPL_BANLIST = 367,
|
||||
RPL_ENDOFBANLIST = 368,
|
||||
RPL_INFO = 371,
|
||||
RPL_ENDOFINFO = 374,
|
||||
RPL_MOTDSTART = 375,
|
||||
RPL_MOTD = 372,
|
||||
RPL_ENDOFMOTD = 376,
|
||||
RPL_YOUREOPER = 381,
|
||||
RPL_REHASHING = 382,
|
||||
RPL_TIME = 391,
|
||||
RPL_USERSSTART = 392,
|
||||
RPL_USERS = 393,
|
||||
RPL_ENDOFUSERS = 394,
|
||||
RPL_NOUSERS = 395,
|
||||
RPL_TRACELINK = 200,
|
||||
RPL_TRACECONNECTING = 201,
|
||||
RPL_TRACEHANDSHAKE = 202,
|
||||
RPL_TRACEUNKNOWN = 203,
|
||||
RPL_TRACEOPERATOR = 204,
|
||||
RPL_TRACEUSER = 205,
|
||||
RPL_TRACESERVER = 206,
|
||||
RPL_TRACENEWTYPE = 208,
|
||||
RPL_TRACELOG = 261,
|
||||
RPL_STATSLINKINFO = 211,
|
||||
RPL_STATSCOMMANDS = 212,
|
||||
RPL_STATSCLINE = 213,
|
||||
RPL_STATSNLINE = 214,
|
||||
RPL_STATSILINE = 215,
|
||||
RPL_STATSKLINE = 216,
|
||||
RPL_STATSYLINE = 218,
|
||||
RPL_ENDOFSTATS = 219,
|
||||
RPL_STATSLLINE = 241,
|
||||
RPL_STATSUPTIME = 242,
|
||||
RPL_STATSOLINE = 243,
|
||||
RPL_STATSHLINE = 244,
|
||||
RPL_UMODEIS = 221,
|
||||
RPL_LUSERCLIENT = 251,
|
||||
RPL_LUSEROP = 252,
|
||||
RPL_LUSERUNKNOWN = 253,
|
||||
RPL_LUSERCHANNELS = 254,
|
||||
RPL_LUSERME = 255,
|
||||
RPL_ADMINME = 256,
|
||||
RPL_ADMINLOC1 = 257,
|
||||
RPL_ADMINLOC2 = 258,
|
||||
RPL_ADMINEMAIL = 259,
|
||||
/* Error replies */
|
||||
ERR_NOSUCHNICK = 401,
|
||||
ERR_NOSUCHSERVER = 402,
|
||||
ERR_NOSUCHCHANNEL = 403,
|
||||
ERR_CANNOTSENDTOCHAN = 404,
|
||||
ERR_TOOMANYCHANNELS = 405,
|
||||
ERR_WASNOSUCHNICK = 406,
|
||||
ERR_TOOMANYTARGETS = 407,
|
||||
ERR_NOORIGIN = 409,
|
||||
ERR_NORECIPIENT = 411,
|
||||
ERR_NOTEXTTOSEND = 412,
|
||||
ERR_NOTOPLEVEL = 413,
|
||||
ERR_WILDTOPLEVEL = 414,
|
||||
ERR_UNKNOWNCOMMAND = 421,
|
||||
ERR_NOMOTD = 422,
|
||||
ERR_NOADMININFO = 423,
|
||||
ERR_FILEERROR = 424,
|
||||
ERR_NONICKNAMEGIVEN = 431,
|
||||
ERR_ERRONEUSNICKNAME = 432,
|
||||
ERR_NICKNAMEINUSE = 433,
|
||||
ERR_NICKCOLLISION = 436,
|
||||
ERR_USERNOTINCHANNEL = 441,
|
||||
ERR_NOTONCHANNEL = 442,
|
||||
ERR_USERONCHANNEL = 443,
|
||||
ERR_NOLOGIN = 444,
|
||||
ERR_SUMMONDISABLED = 445,
|
||||
ERR_USERSDISABLED = 446,
|
||||
ERR_NOTREGISTERED = 451,
|
||||
ERR_NEEDMOREPARAMS = 461,
|
||||
ERR_ALREADYREGISTRED = 462,
|
||||
ERR_NOPERMFORHOST = 463,
|
||||
ERR_PASSWDMISMATCH = 464,
|
||||
ERR_YOUREBANNEDCREEP = 465,
|
||||
ERR_KEYSET = 467,
|
||||
ERR_CHANNELISFULL = 471,
|
||||
ERR_UNKNOWNMODE = 472,
|
||||
ERR_INVITEONLYCHAN = 473,
|
||||
ERR_BANNEDFROMCHAN = 474,
|
||||
ERR_BADCHANNELKEY = 475,
|
||||
ERR_NOPRIVILEGES = 481,
|
||||
ERR_CHANOPRIVSNEEDED = 482,
|
||||
ERR_CANTKILLSERVER = 483,
|
||||
ERR_NOOPERHOST = 491,
|
||||
ERR_UMODEUNKNOWNFLAG = 501,
|
||||
ERR_USERSDONTMATCH = 502,
|
||||
|
||||
/* Reserved Numerics (See section 6.3 in RFC 1459) */
|
||||
RPL_TRACECLASS = 209,
|
||||
RPL_SERVICEINFO = 231,
|
||||
RPL_SERVICE = 233,
|
||||
RPL_SERVLISTEND = 235,
|
||||
RPL_WHOISCHANOP = 316,
|
||||
RPL_CLOSING = 362,
|
||||
RPL_INFOSTART = 372,
|
||||
ERR_YOUWILLBEBANNED = 466,
|
||||
ERR_NOSERVICEHOST = 492,
|
||||
RPL_STATSQLINE = 217,
|
||||
RPL_ENDOFSERVICES = 232,
|
||||
RPL_SERVLIST = 234,
|
||||
RPL_KILLDONE = 361,
|
||||
RPL_CLOSEEND = 363,
|
||||
RPL_MYPORTIS = 384,
|
||||
ERR_BADCHANMASK = 476,
|
||||
/* Command responses */
|
||||
RPL_NONE = 300,
|
||||
RPL_USERHOST = 302,
|
||||
RPL_ISON = 303,
|
||||
RPL_AWAY = 301,
|
||||
RPL_UNAWAY = 305,
|
||||
RPL_NOWAWAY = 306,
|
||||
RPL_WHOISUSER = 311,
|
||||
RPL_WHOISSERVER = 312,
|
||||
RPL_WHOISOPERATOR = 313,
|
||||
RPL_WHOISIDLE = 317,
|
||||
RPL_ENDOFWHOIS = 318,
|
||||
RPL_WHOISCHANNELS = 319,
|
||||
RPL_WHOWASUSER = 314,
|
||||
RPL_ENDOFWHOWAS = 369,
|
||||
RPL_LISTSTART = 321,
|
||||
RPL_LIST = 322,
|
||||
RPL_LISTEND = 323,
|
||||
RPL_CHANNELMODEIS = 324,
|
||||
RPL_NOTOPIC = 331,
|
||||
RPL_TOPIC = 332,
|
||||
RPL_INVITING = 341,
|
||||
RPL_SUMMONING = 342,
|
||||
RPL_VERSION = 351,
|
||||
RPL_WHOREPLY = 352,
|
||||
RPL_ENDOFWHO = 315,
|
||||
RPL_NAMREPLY = 353,
|
||||
RPL_ENDOFNAMES = 366,
|
||||
RPL_LINKS = 364,
|
||||
RPL_ENDOFLINKS = 365,
|
||||
RPL_BANLIST = 367,
|
||||
RPL_ENDOFBANLIST = 368,
|
||||
RPL_INFO = 371,
|
||||
RPL_ENDOFINFO = 374,
|
||||
RPL_MOTDSTART = 375,
|
||||
RPL_MOTD = 372,
|
||||
RPL_ENDOFMOTD = 376,
|
||||
RPL_YOUREOPER = 381,
|
||||
RPL_REHASHING = 382,
|
||||
RPL_TIME = 391,
|
||||
RPL_USERSSTART = 392,
|
||||
RPL_USERS = 393,
|
||||
RPL_ENDOFUSERS = 394,
|
||||
RPL_NOUSERS = 395,
|
||||
RPL_TRACELINK = 200,
|
||||
RPL_TRACECONNECTING = 201,
|
||||
RPL_TRACEHANDSHAKE = 202,
|
||||
RPL_TRACEUNKNOWN = 203,
|
||||
RPL_TRACEOPERATOR = 204,
|
||||
RPL_TRACEUSER = 205,
|
||||
RPL_TRACESERVER = 206,
|
||||
RPL_TRACENEWTYPE = 208,
|
||||
RPL_TRACELOG = 261,
|
||||
RPL_STATSLINKINFO = 211,
|
||||
RPL_STATSCOMMANDS = 212,
|
||||
RPL_STATSCLINE = 213,
|
||||
RPL_STATSNLINE = 214,
|
||||
RPL_STATSILINE = 215,
|
||||
RPL_STATSKLINE = 216,
|
||||
RPL_STATSYLINE = 218,
|
||||
RPL_ENDOFSTATS = 219,
|
||||
RPL_STATSLLINE = 241,
|
||||
RPL_STATSUPTIME = 242,
|
||||
RPL_STATSOLINE = 243,
|
||||
RPL_STATSHLINE = 244,
|
||||
RPL_UMODEIS = 221,
|
||||
RPL_LUSERCLIENT = 251,
|
||||
RPL_LUSEROP = 252,
|
||||
RPL_LUSERUNKNOWN = 253,
|
||||
RPL_LUSERCHANNELS = 254,
|
||||
RPL_LUSERME = 255,
|
||||
RPL_ADMINME = 256,
|
||||
RPL_ADMINLOC1 = 257,
|
||||
RPL_ADMINLOC2 = 258,
|
||||
RPL_ADMINEMAIL = 259,
|
||||
|
||||
/* Reserved Numerics (See section 6.3 in RFC 1459) */
|
||||
RPL_TRACECLASS = 209,
|
||||
RPL_SERVICEINFO = 231,
|
||||
RPL_SERVICE = 233,
|
||||
RPL_SERVLISTEND = 235,
|
||||
RPL_WHOISCHANOP = 316,
|
||||
RPL_CLOSING = 362,
|
||||
RPL_INFOSTART = 372,
|
||||
ERR_YOUWILLBEBANNED = 466,
|
||||
ERR_NOSERVICEHOST = 492,
|
||||
RPL_STATSQLINE = 217,
|
||||
RPL_ENDOFSERVICES = 232,
|
||||
RPL_SERVLIST = 234,
|
||||
RPL_KILLDONE = 361,
|
||||
RPL_CLOSEEND = 363,
|
||||
RPL_MYPORTIS = 384,
|
||||
ERR_BADCHANMASK = 476,
|
||||
|
||||
|
||||
BIRCHWOOD_UNKNOWN_RESP_CODE = 0
|
||||
/**
|
||||
* rfc 2812
|
||||
*/
|
||||
RPL_WELCOME = 1,
|
||||
RPL_YOURHOST = 2,
|
||||
RPL_CREATED = 3,
|
||||
RPL_MYINFO = 4,
|
||||
RPL_BOUNCE = 5, // TODO: We care about the key-value pairs here in RPL_BOUNCE
|
||||
ERR_NOCHANMODES = 477,
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* If no code is matched then this is the default
|
||||
*/
|
||||
BIRCHWOOD_UNKNOWN_RESP_CODE = 0
|
||||
}
|
@ -138,6 +138,9 @@ public final class Message
|
||||
logger.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
/* Parse the parameters into key-value pairs (if any) and trailing text (if any) */
|
||||
parameterParse();
|
||||
}
|
||||
|
||||
/* TODO: Implement encoder function */
|
||||
@ -276,6 +279,192 @@ public final class Message
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the trailing text in the paramaters
|
||||
* (if any)
|
||||
*
|
||||
* Returns: the trailing text
|
||||
*/
|
||||
public string getTrailing()
|
||||
{
|
||||
return ppTrailing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parameters excluding the trailing text
|
||||
* which are seperated by spaces but only those
|
||||
* which are key-value pairs
|
||||
*
|
||||
* Returns: the key-value pair parameters
|
||||
*/
|
||||
public string[string] getKVPairs()
|
||||
{
|
||||
return ppKVPairs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parameters excluding the trailing text
|
||||
* which are seperated by spaces
|
||||
*
|
||||
* Returns: the parameters
|
||||
*/
|
||||
public string[] getPairs()
|
||||
{
|
||||
return ppPairs;
|
||||
}
|
||||
|
||||
private string ppTrailing;
|
||||
private string[string] ppKVPairs;
|
||||
private string[] ppPairs;
|
||||
|
||||
|
||||
version(unittest)
|
||||
{
|
||||
import std.stdio;
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
string testInput = "A:=1 A=2 :Hello this is text";
|
||||
writeln("Input: ", testInput);
|
||||
|
||||
bool hasTrailer;
|
||||
string[] splitted = splitting(testInput, hasTrailer);
|
||||
writeln("Input (split): ", splitted);
|
||||
|
||||
|
||||
|
||||
assert(cmp(splitted[0], "A:=1") == 0);
|
||||
assert(cmp(splitted[1], "A=2") == 0);
|
||||
|
||||
/* Trailer test */
|
||||
assert(hasTrailer);
|
||||
assert(cmp(splitted[2], "Hello this is text") == 0);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
string testInput = ":Hello this is text";
|
||||
bool hasTrailer;
|
||||
string[] splitted = splitting(testInput, hasTrailer);
|
||||
|
||||
/* Trailer test */
|
||||
assert(hasTrailer);
|
||||
assert(cmp(splitted[0], "Hello this is text") == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Imagine: `A:=1 A=2 :Hello`
|
||||
*
|
||||
* Params:
|
||||
* input =
|
||||
* Returns:
|
||||
*/
|
||||
private static string[] splitting(string input, ref bool hasTrailer)
|
||||
{
|
||||
string[] splits;
|
||||
|
||||
bool trailingMode;
|
||||
string buildUp;
|
||||
for(ulong idx = 0; idx < input.length; idx++)
|
||||
{
|
||||
/* Get current character */
|
||||
char curCHar = input[idx];
|
||||
|
||||
|
||||
if(trailingMode)
|
||||
{
|
||||
buildUp ~= curCHar;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(buildUp.length == 0)
|
||||
{
|
||||
if(curCHar == ':')
|
||||
{
|
||||
trailingMode = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(curCHar == ' ')
|
||||
{
|
||||
/* Flush */
|
||||
splits ~= buildUp;
|
||||
buildUp = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
buildUp ~= curCHar;
|
||||
}
|
||||
}
|
||||
|
||||
if(buildUp.length)
|
||||
{
|
||||
splits ~= buildUp;
|
||||
}
|
||||
|
||||
hasTrailer = trailingMode;
|
||||
|
||||
return splits;
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: This needs more work with trailing support
|
||||
* we must make sure we only look for lastInex of `:`
|
||||
* where it is first cyaracter after space but NOT within
|
||||
* an active parameter
|
||||
*/
|
||||
private void parameterParse()
|
||||
{
|
||||
/* Only parse if there are params */
|
||||
if(params.length)
|
||||
{
|
||||
/* Trailing text */
|
||||
string trailing;
|
||||
|
||||
/* Split the `<params>` */
|
||||
bool hasTrailer;
|
||||
string[] paramsSplit = splitting(params, hasTrailer);
|
||||
|
||||
logger.debug_("ParamsSPlit direct:", paramsSplit);
|
||||
|
||||
|
||||
|
||||
/* Extract the trailer as the last item in the array (if it exists) */
|
||||
if(hasTrailer)
|
||||
{
|
||||
trailing = paramsSplit[paramsSplit.length-1];
|
||||
|
||||
/* Remove it from the parameters */
|
||||
paramsSplit = paramsSplit[0..$-1];
|
||||
|
||||
logger.debug_("GOt railer ", trailing);
|
||||
}
|
||||
|
||||
ppPairs = paramsSplit;
|
||||
|
||||
|
||||
/* Generate the key-value pairs */
|
||||
foreach(string pair; paramsSplit)
|
||||
{
|
||||
/* Only do this if we have an `=` in the current pair */
|
||||
if(indexOf(pair, "=") > -1)
|
||||
{
|
||||
string key = split(pair, "=")[0];
|
||||
string value = split(pair, "=")[1];
|
||||
ppKVPairs[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/* Save the trailing */
|
||||
ppTrailing = trailing;
|
||||
|
||||
logger.debug_("ppTrailing: ", ppTrailing);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not this message was
|
||||
* a numeric response
|
||||
|
@ -1,6 +1,7 @@
|
||||
module birchwood.protocol;
|
||||
|
||||
public import birchwood.protocol.messages : Message;
|
||||
public import birchwood.protocol.constants : ReplyType;
|
||||
|
||||
// TODO: Look how to neaten up (if any)
|
||||
public import birchwood.protocol.formatting;
|
Loading…
Reference in New Issue
Block a user