summaryrefslogtreecommitdiff
path: root/src/irc
diff options
context:
space:
mode:
Diffstat (limited to 'src/irc')
-rw-r--r--src/irc/iid.cpp111
-rw-r--r--src/irc/iid.hpp58
-rw-r--r--src/irc/irc_channel.cpp6
-rw-r--r--src/irc/irc_channel.hpp15
-rw-r--r--src/irc/irc_client.cpp123
-rw-r--r--src/irc/irc_client.hpp13
6 files changed, 197 insertions, 129 deletions
diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp
index 0e2841e..d442013 100644
--- a/src/irc/iid.cpp
+++ b/src/irc/iid.cpp
@@ -1,62 +1,72 @@
#include <utils/tolower.hpp>
#include <config/config.hpp>
-
+#include <bridge/bridge.hpp>
#include <irc/iid.hpp>
#include <utils/encoding.hpp>
-Iid::Iid(const std::string& iid):
- is_channel(false),
- is_user(false)
+constexpr char Iid::separator[];
+
+Iid::Iid(const std::string& local, const std::string& server, Iid::Type type):
+ type(type),
+ local(local),
+ server(server)
{
- const std::string fixed_irc_server = Config::get("fixed_irc_server", "");
- if (fixed_irc_server.empty())
- this->init(iid);
- else
- this->init_with_fixed_server(iid, fixed_irc_server);
}
+Iid::Iid(const std::string& iid, const std::set<char>& chantypes)
+{
+ this->init(iid);
+ this->set_type(std::set<char>(chantypes));
+}
-void Iid::init(const std::string& iid)
+Iid::Iid(const std::string& iid, const std::initializer_list<char>& chantypes):
+ Iid(iid, std::set<char>(chantypes))
{
- const std::string::size_type sep = iid.find_first_of("%!");
- if (sep != std::string::npos)
- {
- if (iid[sep] == '%')
- this->is_channel = true;
- else
- this->is_user = true;
- this->set_local(iid.substr(0, sep));
- this->set_server(iid.substr(sep + 1));
- }
- else
- this->set_server(iid);
}
-void Iid::init_with_fixed_server(const std::string& iid, const std::string& hostname)
+Iid::Iid(const std::string& iid, const Bridge *bridge)
+{
+ this->init(iid);
+ const auto chantypes = bridge->get_chantypes(this->server);
+ this->set_type(chantypes);
+}
+
+void Iid::set_type(const std::set<char>& chantypes)
{
- this->set_server(hostname);
+ if (this->local.empty())
+ return;
- const std::string::size_type sep = iid.find("!");
+ if (chantypes.count(this->local[0]) == 1)
+ this->type = Iid::Type::Channel;
+ else
+ this->type = Iid::Type::User;
+}
+
+void Iid::init(const std::string& iid)
+{
+ const std::string fixed_irc_server = Config::get("fixed_irc_server", "");
- // Without any separator, we consider that it's a channel
- if (sep == std::string::npos)
+ if (fixed_irc_server.empty())
+ {
+ const std::string::size_type sep = iid.find('%');
+ if (sep != std::string::npos)
{
- this->is_channel = true;
- this->set_local(iid);
+ this->set_local(iid.substr(0, sep));
+ this->set_server(iid.substr(sep + 1));
+ this->type = Iid::Type::Channel;
}
- else // A separator can be present to differenciate a channel from a user,
- // but the part behind it (the hostname) is ignored
+ else
{
- this->set_local(iid.substr(0, sep));
- this->is_user = true;
+ this->set_server(iid);
+ this->type = Iid::Type::Server;
}
-}
-
-Iid::Iid():
- is_channel(false),
- is_user(false)
-{
+ }
+ else
+ {
+ this->set_server(fixed_irc_server);
+ this->set_local(iid);
+ }
}
void Iid::set_local(const std::string& loc)
@@ -88,27 +98,18 @@ const std::string& Iid::get_server() const
return this->server;
}
-std::string Iid::get_sep() const
-{
- if (this->is_channel)
- return "%";
- else if (this->is_user)
- return "!";
- return "";
-}
-
namespace std {
const std::string to_string(const Iid& iid)
{
if (Config::get("fixed_irc_server", "").empty())
- return iid.get_encoded_local() + iid.get_sep() + iid.get_server();
+ {
+ if (iid.type == Iid::Type::Server)
+ return iid.get_server();
+ else
+ return iid.get_encoded_local() + iid.separator + iid.get_server();
+ }
else
- {
- if (iid.get_sep() == "!")
- return iid.get_encoded_local() + iid.get_sep();
- else
- return iid.get_encoded_local();
- }
+ return iid.get_encoded_local();
}
}
diff --git a/src/irc/iid.hpp b/src/irc/iid.hpp
index 3b11470..44861c1 100644
--- a/src/irc/iid.hpp
+++ b/src/irc/iid.hpp
@@ -2,48 +2,64 @@
#include <string>
+#include <set>
+
+class Bridge;
/**
* A name representing an IRC channel on an IRC server, or an IRC user on an
* IRC server, or just an IRC server.
*
- * The separator for an user is '!', for a channel it's '%'. If no separator
- * is present, it's just an irc server.
+ * The separator is '%' between the local part (nickname or channel) and the
+ * server part. If no separator is present, it's just an irc server.
+ * If it is present, the first character of the local part determines if it’s
+ * a channel or a user: ff the local part is empty or if its first character
+ * is part of the chantypes characters, then it’s a channel, otherwise it’s
+ * a user.
+ *
* It’s possible to have an empty-string server, but it makes no sense in
- * the biboumi context.
+ * biboumi’s context.
+ *
+ * Assuming the chantypes are '#' and '&':
*
* #test%irc.example.org has :
* - local: "#test" (the # is part of the name, it could very well be absent, or & (for example) instead)
* - server: "irc.example.org"
- * - is_channel: true
- * - is_user: false
+ * - type: channel
*
* %irc.example.org:
* - local: ""
* - server: "irc.example.org"
- * - is_channel: true
- * - is_user: false
- * Note: this is the special empty-string channel, used internal in biboumi
+ * - type: channel
+ * Note: this is the special empty-string channel, used internally in biboumi
* but has no meaning on IRC.
*
- * foo!irc.example.org
+ * foo%irc.example.org
* - local: "foo"
* - server: "irc.example.org"
- * - is_channel: false
- * - is_user: true
- * Note: the empty-string user (!irc.example.org) has no special meaning in biboumi
+ * - type: user
+ * Note: the empty-string user (!irc.example.org) makes no sense for biboumi
*
* irc.example.org:
* - local: ""
* - server: "irc.example.org"
- * - is_channel: false
- * - is_user: false
+ * - type: server
*/
class Iid
{
public:
- Iid(const std::string& iid);
- Iid();
+ enum class Type
+ {
+ Channel,
+ User,
+ Server,
+ };
+ static constexpr char separator[]{"%"};
+ Iid(const std::string& iid, const std::set<char>& chantypes);
+ Iid(const std::string& iid, const std::initializer_list<char>& chantypes);
+ Iid(const std::string& iid, const Bridge* bridge);
+ Iid(const std::string& local, const std::string& server, Type type);
+ Iid() = default;
Iid(const Iid&) = default;
Iid(Iid&&) = delete;
@@ -52,21 +68,19 @@ public:
void set_local(const std::string& loc);
void set_server(const std::string& serv);
+
const std::string& get_local() const;
const std::string get_encoded_local() const;
const std::string& get_server() const;
- bool is_channel;
- bool is_user;
-
- std::string get_sep() const;
-
std::tuple<std::string, std::string> to_tuple() const;
+ Type type { Type::Server };
+
private:
void init(const std::string& iid);
- void init_with_fixed_server(const std::string& iid, const std::string& hostname);
+ void set_type(const std::set<char>& chantypes);
std::string local;
std::string server;
diff --git a/src/irc/irc_channel.cpp b/src/irc/irc_channel.cpp
index e769245..40d7f54 100644
--- a/src/irc/irc_channel.cpp
+++ b/src/irc/irc_channel.cpp
@@ -1,12 +1,6 @@
#include <irc/irc_channel.hpp>
#include <algorithm>
-IrcChannel::IrcChannel():
- joined(false),
- self(nullptr)
-{
-}
-
void IrcChannel::set_self(const std::string& name)
{
this->self = std::make_unique<IrcUser>(name);
diff --git a/src/irc/irc_channel.hpp b/src/irc/irc_channel.hpp
index 2bcefaf..7c269b9 100644
--- a/src/irc/irc_channel.hpp
+++ b/src/irc/irc_channel.hpp
@@ -14,16 +14,19 @@
class IrcChannel
{
public:
- explicit IrcChannel();
+ IrcChannel() = default;
IrcChannel(const IrcChannel&) = delete;
IrcChannel(IrcChannel&&) = delete;
IrcChannel& operator=(const IrcChannel&) = delete;
IrcChannel& operator=(IrcChannel&&) = delete;
- bool joined;
- std::string topic;
- std::string topic_author;
+ bool joined{false};
+ // Set to true if we sent a PART but didn’t yet receive the PART ack from
+ // the server
+ bool parting{false};
+ std::string topic{};
+ std::string topic_author{};
void set_self(const std::string& name);
IrcUser* get_self() const;
IrcUser* add_user(const std::string& name,
@@ -35,8 +38,8 @@ public:
{ return this->users; }
protected:
- std::unique_ptr<IrcUser> self;
- std::vector<std::unique_ptr<IrcUser>> users;
+ std::unique_ptr<IrcUser> self{};
+ std::vector<std::unique_ptr<IrcUser>> users{};
};
/**
diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp
index dd83307..b0d3a47 100644
--- a/src/irc/irc_client.cpp
+++ b/src/irc/irc_client.cpp
@@ -46,6 +46,7 @@ static const std::unordered_map<std::string,
{"323", {&IrcClient::on_rpl_listend, {0, 0}}},
{"RPL_NOTOPIC", {&IrcClient::on_empty_topic, {0, 0}}},
{"331", {&IrcClient::on_empty_topic, {0, 0}}},
+ {"341", {&IrcClient::on_invited, {3, 0}}},
{"RPL_MOTDSTART", {&IrcClient::empty_motd, {0, 0}}},
{"375", {&IrcClient::empty_motd, {0, 0}}},
{"RPL_MOTD", {&IrcClient::on_motd_line, {2, 0}}},
@@ -64,6 +65,8 @@ static const std::unordered_map<std::string,
{"432", {&IrcClient::on_erroneous_nickname, {2, 0}}},
{"433", {&IrcClient::on_nickname_conflict, {2, 0}}},
{"438", {&IrcClient::on_nickname_change_too_fast, {2, 0}}},
+ {"443", {&IrcClient::on_useronchannel, {3, 0}}},
+ {"ERR_USERONCHANNEL", {&IrcClient::on_useronchannel, {3, 0}}},
{"001", {&IrcClient::on_welcome_message, {1, 0}}},
{"PART", {&IrcClient::on_part, {1, 0}}},
{"ERROR", {&IrcClient::on_error, {1, 0}}},
@@ -73,6 +76,7 @@ static const std::unordered_map<std::string,
{"PING", {&IrcClient::send_pong_command, {1, 0}}},
{"PONG", {&IrcClient::on_pong, {0, 0}}},
{"KICK", {&IrcClient::on_kick, {3, 0}}},
+ {"INVITE", {&IrcClient::on_invite, {2, 0}}},
{"401", {&IrcClient::on_generic_error, {2, 0}}},
{"402", {&IrcClient::on_generic_error, {2, 0}}},
@@ -95,7 +99,6 @@ static const std::unordered_map<std::string,
{"436", {&IrcClient::on_generic_error, {2, 0}}},
{"441", {&IrcClient::on_generic_error, {2, 0}}},
{"442", {&IrcClient::on_generic_error, {2, 0}}},
- {"443", {&IrcClient::on_generic_error, {2, 0}}},
{"444", {&IrcClient::on_generic_error, {2, 0}}},
{"446", {&IrcClient::on_generic_error, {2, 0}}},
{"451", {&IrcClient::on_generic_error, {2, 0}}},
@@ -213,7 +216,7 @@ void IrcClient::on_connection_failed(const std::string& reason)
// Send an error message for all room that the user wanted to join
for (const auto& tuple: this->channels_to_join)
{
- Iid iid(std::get<0>(tuple) + "%" + this->hostname);
+ Iid iid(std::get<0>(tuple) + "%" + this->hostname, this->chantypes);
this->bridge.send_presence_error(iid, this->current_nick,
"cancel", "item-not-found",
"", reason);
@@ -426,7 +429,12 @@ void IrcClient::send_kick_command(const std::string& chan_name, const std::strin
void IrcClient::send_list_command()
{
- this->send_message(IrcMessage("LIST", {}));
+ this->send_message(IrcMessage("LIST", {"*"}));
+}
+
+void IrcClient::send_invitation(const std::string& chan_name, const std::string& nick)
+{
+ this->send_message(IrcMessage("INVITE", {nick, chan_name}));
}
void IrcClient::send_topic_command(const std::string& chan_name, const std::string& topic)
@@ -495,6 +503,7 @@ void IrcClient::send_part_command(const std::string& chan_name, const std::strin
this->leave_dummy_channel(status_message);
else
this->send_message(IrcMessage("PART", {chan_name, status_message}));
+ channel->parting = true;
}
}
@@ -551,7 +560,7 @@ void IrcClient::on_notice(const IrcMessage& message)
if (this->nicks_to_treat_as_private.find(nick) !=
this->nicks_to_treat_as_private.end())
{ // We previously sent a message to that nick)
- this->bridge.send_message({nick + "!" + this->hostname}, nick, body,
+ this->bridge.send_message({nick, this->hostname, Iid::Type::User}, nick, body,
false);
}
else
@@ -663,12 +672,12 @@ void IrcClient::on_channel_message(const IrcMessage& message)
bool muc = true;
if (!this->get_channel(iid.get_local())->joined)
{
- iid.is_user = true;
+ iid.type = Iid::Type::User;
iid.set_local(nick);
muc = false;
}
else
- iid.is_channel = true;
+ iid.type = Iid::Type::Channel;
if (!body.empty() && body[0] == '\01')
{
if (body.substr(1, 6) == "ACTION")
@@ -701,6 +710,14 @@ void IrcClient::empty_motd(const IrcMessage&)
this->motd.erase();
}
+void IrcClient::on_invited(const IrcMessage& message)
+{
+ const std::string& chan_name = message.arguments[2];
+ const std::string& invited_nick = message.arguments[1];
+
+ this->bridge.send_xmpp_message(this->hostname, "", invited_nick + " has been invited to " + chan_name);
+}
+
void IrcClient::on_empty_topic(const IrcMessage& message)
{
const std::string chan_name = utils::tolower(message.arguments[1]);
@@ -748,7 +765,9 @@ void IrcClient::on_channel_completely_joined(const IrcMessage& message)
const std::string chan_name = utils::tolower(message.arguments[1]);
IrcChannel* channel = this->get_channel(chan_name);
channel->joined = true;
- this->bridge.send_user_join(this->hostname, chan_name, channel->get_self(), channel->get_self()->get_most_significant_mode(this->sorted_user_modes), true);
+ this->bridge.send_user_join(this->hostname, chan_name, channel->get_self(),
+ channel->get_self()->get_most_significant_mode(this->sorted_user_modes), true);
+ this->bridge.send_room_history(this->hostname, chan_name);
this->bridge.send_topic(this->hostname, chan_name, channel->topic, channel->topic_author);
}
@@ -780,7 +799,7 @@ void IrcClient::on_nickname_conflict(const IrcMessage& message)
Iid iid;
iid.set_local(it->first);
iid.set_server(this->hostname);
- iid.is_channel = true;
+ iid.type = Iid::Type::Channel;
this->bridge.send_nickname_conflict_error(iid, nickname);
}
}
@@ -797,13 +816,12 @@ void IrcClient::on_nickname_change_too_fast(const IrcMessage& message)
Iid iid;
iid.set_local(it->first);
iid.set_server(this->hostname);
- iid.is_channel = true;
+ iid.type = Iid::Type::Channel;
this->bridge.send_presence_error(iid, nickname,
"cancel", "not-acceptable",
"", txt);
}
}
-
void IrcClient::on_generic_error(const IrcMessage& message)
{
const std::string error_msg = message.arguments.size() >= 3 ?
@@ -811,6 +829,12 @@ void IrcClient::on_generic_error(const IrcMessage& message)
this->send_gateway_message(message.arguments[1] + ": " + error_msg, message.prefix);
}
+void IrcClient::on_useronchannel(const IrcMessage& message)
+{
+ this->send_gateway_message(message.arguments[1] + " " + message.arguments[3] + " "
+ + message.arguments[2]);
+}
+
void IrcClient::on_welcome_message(const IrcMessage& message)
{
this->current_nick = message.arguments[0];
@@ -858,7 +882,7 @@ void IrcClient::on_part(const IrcMessage& message)
Iid iid;
iid.set_local(chan_name);
iid.set_server(this->hostname);
- iid.is_channel = true;
+ iid.type = Iid::Type::Channel;
bool self = channel->get_self()->nick == nick;
if (self)
{
@@ -880,7 +904,7 @@ void IrcClient::on_error(const IrcMessage& message)
Iid iid;
iid.set_local(it->first);
iid.set_server(this->hostname);
- iid.is_channel = true;
+ iid.type = Iid::Type::Channel;
IrcChannel* channel = it->second.get();
if (!channel->joined)
continue;
@@ -908,7 +932,7 @@ void IrcClient::on_quit(const IrcMessage& message)
Iid iid;
iid.set_local(chan_name);
iid.set_server(this->hostname);
- iid.is_channel = true;
+ iid.type = Iid::Type::Channel;
this->bridge.send_muc_leave(std::move(iid), std::move(nick), txt, false);
}
}
@@ -916,29 +940,38 @@ void IrcClient::on_quit(const IrcMessage& message)
void IrcClient::on_nick(const IrcMessage& message)
{
- const std::string new_nick = message.arguments[0];
+ const std::string new_nick = IrcUser(message.arguments[0]).nick;
+ const std::string current_nick = IrcUser(message.prefix).nick;
+ const auto change_nick_func = [this, &new_nick, &current_nick](const std::string& chan_name, const IrcChannel* channel)
+ {
+ IrcUser* user;
+ if (channel->get_self() && channel->get_self()->nick == current_nick)
+ user = channel->get_self();
+ else
+ user = channel->find_user(current_nick);
+ if (user)
+ {
+ std::string old_nick = user->nick;
+ Iid iid(chan_name, this->hostname, Iid::Type::Channel);
+ const bool self = channel->get_self()->nick == old_nick;
+ const char user_mode = user->get_most_significant_mode(this->sorted_user_modes);
+ this->bridge.send_nick_change(std::move(iid), old_nick, new_nick, user_mode, self);
+ user->nick = new_nick;
+ if (self)
+ {
+ channel->get_self()->nick = new_nick;
+ this->current_nick = new_nick;
+ }
+ }
+ };
+
+ if (this->get_dummy_channel().joined)
+ {
+ change_nick_func("", &this->get_dummy_channel());
+ }
for (auto it = this->channels.begin(); it != this->channels.end(); ++it)
{
- const std::string chan_name = it->first;
- IrcChannel* channel = it->second.get();
- IrcUser* user = channel->find_user(message.prefix);
- if (user)
- {
- std::string old_nick = user->nick;
- Iid iid;
- iid.set_local(chan_name);
- iid.set_server(this->hostname);
- iid.is_channel = true;
- const bool self = channel->get_self()->nick == old_nick;
- const char user_mode = user->get_most_significant_mode(this->sorted_user_modes);
- this->bridge.send_nick_change(std::move(iid), old_nick, new_nick, user_mode, self);
- user->nick = new_nick;
- if (self)
- {
- channel->get_self()->nick = new_nick;
- this->current_nick = new_nick;
- }
- }
+ change_nick_func(it->first, it->second.get());
}
}
@@ -950,14 +983,26 @@ void IrcClient::on_kick(const IrcMessage& message)
IrcChannel* channel = this->get_channel(chan_name);
if (!channel->joined)
return ;
- if (channel->get_self()->nick == target)
+ const bool self = channel->get_self()->nick == target;
+ if (self)
channel->joined = false;
IrcUser author(message.prefix);
Iid iid;
iid.set_local(chan_name);
iid.set_server(this->hostname);
- iid.is_channel = true;
- this->bridge.kick_muc_user(std::move(iid), target, reason, author.nick);
+ iid.type = Iid::Type::Channel;
+ this->bridge.kick_muc_user(std::move(iid), target, reason, author.nick, self);
+}
+
+void IrcClient::on_invite(const IrcMessage& message)
+{
+ IrcUser author(message.prefix);
+ Iid iid;
+ iid.set_local(message.arguments[1]);
+ iid.set_server(this->hostname);
+ iid.type = Iid::Type::Channel;
+
+ this->bridge.send_xmpp_invitation(iid, author.nick);
}
void IrcClient::on_mode(const IrcMessage& message)
@@ -976,7 +1021,7 @@ void IrcClient::on_channel_mode(const IrcMessage& message)
Iid iid;
iid.set_local(message.arguments[0]);
iid.set_server(this->hostname);
- iid.is_channel = true;
+ iid.type = Iid::Type::Channel;
IrcUser user(message.prefix);
std::string mode_arguments;
for (size_t i = 1; i < message.arguments.size(); ++i)
@@ -1105,7 +1150,7 @@ void IrcClient::leave_dummy_channel(const std::string& exit_message)
this->dummy_channel.joined = false;
this->dummy_channel.joining = false;
this->dummy_channel.remove_all_users();
- this->bridge.send_muc_leave(Iid("%"s + this->hostname), std::string(this->current_nick), exit_message, true);
+ this->bridge.send_muc_leave(Iid("%"s + this->hostname, this->chantypes), std::string(this->current_nick), exit_message, true);
}
#ifdef BOTAN_FOUND
diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp
index fc3918e..1b4d892 100644
--- a/src/irc/irc_client.hpp
+++ b/src/irc/irc_client.hpp
@@ -129,6 +129,7 @@ public:
* Send the LIST irc command
*/
void send_list_command();
+ void send_invitation(const std::string& chan_name, const std::string& nick);
void send_topic_command(const std::string& chan_name, const std::string& topic);
/**
* Send the QUIT irc command
@@ -213,6 +214,10 @@ public:
*/
void on_empty_topic(const IrcMessage& message);
/**
+ * The IRC server is confirming that the invitation has been forwarded
+ */
+ void on_invited(const IrcMessage& message);
+ /**
* The channel has been completely joined (self presence, topic, all names
* received etc), send the self presence and topic to the XMPP user.
*/
@@ -235,6 +240,10 @@ public:
*/
void on_nickname_change_too_fast(const IrcMessage& message);
/**
+ * An error when we try to invite a user already in the channel
+ */
+ void on_useronchannel(const IrcMessage& message);
+ /**
* Handles most errors from the server by just forwarding the message to the user.
*/
void on_generic_error(const IrcMessage& message);
@@ -244,6 +253,7 @@ public:
void on_welcome_message(const IrcMessage& message);
void on_part(const IrcMessage& message);
void on_error(const IrcMessage& message);
+ void on_invite(const IrcMessage& message);
void on_nick(const IrcMessage& message);
void on_kick(const IrcMessage& message);
void on_mode(const IrcMessage& message);
@@ -280,8 +290,9 @@ public:
const Resolver& get_resolver() const { return this->dns_resolver; }
- const std::vector<char>& get_sorted_user_modes() const { return sorted_user_modes; }
+ const std::vector<char>& get_sorted_user_modes() const { return this->sorted_user_modes; }
+ std::set<char> get_chantypes() const { return this->chantypes; }
private:
/**
* The hostname of the server we are connected to.