salty-ircd/source/ircd/channel.d

409 lines
9.4 KiB
D

module ircd.channel;
import std.algorithm;
import std.string;
import std.typecons : Nullable;
import ircd.connection;
import ircd.server;
import ircd.message;
import ircd.helpers;
//TODO: Make this a struct?
class Channel
{
string name;
string topic = "";
Connection[] members;
char[] modes;
char[][Connection] memberModes;
string[][char] maskLists;
string key;
Nullable!uint memberLimit;
Connection[] inviteHolders;
private Server _server;
this(string name, Server server)
{
this.name = name;
this._server = server;
this.maskLists = ['b': [], 'e': [], 'I': []];
}
void join(Connection connection)
{
members ~= connection;
if (members.length == 1)
{
memberModes[connection] ~= 'o';
}
else
{
memberModes[connection] = [];
}
if (inviteHolders.canFind(connection))
{
inviteHolders = inviteHolders.remove!(c => c == connection);
}
}
void part(Connection connection, string partMessage)
{
foreach (member; members)
{
if (partMessage !is null)
{
member.send(Message(connection.prefix, "PART", [
name, partMessage
], true));
}
else
{
member.send(Message(connection.prefix, "PART", [name]));
}
}
members = members.remove!(m => m == connection);
memberModes.remove(connection);
}
void invite(Connection connection)
{
inviteHolders ~= connection;
}
void sendNames(Connection connection, bool sendRplEndOfNames = true)
{
string channelType;
if (modes.canFind('s'))
{
channelType = "@";
}
else if (modes.canFind('p'))
{
channelType = "*";
}
else
{
channelType = "=";
}
auto onChannel = members.canFind(connection);
connection.send(Message(_server.name, "353", [
connection.nick, channelType, name,
members.filter!(m => onChannel || !m.modes.canFind('i'))
.map!(m => prefixedNick(m))
.join(' ')
], true));
if (sendRplEndOfNames)
{
connection.sendRplEndOfNames(name);
}
}
void sendPrivMsg(Connection sender, string text)
{
foreach (member; members.filter!(m => m.nick != sender.nick))
{
member.send(Message(sender.prefix, "PRIVMSG", [name, text], true));
}
}
void sendNotice(Connection sender, string text)
{
foreach (member; members.filter!(m => m.nick != sender.nick))
{
member.send(Message(sender.prefix, "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.prefix, "TOPIC", [name, newTopic], true));
}
}
void kick(Connection kicker, Connection user, string comment)
{
foreach (member; members)
{
member.send(Message(kicker.prefix, "KICK", [
name, user.nick, comment
], true));
}
members = members.remove!(m => m == user);
memberModes.remove(user);
}
void sendModes(Connection user)
{
auto specialModes = "";
string[] specialModeParameters;
if (members.canFind(user) && key !is null)
{
specialModes ~= "k";
specialModeParameters ~= key;
}
if (members.canFind(user) && !memberLimit.isNull)
{
import std.conv : to;
specialModes ~= "l";
specialModeParameters ~= memberLimit.to!string;
}
user.send(Message(_server.name, "324", [
user.nick, name, "+" ~ modes.idup ~ specialModes
] ~ specialModeParameters));
}
bool setMemberMode(Connection target, char mode)
{
if (memberModes[target].canFind(mode))
{
return false;
}
memberModes[target] ~= mode;
return true;
}
bool unsetMemberMode(Connection target, char mode)
{
if (!memberModes[target].canFind(mode))
{
return false;
}
//NOTE: byCodeUnit is necessary due to auto-decoding (https://wiki.dlang.org/Language_issues#Unicode_and_ranges)
import std.utf : byCodeUnit;
import std.range : array;
memberModes[target] = memberModes[target].byCodeUnit.remove!(m => m == mode).array;
return true;
}
bool setMode(char mode)
{
if (modes.canFind(mode))
{
return false;
}
modes ~= mode;
//NOTE: The RFCs don't specify that the invite list should be cleared on +i
version (BasicFixes)
{
if (mode == 'i')
{
inviteHolders = [];
}
}
return true;
}
bool unsetMode(char mode)
{
if (!modes.canFind(mode))
{
return false;
}
//NOTE: byCodeUnit is necessary due to auto-decoding (https://wiki.dlang.org/Language_issues#Unicode_and_ranges)
import std.utf : byCodeUnit;
import std.range : array;
modes = modes.byCodeUnit.remove!(m => m == mode).array;
return true;
}
bool addMaskListEntry(string mask, char mode)
{
if (maskLists[mode].canFind!(m => m.toIRCLower == mask.toIRCLower))
{
return false;
}
maskLists[mode] ~= mask;
return true;
}
bool removeMaskListEntry(string mask, char mode)
{
if (!maskLists[mode].canFind!(m => m.toIRCLower == mask.toIRCLower))
{
return false;
}
maskLists[mode] = maskLists[mode].remove!(m => m.toIRCLower == mask.toIRCLower);
return true;
}
void sendBanList(Connection connection)
{
foreach (entry; maskLists['b'])
{
connection.send(Message(_server.name, "367", [
connection.nick, name, entry
], false));
}
connection.send(Message(_server.name, "368", [
connection.nick, name, "End of channel ban list"
], true));
}
void sendExceptList(Connection connection)
{
foreach (entry; maskLists['e'])
{
connection.send(Message(_server.name, "348", [
connection.nick, name, entry
], false));
}
connection.send(Message(_server.name, "349", [
connection.nick, name, "End of channel exception list"
], true));
}
void sendInviteList(Connection connection)
{
foreach (entry; maskLists['I'])
{
connection.send(Message(_server.name, "346", [
connection.nick, name, entry
], false));
}
connection.send(Message(_server.name, "347", [
connection.nick, name, "End of channel invite list"
], true));
}
bool setKey(string key)
{
this.key = key;
return true;
}
bool unsetKey(string key)
{
if (this.key != key)
{
return false;
}
this.key = null;
return true;
}
void setMemberLimit(uint memberLimit)
{
this.memberLimit = memberLimit;
}
bool unsetMemberLimit()
{
if (memberLimit.isNull)
{
return false;
}
memberLimit.nullify();
return true;
}
string nickPrefix(Connection member)
{
if (!members.canFind(member))
return null;
if (memberModes[member].canFind('o'))
{
return "@";
}
else if (memberModes[member].canFind('v'))
{
return "+";
}
return "";
}
string prefixedNick(Connection member)
{
return nickPrefix(member) ~ member.nick;
}
bool visibleTo(Connection connection)
{
return members.canFind(connection) || !modes.canFind('s') && !modes.canFind('p');
}
bool canReceiveMessagesFromUser(Connection connection)
{
if (modes.canFind('n') && !members.canFind(connection))
{
return false;
}
else if (modes.canFind('m') && nickPrefix(connection).empty)
{
return false;
}
else if (maskLists['b'].any!(m => connection.matchesMask(m))
&& !maskLists['e'].any!(m => connection.matchesMask(m))
&& nickPrefix(connection).length == 0)
{
return false;
}
return true;
}
bool hasMember(Connection connection)
{
return members.canFind(connection);
}
}