From b34faa209984621822670ed64d6a8da90327864a Mon Sep 17 00:00:00 2001 From: Jabber Developer Date: Thu, 9 Jul 2026 15:19:07 +0000 Subject: [PATCH] feat(caps): add cache clearing to /caps Add /caps clear to wipe the local capabilities cache. Implement autocomplete for new subcommand. --- src/command/cmd_ac.c | 35 ++++++++++++++++++++++++++++++++ src/command/cmd_defs.c | 12 +++++++---- src/command/cmd_funcs.c | 8 ++++++++ src/xmpp/capabilities.c | 14 +++++++++++++ src/xmpp/capabilities.h | 1 + tests/unittests/xmpp/stub_xmpp.c | 5 +++++ 6 files changed, 71 insertions(+), 4 deletions(-) diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c index 99c2ac36..6b24dc22 100644 --- a/src/command/cmd_ac.c +++ b/src/command/cmd_ac.c @@ -42,6 +42,7 @@ #include "ai/ai_client.h" +static char* _caps_autocomplete(ProfWin* window, const char* const input, gboolean previous); static char* _sub_autocomplete(ProfWin* window, const char* const input, gboolean previous); static char* _notify_autocomplete(ProfWin* window, const char* const input, gboolean previous); static char* _theme_autocomplete(ProfWin* window, const char* const input, gboolean previous); @@ -151,6 +152,7 @@ static Autocomplete account_clear_ac; static Autocomplete account_default_ac; static Autocomplete account_status_ac; static Autocomplete disco_ac; +static Autocomplete caps_subcommands_ac; static Autocomplete wins_ac; static Autocomplete roster_ac; static Autocomplete roster_show_ac; @@ -321,6 +323,7 @@ static Autocomplete* all_acs[] = { &account_default_ac, &account_status_ac, &disco_ac, + &caps_subcommands_ac, &wins_ac, &roster_ac, &roster_show_ac, @@ -597,6 +600,8 @@ cmd_ac_init(void) autocomplete_add(disco_ac, "info"); autocomplete_add(disco_ac, "items"); + autocomplete_add(caps_subcommands_ac, "clear"); + autocomplete_add(account_ac, "list"); autocomplete_add(account_ac, "show"); autocomplete_add(account_ac, "add"); @@ -1414,6 +1419,7 @@ cmd_ac_init(void) g_hash_table_insert(ac_funcs, "/ban", _ban_autocomplete); g_hash_table_insert(ac_funcs, "/blocked", _blocked_autocomplete); g_hash_table_insert(ac_funcs, "/bookmark", _bookmark_autocomplete); + g_hash_table_insert(ac_funcs, "/caps", _caps_autocomplete); g_hash_table_insert(ac_funcs, "/clear", _clear_autocomplete); g_hash_table_insert(ac_funcs, "/close", _close_autocomplete); g_hash_table_insert(ac_funcs, "/cmd", _adhoc_cmd_autocomplete); @@ -1985,6 +1991,35 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ return NULL; } +static char* +_caps_autocomplete(ProfWin* window, const char* const input, gboolean previous) +{ + char* result = NULL; + result = autocomplete_param_with_ac(input, "/caps", caps_subcommands_ac, TRUE, previous); + if (result) { + return result; + } + if (window->type == WIN_MUC) { + ProfMucWin* mucwin = (ProfMucWin*)window; + assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK); + Autocomplete nick_ac = muc_roster_ac(mucwin->roomjid); + if (nick_ac) { + result = autocomplete_param_with_ac(input, "/caps", nick_ac, TRUE, previous); + } + } else if (connection_get_status() == JABBER_CONNECTED) { + result = autocomplete_param_with_func(input, "/caps", roster_contact_autocomplete, previous, NULL); + if (result) { + return result; + } + result = autocomplete_param_with_func(input, "/caps", roster_barejid_autocomplete, previous, NULL); + if (result) { + return result; + } + result = autocomplete_param_with_func(input, "/caps", roster_fulljid_autocomplete, previous, NULL); + } + return result; +} + static char* _sub_autocomplete(ProfWin* window, const char* const input, gboolean previous) { diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c index 795d0547..4424d4a3 100644 --- a/src/command/cmd_defs.c +++ b/src/command/cmd_defs.c @@ -427,17 +427,21 @@ static const struct cmd_t command_defs[] = { CMD_TAG_GROUPCHAT) CMD_SYN( "/caps", - "/caps |") + "/caps |", + "/caps clear") CMD_DESC( "Find out a contacts, or room members client software capabilities. " - "If in private chat initiated from a chat room, no parameter is required.") + "If in private chat initiated from a chat room, no parameter is required. " + "Use clear to remove all cached capabilities.") CMD_ARGS( { "", "If in the console or a chat window, the full JID for which you wish to see capabilities." }, - { "", "If in a chat room, nickname for which you wish to see capabilities." }) + { "", "If in a chat room, nickname for which you wish to see capabilities." }, + { "clear", "Clear all cached capabilities." }) CMD_EXAMPLES( "/caps ran@cold.sea.org/laptop", "/caps ran@cold.sea.org/phone", - "/caps aegir") + "/caps aegir", + "/caps clear") }, { CMD_PREAMBLE("/software", diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index d9293e66..77b6dd96 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -70,6 +70,7 @@ #include "xmpp/stanza.h" #include "xmpp/vcard_funcs.h" #include "xmpp/xmpp.h" +#include "xmpp/capabilities.h" #ifdef HAVE_LIBOTR #include "otr/otr.h" @@ -3387,6 +3388,12 @@ cmd_caps(ProfWin* window, const char* const command, gchar** args) return TRUE; } + if (g_strcmp0(args[0], "clear") == 0) { + caps_cache_clear(); + cons_show("Capabilities cache cleared."); + return TRUE; + } + switch (window->type) { case WIN_MUC: if (args[0]) { @@ -3405,6 +3412,7 @@ cmd_caps(ProfWin* window, const char* const command, gchar** args) break; case WIN_CHAT: case WIN_CONSOLE: + case WIN_XML: if (args[0]) { auto_jid Jid* jid = jid_create(args[0]); diff --git a/src/xmpp/capabilities.c b/src/xmpp/capabilities.c index b018d7f8..3e788d80 100644 --- a/src/xmpp/capabilities.c +++ b/src/xmpp/capabilities.c @@ -23,6 +23,7 @@ #include "plugins/plugins.h" #include "config/files.h" #include "config/preferences.h" +#include "xmpp/connection.h" #include "xmpp/xmpp.h" #include "xmpp/stanza.h" #include "xmpp/form.h" @@ -271,6 +272,19 @@ caps_map_jid_to_ver(const char* const jid, const char* const ver) g_hash_table_insert(jid_to_ver, strdup(jid), strdup(ver)); } +void +caps_cache_clear(void) +{ + g_hash_table_remove_all(jid_to_ver); + g_hash_table_remove_all(jid_to_caps); + if (caps_prof_keyfile.keyfile) { + g_key_file_free(caps_prof_keyfile.keyfile); + } + caps_prof_keyfile.keyfile = g_key_file_new(); + cache = caps_prof_keyfile.keyfile; + save_keyfile(&caps_prof_keyfile); +} + gboolean caps_cache_contains(const char* const ver) { diff --git a/src/xmpp/capabilities.h b/src/xmpp/capabilities.h index ed5b95a4..28503478 100644 --- a/src/xmpp/capabilities.h +++ b/src/xmpp/capabilities.h @@ -28,5 +28,6 @@ void caps_map_jid_to_ver(const char* const jid, const char* const ver); gboolean caps_cache_contains(const char* const ver); GList* caps_get_features(void); char* caps_get_my_sha1(xmpp_ctx_t* const ctx); +void caps_cache_clear(void); #endif diff --git a/tests/unittests/xmpp/stub_xmpp.c b/tests/unittests/xmpp/stub_xmpp.c index 987ab67b..9cdb3ba6 100644 --- a/tests/unittests/xmpp/stub_xmpp.c +++ b/tests/unittests/xmpp/stub_xmpp.c @@ -500,6 +500,11 @@ caps_jid_has_feature(const char* const jid, const char* const feature) return FALSE; } +void +caps_cache_clear(void) +{ +} + gboolean bookmark_add(const char* jid, const char* nick, const char* password, const char* autojoin_str, const char* name) {