diff options
-rw-r--r-- | doc/biboumi.md | 12 | ||||
-rw-r--r-- | src/bridge/bridge.cpp | 32 | ||||
-rw-r--r-- | src/bridge/bridge.hpp | 4 | ||||
-rw-r--r-- | src/irc/irc_channel.cpp | 2 | ||||
-rw-r--r-- | src/irc/irc_channel.hpp | 2 | ||||
-rw-r--r-- | src/irc/irc_client.cpp | 75 | ||||
-rw-r--r-- | src/irc/irc_client.hpp | 7 | ||||
-rw-r--r-- | src/irc/irc_user.cpp | 20 | ||||
-rw-r--r-- | src/irc/irc_user.hpp | 4 | ||||
-rw-r--r-- | src/xmpp/xmpp_component.cpp | 22 | ||||
-rw-r--r-- | src/xmpp/xmpp_component.hpp | 9 |
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); |