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) {