forked from lesderid/salty-ircd
Add basic channel support
This commit is contained in:
parent
10c1da7581
commit
dad9ac49f9
|
@ -0,0 +1,40 @@
|
|||
module ircd.channel;
|
||||
|
||||
import std.algorithm;
|
||||
import std.string;
|
||||
|
||||
import ircd.connection;
|
||||
import ircd.server;
|
||||
import ircd.message;
|
||||
|
||||
class Channel
|
||||
{
|
||||
private string _name;
|
||||
|
||||
Connection[] members;
|
||||
Connection owner;
|
||||
|
||||
private Server _server;
|
||||
|
||||
this(string name, Connection owner, Server server)
|
||||
{
|
||||
this._name = name;
|
||||
this.owner = owner;
|
||||
this.members = [owner];
|
||||
this._server = server;
|
||||
}
|
||||
|
||||
@property
|
||||
string name()
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
void sendNames(Connection connection)
|
||||
{
|
||||
enum channelType = "="; //TODO: Support secret and private channels
|
||||
|
||||
connection.send(Message(_server.name, "353", [channelType, name, members.map!(m => m.nick).join(' ')], true));
|
||||
connection.send(Message(_server.name, "366", [name, "End of NAMES list"], true));
|
||||
}
|
||||
}
|
|
@ -2,12 +2,16 @@ module ircd.connection;
|
|||
|
||||
import std.stdio;
|
||||
import std.string;
|
||||
import std.algorithm;
|
||||
import std.range;
|
||||
import std.conv;
|
||||
|
||||
import vibe.core.core;
|
||||
import vibe.stream.operations;
|
||||
|
||||
import ircd.message;
|
||||
import ircd.server;
|
||||
import ircd.channel;
|
||||
|
||||
class Connection
|
||||
{
|
||||
|
@ -18,6 +22,11 @@ class Connection
|
|||
string nick;
|
||||
string user;
|
||||
string realname;
|
||||
string hostname = "HOSTNAME";
|
||||
|
||||
@property string mask() { return nick ~ "!" ~ user ~ "@" ~ hostname; }
|
||||
|
||||
@property Channel[] channels() { return _server.channels.filter!(c => c.members.canFind(this)).array; }
|
||||
|
||||
bool connected;
|
||||
|
||||
|
@ -25,58 +34,148 @@ class Connection
|
|||
{
|
||||
_connection = connection;
|
||||
_server = server;
|
||||
|
||||
connected = _connection.connected;
|
||||
}
|
||||
|
||||
void send(Message message)
|
||||
{
|
||||
string messageString = message.toString;
|
||||
writeln("S> " ~ messageString);
|
||||
writeln("S> " ~ messageString ~ " (" ~ nick.to!string ~ ")");
|
||||
_connection.write(messageString ~ "\r\n");
|
||||
}
|
||||
|
||||
void onDisconnect()
|
||||
{
|
||||
writeln("client disconnected");
|
||||
}
|
||||
|
||||
void handle()
|
||||
{
|
||||
connected = true;
|
||||
while(connected)
|
||||
{
|
||||
auto message = Message.fromString((cast(string)_connection.readLine()).chomp);
|
||||
Message message = void;
|
||||
try
|
||||
{
|
||||
message = Message.fromString((cast(string)_connection.readLine()).chomp);
|
||||
}
|
||||
catch(Throwable)
|
||||
{
|
||||
//TODO: The actual Throwable could be useful?
|
||||
connected = _connection.connected;
|
||||
continue;
|
||||
}
|
||||
|
||||
writeln("C> " ~ message.toString);
|
||||
|
||||
//TODO: If RFC-strictness is off, ignore case
|
||||
switch(message.command)
|
||||
{
|
||||
case "NICK":
|
||||
//TODO: Check availablity and validity etc.
|
||||
nick = message.parameters[0];
|
||||
onNick(message);
|
||||
break;
|
||||
case "USER":
|
||||
user = message.parameters[0];
|
||||
realname = message.parameters[3];
|
||||
|
||||
writeln("mode: " ~ message.parameters[1]);
|
||||
writeln("unused: " ~ message.parameters[2]);
|
||||
|
||||
send(Message("localhost", "001", [nick, "Welcome to the Internet Relay Network " ~ nick ~ "!" ~ user ~ "@hostname"], true));
|
||||
send(Message("localhost", "002", [nick, "Your host is " ~ _server.name ~ ", running version " ~ _server.versionString], true));
|
||||
send(Message("localhost", "003", [nick, "This server was created " ~ _server.creationDate], true));
|
||||
send(Message("localhost", "004", [nick, _server.name, _server.versionString, "w", "snt"]));
|
||||
onUser(message);
|
||||
break;
|
||||
case "PING":
|
||||
send(Message(null, "PONG", [message.parameters[0]], true));
|
||||
//TODO: Connection timeout when we don't get a PONG
|
||||
send(Message(_server.name, "PONG", [_server.name, message.parameters[0]], true));
|
||||
break;
|
||||
case "PONG":
|
||||
//TODO: Handle pong
|
||||
break;
|
||||
case "QUIT":
|
||||
send(Message(null, "ERROR", ["Bye!"]));
|
||||
connected = false;
|
||||
onQuit(message);
|
||||
break;
|
||||
case "JOIN":
|
||||
onJoin(message);
|
||||
break;
|
||||
case "PART":
|
||||
onPart(message);
|
||||
break;
|
||||
default:
|
||||
writeln("unknown command '", message.command, "'");
|
||||
send(Message(_server.name, "421", ["Unknown command"]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
onDisconnect();
|
||||
}
|
||||
|
||||
void onNick(Message message)
|
||||
{
|
||||
auto newNick = message.parameters[0];
|
||||
if(nick !is null)
|
||||
{
|
||||
foreach(connection; channels.map!(c => c.members).fold!((a, b) => a ~ b).sort().uniq.filter!(c => c != this))
|
||||
{
|
||||
connection.send(Message(nick, "NICK", [newNick]));
|
||||
}
|
||||
send(Message(nick, "NICK", [newNick]));
|
||||
}
|
||||
|
||||
//TODO: Check availablity and validity etc.
|
||||
nick = newNick;
|
||||
}
|
||||
|
||||
void onUser(Message message)
|
||||
{
|
||||
//TODO: Parse user mode
|
||||
user = message.parameters[0];
|
||||
writeln("mode: " ~ message.parameters[1]);
|
||||
writeln("unused: " ~ message.parameters[2]);
|
||||
realname = message.parameters[3];
|
||||
|
||||
send(Message(_server.name, "001", [nick, "Welcome to the Internet Relay Network " ~ mask], true));
|
||||
send(Message(_server.name, "002", [nick, "Your host is " ~ _server.name ~ ", running version " ~ _server.versionString], true));
|
||||
send(Message(_server.name, "003", [nick, "This server was created " ~ _server.creationDate], true));
|
||||
send(Message(_server.name, "004", [nick, _server.name, _server.versionString, "w", "snt"]));
|
||||
}
|
||||
|
||||
void onQuit(Message message)
|
||||
{
|
||||
connected = false;
|
||||
send(Message(_server.name, "ERROR", ["Bye!"]));
|
||||
}
|
||||
|
||||
void onJoin(Message message)
|
||||
{
|
||||
auto channel = message.parameters[0];
|
||||
if(!Server.isValidChannelName(channel))
|
||||
{
|
||||
send(Message(_server.name, "403", [channel, "No such channel"], true));
|
||||
}
|
||||
else
|
||||
{
|
||||
_server.join(this, channel);
|
||||
}
|
||||
}
|
||||
|
||||
void onPart(Message message)
|
||||
{
|
||||
//TODO: Support channel lists
|
||||
//TODO: Check if user is member of channel(s)
|
||||
auto channel = message.parameters[0];
|
||||
if(!Server.isValidChannelName(channel))
|
||||
{
|
||||
send(Message(_server.name, "403", [channel, "No such channel"], true));
|
||||
}
|
||||
else if(!channels.canFind!(c => c.name == channel))
|
||||
{
|
||||
send(Message(_server.name, "442", [channel, "You're not on that channel"], true));
|
||||
}
|
||||
else
|
||||
{
|
||||
if(message.parameters.length > 1)
|
||||
{
|
||||
_server.part(this, channel, message.parameters[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
_server.part(this, channel, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import std.array;
|
|||
import std.algorithm;
|
||||
import std.conv;
|
||||
|
||||
//TODO: Make this a class
|
||||
struct Message
|
||||
{
|
||||
string prefix;
|
||||
|
|
|
@ -13,6 +13,7 @@ import ircd.packageVersion;
|
|||
|
||||
import ircd.message;
|
||||
import ircd.connection;
|
||||
import ircd.channel;
|
||||
|
||||
class Server
|
||||
{
|
||||
|
@ -23,6 +24,8 @@ class Server
|
|||
|
||||
string name;
|
||||
|
||||
Channel[] channels;
|
||||
|
||||
this()
|
||||
{
|
||||
name = Socket.hostName;
|
||||
|
@ -36,9 +39,9 @@ class Server
|
|||
{
|
||||
foreach(connection; connections)
|
||||
{
|
||||
connection.send(Message(null, "PING", [connection.nick]));
|
||||
connection.send(Message(null, "PING", [name], true));
|
||||
}
|
||||
sleep(10.seconds);
|
||||
sleep(30.seconds);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,6 +53,53 @@ class Server
|
|||
connections = connections.filter!(c => c != connection).array;
|
||||
}
|
||||
|
||||
static bool isValidChannelName(string name)
|
||||
{
|
||||
return (name.startsWith('#') || name.startsWith('&')) && name.length <= 200;
|
||||
}
|
||||
|
||||
void join(Connection connection, string channelName)
|
||||
{
|
||||
auto channelRange = channels.find!(c => c.name == channelName);
|
||||
Channel channel;
|
||||
if(channelRange.empty)
|
||||
{
|
||||
channel = new Channel(channelName, connection, this);
|
||||
channels ~= channel;
|
||||
}
|
||||
else
|
||||
{
|
||||
channel = channelRange[0];
|
||||
channel.members ~= connection;
|
||||
}
|
||||
|
||||
foreach(member; channel.members)
|
||||
{
|
||||
member.send(Message(connection.mask, "JOIN", [channelName]));
|
||||
}
|
||||
|
||||
channel.sendNames(connection);
|
||||
}
|
||||
|
||||
void part(Connection connection, string channelName, string partMessage)
|
||||
{
|
||||
auto channel = connection.channels.find!(c => c.name == channelName)[0];
|
||||
|
||||
foreach(member; channel.members)
|
||||
{
|
||||
if(partMessage !is null)
|
||||
{
|
||||
member.send(Message(connection.mask, "PART", [channelName, partMessage], true));
|
||||
}
|
||||
else
|
||||
{
|
||||
member.send(Message(connection.mask, "PART", [channelName]));
|
||||
}
|
||||
}
|
||||
|
||||
channel.members = channel.members.remove!(m => m == connection);
|
||||
}
|
||||
|
||||
void listen(ushort port = 6667)
|
||||
{
|
||||
listenTCP(port, &acceptConnection);
|
||||
|
|
Loading…
Reference in New Issue