mirror of
https://github.com/deavmi/birchwood
synced 2024-09-20 16:23:39 +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
|
## Compatibility
|
||||||
|
|
||||||
- [ ] rfc1459
|
- [x] [rfc1459](https://www.rfc-editor.org/rfc/rfc1459)
|
||||||
* Should be more or less stable in supporting this standard
|
* 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.
|
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: Do something here, tare downs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Investigate
|
||||||
public ConnectionInfo getConnInfo()
|
public ConnectionInfo getConnInfo()
|
||||||
{
|
{
|
||||||
return connInfo;
|
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)
|
public void onChannelMessage(Message fullMessage, string channel, string msgBody)
|
||||||
{
|
{
|
||||||
/* Default implementation */
|
/* Default implementation */
|
||||||
logger.log("Channel("~channel~"): "~msgBody);
|
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)
|
public void onDirectMessage(Message fullMessage, string nickname, string msgBody)
|
||||||
{
|
{
|
||||||
/* Default implementation */
|
/* Default implementation */
|
||||||
logger.log("DirectMessage("~nickname~"): "~msgBody);
|
logger.log("DirectMessage("~nickname~"): "~msgBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called on generic commands
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* commandReply = the generic message
|
||||||
|
*/
|
||||||
public void onGenericCommand(Message message)
|
public void onGenericCommand(Message message)
|
||||||
{
|
{
|
||||||
/* Default implementation */
|
/* Default implementation */
|
||||||
logger.log("Generic("~message.getCommand()~", "~message.getFrom()~"): "~message.getParams());
|
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)
|
public void onCommandReply(Message commandReply)
|
||||||
{
|
{
|
||||||
|
// TODO: Add numeric response check here for CERTAIN ones which add to client
|
||||||
|
// ... state
|
||||||
|
|
||||||
/* Default implementation */
|
/* Default implementation */
|
||||||
logger.log("Response("~to!(string)(commandReply.getReplyType())~", "~commandReply.getFrom()~"): "~commandReply.toString());
|
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)
|
* User operations (request-response type)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO: Add joinChannels(strung[])
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Joins the requested channel
|
* 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
|
* Parts from a list of channel(s) in one go
|
||||||
*
|
*
|
||||||
@ -500,6 +630,14 @@ public class Client : Thread
|
|||||||
|
|
||||||
if(cmp(command, "PRIVMSG") == 0)
|
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)*/
|
/* Split up into (channel/nick) and (message)*/
|
||||||
long firstSpaceIdx = indexOf(params, " "); //TODO: validity check;
|
long firstSpaceIdx = indexOf(params, " "); //TODO: validity check;
|
||||||
string chanNick = params[0..firstSpaceIdx];
|
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
|
// If the command is numeric then it is a reply of some sorts
|
||||||
else if(ircMessage.isResponseMessage())
|
else if(ircMessage.isResponseMessage())
|
||||||
{
|
{
|
||||||
|
// TODO: Add numeric response check here for CERTAIN ones which add to client
|
||||||
|
// ... state
|
||||||
|
|
||||||
/* Call the command reply handler */
|
/* Call the command reply handler */
|
||||||
onCommandReply(ircMessage);
|
onCommandReply(ircMessage);
|
||||||
}
|
}
|
||||||
@ -632,25 +773,6 @@ public class Client : Thread
|
|||||||
receiver.rq(message);
|
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
|
* Sends a message to the server by enqueuing it on
|
||||||
* the client-side send queue.
|
* the client-side send queue.
|
||||||
@ -900,9 +1022,11 @@ public class Client : Thread
|
|||||||
client.joinChannel("#birchwood");
|
client.joinChannel("#birchwood");
|
||||||
// TODO: Add a joinChannels(string[])
|
// TODO: Add a joinChannels(string[])
|
||||||
client.joinChannel("#birchwood2");
|
client.joinChannel("#birchwood2");
|
||||||
client.joinChannel("#birchwoodLeave1");
|
|
||||||
client.joinChannel("#birchwoodLeave2");
|
client.joinChannel(["#birchwoodLeave1", "#birchwoodLeave2", "#birchwoodLeave3"]);
|
||||||
client.joinChannel("#birchwoodLeave3");
|
// client.joinChannel("#birchwoodLeave1");
|
||||||
|
// client.joinChannel("#birchwoodLeave2");
|
||||||
|
// client.joinChannel("#birchwoodLeave3");
|
||||||
|
|
||||||
Thread.sleep(dur!("seconds")(2));
|
Thread.sleep(dur!("seconds")(2));
|
||||||
client.command(new Message("", "NAMES", "")); // TODO: add names commdn
|
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`
|
// 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)
|
public void rq(ubyte[] encodedMessage)
|
||||||
{
|
{
|
||||||
/* Lock queue */
|
/* Lock queue */
|
||||||
@ -117,29 +124,62 @@ public final class ReceiverThread : Thread
|
|||||||
/* Lock the receieve queue */
|
/* Lock the receieve queue */
|
||||||
recvQueueLock.lock();
|
recvQueueLock.lock();
|
||||||
|
|
||||||
/* Message being analysed */
|
/* Parsed messages */
|
||||||
Message curMsg;
|
SList!(Message) currentMessageQueue;
|
||||||
|
|
||||||
/* Search for a PING */
|
/**
|
||||||
ubyte[] pingMessage;
|
* Parse all messages and save them
|
||||||
|
* into the above array
|
||||||
ulong pos = 0;
|
*/
|
||||||
foreach(ubyte[] message; recvQueue[])
|
foreach(ubyte[] message; recvQueue[])
|
||||||
{
|
{
|
||||||
// FIXME: Holy shit this is funny (see https://github.com/deavmi/birchwood/issues/13)
|
/* Decode the message */
|
||||||
if(indexOf(cast(string)message, "PING") > -1)
|
string decodedMessage = decodeMessage(message);
|
||||||
{
|
|
||||||
pingMessage = message;
|
|
||||||
recvQueue.linearRemoveElement(message);
|
|
||||||
|
|
||||||
import std.stdio;
|
/* Parse the message */
|
||||||
writeln("\n\nHOLY SHIT\n: "~cast(string)(message)~"\n\n");
|
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;
|
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
|
* TODO: Plan of action
|
||||||
@ -156,62 +196,26 @@ public final class ReceiverThread : Thread
|
|||||||
* - we can cache or remember stuff when we get 353
|
* - 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
|
* Process each message remaining in the queue now
|
||||||
* till it is empty
|
* till it is empty
|
||||||
*/
|
*/
|
||||||
while(!recvQueue.empty())
|
while(!currentMessageQueue.empty())
|
||||||
{
|
{
|
||||||
ubyte[] message = recvQueue.front();
|
/* Get the frontmost Message */
|
||||||
|
Message curMsg = currentMessageQueue.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);
|
|
||||||
|
|
||||||
// TODO: Remove the Eventy push and replace with a handler call (on second thought no)
|
// TODO: Remove the Eventy push and replace with a handler call (on second thought no)
|
||||||
EventyEvent ircEvent = new IRCEvent(curMsg);
|
EventyEvent ircEvent = new IRCEvent(curMsg);
|
||||||
client.engine.push(ircEvent);
|
client.engine.push(ircEvent);
|
||||||
|
|
||||||
|
/* Remove the message from the queue */
|
||||||
|
currentMessageQueue.linearRemoveElement(curMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Clear the receive queue */
|
||||||
|
recvQueue.clear();
|
||||||
|
|
||||||
/* Unlock the receive queue */
|
/* Unlock the receive queue */
|
||||||
recvQueueLock.unlock();
|
recvQueueLock.unlock();
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,13 @@ public final class SenderThread : Thread
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Rename to `sendQ`
|
// 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)
|
public void sq(ubyte[] encodedMessage)
|
||||||
{
|
{
|
||||||
/* Lock queue */
|
/* Lock queue */
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* COnfiguration-related types
|
||||||
|
*/
|
||||||
module birchwood.config.conninfo;
|
module birchwood.config.conninfo;
|
||||||
|
|
||||||
import std.socket : SocketException, Address, getAddress;
|
import std.socket : SocketException, Address, getAddress;
|
||||||
import birchwood.client.exceptions : BirchwoodException;
|
import birchwood.client.exceptions : BirchwoodException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the connection details for a server
|
||||||
|
* to connect to
|
||||||
|
*/
|
||||||
public struct ConnectionInfo
|
public struct ConnectionInfo
|
||||||
{
|
{
|
||||||
/* Server address information */
|
/* Server address information */
|
||||||
@ -36,16 +43,32 @@ public struct ConnectionInfo
|
|||||||
return this.bulkReadSize;
|
return this.bulkReadSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the address of the endpoint server
|
||||||
|
*
|
||||||
|
* Returns: the server's address
|
||||||
|
*/
|
||||||
public Address getAddr()
|
public Address getAddr()
|
||||||
{
|
{
|
||||||
return addrInfo;
|
return addrInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the chosen fake lag
|
||||||
|
*
|
||||||
|
* Returns: the fake lag in seconds
|
||||||
|
*/
|
||||||
public ulong getFakeLag()
|
public ulong getFakeLag()
|
||||||
{
|
{
|
||||||
return fakeLag;
|
return fakeLag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the fake lag in seconds
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* fakeLag = the fake lag to use
|
||||||
|
*/
|
||||||
public void setFakeLag(ulong fakeLag)
|
public void setFakeLag(ulong fakeLag)
|
||||||
{
|
{
|
||||||
this.fakeLag = fakeLag;
|
this.fakeLag = fakeLag;
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
module birchwood.protocol.constants;
|
module birchwood.protocol.constants;
|
||||||
|
|
||||||
/* Reply object */
|
/**
|
||||||
|
* The type of numeric response
|
||||||
|
*/
|
||||||
public enum ReplyType : ulong
|
public enum ReplyType : ulong
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* rfc 1459
|
||||||
|
*/
|
||||||
|
|
||||||
/* Error replies */
|
/* Error replies */
|
||||||
ERR_NOSUCHNICK = 401,
|
ERR_NOSUCHNICK = 401,
|
||||||
ERR_NOSUCHSERVER = 402,
|
ERR_NOSUCHSERVER = 402,
|
||||||
@ -144,5 +150,20 @@ module birchwood.protocol.constants;
|
|||||||
ERR_BADCHANMASK = 476,
|
ERR_BADCHANMASK = 476,
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
BIRCHWOOD_UNKNOWN_RESP_CODE = 0
|
||||||
}
|
}
|
@ -138,6 +138,9 @@ public final class Message
|
|||||||
logger.log(e);
|
logger.log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Parse the parameters into key-value pairs (if any) and trailing text (if any) */
|
||||||
|
parameterParse();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: Implement encoder function */
|
/* TODO: Implement encoder function */
|
||||||
@ -276,6 +279,192 @@ public final class Message
|
|||||||
return params;
|
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
|
* Returns whether or not this message was
|
||||||
* a numeric response
|
* a numeric response
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
module birchwood.protocol;
|
module birchwood.protocol;
|
||||||
|
|
||||||
public import birchwood.protocol.messages : Message;
|
public import birchwood.protocol.messages : Message;
|
||||||
|
public import birchwood.protocol.constants : ReplyType;
|
||||||
|
|
||||||
// TODO: Look how to neaten up (if any)
|
// TODO: Look how to neaten up (if any)
|
||||||
public import birchwood.protocol.formatting;
|
public import birchwood.protocol.formatting;
|
Loading…
Reference in New Issue
Block a user