From fc3ac84c2fa091d12c2034f06b227618e76c1226 Mon Sep 17 00:00:00 2001 From: defanor Date: Sun, 5 Jul 2020 22:28:44 +0300 Subject: Add presence tracking Presence of roster contacts is tracked by rexmpp now (optionally, by default), and the weechat plugin marks online contacts with the "+" prefix. --- README | 2 +- examples/weechat.c | 33 +++++++++++++++++++++++++++++ src/rexmpp.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/rexmpp.h | 2 ++ 4 files changed, 98 insertions(+), 1 deletion(-) diff --git a/README b/README index 3a43f2a..8daedda 100644 --- a/README +++ b/README @@ -71,7 +71,7 @@ A rough roadmap: - Additional state tracking: -[ ] XMPP IM (RFC 6121): track presences of contacts. +[+] XMPP IM (RFC 6121): track presences of contacts. [ ] XEP-0030: Service Discovery: track features provided by known entities. [ ] XEP-0115: Entity Capabilities: maintain a capability database, diff --git a/examples/weechat.c b/examples/weechat.c index 0c26f6e..ce2ffef 100644 --- a/examples/weechat.c +++ b/examples/weechat.c @@ -119,6 +119,7 @@ int muc_close_cb (struct weechat_rexmpp_muc *wrm, void *data, } int my_xml_in_cb (struct weechat_rexmpp *wr, xmlNodePtr node) { + rexmpp_t *s = &wr->rexmpp_state; char *xml_buf = rexmpp_xml_serialize(node); weechat_printf(wr->server_buffer, "recv: %s\n", xml_buf); /* free(xml_buf); */ @@ -161,6 +162,7 @@ int my_xml_in_cb (struct weechat_rexmpp *wr, xmlNodePtr node) { if (rexmpp_xml_match(node, "jabber:client", "presence")) { char *presence_type = xmlGetProp(node, "type"); char *jid = xmlGetProp(node, "from"); + char *full_jid = strdup(jid); int i; char *resource = ""; for (i = 0; i < strlen(jid); i++) { @@ -185,8 +187,39 @@ int my_xml_in_cb (struct weechat_rexmpp *wr, xmlNodePtr node) { "bar_fg", "", "lightgreen", 1); } } + } else if (rexmpp_roster_find_item(s, jid, NULL) != NULL) { + /* A roster item. */ + struct t_gui_nick *nick = weechat_nicklist_search_nick(wr->server_buffer, NULL, jid); + if (presence_type == NULL) { + /* An "available" presence: just ensure that it's shown as + online. */ + weechat_nicklist_nick_set(wr->server_buffer, nick, "prefix", "+"); + } else if (strcmp(presence_type, "unavailable") == 0) { + /* An "unavailable" presence: set it to "offline" if there's + no remaining online resources (i.e., if we can find an + online resource for this bare JID other than the one that + just went offline). */ + xmlNodePtr cur; + int found = 0; + for (cur = s->roster_presence; + cur != NULL; + cur = xmlNextElementSibling(cur)) { + char *cur_from = xmlGetProp(cur, "from"); + if (strcmp(cur_from, full_jid) != 0 && + strncmp(cur_from, jid, strlen(jid)) == 0 && + strlen(cur_from) > strlen(jid) && + cur_from[strlen(jid)] == '/') { + found = 1; + } + free(cur_from); + } + if (! found) { + weechat_nicklist_nick_set(wr->server_buffer, nick, "prefix", ""); + } + } } free(jid); + free(full_jid); if (presence_type != NULL) { free(presence_type); } diff --git a/src/rexmpp.c b/src/rexmpp.c index c4c1e1c..deee805 100644 --- a/src/rexmpp.c +++ b/src/rexmpp.c @@ -238,6 +238,7 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, const char *jid) s->enable_service_discovery = 1; s->manage_roster = 1; s->roster_cache_file = NULL; + s->track_roster_presence = 1; s->send_buffer = NULL; s->send_queue = NULL; s->server_srv = NULL; @@ -250,6 +251,7 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, const char *jid) s->stream_features = NULL; s->roster_items = NULL; s->roster_ver = NULL; + s->roster_presence = NULL; s->stanza_queue = NULL; s->stream_id = NULL; s->active_iq = NULL; @@ -419,6 +421,10 @@ void rexmpp_done (rexmpp_t *s) { xmlFreeNodeList(s->roster_items); s->roster_items = NULL; } + if (s->roster_presence != NULL) { + xmlFreeNodeList(s->roster_presence); + s->roster_presence = NULL; + } if (s->roster_ver != NULL) { free(s->roster_ver); s->roster_ver = NULL; @@ -1493,6 +1499,62 @@ void rexmpp_process_element (rexmpp_t *s) { free(type); } + /* Incoming presence information. */ + if (rexmpp_xml_match(elem, "jabber:client", "presence") && + s->manage_roster && + s->track_roster_presence) { + char *from = xmlGetProp(elem, "from"); + if (from != NULL) { + size_t i; + int resource_removed = 0; + for (i = 0; i < strlen(from); i++) { + if (from[i] == '/') { + from[i] = '\0'; + resource_removed = i; + break; + } + } + if (rexmpp_roster_find_item(s, from, NULL) != NULL) { + /* The bare JID is in the roster. */ + if (resource_removed) { + /* Restore full JID. */ + from[resource_removed] = '/'; + } + char *type = xmlGetProp(elem, "type"); + xmlNodePtr cur, prev; + if (type == NULL || strcmp(type, "unavailable") == 0) { + /* Either a new "available" presence or an "unavailable" + one: remove the previously stored presence for this + JID. */ + for (prev = NULL, cur = s->roster_presence; + cur != NULL; + prev = cur, cur = xmlNextElementSibling(cur)) { + char *cur_from = xmlGetProp(cur, "from"); + if (strcmp(cur_from, from) == 0) { + if (prev == NULL) { + s->roster_presence = cur->next; + } else { + prev->next = cur->next; + } + xmlFreeNode(cur); + cur = NULL; + } + free(cur_from); + } + } + if (type == NULL) { + /* An "available" presence: add it. */ + xmlNodePtr presence = xmlCopyNode(elem, 1); + presence->next = s->roster_presence; + s->roster_presence = presence; + } else { + free(type); + } + } + free(from); + } + } + /* Stream negotiation, https://tools.ietf.org/html/rfc6120#section-4.3 */ if (s->stream_state == REXMPP_STREAM_NEGOTIATION && diff --git a/src/rexmpp.h b/src/rexmpp.h index 6157f86..7e2376f 100644 --- a/src/rexmpp.h +++ b/src/rexmpp.h @@ -238,6 +238,7 @@ struct rexmpp int enable_service_discovery; int manage_roster; const char *roster_cache_file; + int track_roster_presence; /* Resource limits. */ uint32_t stanza_queue_size; @@ -256,6 +257,7 @@ struct rexmpp xmlNodePtr stream_features; xmlNodePtr roster_items; char *roster_ver; + xmlNodePtr roster_presence; /* Other dynamic data. */ xmlNodePtr disco_info; -- cgit v1.2.3