diff --git a/profanity.doap b/profanity.doap
index bb188d403..b0c725d5b 100644
--- a/profanity.doap
+++ b/profanity.doap
@@ -360,7 +360,7 @@
partial
1.1.0
0.14.0
- No MUC MAM
+ MAM since 0.14.0. MUC MAM since DEV
diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c
index f6ad8caa1..c2fa6c448 100644
--- a/src/command/cmd_ac.c
+++ b/src/command/cmd_ac.c
@@ -112,6 +112,7 @@ static char* _strophe_autocomplete(ProfWin* window, const char* const input, gbo
static char* _stamp_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _adhoc_cmd_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _vcard_autocomplete(ProfWin* window, const char* const input, gboolean previous);
+static char* _mam_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _script_autocomplete_func(const char* const prefix, gboolean previous, void* context);
@@ -276,6 +277,7 @@ static Autocomplete vcard_name_ac;
static Autocomplete vcard_set_param_ac;
static Autocomplete vcard_togglable_param_ac;
static Autocomplete vcard_address_type_ac;
+static Autocomplete mam_ac;
static char* last_filepath_input = NULL;
@@ -434,6 +436,7 @@ static Autocomplete* all_acs[] = {
&vcard_set_param_ac,
&vcard_togglable_param_ac,
&vcard_address_type_ac,
+ &mam_ac,
};
static GHashTable* ac_funcs = NULL;
@@ -1345,6 +1348,10 @@ cmd_ac_init(void)
autocomplete_add(vcard_address_type_ac, "domestic");
autocomplete_add(vcard_address_type_ac, "international");
+ autocomplete_add(mam_ac, "on");
+ autocomplete_add(mam_ac, "off");
+ autocomplete_add(mam_ac, "verify");
+
if (ac_funcs != NULL)
g_hash_table_destroy(ac_funcs);
ac_funcs = g_hash_table_new(g_str_hash, g_str_equal);
@@ -1376,6 +1383,7 @@ cmd_ac_init(void)
g_hash_table_insert(ac_funcs, "/lastactivity", _lastactivity_autocomplete);
g_hash_table_insert(ac_funcs, "/log", _log_autocomplete);
g_hash_table_insert(ac_funcs, "/logging", _logging_autocomplete);
+ g_hash_table_insert(ac_funcs, "/mam", _mam_autocomplete);
g_hash_table_insert(ac_funcs, "/privacy", _privacy_autocomplete);
g_hash_table_insert(ac_funcs, "/mood", _mood_autocomplete);
g_hash_table_insert(ac_funcs, "/notify", _notify_autocomplete);
@@ -1771,6 +1779,16 @@ _spellcheck_autocomplete(ProfWin* window, const char* const input, gboolean prev
return result;
}
+static char*
+_mam_autocomplete(ProfWin* window, const char* const input, gboolean previous)
+{
+ char* result = NULL;
+
+ result = autocomplete_param_with_ac(input, "/mam", mam_ac, TRUE, previous);
+
+ return result;
+}
+
static char*
_cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previous)
{
@@ -1781,7 +1799,7 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ
// autocomplete boolean settings
gchar* boolean_choices[] = { "/beep", "/states", "/outtype", "/flash", "/splash",
"/history", "/vercheck", "/privileges", "/wrap",
- "/carbons", "/slashguard", "/mam", "/silence" };
+ "/carbons", "/slashguard", "/silence" };
for (size_t i = 0; i < ARRAY_SIZE(boolean_choices); i++) {
result = autocomplete_param_with_func(input, boolean_choices[i], prefs_autocomplete_boolean_choice, previous, NULL);
diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c
index 1b65356fe..9861f94ad 100644
--- a/src/command/cmd_defs.c
+++ b/src/command/cmd_defs.c
@@ -2615,19 +2615,21 @@ static const struct cmd_t command_defs[] = {
},
{ CMD_PREAMBLE("/mam",
- parse_args, 1, 1, &cons_mam_setting)
- CMD_MAINFUNC(cmd_mam)
- CMD_TAGS(
- CMD_TAG_CHAT)
- CMD_SYN(
- "/mam |")
- CMD_DESC(
- "Enable/Disable Message Archive Management (XEP-0313) "
- "Currently MAM in groupchats (MUCs) is not supported. "
- "Use the PG UP key to load more history.")
- CMD_ARGS(
- { "on|off", "Enable or disable MAM" })
- },
+ parse_args, 1, 1, &cons_mam_setting)
+ CMD_MAINFUNC(cmd_mam)
+ CMD_TAGS(
+ CMD_TAG_CHAT,
+ CMD_TAG_GROUPCHAT)
+ CMD_SYN(
+ "/mam |",
+ "/mam verify")
+ CMD_DESC(
+ "Enable or disable Message Archive Management (XEP-0313), or verify loaded history. "
+ "Use the PG UP key to load more history.")
+ CMD_ARGS(
+ { "on|off", "Enable or disable MAM" },
+ { "verify", "Verify history between the oldest and newest loaded messages in the current window and fetch any missing ones from the server" })
+ },
{ CMD_PREAMBLE("/changepassword",
parse_args, 0, 0, NULL)
diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c
index 96267ca52..2327a7c85 100644
--- a/src/command/cmd_funcs.c
+++ b/src/command/cmd_funcs.c
@@ -9648,6 +9648,61 @@ cmd_executable_vcard_photo(ProfWin* window, const char* const command, gchar** a
gboolean
cmd_mam(ProfWin* window, const char* const command, gchar** args)
{
+ if (g_strcmp0(args[0], "verify") == 0) {
+ if (connection_get_status() != JABBER_CONNECTED) {
+ cons_show("You are not currently connected.");
+ return TRUE;
+ }
+ if (window->type != WIN_CHAT && window->type != WIN_MUC) {
+ cons_show("MAM verification can only be performed in chat or groupchat windows.");
+ return TRUE;
+ }
+ if (!prefs_get_boolean(PREF_MAM)) {
+ cons_show("Message Archive Management (MAM) is currently disabled.");
+ return TRUE;
+ }
+ if (!connection_supports(XMPP_FEATURE_MAM2)) {
+ cons_show("Your server does not support Message Archive Management (MAM2).");
+ return TRUE;
+ }
+
+ unsigned int buf_size = buffer_size(window->layout->buffer);
+ if (buf_size == 0) {
+ cons_show("No loaded history in this window to verify.");
+ return TRUE;
+ }
+
+ ProfBuffEntry* first_msg = NULL;
+ ProfBuffEntry* last_msg = NULL;
+
+ for (unsigned int i = 0; i < buf_size; i++) {
+ ProfBuffEntry* entry = buffer_get_entry(window->layout->buffer, i);
+ if (entry && entry->message && entry->message[0] != '-') {
+ if (!first_msg) {
+ first_msg = entry;
+ }
+ last_msg = entry;
+ }
+ }
+
+ if (!first_msg || !last_msg) {
+ cons_show("No messages found in the active scrollback buffer.");
+ return TRUE;
+ }
+
+ GDateTime* utc_start = g_date_time_to_timezone(first_msg->time, g_time_zone_new_utc());
+ GDateTime* utc_end = g_date_time_to_timezone(last_msg->time, g_time_zone_new_utc());
+ auto_gchar gchar* start_str = g_date_time_format(utc_start, "%FT%T.%f%:z");
+ auto_gchar gchar* end_str = g_date_time_format(utc_end, "%FT%T.%f%:z");
+ g_date_time_unref(utc_start);
+ g_date_time_unref(utc_end);
+
+ cons_show("Verifying history from %s to %s...", start_str, end_str);
+ iq_mam_verify_request(window, start_str, end_str);
+
+ return TRUE;
+ }
+
_cmd_set_boolean_preference(args[0], "Message Archive Management", PREF_MAM);
return TRUE;
diff --git a/src/database.c b/src/database.c
index c9059f019..2f3fcb66a 100644
--- a/src/database.c
+++ b/src/database.c
@@ -29,7 +29,7 @@
static sqlite3* g_chatlog_database;
-static void _add_to_db(ProfMessage* message, char* type, const Jid* const from_jid, const Jid* const to_jid);
+static gboolean _add_to_db(ProfMessage* message, char* type, const Jid* const from_jid, const Jid* const to_jid);
static char* _get_db_filename(ProfAccount* account);
static prof_msg_type_t _get_message_type_type(const char* const type);
static prof_enc_t _get_message_enc_type(const char* const encstr);
@@ -211,13 +211,13 @@ log_database_close(void)
}
}
-void
+gboolean
log_database_add_incoming(ProfMessage* message)
{
if (message->to_jid) {
- _add_to_db(message, NULL, message->from_jid, message->to_jid);
+ return _add_to_db(message, NULL, message->from_jid, message->to_jid);
} else {
- _add_to_db(message, NULL, message->from_jid, connection_get_jid());
+ return _add_to_db(message, NULL, message->from_jid, connection_get_jid());
}
}
@@ -256,7 +256,7 @@ log_database_add_outgoing_muc_pm(const char* const id, const char* const barejid
_log_database_add_outgoing("mucpm", id, barejid, message, replace_id, enc);
}
-// Get info (timestamp and stanza_id) of the first or last message in db
+// Get info (timestamp and stanza_id) of the first or last message in db (personal chats)
ProfMessage*
log_database_get_limits_info(const gchar* const contact_barejid, gboolean is_last)
{
@@ -267,8 +267,9 @@ log_database_get_limits_info(const gchar* const contact_barejid, gboolean is_las
const char* order = is_last ? "DESC" : "ASC";
auto_sqlite char* query = sqlite3_mprintf("SELECT `archive_id`, `timestamp` FROM `ChatLogs` WHERE "
+ "`type` = 'chat' AND ("
"(`from_jid` = %Q AND `to_jid` = %Q) OR "
- "(`from_jid` = %Q AND `to_jid` = %Q) "
+ "(`from_jid` = %Q AND `to_jid` = %Q)) "
"ORDER BY `timestamp` %s LIMIT 1;",
contact_barejid, myjid->barejid, myjid->barejid, contact_barejid, order);
@@ -300,6 +301,51 @@ log_database_get_limits_info(const gchar* const contact_barejid, gboolean is_las
return msg;
}
+// Get info (timestamp and stanza_id) of the first or last message in db (MUCs)
+ProfMessage*
+log_database_get_limits_info_muc(const gchar* const room_jid, gboolean is_last)
+{
+ sqlite3_stmt* stmt = NULL;
+ const Jid* myjid = connection_get_jid();
+ if (!myjid->str)
+ return NULL;
+
+ const char* order = is_last ? "DESC" : "ASC";
+ auto_sqlite char* query = sqlite3_mprintf("SELECT `archive_id`, `timestamp` FROM `ChatLogs` WHERE "
+ "`type` = 'muc' AND ("
+ "(`from_jid` = %Q AND `to_jid` = %Q) OR "
+ "(`from_jid` = %Q AND `to_jid` = %Q)) "
+ "ORDER BY `timestamp` %s LIMIT 1;",
+ room_jid, myjid->barejid, myjid->barejid, room_jid, order);
+
+ if (!query) {
+ log_error("Could not allocate memory for SQL query in log_database_get_limits_info_muc()");
+ return NULL;
+ }
+
+ int rc = sqlite3_prepare_v2(g_chatlog_database, query, -1, &stmt, NULL);
+ if (rc != SQLITE_OK) {
+ log_error("Unknown SQLite error in log_database_get_limits_info_muc().");
+ return NULL;
+ }
+
+ ProfMessage* msg = message_init();
+
+ if (sqlite3_step(stmt) == SQLITE_ROW) {
+ char* archive_id = (char*)sqlite3_column_text(stmt, 0);
+ char* date = (char*)sqlite3_column_text(stmt, 1);
+
+ msg->stanzaid = _db_strdup(archive_id);
+ msg->timestamp = g_date_time_new_from_iso8601(date, NULL);
+ } else {
+ message_free(msg);
+ msg = NULL;
+ }
+ sqlite3_finalize(stmt);
+
+ return msg;
+}
+
// Query previous chats, constraints start_time and end_time. If end_time is
// null the current time is used. from_start gets first few messages if true
// otherwise the last ones. Flip flips the order of the results
@@ -320,6 +366,7 @@ log_database_get_previous_chat(const gchar* const contact_barejid, const char* s
"A.`timestamp`, A.`from_jid`, A.`from_resource`, A.`to_jid`, A.`to_resource`, A.`type`, A.`encryption`, A.`stanza_id` FROM `ChatLogs` AS A "
"LEFT JOIN `ChatLogs` AS B ON (A.`replaced_by_db_id` = B.`id` AND A.`from_jid` = B.`from_jid`) "
"WHERE (A.`replaces_db_id` IS NULL) "
+ "AND A.`type` = 'chat' "
"AND ((A.`from_jid` = %Q AND A.`to_jid` = %Q) OR (A.`from_jid` = %Q AND A.`to_jid` = %Q)) "
"AND A.`timestamp` < %Q "
"AND (%Q IS NULL OR A.`timestamp` > %Q) "
@@ -367,6 +414,125 @@ log_database_get_previous_chat(const gchar* const contact_barejid, const char* s
return history;
}
+// Query previous MUCs, constraints start_time and end_time. If end_time is
+// null the current time is used. from_start gets first few messages if true
+// otherwise the last ones. Flip flips the order of the results
+GSList*
+log_database_get_previous_muc(const gchar* const room_jid, const char* start_time, const char* end_time, gboolean from_start, gboolean flip)
+{
+ sqlite3_stmt* stmt = NULL;
+ const Jid* myjid = connection_get_jid();
+ if (!myjid->str)
+ return NULL;
+
+ // Flip order when querying older pages
+ gchar* sort1 = from_start ? "ASC" : "DESC";
+ gchar* sort2 = !flip ? "ASC" : "DESC";
+ auto_gchar gchar* end_date_fmt = end_time ? g_strdup(end_time) : prof_date_time_format_iso8601(NULL);
+ auto_sqlite gchar* query = sqlite3_mprintf("SELECT * FROM ("
+ "SELECT COALESCE(B.`message`, A.`message`) AS message, "
+ "A.`timestamp`, A.`from_jid`, A.`from_resource`, A.`to_jid`, A.`to_resource`, A.`type`, A.`encryption`, A.`stanza_id` FROM `ChatLogs` AS A "
+ "LEFT JOIN `ChatLogs` AS B ON (A.`replaced_by_db_id` = B.`id` AND A.`from_jid` = B.`from_jid`) "
+ "WHERE (A.`replaces_db_id` IS NULL) "
+ "AND A.`type` = 'muc' "
+ "AND ((A.`from_jid` = %Q AND A.`to_jid` = %Q) OR (A.`from_jid` = %Q AND A.`to_jid` = %Q)) "
+ "AND A.`timestamp` < %Q "
+ "AND (%Q IS NULL OR A.`timestamp` > %Q) "
+ "ORDER BY A.`timestamp` %s LIMIT %d) "
+ "ORDER BY `timestamp` %s;",
+ room_jid, myjid->barejid, myjid->barejid, room_jid, end_date_fmt, start_time, start_time, sort1, MESSAGES_TO_RETRIEVE, sort2);
+
+ if (!query) {
+ log_error("Could not allocate memory in log_database_get_previous_muc()");
+ return NULL;
+ }
+
+ int rc = sqlite3_prepare_v2(g_chatlog_database, query, -1, &stmt, NULL);
+ if (rc != SQLITE_OK) {
+ log_error("SQLite error in log_database_get_previous_muc(): %s", sqlite3_errmsg(g_chatlog_database));
+ return NULL;
+ }
+
+ GSList* history = NULL;
+
+ while (sqlite3_step(stmt) == SQLITE_ROW) {
+ char* message = (char*)sqlite3_column_text(stmt, 0);
+ char* date = (char*)sqlite3_column_text(stmt, 1);
+ char* from_jid = (char*)sqlite3_column_text(stmt, 2);
+ char* from_resource = (char*)sqlite3_column_text(stmt, 3);
+ char* to_jid = (char*)sqlite3_column_text(stmt, 4);
+ char* to_resource = (char*)sqlite3_column_text(stmt, 5);
+ char* type = (char*)sqlite3_column_text(stmt, 6);
+ char* encryption = (char*)sqlite3_column_text(stmt, 7);
+ char* id = (char*)sqlite3_column_text(stmt, 8);
+
+ ProfMessage* msg = message_init();
+ msg->id = id ? strdup(id) : NULL;
+ msg->from_jid = jid_create_from_bare_and_resource(from_jid, from_resource);
+ msg->to_jid = jid_create_from_bare_and_resource(to_jid, to_resource);
+ msg->plain = strdup(message ?: "");
+ msg->timestamp = g_date_time_new_from_iso8601(date, NULL);
+ msg->type = _get_message_type_type(type);
+ msg->enc = _get_message_enc_type(encryption);
+
+ history = g_slist_append(history, msg);
+ }
+ sqlite3_finalize(stmt);
+
+ return history;
+}
+
+int
+log_database_get_chat_count(const gchar* const contact_barejid, const char* start_time, const char* end_time)
+{
+ sqlite3_stmt* stmt = NULL;
+ const Jid* myjid = connection_get_jid();
+ if (!myjid->str)
+ return 0;
+
+ auto_sqlite char* query = sqlite3_mprintf("SELECT COUNT(*) FROM `ChatLogs` WHERE "
+ "`type` = 'chat' AND ("
+ "(`from_jid` = %Q AND `to_jid` = %Q) OR "
+ "(`from_jid` = %Q AND `to_jid` = %Q)) "
+ "AND `timestamp` >= %Q AND `timestamp` <= %Q;",
+ contact_barejid, myjid->barejid, myjid->barejid, contact_barejid, start_time, end_time);
+
+ int count = 0;
+ int rc = sqlite3_prepare_v2(g_chatlog_database, query, -1, &stmt, NULL);
+ if (rc == SQLITE_OK) {
+ if (sqlite3_step(stmt) == SQLITE_ROW) {
+ count = sqlite3_column_int(stmt, 0);
+ }
+ }
+ sqlite3_finalize(stmt);
+ return count;
+}
+
+int
+log_database_get_muc_count(const gchar* const room_jid, const char* start_time, const char* end_time)
+{
+ sqlite3_stmt* stmt = NULL;
+ const Jid* myjid = connection_get_jid();
+ if (!myjid->str)
+ return 0;
+
+ auto_sqlite char* query = sqlite3_mprintf("SELECT COUNT(*) FROM `ChatLogs` WHERE "
+ "`type` = 'muc' AND "
+ "(`from_jid` = %Q OR `to_jid` = %Q) "
+ "AND `timestamp` >= %Q AND `timestamp` <= %Q;",
+ room_jid, room_jid, start_time, end_time);
+
+ int count = 0;
+ int rc = sqlite3_prepare_v2(g_chatlog_database, query, -1, &stmt, NULL);
+ if (rc == SQLITE_OK) {
+ if (sqlite3_step(stmt) == SQLITE_ROW) {
+ count = sqlite3_column_int(stmt, 0);
+ }
+ }
+ sqlite3_finalize(stmt);
+ return count;
+}
+
static const char*
_get_message_type_str(prof_msg_type_t type)
{
@@ -432,14 +598,14 @@ _get_message_enc_type(const char* const encstr)
return PROF_MSG_ENC_NONE;
}
-static void
+static gboolean
_add_to_db(ProfMessage* message, char* type, const Jid* const from_jid, const Jid* const to_jid)
{
auto_gchar gchar* pref_dblog = prefs_get_string(PREF_DBLOG);
sqlite_int64 original_message_id = -1;
if (g_strcmp0(pref_dblog, "off") == 0) {
- return;
+ return TRUE;
} else if (g_strcmp0(pref_dblog, "redact") == 0) {
if (message->plain) {
free(message->plain);
@@ -449,7 +615,7 @@ _add_to_db(ProfMessage* message, char* type, const Jid* const from_jid, const Ji
if (!g_chatlog_database) {
log_debug("log_database_add() called but db is not initialized");
- return;
+ return TRUE;
}
auto_gchar gchar* date_fmt = prof_date_time_format_iso8601(message->timestamp);
@@ -466,14 +632,14 @@ _add_to_db(ProfMessage* message, char* type, const Jid* const from_jid, const Ji
if (!replace_check_query) {
log_error("Could not allocate memory for SQL replace query in log_database_add()");
- return;
+ return FALSE;
}
sqlite3_stmt* lmc_stmt = NULL;
if (SQLITE_OK != sqlite3_prepare_v2(g_chatlog_database, replace_check_query, -1, &lmc_stmt, NULL)) {
log_error("SQLite error in _add_to_db() on selecting original message: %s", sqlite3_errmsg(g_chatlog_database));
- return;
+ return FALSE;
}
if (sqlite3_step(lmc_stmt) == SQLITE_ROW) {
@@ -488,7 +654,7 @@ _add_to_db(ProfMessage* message, char* type, const Jid* const from_jid, const Ji
log_error("Mismatch in sender JIDs when trying to do LMC. Corrected message sender: %s. Original message sender: %s. Replace-ID: %s. Message: %s", from_jid->barejid, from_jid_orig, message->replace_id, message->plain);
cons_show_error("%s sent a message correction with mismatched sender. See log for details.", from_jid->barejid);
sqlite3_finalize(lmc_stmt);
- return;
+ return FALSE;
}
} else {
log_warning("Got LMC message that does not have original message counterpart in the database from %s", message->from_jid->fulljid);
@@ -522,7 +688,7 @@ _add_to_db(ProfMessage* message, char* type, const Jid* const from_jid, const Ji
enc);
if (!query) {
log_error("Could not allocate memory for SQL insert query in log_database_add()");
- return;
+ return FALSE;
}
log_debug("Writing to DB. Query: %s", query);
@@ -531,19 +697,23 @@ _add_to_db(ProfMessage* message, char* type, const Jid* const from_jid, const Ji
int rc = sqlite3_prepare_v2(g_chatlog_database, query, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
log_error("SQLite error in _add_to_db() (prepare): %s", sqlite3_errmsg(g_chatlog_database));
- return;
+ return FALSE;
}
+ gboolean is_new = TRUE;
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
log_debug("Successfully inserted message into database.");
} else if (rc == SQLITE_DONE) {
log_debug("Message already exists in database (archive_id: %s), skipping.", message->stanzaid);
+ is_new = FALSE;
} else {
log_error("SQLite error in _add_to_db() (step): %s", sqlite3_errmsg(g_chatlog_database));
+ is_new = FALSE;
}
sqlite3_finalize(stmt);
+ return is_new;
}
static int
diff --git a/src/database.h b/src/database.h
index dc5312d3f..129f25eab 100644
--- a/src/database.h
+++ b/src/database.h
@@ -17,12 +17,16 @@
#define MESSAGES_TO_RETRIEVE 10
gboolean log_database_init(ProfAccount* account);
-void log_database_add_incoming(ProfMessage* message);
+gboolean log_database_add_incoming(ProfMessage* message);
void log_database_add_outgoing_chat(const char* const id, const char* const barejid, const char* const message, const char* const replace_id, prof_enc_t enc);
void log_database_add_outgoing_muc(const char* const id, const char* const barejid, const char* const message, const char* const replace_id, prof_enc_t enc);
void log_database_add_outgoing_muc_pm(const char* const id, const char* const barejid, const char* const message, const char* const replace_id, prof_enc_t enc);
GSList* log_database_get_previous_chat(const gchar* const contact_barejid, const char* start_time, const char* end_time, gboolean from_start, gboolean flip);
+GSList* log_database_get_previous_muc(const gchar* const room_jid, const char* start_time, const char* end_time, gboolean from_start, gboolean flip);
+int log_database_get_chat_count(const gchar* const contact_barejid, const char* start_time, const char* end_time);
+int log_database_get_muc_count(const gchar* const room_jid, const char* start_time, const char* end_time);
ProfMessage* log_database_get_limits_info(const gchar* const contact_barejid, gboolean is_last);
+ProfMessage* log_database_get_limits_info_muc(const gchar* const room_jid, gboolean is_last);
void log_database_close(void);
#endif // DATABASE_H
diff --git a/src/event/server_events.c b/src/event/server_events.c
index 5c2b69740..8b05e8613 100644
--- a/src/event/server_events.c
+++ b/src/event/server_events.c
@@ -38,6 +38,8 @@
#include "xmpp/roster_list.h"
#include "xmpp/avatar.h"
#include "xmpp/vcard_funcs.h"
+#include "xmpp/bookmark.h"
+#include "xmpp/stanza.h"
#ifdef HAVE_LIBOTR
#include "otr/otr.h"
@@ -181,6 +183,11 @@ sv_ev_connection_features_received(void)
#ifdef HAVE_OMEMO
omemo_publish_crypto_materials();
#endif
+ bookmark_request();
+
+ if (prefs_get_boolean(PREF_CARBONS) && connection_supports(STANZA_NS_CARBONS)) {
+ iq_enable_carbons();
+ }
}
void
@@ -297,7 +304,7 @@ sv_ev_room_history(ProfMessage* message)
}
}
-static void
+static gboolean
_log_muc(ProfMessage* message)
{
if (message->enc == PROF_MSG_ENC_OMEMO) {
@@ -305,7 +312,7 @@ _log_muc(ProfMessage* message)
} else {
groupchat_log_msg_in(message->from_jid->barejid, message->from_jid->resourcepart, message->plain);
}
- log_database_add_incoming(message);
+ return log_database_add_incoming(message);
}
void
@@ -318,10 +325,14 @@ sv_ev_room_message(ProfMessage* message)
const char* const mynick = muc_nick(mucwin->roomjid);
+ gboolean is_duplicate = FALSE;
+
// only log message not coming from this client (but maybe same account, different client)
// our messages are logged when outgoing
if (!(g_strcmp0(mynick, message->from_jid->resourcepart) == 0 && message_is_sent_by_us(message, TRUE))) {
- _log_muc(message);
+ if (!_log_muc(message)) {
+ is_duplicate = TRUE;
+ }
}
char* old_plain = message->plain;
@@ -334,56 +345,73 @@ sv_ev_room_message(ProfMessage* message)
GList* triggers = prefs_message_get_triggers(message->plain);
_clean_incoming_message(message);
- mucwin_incoming_msg(mucwin, message, mentions, triggers, TRUE);
- g_slist_free(mentions);
+ if (is_duplicate) {
+ log_debug("Skipping duplicate room message display: JID=%s, message=%s",
+ message->from_jid->barejid, message->plain);
+ g_slist_free(mentions);
+ if (triggers) {
+ g_list_free_full(triggers, free);
+ }
+ message->plain = old_plain;
+ return;
+ }
- ProfWin* window = (ProfWin*)mucwin;
- int num = wins_get_num(window);
- gboolean is_current = FALSE;
+ if (message->is_mam) {
+ // For MAM, print to window but disable all notifications, unread count increment etc
+ // And don't filter reflection of our own sent messages (so they get displayed)
+ mucwin_incoming_msg(mucwin, message, mentions, triggers, FALSE);
+ } else {
+ mucwin_incoming_msg(mucwin, message, mentions, triggers, TRUE);
- // currently in groupchat window
- if (wins_is_current(window)) {
- is_current = TRUE;
- status_bar_active(num, WIN_MUC, mucwin->roomjid);
+ ProfWin* window = (ProfWin*)mucwin;
+ int num = wins_get_num(window);
+ gboolean is_current = FALSE;
- if ((g_strcmp0(mynick, message->from_jid->resourcepart) != 0) && (prefs_get_boolean(PREF_BEEP))) {
- ui_beep();
- }
+ // currently in groupchat window
+ if (wins_is_current(window)) {
+ is_current = TRUE;
+ status_bar_active(num, WIN_MUC, mucwin->roomjid);
- // not currently on groupchat window
- } else {
- status_bar_new(num, WIN_MUC, mucwin->roomjid);
+ if ((g_strcmp0(mynick, message->from_jid->resourcepart) != 0) && (prefs_get_boolean(PREF_BEEP))) {
+ ui_beep();
+ }
- if ((g_strcmp0(mynick, message->from_jid->resourcepart) != 0) && (prefs_get_boolean(PREF_FLASH))) {
- ui_flash();
- }
+ // not currently on groupchat window
+ } else {
+ status_bar_new(num, WIN_MUC, mucwin->roomjid);
- cons_show_incoming_room_message(message->from_jid->resourcepart, mucwin->roomjid, num, mention, triggers, mucwin->unread, window);
+ if ((g_strcmp0(mynick, message->from_jid->resourcepart) != 0) && (prefs_get_boolean(PREF_FLASH))) {
+ ui_flash();
+ }
- mucwin->unread++;
+ cons_show_incoming_room_message(message->from_jid->resourcepart, mucwin->roomjid, num, mention, triggers, mucwin->unread, window);
- if (mention) {
- mucwin->unread_mentions = TRUE;
- }
- if (triggers) {
- mucwin->unread_triggers = TRUE;
+ mucwin->unread++;
+
+ if (mention) {
+ mucwin->unread_mentions = TRUE;
+ }
+ if (triggers) {
+ mucwin->unread_triggers = TRUE;
+ }
}
- }
- // save timestamp of last received muc message
- if (mucwin->last_msg_timestamp) {
- g_date_time_unref(mucwin->last_msg_timestamp);
- }
- mucwin->last_msg_timestamp = g_date_time_new_now_local();
+ // save timestamp of last received muc message
+ if (mucwin->last_msg_timestamp) {
+ g_date_time_unref(mucwin->last_msg_timestamp);
+ }
+ mucwin->last_msg_timestamp = g_date_time_new_now_local();
- if (prefs_do_room_notify(is_current, mucwin->roomjid, mynick, message->from_jid->resourcepart, message->plain, mention, triggers != NULL)) {
- auto_jid Jid* jidp = jid_create(mucwin->roomjid);
- if (jidp) {
- notify_room_message(message->from_jid->resourcepart, jidp->localpart, num, message->plain);
+ if (prefs_do_room_notify(is_current, mucwin->roomjid, mynick, message->from_jid->resourcepart, message->plain, mention, triggers != NULL)) {
+ auto_jid Jid* jidp = jid_create(mucwin->roomjid);
+ if (jidp) {
+ notify_room_message(message->from_jid->resourcepart, jidp->localpart, num, message->plain);
+ }
}
}
+ g_slist_free(mentions);
if (triggers) {
g_list_free_full(triggers, free);
}
@@ -622,7 +650,7 @@ sv_ev_incoming_message(ProfMessage* message)
if (prefs_get_boolean(PREF_MAM)) {
win_print_loading_history(window);
- iq_mam_request(chatwin, g_date_time_ref(message->timestamp)); // copy timestamp
+ iq_mam_request((ProfWin*)chatwin, g_date_time_ref(message->timestamp)); // copy timestamp
}
#ifdef HAVE_OMEMO
diff --git a/src/ui/chatwin.c b/src/ui/chatwin.c
index 52c2915fc..11fa15310 100644
--- a/src/ui/chatwin.c
+++ b/src/ui/chatwin.c
@@ -121,7 +121,7 @@ chatwin_new(const char* const barejid)
}
if (prefs_get_boolean(PREF_MAM)) {
- iq_mam_request(chatwin, NULL);
+ iq_mam_request((ProfWin*)chatwin, NULL);
win_print_loading_history(window);
}
diff --git a/src/ui/mucwin.c b/src/ui/mucwin.c
index 6e308a769..f943d33b6 100644
--- a/src/ui/mucwin.c
+++ b/src/ui/mucwin.c
@@ -11,6 +11,7 @@
#include "config.h"
#include "ui.h"
+#include "database.h"
#include
#include
@@ -355,7 +356,7 @@ mucwin_history(ProfMucWin* mucwin, const ProfMessage* const message)
}
static void
-_mucwin_print_mention(ProfWin* window, const char* const message, const char* const from, const char* const mynick, GSList* mentions, const char* const ch, int flags)
+_mucwin_print_mention(ProfWin* window, const char* const message, const char* const from, const char* const mynick, GSList* mentions, const char* const ch, int flags, GDateTime* timestamp)
{
int last_pos = 0;
int pos;
@@ -368,19 +369,19 @@ _mucwin_print_mention(ProfWin* window, const char* const message, const char* co
auto_gchar gchar* before_str = g_utf8_substring(message, last_pos, pos);
if (last_pos == 0 && strncmp(before_str, "/me ", 4) == 0) {
- win_print_them(window, THEME_ROOMMENTION, ch, flags, "");
- win_append_highlight(window, THEME_ROOMMENTION, "*%s ", from);
- win_append_highlight(window, THEME_ROOMMENTION, "%s", before_str + 4);
+ win_print_them(window, THEME_ROOMMENTION, timestamp, ch, flags, "");
+ win_append_highlight(window, THEME_ROOMMENTION, timestamp, "*%s ", from);
+ win_append_highlight(window, THEME_ROOMMENTION, timestamp, "%s", before_str + 4);
} else {
// print time and nick only once at beginning of the line
if (last_pos == 0) {
- win_print_them(window, THEME_ROOMMENTION, ch, flags, from);
+ win_print_them(window, THEME_ROOMMENTION, timestamp, ch, flags, from);
}
- win_append_highlight(window, THEME_ROOMMENTION, "%s", before_str);
+ win_append_highlight(window, THEME_ROOMMENTION, timestamp, "%s", before_str);
}
auto_gchar gchar* mynick_str = g_utf8_substring(message, pos, pos + mynick_len);
- win_append_highlight(window, THEME_ROOMMENTION_TERM, "%s", mynick_str);
+ win_append_highlight(window, THEME_ROOMMENTION_TERM, timestamp, "%s", mynick_str);
last_pos = pos + mynick_len;
@@ -391,9 +392,9 @@ _mucwin_print_mention(ProfWin* window, const char* const message, const char* co
if (last_pos < message_len) {
// get tail without allocating a new string
gchar* rest = g_utf8_offset_to_pointer(message, last_pos);
- win_appendln_highlight(window, THEME_ROOMMENTION, "%s", rest);
+ win_appendln_highlight(window, THEME_ROOMMENTION, timestamp, "%s", rest);
} else {
- win_appendln_highlight(window, THEME_ROOMMENTION, "");
+ win_appendln_highlight(window, THEME_ROOMMENTION, timestamp, "");
}
}
@@ -412,7 +413,7 @@ _cmp_trigger_weight(gconstpointer a, gconstpointer b)
}
static void
-_mucwin_print_triggers(ProfWin* window, const char* const message, GList* triggers)
+_mucwin_print_triggers(ProfWin* window, const char* const message, GList* triggers, GDateTime* timestamp)
{
GList* weighted_triggers = NULL;
GList* curr = triggers;
@@ -451,7 +452,7 @@ _mucwin_print_triggers(ProfWin* window, const char* const message, GList* trigge
// no triggers found
if (first_trigger_pos == -1) {
- win_appendln_highlight(window, THEME_ROOMTRIGGER, "%s", message);
+ win_appendln_highlight(window, THEME_ROOMTRIGGER, timestamp, "%s", message);
} else {
if (first_trigger_pos > 0) {
char message_section[strlen(message) + 1];
@@ -461,7 +462,7 @@ _mucwin_print_triggers(ProfWin* window, const char* const message, GList* trigge
i++;
}
message_section[i] = '\0';
- win_append_highlight(window, THEME_ROOMTRIGGER, "%s", message_section);
+ win_append_highlight(window, THEME_ROOMTRIGGER, timestamp, "%s", message_section);
}
char trigger_section[first_trigger_len + 1];
int i = 0;
@@ -472,10 +473,10 @@ _mucwin_print_triggers(ProfWin* window, const char* const message, GList* trigge
trigger_section[i] = '\0';
if (first_trigger_pos + first_trigger_len < (int)strlen(message)) {
- win_append_highlight(window, THEME_ROOMTRIGGER_TERM, "%s", trigger_section);
- _mucwin_print_triggers(window, &message[first_trigger_pos + first_trigger_len], triggers);
+ win_append_highlight(window, THEME_ROOMTRIGGER_TERM, timestamp, "%s", trigger_section);
+ _mucwin_print_triggers(window, &message[first_trigger_pos + first_trigger_len], triggers, timestamp);
} else {
- win_appendln_highlight(window, THEME_ROOMTRIGGER_TERM, "%s", trigger_section);
+ win_appendln_highlight(window, THEME_ROOMTRIGGER_TERM, timestamp, "%s", trigger_section);
}
}
}
@@ -532,10 +533,10 @@ mucwin_incoming_msg(ProfMucWin* mucwin, const ProfMessage* const message, GSList
wins_add_quotes_ac(window, message->plain, FALSE);
if (g_slist_length(mentions) > 0) {
- _mucwin_print_mention(window, message->plain, message->from_jid->resourcepart, mynick, mentions, ch, flags);
+ _mucwin_print_mention(window, message->plain, message->from_jid->resourcepart, mynick, mentions, ch, flags, message->timestamp);
} else if (triggers) {
- win_print_them(window, THEME_ROOMTRIGGER, ch, flags, message->from_jid->resourcepart);
- _mucwin_print_triggers(window, message->plain, triggers);
+ win_print_them(window, THEME_ROOMTRIGGER, message->timestamp, ch, flags, message->from_jid->resourcepart);
+ _mucwin_print_triggers(window, message->plain, triggers, message->timestamp);
} else {
win_println_incoming_muc_msg(window, ch, flags, message);
}
@@ -988,3 +989,37 @@ mucwin_set_room_name(const gchar* const muc_jid, const gchar* const new_room_nam
}
return TRUE;
}
+
+gboolean
+mucwin_db_history(ProfMucWin* mucwin, const char* start_time, const char* end_time, gboolean flip)
+{
+ auto_gchar gchar* end_time_ = NULL;
+ if (!end_time && buffer_size(((ProfWin*)mucwin)->layout->buffer) != 0) {
+ end_time = end_time_ = g_date_time_format_iso8601(buffer_get_entry(((ProfWin*)mucwin)->layout->buffer, 0)->time);
+ }
+
+ GSList* history = log_database_get_previous_muc(mucwin->roomjid, start_time, end_time, !flip, flip);
+ gboolean has_items = g_slist_length(history) != 0;
+ GSList* curr = history;
+
+ while (curr) {
+ ProfMessage* msg = curr->data;
+ char* old_plain = msg->plain;
+ auto_char char* plugin_msg = plugins_pre_room_message_display(msg->from_jid->barejid, msg->from_jid->resourcepart, msg->plain);
+ if (plugin_msg)
+ msg->plain = plugin_msg;
+ if (flip) {
+ win_print_old_history((ProfWin*)mucwin, msg);
+ } else {
+ win_print_history((ProfWin*)mucwin, msg);
+ }
+ plugins_post_room_message_display(msg->from_jid->barejid, msg->from_jid->resourcepart, msg->plain);
+ msg->plain = old_plain;
+ curr = g_slist_next(curr);
+ }
+
+ g_slist_free_full(history, (GDestroyNotify)message_free);
+ win_redraw((ProfWin*)mucwin);
+
+ return has_items;
+}
diff --git a/src/ui/titlebar.c b/src/ui/titlebar.c
index 07a5d2be4..d9b7ed7d2 100644
--- a/src/ui/titlebar.c
+++ b/src/ui/titlebar.c
@@ -243,6 +243,7 @@ _show_scrolled(ProfWin* current)
int bracket_attrs = theme_attrs(THEME_TITLE_BRACKET);
int scrolled_attrs = theme_attrs(THEME_TITLE_SCROLLED);
+ wprintw(win, " ");
wattron(win, bracket_attrs);
wprintw(win, "[");
wattroff(win, bracket_attrs);
diff --git a/src/ui/ui.h b/src/ui/ui.h
index 1332f18db..0c291f39b 100644
--- a/src/ui/ui.h
+++ b/src/ui/ui.h
@@ -126,6 +126,7 @@ void chatwin_unset_incoming_char(ProfChatWin* chatwin);
void chatwin_set_outgoing_char(ProfChatWin* chatwin, const char* const ch);
void chatwin_unset_outgoing_char(ProfChatWin* chatwin);
gboolean chatwin_db_history(ProfChatWin* chatwin, const char* start_time, const char* end_time, gboolean flip);
+gboolean mucwin_db_history(ProfMucWin* mucwin, const char* start_time, const char* end_time, gboolean flip);
// MUC window
ProfMucWin* mucwin_new(const char* const barejid);
@@ -382,8 +383,8 @@ void win_println_va(ProfWin* window, theme_item_t theme_item, const char* show_c
void win_append(ProfWin* window, theme_item_t theme_item, const char* const message, ...);
void win_appendln(ProfWin* window, theme_item_t theme_item, const char* const message, ...);
-void win_append_highlight(ProfWin* window, theme_item_t theme_item, const char* const message, ...);
-void win_appendln_highlight(ProfWin* window, theme_item_t theme_item, const char* const message, ...);
+void win_append_highlight(ProfWin* window, theme_item_t theme_item, GDateTime* timestamp, const char* const message, ...);
+void win_appendln_highlight(ProfWin* window, theme_item_t theme_item, GDateTime* timestamp, const char* const message, ...);
gchar* win_get_title(ProfWin* window);
void win_show_occupant(ProfWin* window, Occupant* occupant);
diff --git a/src/ui/window.c b/src/ui/window.c
index 2ee55176d..a5135f978 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -65,6 +65,7 @@ _win_ensure_pad_capacity(ProfWin* window, WINDOW* win, int lines_needed)
}
}
static const char* LOADING_MESSAGE = "Loading older messages…";
+static const char* END_OF_ARCHIVE_MESSAGE = "End of archive reached";
static const char* CONS_WIN_TITLE = "Profanity. Type /help for help information.";
static const char* XML_WIN_TITLE = "XML Console";
@@ -688,19 +689,23 @@ win_page_up(ProfWin* window, int scroll_size)
*scroll_state = (*scroll_state == WIN_SCROLL_REACHED_BOTTOM) ? WIN_SCROLL_INNER : *scroll_state;
*page_start -= scroll_size;
- if (*page_start == -scroll_size && window->type == WIN_CHAT) {
- ProfChatWin* chatwin = (ProfChatWin*)window;
+ if (*page_start == -scroll_size && (window->type == WIN_CHAT || window->type == WIN_MUC)) {
ProfBuffEntry* first_entry = buffer_size(window->layout->buffer) != 0 ? buffer_get_entry(window->layout->buffer, 0) : NULL;
- // Don't do anything if still fetching mam messages
- if (first_entry && !(first_entry->theme_item == THEME_ROOMINFO && g_strcmp0(first_entry->message, LOADING_MESSAGE) == 0)) {
+ // Don't do anything if still fetching mam messages or if end of archive is reached
+ if (first_entry && !(first_entry->theme_item == THEME_ROOMINFO && g_strcmp0(first_entry->message, LOADING_MESSAGE) == 0)
+ && !(first_entry->theme_item == THEME_ROOMINFO && g_strcmp0(first_entry->message, END_OF_ARCHIVE_MESSAGE) == 0)) {
if (*scroll_state != WIN_SCROLL_REACHED_TOP) {
- *scroll_state = !chatwin_db_history(chatwin, NULL, NULL, TRUE) ? WIN_SCROLL_REACHED_TOP : WIN_SCROLL_INNER;
+ if (window->type == WIN_MUC) {
+ *scroll_state = !mucwin_db_history((ProfMucWin*)window, NULL, NULL, TRUE) ? WIN_SCROLL_REACHED_TOP : WIN_SCROLL_INNER;
+ } else {
+ *scroll_state = !chatwin_db_history((ProfChatWin*)window, NULL, NULL, TRUE) ? WIN_SCROLL_REACHED_TOP : WIN_SCROLL_INNER;
+ }
}
if (*scroll_state == WIN_SCROLL_REACHED_TOP && prefs_get_boolean(PREF_MAM)) {
win_print_loading_history(window);
- iq_mam_request_older(chatwin);
+ iq_mam_request_older(window);
}
unsigned int buff_size = buffer_size(window->layout->buffer);
@@ -1490,9 +1495,9 @@ win_print_incoming(ProfWin* window, const char* const display_name_from, ProfMes
}
void
-win_print_them(ProfWin* window, theme_item_t theme_item, const char* const show_char, int flags, const char* const them)
+win_print_them(ProfWin* window, theme_item_t theme_item, GDateTime* timestamp, const char* const show_char, int flags, const char* const them)
{
- _win_printf(window, show_char, 0, NULL, flags | NO_ME | NO_EOL, theme_item, them, NULL, NULL, "");
+ _win_printf(window, show_char, 0, timestamp, flags | NO_ME | NO_EOL, theme_item, them, NULL, NULL, "");
}
void
@@ -1573,9 +1578,13 @@ win_print_old_history(ProfWin* window, const ProfMessage* const message)
}
static void
-win_println_va_internal(ProfWin* window, theme_item_t theme_item, int pad, int flags, const char* show_char, const char* const message, va_list arg)
+win_println_va_internal_with_timestamp(ProfWin* window, theme_item_t theme_item, int pad, int flags, const char* show_char, GDateTime* timestamp, const char* const message, va_list arg)
{
- GDateTime* timestamp = g_date_time_new_now_local();
+ gboolean created_timestamp = FALSE;
+ if (!timestamp) {
+ timestamp = g_date_time_new_now_local();
+ created_timestamp = TRUE;
+ }
auto_gchar gchar* msg = g_strdup_vprintf(message, arg);
@@ -1584,7 +1593,15 @@ win_println_va_internal(ProfWin* window, theme_item_t theme_item, int pad, int f
buffer_append(window->layout->buffer, show_char, pad, timestamp, flags, theme_item, "", NULL, msg, NULL, NULL, y_start_pos, getcury(window->layout->win));
inp_nonblocking(TRUE);
- g_date_time_unref(timestamp);
+ if (created_timestamp) {
+ g_date_time_unref(timestamp);
+ }
+}
+
+static void
+win_println_va_internal(ProfWin* window, theme_item_t theme_item, int pad, int flags, const char* show_char, const char* const message, va_list arg)
+{
+ win_println_va_internal_with_timestamp(window, theme_item, pad, flags, show_char, NULL, message, arg);
}
void
@@ -1639,20 +1656,20 @@ win_appendln(ProfWin* window, theme_item_t theme_item, const char* const message
}
void
-win_append_highlight(ProfWin* window, theme_item_t theme_item, const char* const message, ...)
+win_append_highlight(ProfWin* window, theme_item_t theme_item, GDateTime* timestamp, const char* const message, ...)
{
va_list arg;
va_start(arg, message);
- win_println_va_internal(window, theme_item, 0, NO_DATE | NO_ME | NO_EOL, "-", message, arg);
+ win_println_va_internal_with_timestamp(window, theme_item, 0, NO_DATE | NO_ME | NO_EOL, "-", timestamp, message, arg);
va_end(arg);
}
void
-win_appendln_highlight(ProfWin* window, theme_item_t theme_item, const char* const message, ...)
+win_appendln_highlight(ProfWin* window, theme_item_t theme_item, GDateTime* timestamp, const char* const message, ...)
{
va_list arg;
va_start(arg, message);
- win_println_va_internal(window, theme_item, 0, NO_DATE | NO_ME, "-", message, arg);
+ win_println_va_internal_with_timestamp(window, theme_item, 0, NO_DATE | NO_ME, "-", timestamp, message, arg);
va_end(arg);
}
@@ -2095,6 +2112,41 @@ win_print_loading_history(ProfWin* window)
win_redraw(window);
}
+void
+win_print_end_of_archive(ProfWin* window)
+{
+ GDateTime* timestamp;
+ gboolean is_buffer_empty = buffer_size(window->layout->buffer) == 0;
+
+ // Reset scroll position and exit paged state to make the banner visible!
+ window->layout->y_pos = 0;
+ window->layout->paged = 0;
+
+ if (!is_buffer_empty) {
+ timestamp = buffer_get_entry(window->layout->buffer, 0)->time;
+ } else {
+ timestamp = g_date_time_new_now_local();
+ }
+
+ if (!is_buffer_empty) {
+ ProfBuffEntry* first_entry = buffer_get_entry(window->layout->buffer, 0);
+ if (first_entry->theme_item == THEME_ROOMINFO && g_strcmp0(first_entry->message, END_OF_ARCHIVE_MESSAGE) == 0) {
+ if (is_buffer_empty)
+ g_date_time_unref(timestamp);
+ win_redraw(window);
+ return;
+ }
+ }
+
+ int cur_y = getcury(window->layout->win);
+ buffer_prepend(window->layout->buffer, "-", 0, timestamp, NO_DATE, THEME_ROOMINFO, NULL, NULL, END_OF_ARCHIVE_MESSAGE, NULL, NULL, cur_y, cur_y + 1);
+
+ if (is_buffer_empty)
+ g_date_time_unref(timestamp);
+
+ win_redraw(window);
+}
+
gboolean
win_has_active_subwin(ProfWin* window)
{
diff --git a/src/ui/window.h b/src/ui/window.h
index 556655118..3d7da5cc0 100644
--- a/src/ui/window.h
+++ b/src/ui/window.h
@@ -36,7 +36,7 @@ void win_show_status_string(ProfWin* window, const char* const from,
GDateTime* last_activity, const char* const pre,
const char* const default_show);
-void win_print_them(ProfWin* window, theme_item_t theme_item, const char* const show_char, int flags, const char* const them);
+void win_print_them(ProfWin* window, theme_item_t theme_item, GDateTime* timestamp, const char* const show_char, int flags, const char* const them);
void win_print_incoming(ProfWin* window, const char* const from, ProfMessage* message);
void win_print_outgoing(ProfWin* window, const char* show_char, const char* const id, const char* const replace_id, const char* const message);
void win_print_outgoing_with_receipt(ProfWin* window, const char* show_char, const char* const from, const char* const message, const char* id, const char* const replace_id);
@@ -50,6 +50,7 @@ void win_print_http_transfer(ProfWin* window, const char* const message, char* i
void win_newline(ProfWin* window);
void win_redraw(ProfWin* window);
void win_print_loading_history(ProfWin* window);
+void win_print_end_of_archive(ProfWin* window);
int win_roster_cols(void);
int win_occpuants_cols(void);
void win_sub_print(WINDOW* win, char* msg, gboolean newline, gboolean wrap, int indent);
diff --git a/src/ui/window_list.c b/src/ui/window_list.c
index 4f73bf9b6..0fce68004 100644
--- a/src/ui/window_list.c
+++ b/src/ui/window_list.c
@@ -847,6 +847,10 @@ wins_reestablished_connection(void)
}
#endif
+ if (prefs_get_boolean(PREF_MAM) && (window->type == WIN_CHAT || window->type == WIN_MUC)) {
+ iq_mam_request(window, NULL);
+ }
+
// if current win, set current_win_dirty
if (wins_is_current(window)) {
win_update_virtual(window);
diff --git a/src/xmpp/iq.c b/src/xmpp/iq.c
index dc13af3e7..05a9eb855 100644
--- a/src/xmpp/iq.c
+++ b/src/xmpp/iq.c
@@ -80,7 +80,7 @@ typedef struct mam_rsm_userdata
char* start_datestr;
char* end_datestr;
gboolean fetch_next;
- ProfChatWin* win;
+ ProfWin* win;
} MamRsmUserdata;
typedef struct late_delivery_userdata
@@ -127,7 +127,7 @@ static int _command_exec_response_handler(xmpp_stanza_t* const stanza, void* con
static int _mam_rsm_id_handler(xmpp_stanza_t* const stanza, void* const userdata);
static int _register_change_password_result_id_handler(xmpp_stanza_t* const stanza, void* const userdata);
-static void _iq_mam_request(ProfChatWin* win, GDateTime* startdate, GDateTime* enddate);
+static void _iq_mam_request(ProfWin* win, GDateTime* startdate, GDateTime* enddate);
static void _iq_free_room_data(ProfRoomInfoData* roominfo);
static void _iq_free_affiliation_set(ProfPrivilegeSet* affiliation_set);
static void _iq_free_affiliation_list(ProfAffiliationList* affiliation_list);
@@ -2341,6 +2341,10 @@ _room_info_response_id_handler(xmpp_stanza_t* const stanza, void* const userdata
mucwin->is_omemo = TRUE;
}
#endif
+ if (prefs_get_boolean(PREF_MAM) && muc_supports_mam(cb_data->room)) {
+ win_print_loading_history((ProfWin*)mucwin);
+ iq_mam_request((ProfWin*)mucwin, NULL);
+ }
if (cb_data->display) {
mucwin_room_disco_info(mucwin, identities, features);
}
@@ -2657,7 +2661,7 @@ _disco_items_result_handler(xmpp_stanza_t* const stanza)
while (late_delivery_windows) {
LateDeliveryUserdata* del_data = late_delivery_windows->data;
- _iq_mam_request(del_data->win, del_data->startdate, del_data->enddate);
+ _iq_mam_request((ProfWin*)del_data->win, del_data->startdate, del_data->enddate);
free(del_data);
late_delivery_windows = g_slist_delete_link(late_delivery_windows,
late_delivery_windows);
@@ -2721,17 +2725,38 @@ _iq_free_affiliation_list(ProfAffiliationList* affiliation_list)
static int
_mam_buffer_commit_handler(xmpp_stanza_t* const stanza, void* const userdata)
{
- ProfChatWin* chatwin = (ProfChatWin*)userdata;
+ ProfWin* window = (ProfWin*)userdata;
+ if (wins_get_num(window) == -1) {
+ log_error("Window %p should not get any events anymore", window);
+ return 0;
+ }
+
// Remove the "Loading messages…" message
- buffer_remove_entry(((ProfWin*)chatwin)->layout->buffer, 0);
- chatwin_db_history(chatwin, NULL, NULL, TRUE);
+ buffer_remove_entry(window->layout->buffer, 0);
+
+ xmpp_stanza_t* fin = xmpp_stanza_get_child_by_name_and_ns(stanza, STANZA_NAME_FIN, STANZA_NS_MAM2);
+ gboolean is_complete = FALSE;
+ if (fin) {
+ is_complete = g_strcmp0(xmpp_stanza_get_attribute(fin, "complete"), "true") == 0;
+ }
+
+ if (window->type == WIN_MUC) {
+ mucwin_db_history((ProfMucWin*)window, NULL, NULL, TRUE);
+ } else {
+ chatwin_db_history((ProfChatWin*)window, NULL, NULL, TRUE);
+ }
+
+ if (is_complete) {
+ win_print_end_of_archive(window);
+ }
+
return 0;
}
static const gchar* mam_timestamp_format_string = "%FT%T.%f%:z";
void
-iq_mam_request_older(ProfChatWin* win)
+iq_mam_request_older(ProfWin* win)
{
if (connection_supports(XMPP_FEATURE_MAM2) == FALSE) {
log_warning("Server doesn't advertise %s feature.", XMPP_FEATURE_MAM2);
@@ -2739,13 +2764,20 @@ iq_mam_request_older(ProfChatWin* win)
return;
}
- ProfMessage* first_msg = log_database_get_limits_info(win->barejid, FALSE);
+ const char* target_jid = (win->type == WIN_MUC) ? ((ProfMucWin*)win)->roomjid : ((ProfChatWin*)win)->barejid;
+
+ ProfMessage* first_msg;
+ if (win->type == WIN_MUC) {
+ first_msg = log_database_get_limits_info_muc(target_jid, FALSE);
+ } else {
+ first_msg = log_database_get_limits_info(target_jid, FALSE);
+ }
char* firstid = NULL;
auto_gchar gchar* enddate = NULL;
// If first message found
if (first_msg && first_msg->timestamp) {
- firstid = first_msg->stanzaid;
+ firstid = first_msg->stanzaid ? first_msg->stanzaid : "";
enddate = g_date_time_format(first_msg->timestamp, mam_timestamp_format_string);
} else {
if (first_msg) {
@@ -2755,7 +2787,12 @@ iq_mam_request_older(ProfChatWin* win)
}
xmpp_ctx_t* const ctx = connection_get_ctx();
- xmpp_stanza_t* iq = stanza_create_mam_iq(ctx, win->barejid, NULL, enddate, firstid, NULL);
+ xmpp_stanza_t* iq;
+ if (win->type == WIN_MUC) {
+ iq = stanza_create_muc_mam_iq(ctx, target_jid, NULL, enddate, firstid, NULL);
+ } else {
+ iq = stanza_create_mam_iq(ctx, target_jid, NULL, enddate, firstid, NULL);
+ }
iq_id_handler_add(xmpp_stanza_get_id(iq), _mam_buffer_commit_handler, NULL, win);
message_free(first_msg);
@@ -2778,8 +2815,111 @@ _mam_userdata_free(MamRsmUserdata* data)
free(data);
}
+typedef struct mam_verify_userdata
+{
+ char* barejid;
+ char* start_datestr;
+ char* end_datestr;
+ gboolean is_muc;
+ ProfWin* win;
+} MamVerifyUserdata;
+
+static void
+_mam_verify_userdata_free(MamVerifyUserdata* data)
+{
+ free(data->start_datestr);
+ free(data->end_datestr);
+ free(data->barejid);
+ free(data);
+}
+
+static int
+_mam_verify_id_handler(xmpp_stanza_t* const stanza, void* const userdata)
+{
+ MamVerifyUserdata* data = (MamVerifyUserdata*)userdata;
+ ProfWin* window = data->win;
+ if (wins_get_num(window) == -1) {
+ log_error("Window %p is not active anymore", window);
+ return 0;
+ }
+
+ const char* type = xmpp_stanza_get_type(stanza);
+ if (g_strcmp0(type, "error") == 0) {
+ auto_char char* error_message = stanza_get_error_message(stanza);
+ cons_show_error("MAM verification failed: %s", error_message);
+ return 0;
+ }
+
+ xmpp_stanza_t* fin = xmpp_stanza_get_child_by_name_and_ns(stanza, STANZA_NAME_FIN, STANZA_NS_MAM2);
+ if (fin) {
+ xmpp_stanza_t* set = xmpp_stanza_get_child_by_name_and_ns(fin, STANZA_TYPE_SET, STANZA_NS_RSM);
+ if (set) {
+ xmpp_stanza_t* count_st = xmpp_stanza_get_child_by_name(set, "count");
+ if (count_st) {
+ char* count_str = xmpp_stanza_get_text(count_st);
+ int server_count = count_str ? atoi(count_str) : 0;
+
+ int local_count = 0;
+ if (data->is_muc) {
+ local_count = log_database_get_muc_count(data->barejid, data->start_datestr, data->end_datestr);
+ } else {
+ local_count = log_database_get_chat_count(data->barejid, data->start_datestr, data->end_datestr);
+ }
+
+ if (local_count >= server_count) {
+ cons_show("MAM Verification for %s: History is contiguous and complete (verified %d messages).",
+ data->barejid, local_count);
+ } else {
+ int missing = server_count - local_count;
+ cons_show("MAM Verification for %s: Missing %d messages. Initiating auto-repair...",
+ data->barejid, missing);
+
+ GDateTime* start = g_date_time_new_from_iso8601(data->start_datestr, NULL);
+ GDateTime* end = g_date_time_new_from_iso8601(data->end_datestr, NULL);
+ if (start && end) {
+ win_print_loading_history(window);
+ _iq_mam_request(window, start, end);
+ } else {
+ if (start)
+ g_date_time_unref(start);
+ if (end)
+ g_date_time_unref(end);
+ }
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
void
-_iq_mam_request(ProfChatWin* win, GDateTime* startdate, GDateTime* enddate)
+iq_mam_verify_request(ProfWin* win, const char* const startdate, const char* const enddate)
+{
+ gboolean is_muc = (win->type == WIN_MUC);
+ char* barejid = is_muc ? strdup(((ProfMucWin*)win)->roomjid) : strdup(((ProfChatWin*)win)->barejid);
+
+ xmpp_ctx_t* const ctx = connection_get_ctx();
+ xmpp_stanza_t* iq = stanza_create_mam_count_iq(ctx, barejid, startdate, enddate, is_muc);
+
+ MamVerifyUserdata* data = g_new0(MamVerifyUserdata, 1);
+ if (data) {
+ data->barejid = strdup(barejid);
+ data->start_datestr = strdup(startdate);
+ data->end_datestr = strdup(enddate);
+ data->is_muc = is_muc;
+ data->win = win;
+
+ iq_id_handler_add(xmpp_stanza_get_id(iq), _mam_verify_id_handler, (ProfIqFreeCallback)_mam_verify_userdata_free, data);
+ }
+
+ iq_send_stanza(iq);
+ free(barejid);
+ xmpp_stanza_release(iq);
+}
+
+void
+_iq_mam_request(ProfWin* win, GDateTime* startdate, GDateTime* enddate)
{
if (connection_supports(XMPP_FEATURE_MAM2) == FALSE) {
log_warning("Server doesn't advertise %s feature.", XMPP_FEATURE_MAM2);
@@ -2791,6 +2931,8 @@ _iq_mam_request(ProfChatWin* win, GDateTime* startdate, GDateTime* enddate)
return;
}
+ const char* target_jid = (win->type == WIN_MUC) ? ((ProfMucWin*)win)->roomjid : ((ProfChatWin*)win)->barejid;
+
char* firstid = "";
char* startdate_str = NULL;
char* enddate_str = NULL;
@@ -2813,13 +2955,18 @@ _iq_mam_request(ProfChatWin* win, GDateTime* startdate, GDateTime* enddate)
xmpp_ctx_t* const ctx = connection_get_ctx();
- xmpp_stanza_t* iq = stanza_create_mam_iq(ctx, win->barejid, startdate_str, enddate_str, firstid, NULL);
+ xmpp_stanza_t* iq;
+ if (win->type == WIN_MUC) {
+ iq = stanza_create_muc_mam_iq(ctx, target_jid, startdate_str, enddate_str, firstid, NULL);
+ } else {
+ iq = stanza_create_mam_iq(ctx, target_jid, startdate_str, enddate_str, firstid, NULL);
+ }
MamRsmUserdata* data = g_new0(MamRsmUserdata, 1);
if (data) {
data->start_datestr = startdate_str;
data->end_datestr = enddate_str;
- data->barejid = strdup(win->barejid);
+ data->barejid = strdup(target_jid);
data->fetch_next = fetch_next;
data->win = win;
@@ -2833,9 +2980,16 @@ _iq_mam_request(ProfChatWin* win, GDateTime* startdate, GDateTime* enddate)
}
void
-iq_mam_request(ProfChatWin* win, GDateTime* enddate)
+iq_mam_request(ProfWin* win, GDateTime* enddate)
{
- ProfMessage* last_msg = log_database_get_limits_info(win->barejid, TRUE);
+ const char* target_jid = (win->type == WIN_MUC) ? ((ProfMucWin*)win)->roomjid : ((ProfChatWin*)win)->barejid;
+
+ ProfMessage* last_msg;
+ if (win->type == WIN_MUC) {
+ last_msg = log_database_get_limits_info_muc(target_jid, TRUE);
+ } else {
+ last_msg = log_database_get_limits_info(target_jid, TRUE);
+ }
GDateTime* startdate = NULL;
if (last_msg) {
if (last_msg->timestamp)
@@ -2846,11 +3000,11 @@ iq_mam_request(ProfChatWin* win, GDateTime* enddate)
// Save request for later if disco items haven't been received yet
if (!received_disco_items) {
LateDeliveryUserdata* cur_del_data = g_new0(LateDeliveryUserdata, 1);
- cur_del_data->win = win;
+ cur_del_data->win = (ProfChatWin*)win;
cur_del_data->enddate = enddate;
cur_del_data->startdate = startdate;
late_delivery_windows = g_slist_append(late_delivery_windows, cur_del_data);
- log_debug("Save MAM request of %s for later", win->barejid);
+ log_debug("Save MAM request of %s for later", target_jid);
return;
}
@@ -2898,11 +3052,19 @@ _mam_rsm_id_handler(xmpp_stanza_t* const stanza, void* const userdata)
}
if (is_complete || !data->fetch_next) {
- chatwin_db_history(data->win, is_complete ? NULL : start_str, end_str, TRUE);
+ if (window->type == WIN_MUC) {
+ mucwin_db_history((ProfMucWin*)window, is_complete ? NULL : start_str, end_str, TRUE);
+ } else {
+ chatwin_db_history((ProfChatWin*)window, is_complete ? NULL : start_str, end_str, TRUE);
+ }
return 0;
}
- chatwin_db_history(data->win, start_str, end_str, TRUE);
+ if (window->type == WIN_MUC) {
+ mucwin_db_history((ProfMucWin*)window, start_str, end_str, TRUE);
+ } else {
+ chatwin_db_history((ProfChatWin*)window, start_str, end_str, TRUE);
+ }
xmpp_stanza_t* set = xmpp_stanza_get_child_by_name_and_ns(fin, STANZA_TYPE_SET, STANZA_NS_RSM);
if (set) {
@@ -2920,7 +3082,12 @@ _mam_rsm_id_handler(xmpp_stanza_t* const stanza, void* const userdata)
free(data->end_datestr);
data->end_datestr = NULL;
}
- xmpp_stanza_t* iq = stanza_create_mam_iq(ctx, data->barejid, data->start_datestr, NULL, firstid, NULL);
+ xmpp_stanza_t* iq;
+ if (window->type == WIN_MUC) {
+ iq = stanza_create_muc_mam_iq(ctx, data->barejid, data->start_datestr, NULL, firstid, NULL);
+ } else {
+ iq = stanza_create_mam_iq(ctx, data->barejid, data->start_datestr, NULL, firstid, NULL);
+ }
MamRsmUserdata* ndata = g_new0(MamRsmUserdata, 1);
*ndata = *data;
diff --git a/src/xmpp/message.c b/src/xmpp/message.c
index 232457905..83465e839 100644
--- a/src/xmpp/message.c
+++ b/src/xmpp/message.c
@@ -50,7 +50,7 @@ typedef struct p_message_handle_t
static int _message_handler(xmpp_conn_t* const conn, xmpp_stanza_t* const stanza, void* const userdata);
static void _handle_error(xmpp_stanza_t* const stanza);
-static void _handle_groupchat(xmpp_stanza_t* const stanza);
+static void _handle_groupchat(xmpp_stanza_t* const stanza, gboolean is_mam, const char* result_id, GDateTime* timestamp);
static void _handle_muc_user(xmpp_stanza_t* const stanza);
static void _handle_muc_private_message(xmpp_stanza_t* const stanza);
static void _handle_conference(xmpp_stanza_t* const stanza);
@@ -75,6 +75,18 @@ static GHashTable* pubsub_event_handlers;
gchar*
get_display_name(const ProfMessage* const message, int* flags)
{
+ if (message->type == PROF_MSG_TYPE_MUC) {
+ const char* const mynick = muc_nick(message->from_jid->barejid);
+ gboolean is_me = (g_strcmp0(mynick, message->from_jid->resourcepart) == 0);
+ if (is_me) {
+ return prefs_get_string(PREF_OUTGOING_STAMP);
+ } else {
+ if (flags)
+ *flags = NO_ME;
+ return g_strdup(message->from_jid->resourcepart);
+ }
+ }
+
if (equals_our_barejid(message->from_jid->barejid)) {
return prefs_get_string(PREF_OUTGOING_STAMP);
} else {
@@ -152,7 +164,7 @@ _message_handler(xmpp_conn_t* const conn, xmpp_stanza_t* const stanza, void* con
_handle_error(stanza);
} else if (type && g_strcmp0(type, STANZA_TYPE_GROUPCHAT) == 0) {
// XEP-0045: Multi-User Chat
- _handle_groupchat(stanza);
+ _handle_groupchat(stanza, FALSE, NULL, NULL);
} else if (type && g_strcmp0(type, STANZA_TYPE_HEADLINE) == 0) {
xmpp_stanza_t* event = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_PUBSUB_EVENT);
@@ -984,7 +996,7 @@ _room_config_handler(xmpp_stanza_t* const stanza, void* const userdata)
}
static void
-_handle_groupchat(xmpp_stanza_t* const stanza)
+_handle_groupchat(xmpp_stanza_t* const stanza, gboolean is_mam, const char* result_id, GDateTime* timestamp)
{
xmpp_ctx_t* ctx = connection_get_ctx();
@@ -1057,6 +1069,7 @@ _handle_groupchat(xmpp_stanza_t* const stanza)
}
ProfMessage* message = message_init();
+ message->is_mam = is_mam;
jid_ref(from_jid);
message->from_jid = from_jid;
message->type = PROF_MSG_TYPE_MUC;
@@ -1066,14 +1079,22 @@ _handle_groupchat(xmpp_stanza_t* const stanza)
message->id = strdup(id);
}
- char* stanzaid = NULL;
- xmpp_stanza_t* stanzaidst = xmpp_stanza_get_child_by_name_and_ns(stanza, STANZA_NAME_STANZA_ID, STANZA_NS_STABLE_ID);
- if (stanzaidst) {
- const char* by = xmpp_stanza_get_attribute(stanzaidst, "by");
- if (by && (g_strcmp0(by, from_jid->barejid) == 0)) {
- stanzaid = (char*)xmpp_stanza_get_attribute(stanzaidst, STANZA_ATTR_ID);
- if (stanzaid) {
- message->stanzaid = strdup(stanzaid);
+ if (is_mam) {
+ if (result_id) {
+ message->stanzaid = strdup(result_id);
+ } else {
+ log_warning("MAM received with no result id");
+ }
+ } else {
+ char* stanzaid = NULL;
+ xmpp_stanza_t* stanzaidst = xmpp_stanza_get_child_by_name_and_ns(stanza, STANZA_NAME_STANZA_ID, STANZA_NS_STABLE_ID);
+ if (stanzaidst) {
+ const char* by = xmpp_stanza_get_attribute(stanzaidst, "by");
+ if (by && (g_strcmp0(by, from_jid->barejid) == 0)) {
+ stanzaid = (char*)xmpp_stanza_get_attribute(stanzaidst, STANZA_ATTR_ID);
+ if (stanzaid) {
+ message->stanzaid = strdup(stanzaid);
+ }
}
}
}
@@ -1108,42 +1129,51 @@ _handle_groupchat(xmpp_stanza_t* const stanza)
message->plain = strdup(message->body);
}
- // determine if the notifications happened whilst offline (MUC history)
- message->timestamp = stanza_get_delay_from(stanza, from_jid->barejid);
- if (message->timestamp == NULL) {
- // checking the domainpart is a workaround for some prosody versions (gh#1190)
- message->timestamp = stanza_get_delay_from(stanza, from_jid->domainpart);
- }
+ if (is_mam) {
+ if (timestamp) {
+ message->timestamp = timestamp;
+ } else {
+ message->timestamp = g_date_time_new_now_local();
+ }
+ sv_ev_room_message(message);
+ } else {
+ // determine if the notifications happened whilst offline (MUC history)
+ message->timestamp = stanza_get_delay_from(stanza, from_jid->barejid);
+ if (message->timestamp == NULL) {
+ // checking the domainpart is a workaround for some prosody versions (gh#1190)
+ message->timestamp = stanza_get_delay_from(stanza, from_jid->domainpart);
+ }
- bool is_muc_history = FALSE;
- if (message->timestamp != NULL) {
- is_muc_history = TRUE;
- g_date_time_unref(message->timestamp);
- message->timestamp = NULL;
- }
+ bool is_muc_history = FALSE;
+ if (message->timestamp != NULL) {
+ is_muc_history = TRUE;
+ g_date_time_unref(message->timestamp);
+ message->timestamp = NULL;
+ }
- // we want to display the oldest delay
- message->timestamp = stanza_get_oldest_delay(stanza);
+ // we want to display the oldest delay
+ message->timestamp = stanza_get_oldest_delay(stanza);
- // now this has nothing to do with MUC history
- // it's just setting the time to the received time so upon displaying we can use this time
- // for example in win_println_incoming_muc_msg()
- if (!message->timestamp) {
- message->timestamp = g_date_time_new_now_local();
- }
+ // now this has nothing to do with MUC history
+ // it's just setting the time to the received time so upon displaying we can use this time
+ // for example in win_println_incoming_muc_msg()
+ if (!message->timestamp) {
+ message->timestamp = g_date_time_new_now_local();
+ }
- if (is_muc_history) {
- sv_ev_room_history(message);
- } else {
- // XEP-0308 states: `corrections must not be allowed (by the receiver) for messages received before the sender joined the room`
- xmpp_stanza_t* replace_id_stanza = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_LAST_MESSAGE_CORRECTION);
- if (replace_id_stanza) {
- const char* replace_id = xmpp_stanza_get_id(replace_id_stanza);
- if (replace_id) {
- message->replace_id = strdup(replace_id);
+ if (is_muc_history) {
+ sv_ev_room_history(message);
+ } else {
+ // XEP-0308 states: `corrections must not be allowed (by the receiver) for messages received before the sender joined the room`
+ xmpp_stanza_t* replace_id_stanza = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_LAST_MESSAGE_CORRECTION);
+ if (replace_id_stanza) {
+ const char* replace_id = xmpp_stanza_get_id(replace_id_stanza);
+ if (replace_id) {
+ message->replace_id = strdup(replace_id);
+ }
}
+ sv_ev_room_message(message);
}
- sv_ev_room_message(message);
}
out:
@@ -1542,8 +1572,8 @@ _handle_mam(xmpp_stanza_t* const stanza)
return TRUE;
}
} else {
- if (from && !equals_our_barejid(from)) {
- log_warning("MAM result from %s with no 'by' attribute (expected our own JID)", from);
+ if (from && !equals_our_barejid(from) && !muc_active(from)) {
+ log_warning("MAM result from %s with no 'by' attribute (expected our own JID or active MUC JID)", from);
return TRUE;
}
}
@@ -1554,7 +1584,21 @@ _handle_mam(xmpp_stanza_t* const stanza)
xmpp_stanza_t* message_stanza = xmpp_stanza_get_child_by_ns(forwarded, "jabber:client");
- _handle_chat(message_stanza, TRUE, FALSE, result_id, timestamp);
+ const char* inner_type = xmpp_stanza_get_type(message_stanza);
+
+ if (from && muc_active(from)) {
+ if (!inner_type || g_strcmp0(inner_type, STANZA_TYPE_GROUPCHAT) != 0) {
+ log_warning("Ignoring non-groupchat MAM message of type '%s' from MUC archive '%s' to prevent PM leak.",
+ inner_type ? inner_type : "none", from);
+ return TRUE;
+ }
+ }
+
+ if (inner_type && g_strcmp0(inner_type, STANZA_TYPE_GROUPCHAT) == 0) {
+ _handle_groupchat(message_stanza, TRUE, result_id, timestamp);
+ } else {
+ _handle_chat(message_stanza, TRUE, FALSE, result_id, timestamp);
+ }
return TRUE;
}
diff --git a/src/xmpp/muc.c b/src/xmpp/muc.c
index c5fedf696..5dcdfdbc2 100644
--- a/src/xmpp/muc.c
+++ b/src/xmpp/muc.c
@@ -50,6 +50,7 @@ typedef struct _muc_room_t
muc_anonymity_type_t anonymity_type;
gint64 last_activity;
gint64 ping_sent_time;
+ gboolean supports_mam;
} ChatRoom;
GHashTable* rooms = NULL;
@@ -315,6 +316,22 @@ muc_set_features(const char* const room, GSList* features)
} else {
chat_room->anonymity_type = MUC_ANONYMITY_TYPE_UNKNOWN;
}
+ if (g_slist_find_custom(features, "urn:xmpp:mam:2", (GCompareFunc)g_strcmp0)) {
+ chat_room->supports_mam = TRUE;
+ } else {
+ chat_room->supports_mam = FALSE;
+ }
+ }
+}
+
+gboolean
+muc_supports_mam(const char* const room)
+{
+ ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
+ if (chat_room) {
+ return chat_room->supports_mam;
+ } else {
+ return FALSE;
}
}
diff --git a/src/xmpp/muc.h b/src/xmpp/muc.h
index 8885d06f6..2debed869 100644
--- a/src/xmpp/muc.h
+++ b/src/xmpp/muc.h
@@ -71,6 +71,7 @@ gboolean muc_autojoin(const char* const room);
GList* muc_rooms(void);
void muc_set_features(const char* const room, GSList* features);
+gboolean muc_supports_mam(const char* const room);
const char* muc_nick(const char* const room);
char* muc_password(const char* const room);
diff --git a/src/xmpp/presence.c b/src/xmpp/presence.c
index c62d862ac..b5422050e 100644
--- a/src/xmpp/presence.c
+++ b/src/xmpp/presence.c
@@ -266,7 +266,8 @@ presence_join_room(const char* const room, const char* const nick, const char* c
int pri = accounts_get_priority_for_presence_type(session_get_account_name(), presence_type);
xmpp_ctx_t* ctx = connection_get_ctx();
- xmpp_stanza_t* presence = stanza_create_room_join_presence(ctx, jid->fulljid, passwd);
+ gboolean max_stanzas_zero = prefs_get_boolean(PREF_MAM) && connection_supports(XMPP_FEATURE_MAM2);
+ xmpp_stanza_t* presence = stanza_create_room_join_presence(ctx, jid->fulljid, passwd, max_stanzas_zero);
stanza_attach_show(ctx, presence, show);
stanza_attach_status(ctx, presence, status);
stanza_attach_priority(ctx, presence, pri);
diff --git a/src/xmpp/session.c b/src/xmpp/session.c
index 2e70343b9..4a169a8b6 100644
--- a/src/xmpp/session.c
+++ b/src/xmpp/session.c
@@ -324,7 +324,6 @@ session_login_success(gboolean secured)
}
roster_request();
- bookmark_request();
blocking_request();
// items discovery
@@ -332,10 +331,6 @@ session_login_success(gboolean secured)
const char* domain = connection_get_domain();
iq_disco_items_request_onconnect(domain);
- if (prefs_get_boolean(PREF_CARBONS)) {
- iq_enable_carbons();
- }
-
if ((prefs_get_reconnect() != 0) && reconnect_timer) {
g_timer_destroy(reconnect_timer);
reconnect_timer = NULL;
diff --git a/src/xmpp/stanza.c b/src/xmpp/stanza.c
index 8876662e6..c14e067dc 100644
--- a/src/xmpp/stanza.c
+++ b/src/xmpp/stanza.c
@@ -509,7 +509,8 @@ stanza_create_mediated_invite(xmpp_ctx_t* ctx, const char* const room,
xmpp_stanza_t*
stanza_create_room_join_presence(xmpp_ctx_t* const ctx,
- const char* const full_room_jid, const char* const passwd)
+ const char* const full_room_jid, const char* const passwd,
+ gboolean max_stanzas_zero)
{
xmpp_stanza_t* presence = xmpp_presence_new(ctx);
xmpp_stanza_set_to(presence, full_room_jid);
@@ -530,6 +531,16 @@ stanza_create_room_join_presence(xmpp_ctx_t* const ctx,
xmpp_stanza_release(pass);
}
+ if (max_stanzas_zero) {
+ xmpp_stanza_t* history = xmpp_stanza_new(ctx);
+ if (history) {
+ xmpp_stanza_set_name(history, "history");
+ xmpp_stanza_set_attribute(history, "maxstanzas", "0");
+ xmpp_stanza_add_child(x, history);
+ xmpp_stanza_release(history);
+ }
+ }
+
xmpp_stanza_add_child(presence, x);
xmpp_stanza_release(x);
@@ -2782,6 +2793,154 @@ stanza_create_mam_iq(xmpp_ctx_t* ctx, const char* const jid, const char* const s
return iq;
}
+xmpp_stanza_t*
+stanza_create_muc_mam_iq(xmpp_ctx_t* ctx, const char* const room, const char* const startdate, const char* const enddate, const char* const firstid, const char* const lastid)
+{
+ auto_char char* id = connection_create_stanza_id();
+ xmpp_stanza_t* iq = xmpp_iq_new(ctx, STANZA_TYPE_SET, id);
+ xmpp_stanza_set_to(iq, room);
+
+ xmpp_stanza_t* query = xmpp_stanza_new(ctx);
+ xmpp_stanza_set_name(query, STANZA_NAME_QUERY);
+ xmpp_stanza_set_ns(query, STANZA_NS_MAM2);
+
+ xmpp_stanza_t* x = xmpp_stanza_new(ctx);
+ xmpp_stanza_set_name(x, STANZA_NAME_X);
+ xmpp_stanza_set_ns(x, STANZA_NS_DATA);
+ xmpp_stanza_set_type(x, "submit");
+
+ // field FORM_TYPE MAM2
+ xmpp_stanza_t* field_form_type = xmpp_stanza_new(ctx);
+ xmpp_stanza_set_name(field_form_type, STANZA_NAME_FIELD);
+ xmpp_stanza_set_attribute(field_form_type, STANZA_ATTR_VAR, "FORM_TYPE");
+ xmpp_stanza_set_type(field_form_type, "hidden");
+
+ xmpp_stanza_t* value_mam = _text_stanza(ctx, STANZA_NAME_VALUE, STANZA_NS_MAM2);
+ xmpp_stanza_add_child_ex(field_form_type, value_mam, 0);
+
+ // 4.3.2 set/rsm
+ xmpp_stanza_t* set = xmpp_stanza_new(ctx);
+ xmpp_stanza_set_name(set, STANZA_TYPE_SET);
+ xmpp_stanza_set_ns(set, STANZA_NS_RSM);
+
+ xmpp_stanza_t* max = _text_stanza(ctx, STANZA_NAME_MAX, PROF_STRINGIFY(MESSAGES_TO_RETRIEVE));
+ xmpp_stanza_add_child_ex(set, max, 0);
+
+ if (lastid) {
+ xmpp_stanza_t* after = _text_stanza(ctx, STANZA_NAME_AFTER, lastid);
+ xmpp_stanza_add_child_ex(set, after, 0);
+ }
+
+ if (firstid) {
+ xmpp_stanza_t* before = _text_stanza(ctx, STANZA_NAME_BEFORE, firstid);
+ xmpp_stanza_add_child_ex(set, before, 0);
+ }
+
+ // add and release
+ xmpp_stanza_add_child_ex(iq, query, 0);
+ xmpp_stanza_add_child_ex(query, x, 0);
+ xmpp_stanza_add_child_ex(x, field_form_type, 0);
+
+ // field 'start'
+ if (startdate) {
+ xmpp_stanza_t* field_start = xmpp_stanza_new(ctx);
+ xmpp_stanza_set_name(field_start, STANZA_NAME_FIELD);
+ xmpp_stanza_set_attribute(field_start, STANZA_ATTR_VAR, "start");
+
+ xmpp_stanza_t* value_start = _text_stanza(ctx, STANZA_NAME_VALUE, startdate);
+
+ xmpp_stanza_add_child_ex(field_start, value_start, 0);
+ xmpp_stanza_add_child_ex(x, field_start, 0);
+ }
+
+ if (enddate) {
+ xmpp_stanza_t* field_end = xmpp_stanza_new(ctx);
+ xmpp_stanza_set_name(field_end, STANZA_NAME_FIELD);
+ xmpp_stanza_set_attribute(field_end, STANZA_ATTR_VAR, "end");
+
+ xmpp_stanza_t* value_end = _text_stanza(ctx, STANZA_NAME_VALUE, enddate);
+
+ xmpp_stanza_add_child_ex(field_end, value_end, 0);
+ xmpp_stanza_add_child_ex(x, field_end, 0);
+ }
+
+ xmpp_stanza_add_child_ex(query, set, 0);
+
+ return iq;
+}
+
+xmpp_stanza_t*
+stanza_create_mam_count_iq(xmpp_ctx_t* ctx, const char* const jid, const char* const startdate, const char* const enddate, gboolean is_muc)
+{
+ auto_char char* id = connection_create_stanza_id();
+ xmpp_stanza_t* iq = xmpp_iq_new(ctx, STANZA_TYPE_SET, id);
+ if (is_muc) {
+ xmpp_stanza_set_to(iq, jid);
+ }
+
+ xmpp_stanza_t* query = xmpp_stanza_new(ctx);
+ xmpp_stanza_set_name(query, STANZA_NAME_QUERY);
+ xmpp_stanza_set_ns(query, STANZA_NS_MAM2);
+
+ xmpp_stanza_t* x = xmpp_stanza_new(ctx);
+ xmpp_stanza_set_name(x, STANZA_NAME_X);
+ xmpp_stanza_set_ns(x, STANZA_NS_DATA);
+ xmpp_stanza_set_type(x, "submit");
+
+ // FORM_TYPE
+ xmpp_stanza_t* field_form_type = xmpp_stanza_new(ctx);
+ xmpp_stanza_set_name(field_form_type, STANZA_NAME_FIELD);
+ xmpp_stanza_set_attribute(field_form_type, STANZA_ATTR_VAR, "FORM_TYPE");
+ xmpp_stanza_set_type(field_form_type, "hidden");
+
+ xmpp_stanza_t* value_mam = _text_stanza(ctx, STANZA_NAME_VALUE, STANZA_NS_MAM2);
+ xmpp_stanza_add_child_ex(field_form_type, value_mam, 0);
+ xmpp_stanza_add_child_ex(x, field_form_type, 0);
+
+ if (!is_muc) {
+ xmpp_stanza_t* field_with = xmpp_stanza_new(ctx);
+ xmpp_stanza_set_name(field_with, STANZA_NAME_FIELD);
+ xmpp_stanza_set_attribute(field_with, STANZA_ATTR_VAR, "with");
+
+ xmpp_stanza_t* value_with = _text_stanza(ctx, STANZA_NAME_VALUE, jid);
+ xmpp_stanza_add_child_ex(field_with, value_with, 0);
+ xmpp_stanza_add_child_ex(x, field_with, 0);
+ }
+
+ if (startdate) {
+ xmpp_stanza_t* field_start = xmpp_stanza_new(ctx);
+ xmpp_stanza_set_name(field_start, STANZA_NAME_FIELD);
+ xmpp_stanza_set_attribute(field_start, STANZA_ATTR_VAR, "start");
+
+ xmpp_stanza_t* value_start = _text_stanza(ctx, STANZA_NAME_VALUE, startdate);
+ xmpp_stanza_add_child_ex(field_start, value_start, 0);
+ xmpp_stanza_add_child_ex(x, field_start, 0);
+ }
+
+ if (enddate) {
+ xmpp_stanza_t* field_end = xmpp_stanza_new(ctx);
+ xmpp_stanza_set_name(field_end, STANZA_NAME_FIELD);
+ xmpp_stanza_set_attribute(field_end, STANZA_ATTR_VAR, "end");
+
+ xmpp_stanza_t* value_end = _text_stanza(ctx, STANZA_NAME_VALUE, enddate);
+ xmpp_stanza_add_child_ex(field_end, value_end, 0);
+ xmpp_stanza_add_child_ex(x, field_end, 0);
+ }
+
+ xmpp_stanza_t* set = xmpp_stanza_new(ctx);
+ xmpp_stanza_set_name(set, STANZA_TYPE_SET);
+ xmpp_stanza_set_ns(set, STANZA_NS_RSM);
+
+ xmpp_stanza_t* max = _text_stanza(ctx, STANZA_NAME_MAX, "0");
+ xmpp_stanza_add_child_ex(set, max, 0);
+
+ xmpp_stanza_add_child_ex(query, x, 0);
+ xmpp_stanza_add_child_ex(query, set, 0);
+ xmpp_stanza_add_child_ex(iq, query, 0);
+
+ return iq;
+}
+
xmpp_stanza_t*
stanza_change_password(xmpp_ctx_t* ctx, const char* const user, const char* const password)
{
diff --git a/src/xmpp/stanza.h b/src/xmpp/stanza.h
index e50b5c78f..466f61c8a 100644
--- a/src/xmpp/stanza.h
+++ b/src/xmpp/stanza.h
@@ -281,7 +281,8 @@ xmpp_stanza_t* stanza_attach_origin_id(xmpp_ctx_t* ctx, xmpp_stanza_t* stanza, c
xmpp_stanza_t* stanza_attach_correction(xmpp_ctx_t* ctx, xmpp_stanza_t* stanza, const char* const replace_id);
xmpp_stanza_t* stanza_create_room_join_presence(xmpp_ctx_t* const ctx,
- const char* const full_room_jid, const char* const passwd);
+ const char* const full_room_jid, const char* const passwd,
+ gboolean max_stanzas_zero);
xmpp_stanza_t* stanza_create_room_newnick_presence(xmpp_ctx_t* ctx,
const char* const full_room_jid);
@@ -397,6 +398,8 @@ xmpp_stanza_t* stanza_create_avatar_metadata_publish_iq(xmpp_ctx_t* ctx, const c
xmpp_stanza_t* stanza_disable_avatar_publish_iq(xmpp_ctx_t* ctx);
xmpp_stanza_t* stanza_create_vcard_request_iq(xmpp_ctx_t* ctx, const char* const jid, const char* const stanza_id);
xmpp_stanza_t* stanza_create_mam_iq(xmpp_ctx_t* ctx, const char* const jid, const char* const startdate, const char* const enddate, const char* const firstid, const char* const lastid);
+xmpp_stanza_t* stanza_create_muc_mam_iq(xmpp_ctx_t* ctx, const char* const room, const char* const startdate, const char* const enddate, const char* const firstid, const char* const lastid);
+xmpp_stanza_t* stanza_create_mam_count_iq(xmpp_ctx_t* ctx, const char* const jid, const char* const startdate, const char* const enddate, gboolean is_muc);
xmpp_stanza_t* stanza_change_password(xmpp_ctx_t* ctx, const char* const user, const char* const password);
xmpp_stanza_t* stanza_register_new_account(xmpp_ctx_t* ctx, const char* const user, const char* const password);
xmpp_stanza_t* stanza_request_voice(xmpp_ctx_t* ctx, const char* const room);
diff --git a/src/xmpp/xmpp.h b/src/xmpp/xmpp.h
index 83d87fb66..c597dbef3 100644
--- a/src/xmpp/xmpp.h
+++ b/src/xmpp/xmpp.h
@@ -253,8 +253,9 @@ void iq_autoping_check(void);
void iq_http_upload_request(HTTPUpload* upload);
void iq_command_list(const char* const target);
void iq_command_exec(const char* const target, const char* const command);
-void iq_mam_request(ProfChatWin* win, GDateTime* enddate);
-void iq_mam_request_older(ProfChatWin* win);
+void iq_mam_request(ProfWin* win, GDateTime* enddate);
+void iq_mam_request_older(ProfWin* win);
+void iq_mam_verify_request(ProfWin* win, const char* const startdate, const char* const enddate);
void iq_register_change_password(const char* const user, const char* const password);
void iq_muc_register_nick(const char* const roomjid);
diff --git a/tests/unittests/database/stub_database.c b/tests/unittests/database/stub_database.c
index 913877e37..cedfc16c8 100644
--- a/tests/unittests/database/stub_database.c
+++ b/tests/unittests/database/stub_database.c
@@ -16,9 +16,10 @@ log_database_init(ProfAccount* account)
{
return TRUE;
}
-void
+gboolean
log_database_add_incoming(ProfMessage* message)
{
+ return TRUE;
}
void
log_database_add_outgoing_chat(const char* const id, const char* const barejid, const char* const message, const char* const replace_id, prof_enc_t enc)
diff --git a/tests/unittests/ui/stub_ui.c b/tests/unittests/ui/stub_ui.c
index a9d76fa51..074dc2e59 100644
--- a/tests/unittests/ui/stub_ui.c
+++ b/tests/unittests/ui/stub_ui.c
@@ -1479,3 +1479,15 @@ void
ui_flash(void)
{
}
+
+unsigned int
+buffer_size(ProfBuff buffer)
+{
+ return 0;
+}
+
+ProfBuffEntry*
+buffer_get_entry(ProfBuff buffer, unsigned int index)
+{
+ return NULL;
+}
diff --git a/tests/unittests/xmpp/stub_xmpp.c b/tests/unittests/xmpp/stub_xmpp.c
index b519a0059..f9c0b3a18 100644
--- a/tests/unittests/xmpp/stub_xmpp.c
+++ b/tests/unittests/xmpp/stub_xmpp.c
@@ -447,7 +447,12 @@ iq_muc_register_nick(const char* const roomjid)
}
void
-iq_mam_request(ProfChatWin* win, GDateTime* enddate)
+iq_mam_request(ProfWin* win, GDateTime* enddate)
+{
+}
+
+void
+iq_mam_verify_request(ProfWin* win, const char* const startdate, const char* const enddate)
{
}
@@ -546,6 +551,11 @@ bookmark_autocomplete_reset(void)
{
}
+void
+bookmark_request(void)
+{
+}
+
void
roster_send_name_change(const char* const barejid, const char* const new_name, GSList* groups)
{