Add support for the "history" node on MUC join
Supports the "seconds", "maxstanzas", "since" and "maxchars" (but only =0) attributes. fix #3270
This commit is contained in:
+10
-6
@@ -167,10 +167,11 @@ IrcClient* Bridge::find_irc_client(const std::string& hostname) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Bridge::join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password,
|
bool Bridge::join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password,
|
||||||
const std::string& resource)
|
const std::string& resource, HistoryLimit history_limit)
|
||||||
{
|
{
|
||||||
const auto& hostname = iid.get_server();
|
const auto& hostname = iid.get_server();
|
||||||
IrcClient* irc = this->make_irc_client(hostname, nickname);
|
IrcClient* irc = this->make_irc_client(hostname, nickname);
|
||||||
|
irc->history_limit = history_limit;
|
||||||
this->add_resource_to_server(hostname, resource);
|
this->add_resource_to_server(hostname, resource);
|
||||||
auto res_in_chan = this->is_resource_in_chan(ChannelKey{iid.get_local(), hostname}, resource);
|
auto res_in_chan = this->is_resource_in_chan(ChannelKey{iid.get_local(), hostname}, resource);
|
||||||
if (!res_in_chan)
|
if (!res_in_chan)
|
||||||
@@ -993,17 +994,20 @@ void Bridge::send_topic(const std::string& hostname, const std::string& chan_nam
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bridge::send_room_history(const std::string& hostname, const std::string& chan_name)
|
void Bridge::send_room_history(const std::string& hostname, const std::string& chan_name, const HistoryLimit& history_limit)
|
||||||
{
|
{
|
||||||
for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}])
|
for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}])
|
||||||
this->send_room_history(hostname, chan_name, resource);
|
this->send_room_history(hostname, chan_name, resource, history_limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bridge::send_room_history(const std::string& hostname, std::string chan_name, const std::string& resource)
|
void Bridge::send_room_history(const std::string& hostname, std::string chan_name, const std::string& resource, const HistoryLimit& history_limit)
|
||||||
{
|
{
|
||||||
#ifdef USE_DATABASE
|
#ifdef USE_DATABASE
|
||||||
const auto coptions = Database::get_irc_channel_options_with_server_and_global_default(this->user_jid, hostname, chan_name);
|
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.col<Database::MaxHistoryLength>());
|
auto limit = coptions.col<Database::MaxHistoryLength>();
|
||||||
|
if (history_limit.stanzas >= 0 && history_limit.stanzas < limit)
|
||||||
|
limit = history_limit.stanzas;
|
||||||
|
const auto lines = Database::get_muc_logs(this->user_jid, chan_name, hostname, limit, history_limit.since);
|
||||||
chan_name.append(utils::empty_if_fixed_server("%" + hostname));
|
chan_name.append(utils::empty_if_fixed_server("%" + hostname));
|
||||||
for (const auto& line: lines)
|
for (const auto& line: lines)
|
||||||
{
|
{
|
||||||
@@ -1257,7 +1261,7 @@ void Bridge::generate_channel_join_for_resource(const Iid& iid, const std::strin
|
|||||||
this->send_user_join(iid.get_server(), iid.get_encoded_local(),
|
this->send_user_join(iid.get_server(), iid.get_encoded_local(),
|
||||||
self, self->get_most_significant_mode(irc->get_sorted_user_modes()),
|
self, self->get_most_significant_mode(irc->get_sorted_user_modes()),
|
||||||
true, resource);
|
true, resource);
|
||||||
this->send_room_history(iid.get_server(), iid.get_local(), resource);
|
this->send_room_history(iid.get_server(), iid.get_local(), resource, irc->history_limit);
|
||||||
this->send_topic(iid.get_server(), iid.get_encoded_local(), channel->topic, channel->topic_author, resource);
|
this->send_topic(iid.get_server(), iid.get_encoded_local(), channel->topic, channel->topic_author, resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <bridge/result_set_management.hpp>
|
#include <bridge/result_set_management.hpp>
|
||||||
#include <bridge/list_element.hpp>
|
#include <bridge/list_element.hpp>
|
||||||
|
#include <bridge/history_limit.hpp>
|
||||||
|
|
||||||
#include <irc/irc_message.hpp>
|
#include <irc/irc_message.hpp>
|
||||||
#include <irc/irc_client.hpp>
|
#include <irc/irc_client.hpp>
|
||||||
@@ -74,7 +75,7 @@ public:
|
|||||||
* Try to join an irc_channel, does nothing and return true if the channel
|
* Try to join an irc_channel, does nothing and return true if the channel
|
||||||
* was already joined.
|
* was already joined.
|
||||||
*/
|
*/
|
||||||
bool join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password, const std::string& resource);
|
bool join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password, const std::string& resource, HistoryLimit history_limit);
|
||||||
|
|
||||||
void send_channel_message(const Iid& iid, const std::string& body);
|
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_private_message(const Iid& iid, const std::string& body, const std::string& type="PRIVMSG");
|
||||||
@@ -156,8 +157,8 @@ public:
|
|||||||
/**
|
/**
|
||||||
* Send the MUC history to the user
|
* 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 HistoryLimit& history_limit);
|
||||||
void send_room_history(const std::string& hostname, std::string chan_name, const std::string& resource);
|
void send_room_history(const std::string& hostname, std::string chan_name, const std::string& resource, const HistoryLimit& history_limit);
|
||||||
/**
|
/**
|
||||||
* Send a MUC message from some participant
|
* Send a MUC message from some participant
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Default values means no limit
|
||||||
|
struct HistoryLimit
|
||||||
|
{
|
||||||
|
int stanzas{-1};
|
||||||
|
std::string since{};
|
||||||
|
};
|
||||||
@@ -784,7 +784,7 @@ void IrcClient::on_channel_completely_joined(const IrcMessage& message)
|
|||||||
channel->joined = true;
|
channel->joined = true;
|
||||||
this->bridge.send_user_join(this->hostname, chan_name, channel->get_self(),
|
this->bridge.send_user_join(this->hostname, chan_name, channel->get_self(),
|
||||||
channel->get_self()->get_most_significant_mode(this->sorted_user_modes), true);
|
channel->get_self()->get_most_significant_mode(this->sorted_user_modes), true);
|
||||||
this->bridge.send_room_history(this->hostname, chan_name);
|
this->bridge.send_room_history(this->hostname, chan_name, this->history_limit);
|
||||||
this->bridge.send_topic(this->hostname, chan_name, channel->topic, channel->topic_author);
|
this->bridge.send_topic(this->hostname, chan_name, channel->topic, channel->topic_author);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
#include <irc/irc_channel.hpp>
|
#include <irc/irc_channel.hpp>
|
||||||
#include <irc/iid.hpp>
|
#include <irc/iid.hpp>
|
||||||
|
|
||||||
|
#include <bridge/history_limit.hpp>
|
||||||
|
|
||||||
#include <network/tcp_client_socket_handler.hpp>
|
#include <network/tcp_client_socket_handler.hpp>
|
||||||
#include <network/resolver.hpp>
|
#include <network/resolver.hpp>
|
||||||
|
|
||||||
@@ -296,6 +298,11 @@ public:
|
|||||||
const std::vector<char>& get_sorted_user_modes() const { return this->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; }
|
std::set<char> get_chantypes() const { return this->chantypes; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the history limit that the client asked when joining this room.
|
||||||
|
*/
|
||||||
|
HistoryLimit history_limit;
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* The hostname of the server we are connected to.
|
* The hostname of the server we are connected to.
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
#include <database/database.hpp>
|
#include <database/database.hpp>
|
||||||
#include <bridge/result_set_management.hpp>
|
#include <bridge/result_set_management.hpp>
|
||||||
|
#include <bridge/history_limit.hpp>
|
||||||
|
|
||||||
using namespace std::string_literals;
|
using namespace std::string_literals;
|
||||||
|
|
||||||
@@ -155,8 +156,33 @@ void BiboumiComponent::handle_presence(const Stanza& stanza)
|
|||||||
bridge->send_irc_nick_change(iid, to.resource, from.resource);
|
bridge->send_irc_nick_change(iid, to.resource, from.resource);
|
||||||
const XmlNode* x = stanza.get_child("x", MUC_NS);
|
const XmlNode* x = stanza.get_child("x", MUC_NS);
|
||||||
const XmlNode* password = x ? x->get_child("password", MUC_NS): nullptr;
|
const XmlNode* password = x ? x->get_child("password", MUC_NS): nullptr;
|
||||||
|
const XmlNode* history = x ? x->get_child("history", MUC_NS): nullptr;
|
||||||
|
HistoryLimit history_limit;
|
||||||
|
if (history)
|
||||||
|
{
|
||||||
|
// TODO implement the "seconds"
|
||||||
|
const auto seconds = history->get_tag("seconds");
|
||||||
|
if (!seconds.empty())
|
||||||
|
{
|
||||||
|
const auto now = std::chrono::system_clock::now();
|
||||||
|
std::time_t timestamp = std::chrono::system_clock::to_time_t(now);
|
||||||
|
int int_seconds = std::atoi(seconds.data());
|
||||||
|
timestamp -= int_seconds;
|
||||||
|
history_limit.since = utils::to_string(timestamp);
|
||||||
|
}
|
||||||
|
const auto since = history->get_tag("since");
|
||||||
|
if (!since.empty())
|
||||||
|
history_limit.since = since;
|
||||||
|
const auto maxstanzas = history->get_tag("maxstanzas");
|
||||||
|
if (!maxstanzas.empty())
|
||||||
|
history_limit.stanzas = std::atoi(maxstanzas.data());
|
||||||
|
// Ignore any other value, because this is too complex to implement,
|
||||||
|
// so I won’t do it.
|
||||||
|
if (history->get_tag("maxchars") == "0")
|
||||||
|
history_limit.stanzas = 0;
|
||||||
|
}
|
||||||
bridge->join_irc_channel(iid, to.resource, password ? password->get_inner(): "",
|
bridge->join_irc_channel(iid, to.resource, password ? password->get_inner(): "",
|
||||||
from.resource);
|
from.resource, history_limit);
|
||||||
}
|
}
|
||||||
else if (type == "unavailable")
|
else if (type == "unavailable")
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1942,6 +1942,124 @@ if __name__ == '__main__':
|
|||||||
partial(expect_stanza,
|
partial(expect_stanza,
|
||||||
"/iq[@type='result'][@id='id8'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']"),
|
"/iq[@type='result'][@id='id8'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']"),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
|
||||||
|
Scenario("join_history_limits",
|
||||||
|
[
|
||||||
|
handshake_sequence(),
|
||||||
|
partial(send_stanza,
|
||||||
|
"<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
|
||||||
|
connection_sequence("irc.localhost", '{jid_one}/{resource_one}'),
|
||||||
|
partial(expect_stanza,
|
||||||
|
"/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
|
||||||
|
partial(expect_stanza,
|
||||||
|
("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",
|
||||||
|
"/presence/muc_user:x/muc_user:status[@code='110']")
|
||||||
|
),
|
||||||
|
partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),
|
||||||
|
|
||||||
|
# Send two channel messages
|
||||||
|
partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"),
|
||||||
|
partial(expect_stanza,
|
||||||
|
("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']",
|
||||||
|
"/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]",)
|
||||||
|
),
|
||||||
|
|
||||||
|
partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 2</body></message>"),
|
||||||
|
# Record the current time
|
||||||
|
partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou 2']",
|
||||||
|
after = partial(save_current_timestamp_plus_delta, "first_timestamp", datetime.timedelta(seconds=1))),
|
||||||
|
|
||||||
|
# Wait two seconds before sending two new messages
|
||||||
|
partial(sleep_for, 2),
|
||||||
|
partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 3</body></message>"),
|
||||||
|
partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 4</body></message>"),
|
||||||
|
partial(expect_stanza, "/message[@type='groupchat']/body[text()='coucou 3']"),
|
||||||
|
partial(expect_stanza, "/message[@type='groupchat']/body[text()='coucou 4']",
|
||||||
|
after = partial(save_current_timestamp_plus_delta, "second_timestamp", datetime.timedelta(seconds=1))),
|
||||||
|
|
||||||
|
# join the virtual channel, to stay connected to the server even after leaving #foo
|
||||||
|
partial(send_stanza,
|
||||||
|
"<presence from='{jid_one}/{resource_one}' to='%{irc_server_one}/{nick_one}' />"),
|
||||||
|
partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"),
|
||||||
|
partial(expect_stanza, "/message/subject"),
|
||||||
|
|
||||||
|
# Leave #foo
|
||||||
|
partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"),
|
||||||
|
partial(expect_stanza, "/presence[@type='unavailable']"),
|
||||||
|
|
||||||
|
# Rejoin #foo, with some history limit
|
||||||
|
partial(send_stanza,
|
||||||
|
"<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history maxchars='0'/></x></presence>"),
|
||||||
|
partial(expect_stanza, "/message"),
|
||||||
|
partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"),
|
||||||
|
partial(expect_stanza, "/message/subject"),
|
||||||
|
|
||||||
|
partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"),
|
||||||
|
partial(expect_stanza, "/presence[@type='unavailable']"),
|
||||||
|
|
||||||
|
# Rejoin #foo, with some history limit
|
||||||
|
partial(send_stanza,
|
||||||
|
"<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history maxstanzas='3'/></x></presence>"),
|
||||||
|
partial(expect_stanza, "/message"),
|
||||||
|
partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"),
|
||||||
|
partial(expect_stanza,
|
||||||
|
"/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 2']"),
|
||||||
|
partial(expect_stanza,
|
||||||
|
"/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"),
|
||||||
|
partial(expect_stanza,
|
||||||
|
"/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 4']"),
|
||||||
|
partial(expect_stanza, "/message/subject"),
|
||||||
|
|
||||||
|
partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"),
|
||||||
|
partial(expect_stanza, "/presence[@type='unavailable']"),
|
||||||
|
|
||||||
|
|
||||||
|
# Rejoin #foo, with some history limit
|
||||||
|
partial(send_stanza,
|
||||||
|
"<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history since='{first_timestamp}'/></x></presence>"),
|
||||||
|
partial(expect_stanza, "/message"),
|
||||||
|
partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"),
|
||||||
|
partial(expect_stanza,
|
||||||
|
"/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"),
|
||||||
|
partial(expect_stanza,
|
||||||
|
"/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 4']"),
|
||||||
|
partial(expect_stanza, "/message/subject"),
|
||||||
|
partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"),
|
||||||
|
partial(expect_stanza, "/presence[@type='unavailable']"),
|
||||||
|
|
||||||
|
# Rejoin #foo, with some history limit
|
||||||
|
partial(send_stanza,
|
||||||
|
"<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history seconds='1'/></x></presence>"),
|
||||||
|
partial(expect_stanza, "/message"),
|
||||||
|
partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"),
|
||||||
|
partial(expect_stanza,
|
||||||
|
"/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"),
|
||||||
|
partial(expect_stanza,
|
||||||
|
"/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 4']"),
|
||||||
|
partial(expect_stanza, "/message/subject"),
|
||||||
|
partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"),
|
||||||
|
partial(expect_stanza, "/presence[@type='unavailable']"),
|
||||||
|
|
||||||
|
# Rejoin #foo, with some history limit
|
||||||
|
partial(send_stanza,
|
||||||
|
"<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}'><x xmlns='http://jabber.org/protocol/muc'><history seconds='5'/></x></presence>"),
|
||||||
|
partial(expect_stanza, "/message"),
|
||||||
|
partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"),
|
||||||
|
partial(expect_stanza,
|
||||||
|
"/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou']"), partial(expect_stanza,
|
||||||
|
"/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 2']"),
|
||||||
|
partial(expect_stanza,
|
||||||
|
"/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"),
|
||||||
|
partial(expect_stanza,
|
||||||
|
"/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 4']"),
|
||||||
|
partial(expect_stanza, "/message/subject"),
|
||||||
|
partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='unavailable' />"),
|
||||||
|
partial(expect_stanza, "/presence[@type='unavailable']"),
|
||||||
|
|
||||||
|
]),
|
||||||
|
|
||||||
|
|
||||||
Scenario("mam_on_fixed_server",
|
Scenario("mam_on_fixed_server",
|
||||||
[
|
[
|
||||||
handshake_sequence(),
|
handshake_sequence(),
|
||||||
|
|||||||
Reference in New Issue
Block a user