summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bridge/bridge.cpp380
-rw-r--r--src/bridge/bridge.hpp59
-rw-r--r--src/bridge/list_element.hpp7
-rw-r--r--src/bridge/result_set_management.hpp10
-rw-r--r--src/database/database.cpp105
-rw-r--r--src/database/database.hpp13
-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
-rw-r--r--src/main.cpp7
-rw-r--r--src/xmpp/biboumi_adhoc_commands.cpp179
-rw-r--r--src/xmpp/biboumi_adhoc_commands.hpp5
-rw-r--r--src/xmpp/biboumi_component.cpp384
-rw-r--r--src/xmpp/biboumi_component.hpp28
17 files changed, 1204 insertions, 299 deletions
diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp
index 17d3ec6..a0ecc6e 100644
--- a/src/bridge/bridge.cpp
+++ b/src/bridge/bridge.cpp
@@ -1,5 +1,4 @@
#include <bridge/bridge.hpp>
-#include <bridge/list_element.hpp>
#include <xmpp/biboumi_component.hpp>
#include <network/poller.hpp>
#include <utils/empty_if_fixed_server.hpp>
@@ -10,6 +9,8 @@
#include <utils/split.hpp>
#include <xmpp/jid.hpp>
#include <database/database.hpp>
+#include "result_set_management.hpp"
+#include <algorithm>
using namespace std::string_literals;
@@ -32,6 +33,10 @@ Bridge::Bridge(const std::string& user_jid, BiboumiComponent& xmpp, std::shared_
xmpp(xmpp),
poller(poller)
{
+#ifdef USE_DATABASE
+ const auto options = Database::get_global_options(this->user_jid);
+ this->set_record_history(options.recordHistory.value());
+#endif
}
/**
@@ -60,6 +65,20 @@ void Bridge::shutdown(const std::string& exit_message)
}
}
+void Bridge::remove_resource(const std::string& resource,
+ const std::string& part_message)
+{
+ const auto resources_in_chan_copy = this->resources_in_chan;
+ for (const auto& chan_pair: resources_in_chan_copy)
+ {
+ const ChannelKey& channel_key = chan_pair.first;
+ const std::set<Resource>& resources = chan_pair.second;
+ if (resources.count(resource))
+ this->leave_irc_channel({std::get<0>(channel_key), std::get<1>(channel_key), {}},
+ part_message, resource);
+ }
+}
+
void Bridge::clean()
{
auto it = this->irc_clients.begin();
@@ -133,7 +152,7 @@ IrcClient* Bridge::get_irc_client(const std::string& hostname)
}
}
-IrcClient* Bridge::find_irc_client(const std::string& hostname)
+IrcClient* Bridge::find_irc_client(const std::string& hostname) const
{
try
{
@@ -158,16 +177,23 @@ bool Bridge::join_irc_channel(const Iid& iid, const std::string& nickname, const
{ // Join the dummy channel
if (irc->is_welcomed())
{
- if (irc->get_dummy_channel().joined)
+ if (res_in_chan)
return false;
// Immediately simulate a message coming from the IRC server saying that we
// joined the channel
- const IrcMessage join_message(irc->get_nick(), "JOIN", {""});
- irc->on_channel_join(join_message);
- const IrcMessage end_join_message(std::string(iid.get_server()), "366",
- {irc->get_nick(),
- "", "End of NAMES list"});
- irc->on_channel_completely_joined(end_join_message);
+ if (irc->get_dummy_channel().joined)
+ {
+ this->generate_channel_join_for_resource(iid, resource);
+ }
+ else
+ {
+ const IrcMessage join_message(irc->get_nick(), "JOIN", {""});
+ irc->on_channel_join(join_message);
+ const IrcMessage end_join_message(std::string(iid.get_server()), "366",
+ {irc->get_nick(),
+ "", "End of NAMES list"});
+ irc->on_channel_completely_joined(end_join_message);
+ }
}
else
{
@@ -224,6 +250,13 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body)
irc->send_channel_message(iid.get_local(), action_prefix + line.substr(4) + "\01");
else
irc->send_channel_message(iid.get_local(), line);
+
+#ifdef USE_DATABASE
+ const auto xmpp_body = this->make_xmpp_body(line);
+ if (this->record_history)
+ Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(),
+ std::get<0>(xmpp_body), irc->get_own_nick());
+#endif
for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(),
this->make_xmpp_body(line), this->user_jid + "/" + resource);
@@ -324,7 +357,7 @@ void Bridge::send_raw_message(const std::string& hostname, const std::string& bo
irc->send_raw(body);
}
-void Bridge::leave_irc_channel(Iid&& iid, std::string&& status_message, const std::string& resource)
+void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, const std::string& resource)
{
IrcClient* irc = this->get_irc_client(iid.get_server());
const auto key = iid.to_tuple();
@@ -334,7 +367,12 @@ void Bridge::leave_irc_channel(Iid&& iid, std::string&& status_message, const st
const auto resources = this->number_of_resources_in_chan(key);
if (resources == 1)
{
- irc->send_part_command(iid.get_local(), status_message);
+ // 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);
// 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());
@@ -361,45 +399,164 @@ void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick)
irc->send_nick_command(new_nick);
}
-void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq_id,
- const std::string& to_jid)
+void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq_id, const std::string& to_jid,
+ ResultSetInfo rs_info)
{
- IrcClient* irc = this->get_irc_client(iid.get_server());
+ auto& list = channel_list_cache[iid.get_server()];
+
+ // We fetch the list from the IRC server only if we have a complete
+ // cached list that needs to be invalidated (that is, when the request
+ // doesn’t have a after or before, or when the list is empty).
+ // If the list is not complete, this means that a request is already
+ // ongoing, so we just need to add the callback.
+ // By default the list is complete and empty.
+ if (list.complete &&
+ (list.channels.empty() || (rs_info.after.empty() && rs_info.before.empty())))
+ {
+ IrcClient* irc = this->get_irc_client(iid.get_server());
+ irc->send_list_command();
+
+ // Add a callback that will populate our list
+ list.channels.clear();
+ list.complete = false;
+ irc_responder_callback_t cb = [this, iid](const std::string& irc_hostname,
+ const IrcMessage& message) -> bool
+ {
+ if (irc_hostname != iid.get_server())
+ return false;
- irc->send_list_command();
+ auto& list = channel_list_cache[iid.get_server()];
+
+ if (message.command == "263" || message.command == "RPL_TRYAGAIN" || message.command == "ERR_TOOMANYMATCHES"
+ || message.command == "ERR_NOSUCHSERVER")
+ {
+ list.complete = true;
+ return true;
+ }
+ else if (message.command == "322" || message.command == "RPL_LIST")
+ { // Add element to list
+ if (message.arguments.size() == 4)
+ {
+ list.channels.emplace_back(message.arguments[1] + utils::empty_if_fixed_server("%" + iid.get_server()),
+ message.arguments[2], message.arguments[3]);
+ }
+ return false;
+ }
+ else if (message.command == "323" || message.command == "RPL_LISTEND")
+ { // Send the iq response with the content of the list
+ list.complete = true;
+ return true;
+ }
+ return false;
+ };
- std::vector<ListElement> list;
+ this->add_waiting_irc(std::move(cb));
+ }
- irc_responder_callback_t cb = [this, iid, iq_id, to_jid, list=std::move(list)](const std::string& irc_hostname,
- const IrcMessage& message) mutable -> bool
+ // If the list is complete, we immediately send the answer.
+ // Otherwise, we install a callback, that will populate our list and send
+ // the answer when we can.
+ if (list.complete)
{
- if (irc_hostname != iid.get_server())
+ this->send_matching_channel_list(list, rs_info, iq_id, to_jid, std::to_string(iid));
+ }
+ else
+ {
+ // Add a callback to answer the request as soon as we can
+ irc_responder_callback_t cb = [this, iid, iq_id, to_jid,
+ rs_info=std::move(rs_info)](const std::string& irc_hostname,
+ const IrcMessage& message) -> bool
+ {
+ if (irc_hostname != iid.get_server())
+ return false;
+
+ if (message.command == "263" || message.command == "RPL_TRYAGAIN" || message.command == "ERR_TOOMANYMATCHES"
+ || message.command == "ERR_NOSUCHSERVER")
+ {
+ std::string text;
+ if (message.arguments.size() >= 2)
+ text = message.arguments[1];
+ this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "wait", "service-unavailable", text, false);
+ return true;
+ }
+ else if (message.command == "322" || message.command == "RPL_LIST")
+ {
+ auto& list = channel_list_cache[iid.get_server()];
+ const auto res = this->send_matching_channel_list(list, rs_info, iq_id, to_jid, std::to_string(iid));
+ log_debug("We added a new channel in our list, can we send the result? ", std::boolalpha, res);
+ return res;
+ }
+ else if (message.command == "323" || message.command == "RPL_LISTEND")
+ { // Send the iq response with the content of the list
+ auto& list = channel_list_cache[iid.get_server()];
+ this->send_matching_channel_list(list, rs_info, iq_id, to_jid, std::to_string(iid));
+ return true;
+ }
return false;
- if (message.command == "263" || message.command == "RPL_TRYAGAIN" ||
- message.command == "ERR_TOOMANYMATCHES" || message.command == "ERR_NOSUCHSERVER")
+ };
+
+ this->add_waiting_irc(std::move(cb));
+ }
+}
+
+bool Bridge::send_matching_channel_list(const ChannelList& channel_list, const ResultSetInfo& rs_info,
+ const std::string& id, const std::string& to_jid, const std::string& from)
+{
+ auto begin = channel_list.channels.begin();
+ auto end = channel_list.channels.begin();
+ if (channel_list.complete)
+ {
+ begin = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element)
+ {
+ return rs_info.after == element.channel + "@" + this->xmpp.get_served_hostname();
+ });
+ if (begin == channel_list.channels.end())
+ begin = channel_list.channels.begin();
+ else
+ begin = std::next(begin);
+ end = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element)
+ {
+ return rs_info.before == element.channel + "@" + this->xmpp.get_served_hostname();
+ });
+ if (rs_info.max >= 0)
{
- std::string text;
- if (message.arguments.size() >= 2)
- text = message.arguments[1];
- this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id,
- "wait", "service-unavailable", text, false);
- return true;
+ if (std::distance(begin, end) >= rs_info.max)
+ end = begin + rs_info.max;
}
- else if (message.command == "322" || message.command == "RPL_LIST")
- { // Add element to list
- if (message.arguments.size() == 4)
- list.emplace_back(message.arguments[1], message.arguments[2],
- message.arguments[3]);
- return false;
+ }
+ else
+ {
+ if (rs_info.after.empty() && rs_info.before.empty() && rs_info.max < 0)
+ return false;
+ if (!rs_info.after.empty())
+ {
+ begin = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element)
+ {
+ return rs_info.after == element.channel + "@" + this->xmpp.get_served_hostname();
+ });
+ if (begin == channel_list.channels.end())
+ return false;
+ begin = std::next(begin);
}
- else if (message.command == "323" || message.command == "RPL_LISTEND")
- { // Send the iq response with the content of the list
- this->xmpp.send_iq_room_list_result(iq_id, to_jid, std::to_string(iid), list);
- return true;
+ if (!rs_info.before.empty())
+ {
+ end = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element)
+ {
+ return rs_info.before == element.channel + "@" + this->xmpp.get_served_hostname();
+ });
+ if (end == channel_list.channels.end())
+ return false;
}
- return false;
- };
- this->add_waiting_irc(std::move(cb));
+ if (rs_info.max >= 0)
+ {
+ if (std::distance(begin, end) < rs_info.max)
+ return false;
+ else
+ end = begin + rs_info.max;
+ }
+ }
+ this->xmpp.send_iq_room_list_result(id, to_jid, from, channel_list, begin, end, rs_info);
+ return true;
}
void Bridge::send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason,
@@ -470,10 +627,11 @@ void Bridge::send_irc_user_ping_request(const std::string& irc_hostname, const s
const std::string& iq_id, const std::string& to_jid,
const std::string& from_jid)
{
- Iid iid(nick + "!" + irc_hostname);
+ Iid iid(nick, irc_hostname, Iid::Type::User);
this->send_private_message(iid, "\01PING " + iq_id + "\01");
- irc_responder_callback_t cb = [this, nick=utils::tolower(nick), iq_id, to_jid, irc_hostname, from_jid](const std::string& hostname, const IrcMessage& message) -> bool
+ irc_responder_callback_t cb = [this, nick=utils::tolower(nick), iq_id, to_jid, irc_hostname, from_jid]
+ (const std::string& hostname, const IrcMessage& message) -> bool
{
if (irc_hostname != hostname || message.arguments.size() < 2)
return false;
@@ -537,20 +695,37 @@ void Bridge::on_gateway_ping(const std::string& irc_hostname, const std::string&
"", true);
}
+void Bridge::send_irc_invitation(const Iid& iid, const std::string& to)
+{
+ IrcClient* irc = this->get_irc_client(iid.get_server());
+ Jid to_jid(to);
+ std::string target_nick;
+ // Many ways to address a nick:
+ // A jid (ANY jid…) with a resource
+ if (!to_jid.resource.empty())
+ target_nick = to_jid.resource;
+ else if (!to_jid.local.empty()) // A jid with a iid with a local part
+ target_nick = Iid(to_jid.local, {}).get_local();
+ else
+ target_nick = to; // Not a jid, just the nick
+ irc->send_invitation(iid.get_local(), target_nick);
+}
+
void Bridge::send_irc_version_request(const std::string& irc_hostname, const std::string& target,
const std::string& iq_id, const std::string& to_jid,
const std::string& from_jid)
{
- Iid iid(target + "!" + irc_hostname);
+ Iid iid(target, irc_hostname, Iid::Type::User);
this->send_private_message(iid, "\01VERSION\01");
// TODO, add a timer to remove that waiting iq if the server does not
// respond with a matching command before n seconds
- irc_responder_callback_t cb = [this, target, iq_id, to_jid, irc_hostname, from_jid](const std::string& hostname, const IrcMessage& message) -> bool
+ irc_responder_callback_t cb = [this, target, iq_id, to_jid, irc_hostname, from_jid]
+ (const std::string& hostname, const IrcMessage& message) -> bool
{
if (irc_hostname != hostname)
return false;
IrcUser user(message.prefix);
- if (message.command == "NOTICE" && user.nick == target &&
+ if (message.command == "NOTICE" && utils::tolower(user.nick) == utils::tolower(target) &&
message.arguments.size() >= 2 && message.arguments[1].substr(0, 9) == "\01VERSION ")
{
// remove the "\01VERSION " and the "\01" parts from the string
@@ -578,10 +753,17 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st
const auto encoding = in_encoding_for(*this, iid);
if (muc)
{
+#ifdef USE_DATABASE
+ const auto xmpp_body = this->make_xmpp_body(body, encoding);
+ if (!nick.empty() && this->record_history)
+ Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(),
+ std::get<0>(xmpp_body), nick);
+#endif
for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
{
this->xmpp.send_muc_message(std::to_string(iid), nick,
this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource);
+
}
}
else
@@ -590,16 +772,16 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st
const auto it = this->preferred_user_from.find(iid.get_local());
if (it != this->preferred_user_from.end())
{
- const auto chan_name = Iid(Jid(it->second).local).get_local();
+ const auto chan_name = Iid(Jid(it->second).local, {}).get_local();
for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, iid.get_server()}])
this->xmpp.send_message(it->second, this->make_xmpp_body(body, encoding),
- this->user_jid + "/" + resource, "chat", true);
+ this->user_jid + "/" + resource, "chat", true, true);
}
else
{
for (const auto& resource: this->resources_in_server[iid.get_server()])
this->xmpp.send_message(std::to_string(iid), this->make_xmpp_body(body, encoding),
- this->user_jid + "/" + resource, "chat", false);
+ this->user_jid + "/" + resource, "chat", false, true);
}
}
}
@@ -611,15 +793,16 @@ void Bridge::send_presence_error(const Iid& iid, const std::string& nick,
this->xmpp.send_presence_error(std::to_string(iid), nick, this->user_jid, type, condition, error_code, text);
}
-void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self, const std::string& resource)
+void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self,
+ const std::string& resource)
{
if (!resource.empty())
- this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid + "/" + resource,
- self);
+ 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);
+ this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message),
+ this->user_jid + "/" + res, self);
IrcClient* irc = this->find_irc_client(iid.get_server());
if (irc && irc->number_of_joined_channels() == 0)
irc->send_quit_command("");
@@ -653,18 +836,29 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho
else
body = msg;
- const auto encoding = in_encoding_for(*this, {from});
+ const auto encoding = in_encoding_for(*this, {from, this});
for (const auto& resource: this->resources_in_server[from])
{
- this->xmpp.send_message(from, this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource, "chat");
+ this->xmpp.send_message(from, this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource, "chat", false, false);
}
}
void Bridge::send_user_join(const std::string& hostname, const std::string& chan_name,
const IrcUser* user, const char user_mode, const bool self)
{
- for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}])
- this->send_user_join(hostname, chan_name, user, user_mode, self, resource);
+ const auto resources = this->resources_in_chan[ChannelKey{chan_name, hostname}];
+ if (self && resources.empty())
+ { // This was a forced join: no client ever asked to join this room,
+ // but the server tells us we are in that room anyway. XMPP can’t
+ // do that, so we invite all the resources to join that channel.
+ const Iid iid(chan_name, hostname, Iid::Type::Channel);
+ this->send_xmpp_invitation(iid, "");
+ }
+ else
+ {
+ for (const auto& resource: resources)
+ this->send_user_join(hostname, chan_name, user, user_mode, self, resource);
+ }
}
void Bridge::send_user_join(const std::string& hostname, const std::string& chan_name,
@@ -682,7 +876,8 @@ void Bridge::send_user_join(const std::string& hostname, const std::string& chan
affiliation, role, this->user_jid + "/" + resource, self);
}
-void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic, const std::string& who)
+void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic,
+ const std::string& who)
{
for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}])
{
@@ -696,18 +891,39 @@ void Bridge::send_topic(const std::string& hostname, const std::string& chan_nam
{
std::string encoded_chan_name(chan_name);
xep0106::encode(encoded_chan_name);
- const auto encoding = in_encoding_for(*this, {encoded_chan_name + '%' + hostname});
+ const auto encoding = in_encoding_for(*this, {encoded_chan_name, hostname, Iid::Type::Channel});
this->xmpp.send_topic(encoded_chan_name + utils::empty_if_fixed_server(
"%" + hostname), this->make_xmpp_body(topic, encoding), this->user_jid + "/" + resource, who);
}
+void Bridge::send_room_history(const std::string& hostname, const std::string& chan_name)
+{
+ for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}])
+ this->send_room_history(hostname, chan_name, resource);
+}
+
+void Bridge::send_room_history(const std::string& hostname, const std::string& chan_name, const std::string& resource)
+{
+#ifdef USE_DATABASE
+ const auto coptions = Database::get_irc_channel_options_with_server_and_global_default(this->user_jid, hostname, chan_name);
+ const auto lines = Database::get_muc_logs(this->user_jid, chan_name, hostname, coptions.maxHistoryLength.value());
+ for (const auto& line: lines)
+ {
+ const auto seconds = line.date.value().timeStamp();
+ this->xmpp.send_history_message(chan_name + utils::empty_if_fixed_server("%" + hostname), line.nick.value(),
+ line.body.value(),
+ this->user_jid + "/" + resource, seconds);
+ }
+#endif
+}
+
std::string Bridge::get_own_nick(const Iid& iid)
{
IrcClient* irc = this->find_irc_client(iid.get_server());
if (irc)
return irc->get_own_nick();
- return "";
+ return {};
}
size_t Bridge::active_clients() const
@@ -715,16 +931,18 @@ size_t Bridge::active_clients() const
return this->irc_clients.size();
}
-void Bridge::kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author)
+void Bridge::kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author,
+ const bool self)
{
for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
- this->xmpp.kick_user(std::to_string(iid), target, reason, author, this->user_jid + "/" + resource);
+ this->xmpp.kick_user(std::to_string(iid), target, reason, author, this->user_jid + "/" + resource, self);
}
void Bridge::send_nickname_conflict_error(const Iid& iid, const std::string& nickname)
{
- for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
- this->xmpp.send_presence_error(std::to_string(iid), nickname, this->user_jid + "/" + resource, "cancel", "conflict", "409", "");
+ for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
+ this->xmpp.send_presence_error(std::to_string(iid), nickname, this->user_jid + "/" + resource,
+ "cancel", "conflict", "409", "");
}
void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& target, const char mode)
@@ -734,14 +952,16 @@ void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& tar
std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(mode);
for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
- this->xmpp.send_affiliation_role_change(std::to_string(iid), target, affiliation, role, this->user_jid + "/" + resource);
+ this->xmpp.send_affiliation_role_change(std::to_string(iid), target, affiliation, role,
+ this->user_jid + "/" + resource);
}
void Bridge::send_iq_version_request(const std::string& nick, const std::string& hostname)
{
const auto resources = this->resources_in_server[hostname];
if (resources.begin() != resources.end())
- this->xmpp.send_iq_version_request(utils::tolower(nick) + "!" + utils::empty_if_fixed_server(hostname), this->user_jid + "/" + *resources.begin());
+ this->xmpp.send_iq_version_request(utils::tolower(nick) + "%" + utils::empty_if_fixed_server(hostname),
+ this->user_jid + "/" + *resources.begin());
}
void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& hostname,
@@ -753,7 +973,14 @@ void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string&
// Forward to the first resource (arbitrary, based on the “order” of the std::set) only
const auto resources = this->resources_in_server[hostname];
if (resources.begin() != resources.end())
- this->xmpp.send_ping_request(utils::tolower(nick) + "!" + utils::empty_if_fixed_server(hostname), this->user_jid + "/" + *resources.begin(), utils::revstr(id));
+ this->xmpp.send_ping_request(utils::tolower(nick) + "%" + utils::empty_if_fixed_server(hostname),
+ this->user_jid + "/" + *resources.begin(), utils::revstr(id));
+}
+
+void Bridge::send_xmpp_invitation(const Iid& iid, const std::string& author)
+{
+ for (const auto& resource: this->resources_in_server[iid.get_server()])
+ this->xmpp.send_invitation(std::to_string(iid), this->user_jid + "/" + resource, author);
}
void Bridge::set_preferred_from_jid(const std::string& nick, const std::string& full_jid)
@@ -776,7 +1003,7 @@ void Bridge::remove_all_preferred_from_jid_of_room(const std::string& channel_na
{
for (auto it = this->preferred_user_from.begin(); it != this->preferred_user_from.end();)
{
- Iid iid(Jid(it->second).local);
+ Iid iid(Jid(it->second).local, {});
if (iid.get_local() == channel_name)
it = this->preferred_user_from.erase(it);
else
@@ -806,6 +1033,14 @@ std::unordered_map<std::string, std::shared_ptr<IrcClient>>& Bridge::get_irc_cli
return this->irc_clients;
}
+std::set<char> Bridge::get_chantypes(const std::string& hostname) const
+{
+ IrcClient* irc = this->find_irc_client(hostname);
+ if (!irc)
+ return {'#', '&'};
+ return irc->get_chantypes();
+}
+
void Bridge::add_resource_to_chan(const Bridge::ChannelKey& channel, const std::string& resource)
{
auto it = this->resources_in_chan.find(channel);
@@ -894,7 +1129,6 @@ void Bridge::generate_channel_join_for_resource(const Iid& iid, const std::strin
{
if (user->nick != self->nick)
{
- log_debug(user->nick);
this->send_user_join(iid.get_server(), iid.get_encoded_local(),
user.get(), user->get_most_significant_mode(irc->get_sorted_user_modes()),
false, resource);
@@ -903,5 +1137,13 @@ void Bridge::generate_channel_join_for_resource(const Iid& iid, const std::strin
this->send_user_join(iid.get_server(), iid.get_encoded_local(),
self, self->get_most_significant_mode(irc->get_sorted_user_modes()),
true, resource);
+ this->send_room_history(iid.get_server(), iid.get_local(), resource);
this->send_topic(iid.get_server(), iid.get_encoded_local(), channel->topic, channel->topic_author, resource);
}
+
+#ifdef USE_DATABASE
+void Bridge::set_record_history(const bool val)
+{
+ this->record_history = val;
+}
+#endif
diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp
index 69b7bd5..18ebfeb 100644
--- a/src/bridge/bridge.hpp
+++ b/src/bridge/bridge.hpp
@@ -1,5 +1,7 @@
#pragma once
+#include <bridge/result_set_management.hpp>
+#include <bridge/list_element.hpp>
#include <irc/irc_message.hpp>
#include <irc/irc_client.hpp>
@@ -13,8 +15,11 @@
#include <string>
#include <memory>
+#include <biboumi.h>
+
class BiboumiComponent;
class Poller;
+struct ResultSetInfo;
/**
* A callback called for each IrcMessage we receive. If the message triggers
@@ -45,6 +50,10 @@ public:
*/
void shutdown(const std::string& exit_message);
/**
+ * PART the given resource from all the channels
+ */
+ void remove_resource(const std::string& resource, const std::string& part_message);
+ /**
* Remove all inactive IrcClients
*/
void clean();
@@ -70,7 +79,7 @@ public:
void send_channel_message(const Iid& iid, const std::string& body);
void send_private_message(const Iid& iid, const std::string& body, const std::string& type="PRIVMSG");
void send_raw_message(const std::string& hostname, const std::string& body);
- void leave_irc_channel(Iid&& iid, std::string&& status_message, const std::string& resource);
+ void leave_irc_channel(Iid&& iid, const std::string& status_message, const std::string& resource);
void send_irc_nick_change(const Iid& iid, const std::string& new_nick);
void send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason,
const std::string& iq_id, const std::string& to_jid);
@@ -81,8 +90,19 @@ public:
void send_irc_version_request(const std::string& irc_hostname, const std::string& target,
const std::string& iq_id, const std::string& to_jid,
const std::string& from_jid);
- void send_irc_channel_list_request(const Iid& iid, const std::string& iq_id,
- const std::string& to_jid);
+ void send_irc_channel_list_request(const Iid& iid, const std::string& iq_id, const std::string& to_jid,
+ ResultSetInfo rs_info);
+ /**
+ * Check if the channel list contains what is needed to answer the RSM request,
+ * if it does, send the iq result. If the list is complete but does not contain
+ * everything, send the result anyway (because there are no more available
+ * channels that could complete the list).
+ *
+ * Returns true if we sent the answer.
+ */
+ bool send_matching_channel_list(const ChannelList& channel_list,
+ const ResultSetInfo& rs_info, const std::string& id, const std::string& to_jid,
+ const std::string& from);
void forward_affiliation_role_change(const Iid& iid, const std::string& nick,
const std::string& affiliation, const std::string& role);
/**
@@ -105,6 +125,8 @@ public:
void on_gateway_ping(const std::string& irc_hostname, const std::string& iq_id, const std::string& to_jid,
const std::string& from_jid);
+ void send_irc_invitation(const Iid& iid, const std::string& to);
+
/***
**
** From IRC to XMPP.
@@ -132,6 +154,11 @@ public:
void send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic, const std::string& who);
void send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic, const std::string& who, const std::string& resource);
/**
+ * Send the MUC history to the user
+ */
+ void send_room_history(const std::string& hostname, const std::string& chan_name);
+ void send_room_history(const std::string& hostname, const std::string& chan_name, const std::string& resource);
+ /**
* Send a MUC message from some participant
*/
void send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc);
@@ -154,7 +181,8 @@ public:
const std::string& new_nick,
const char user_mode,
const bool self);
- void kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author);
+ void kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author,
+ const bool self);
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
@@ -169,6 +197,8 @@ public:
*/
void send_xmpp_ping_request(const std::string& nick, const std::string& hostname,
const std::string& id);
+ void send_xmpp_invitation(const Iid& iid, const std::string& author);
+
/**
* Misc
*/
@@ -201,6 +231,10 @@ public:
*/
void trigger_on_irc_message(const std::string& irc_hostname, const IrcMessage& message);
std::unordered_map<std::string, std::shared_ptr<IrcClient>>& get_irc_clients();
+ std::set<char> get_chantypes(const std::string& hostname) const;
+#ifdef USE_DATABASE
+ void set_record_history(const bool val);
+#endif
private:
/**
@@ -214,10 +248,12 @@ private:
* a IRCServerNotConnected error in that case.
*/
IrcClient* get_irc_client(const std::string& hostname);
+public:
/**
* Idem, but returns nullptr if the server does not exist.
*/
- IrcClient* find_irc_client(const std::string& hostname);
+ IrcClient* find_irc_client(const std::string& hostname) const;
+private:
/**
* The bare JID of the user associated with this bridge. Messages from/to this
* JID are only managed by this bridge.
@@ -252,7 +288,6 @@ private:
* response iq.
*/
std::vector<irc_responder_callback_t> waiting_irc;
-
/**
* Resources to IRC channel/server mapping:
*/
@@ -260,7 +295,9 @@ private:
using ChannelName = std::string;
using IrcHostname = std::string;
using ChannelKey = std::tuple<ChannelName, IrcHostname>;
+public:
std::map<ChannelKey, std::set<Resource>> resources_in_chan;
+private:
std::map<IrcHostname, std::set<Resource>> resources_in_server;
/**
* Manage which resource is in which channel
@@ -281,6 +318,16 @@ private:
* TODO: send message history
*/
void generate_channel_join_for_resource(const Iid& iid, const std::string& resource);
+ /**
+ * A cache of the channels list (as returned by the server on a LIST
+ * request), to be re-used on a subsequent XMPP list request that
+ * uses result-set-management.
+ */
+ std::map<IrcHostname, ChannelList> channel_list_cache;
+
+#ifdef USE_DATABASE
+ bool record_history { true };
+#endif
};
struct IRCNotConnected: public std::exception
diff --git a/src/bridge/list_element.hpp b/src/bridge/list_element.hpp
index 1eff2ee..554c83d 100644
--- a/src/bridge/list_element.hpp
+++ b/src/bridge/list_element.hpp
@@ -1,6 +1,6 @@
#pragma once
-
+#include <vector>
#include <string>
struct ListElement
@@ -17,3 +17,8 @@ struct ListElement
};
+struct ChannelList
+{
+ bool complete{true};
+ std::vector<ListElement> channels{};
+};
diff --git a/src/bridge/result_set_management.hpp b/src/bridge/result_set_management.hpp
new file mode 100644
index 0000000..6ff82ba
--- /dev/null
+++ b/src/bridge/result_set_management.hpp
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <string>
+
+struct ResultSetInfo
+{
+ int max{-1};
+ std::string before{};
+ std::string after{};
+};
diff --git a/src/database/database.cpp b/src/database/database.cpp
index 61e1b47..f7d309b 100644
--- a/src/database/database.cpp
+++ b/src/database/database.cpp
@@ -3,7 +3,10 @@
#include <database/database.hpp>
#include <logger/logger.hpp>
-#include <string>
+#include <irc/iid.hpp>
+#include <uuid/uuid.h>
+#include <utils/get_first_non_empty.hpp>
+#include <utils/time.hpp>
using namespace std::string_literals;
@@ -29,6 +32,19 @@ void Database::set_verbose(const bool val)
Database::db->verbose = val;
}
+db::GlobalOptions Database::get_global_options(const std::string& owner)
+{
+ try {
+ auto options = litesql::select<db::GlobalOptions>(*Database::db,
+ db::GlobalOptions::Owner == owner).one();
+ return options;
+ } catch (const litesql::NotFound& e) {
+ db::GlobalOptions options(*Database::db);
+ options.owner = owner;
+ return options;
+ }
+}
+
db::IrcServerOptions Database::get_irc_server_options(const std::string& owner,
const std::string& server)
{
@@ -71,17 +87,96 @@ db::IrcChannelOptions Database::get_irc_channel_options_with_server_default(cons
{
auto coptions = Database::get_irc_channel_options(owner, server, channel);
auto soptions = Database::get_irc_server_options(owner, server);
- if (coptions.encodingIn.value().empty())
- coptions.encodingIn = soptions.encodingIn;
- if (coptions.encodingOut.value().empty())
- coptions.encodingOut = soptions.encodingOut;
+
+ coptions.encodingIn = get_first_non_empty(coptions.encodingIn.value(),
+ soptions.encodingIn.value());
+ coptions.encodingOut = get_first_non_empty(coptions.encodingOut.value(),
+ soptions.encodingOut.value());
+
+ coptions.maxHistoryLength = get_first_non_empty(coptions.maxHistoryLength.value(),
+ soptions.maxHistoryLength.value());
return coptions;
}
+db::IrcChannelOptions Database::get_irc_channel_options_with_server_and_global_default(const std::string& owner,
+ const std::string& server,
+ const std::string& channel)
+{
+ auto coptions = Database::get_irc_channel_options(owner, server, channel);
+ auto soptions = Database::get_irc_server_options(owner, server);
+ auto goptions = Database::get_global_options(owner);
+
+ coptions.encodingIn = get_first_non_empty(coptions.encodingIn.value(),
+ soptions.encodingIn.value());
+ coptions.encodingOut = get_first_non_empty(coptions.encodingOut.value(),
+ soptions.encodingOut.value());
+
+ coptions.maxHistoryLength = get_first_non_empty(coptions.maxHistoryLength.value(),
+ soptions.maxHistoryLength.value(),
+ goptions.maxHistoryLength.value());
+
+ return coptions;
+}
+
+void Database::store_muc_message(const std::string& owner, const Iid& iid,
+ Database::time_point date,
+ const std::string& body,
+ const std::string& nick)
+{
+ db::MucLogLine line(*Database::db);
+
+ line.uuid = Database::gen_uuid();
+ line.owner = owner;
+ line.ircChanName = iid.get_local();
+ line.ircServerName = iid.get_server();
+ line.date = date.time_since_epoch().count() / 1'000'000'000;
+ line.body = body;
+ line.nick = nick;
+
+ line.update();
+}
+
+std::vector<db::MucLogLine> Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server,
+ int limit, const std::string& start, const std::string& end)
+{
+ auto request = litesql::select<db::MucLogLine>(*Database::db,
+ db::MucLogLine::Owner == owner &&
+ db::MucLogLine::IrcChanName == chan_name &&
+ db::MucLogLine::IrcServerName == server);
+ request.orderBy(db::MucLogLine::Id, false);
+
+ if (limit >= 0)
+ request.limit(limit);
+ if (!start.empty())
+ {
+ const auto start_time = utils::parse_datetime(start);
+ if (start_time != -1)
+ request.where(db::MucLogLine::Date >= start_time);
+ }
+ if (!end.empty())
+ {
+ const auto end_time = utils::parse_datetime(end);
+ if (end_time != -1)
+ request.where(db::MucLogLine::Date <= end_time);
+ }
+ const auto& res = request.all();
+ return {res.crbegin(), res.crend()};
+}
+
void Database::close()
{
Database::db.reset(nullptr);
}
+std::string Database::gen_uuid()
+{
+ char uuid_str[37];
+ uuid_t uuid;
+ uuid_generate(uuid);
+ uuid_unparse(uuid, uuid_str);
+ return uuid_str;
+}
+
+
#endif
diff --git a/src/database/database.hpp b/src/database/database.hpp
index 7173bcd..6823574 100644
--- a/src/database/database.hpp
+++ b/src/database/database.hpp
@@ -9,10 +9,14 @@
#include <memory>
#include <litesql.hpp>
+#include <chrono>
+
+class Iid;
class Database
{
public:
+ using time_point = std::chrono::system_clock::time_point;
Database() = default;
~Database() = default;
@@ -32,6 +36,7 @@ public:
* Return the object from the db. Create it beforehand (with all default
* values) if it is not already present.
*/
+ static db::GlobalOptions get_global_options(const std::string& owner);
static db::IrcServerOptions get_irc_server_options(const std::string& owner,
const std::string& server);
static db::IrcChannelOptions get_irc_channel_options(const std::string& owner,
@@ -40,12 +45,20 @@ public:
static db::IrcChannelOptions get_irc_channel_options_with_server_default(const std::string& owner,
const std::string& server,
const std::string& channel);
+ static db::IrcChannelOptions get_irc_channel_options_with_server_and_global_default(const std::string& owner,
+ const std::string& server,
+ const std::string& channel);
+ static std::vector<db::MucLogLine> get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server,
+ int limit=-1, const std::string& before="", const std::string& after="");
+ static void store_muc_message(const std::string& owner, const Iid& iid,
+ time_point date, const std::string& body, const std::string& nick);
static void close();
static void open(const std::string& filename, const std::string& db_type="sqlite3");
private:
+ static std::string gen_uuid();
static std::unique_ptr<db::BibouDB> db;
};
#endif /* USE_DATABASE */
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.
diff --git a/src/main.cpp b/src/main.cpp
index 53f3193..019dff0 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -12,11 +12,12 @@
#include <atomic>
#include <signal.h>
+#include <litesql.hpp>
// A flag set by the SIGINT signal handler.
-static volatile std::atomic<bool> stop(false);
+static std::atomic<bool> stop(false);
// Flag set by the SIGUSR1/2 signal handler.
-static volatile std::atomic<bool> reload(false);
+static std::atomic<bool> reload(false);
// A flag indicating that we are wanting to exit the process. i.e: if this
// flag is set and all connections are closed, we can exit properly.
static bool exiting = false;
@@ -84,7 +85,7 @@ int main(int ac, char** av)
try {
open_database();
- } catch (...) {
+ } catch (const litesql::DatabaseError&) {
return 1;
}
diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp
index eec930d..003b901 100644
--- a/src/xmpp/biboumi_adhoc_commands.cpp
+++ b/src/xmpp/biboumi_adhoc_commands.cpp
@@ -1,9 +1,13 @@
#include <xmpp/biboumi_adhoc_commands.hpp>
#include <xmpp/biboumi_component.hpp>
+#include <utils/scopeguard.hpp>
+#include <bridge/bridge.hpp>
#include <config/config.hpp>
#include <utils/string.hpp>
#include <utils/split.hpp>
#include <xmpp/jid.hpp>
+#include <algorithm>
+#include <iomanip>
#include <biboumi.h>
@@ -11,9 +15,9 @@
#include <database/database.hpp>
#endif
-#include <louloulibs.h>
-
-#include <algorithm>
+#ifndef HAS_PUT_TIME
+#include <ctime>
+#endif
using namespace std::string_literals;
@@ -114,6 +118,96 @@ void DisconnectUserStep2(XmppComponent& xmpp_component, AdhocSession& session, X
}
#ifdef USE_DATABASE
+
+void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node)
+{
+ const Jid owner(session.get_owner_jid());
+ const Jid target(session.get_target_jid());
+
+ auto options = Database::get_global_options(owner.bare());
+
+ XmlNode x("jabber:x:data:x");
+ x["type"] = "form";
+ XmlNode title("title");
+ title.set_inner("Configure some global default settings.");
+ x.add_child(std::move(title));
+ XmlNode instructions("instructions");
+ instructions.set_inner("Edit the form, to configure your global settings for the component.");
+ x.add_child(std::move(instructions));
+
+ XmlNode required("required");
+
+ XmlNode max_histo_length("field");
+ max_histo_length["var"] = "max_history_length";
+ max_histo_length["type"] = "text-single";
+ max_histo_length["label"] = "Max history length";
+ max_histo_length["desc"] = "The maximum number of lines in the history that the server sends when joining a channel";
+
+ XmlNode value("value");
+ value.set_inner(std::to_string(options.maxHistoryLength.value()));
+ max_histo_length.add_child(std::move(value));
+ x.add_child(std::move(max_histo_length));
+
+ XmlNode record_history("field");
+ record_history["var"] = "record_history";
+ record_history["type"] = "boolean";
+ record_history["label"] = "Record history";
+ record_history["desc"] = "Whether to save the messages into the database, or not";
+
+ value.set_name("value");
+ if (options.recordHistory.value())
+ value.set_inner("true");
+ else
+ value.set_inner("false");
+ record_history.add_child(std::move(value));
+ x.add_child(std::move(record_history));
+
+ command_node.add_child(std::move(x));
+}
+
+void ConfigureGlobalStep2(XmppComponent& xmpp_component, AdhocSession& session, XmlNode& command_node)
+{
+ BiboumiComponent& biboumi_component = static_cast<BiboumiComponent&>(xmpp_component);
+
+ const XmlNode* x = command_node.get_child("x", "jabber:x:data");
+ if (x)
+ {
+ const Jid owner(session.get_owner_jid());
+ auto options = Database::get_global_options(owner.bare());
+ for (const XmlNode* field: x->get_children("field", "jabber:x:data"))
+ {
+ const XmlNode* value = field->get_child("value", "jabber:x:data");
+
+ if (field->get_tag("var") == "max_history_length" &&
+ value && !value->get_inner().empty())
+ options.maxHistoryLength = value->get_inner();
+ else if (field->get_tag("var") == "record_history" &&
+ value && !value->get_inner().empty())
+ {
+ options.recordHistory = to_bool(value->get_inner());
+ Bridge* bridge = biboumi_component.find_user_bridge(owner.bare());
+ if (bridge)
+ bridge->set_record_history(options.recordHistory.value());
+ }
+ }
+
+ options.update();
+
+ command_node.delete_all_children();
+ XmlNode note("note");
+ note["type"] = "info";
+ note.set_inner("Configuration successfully applied.");
+ command_node.add_child(std::move(note));
+ return;
+ }
+ XmlNode error(ADHOC_NS":error");
+ error["type"] = "modify";
+ XmlNode condition(STANZA_NS":bad-request");
+ error.add_child(std::move(condition));
+ command_node.add_child(std::move(error));
+ session.terminate();
+}
+
void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node)
{
const Jid owner(session.get_owner_jid());
@@ -315,7 +409,7 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com
}
else if (field->get_tag("var") == "verify_cert" && value
- && !value->get_inner().empty())
+ && !value->get_inner().empty())
{
auto val = to_bool(value->get_inner());
options.verifyCert = val;
@@ -381,7 +475,7 @@ void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& co
{
const Jid owner(session.get_owner_jid());
const Jid target(session.get_target_jid());
- const Iid iid(target.local);
+ const Iid iid(target.local, {});
auto options = Database::get_irc_channel_options_with_server_default(owner.local + "@" + owner.domain,
iid.get_server(), iid.get_local());
@@ -434,7 +528,7 @@ void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& co
{
const Jid owner(session.get_owner_jid());
const Jid target(session.get_target_jid());
- const Iid iid(target.local);
+ const Iid iid(target.local, {});
auto options = Database::get_irc_channel_options(owner.local + "@" + owner.domain,
iid.get_server(), iid.get_local());
for (const XmlNode* field: x->get_children("field", "jabber:x:data"))
@@ -442,7 +536,7 @@ void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& co
const XmlNode* value = field->get_child("value", "jabber:x:data");
if (field->get_tag("var") == "encoding_out" &&
- value && !value->get_inner().empty())
+ value && !value->get_inner().empty())
options.encodingOut = value->get_inner();
else if (field->get_tag("var") == "encoding_in" &&
@@ -633,3 +727,74 @@ void DisconnectUserFromServerStep3(XmppComponent& xmpp_component, AdhocSession&
note.set_inner(msg);
command_node.add_child(std::move(note));
}
+
+void GetIrcConnectionInfoStep1(XmppComponent& component, AdhocSession& session, XmlNode& command_node)
+{
+ BiboumiComponent& biboumi_component = static_cast<BiboumiComponent&>(component);
+
+ const Jid owner(session.get_owner_jid());
+ const Jid target(session.get_target_jid());
+
+ std::string message{};
+
+ // As the function is exited, set the message in the response.
+ utils::ScopeGuard sg([&message, &command_node]()
+ {
+ command_node.delete_all_children();
+ XmlNode note("note");
+ note["type"] = "info";
+ note.set_inner(message);
+ command_node.add_child(std::move(note));
+ });
+
+ Bridge* bridge = biboumi_component.get_user_bridge(owner.bare());
+ if (!bridge)
+ {
+ message = "You are not connected to anything.";
+ return;
+ }
+
+ std::string hostname;
+ if ((hostname = Config::get("fixed_irc_server", "")).empty())
+ hostname = target.local;
+
+ IrcClient* irc = bridge->find_irc_client(hostname);
+ if (!irc || !irc->is_connected())
+ {
+ message = "You are not connected to the IRC server "s + hostname;
+ return;
+ }
+
+ std::ostringstream ss;
+ ss << "Connected to IRC server " << irc->get_hostname() << " on port " << irc->get_port();
+ if (irc->is_using_tls())
+ ss << " (using TLS)";
+ const std::time_t now_c = std::chrono::system_clock::to_time_t(irc->connection_date);
+#ifdef HAS_PUT_TIME
+ ss << " since " << std::put_time(std::localtime(&now_c), "%F %T");
+#else
+ constexpr std::size_t timestamp_size{10 + 1 + 8 + 1};
+ char buf[timestamp_size] = {};
+ const auto res = std::strftime(buf, timestamp_size, "%F %T", std::localtime(&now_c));
+ if (res > 0)
+ ss << " since " << buf;
+#endif
+ ss << " (" << std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - irc->connection_date).count() << " seconds ago).";
+
+ for (const auto& it: bridge->resources_in_chan)
+ {
+ const auto& channel_key = it.first;
+ const auto& irc_hostname = std::get<1>(channel_key);
+ const auto& resources = it.second;
+
+ if (irc_hostname == irc->get_hostname() && !resources.empty())
+ {
+ const auto& channel_name = std::get<0>(channel_key);
+ ss << "\n" << channel_name << " from " << resources.size() << " resource" << (resources.size() > 1 ? "s": "") << ": ";
+ for (const auto& resource: resources)
+ ss << resource << " ";
+ }
+ }
+
+ message = ss.str();
+}
diff --git a/src/xmpp/biboumi_adhoc_commands.hpp b/src/xmpp/biboumi_adhoc_commands.hpp
index 2763a9f..b5fce61 100644
--- a/src/xmpp/biboumi_adhoc_commands.hpp
+++ b/src/xmpp/biboumi_adhoc_commands.hpp
@@ -10,6 +10,9 @@ class XmppComponent;
void DisconnectUserStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node);
void DisconnectUserStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node);
+void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node);
+void ConfigureGlobalStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node);
+
void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node);
void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node);
@@ -20,4 +23,4 @@ void DisconnectUserFromServerStep1(XmppComponent&, AdhocSession& session, XmlNod
void DisconnectUserFromServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node);
void DisconnectUserFromServerStep3(XmppComponent&, AdhocSession& session, XmlNode& command_node);
-
+void GetIrcConnectionInfoStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node);
diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp
index a6aac21..d6782e2 100644
--- a/src/xmpp/biboumi_component.cpp
+++ b/src/xmpp/biboumi_component.cpp
@@ -8,23 +8,27 @@
#include <xmpp/biboumi_adhoc_commands.hpp>
#include <bridge/list_element.hpp>
#include <config/config.hpp>
-#include <xmpp/jid.hpp>
#include <utils/sha1.hpp>
+#include <utils/time.hpp>
+#include <xmpp/jid.hpp>
#include <stdexcept>
#include <iostream>
-#include <stdio.h>
+#include <cstdlib>
#include <louloulibs.h>
#include <biboumi.h>
-#include <uuid.h>
+#include <uuid/uuid.h>
#ifdef SYSTEMD_FOUND
# include <systemd/sd-daemon.h>
#endif
+#include <database/database.hpp>
+#include <bridge/result_set_management.hpp>
+
using namespace std::string_literals;
static std::set<std::string> kickable_errors{
@@ -53,33 +57,30 @@ BiboumiComponent::BiboumiComponent(std::shared_ptr<Poller> poller, const std::st
this->stanza_handlers.emplace("iq",
std::bind(&BiboumiComponent::handle_iq, this,std::placeholders::_1));
- this->adhoc_commands_handler.get_commands() = {
- {"ping", AdhocCommand({&PingStep1}, "Do a ping", false)},
- {"hello", AdhocCommand({&HelloStep1, &HelloStep2}, "Receive a custom greeting", false)},
- {"disconnect-user", AdhocCommand({&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect selected users from the gateway", true)},
- {"disconnect-from-irc-servers", AdhocCommand({&DisconnectUserFromServerStep1, &DisconnectUserFromServerStep2, &DisconnectUserFromServerStep3}, "Disconnect from the selected IRC servers", false)},
- {"reload", AdhocCommand({&Reload}, "Reload biboumi’s configuration", true)}
- };
+ this->adhoc_commands_handler.add_command("ping", {{&PingStep1}, "Do a ping", false});
+ this->adhoc_commands_handler.add_command("hello", {{&HelloStep1, &HelloStep2}, "Receive a custom greeting", false});
+ this->adhoc_commands_handler.add_command("disconnect-user", {{&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect selected users from the gateway", true});
+ this->adhoc_commands_handler.add_command("disconnect-from-irc-server", {{&DisconnectUserFromServerStep1, &DisconnectUserFromServerStep2, &DisconnectUserFromServerStep3}, "Disconnect from the selected IRC servers", false});
+ this->adhoc_commands_handler.add_command("reload", {{&Reload}, "Reload biboumi’s configuration", true});
+
+ AdhocCommand get_irc_connection_info{{&GetIrcConnectionInfoStep1}, "Returns various information about your connection to this IRC server.", false};
+ if (!Config::get("fixed_irc_server", "").empty())
+ this->adhoc_commands_handler.add_command("get-irc-connection-info", get_irc_connection_info);
+ else
+ this->irc_server_adhoc_commands_handler.add_command("get-irc-connection-info", get_irc_connection_info);
#ifdef USE_DATABASE
AdhocCommand configure_server_command({&ConfigureIrcServerStep1, &ConfigureIrcServerStep2}, "Configure a few settings for that IRC server", false);
+ AdhocCommand configure_global_command({&ConfigureGlobalStep1, &ConfigureGlobalStep2}, "Configure a few settings", false);
+
if (!Config::get("fixed_irc_server", "").empty())
- {
- this->adhoc_commands_handler.get_commands().emplace(std::make_pair("configure",
- configure_server_command));
- }
-#endif
+ this->adhoc_commands_handler.add_command("configure", configure_server_command);
+ else
+ this->adhoc_commands_handler.add_command("configure", configure_global_command);
- this->irc_server_adhoc_commands_handler.get_commands() = {
-#ifdef USE_DATABASE
- {"configure", configure_server_command},
+ this->irc_server_adhoc_commands_handler.add_command("configure", configure_server_command);
+ this->irc_channel_adhoc_commands_handler.add_command("configure", {{&ConfigureIrcChannelStep1, &ConfigureIrcChannelStep2}, "Configure a few settings for that IRC channel", false});
#endif
- };
- this->irc_channel_adhoc_commands_handler.get_commands() = {
-#ifdef USE_DATABASE
- {"configure", AdhocCommand({&ConfigureIrcChannelStep1, &ConfigureIrcChannelStep2}, "Configure a few settings for that IRC channel", false)},
-#endif
- };
}
void BiboumiComponent::shutdown()
@@ -126,7 +127,7 @@ void BiboumiComponent::handle_presence(const Stanza& stanza)
Bridge* bridge = this->get_user_bridge(from_str);
Jid to(to_str);
Jid from(from_str);
- Iid iid(to.local);
+ Iid iid(to.local, bridge);
// An error stanza is sent whenever we exit this function without
// disabling this scopeguard. If error_type and error_name are not
@@ -142,7 +143,7 @@ void BiboumiComponent::handle_presence(const Stanza& stanza)
});
try {
- if (iid.is_channel && !iid.get_server().empty())
+ if (iid.type == Iid::Type::Channel && !iid.get_server().empty())
{ // presence toward a MUC that corresponds to an irc channel, or a
// dummy channel if iid.chan is empty
if (type.empty())
@@ -163,7 +164,7 @@ void BiboumiComponent::handle_presence(const Stanza& stanza)
}
else
{
- // An user wants to join an invalid IRC channel, return a presence error to him
+ // A user wants to join an invalid IRC channel, return a presence error to him/her
if (type.empty())
this->send_invalid_room_error(to.local, to.resource, from_str);
}
@@ -180,29 +181,30 @@ void BiboumiComponent::handle_presence(const Stanza& stanza)
void BiboumiComponent::handle_message(const Stanza& stanza)
{
- std::string from = stanza.get_tag("from");
+ std::string from_str = stanza.get_tag("from");
std::string id = stanza.get_tag("id");
std::string to_str = stanza.get_tag("to");
std::string type = stanza.get_tag("type");
- if (from.empty())
+ if (from_str.empty())
return;
if (type.empty())
type = "normal";
- Bridge* bridge = this->get_user_bridge(from);
+ Bridge* bridge = this->get_user_bridge(from_str);
+ Jid from(from_str);
Jid to(to_str);
- Iid iid(to.local);
+ Iid iid(to.local, bridge);
std::string error_type("cancel");
std::string error_name("internal-server-error");
utils::ScopeGuard stanza_error([&](){
- this->send_stanza_error("message", from, to_str, id,
+ this->send_stanza_error("message", from_str, to_str, id,
error_type, error_name, "");
});
const XmlNode* body = stanza.get_child("body", COMPONENT_NS);
try { // catch IRCNotConnected exceptions
- if (type == "groupchat" && iid.is_channel)
+ if (type == "groupchat" && iid.type == Iid::Type::Channel)
{
if (body && !body->get_inner().empty())
{
@@ -216,7 +218,7 @@ void BiboumiComponent::handle_message(const Stanza& stanza)
{
const XmlNode* error = stanza.get_child("error", COMPONENT_NS);
// Only a set of errors are considered “fatal”. If we encounter one of
- // them, we purge (we disconnect the user from all the IRC servers).
+ // them, we purge (we disconnect that resource from all the IRC servers)
// We consider this to be true, unless the error condition is
// specified and is not in the kickable_errors set
bool kickable_error = true;
@@ -227,38 +229,49 @@ void BiboumiComponent::handle_message(const Stanza& stanza)
kickable_error = false;
}
if (kickable_error)
- bridge->shutdown("Error from remote client");
+ bridge->remove_resource(from.resource, "Error from remote client");
}
else if (type == "chat")
{
if (body && !body->get_inner().empty())
{
// a message for nick!server
- if (iid.is_user && !iid.get_local().empty())
+ if (iid.type == Iid::Type::User && !iid.get_local().empty())
{
bridge->send_private_message(iid, body->get_inner());
bridge->remove_preferred_from_jid(iid.get_local());
}
- else if (!iid.is_user && !to.resource.empty())
+ else if (iid.type != Iid::Type::User && !to.resource.empty())
{ // a message for chan%server@biboumi/Nick or
// server@biboumi/Nick
// Convert that into a message to nick!server
- Iid user_iid(utils::tolower(to.resource) + "!" + iid.get_server());
+ Iid user_iid(utils::tolower(to.resource), iid.get_server(), Iid::Type::User);
bridge->send_private_message(user_iid, body->get_inner());
bridge->set_preferred_from_jid(user_iid.get_local(), to_str);
}
- else if (!iid.is_user && !iid.is_channel)
+ else if (iid.type == Iid::Type::Server)
{ // Message sent to the server JID
// Convert the message body into a raw IRC message
bridge->send_raw_message(iid.get_server(), body->get_inner());
}
}
}
- else if (iid.is_user)
- this->send_invalid_user_error(to.local, from);
+ else if (type == "normal" && iid.type == Iid::Type::Channel)
+ {
+ if (const XmlNode* x = stanza.get_child("x", MUC_USER_NS))
+ if (const XmlNode* invite = x->get_child("invite", MUC_USER_NS))
+ {
+ const auto invite_to = invite->get_tag("to");
+ if (!invite_to.empty())
+ {
+ bridge->send_irc_invitation(iid, invite_to);
+ }
+ }
+
+ }
} catch (const IRCNotConnected& ex)
{
- this->send_stanza_error("message", from, to_str, id,
+ this->send_stanza_error("message", from_str, to_str, id,
"cancel", "remote-server-not-found",
"Not connected to IRC server "s + ex.hostname,
true);
@@ -321,7 +334,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
std::string affiliation = child->get_tag("affiliation");
if (!nick.empty())
{
- Iid iid(to.local);
+ Iid iid(to.local, {});
if (role == "none")
{ // This is a kick
std::string reason;
@@ -345,15 +358,17 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
// Depending on the 'to' jid in the request, we use one adhoc
// command handler or an other
- Iid iid(to.local);
+ Iid iid(to.local, {});
AdhocCommandsHandler* adhoc_handler;
- if (!to.local.empty() && !iid.is_user && !iid.is_channel)
- adhoc_handler = &this->irc_server_adhoc_commands_handler;
- else if (!to.local.empty() && iid.is_channel)
- adhoc_handler = &this->irc_channel_adhoc_commands_handler;
- else
+ if (to.local.empty())
adhoc_handler = &this->adhoc_commands_handler;
-
+ else
+ {
+ if (iid.type == Iid::Type::Server)
+ adhoc_handler = &this->irc_server_adhoc_commands_handler;
+ else
+ adhoc_handler = &this->irc_channel_adhoc_commands_handler;
+ }
// Execute the command, if any, and get a result XmlNode that we
// insert in our response
XmlNode inner_node = adhoc_handler->handle_request(from, to_str, *query);
@@ -365,15 +380,23 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
this->send_stanza(response);
stanza_error.disable();
}
+#ifdef USE_DATABASE
+ else if ((query = stanza.get_child("query", MAM_NS)))
+ {
+ if (this->handle_mam_request(stanza))
+ stanza_error.disable();
+ }
+#endif
}
else if (type == "get")
{
const XmlNode* query;
if ((query = stanza.get_child("query", DISCO_INFO_NS)))
{ // Disco info
+ Iid iid(to.local, {'#', '&'});
+ const std::string node = query->get_tag("node");
if (to_str == this->served_hostname)
{
- const std::string node = query->get_tag("node");
if (node.empty())
{
// On the gateway itself
@@ -381,16 +404,32 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
stanza_error.disable();
}
}
+ else if (iid.type == Iid::Type::Server)
+ {
+ if (node.empty())
+ {
+ this->send_irc_server_disco_info(id, from, to_str);
+ stanza_error.disable();
+ }
+ }
+ else if (iid.type == Iid::Type::Channel)
+ {
+ if (node == MUC_TRAFFIC_NS)
+ {
+ this->send_irc_channel_muc_traffic_info(id, from, to_str);
+ stanza_error.disable();
+ }
+ }
}
else if ((query = stanza.get_child("query", VERSION_NS)))
{
- Iid iid(to.local);
- if (iid.is_user ||
- (iid.is_channel && !to.resource.empty()))
+ Iid iid(to.local, bridge);
+ if ((iid.type == Iid::Type::Channel && !to.resource.empty()) ||
+ (iid.type == Iid::Type::User))
{
// Get the IRC user version
std::string target;
- if (iid.is_user)
+ if (iid.type == Iid::Type::User)
target = iid.get_local();
else
target = to.resource;
@@ -406,7 +445,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
}
else if ((query = stanza.get_child("query", DISCO_ITEMS_NS)))
{
- Iid iid(to.local);
+ Iid iid(to.local, bridge);
const std::string node = query->get_tag("node");
if (node == ADHOC_NS)
{
@@ -419,7 +458,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
this->adhoc_commands_handler);
stanza_error.disable();
}
- else if (!iid.is_user && !iid.is_channel)
+ else if (iid.type == Iid::Type::Server)
{ // Get the server's adhoc commands
this->send_adhoc_commands_list(id, from, to_str,
(Config::get("admin", "") ==
@@ -427,7 +466,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
this->irc_server_adhoc_commands_handler);
stanza_error.disable();
}
- else if (!iid.is_user && iid.is_channel)
+ else if (iid.type == Iid::Type::Channel)
{ // Get the channel's adhoc commands
this->send_adhoc_commands_list(id, from, to_str,
(Config::get("admin", "") ==
@@ -436,21 +475,36 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
stanza_error.disable();
}
}
- else if (node.empty() && !iid.is_user && !iid.is_channel)
+ else if (node.empty() && iid.type == Iid::Type::Server)
{ // Disco on an IRC server: get the list of channels
- bridge->send_irc_channel_list_request(iid, id, from);
+ ResultSetInfo rs_info;
+ const XmlNode* set_node = query->get_child("set", RSM_NS);
+ if (set_node)
+ {
+ const XmlNode* after = set_node->get_child("after", RSM_NS);
+ if (after)
+ rs_info.after = after->get_inner();
+ const XmlNode* before = set_node->get_child("before", RSM_NS);
+ if (before)
+ rs_info.before = before->get_inner();
+ const XmlNode* max = set_node->get_child("max", RSM_NS);
+ if (max)
+ rs_info.max = std::atoi(max->get_inner().data());
+
+ }
+ bridge->send_irc_channel_list_request(iid, id, from, std::move(rs_info));
stanza_error.disable();
}
}
else if ((query = stanza.get_child("ping", PING_NS)))
{
- Iid iid(to.local);
- if (iid.is_user)
+ Iid iid(to.local, bridge);
+ if (iid.type == Iid::Type::User)
{ // Ping any user (no check on the nick done ourself)
bridge->send_irc_user_ping_request(iid.get_server(),
iid.get_local(), id, from, to_str);
}
- else if (iid.is_channel && !to.resource.empty())
+ else if (iid.type == Iid::Type::Channel && !to.resource.empty())
{ // Ping a room participant (we check if the nick is in the room)
bridge->send_irc_participant_ping_request(iid,
to.resource, id, from, to_str);
@@ -481,7 +535,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
version = version_node->get_inner();
if (os_node)
os = os_node->get_inner();
- const Iid iid(to.local);
+ const Iid iid(to.local, bridge);
bridge->send_xmpp_version_to_irc(iid, name, version, os);
}
else
@@ -508,6 +562,96 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
error_name = "feature-not-implemented";
}
+#ifdef USE_DATABASE
+bool BiboumiComponent::handle_mam_request(const Stanza& stanza)
+{
+ std::string id = stanza.get_tag("id");
+ Jid from(stanza.get_tag("from"));
+ Jid to(stanza.get_tag("to"));
+
+ const XmlNode* query = stanza.get_child("query", MAM_NS);
+ std::string query_id;
+ if (query)
+ query_id = query->get_tag("queryid");
+
+ Iid iid(to.local, {'#', '&'});
+ if (iid.type == Iid::Type::Channel && to.resource.empty())
+ {
+ std::string start;
+ std::string end;
+ const XmlNode* x = query->get_child("x", DATAFORM_NS);
+ if (x)
+ {
+ const XmlNode* value;
+ const auto fields = x->get_children("field", DATAFORM_NS);
+ for (const auto& field: fields)
+ {
+ if (field->get_tag("var") == "start")
+ {
+ value = field->get_child("value", DATAFORM_NS);
+ if (value)
+ start = value->get_inner();
+ }
+ else if (field->get_tag("var") == "end")
+ {
+ value = field->get_child("value", DATAFORM_NS);
+ if (value)
+ end = value->get_inner();
+ }
+ }
+ }
+ const auto lines = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(), -1, start, end);
+ for (const db::MucLogLine& line: lines)
+ {
+ if (!line.nick.value().empty())
+ this->send_archived_message(line, to.full(), from.full(), query_id);
+ }
+ this->send_iq_result_full_jid(id, from.full(), to.full());
+ return true;
+ }
+ return false;
+}
+
+void BiboumiComponent::send_archived_message(const db::MucLogLine& log_line, const std::string& from, const std::string& to,
+ const std::string& queryid)
+{
+ Stanza message("message");
+ message["from"] = from;
+ message["to"] = to;
+
+ XmlNode result("result");
+ result["xmlns"] = MAM_NS;
+ if (!queryid.empty())
+ result["queryid"] = queryid;
+ result["id"] = log_line.uuid.value();
+
+ XmlNode forwarded("forwarded");
+ forwarded["xmlns"] = FORWARD_NS;
+
+ XmlNode delay("delay");
+ delay["xmlns"] = DELAY_NS;
+ delay["stamp"] = utils::to_string(log_line.date.value().timeStamp());
+
+ forwarded.add_child(std::move(delay));
+
+ XmlNode submessage("message");
+ submessage["xmlns"] = CLIENT_NS;
+ submessage["from"] = from + "/" + log_line.nick.value();
+ submessage["type"] = "groupchat";
+
+ XmlNode body("body");
+ body.set_inner(log_line.body.value());
+ submessage.add_child(std::move(body));
+
+ forwarded.add_child(std::move(submessage));
+ result.add_child(std::move(forwarded));
+ message.add_child(std::move(result));
+
+ this->send_stanza(message);
+}
+
+#endif
+
Bridge* BiboumiComponent::get_user_bridge(const std::string& user_jid)
{
auto bare_jid = Jid{user_jid}.bare();
@@ -517,8 +661,7 @@ Bridge* BiboumiComponent::get_user_bridge(const std::string& user_jid)
}
catch (const std::out_of_range& exception)
{
- this->bridges.emplace(bare_jid, std::make_unique<Bridge>(bare_jid, *this, this->poller));
- return this->bridges.at(bare_jid).get();
+ return this->bridges.emplace(bare_jid, std::make_unique<Bridge>(bare_jid, *this, this->poller)).first->second.get();
}
}
@@ -557,7 +700,32 @@ void BiboumiComponent::send_self_disco_info(const std::string& id, const std::st
identity["type"] = "irc";
identity["name"] = "Biboumi XMPP-IRC gateway";
query.add_child(std::move(identity));
- for (const char* ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS})
+ for (const char* ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS})
+ {
+ XmlNode feature("feature");
+ feature["var"] = ns;
+ query.add_child(std::move(feature));
+ }
+ iq.add_child(std::move(query));
+ this->send_stanza(iq);
+}
+
+void BiboumiComponent::send_irc_server_disco_info(const std::string& id, const std::string& jid_to, const std::string& jid_from)
+{
+ Jid from(jid_from);
+ Stanza iq("iq");
+ iq["type"] = "result";
+ iq["id"] = id;
+ iq["to"] = jid_to;
+ iq["from"] = jid_from;
+ XmlNode query("query");
+ query["xmlns"] = DISCO_INFO_NS;
+ XmlNode identity("identity");
+ identity["category"] = "conference";
+ identity["type"] = "irc";
+ identity["name"] = "IRC server "s + from.local + " over Biboumi";
+ query.add_child(std::move(identity));
+ for (const char* ns: {DISCO_INFO_NS, ADHOC_NS, PING_NS, VERSION_NS})
{
XmlNode feature("feature");
feature["var"] = ns;
@@ -567,6 +735,25 @@ void BiboumiComponent::send_self_disco_info(const std::string& id, const std::st
this->send_stanza(iq);
}
+void BiboumiComponent::send_irc_channel_muc_traffic_info(const std::string id, const std::string& jid_from, const std::string& jid_to)
+{
+ Stanza iq("iq");
+ iq["type"] = "result";
+ iq["id"] = id;
+ iq["from"] = jid_from;
+ iq["to"] = jid_to;
+
+ XmlNode query("query");
+ query["xmlns"] = DISCO_INFO_NS;
+ query["node"] = MUC_TRAFFIC_NS;
+ // We drop all “special” traffic (like xhtml-im, chatstates, etc), so
+ // don’t include any <feature/>
+ iq.add_child(std::move(query));
+
+ this->send_stanza(iq);
+
+}
+
void BiboumiComponent::send_iq_version_request(const std::string& from,
const std::string& jid_to)
{
@@ -604,15 +791,16 @@ void BiboumiComponent::send_ping_request(const std::string& from,
"the response mismatches the 'from' of the request");
}
else
- bridge->send_irc_ping_result(from, id);
+ bridge->send_irc_ping_result({from, bridge}, id);
};
this->waiting_iq[id] = result_cb;
}
-void BiboumiComponent::send_iq_room_list_result(const std::string& id,
- const std::string& to_jid,
- const std::string& from,
- const std::vector<ListElement>& rooms_list)
+void BiboumiComponent::send_iq_room_list_result(const std::string& id, const std::string& to_jid,
+ const std::string& from, const ChannelList& channel_list,
+ std::vector<ListElement>::const_iterator begin,
+ std::vector<ListElement>::const_iterator end,
+ const ResultSetInfo& rs_info)
{
Stanza iq("iq");
iq["from"] = from + "@" + this->served_hostname;
@@ -621,12 +809,60 @@ void BiboumiComponent::send_iq_room_list_result(const std::string& id,
iq["type"] = "result";
XmlNode query("query");
query["xmlns"] = DISCO_ITEMS_NS;
- for (const auto& room: rooms_list)
+
+ for (auto it = begin; it != end; ++it)
{
XmlNode item("item");
- item["jid"] = room.channel + "%" + from + "@" + this->served_hostname;
+ item["jid"] = it->channel + "@" + this->served_hostname;
query.add_child(std::move(item));
}
+
+ if ((rs_info.max >= 0 || !rs_info.after.empty() || !rs_info.before.empty()))
+ {
+ XmlNode set_node("set");
+ set_node["xmlns"] = RSM_NS;
+
+ if (begin != channel_list.channels.cend())
+ {
+ XmlNode first_node("first");
+ first_node["index"] = std::to_string(std::distance(channel_list.channels.cbegin(), begin));
+ first_node.set_inner(begin->channel + "@" + this->served_hostname);
+ set_node.add_child(std::move(first_node));
+ }
+ if (end != channel_list.channels.cbegin())
+ {
+ XmlNode last_node("last");
+ last_node.set_inner(std::prev(end)->channel + "@" + this->served_hostname);
+ set_node.add_child(std::move(last_node));
+ }
+ if (channel_list.complete)
+ {
+ XmlNode count_node("count");
+ count_node.set_inner(std::to_string(channel_list.channels.size()));
+ set_node.add_child(std::move(count_node));
+ }
+ query.add_child(std::move(set_node));
+ }
+
iq.add_child(std::move(query));
this->send_stanza(iq);
}
+
+void BiboumiComponent::send_invitation(const std::string& room_target,
+ const std::string& jid_to,
+ const std::string& author_nick)
+{
+ Stanza message("message");
+ message["from"] = room_target + "@" + this->served_hostname;
+ message["to"] = jid_to;
+ XmlNode x("x");
+ x["xmlns"] = MUC_USER_NS;
+ XmlNode invite("invite");
+ if (author_nick.empty())
+ invite["from"] = room_target + "@" + this->served_hostname;
+ else
+ invite["from"] = room_target + "@" + this->served_hostname + "/" + author_nick;
+ x.add_child(std::move(invite));
+ message.add_child(std::move(x));
+ this->send_stanza(message);
+}
diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp
index 24d768a..999001f 100644
--- a/src/xmpp/biboumi_component.hpp
+++ b/src/xmpp/biboumi_component.hpp
@@ -9,6 +9,10 @@
#include <string>
#include <map>
+namespace db
+{
+class MucLogLine;
+}
struct ListElement;
/**
@@ -58,6 +62,15 @@ public:
*/
void send_self_disco_info(const std::string& id, const std::string& jid_to);
/**
+ * Send a result IQ with the disco informations regarding IRC server JIDs.
+ */
+ void send_irc_server_disco_info(const std::string& id, const std::string& jid_to, const std::string& jid_from);
+ /**
+ * Sends the allowed namespaces in MUC message, according to
+ * http://xmpp.org/extensions/xep-0045.html#impl-service-traffic
+ */
+ void send_irc_channel_muc_traffic_info(const std::string id, const std::string& jid_from, const std::string& jid_to);
+ /**
* Send an iq version request
*/
void send_iq_version_request(const std::string& from,
@@ -71,9 +84,10 @@ public:
/**
* Send the channels list in one big stanza
*/
- void send_iq_room_list_result(const std::string& id, const std::string& to_jid,
- const std::string& from,
- const std::vector<ListElement>& rooms_list);
+ void send_iq_room_list_result(const std::string& id, const std::string& to_jid, const std::string& from,
+ const ChannelList& channel_list, std::vector<ListElement>::const_iterator begin,
+ std::vector<ListElement>::const_iterator end, const ResultSetInfo& rs_info);
+ void send_invitation(const std::string& room_target, const std::string& jid_to, const std::string& author_nick);
/**
* Handle the various stanza types
*/
@@ -81,13 +95,19 @@ public:
void handle_message(const Stanza& stanza);
void handle_iq(const Stanza& stanza);
-private:
+#ifdef USE_DATABASE
+ bool handle_mam_request(const Stanza& stanza);
+ void send_archived_message(const db::MucLogLine& log_line, const std::string& from, const std::string& to,
+ const std::string& queryid);
+#endif
+
/**
* Return the bridge associated with the bare JID. Create a new one
* if none already exist.
*/
Bridge* get_user_bridge(const std::string& user_jid);
+private:
/**
* A map of id -> callback. When we want to wait for an iq result, we add
* the callback to this map, with the iq id as the key. When an iq result