diff options
-rw-r--r-- | database/database.xml | 2 | ||||
-rw-r--r-- | doc/biboumi.1.rst | 7 | ||||
-rw-r--r-- | src/bridge/bridge.cpp | 60 | ||||
-rw-r--r-- | src/bridge/bridge.hpp | 1 | ||||
-rw-r--r-- | src/irc/irc_client.cpp | 14 | ||||
-rw-r--r-- | src/irc/irc_client.hpp | 2 | ||||
-rw-r--r-- | src/xmpp/biboumi_adhoc_commands.cpp | 21 |
7 files changed, 76 insertions, 31 deletions
diff --git a/database/database.xml b/database/database.xml index 7dc70e1..0bc6e35 100644 --- a/database/database.xml +++ b/database/database.xml @@ -46,6 +46,8 @@ <field name="maxHistoryLength" type="integer" default="20"/> + <field name="persistent" type="boolean" default="false"/> + <index unique="true"> <indexfield name="owner"/> <indexfield name="server"/> diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst index 353638d..ab7beac 100644 --- a/doc/biboumi.1.rst +++ b/doc/biboumi.1.rst @@ -564,6 +564,13 @@ On a channel JID (e.g on the JID #test%chat.freenode.org@biboumi.example.com) * In encoding: see the option with the same name in the server configuration form. * Out encoding: Currently ignored. + * Persistent: If set to true, biboumi will stay in this channel even when + all the XMPP resources have left the room. I.e. it will not send a PART + command, and will stay idle in the channel until the connection is + forcibly closed. If a resource comes back in the room again, and if + the archiving of messages is enabled for this room, the client will + receive the messages that where sent in this channel. This option can be + used to make biboumi act as an IRC bouncer. Raw IRC messages ---------------- diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 7e2d8c1..d033acc 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -62,7 +62,7 @@ void Bridge::shutdown(const std::string& exit_message) for (auto it = this->irc_clients.begin(); it != this->irc_clients.end(); ++it) { it->second->send_quit_command(exit_message); - it->second->leave_dummy_channel(exit_message); + it->second->leave_dummy_channel(exit_message, {}); } } @@ -422,33 +422,48 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con if (!this->is_resource_in_chan(key, resource)) return ; + IrcChannel* channel = irc->get_channel(iid.get_local()); + auto nick = channel->get_self()->nick; + const auto resources = this->number_of_resources_in_chan(key); if (resources == 1) { // Do not send a PART message if we actually are not in that channel // or if we already sent a PART but we are just waiting for the // acknowledgment from the server - IrcChannel* channel = irc->get_channel(iid.get_local()); - if (channel->joined && !channel->parting) - irc->send_part_command(iid.get_local(), status_message); + bool persistent = false; +#ifdef USE_DATABASE + const auto coptions = Database::get_irc_channel_options_with_server_default(this->user_jid, + iid.get_server(), iid.get_local()); + persistent = coptions.persistent.value(); +#endif + if (channel->joined && !channel->parting && !persistent) + { + const auto chan_name = iid.get_local(); + if (chan_name.empty()) + irc->leave_dummy_channel(status_message, resource); + else + irc->send_part_command(iid.get_local(), status_message); + } + else + { + this->send_muc_leave(std::move(iid), std::move(nick), "", true, resource); + } // Since there are no resources left in that channel, we don't // want to receive private messages using this room's JID this->remove_all_preferred_from_jid_of_room(iid.get_local()); } else { - IrcChannel* chan = irc->get_channel(iid.get_local()); - if (chan) - { - auto nick = chan->get_self()->nick; - this->remove_resource_from_chan(key, resource); - this->send_muc_leave(std::move(iid), std::move(nick), - "Biboumi note: "s + std::to_string(resources - 1) + " resources are still in this channel.", - true, resource); - if (this->number_of_channels_the_resource_is_in(iid.get_server(), resource) == 0) - this->remove_resource_from_server(iid.get_server(), resource); - } + if (channel) + this->send_muc_leave(std::move(iid), std::move(nick), + "Biboumi note: "s + std::to_string(resources - 1) + " resources are still in this channel.", + true, resource); + this->remove_resource_from_chan(key, resource); + if (this->number_of_channels_the_resource_is_in(iid.get_server(), resource) == 0) + this->remove_resource_from_server(iid.get_server(), resource); } + } void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick, const std::string& requesting_resource) @@ -862,9 +877,13 @@ void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& me this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid + "/" + resource, self); else - for (const auto& res: this->resources_in_chan[iid.to_tuple()]) - this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), - this->user_jid + "/" + res, self); + { + for (const auto &res: this->resources_in_chan[iid.to_tuple()]) + this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), + this->user_jid + "/" + res, self); + this->remove_all_resources_from_chan(iid.to_tuple()); + + } IrcClient* irc = this->find_irc_client(iid.get_server()); if (irc && irc->number_of_joined_channels() == 0) this->quit_or_start_linger_timer(iid.get_server()); @@ -1137,6 +1156,11 @@ bool Bridge::is_resource_in_chan(const Bridge::ChannelKey& channel, const std::s return false; } +void Bridge::remove_all_resources_from_chan(const Bridge::ChannelKey& channel_key) +{ + this->resources_in_chan.erase(channel_key); +} + void Bridge::add_resource_to_server(const Bridge::IrcHostname& irc_hostname, const std::string& resource) { auto it = this->resources_in_server.find(irc_hostname); diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 73daae7..03eb716 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -312,6 +312,7 @@ private: void add_resource_to_chan(const ChannelKey& channel_key, const std::string& resource); void remove_resource_from_chan(const ChannelKey& channel_key, const std::string& resource); bool is_resource_in_chan(const ChannelKey& channel_key, const std::string& resource) const; + void remove_all_resources_from_chan(const ChannelKey& channel_key); std::size_t number_of_resources_in_chan(const ChannelKey& channel_key) const; void add_resource_to_server(const IrcHostname& irc_hostname, const std::string& resource); diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 93e463b..00eab6f 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -501,15 +501,7 @@ void IrcClient::send_private_message(const std::string& username, const std::str void IrcClient::send_part_command(const std::string& chan_name, const std::string& status_message) { - IrcChannel* channel = this->get_channel(chan_name); - if (channel->joined == true) - { - if (chan_name.empty()) - this->leave_dummy_channel(status_message); - else - this->send_message(IrcMessage("PART", {chan_name, status_message})); - channel->parting = true; - } + this->send_message(IrcMessage("PART", {chan_name, status_message})); } void IrcClient::send_mode_command(const std::string& chan_name, const std::vector<std::string>& arguments) @@ -1160,14 +1152,14 @@ DummyIrcChannel& IrcClient::get_dummy_channel() return this->dummy_channel; } -void IrcClient::leave_dummy_channel(const std::string& exit_message) +void IrcClient::leave_dummy_channel(const std::string& exit_message, const std::string& resource) { if (!this->dummy_channel.joined) return; 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, this->chantypes), 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, resource); } #ifdef BOTAN_FOUND diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 009d0c9..435dce6 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -283,7 +283,7 @@ public: * Leave the dummy channel: forward a message to the user to indicate that * he left it, and mark it as not joined. */ - void leave_dummy_channel(const std::string& exit_message); + void leave_dummy_channel(const std::string& exit_message, const std::string& resource); const std::string& get_hostname() const { return this->hostname; } std::string get_nick() const { return this->current_nick; } diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index a83af80..5ec11da 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -256,7 +256,8 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com XmlSubNode pass(x, "field"); pass["var"] = "pass"; pass["type"] = "text-private"; - pass["label"] = "Server password (to be used in a PASS command when connecting)"; + pass["label"] = "Server password"; + pass["desc"] = "Will be used in a PASS command when connecting"; if (!options.pass.value().empty()) { XmlSubNode pass_value(pass, "value"); @@ -463,6 +464,20 @@ void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& co XmlSubNode encoding_in_value(encoding_in, "value"); encoding_in_value.set_inner(options.encodingIn.value()); } + + XmlSubNode persistent(x, "field"); + persistent["var"] = "persistent"; + persistent["type"] = "boolean"; + persistent["desc"] = "If set to true, when all XMPP clients have left this channel, biboumi will stay idle in it, without sending a PART command."; + persistent["label"] = "Persistent"; + { + XmlSubNode value(persistent, "value"); + value.set_name("value"); + if (options.persistent.value()) + value.set_inner("true"); + else + value.set_inner("false"); + } } void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node) @@ -486,6 +501,10 @@ void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& co else if (field->get_tag("var") == "encoding_in" && value && !value->get_inner().empty()) options.encodingIn = value->get_inner(); + + else if (field->get_tag("var") == "persistent" && + value) + options.persistent = to_bool(value->get_inner()); } options.update(); |