* https://codeberg.org/poezio/biboumi: (65 commits) build: fix wrong constextpr preventing some compilers from building biboumi database: various optimizations for SQLite and PostgreSQL doc(admin): format verify_certificate documentation into a list Fix RPM builds on Fedora Linux Systemd service dir is /usr/lib/systemd/system cmake: Explicitly search for Catch2 version 3 add CAP LS 302 support and max-lines support for draft/multiline messages install files using GNUInstallDirs change to direct /doc/ Fix link to documentation fix multiline private messages add archive_expires_after option Fix documentation warnings fix fedora package add ctest support and remove absolute path from etc directory Rename Ports to Unencrypted ports build docker image from local files, not git clone update some version strings add default_persistence_allowed add persist_user_denylist and persist_user_allowlist ...
This commit is contained in:
+125
-7
@@ -2,18 +2,22 @@
|
||||
#include <utility>
|
||||
#include <xmpp/biboumi_component.hpp>
|
||||
#include <network/poller.hpp>
|
||||
#include <utils/chunk.hpp>
|
||||
#include <utils/empty_if_fixed_server.hpp>
|
||||
#include <utils/encoding.hpp>
|
||||
#include <utils/tolower.hpp>
|
||||
#include <utils/uuid.hpp>
|
||||
#include <logger/logger.hpp>
|
||||
#include <utils/is_requester_allowed_to_persist.hpp>
|
||||
#include <utils/revstr.hpp>
|
||||
#include <utils/split.hpp>
|
||||
#include <xmpp/jid.hpp>
|
||||
#include <database/database.hpp>
|
||||
#include <irc/parse_caps.hpp>
|
||||
#include "result_set_management.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
@@ -64,7 +68,7 @@ void Bridge::shutdown(const std::string& exit_message)
|
||||
{
|
||||
for (auto& pair: this->irc_clients)
|
||||
{
|
||||
std::unique_ptr<IrcClient>& irc = pair.second;
|
||||
std::shared_ptr<IrcClient>& irc = pair.second;
|
||||
irc->send_quit_command(exit_message);
|
||||
}
|
||||
}
|
||||
@@ -135,11 +139,11 @@ IrcClient* Bridge::make_irc_client(const std::string& hostname, const std::strin
|
||||
realname = this->get_bare_jid();
|
||||
}
|
||||
this->irc_clients.emplace(hostname,
|
||||
std::make_unique<IrcClient>(this->poller, hostname,
|
||||
std::make_shared<IrcClient>(this->poller, hostname,
|
||||
nickname, username,
|
||||
realname, jid.domain,
|
||||
*this));
|
||||
std::unique_ptr<IrcClient>& irc = this->irc_clients.at(hostname);
|
||||
std::shared_ptr<IrcClient> irc = this->irc_clients.at(hostname);
|
||||
return irc.get();
|
||||
}
|
||||
}
|
||||
@@ -221,6 +225,83 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body, std::
|
||||
std::vector<std::string> lines = utils::split(body, '\n', true);
|
||||
if (lines.empty())
|
||||
return ;
|
||||
|
||||
if (irc->has_capability("draft/multiline")
|
||||
&& lines.size() > 1
|
||||
// /me and /mode cannot be multi lined
|
||||
&& lines[0].substr(0, 5) != "/mode"
|
||||
&& lines[0].substr(0, 4) != "/me ")
|
||||
{
|
||||
std::string uuid;
|
||||
const auto xmpp_body = this->make_xmpp_body(body);
|
||||
#ifdef USE_DATABASE
|
||||
if (this->record_history)
|
||||
uuid = Database::store_muc_message(this->get_bare_jid(), iid.get_local(), iid.get_server(), std::chrono::system_clock::now(),
|
||||
std::get<0>(xmpp_body), irc->get_own_nick());
|
||||
#else
|
||||
uuid = utils::gen_uuid();
|
||||
#endif
|
||||
if (id.empty())
|
||||
id = utils::gen_uuid();
|
||||
|
||||
// A message with a lot of lines may need to be split, even if the
|
||||
// server supports draft/multiline, as we may not be allowed to send
|
||||
// that many lines all at once. As such check max-lines
|
||||
// https://ircv3.net/specs/extensions/multiline#capabilities
|
||||
std::vector<std::vector<std::string>> lines_chunked = {lines};
|
||||
auto cap_value = irc->get_capability_value("draft/multiline");
|
||||
if (cap_value) {
|
||||
auto cap_value_parsed = parse_cap_keyval(cap_value.value());
|
||||
if (cap_value_parsed.contains("max-lines") &&
|
||||
cap_value_parsed.at("max-lines").has_value()) {
|
||||
std::stringstream max_lines_string(
|
||||
cap_value_parsed.at("max-lines").value());
|
||||
size_t size;
|
||||
max_lines_string >> size;
|
||||
if (size > 0)
|
||||
lines_chunked = utils::chunk(lines, size);
|
||||
}
|
||||
}
|
||||
for (const auto &lines : lines_chunked) {
|
||||
std::vector<IrcMessage> messages;
|
||||
for (const std::string &line : lines) {
|
||||
constexpr auto max_username_size = 10;
|
||||
constexpr auto max_hostname_size = 63;
|
||||
const auto line_size = 512 - 1 - ::strlen("batch=") - uuid.length() - 1 - ::strlen("draft/multiline-concat") - 1 -
|
||||
irc->get_own_nick().size() - max_username_size - max_hostname_size -
|
||||
::strlen(":!@ PRIVMSG ") - iid.get_local().length() - ::strlen(" :\r\n");
|
||||
const auto privmsgs = cut(line, line_size);
|
||||
bool first = true;
|
||||
for (const auto& privmsg: privmsgs)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
messages.push_back(IrcMessage({}, std::string(), "PRIVMSG", {iid.get_local(), privmsg}));
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
messages.push_back(IrcMessage({{"draft/multiline-concat", {}}}, std::string(), "PRIVMSG", {iid.get_local(), privmsg}));
|
||||
}
|
||||
}
|
||||
|
||||
MessageCallback mirror_to_all_resources = [this, iid, uuid, id, nodes_to_reflect, body](const IrcClient* irc, const IrcMessage&) {
|
||||
for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
|
||||
{
|
||||
auto stanza = this->xmpp.make_muc_message(std::to_string(iid), irc->get_own_nick(), this->make_xmpp_body(body),
|
||||
this->user_jid + "/"
|
||||
+ resource, uuid, id);
|
||||
for (const auto& node: nodes_to_reflect)
|
||||
stanza.add_child(node);
|
||||
this->xmpp.send_stanza(stanza);
|
||||
}
|
||||
};
|
||||
irc->send_batch(uuid, "draft/multiline", {iid.get_local()}, messages, std::move(mirror_to_all_resources));
|
||||
id = utils::gen_uuid();
|
||||
uuid = utils::gen_uuid();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool first = true;
|
||||
for (const std::string& line: lines)
|
||||
{
|
||||
@@ -403,6 +484,41 @@ void Bridge::send_private_message(const Iid& iid, const std::string& body, const
|
||||
std::vector<std::string> lines = utils::split(body, '\n', true);
|
||||
if (lines.empty())
|
||||
return ;
|
||||
|
||||
if (irc->has_capability("draft/multiline")
|
||||
&& lines.size() > 1
|
||||
// /me and /mode cannot be multi lined
|
||||
&& lines[0].substr(0, 5) != "/mode"
|
||||
&& lines[0].substr(0, 4) != "/me ")
|
||||
{
|
||||
std::basic_string<char> uuid = utils::gen_uuid();
|
||||
std::vector<IrcMessage> messages;
|
||||
for (const std::string &line : lines) {
|
||||
constexpr auto max_username_size = 10;
|
||||
constexpr auto max_hostname_size = 63;
|
||||
const auto line_size = 512 - 1 - ::strlen("batch=") - uuid.length() - 1 -
|
||||
::strlen("draft/multiline-concat") - 1 -
|
||||
irc->get_own_nick().size() - max_username_size -
|
||||
max_hostname_size - ::strlen(":!@ PRIVMSG ") -
|
||||
iid.get_local().length() - ::strlen(" :\r\n");
|
||||
const auto privmsgs = cut(line, line_size);
|
||||
bool first = true;
|
||||
for (const auto &privmsg : privmsgs) {
|
||||
if (first) {
|
||||
messages.push_back(IrcMessage({}, std::string(), "PRIVMSG",
|
||||
{iid.get_local(), privmsg}));
|
||||
first = false;
|
||||
} else
|
||||
messages.push_back(IrcMessage({{"draft/multiline-concat", {}}},
|
||||
std::string(), "PRIVMSG",
|
||||
{iid.get_local(), privmsg}));
|
||||
}
|
||||
}
|
||||
irc->send_batch(uuid, "draft/multiline", {iid.get_local()}, messages,
|
||||
[](const IrcClient *, const IrcMessage &) {});
|
||||
return;
|
||||
}
|
||||
|
||||
for (const std::string& line: lines)
|
||||
{
|
||||
if (line.substr(0, 4) == "/me ")
|
||||
@@ -436,7 +552,9 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con
|
||||
bool persistent = false;
|
||||
#ifdef USE_DATABASE
|
||||
const auto goptions = Database::get_global_options(this->user_jid);
|
||||
if (goptions.col<Database::GlobalPersistent>())
|
||||
if (!utils::is_requester_allowed_to_persist(Jid(this->user_jid)))
|
||||
persistent = false;
|
||||
else if (goptions.col<Database::GlobalPersistent>())
|
||||
persistent = true;
|
||||
else
|
||||
{
|
||||
@@ -1151,12 +1269,12 @@ void Bridge::trigger_on_irc_message(const std::string& irc_hostname, const IrcMe
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::unique_ptr<IrcClient>>& Bridge::get_irc_clients()
|
||||
std::unordered_map<std::string, std::shared_ptr<IrcClient>>& Bridge::get_irc_clients()
|
||||
{
|
||||
return this->irc_clients;
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, std::unique_ptr<IrcClient>>& Bridge::get_irc_clients() const
|
||||
const std::unordered_map<std::string, std::shared_ptr<IrcClient>>& Bridge::get_irc_clients() const
|
||||
{
|
||||
return this->irc_clients;
|
||||
}
|
||||
@@ -1165,7 +1283,7 @@ std::set<char> Bridge::get_chantypes(const std::string& hostname) const
|
||||
{
|
||||
IrcClient* irc = this->find_irc_client(hostname);
|
||||
if (!irc)
|
||||
return {'#', '&'};
|
||||
return {'&', '#', '+', '!'};
|
||||
return irc->get_chantypes();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user