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.stdio;
|
||||||
import std.string;
|
import std.string;
|
||||||
|
import std.algorithm;
|
||||||
|
import std.range;
|
||||||
|
import std.conv;
|
||||||
|
|
||||||
import vibe.core.core;
|
import vibe.core.core;
|
||||||
import vibe.stream.operations;
|
import vibe.stream.operations;
|
||||||
|
|
||||||
import ircd.message;
|
import ircd.message;
|
||||||
import ircd.server;
|
import ircd.server;
|
||||||
|
import ircd.channel;
|
||||||
|
|
||||||
class Connection
|
class Connection
|
||||||
{
|
{
|
||||||
|
@ -18,6 +22,11 @@ class Connection
|
||||||
string nick;
|
string nick;
|
||||||
string user;
|
string user;
|
||||||
string realname;
|
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;
|
bool connected;
|
||||||
|
|
||||||
|
@ -25,58 +34,148 @@ class Connection
|
||||||
{
|
{
|
||||||
_connection = connection;
|
_connection = connection;
|
||||||
_server = server;
|
_server = server;
|
||||||
|
|
||||||
|
connected = _connection.connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
void send(Message message)
|
void send(Message message)
|
||||||
{
|
{
|
||||||
string messageString = message.toString;
|
string messageString = message.toString;
|
||||||
writeln("S> " ~ messageString);
|
writeln("S> " ~ messageString ~ " (" ~ nick.to!string ~ ")");
|
||||||
_connection.write(messageString ~ "\r\n");
|
_connection.write(messageString ~ "\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onDisconnect()
|
||||||
|
{
|
||||||
|
writeln("client disconnected");
|
||||||
|
}
|
||||||
|
|
||||||
void handle()
|
void handle()
|
||||||
{
|
{
|
||||||
connected = true;
|
|
||||||
while(connected)
|
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);
|
writeln("C> " ~ message.toString);
|
||||||
|
|
||||||
//TODO: If RFC-strictness is off, ignore case
|
//TODO: If RFC-strictness is off, ignore case
|
||||||
switch(message.command)
|
switch(message.command)
|
||||||
{
|
{
|
||||||
case "NICK":
|
case "NICK":
|
||||||
//TODO: Check availablity and validity etc.
|
onNick(message);
|
||||||
nick = message.parameters[0];
|
|
||||||
break;
|
break;
|
||||||
case "USER":
|
case "USER":
|
||||||
user = message.parameters[0];
|
onUser(message);
|
||||||
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"]));
|
|
||||||
break;
|
break;
|
||||||
case "PING":
|
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;
|
break;
|
||||||
case "PONG":
|
case "PONG":
|
||||||
//TODO: Handle pong
|
//TODO: Handle pong
|
||||||
break;
|
break;
|
||||||
case "QUIT":
|
case "QUIT":
|
||||||
send(Message(null, "ERROR", ["Bye!"]));
|
onQuit(message);
|
||||||
connected = false;
|
break;
|
||||||
|
case "JOIN":
|
||||||
|
onJoin(message);
|
||||||
|
break;
|
||||||
|
case "PART":
|
||||||
|
onPart(message);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
writeln("unknown command '", message.command, "'");
|
writeln("unknown command '", message.command, "'");
|
||||||
|
send(Message(_server.name, "421", ["Unknown command"]));
|
||||||
break;
|
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.algorithm;
|
||||||
import std.conv;
|
import std.conv;
|
||||||
|
|
||||||
|
//TODO: Make this a class
|
||||||
struct Message
|
struct Message
|
||||||
{
|
{
|
||||||
string prefix;
|
string prefix;
|
||||||
|
|
|
@ -13,6 +13,7 @@ import ircd.packageVersion;
|
||||||
|
|
||||||
import ircd.message;
|
import ircd.message;
|
||||||
import ircd.connection;
|
import ircd.connection;
|
||||||
|
import ircd.channel;
|
||||||
|
|
||||||
class Server
|
class Server
|
||||||
{
|
{
|
||||||
|
@ -23,6 +24,8 @@ class Server
|
||||||
|
|
||||||
string name;
|
string name;
|
||||||
|
|
||||||
|
Channel[] channels;
|
||||||
|
|
||||||
this()
|
this()
|
||||||
{
|
{
|
||||||
name = Socket.hostName;
|
name = Socket.hostName;
|
||||||
|
@ -36,9 +39,9 @@ class Server
|
||||||
{
|
{
|
||||||
foreach(connection; connections)
|
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;
|
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)
|
void listen(ushort port = 6667)
|
||||||
{
|
{
|
||||||
listenTCP(port, &acceptConnection);
|
listenTCP(port, &acceptConnection);
|
||||||
|
|
Loading…
Reference in New Issue