mirror of
https://github.com/deavmi/birchwood
synced 2024-09-20 09:03:29 +02:00
Added initial working code
This commit is contained in:
parent
0985017f49
commit
6f20ef09fa
@ -1,6 +0,0 @@
|
|||||||
import std.stdio;
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
writeln("Edit source/app.d to start your project.");
|
|
||||||
}
|
|
619
source/birchwood/client.d
Normal file
619
source/birchwood/client.d
Normal file
@ -0,0 +1,619 @@
|
|||||||
|
module birchwood.client;
|
||||||
|
|
||||||
|
import std.socket : Socket, SocketException, Address, parseAddress, SocketType, ProtocolType, SocketOSException;
|
||||||
|
import std.socket : SocketFlags;
|
||||||
|
import std.conv : to;
|
||||||
|
import std.container.slist : SList;
|
||||||
|
import core.sync.mutex : Mutex;
|
||||||
|
import core.thread : Thread, dur;
|
||||||
|
import std.string;
|
||||||
|
|
||||||
|
// TODO: Remove this import
|
||||||
|
import std.stdio : writeln;
|
||||||
|
|
||||||
|
public class BirchwoodException : Exception
|
||||||
|
{
|
||||||
|
public enum ErrorType
|
||||||
|
{
|
||||||
|
INVALID_CONN_INFO,
|
||||||
|
ALREADY_CONNECTED,
|
||||||
|
CONNECT_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
private ErrorType errType;
|
||||||
|
|
||||||
|
/* Auxillary error information */
|
||||||
|
/* TODO: Make these actually Object */
|
||||||
|
private string auxInfo;
|
||||||
|
|
||||||
|
this(ErrorType errType)
|
||||||
|
{
|
||||||
|
super("BirchwoodError("~to!(string)(errType)~")"~(auxInfo.length == 0 ? "" : " "~auxInfo));
|
||||||
|
this.errType = errType;
|
||||||
|
}
|
||||||
|
|
||||||
|
this(ErrorType errType, string auxInfo)
|
||||||
|
{
|
||||||
|
this(errType);
|
||||||
|
this.auxInfo = auxInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ErrorType getType()
|
||||||
|
{
|
||||||
|
return errType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct ConnectionInfo
|
||||||
|
{
|
||||||
|
/* Server address information */
|
||||||
|
private Address addrInfo;
|
||||||
|
private string nickname;
|
||||||
|
|
||||||
|
/* Misc. */
|
||||||
|
/* TODO: Make final/const (find out difference) */
|
||||||
|
private ulong bulkReadSize;
|
||||||
|
|
||||||
|
private this(Address addrInfo, string nickname, ulong bulkReadSize = 20)
|
||||||
|
{
|
||||||
|
this.addrInfo = addrInfo;
|
||||||
|
this.nickname = nickname;
|
||||||
|
this.bulkReadSize = bulkReadSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong getBulkReadSize()
|
||||||
|
{
|
||||||
|
return this.bulkReadSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Address getAddr()
|
||||||
|
{
|
||||||
|
return addrInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ConnectionInfo newConnection(string hostname, ushort port, string nickname)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
/* Attempt to parse address (may throw SocketException) */
|
||||||
|
Address addrInfo = parseAddress(hostname, port);
|
||||||
|
|
||||||
|
/* Username check */
|
||||||
|
if(!nickname.length)
|
||||||
|
{
|
||||||
|
throw new BirchwoodException(BirchwoodException.ErrorType.INVALID_CONN_INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return ConnectionInfo(addrInfo, nickname);
|
||||||
|
}
|
||||||
|
catch(SocketException e)
|
||||||
|
{
|
||||||
|
throw new BirchwoodException(BirchwoodException.ErrorType.INVALID_CONN_INFO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests invalid conneciton information
|
||||||
|
*
|
||||||
|
* 1. Invalid hostnames
|
||||||
|
* 2. Invalid usernames
|
||||||
|
*/
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
newConnection("1.", 21, "deavmi");
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
catch(BirchwoodException e)
|
||||||
|
{
|
||||||
|
assert(e.getType() == BirchwoodException.ErrorType.INVALID_CONN_INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
newConnection("1.1.1.1", 21, "");
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
catch(BirchwoodException e)
|
||||||
|
{
|
||||||
|
assert(e.getType() == BirchwoodException.ErrorType.INVALID_CONN_INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Client
|
||||||
|
{
|
||||||
|
/* Connection information */
|
||||||
|
private ConnectionInfo connInfo;
|
||||||
|
|
||||||
|
private Socket socket;
|
||||||
|
|
||||||
|
/* Message queues (and handlers) */
|
||||||
|
private SList!(ubyte[]) recvQueue, sendQueue;
|
||||||
|
private Mutex recvQueueLock, sendQueueLock;
|
||||||
|
private Thread recvHandler, sendHandler;
|
||||||
|
|
||||||
|
this(ConnectionInfo connInfo)
|
||||||
|
{
|
||||||
|
this.connInfo = connInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
~this()
|
||||||
|
{
|
||||||
|
//TODO: Do something here, tare downs
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects to the server
|
||||||
|
*/
|
||||||
|
public void connect()
|
||||||
|
{
|
||||||
|
if(socket is null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
/* Attempt to connect */
|
||||||
|
this.socket = new Socket(connInfo.getAddr().addressFamily(), SocketType.STREAM, ProtocolType.TCP);
|
||||||
|
this.socket.connect(connInfo.getAddr());
|
||||||
|
|
||||||
|
/* Initialize queue locks */
|
||||||
|
this.recvQueueLock = new Mutex();
|
||||||
|
this.sendQueueLock = new Mutex();
|
||||||
|
|
||||||
|
/* TODO: Clean this up and place elsewhere */
|
||||||
|
this.recvHandler = new Thread(&recvHandlerFunc);
|
||||||
|
this.recvHandler.start();
|
||||||
|
|
||||||
|
this.sendHandler = new Thread(&sendHandlerFunc);
|
||||||
|
this.sendHandler.start();
|
||||||
|
}
|
||||||
|
catch(SocketOSException e)
|
||||||
|
{
|
||||||
|
throw new BirchwoodException(BirchwoodException.ErrorType.CONNECT_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new BirchwoodException(BirchwoodException.ErrorType.ALREADY_CONNECTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ulong j = 0;
|
||||||
|
// bool f = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We need to create a queue of messages and then have a seperate thread
|
||||||
|
* go through them, such as replying to pings etc.
|
||||||
|
*
|
||||||
|
* We should maybe have two quues, urgent ones (for pings coming in)
|
||||||
|
* of which we check first and then everything else into another queue
|
||||||
|
*/
|
||||||
|
private void receiveQ(ubyte[] message)
|
||||||
|
{
|
||||||
|
/* Lock queue */
|
||||||
|
recvQueueLock.lock();
|
||||||
|
|
||||||
|
/* Add to queue */
|
||||||
|
recvQueue.insertAfter(recvQueue[], message);
|
||||||
|
|
||||||
|
/* Unlock queue */
|
||||||
|
recvQueueLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ubyte[] encodeMessage(string messageIn)
|
||||||
|
{
|
||||||
|
ubyte[] messageOut = cast(ubyte[])messageIn;
|
||||||
|
messageOut~=[cast(ubyte)13, cast(ubyte)10];
|
||||||
|
return messageOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string decodeMessage(ubyte[] messageIn)
|
||||||
|
{
|
||||||
|
/* TODO: We could do a chekc to ESNURE it is well encoded */
|
||||||
|
|
||||||
|
return cast(string)messageIn[0..messageIn.length-2];
|
||||||
|
// return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void defaultHandler(string from, string command, string params)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// private
|
||||||
|
private void function() getHandler()
|
||||||
|
{
|
||||||
|
/* The chosen handler */
|
||||||
|
void function() handlerPtr;
|
||||||
|
|
||||||
|
|
||||||
|
return handlerPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: Implement me */
|
||||||
|
private void parseReceivedMessage(string message)
|
||||||
|
{
|
||||||
|
/* Command */
|
||||||
|
string command;
|
||||||
|
|
||||||
|
/* Check if there is a PREFIX (according to RFC 1459) */
|
||||||
|
if(message[0] == ':')
|
||||||
|
{
|
||||||
|
/* prefix ends after first space (we fetch servername, host/user) */
|
||||||
|
//TODO: make sure not -1
|
||||||
|
long firstSpace = indexOf(message, ' ');
|
||||||
|
|
||||||
|
/* TODO: double check the condition */
|
||||||
|
if(firstSpace > 0)
|
||||||
|
{
|
||||||
|
string from = message[1..firstSpace];
|
||||||
|
|
||||||
|
writeln("from: "~from);
|
||||||
|
|
||||||
|
/* TODO: Find next space (what follows `from` is `' ' { ' ' }`) */
|
||||||
|
ulong i = firstSpace;
|
||||||
|
for(; i < message.length; i++)
|
||||||
|
{
|
||||||
|
if(message[i] != ' ')
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeln("Yo");
|
||||||
|
|
||||||
|
string rem = message[i..message.length];
|
||||||
|
// writeln("Rem: "~rem);
|
||||||
|
long idx = indexOf(rem, " "); //TOOD: -1 check
|
||||||
|
|
||||||
|
/* Extract the command */
|
||||||
|
command = rem[0..idx];
|
||||||
|
writeln("command: "~command);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//TODO: handle
|
||||||
|
writeln("Malformed message start after :");
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: Spawn a thread worker that reacts */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is run as part of the "reactor"
|
||||||
|
* thread and its job is to effectively dequeue
|
||||||
|
* messages from the receive queue and call the
|
||||||
|
* correct handler function with the message as
|
||||||
|
* the event payload.
|
||||||
|
*
|
||||||
|
* It pays high priority to looking for a PING
|
||||||
|
* message first and handling those and then doing
|
||||||
|
* a second pass for other messages
|
||||||
|
*/
|
||||||
|
private void recvHandlerFunc()
|
||||||
|
{
|
||||||
|
while(true)
|
||||||
|
{
|
||||||
|
/* Lock the receieve queue */
|
||||||
|
recvQueueLock.lock();
|
||||||
|
|
||||||
|
|
||||||
|
/* Search for a PING */
|
||||||
|
ubyte[] pingMessage;
|
||||||
|
|
||||||
|
ulong pos = 0;
|
||||||
|
foreach(ubyte[] message; recvQueue[])
|
||||||
|
{
|
||||||
|
if(indexOf(cast(string)message, "PING") > -1)
|
||||||
|
{
|
||||||
|
pingMessage = message;
|
||||||
|
recvQueue.linearRemoveElement(message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* If we found a PING */
|
||||||
|
if(pingMessage.length > 0)
|
||||||
|
{
|
||||||
|
writeln("Found a ping: "~cast(string)pingMessage);
|
||||||
|
string ogMessage = cast(string)pingMessage;
|
||||||
|
long idxSigStart = indexOf(ogMessage, ":")+1;
|
||||||
|
long idxSigEnd = lastIndexOf(ogMessage, '\r');
|
||||||
|
|
||||||
|
string pingID = ogMessage[idxSigStart..idxSigEnd];
|
||||||
|
|
||||||
|
|
||||||
|
// this.socket.send(encodeMessage("PONG "~pingID));
|
||||||
|
|
||||||
|
string messageToSend = "PONG "~pingID;
|
||||||
|
|
||||||
|
sendMessage(messageToSend);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Now let's go message by message */
|
||||||
|
if(!recvQueue.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 */
|
||||||
|
parseReceivedMessage(messageNormal);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Unlock the receive queue */
|
||||||
|
recvQueueLock.unlock();
|
||||||
|
|
||||||
|
/* TODO: Threading yield here */
|
||||||
|
Thread.yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendHandlerFunc()
|
||||||
|
{
|
||||||
|
/* TODO: Hoist up into ConnInfo */
|
||||||
|
ulong fakeLagInBetween = 1;
|
||||||
|
|
||||||
|
while(true)
|
||||||
|
{
|
||||||
|
|
||||||
|
/* TODO: handle normal messages (xCount with fakeLagInBetween) */
|
||||||
|
sendQueueLock.lock();
|
||||||
|
|
||||||
|
foreach(ubyte[] message; sendQueue[])
|
||||||
|
{
|
||||||
|
this.socket.send(message);
|
||||||
|
Thread.sleep(dur!("seconds")(fakeLagInBetween));
|
||||||
|
}
|
||||||
|
|
||||||
|
sendQueue.clear();
|
||||||
|
|
||||||
|
sendQueueLock.unlock();
|
||||||
|
|
||||||
|
/* TODO: Yield */
|
||||||
|
Thread.yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Make send queue which is used on another thread to send messages
|
||||||
|
*
|
||||||
|
* This allows us to intrpoduce fakelag and also prioritse pongs (we should
|
||||||
|
* send them via here)
|
||||||
|
*/
|
||||||
|
private void sendMessage(string messageOut)
|
||||||
|
{
|
||||||
|
/* Encode the mesage */
|
||||||
|
ubyte[] encodedMessage = encodeMessage(messageOut);
|
||||||
|
|
||||||
|
/* Lock queue */
|
||||||
|
sendQueueLock.lock();
|
||||||
|
|
||||||
|
/* Add to queue */
|
||||||
|
sendQueue.insertAfter(sendQueue[], encodedMessage);
|
||||||
|
|
||||||
|
/* Unlock queue */
|
||||||
|
sendQueueLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool yes = true;
|
||||||
|
bool hasJoined = false;
|
||||||
|
|
||||||
|
private void processMessage(ubyte[] message)
|
||||||
|
{
|
||||||
|
// import std.stdio;
|
||||||
|
// writeln("Message length: "~to!(string)(message.length));
|
||||||
|
// writeln("InterpAsString: "~cast(string)message);
|
||||||
|
|
||||||
|
receiveQ(message);
|
||||||
|
|
||||||
|
j++;
|
||||||
|
|
||||||
|
if(j >= 3)
|
||||||
|
{
|
||||||
|
// import core.thread;
|
||||||
|
// Thread.sleep(dur!("seconds")(10));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if(yes)
|
||||||
|
{
|
||||||
|
// this.socket.send((cast(ubyte[])"CAP LS")~[cast(ubyte)13, cast(ubyte)10]);
|
||||||
|
import core.thread;
|
||||||
|
Thread.sleep(dur!("seconds")(2));
|
||||||
|
|
||||||
|
this.socket.send((cast(ubyte[])"NICK birchwood")~[cast(ubyte)13, cast(ubyte)10]);
|
||||||
|
|
||||||
|
import core.thread;
|
||||||
|
Thread.sleep(dur!("seconds")(2));
|
||||||
|
this.socket.send((cast(ubyte[])"USER doggie doggie irc.freenode.net :Tristan B. Kildaire")~[cast(ubyte)13, cast(ubyte)10]);
|
||||||
|
|
||||||
|
yes=false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(hasJoined == false)
|
||||||
|
{
|
||||||
|
import core.thread;
|
||||||
|
Thread.sleep(dur!("seconds")(4));
|
||||||
|
this.socket.send((cast(ubyte[])"join #birchwoodtesting")~[cast(ubyte)13, cast(ubyte)10]);
|
||||||
|
hasJoined = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// this.socket.send((cast(ubyte[])"PONG irc.freenode.net")~[cast(ubyte)13, cast(ubyte)10]);
|
||||||
|
|
||||||
|
// import core.thread;
|
||||||
|
// Thread.sleep(dur!("seconds")(2));
|
||||||
|
// this.socket.send((cast(ubyte[])"join #birchwoodtesting")~[cast(ubyte)13, cast(ubyte)10]);
|
||||||
|
|
||||||
|
// yes=false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Determine how we want to do this
|
||||||
|
*
|
||||||
|
* This simply receives messages from the server,
|
||||||
|
* parses them and puts them into the receive queue
|
||||||
|
*/
|
||||||
|
public void loop()
|
||||||
|
{
|
||||||
|
/* TODO: We could do below but nah for now as we know max 512 bytes */
|
||||||
|
/* TODO: Make the read bulk size a configurable parameter */
|
||||||
|
/* TODO: Make static array allocation outside, instead of a dynamic one */
|
||||||
|
// ulong bulkReadSize = 20;
|
||||||
|
|
||||||
|
/* Fixed allocation of `bulkReadSize` for temporary data */
|
||||||
|
ubyte[] currentData;
|
||||||
|
currentData.length = connInfo.getBulkReadSize();
|
||||||
|
|
||||||
|
// malloc();
|
||||||
|
|
||||||
|
/* Total built message */
|
||||||
|
ubyte[] currentMessage;
|
||||||
|
|
||||||
|
bool hasCR = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message loop
|
||||||
|
*/
|
||||||
|
while(true)
|
||||||
|
{
|
||||||
|
/* Receieve at most 512 bytes (as per RFC) */
|
||||||
|
ptrdiff_t bytesRead = socket.receive(currentData, SocketFlags.PEEK);
|
||||||
|
|
||||||
|
import std.stdio;
|
||||||
|
// writeln(bytesRead);
|
||||||
|
// writeln(currentData);
|
||||||
|
|
||||||
|
/* FIXME: CHECK BYTES READ FOR SOCKET ERRORS! */
|
||||||
|
|
||||||
|
/* If we had a CR previously then now we need a LF */
|
||||||
|
if(hasCR)
|
||||||
|
{
|
||||||
|
/* First byte following it should be LF */
|
||||||
|
if(currentData[0] == 10)
|
||||||
|
{
|
||||||
|
/* Add to the message */
|
||||||
|
currentMessage~=currentData[0];
|
||||||
|
|
||||||
|
/* TODO: Process mesaage */
|
||||||
|
processMessage(currentMessage);
|
||||||
|
|
||||||
|
/* Reset state for next message */
|
||||||
|
currentMessage.length = 0;
|
||||||
|
hasCR=false;
|
||||||
|
|
||||||
|
/* Chop off the LF */
|
||||||
|
ubyte[] scratch;
|
||||||
|
scratch.length = 1;
|
||||||
|
this.socket.receive(scratch);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* TODO: This is an error */
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong pos;
|
||||||
|
for(pos = 0; pos < bytesRead; pos++)
|
||||||
|
{
|
||||||
|
/* Find first CR */
|
||||||
|
if(currentData[pos] == 13)
|
||||||
|
{
|
||||||
|
/* If we already have CR then that is an error */
|
||||||
|
if(hasCR)
|
||||||
|
{
|
||||||
|
/* TODO: Handle this */
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasCR = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we have a CR, then read up to that */
|
||||||
|
if(hasCR)
|
||||||
|
{
|
||||||
|
/* Read up to CR */
|
||||||
|
currentMessage~=currentData[0..pos+1];
|
||||||
|
|
||||||
|
/* Dequeue this (TODO: way to dispose without copy over) */
|
||||||
|
/* Guaranteed as we peeked this lenght */
|
||||||
|
ubyte[] scratch;
|
||||||
|
scratch.length = pos+1;
|
||||||
|
this.socket.receive(scratch);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add whatever we have read to build-up */
|
||||||
|
currentMessage~=currentData[0..bytesRead];
|
||||||
|
|
||||||
|
/* TODO: Dequeue without peek after this */
|
||||||
|
ubyte[] scratch;
|
||||||
|
scratch.length = bytesRead;
|
||||||
|
this.socket.receive(scratch);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
/* FIXME: Get domaina name resolution support */
|
||||||
|
// ConnectionInfo connInfo = ConnectionInfo.newConnection("irc.freenode.net", 6667, "testBirchwood");
|
||||||
|
//freenode: 149.28.246.185
|
||||||
|
//snootnet: 178.62.125.123
|
||||||
|
//bonobonet: fd08:8441:e254::5
|
||||||
|
ConnectionInfo connInfo = ConnectionInfo.newConnection("149.28.246.185", 6667, "testBirchwood");
|
||||||
|
Client client = new Client(connInfo);
|
||||||
|
|
||||||
|
client.connect();
|
||||||
|
|
||||||
|
client.loop();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
10
source/birchwood/messages.d
Normal file
10
source/birchwood/messages.d
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
module birchwood.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message types
|
||||||
|
*/
|
||||||
|
public class Message
|
||||||
|
{
|
||||||
|
|
||||||
|
private string messageRaw;
|
||||||
|
}
|
3
source/birchwood/package.d
Normal file
3
source/birchwood/package.d
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module birchwood;
|
||||||
|
|
||||||
|
public import birchwood.client;
|
Loading…
Reference in New Issue
Block a user