~singpolyma/biboumi

b7789fe586f375f09134a0817bd3ac19850c048f — louiz’ 6 years ago 1017e8f
Add a Persistent option on channels

fix #3230
M database/database.xml => database/database.xml +2 -0
@@ 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"/>

M doc/biboumi.1.rst => doc/biboumi.1.rst +7 -0
@@ 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
----------------

M src/bridge/bridge.cpp => src/bridge/bridge.cpp +42 -18
@@ 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);

M src/bridge/bridge.hpp => src/bridge/bridge.hpp +1 -0
@@ 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);

M src/irc/irc_client.cpp => src/irc/irc_client.cpp +3 -11
@@ 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

M src/irc/irc_client.hpp => src/irc/irc_client.hpp +1 -1
@@ 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; }

M src/xmpp/biboumi_adhoc_commands.cpp => src/xmpp/biboumi_adhoc_commands.cpp +20 -1
@@ 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();