diff --git a/source/ircd/channel.d b/source/ircd/channel.d index 46b6092..13443ea 100644 --- a/source/ircd/channel.d +++ b/source/ircd/channel.d @@ -9,27 +9,25 @@ import ircd.message; class Channel { - private string _name; + string name; Connection[] members; Connection owner; + string topic = ""; + + char[] modes; + private Server _server; this(string name, Connection owner, Server server) { - this._name = name; + 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 @@ -53,4 +51,26 @@ class Channel member.send(Message(sender.mask, "NOTICE", [name, text], true)); } } + + void sendTopic(Connection connection) + { + if(topic.empty) + { + connection.send(Message(_server.name, "331", [connection.nick, name, "No topic is set"])); + } + else + { + connection.send(Message(_server.name, "332", [connection.nick, name, topic], true)); + } + } + + void setTopic(Connection connection, string newTopic) + { + topic = newTopic; + + foreach(member; members) + { + member.send(Message(connection.mask, "TOPIC", [name, newTopic], true)); + } + } } diff --git a/source/ircd/connection.d b/source/ircd/connection.d index 0051e52..461fcd3 100644 --- a/source/ircd/connection.d +++ b/source/ircd/connection.d @@ -149,6 +149,10 @@ class Connection if(!registered) sendErrNotRegistered(); else onAway(message); break; + case "TOPIC": + if(!registered) sendErrNotRegistered(); + else onTopic(message); + break; default: writeln("unknown command '", message.command, "'"); send(Message(_server.name, "421", [nick, message.command, "Unknown command"])); @@ -404,6 +408,47 @@ class Connection } } + void onTopic(Message message) + { + if(message.parameters.length == 0) + { + sendErrNeedMoreParams(message.command); + return; + } + + auto channelName = message.parameters[0]; + if(message.parameters.length == 1) + { + if(!_server.channels.canFind!(c => c.name == channelName && (!(c.modes.canFind('s') || c.modes.canFind('p')) || c.members.canFind(this)))) + { + //NOTE: The RFCs don't allow ERR_NOSUCHCHANNEL as a response to TOPIC + //TODO: If RFC-strictness is off, do send ERR_NOSUCHCHANNEL + send(Message(_server.name, "331", [nick, channelName, "No topic is set"], true)); + } + else + { + _server.sendChannelTopic(this, channelName); + } + } + else + { + auto newTopic = message.parameters[1]; + if(!channels.canFind!(c => c.name == channelName)) + { + sendErrNotOnChannel(channelName); + } + //TODO: Allow operators to set flags + else if(channels.find!(c => c.name == channelName).map!(c => c.modes.canFind('t') /* && this user isn't an operator */).array[0]) + { + sendErrChanopPrivsNeeded(channelName); + } + else + { + _server.setChannelTopic(this, channelName, newTopic); + } + } + } + void sendWhoReply(string channel, Connection user, uint hopCount) { auto flags = user.modes.canFind('a') ? "G" : "H"; @@ -433,6 +478,11 @@ class Connection send(Message(_server.name, "431", [nick, "No nickname given"], true)); } + void sendErrNotOnChannel(string channel) + { + send(Message(_server.name, "442", [nick, channel, "You're not on that channel"], true)); + } + void sendErrNotRegistered() { send(Message(_server.name, "451", ["(You)", "You have not registered"], true)); @@ -443,6 +493,11 @@ class Connection send(Message(_server.name, "461", [nick, command, "Not enough parameters"], true)); } + void sendErrChanopPrivsNeeded(string channel) + { + send(Message(_server.name, "482", [nick, channel, "You're not channel operator"], true)); + } + void sendWelcome() { send(Message(_server.name, "001", [nick, "Welcome to the Internet Relay Network " ~ mask], true)); diff --git a/source/ircd/server.d b/source/ircd/server.d index ce3093d..d989ee4 100644 --- a/source/ircd/server.d +++ b/source/ircd/server.d @@ -91,6 +91,11 @@ class Server } channel.sendNames(connection); + + if(!channel.topic.empty) + { + channel.sendTopic(connection); + } } void part(Connection connection, string channelName, string partMessage) @@ -163,7 +168,7 @@ class Server .filter!(c => !operatorsOnly || c.isOperator) .filter!(c => [c.hostname, c.servername, c.realname, c.nick].any!(n => wildcardMatch(n, mask)))) { - //TODO: Don't leak secret/private channels (even if the RFCs don't say anything about it?) + //TODO: Don't leak secret/private channels if RFC-strictness is off (the RFCs don't seem to say anything about it?) auto channelName = c.channels.empty ? "*" : c.channels.array[0].name; //TODO: Support hop count origin.sendWhoReply(channelName, c, 0); @@ -194,6 +199,18 @@ class Server user.send(Message(sender.mask, "NOTICE", [target, text], true)); } + void sendChannelTopic(Connection origin, string channelName) + { + auto channel = channels.find!(c => c.name == channelName)[0]; + channel.sendTopic(origin); + } + + void setChannelTopic(Connection origin, string channelName, string newTopic) + { + auto channel = channels.find!(c => c.name == channelName)[0]; + channel.setTopic(origin, newTopic); + } + void listen(ushort port = 6667) { listenTCP(port, &acceptConnection);