summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/biboumi.md12
-rw-r--r--src/bridge/bridge.cpp32
-rw-r--r--src/bridge/bridge.hpp4
-rw-r--r--src/irc/irc_channel.cpp2
-rw-r--r--src/irc/irc_channel.hpp2
-rw-r--r--src/irc/irc_client.cpp75
-rw-r--r--src/irc/irc_client.hpp7
-rw-r--r--src/irc/irc_user.cpp20
-rw-r--r--src/irc/irc_user.hpp4
-rw-r--r--src/xmpp/xmpp_component.cpp22
-rw-r--r--src/xmpp/xmpp_component.hpp9
11 files changed, 181 insertions, 8 deletions
diff --git a/doc/biboumi.md b/doc/biboumi.md
index 811b5c0..35755ef 100644
--- a/doc/biboumi.md
+++ b/doc/biboumi.md
@@ -184,17 +184,21 @@ notified of this XMPP event as well. For example if a mode “+o toto” is
received, then toto’s role will be changed to moderator. The mapping
between IRC modes and XMPP features is as follow:
+`+a`
+
+ Sets the participant’s role to `moderator` and its affiliation to `owner`.
+
`+o`
- Sets the participant’s role to `moderator`.
+ Sets the participant’s role to `moderator` and its affiliation to `admin`.
-`+a`
+`+h`
- Sets the participant’s role to `admin`.
+ Sets the participant’s role to `moderator` and its affiliation to `member`.
`+v`
- Sets the participant’s affiliation to `member`.
+ Sets the participant’s role to `participant` and its affiliation to `member`.
SECURITY
--------
diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp
index fb3afc7..d034bcd 100644
--- a/src/bridge/bridge.cpp
+++ b/src/bridge/bridge.cpp
@@ -225,3 +225,35 @@ void Bridge::send_nickname_conflict_error(const Iid& iid, const std::string& nic
{
this->xmpp->send_nickname_conflict_error(iid.chan + "%" + iid.server, nickname, this->user_jid);
}
+
+void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& target, const char mode)
+{
+ std::string role;
+ std::string affiliation;
+ if (mode == 0)
+ {
+ role = "participant";
+ affiliation = "none";
+ }
+ else if (mode == 'a')
+ {
+ role = "moderator";
+ affiliation = "owner";
+ }
+ else if (mode == 'o')
+ {
+ role = "moderator";
+ affiliation = "admin";
+ }
+ else if (mode == 'h')
+ {
+ role = "moderator";
+ affiliation = "member";
+ }
+ else if (mode == 'v')
+ {
+ role = "participant";
+ affiliation = "member";
+ }
+ this->xmpp->send_affiliation_role_change(iid.chan + "%" + iid.server, target, affiliation, role, this->user_jid);
+}
diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp
index a4eeaa4..7e881d9 100644
--- a/src/bridge/bridge.hpp
+++ b/src/bridge/bridge.hpp
@@ -87,6 +87,10 @@ public:
void send_nick_change(Iid&& iid, const std::string& old_nick, const std::string& new_nick, const bool self);
void kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author);
void send_nickname_conflict_error(const Iid& iid, const std::string& nickname);
+ /**
+ * Send a role/affiliation change, matching the change of mode for that user
+ */
+ void send_affiliation_role_change(const Iid& iid, const std::string& target, const char mode);
/**
* Misc
diff --git a/src/irc/irc_channel.cpp b/src/irc/irc_channel.cpp
index 80e9b24..99783fa 100644
--- a/src/irc/irc_channel.cpp
+++ b/src/irc/irc_channel.cpp
@@ -24,7 +24,7 @@ IrcUser* IrcChannel::get_self() const
return this->self.get();
}
-IrcUser* IrcChannel::find_user(const std::string& name)
+IrcUser* IrcChannel::find_user(const std::string& name) const
{
IrcUser user(name);
for (const auto& u: this->users)
diff --git a/src/irc/irc_channel.hpp b/src/irc/irc_channel.hpp
index edb779a..0160469 100644
--- a/src/irc/irc_channel.hpp
+++ b/src/irc/irc_channel.hpp
@@ -22,7 +22,7 @@ public:
IrcUser* get_self() const;
IrcUser* add_user(const std::string& name,
const std::map<char, char> prefix_to_mode);
- IrcUser* find_user(const std::string& name);
+ IrcUser* find_user(const std::string& name) const;
void remove_user(const IrcUser* user);
private:
diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp
index cdda2b5..8a57371 100644
--- a/src/irc/irc_client.cpp
+++ b/src/irc/irc_client.cpp
@@ -218,7 +218,10 @@ void IrcClient::on_isupport_message(const IrcMessage& message)
j++;
j++;
while (j < token.size() && token[i] != ')')
- this->prefix_to_mode[token[j++]] = token[i++];
+ {
+ this->sorted_user_modes.push_back(token[i]);
+ this->prefix_to_mode[token[j++]] = token[i++];
+ }
}
}
}
@@ -508,6 +511,76 @@ void IrcClient::on_channel_mode(const IrcMessage& message)
this->bridge->send_message(iid, "", std::string("Mode ") + iid.chan +
" [" + mode_arguments + "] by " + user.nick,
true);
+ const IrcChannel* channel = this->get_channel(iid.chan);
+ if (!channel)
+ return;
+
+ // parse the received modes, we need to handle things like "+m-oo coucou toutou"
+ const std::string modes = message.arguments[1];
+ // a list of modified IrcUsers. When we applied all modes, we check the
+ // modes that now applies to each of them, and send a notification for
+ // each one. This is to disallow sending two notifications or more when a
+ // single MODE command changes two or more modes on the same participant
+ std::set<const IrcUser*> modified_users;
+ // If it is true, the modes are added, if it’s false they are
+ // removed. When we encounter the '+' char, the value is changed to true,
+ // and with '-' it is changed to false.
+ bool add = true;
+ bool use_arg;
+ size_t arg_pos = 2;
+ for (const char c: modes)
+ {
+ if (c == '+')
+ add = true;
+ else if (c == '-')
+ add = false;
+ else
+ { // lookup the mode symbol in the 4 chanmodes lists, depending on
+ // the list where it is found, it takes an argument or not
+ size_t type;
+ for (type = 0; type < 4; ++type)
+ if (this->chanmodes[type].find(c) != std::string::npos)
+ break;
+ if (type == 4) // if mode was not found
+ {
+ // That mode can also be of type B if it is present in the
+ // prefix_to_mode map
+ for (const std::pair<char, char>& pair: this->prefix_to_mode)
+ if (pair.second == c)
+ {
+ type = 1;
+ break;
+ }
+ }
+ // modes of type A, B or C (but only with add == true)
+ if (type == 0 || type == 1 ||
+ (type == 2 && add == true))
+ use_arg = true;
+ else // modes of type C (but only with add == false), D, or unknown
+ use_arg = false;
+ if (use_arg == true && message.arguments.size() > arg_pos)
+ {
+ const std::string target = message.arguments[arg_pos++];
+ IrcUser* user = channel->find_user(target);
+ if (!user)
+ {
+ log_warning("Trying to set mode for non-existing user '" << target
+ << "' in channel" << iid.chan);
+ return;
+ }
+ if (add)
+ user->add_mode(c);
+ else
+ user->remove_mode(c);
+ modified_users.insert(user);
+ }
+ }
+ }
+ for (const IrcUser* u: modified_users)
+ {
+ char most_significant_mode = u->get_most_significant_mode(this->sorted_user_modes);
+ this->bridge->send_affiliation_role_change(iid, u->nick, most_significant_mode);
+ }
}
void IrcClient::on_user_mode(const IrcMessage& message)
diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp
index a0258ee..96ded44 100644
--- a/src/irc/irc_client.hpp
+++ b/src/irc/irc_client.hpp
@@ -229,9 +229,14 @@ private:
/**
* See http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt section 3.14
* The example given would be transformed into
- * modes_to_prefix = {{'a', '&'}, {'b', '*'}}
+ * modes_to_prefix = {{'&', 'a'}, {'*', 'b'}}
*/
std::map<char, char> prefix_to_mode;
+ /**
+ * Available user modes, sorted from most significant to least significant
+ * (for example 'ahov' is a common order).
+ */
+ std::vector<char> sorted_user_modes;
IrcClient(const IrcClient&) = delete;
IrcClient(IrcClient&&) = delete;
diff --git a/src/irc/irc_user.cpp b/src/irc/irc_user.cpp
index 934988a..0f1b1ee 100644
--- a/src/irc/irc_user.cpp
+++ b/src/irc/irc_user.cpp
@@ -25,3 +25,23 @@ IrcUser::IrcUser(const std::string& name):
IrcUser(name, {})
{
}
+
+void IrcUser::add_mode(const char mode)
+{
+ this->modes.insert(mode);
+}
+
+void IrcUser::remove_mode(const char mode)
+{
+ this->modes.erase(mode);
+}
+
+char IrcUser::get_most_significant_mode(const std::vector<char>& modes) const
+{
+ for (const char mode: modes)
+ {
+ if (this->modes.find(mode) != this->modes.end())
+ return mode;
+ }
+ return 0;
+}
diff --git a/src/irc/irc_user.hpp b/src/irc/irc_user.hpp
index f30da4d..d3397ca 100644
--- a/src/irc/irc_user.hpp
+++ b/src/irc/irc_user.hpp
@@ -1,6 +1,7 @@
#ifndef IRC_USER_INCLUDED
# define IRC_USER_INCLUDED
+#include <vector>
#include <string>
#include <map>
#include <set>
@@ -14,6 +15,9 @@ public:
explicit IrcUser(const std::string& name,
const std::map<char, char>& prefix_to_mode);
explicit IrcUser(const std::string& name);
+ void add_mode(const char mode);
+ void remove_mode(const char mode);
+ char get_most_significant_mode(const std::vector<char>& sorted_user_modes) const;
std::string nick;
std::string host;
std::set<char> modes;
diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp
index f0d1d3a..a0054ea 100644
--- a/src/xmpp/xmpp_component.cpp
+++ b/src/xmpp/xmpp_component.cpp
@@ -484,3 +484,25 @@ void XmppComponent::send_nickname_conflict_error(const std::string& muc_name,
presence.close();
this->send_stanza(presence);
}
+
+void XmppComponent::send_affiliation_role_change(const std::string& muc_name,
+ const std::string& target,
+ const std::string& affiliation,
+ const std::string& role,
+ const std::string& jid_to)
+{
+ Stanza presence("presence");
+ presence["from"] = muc_name + "@" + this->served_hostname + "/" + target;
+ presence["to"] = jid_to;
+ XmlNode x("x");
+ x["xmlns"] = MUC_USER_NS;
+ XmlNode item("item");
+ item["affiliation"] = affiliation;
+ item["role"] = role;
+ item.close();
+ x.add_child(std::move(item));
+ x.close();
+ presence.add_child(std::move(x));
+ presence.close();
+ this->send_stanza(presence);
+}
diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp
index c10f10a..f86d930 100644
--- a/src/xmpp/xmpp_component.hpp
+++ b/src/xmpp/xmpp_component.hpp
@@ -113,6 +113,15 @@ public:
const std::string& nickname,
const std::string& jid_to);
/**
+ * Send a presence from the MUC indicating a change in the role and/or
+ * affiliation of a participant
+ */
+ void send_affiliation_role_change(const std::string& muc_name,
+ const std::string& target,
+ const std::string& affiliation,
+ const std::string& role,
+ const std::string& jid_to);
+ /**
* Handle the various stanza types
*/
void handle_handshake(const Stanza& stanza);