2 * purple - Jabber Protocol Plugin
4 * Purple is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
25 #include "image-store.h"
38 #include "useravatar.h"
41 #include "adhoccommands.h"
42 #include "google/google.h"
46 } JabberBuddyInfoResource
;
53 GHashTable
*resources
;
56 PurpleNotifyUserInfo
*user_info
;
62 jabber_buddy_resource_free(JabberBuddyResource
*jbr
)
64 g_return_if_fail(jbr
!= NULL
);
66 jbr
->jb
->resources
= g_list_remove(jbr
->jb
->resources
, jbr
);
68 while(jbr
->commands
) {
69 JabberAdHocCommands
*cmd
= jbr
->commands
->data
;
74 jbr
->commands
= g_list_delete_link(jbr
->commands
, jbr
->commands
);
77 while (jbr
->caps
.exts
) {
78 g_free(jbr
->caps
.exts
->data
);
79 jbr
->caps
.exts
= g_list_delete_link(jbr
->caps
.exts
, jbr
->caps
.exts
);
84 g_free(jbr
->thread_id
);
85 g_free(jbr
->client
.name
);
86 g_free(jbr
->client
.version
);
87 g_free(jbr
->client
.os
);
91 void jabber_buddy_free(JabberBuddy
*jb
)
93 g_return_if_fail(jb
!= NULL
);
95 g_free(jb
->error_msg
);
97 jabber_buddy_resource_free(jb
->resources
->data
);
102 JabberBuddy
*jabber_buddy_find(JabberStream
*js
, const char *name
,
108 if (js
->buddies
== NULL
)
111 if(!(realname
= jabber_get_bare_jid(name
)))
114 jb
= g_hash_table_lookup(js
->buddies
, realname
);
117 jb
= g_new0(JabberBuddy
, 1);
118 g_hash_table_insert(js
->buddies
, realname
, jb
);
125 /* Returns -1 if a is a higher priority resource than b, or is
126 * "more available" than b. 0 if they're the same, and 1 if b is
127 * higher priority/more available than a.
129 static gint
resource_compare_cb(gconstpointer a
, gconstpointer b
)
131 const JabberBuddyResource
*jbra
= a
;
132 const JabberBuddyResource
*jbrb
= b
;
133 JabberBuddyState state_a
, state_b
;
135 if (jbra
->priority
!= jbrb
->priority
)
136 return jbra
->priority
> jbrb
->priority
? -1 : 1;
138 /* Fold the states for easier comparison */
139 /* TODO: Differentiate online/chat and away/dnd? */
140 switch (jbra
->state
) {
141 case JABBER_BUDDY_STATE_ONLINE
:
142 case JABBER_BUDDY_STATE_CHAT
:
143 state_a
= JABBER_BUDDY_STATE_ONLINE
;
145 case JABBER_BUDDY_STATE_AWAY
:
146 case JABBER_BUDDY_STATE_DND
:
147 state_a
= JABBER_BUDDY_STATE_AWAY
;
149 case JABBER_BUDDY_STATE_XA
:
150 state_a
= JABBER_BUDDY_STATE_XA
;
152 case JABBER_BUDDY_STATE_UNAVAILABLE
:
153 state_a
= JABBER_BUDDY_STATE_UNAVAILABLE
;
156 state_a
= JABBER_BUDDY_STATE_UNKNOWN
;
160 switch (jbrb
->state
) {
161 case JABBER_BUDDY_STATE_ONLINE
:
162 case JABBER_BUDDY_STATE_CHAT
:
163 state_b
= JABBER_BUDDY_STATE_ONLINE
;
165 case JABBER_BUDDY_STATE_AWAY
:
166 case JABBER_BUDDY_STATE_DND
:
167 state_b
= JABBER_BUDDY_STATE_AWAY
;
169 case JABBER_BUDDY_STATE_XA
:
170 state_b
= JABBER_BUDDY_STATE_XA
;
172 case JABBER_BUDDY_STATE_UNAVAILABLE
:
173 state_b
= JABBER_BUDDY_STATE_UNAVAILABLE
;
176 state_b
= JABBER_BUDDY_STATE_UNKNOWN
;
180 if (state_a
== state_b
) {
181 if (jbra
->idle
== jbrb
->idle
)
183 else if ((jbra
->idle
&& !jbrb
->idle
) ||
184 (jbra
->idle
&& jbrb
->idle
&& jbra
->idle
< jbrb
->idle
))
190 if (state_a
== JABBER_BUDDY_STATE_ONLINE
)
192 else if (state_a
== JABBER_BUDDY_STATE_AWAY
&&
193 (state_b
== JABBER_BUDDY_STATE_XA
||
194 state_b
== JABBER_BUDDY_STATE_UNAVAILABLE
||
195 state_b
== JABBER_BUDDY_STATE_UNKNOWN
))
197 else if (state_a
== JABBER_BUDDY_STATE_XA
&&
198 (state_b
== JABBER_BUDDY_STATE_UNAVAILABLE
||
199 state_b
== JABBER_BUDDY_STATE_UNKNOWN
))
201 else if (state_a
== JABBER_BUDDY_STATE_UNAVAILABLE
&&
202 state_b
== JABBER_BUDDY_STATE_UNKNOWN
)
208 JabberBuddyResource
*jabber_buddy_find_resource(JabberBuddy
*jb
,
209 const char *resource
)
216 if (resource
== NULL
)
217 return jb
->resources
? jb
->resources
->data
: NULL
;
219 for (l
= jb
->resources
; l
; l
= l
->next
)
221 JabberBuddyResource
*jbr
= l
->data
;
222 if (purple_strequal(resource
, jbr
->name
))
229 JabberBuddyResource
*jabber_buddy_track_resource(JabberBuddy
*jb
, const char *resource
,
230 int priority
, JabberBuddyState state
, const char *status
)
232 /* TODO: Optimization: Only reinsert if priority+state changed */
233 JabberBuddyResource
*jbr
= jabber_buddy_find_resource(jb
, resource
);
235 jb
->resources
= g_list_remove(jb
->resources
, jbr
);
237 jbr
= g_new0(JabberBuddyResource
, 1);
239 jbr
->name
= g_strdup(resource
);
240 jbr
->capabilities
= JABBER_CAP_NONE
;
241 jbr
->tz_off
= PURPLE_NO_TZ_OFF
;
243 jbr
->priority
= priority
;
246 jbr
->status
= g_strdup(status
);
248 jb
->resources
= g_list_insert_sorted(jb
->resources
, jbr
,
249 resource_compare_cb
);
253 void jabber_buddy_remove_resource(JabberBuddy
*jb
, const char *resource
)
255 JabberBuddyResource
*jbr
= jabber_buddy_find_resource(jb
, resource
);
260 jabber_buddy_resource_free(jbr
);
264 * This is the old vCard stuff taken from the old prpl. vCards, by definition
265 * are a temporary thing until jabber can get its act together and come up
266 * with a format for user information, hence the namespace of 'vcard-temp'
268 * Since I don't feel like putting that much work into something that's
269 * _supposed_ to go away, i'm going to just copy the kludgy old code here,
270 * and make it purdy when jabber comes up with a standards-track JEP to
275 /*---------------------------------------*/
276 /* Jabber "set info" (vCard) support */
277 /*---------------------------------------*/
282 * <vCard prodid='' version='' xmlns=''>
312 * http://docs.jabber.org/proto/html/vcard-temp.html
313 * http://www.vcard-xml.org/dtd/vCard-XML-v2-20010520.dtd
317 * Cross-reference user-friendly V-Card entry labels to vCard XML tags
320 * Order is (or should be) unimportant. For example: we have no way of
321 * knowing in what order real data will arrive.
323 * Format: Label, Pre-set text, "visible" flag, "editable" flag, XML tag
324 * name, XML tag's parent tag "path" (relative to vCard node).
326 * List is terminated by a NULL label pointer.
328 * Entries with no label text, but with XML tag and parent tag
329 * entries, are used by V-Card XML construction routines to
330 * "automagically" construct the appropriate XML node tree.
332 * Thoughts on future direction/expansion
334 * This is a "simple" vCard.
336 * It is possible for nodes other than the "vCard" node to have
337 * attributes. Should that prove necessary/desirable, add an
338 * "attributes" pointer to the vcard_template struct, create the
339 * necessary tag_attr structs, and add 'em to the vcard_dflt_data
342 * The above changes will (obviously) require changes to the vCard
343 * construction routines.
346 struct vcard_template
{
347 char *label
; /* label text pointer */
348 char *tag
; /* tag text */
349 char *ptag
; /* parent tag "path" text */
350 } const vcard_template_data
[] = {
351 {N_("Full Name"), "FN", NULL
},
352 {N_("Family Name"), "FAMILY", "N"},
353 {N_("Given Name"), "GIVEN", "N"},
354 {N_("Nickname"), "NICKNAME", NULL
},
355 {N_("URL"), "URL", NULL
},
356 {N_("Street Address"), "STREET", "ADR"},
357 {N_("Extended Address"), "EXTADD", "ADR"},
358 {N_("Locality"), "LOCALITY", "ADR"},
359 {N_("Region"), "REGION", "ADR"},
360 {N_("Postal Code"), "PCODE", "ADR"},
361 {N_("Country"), "CTRY", "ADR"},
362 {N_("Telephone"), "NUMBER", "TEL"},
363 {N_("Email"), "USERID", "EMAIL"},
364 {N_("Organization Name"), "ORGNAME", "ORG"},
365 {N_("Organization Unit"), "ORGUNIT", "ORG"},
366 {N_("Job Title"), "TITLE", NULL
},
367 {N_("Role"), "ROLE", NULL
},
368 {N_("Birthday"), "BDAY", NULL
},
369 {N_("Description"), "DESC", NULL
},
377 * The "vCard" tag's attribute list...
382 } const vcard_tag_attr_list
[] = {
383 {"prodid", "-//HandGen//NONSGML vGen v1.0//EN"},
384 {"version", "2.0", },
385 {"xmlns", "vcard-temp", },
391 * Insert a tag node into an PurpleXmlNode tree, recursively inserting parent tag
394 * Returns pointer to inserted node
396 * Note to hackers: this code is designed to be re-entrant (it's recursive--it
397 * calls itself), so don't put any "static"s in here!
399 static PurpleXmlNode
*insert_tag_to_parent_tag(PurpleXmlNode
*start
, const char *parent_tag
, const char *new_tag
)
401 PurpleXmlNode
*x
= NULL
;
404 * If the parent tag wasn't specified, see if we can get it
405 * from the vCard template struct.
407 if(parent_tag
== NULL
) {
408 const struct vcard_template
*vc_tp
= vcard_template_data
;
410 while(vc_tp
->label
!= NULL
) {
411 if(purple_strequal(vc_tp
->tag
, new_tag
)) {
412 parent_tag
= vc_tp
->ptag
;
420 * If we have a parent tag...
422 if(parent_tag
!= NULL
) {
424 * Try to get the parent node for a tag
426 if((x
= purple_xmlnode_get_child(start
, parent_tag
)) == NULL
) {
430 char *grand_parent
= g_strdup(parent_tag
);
433 if((parent
= strrchr(grand_parent
, '/')) != NULL
) {
435 x
= insert_tag_to_parent_tag(start
, grand_parent
, parent
);
437 x
= purple_xmlnode_new_child(start
, grand_parent
);
439 g_free(grand_parent
);
442 * We found *something* to be the parent node.
443 * Note: may be the "root" node!
446 if((y
= purple_xmlnode_get_child(x
, new_tag
)) != NULL
) {
453 * insert the new tag into its parent node
455 return(purple_xmlnode_new_child((x
== NULL
? start
: x
), new_tag
));
459 * Send vCard info to Jabber server
461 void jabber_set_info(PurpleConnection
*gc
, const char *info
)
465 JabberStream
*js
= purple_connection_get_protocol_data(gc
);
466 PurpleXmlNode
*vc_node
;
467 const struct tag_attr
*tag_attr
;
469 /* if we haven't grabbed the remote vcard yet, we can't
470 * assume that what we have here is correct */
471 if(!js
->vcard_fetched
) {
473 g_free(js
->initial_avatar_hash
);
474 image
= purple_buddy_icons_find_account_icon(purple_connection_get_account(gc
));
476 js
->initial_avatar_hash
= g_compute_checksum_for_data(
478 purple_image_get_data(image
),
479 purple_image_get_data_size(image
)
481 g_object_unref(image
);
483 js
->initial_avatar_hash
= NULL
;
488 if (js
->vcard_timer
) {
489 g_source_remove(js
->vcard_timer
);
493 g_free(js
->avatar_hash
);
494 js
->avatar_hash
= NULL
;
497 * Send only if there's actually any *information* to send
499 vc_node
= info
? purple_xmlnode_from_str(info
, -1) : NULL
;
501 if (vc_node
&& (!vc_node
->name
||
502 g_ascii_strncasecmp(vc_node
->name
, "vCard", 5))) {
503 purple_xmlnode_free(vc_node
);
507 if ((img
= purple_buddy_icons_find_account_icon(purple_connection_get_account(gc
)))) {
508 gconstpointer avatar_data
;
510 PurpleXmlNode
*photo
, *binval
, *type
;
514 vc_node
= purple_xmlnode_new("vCard");
515 for(tag_attr
= vcard_tag_attr_list
; tag_attr
->attr
!= NULL
; ++tag_attr
)
516 purple_xmlnode_set_attrib(vc_node
, tag_attr
->attr
, tag_attr
->value
);
519 avatar_data
= purple_image_get_data(img
);
520 avatar_len
= purple_image_get_data_size(img
);
521 /* Get rid of an old PHOTO if one exists.
522 * TODO: This may want to be modified to remove all old PHOTO
523 * children, at the moment some people have managed to get
524 * multiple PHOTO entries in their vCard. */
525 if((photo
= purple_xmlnode_get_child(vc_node
, "PHOTO"))) {
526 purple_xmlnode_free(photo
);
528 photo
= purple_xmlnode_new_child(vc_node
, "PHOTO");
529 type
= purple_xmlnode_new_child(photo
, "TYPE");
530 purple_xmlnode_insert_data(type
, "image/png", -1);
531 binval
= purple_xmlnode_new_child(photo
, "BINVAL");
532 enc
= g_base64_encode(avatar_data
, avatar_len
);
534 js
->avatar_hash
= g_compute_checksum_for_data(G_CHECKSUM_SHA1
,
535 avatar_data
, avatar_len
);
537 purple_xmlnode_insert_data(binval
, enc
, -1);
540 } else if (vc_node
) {
541 PurpleXmlNode
*photo
;
542 /* TODO: Remove all PHOTO children? (see above note) */
543 if ((photo
= purple_xmlnode_get_child(vc_node
, "PHOTO"))) {
544 purple_xmlnode_free(photo
);
548 if (vc_node
!= NULL
) {
549 iq
= jabber_iq_new(js
, JABBER_IQ_SET
);
550 purple_xmlnode_insert_child(iq
->node
, vc_node
);
553 /* Send presence to update vcard-temp:x:update */
554 jabber_presence_send(js
, FALSE
);
558 void jabber_set_buddy_icon(PurpleConnection
*gc
, PurpleImage
*img
)
560 PurpleAccount
*account
= purple_connection_get_account(gc
);
562 /* Publish the avatar as specified in XEP-0084 */
563 jabber_avatar_set(purple_connection_get_protocol_data(gc
), img
);
564 /* Set the image in our vCard */
565 jabber_set_info(gc
, purple_account_get_user_info(account
));
567 /* TODO: Fake image to ourselves, since a number of servers do not echo
568 * back our presence to us. To do this without uselessly copying the data
569 * of the image, we need purple_buddy_icons_set_for_user_image (i.e. takes
570 * an existing icon/stored image). */
574 * This is the callback from the "ok clicked" for "set vCard"
576 * Sets the vCard with data from PurpleRequestFields.
579 jabber_format_info(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
581 PurpleXmlNode
*vc_node
;
582 PurpleRequestField
*field
;
585 const struct vcard_template
*vc_tp
;
586 const struct tag_attr
*tag_attr
;
588 vc_node
= purple_xmlnode_new("vCard");
590 for(tag_attr
= vcard_tag_attr_list
; tag_attr
->attr
!= NULL
; ++tag_attr
)
591 purple_xmlnode_set_attrib(vc_node
, tag_attr
->attr
, tag_attr
->value
);
593 for (vc_tp
= vcard_template_data
; vc_tp
->label
!= NULL
; vc_tp
++) {
594 if (*vc_tp
->label
== '\0')
597 field
= purple_request_fields_get_field(fields
, vc_tp
->tag
);
598 text
= purple_request_field_string_get_value(field
);
601 if (text
!= NULL
&& *text
!= '\0') {
604 purple_debug_info("jabber", "Setting %s to '%s'\n", vc_tp
->tag
, text
);
606 if ((xp
= insert_tag_to_parent_tag(vc_node
,
607 NULL
, vc_tp
->tag
)) != NULL
) {
609 purple_xmlnode_insert_data(xp
, text
, -1);
614 p
= purple_xmlnode_to_str(vc_node
, NULL
);
615 purple_xmlnode_free(vc_node
);
617 purple_account_set_user_info(purple_connection_get_account(gc
), p
);
618 purple_serv_set_info(gc
, p
);
624 * This gets executed by the proto action
626 * Creates a new PurpleRequestFields struct, gets the XML-formatted user_info
627 * string (if any) into GSLists for the (multi-entry) edit dialog and
628 * calls the set_vcard dialog.
630 void jabber_setup_set_info(PurpleProtocolAction
*action
)
632 PurpleConnection
*gc
= (PurpleConnection
*) action
->connection
;
633 PurpleRequestFields
*fields
;
634 PurpleRequestFieldGroup
*group
;
635 PurpleRequestField
*field
;
636 const struct vcard_template
*vc_tp
;
637 const char *user_info
;
639 PurpleXmlNode
*x_vc_data
= NULL
;
641 fields
= purple_request_fields_new();
642 group
= purple_request_field_group_new(NULL
);
643 purple_request_fields_add_group(fields
, group
);
646 * Get existing, XML-formatted, user info
648 if((user_info
= purple_account_get_user_info(purple_connection_get_account(gc
))) != NULL
)
649 x_vc_data
= purple_xmlnode_from_str(user_info
, -1);
652 * Set up GSLists for edit with labels from "template," data from user info
654 for(vc_tp
= vcard_template_data
; vc_tp
->label
!= NULL
; ++vc_tp
) {
655 PurpleXmlNode
*data_node
;
656 if((vc_tp
->label
)[0] == '\0')
659 if (x_vc_data
!= NULL
) {
660 if(vc_tp
->ptag
== NULL
) {
661 data_node
= purple_xmlnode_get_child(x_vc_data
, vc_tp
->tag
);
663 gchar
*tag
= g_strdup_printf("%s/%s", vc_tp
->ptag
, vc_tp
->tag
);
664 data_node
= purple_xmlnode_get_child(x_vc_data
, tag
);
668 cdata
= purple_xmlnode_get_data(data_node
);
671 if(purple_strequal(vc_tp
->tag
, "DESC")) {
672 field
= purple_request_field_string_new(vc_tp
->tag
,
673 _(vc_tp
->label
), cdata
,
676 field
= purple_request_field_string_new(vc_tp
->tag
,
677 _(vc_tp
->label
), cdata
,
684 purple_request_field_group_add_field(group
, field
);
687 if(x_vc_data
!= NULL
)
688 purple_xmlnode_free(x_vc_data
);
690 purple_request_fields(gc
, _("Edit XMPP vCard"),
691 _("Edit XMPP vCard"),
692 _("All items below are optional. Enter only the "
693 "information with which you feel comfortable."),
695 _("Save"), G_CALLBACK(jabber_format_info
),
697 purple_request_cpar_from_connection(gc
),
701 /*---------------------------------------*/
702 /* End Jabber "set info" (vCard) support */
703 /*---------------------------------------*/
706 * end of that ancient crap that needs to die
709 static void jabber_buddy_info_destroy(JabberBuddyInfo
*jbi
)
711 /* Remove the timeout, which would otherwise trigger jabber_buddy_get_info_timeout() */
712 if (jbi
->timeout_handle
> 0)
713 g_source_remove(jbi
->timeout_handle
);
716 g_hash_table_destroy(jbi
->resources
);
717 g_free(jbi
->last_message
);
718 purple_notify_user_info_destroy(jbi
->user_info
);
723 add_jbr_info(JabberBuddyInfo
*jbi
, const char *resource
,
724 JabberBuddyResource
*jbr
)
726 JabberBuddyInfoResource
*jbir
;
727 PurpleNotifyUserInfo
*user_info
;
729 jbir
= g_hash_table_lookup(jbi
->resources
, resource
);
730 user_info
= jbi
->user_info
;
732 if (jbr
&& jbr
->client
.name
) {
734 g_strdup_printf("%s%s%s", jbr
->client
.name
,
735 (jbr
->client
.version
? " " : ""),
736 (jbr
->client
.version
? jbr
->client
.version
: ""));
737 /* TODO: Check whether it's correct to call prepend_pair_html,
738 or if we should be using prepend_pair_plaintext */
739 purple_notify_user_info_prepend_pair_html(user_info
, _("Client"), tmp
);
742 if (jbr
->client
.os
) {
743 /* TODO: Check whether it's correct to call prepend_pair_html,
744 or if we should be using prepend_pair_plaintext */
745 purple_notify_user_info_prepend_pair_html(user_info
, _("Operating System"), jbr
->client
.os
);
749 if (jbr
&& jbr
->tz_off
!= PURPLE_NO_TZ_OFF
) {
754 now_t
+= jbr
->tz_off
;
755 now
= gmtime(&now_t
);
758 g_strdup_printf("%s %c%02d%02d", purple_time_format(now
),
759 jbr
->tz_off
< 0 ? '-' : '+',
760 abs(jbr
->tz_off
/ (60*60)),
761 abs((jbr
->tz_off
% (60*60)) / 60));
762 purple_notify_user_info_prepend_pair_plaintext(user_info
, _("Local Time"), timestamp
);
766 if (jbir
&& jbir
->idle_seconds
> 0) {
767 char *idle
= purple_str_seconds_to_string(jbir
->idle_seconds
);
768 purple_notify_user_info_prepend_pair_plaintext(user_info
, _("Idle"), idle
);
776 const char *status_name
= jabber_buddy_state_get_name(jbr
->state
);
779 tmp
= purple_markup_escape_text(jbr
->status
, -1);
780 purdy
= purple_strdup_withhtml(tmp
);
783 if (purple_strequal(status_name
, purdy
))
787 tmp
= g_strdup_printf("%s%s%s", (status_name
? status_name
: ""),
788 ((status_name
&& purdy
) ? ": " : ""),
789 (purdy
? purdy
: ""));
790 purple_notify_user_info_prepend_pair_html(user_info
, _("Status"), tmp
);
792 g_snprintf(priority
, sizeof(priority
), "%d", jbr
->priority
);
793 purple_notify_user_info_prepend_pair_plaintext(user_info
, _("Priority"), priority
);
798 purple_notify_user_info_prepend_pair_plaintext(user_info
, _("Status"), _("Unknown"));
802 static void jabber_buddy_info_show_if_ready(JabberBuddyInfo
*jbi
)
805 JabberBuddyResource
*jbr
;
807 PurpleNotifyUserInfo
*user_info
;
813 user_info
= jbi
->user_info
;
814 resource_name
= jabber_get_resource(jbi
->jid
);
816 /* If we have one or more pairs from the vcard, put a section break above it */
817 if (g_queue_get_length(purple_notify_user_info_get_entries(user_info
)))
818 purple_notify_user_info_prepend_section_break(user_info
);
820 /* Add the information about the user's resource(s) */
822 jbr
= jabber_buddy_find_resource(jbi
->jb
, resource_name
);
823 add_jbr_info(jbi
, resource_name
, jbr
);
825 /* TODO: This is in priority-ascending order (lowest prio first), because
826 * everything is prepended. Is that ok? */
827 for (resources
= jbi
->jb
->resources
; resources
; resources
= resources
->next
) {
828 jbr
= resources
->data
;
830 /* put a section break between resources, this is not needed if
831 we are at the first, because one was already added for the vcard
833 if (resources
!= jbi
->jb
->resources
)
834 purple_notify_user_info_prepend_section_break(user_info
);
836 add_jbr_info(jbi
, jbr
->name
, jbr
);
839 /* TODO: Check whether it's correct to call prepend_pair_html,
840 or if we should be using prepend_pair_plaintext */
841 purple_notify_user_info_prepend_pair_html(user_info
, _("Resource"), jbr
->name
);
846 if (!jbi
->jb
->resources
) {
847 /* the buddy is offline */
848 gboolean is_domain
= jabber_jid_is_domain(jbi
->jid
);
850 if (jbi
->last_seconds
> 0) {
851 char *last
= purple_str_seconds_to_string(jbi
->last_seconds
);
852 gchar
*message
= NULL
;
853 const gchar
*title
= NULL
;
859 title
= _("Logged Off");
860 message
= g_strdup_printf(_("%s ago"), last
);
862 purple_notify_user_info_prepend_pair_plaintext(user_info
, title
, message
);
869 g_strdup_printf("%s%s%s", _("Offline"),
870 jbi
->last_message
? ": " : "",
871 jbi
->last_message
? jbi
->last_message
: "");
872 /* TODO: Check whether it's correct to call prepend_pair_html,
873 or if we should be using prepend_pair_plaintext */
874 purple_notify_user_info_prepend_pair_html(user_info
, _("Status"), status
);
879 g_free(resource_name
);
881 purple_notify_userinfo(jbi
->js
->gc
, jbi
->jid
, user_info
, NULL
, NULL
);
883 g_slist_free_full(jbi
->vcard_images
, g_object_unref
);
885 jbi
->js
->pending_buddy_info_requests
= g_slist_remove(jbi
->js
->pending_buddy_info_requests
, jbi
);
887 jabber_buddy_info_destroy(jbi
);
890 static void jabber_buddy_info_remove_id(JabberBuddyInfo
*jbi
, const char *id
)
898 l
= g_slist_find_custom(jbi
->ids
, id
, (GCompareFunc
)g_strcmp0
);
901 jbi
->ids
= g_slist_delete_link(jbi
->ids
, l
);
908 set_own_vcard_cb(gpointer data
)
910 JabberStream
*js
= data
;
911 PurpleAccount
*account
= purple_connection_get_account(js
->gc
);
915 jabber_set_info(js
->gc
, purple_account_get_user_info(account
));
920 static void jabber_vcard_save_mine(JabberStream
*js
, const char *from
,
921 JabberIqType type
, const char *id
,
922 PurpleXmlNode
*packet
, gpointer data
)
924 PurpleXmlNode
*vcard
, *photo
, *binval
;
925 char *txt
, *vcard_hash
= NULL
;
926 PurpleAccount
*account
;
928 if (type
== JABBER_IQ_ERROR
) {
929 PurpleXmlNode
*error
;
930 purple_debug_warning("jabber", "Server returned error while retrieving vCard\n");
932 error
= purple_xmlnode_get_child(packet
, "error");
933 if (!error
|| !purple_xmlnode_get_child(error
, "item-not-found"))
937 account
= purple_connection_get_account(js
->gc
);
939 if((vcard
= purple_xmlnode_get_child(packet
, "vCard")) ||
940 (vcard
= purple_xmlnode_get_child_with_namespace(packet
, "query", "vcard-temp")))
942 txt
= purple_xmlnode_to_str(vcard
, NULL
);
943 purple_account_set_user_info(account
, txt
);
946 /* if we have no vCard, then lets not overwrite what we might have locally */
949 js
->vcard_fetched
= TRUE
;
951 if (vcard
&& (photo
= purple_xmlnode_get_child(vcard
, "PHOTO")) &&
952 (binval
= purple_xmlnode_get_child(photo
, "BINVAL"))) {
954 char *bintext
= purple_xmlnode_get_data(binval
);
956 guchar
*data
= g_base64_decode(bintext
, &size
);
960 vcard_hash
= g_compute_checksum_for_data(
961 G_CHECKSUM_SHA1
, data
, size
);
967 /* Republish our vcard if the photo is different than the server's */
968 if (js
->initial_avatar_hash
&& !purple_strequal(vcard_hash
, js
->initial_avatar_hash
)) {
970 * Google Talk has developed the behavior that it will not accept
971 * a vcard set in the first 10 seconds (or so) of the connection;
972 * it returns an error (namespaces trimmed):
973 * <error code="500" type="wait"><internal-server-error/></error>.
976 js
->vcard_timer
= g_timeout_add_seconds(10, set_own_vcard_cb
,
979 jabber_set_info(js
->gc
, purple_account_get_user_info(account
));
980 } else if (vcard_hash
) {
981 /* A photo is in the vCard. Advertise its hash */
982 js
->avatar_hash
= vcard_hash
;
985 /* Send presence to update vcard-temp:x:update */
986 jabber_presence_send(js
, FALSE
);
992 void jabber_vcard_fetch_mine(JabberStream
*js
)
994 JabberIq
*iq
= jabber_iq_new(js
, JABBER_IQ_GET
);
996 PurpleXmlNode
*vcard
= purple_xmlnode_new_child(iq
->node
, "vCard");
997 purple_xmlnode_set_namespace(vcard
, "vcard-temp");
998 jabber_iq_set_callback(iq
, jabber_vcard_save_mine
, NULL
);
1003 static void jabber_vcard_parse(JabberStream
*js
, const char *from
,
1004 JabberIqType type
, const char *id
,
1005 PurpleXmlNode
*packet
, gpointer data
)
1009 char *serverside_alias
= NULL
;
1010 PurpleXmlNode
*vcard
;
1011 PurpleAccount
*account
;
1012 JabberBuddyInfo
*jbi
= data
;
1013 PurpleNotifyUserInfo
*user_info
;
1015 g_return_if_fail(jbi
!= NULL
);
1017 jabber_buddy_info_remove_id(jbi
, id
);
1019 if (type
== JABBER_IQ_ERROR
) {
1020 purple_debug_info("jabber", "Got error response for vCard\n");
1021 jabber_buddy_info_show_if_ready(jbi
);
1025 user_info
= jbi
->user_info
;
1026 account
= purple_connection_get_account(js
->gc
);
1027 bare_jid
= jabber_get_bare_jid(from
? from
: purple_account_get_username(account
));
1029 /* TODO: Is the query xmlns='vcard-temp' version of this still necessary? */
1030 if((vcard
= purple_xmlnode_get_child(packet
, "vCard")) ||
1031 (vcard
= purple_xmlnode_get_child_with_namespace(packet
, "query", "vcard-temp"))) {
1032 PurpleXmlNode
*child
;
1033 for(child
= vcard
->child
; child
; child
= child
->next
)
1035 PurpleXmlNode
*child2
;
1037 if(child
->type
!= PURPLE_XMLNODE_TYPE_TAG
)
1040 text
= purple_xmlnode_get_data(child
);
1041 if(text
&& purple_strequal(child
->name
, "FN")) {
1042 if (!serverside_alias
)
1043 serverside_alias
= g_strdup(text
);
1045 purple_notify_user_info_add_pair_plaintext(user_info
, _("Full Name"), text
);
1046 } else if(purple_strequal(child
->name
, "N")) {
1047 for(child2
= child
->child
; child2
; child2
= child2
->next
)
1051 if(child2
->type
!= PURPLE_XMLNODE_TYPE_TAG
)
1054 text2
= purple_xmlnode_get_data(child2
);
1055 if(text2
&& purple_strequal(child2
->name
, "FAMILY")) {
1056 purple_notify_user_info_add_pair_plaintext(user_info
, _("Family Name"), text2
);
1057 } else if(text2
&& purple_strequal(child2
->name
, "GIVEN")) {
1058 purple_notify_user_info_add_pair_plaintext(user_info
, _("Given Name"), text2
);
1059 } else if(text2
&& purple_strequal(child2
->name
, "MIDDLE")) {
1060 purple_notify_user_info_add_pair_plaintext(user_info
, _("Middle Name"), text2
);
1064 } else if(text
&& purple_strequal(child
->name
, "NICKNAME")) {
1065 /* Prefer the Nickcname to the Full Name as the serverside alias if it's not just part of the jid.
1066 * Ignore it if it's part of the jid. */
1067 if (strstr(bare_jid
, text
) == NULL
) {
1068 g_free(serverside_alias
);
1069 serverside_alias
= g_strdup(text
);
1071 purple_notify_user_info_add_pair_plaintext(user_info
, _("Nickname"), text
);
1073 } else if(text
&& purple_strequal(child
->name
, "BDAY")) {
1074 purple_notify_user_info_add_pair_plaintext(user_info
, _("Birthday"), text
);
1075 } else if(purple_strequal(child
->name
, "ADR")) {
1076 gboolean address_line_added
= FALSE
;
1078 for(child2
= child
->child
; child2
; child2
= child2
->next
)
1082 if(child2
->type
!= PURPLE_XMLNODE_TYPE_TAG
)
1085 text2
= purple_xmlnode_get_data(child2
);
1089 /* We do this here so that it's not added if all the child
1090 * elements are empty. */
1091 if (!address_line_added
)
1093 purple_notify_user_info_add_section_header(user_info
, _("Address"));
1094 address_line_added
= TRUE
;
1097 if(purple_strequal(child2
->name
, "POBOX")) {
1098 purple_notify_user_info_add_pair_plaintext(user_info
, _("P.O. Box"), text2
);
1099 } else if (purple_strequal(child2
->name
, "EXTADD") || purple_strequal(child2
->name
, "EXTADR")) {
1101 * EXTADD is correct, EXTADR is generated by other
1102 * clients. The next time someone reads this, remove
1105 purple_notify_user_info_add_pair_plaintext(user_info
, _("Extended Address"), text2
);
1106 } else if(purple_strequal(child2
->name
, "STREET")) {
1107 purple_notify_user_info_add_pair_plaintext(user_info
, _("Street Address"), text2
);
1108 } else if(purple_strequal(child2
->name
, "LOCALITY")) {
1109 purple_notify_user_info_add_pair_plaintext(user_info
, _("Locality"), text2
);
1110 } else if(purple_strequal(child2
->name
, "REGION")) {
1111 purple_notify_user_info_add_pair_plaintext(user_info
, _("Region"), text2
);
1112 } else if(purple_strequal(child2
->name
, "PCODE")) {
1113 purple_notify_user_info_add_pair_plaintext(user_info
, _("Postal Code"), text2
);
1114 } else if(purple_strequal(child2
->name
, "CTRY")
1115 || purple_strequal(child2
->name
, "COUNTRY")) {
1116 purple_notify_user_info_add_pair_plaintext(user_info
, _("Country"), text2
);
1121 if (address_line_added
)
1122 purple_notify_user_info_add_section_break(user_info
);
1124 } else if(purple_strequal(child
->name
, "TEL")) {
1126 if((child2
= purple_xmlnode_get_child(child
, "NUMBER"))) {
1127 /* show what kind of number it is */
1128 number
= purple_xmlnode_get_data(child2
);
1130 purple_notify_user_info_add_pair_plaintext(user_info
, _("Telephone"), number
);
1133 } else if((number
= purple_xmlnode_get_data(child
))) {
1134 /* lots of clients (including purple) do this, but it's
1136 purple_notify_user_info_add_pair_plaintext(user_info
, _("Telephone"), number
);
1139 } else if(purple_strequal(child
->name
, "EMAIL")) {
1140 char *userid
, *escaped
;
1141 if((child2
= purple_xmlnode_get_child(child
, "USERID"))) {
1142 /* show what kind of email it is */
1143 userid
= purple_xmlnode_get_data(child2
);
1146 escaped
= g_markup_escape_text(userid
, -1);
1147 mailto
= g_strdup_printf("<a href=\"mailto:%s\">%s</a>", escaped
, escaped
);
1148 purple_notify_user_info_add_pair_html(user_info
, _("Email"), mailto
);
1154 } else if((userid
= purple_xmlnode_get_data(child
))) {
1155 /* lots of clients (including purple) do this, but it's
1159 escaped
= g_markup_escape_text(userid
, -1);
1160 mailto
= g_strdup_printf("<a href=\"mailto:%s\">%s</a>", escaped
, escaped
);
1161 purple_notify_user_info_add_pair_html(user_info
, _("Email"), mailto
);
1167 } else if(purple_strequal(child
->name
, "ORG")) {
1168 for(child2
= child
->child
; child2
; child2
= child2
->next
)
1172 if(child2
->type
!= PURPLE_XMLNODE_TYPE_TAG
)
1175 text2
= purple_xmlnode_get_data(child2
);
1176 if(text2
&& purple_strequal(child2
->name
, "ORGNAME")) {
1177 purple_notify_user_info_add_pair_plaintext(user_info
, _("Organization Name"), text2
);
1178 } else if(text2
&& purple_strequal(child2
->name
, "ORGUNIT")) {
1179 purple_notify_user_info_add_pair_plaintext(user_info
, _("Organization Unit"), text2
);
1183 } else if(text
&& purple_strequal(child
->name
, "TITLE")) {
1184 purple_notify_user_info_add_pair_plaintext(user_info
, _("Job Title"), text
);
1185 } else if(text
&& purple_strequal(child
->name
, "ROLE")) {
1186 purple_notify_user_info_add_pair_plaintext(user_info
, _("Role"), text
);
1187 } else if(text
&& purple_strequal(child
->name
, "DESC")) {
1188 purple_notify_user_info_add_pair_plaintext(user_info
, _("Description"), text
);
1189 } else if(purple_strequal(child
->name
, "PHOTO") ||
1190 purple_strequal(child
->name
, "LOGO")) {
1191 char *bintext
= NULL
;
1192 PurpleXmlNode
*binval
;
1194 if ((binval
= purple_xmlnode_get_child(child
, "BINVAL")) &&
1195 (bintext
= purple_xmlnode_get_data(binval
))) {
1198 gboolean photo
= purple_strequal(child
->name
, "PHOTO");
1200 data
= g_base64_decode(bintext
, &size
);
1207 img
= purple_image_new_from_data(data
, size
);
1208 img_id
= purple_image_store_add(img
);
1210 jbi
->vcard_images
= g_slist_prepend(jbi
->vcard_images
, img
);
1211 img_text
= g_strdup_printf("<img src='"
1212 PURPLE_IMAGE_STORE_PROTOCOL
"%u'>", img_id
);
1214 purple_notify_user_info_add_pair_html(user_info
, (photo
? _("Photo") : _("Logo")), img_text
);
1216 hash
= g_compute_checksum_for_data(G_CHECKSUM_SHA1
, data
, size
);
1217 purple_buddy_icons_set_for_user(account
, bare_jid
, data
, size
, hash
);
1228 if (serverside_alias
) {
1230 /* If we found a serverside alias, set it and tell the core */
1231 purple_serv_got_alias(js
->gc
, bare_jid
, serverside_alias
);
1232 b
= purple_blist_find_buddy(account
, bare_jid
);
1234 purple_blist_node_set_string((PurpleBlistNode
*)b
, "servernick", serverside_alias
);
1237 g_free(serverside_alias
);
1242 jabber_buddy_info_show_if_ready(jbi
);
1245 static void jabber_buddy_info_resource_free(gpointer data
)
1247 JabberBuddyInfoResource
*jbri
= data
;
1251 static guint
jbir_hash(gconstpointer v
)
1254 return g_str_hash(v
);
1259 static gboolean
jbir_equal(gconstpointer v1
, gconstpointer v2
)
1261 const gchar
*resource_1
= v1
;
1262 const gchar
*resource_2
= v2
;
1264 return purple_strequal(resource_1
, resource_2
);
1267 static void jabber_version_parse(JabberStream
*js
, const char *from
,
1268 JabberIqType type
, const char *id
,
1269 PurpleXmlNode
*packet
, gpointer data
)
1271 JabberBuddyInfo
*jbi
= data
;
1272 PurpleXmlNode
*query
;
1273 char *resource_name
;
1275 g_return_if_fail(jbi
!= NULL
);
1277 jabber_buddy_info_remove_id(jbi
, id
);
1282 resource_name
= jabber_get_resource(from
);
1285 if (type
== JABBER_IQ_RESULT
) {
1286 if((query
= purple_xmlnode_get_child(packet
, "query"))) {
1287 JabberBuddyResource
*jbr
= jabber_buddy_find_resource(jbi
->jb
, resource_name
);
1289 PurpleXmlNode
*node
;
1290 if((node
= purple_xmlnode_get_child(query
, "name"))) {
1291 jbr
->client
.name
= purple_xmlnode_get_data(node
);
1293 if((node
= purple_xmlnode_get_child(query
, "version"))) {
1294 jbr
->client
.version
= purple_xmlnode_get_data(node
);
1296 if((node
= purple_xmlnode_get_child(query
, "os"))) {
1297 jbr
->client
.os
= purple_xmlnode_get_data(node
);
1302 g_free(resource_name
);
1305 jabber_buddy_info_show_if_ready(jbi
);
1308 static void jabber_last_parse(JabberStream
*js
, const char *from
,
1309 JabberIqType type
, const char *id
,
1310 PurpleXmlNode
*packet
, gpointer data
)
1312 JabberBuddyInfo
*jbi
= data
;
1313 PurpleXmlNode
*query
;
1314 char *resource_name
;
1315 const char *seconds
;
1317 g_return_if_fail(jbi
!= NULL
);
1319 jabber_buddy_info_remove_id(jbi
, id
);
1324 resource_name
= jabber_get_resource(from
);
1327 if (type
== JABBER_IQ_RESULT
) {
1328 if((query
= purple_xmlnode_get_child(packet
, "query"))) {
1329 seconds
= purple_xmlnode_get_attrib(query
, "seconds");
1332 long sec
= strtol(seconds
, &end
, 10);
1333 JabberBuddy
*jb
= NULL
;
1334 char *resource
= NULL
;
1335 char *buddy_name
= NULL
;
1336 JabberBuddyResource
*jbr
= NULL
;
1338 if(end
!= seconds
) {
1339 JabberBuddyInfoResource
*jbir
= g_hash_table_lookup(jbi
->resources
, resource_name
);
1341 jbir
->idle_seconds
= sec
;
1344 /* Update the idle time of the buddy resource, if we got it.
1345 This will correct the value when a server doesn't mark
1346 delayed presence and we got the presence when signing on */
1347 jb
= jabber_buddy_find(js
, from
, FALSE
);
1349 resource
= jabber_get_resource(from
);
1350 buddy_name
= jabber_get_bare_jid(from
);
1351 /* if the resource already has an idle time set, we
1352 must have gotten it originally from a presence. In
1353 this case we update it. Otherwise don't update it, to
1354 avoid setting an idle and not getting informed about
1355 the resource getting unidle */
1356 if (resource
&& buddy_name
) {
1357 jbr
= jabber_buddy_find_resource(jb
, resource
);
1361 jbr
->idle
= time(NULL
) - sec
;
1367 jabber_buddy_find_resource(jb
, NULL
)) {
1368 purple_protocol_got_user_idle(purple_connection_get_account(js
->gc
),
1369 buddy_name
, jbr
->idle
, jbr
->idle
);
1380 g_free(resource_name
);
1383 jabber_buddy_info_show_if_ready(jbi
);
1386 static void jabber_last_offline_parse(JabberStream
*js
, const char *from
,
1387 JabberIqType type
, const char *id
,
1388 PurpleXmlNode
*packet
, gpointer data
)
1390 JabberBuddyInfo
*jbi
= data
;
1391 PurpleXmlNode
*query
;
1392 const char *seconds
;
1394 g_return_if_fail(jbi
!= NULL
);
1396 jabber_buddy_info_remove_id(jbi
, id
);
1398 if (type
== JABBER_IQ_RESULT
) {
1399 if((query
= purple_xmlnode_get_child(packet
, "query"))) {
1400 seconds
= purple_xmlnode_get_attrib(query
, "seconds");
1403 long sec
= strtol(seconds
, &end
, 10);
1404 if(end
!= seconds
) {
1405 jbi
->last_seconds
= sec
;
1408 jbi
->last_message
= purple_xmlnode_get_data(query
);
1412 jabber_buddy_info_show_if_ready(jbi
);
1415 static void jabber_time_parse(JabberStream
*js
, const char *from
,
1416 JabberIqType type
, const char *id
,
1417 PurpleXmlNode
*packet
, gpointer data
)
1419 JabberBuddyInfo
*jbi
= data
;
1420 JabberBuddyResource
*jbr
;
1421 char *resource_name
;
1423 g_return_if_fail(jbi
!= NULL
);
1425 jabber_buddy_info_remove_id(jbi
, id
);
1430 resource_name
= jabber_get_resource(from
);
1431 jbr
= resource_name
? jabber_buddy_find_resource(jbi
->jb
, resource_name
) : NULL
;
1432 g_free(resource_name
);
1434 if (type
== JABBER_IQ_RESULT
) {
1435 PurpleXmlNode
*time
= purple_xmlnode_get_child(packet
, "time");
1436 PurpleXmlNode
*tzo
= time
? purple_xmlnode_get_child(time
, "tzo") : NULL
;
1437 char *tzo_data
= tzo
? purple_xmlnode_get_data(tzo
) : NULL
;
1441 if (tzo_data
[0] == 'Z' && tzo_data
[1] == '\0') {
1444 gboolean offset_positive
= (tzo_data
[0] == '+');
1446 if (((*c
== '+' || *c
== '-') && (c
= c
+ 1)) &&
1447 sscanf(c
, "%02d:%02d", &hours
, &minutes
) == 2) {
1448 jbr
->tz_off
= 60*60*hours
+ 60*minutes
;
1449 if (!offset_positive
)
1452 purple_debug_info("jabber", "Ignoring malformed timezone %s",
1462 jabber_buddy_info_show_if_ready(jbi
);
1465 void jabber_buddy_remove_all_pending_buddy_info_requests(JabberStream
*js
)
1467 if (js
->pending_buddy_info_requests
)
1469 JabberBuddyInfo
*jbi
;
1470 GSList
*l
= js
->pending_buddy_info_requests
;
1474 g_slist_free(jbi
->ids
);
1475 jabber_buddy_info_destroy(jbi
);
1480 g_slist_free(js
->pending_buddy_info_requests
);
1481 js
->pending_buddy_info_requests
= NULL
;
1485 static gboolean
jabber_buddy_get_info_timeout(gpointer data
)
1487 JabberBuddyInfo
*jbi
= data
;
1489 /* remove the pending callbacks */
1491 char *id
= jbi
->ids
->data
;
1492 jabber_iq_remove_callback_by_id(jbi
->js
, id
);
1493 jbi
->ids
= g_slist_remove(jbi
->ids
, id
);
1497 jbi
->js
->pending_buddy_info_requests
= g_slist_remove(jbi
->js
->pending_buddy_info_requests
, jbi
);
1498 jbi
->timeout_handle
= 0;
1500 jabber_buddy_info_show_if_ready(jbi
);
1505 static gboolean
_client_is_blacklisted(JabberBuddyResource
*jbr
, const char *ns
)
1507 /* can't be blacklisted if we don't know what you're running yet */
1508 if(!jbr
->client
.name
)
1511 if(purple_strequal(ns
, NS_LAST_ACTIVITY
)) {
1512 if(purple_strequal(jbr
->client
.name
, "Trillian")) {
1513 /* verified by nwalp 2007/05/09 */
1514 if(purple_strequal(jbr
->client
.version
, "3.1.0.121") ||
1515 /* verified by nwalp 2007/09/19 */
1516 purple_strequal(jbr
->client
.version
, "3.1.7.0")) {
1526 dispatch_queries_for_resource(JabberStream
*js
, JabberBuddyInfo
*jbi
,
1527 gboolean is_bare_jid
, const char *jid
,
1528 JabberBuddyResource
*jbr
)
1531 JabberBuddyInfoResource
*jbir
;
1532 char *full_jid
= NULL
;
1535 if (is_bare_jid
&& jbr
->name
) {
1536 full_jid
= g_strdup_printf("%s/%s", jid
, jbr
->name
);
1541 jbir
= g_new0(JabberBuddyInfoResource
, 1);
1542 g_hash_table_insert(jbi
->resources
, g_strdup(jbr
->name
), jbir
);
1544 if(!jbr
->client
.name
) {
1545 iq
= jabber_iq_new_query(js
, JABBER_IQ_GET
, "jabber:iq:version");
1546 purple_xmlnode_set_attrib(iq
->node
, "to", to
);
1547 jabber_iq_set_callback(iq
, jabber_version_parse
, jbi
);
1548 jbi
->ids
= g_slist_prepend(jbi
->ids
, g_strdup(iq
->id
));
1552 /* this is to fix the feeling of irritation I get when trying
1553 * to get info on a friend running Trillian, which doesn't
1554 * respond (with an error or otherwise) to jabber:iq:last
1555 * requests. There are a number of Trillian users in my
1557 if(!_client_is_blacklisted(jbr
, NS_LAST_ACTIVITY
)) {
1558 iq
= jabber_iq_new_query(js
, JABBER_IQ_GET
, NS_LAST_ACTIVITY
);
1559 purple_xmlnode_set_attrib(iq
->node
, "to", to
);
1560 jabber_iq_set_callback(iq
, jabber_last_parse
, jbi
);
1561 jbi
->ids
= g_slist_prepend(jbi
->ids
, g_strdup(iq
->id
));
1565 if (jbr
->tz_off
== PURPLE_NO_TZ_OFF
&&
1567 jabber_resource_has_capability(jbr
, NS_ENTITY_TIME
))) {
1568 PurpleXmlNode
*child
;
1569 iq
= jabber_iq_new(js
, JABBER_IQ_GET
);
1570 purple_xmlnode_set_attrib(iq
->node
, "to", to
);
1571 child
= purple_xmlnode_new_child(iq
->node
, "time");
1572 purple_xmlnode_set_namespace(child
, NS_ENTITY_TIME
);
1573 jabber_iq_set_callback(iq
, jabber_time_parse
, jbi
);
1574 jbi
->ids
= g_slist_prepend(jbi
->ids
, g_strdup(iq
->id
));
1581 static void jabber_buddy_get_info_for_jid(JabberStream
*js
, const char *jid
)
1584 PurpleXmlNode
*vcard
;
1587 JabberBuddyInfo
*jbi
;
1589 gboolean is_bare_jid
;
1591 jb
= jabber_buddy_find(js
, jid
, TRUE
);
1597 slash
= strchr(jid
, '/');
1598 is_bare_jid
= (slash
== NULL
);
1600 jbi
= g_new0(JabberBuddyInfo
, 1);
1601 jbi
->jid
= g_strdup(jid
);
1604 jbi
->resources
= g_hash_table_new_full(jbir_hash
, jbir_equal
, g_free
, jabber_buddy_info_resource_free
);
1605 jbi
->user_info
= purple_notify_user_info_new();
1607 iq
= jabber_iq_new(js
, JABBER_IQ_GET
);
1609 purple_xmlnode_set_attrib(iq
->node
, "to", jid
);
1610 vcard
= purple_xmlnode_new_child(iq
->node
, "vCard");
1611 purple_xmlnode_set_namespace(vcard
, "vcard-temp");
1613 jabber_iq_set_callback(iq
, jabber_vcard_parse
, jbi
);
1614 jbi
->ids
= g_slist_prepend(jbi
->ids
, g_strdup(iq
->id
));
1619 if (jb
->resources
) {
1620 for(resources
= jb
->resources
; resources
; resources
= resources
->next
) {
1621 JabberBuddyResource
*jbr
= resources
->data
;
1622 dispatch_queries_for_resource(js
, jbi
, is_bare_jid
, jid
, jbr
);
1625 /* user is offline, send a jabber:iq:last to find out last time online */
1626 iq
= jabber_iq_new_query(js
, JABBER_IQ_GET
, NS_LAST_ACTIVITY
);
1627 purple_xmlnode_set_attrib(iq
->node
, "to", jid
);
1628 jabber_iq_set_callback(iq
, jabber_last_offline_parse
, jbi
);
1629 jbi
->ids
= g_slist_prepend(jbi
->ids
, g_strdup(iq
->id
));
1633 JabberBuddyResource
*jbr
= jabber_buddy_find_resource(jb
, slash
+ 1);
1635 dispatch_queries_for_resource(js
, jbi
, is_bare_jid
, jid
, jbr
);
1637 purple_debug_warning("jabber", "jabber_buddy_get_info_for_jid() "
1638 "was passed JID %s, but there is no corresponding "
1639 "JabberBuddyResource!\n", jid
);
1642 js
->pending_buddy_info_requests
= g_slist_prepend(js
->pending_buddy_info_requests
, jbi
);
1643 jbi
->timeout_handle
= g_timeout_add_seconds(30, jabber_buddy_get_info_timeout
, jbi
);
1646 void jabber_buddy_get_info(PurpleConnection
*gc
, const char *who
)
1648 JabberStream
*js
= purple_connection_get_protocol_data(gc
);
1649 JabberID
*jid
= jabber_id_new(who
);
1654 if (jid
->node
&& jabber_chat_find(js
, jid
->node
, jid
->domain
)) {
1655 /* For a conversation, include the resource (indicates the user). */
1656 jabber_buddy_get_info_for_jid(js
, who
);
1658 char *bare_jid
= jabber_get_bare_jid(who
);
1659 jabber_buddy_get_info_for_jid(js
, bare_jid
);
1663 jabber_id_free(jid
);
1666 static void jabber_buddy_set_invisibility(JabberStream
*js
, const char *who
,
1669 PurplePresence
*gpresence
;
1670 PurpleAccount
*account
;
1671 PurpleStatus
*status
;
1672 JabberBuddy
*jb
= jabber_buddy_find(js
, who
, TRUE
);
1673 PurpleXmlNode
*presence
;
1674 JabberBuddyState state
;
1678 account
= purple_connection_get_account(js
->gc
);
1679 gpresence
= purple_account_get_presence(account
);
1680 status
= purple_presence_get_active_status(gpresence
);
1682 purple_status_to_jabber(status
, &state
, &msg
, &priority
);
1683 presence
= jabber_presence_create_js(js
, state
, msg
, priority
);
1687 purple_xmlnode_set_attrib(presence
, "to", who
);
1689 purple_xmlnode_set_attrib(presence
, "type", "invisible");
1690 jb
->invisible
|= JABBER_INVIS_BUDDY
;
1692 jb
->invisible
&= ~JABBER_INVIS_BUDDY
;
1695 jabber_send(js
, presence
);
1696 purple_xmlnode_free(presence
);
1699 static void jabber_buddy_make_invisible(PurpleBlistNode
*node
, gpointer data
)
1702 PurpleConnection
*gc
;
1705 g_return_if_fail(PURPLE_IS_BUDDY(node
));
1707 buddy
= (PurpleBuddy
*) node
;
1708 gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1709 js
= purple_connection_get_protocol_data(gc
);
1711 jabber_buddy_set_invisibility(js
, purple_buddy_get_name(buddy
), TRUE
);
1714 static void jabber_buddy_make_visible(PurpleBlistNode
*node
, gpointer data
)
1717 PurpleConnection
*gc
;
1720 g_return_if_fail(PURPLE_IS_BUDDY(node
));
1722 buddy
= (PurpleBuddy
*) node
;
1723 gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1724 js
= purple_connection_get_protocol_data(gc
);
1726 jabber_buddy_set_invisibility(js
, purple_buddy_get_name(buddy
), FALSE
);
1729 static void cancel_presence_notification(gpointer data
)
1732 PurpleConnection
*gc
;
1736 gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1737 js
= purple_connection_get_protocol_data(gc
);
1739 jabber_presence_subscription_set(js
, purple_buddy_get_name(buddy
), "unsubscribed");
1743 jabber_buddy_cancel_presence_notification(PurpleBlistNode
*node
,
1747 PurpleAccount
*account
;
1748 PurpleConnection
*gc
;
1752 g_return_if_fail(PURPLE_IS_BUDDY(node
));
1754 buddy
= (PurpleBuddy
*) node
;
1755 name
= purple_buddy_get_name(buddy
);
1756 account
= purple_buddy_get_account(buddy
);
1757 gc
= purple_account_get_connection(account
);
1759 msg
= g_strdup_printf(_("%s will no longer be able to see your status "
1760 "updates. Do you want to continue?"), name
);
1761 purple_request_yes_no(gc
, NULL
, _("Cancel Presence Notification"),
1762 msg
, 0 /* Yes */, purple_request_cpar_from_account(account
), buddy
,
1763 cancel_presence_notification
, NULL
/* Do nothing */);
1767 static void jabber_buddy_rerequest_auth(PurpleBlistNode
*node
, gpointer data
)
1770 PurpleConnection
*gc
;
1773 g_return_if_fail(PURPLE_IS_BUDDY(node
));
1775 buddy
= (PurpleBuddy
*) node
;
1776 gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1777 js
= purple_connection_get_protocol_data(gc
);
1779 jabber_presence_subscription_set(js
, purple_buddy_get_name(buddy
), "subscribe");
1783 static void jabber_buddy_unsubscribe(PurpleBlistNode
*node
, gpointer data
)
1786 PurpleConnection
*gc
;
1789 g_return_if_fail(PURPLE_IS_BUDDY(node
));
1791 buddy
= (PurpleBuddy
*) node
;
1792 gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1793 js
= purple_connection_get_protocol_data(gc
);
1795 jabber_presence_subscription_set(js
, purple_buddy_get_name(buddy
), "unsubscribe");
1798 static void jabber_buddy_login(PurpleBlistNode
*node
, gpointer data
) {
1799 if(PURPLE_IS_BUDDY(node
)) {
1800 /* simply create a directed presence of the current status */
1801 PurpleBuddy
*buddy
= (PurpleBuddy
*) node
;
1802 PurpleConnection
*gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1803 JabberStream
*js
= purple_connection_get_protocol_data(gc
);
1804 PurpleAccount
*account
= purple_connection_get_account(gc
);
1805 PurplePresence
*gpresence
= purple_account_get_presence(account
);
1806 PurpleStatus
*status
= purple_presence_get_active_status(gpresence
);
1807 PurpleXmlNode
*presence
;
1808 JabberBuddyState state
;
1812 purple_status_to_jabber(status
, &state
, &msg
, &priority
);
1813 presence
= jabber_presence_create_js(js
, state
, msg
, priority
);
1817 purple_xmlnode_set_attrib(presence
, "to", purple_buddy_get_name(buddy
));
1819 jabber_send(js
, presence
);
1820 purple_xmlnode_free(presence
);
1824 static void jabber_buddy_logout(PurpleBlistNode
*node
, gpointer data
) {
1825 if(PURPLE_IS_BUDDY(node
)) {
1826 /* simply create a directed unavailable presence */
1827 PurpleBuddy
*buddy
= (PurpleBuddy
*) node
;
1828 PurpleConnection
*gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1829 JabberStream
*js
= purple_connection_get_protocol_data(gc
);
1830 PurpleXmlNode
*presence
;
1832 presence
= jabber_presence_create_js(js
, JABBER_BUDDY_STATE_UNAVAILABLE
, NULL
, 0);
1834 purple_xmlnode_set_attrib(presence
, "to", purple_buddy_get_name(buddy
));
1836 jabber_send(js
, presence
);
1837 purple_xmlnode_free(presence
);
1841 static GList
*jabber_buddy_menu(PurpleBuddy
*buddy
)
1843 PurpleConnection
*gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1844 JabberStream
*js
= purple_connection_get_protocol_data(gc
);
1845 const char *name
= purple_buddy_get_name(buddy
);
1846 JabberBuddy
*jb
= jabber_buddy_find(js
, name
, TRUE
);
1850 PurpleActionMenu
*act
;
1855 if (js
->protocol_version
.major
== 0 && js
->protocol_version
.minor
== 9 &&
1856 jb
!= js
->user_jb
) {
1857 if(jb
->invisible
& JABBER_INVIS_BUDDY
) {
1858 act
= purple_action_menu_new(_("Un-hide From"),
1859 PURPLE_CALLBACK(jabber_buddy_make_visible
),
1862 act
= purple_action_menu_new(_("Temporarily Hide From"),
1863 PURPLE_CALLBACK(jabber_buddy_make_invisible
),
1866 m
= g_list_append(m
, act
);
1869 if(jb
->subscription
& JABBER_SUB_FROM
&& jb
!= js
->user_jb
) {
1870 act
= purple_action_menu_new(_("Cancel Presence Notification"),
1871 PURPLE_CALLBACK(jabber_buddy_cancel_presence_notification
),
1873 m
= g_list_append(m
, act
);
1876 if(!(jb
->subscription
& JABBER_SUB_TO
)) {
1877 act
= purple_action_menu_new(_("(Re-)Request authorization"),
1878 PURPLE_CALLBACK(jabber_buddy_rerequest_auth
),
1880 m
= g_list_append(m
, act
);
1882 } else if (jb
!= js
->user_jb
) {
1884 /* shouldn't this just happen automatically when the buddy is
1886 act
= purple_action_menu_new(_("Unsubscribe"),
1887 PURPLE_CALLBACK(jabber_buddy_unsubscribe
),
1889 m
= g_list_append(m
, act
);
1892 if (js
->googletalk
) {
1893 act
= purple_action_menu_new(_("Initiate _Chat"),
1894 PURPLE_CALLBACK(google_buddy_node_chat
),
1896 m
= g_list_append(m
, act
);
1900 * This if-condition implements parts of XEP-0100: Gateway Interaction
1902 * According to stpeter, there is no way to know if a jid on the roster is a gateway without sending a disco#info.
1903 * However, since the gateway might appear offline to us, we cannot get that information. Therefore, I just assume
1904 * that gateways on the roster can be identified by having no '@' in their jid. This is a faily safe assumption, since
1905 * people don't tend to have a server or other service there.
1907 * TODO: Use disco#info...
1909 if (strchr(name
, '@') == NULL
) {
1910 act
= purple_action_menu_new(_("Log In"),
1911 PURPLE_CALLBACK(jabber_buddy_login
),
1913 m
= g_list_append(m
, act
);
1914 act
= purple_action_menu_new(_("Log Out"),
1915 PURPLE_CALLBACK(jabber_buddy_logout
),
1917 m
= g_list_append(m
, act
);
1920 /* add all ad hoc commands to the action menu */
1921 for(jbrs
= jb
->resources
; jbrs
; jbrs
= g_list_next(jbrs
)) {
1922 JabberBuddyResource
*jbr
= jbrs
->data
;
1926 for(commands
= jbr
->commands
; commands
; commands
= g_list_next(commands
)) {
1927 JabberAdHocCommands
*cmd
= commands
->data
;
1928 act
= purple_action_menu_new(cmd
->name
, PURPLE_CALLBACK(jabber_adhoc_execute_action
), cmd
, NULL
);
1929 m
= g_list_append(m
, act
);
1937 jabber_blist_node_menu(PurpleBlistNode
*node
)
1939 if(PURPLE_IS_BUDDY(node
)) {
1940 return jabber_buddy_menu((PurpleBuddy
*) node
);
1947 static void user_search_result_add_buddy_cb(PurpleConnection
*gc
, GList
*row
, void *user_data
)
1949 /* XXX find out the jid */
1950 purple_blist_request_add_buddy(purple_connection_get_account(gc
),
1951 g_list_nth_data(row
, 0), NULL
, NULL
);
1954 static void user_search_result_cb(JabberStream
*js
, const char *from
,
1955 JabberIqType type
, const char *id
,
1956 PurpleXmlNode
*packet
, gpointer data
)
1958 PurpleNotifySearchResults
*results
;
1959 PurpleNotifySearchColumn
*column
;
1960 PurpleXmlNode
*x
, *query
, *item
, *field
;
1962 /* XXX error checking? */
1963 if(!(query
= purple_xmlnode_get_child(packet
, "query")))
1966 results
= purple_notify_searchresults_new();
1967 if((x
= purple_xmlnode_get_child_with_namespace(query
, "x", "jabber:x:data"))) {
1968 PurpleXmlNode
*reported
;
1969 GSList
*column_vars
= NULL
;
1971 purple_debug_info("jabber", "new-skool\n");
1973 if((reported
= purple_xmlnode_get_child(x
, "reported"))) {
1974 PurpleXmlNode
*field
= purple_xmlnode_get_child(reported
, "field");
1976 const char *var
= purple_xmlnode_get_attrib(field
, "var");
1977 const char *label
= purple_xmlnode_get_attrib(field
, "label");
1979 column
= purple_notify_searchresults_column_new(label
? label
: var
);
1980 purple_notify_searchresults_column_add(results
, column
);
1981 column_vars
= g_slist_append(column_vars
, (char *)var
);
1983 field
= purple_xmlnode_get_next_twin(field
);
1987 item
= purple_xmlnode_get_child(x
, "item");
1991 PurpleXmlNode
*valuenode
;
1994 for (l
= column_vars
; l
!= NULL
; l
= l
->next
) {
1996 * Build a row containing the strings that correspond
1997 * to each column of the search results.
1999 for (field
= purple_xmlnode_get_child(item
, "field");
2001 field
= purple_xmlnode_get_next_twin(field
))
2003 if ((var
= purple_xmlnode_get_attrib(field
, "var")) &&
2004 purple_strequal(var
, l
->data
) &&
2005 (valuenode
= purple_xmlnode_get_child(field
, "value")))
2007 char *value
= purple_xmlnode_get_data(valuenode
);
2008 row
= g_list_append(row
, value
);
2013 /* No data for this column */
2014 row
= g_list_append(row
, NULL
);
2016 purple_notify_searchresults_row_add(results
, row
);
2017 item
= purple_xmlnode_get_next_twin(item
);
2020 g_slist_free(column_vars
);
2023 purple_debug_info("jabber", "old-skool\n");
2025 column
= purple_notify_searchresults_column_new(_("JID"));
2026 purple_notify_searchresults_column_add(results
, column
);
2027 column
= purple_notify_searchresults_column_new(_("First Name"));
2028 purple_notify_searchresults_column_add(results
, column
);
2029 column
= purple_notify_searchresults_column_new(_("Last Name"));
2030 purple_notify_searchresults_column_add(results
, column
);
2031 column
= purple_notify_searchresults_column_new(_("Nickname"));
2032 purple_notify_searchresults_column_add(results
, column
);
2033 column
= purple_notify_searchresults_column_new(_("Email"));
2034 purple_notify_searchresults_column_add(results
, column
);
2036 for(item
= purple_xmlnode_get_child(query
, "item"); item
; item
= purple_xmlnode_get_next_twin(item
)) {
2038 PurpleXmlNode
*node
;
2041 if(!(jid
= purple_xmlnode_get_attrib(item
, "jid")))
2044 row
= g_list_append(row
, g_strdup(jid
));
2045 node
= purple_xmlnode_get_child(item
, "first");
2046 row
= g_list_append(row
, node
? purple_xmlnode_get_data(node
) : NULL
);
2047 node
= purple_xmlnode_get_child(item
, "last");
2048 row
= g_list_append(row
, node
? purple_xmlnode_get_data(node
) : NULL
);
2049 node
= purple_xmlnode_get_child(item
, "nick");
2050 row
= g_list_append(row
, node
? purple_xmlnode_get_data(node
) : NULL
);
2051 node
= purple_xmlnode_get_child(item
, "email");
2052 row
= g_list_append(row
, node
? purple_xmlnode_get_data(node
) : NULL
);
2053 purple_debug_info("jabber", "row=%p\n", row
);
2054 purple_notify_searchresults_row_add(results
, row
);
2058 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_ADD
,
2059 user_search_result_add_buddy_cb
);
2061 purple_notify_searchresults(js
->gc
, NULL
, NULL
, _("The following are the results of your search"), results
, NULL
, NULL
);
2064 static void user_search_x_data_cb(JabberStream
*js
, PurpleXmlNode
*result
, gpointer data
)
2066 PurpleXmlNode
*query
;
2068 char *dir_server
= data
;
2071 /* if they've cancelled the search, we're
2072 * just going to get an error if we send
2073 * a cancel, so skip it */
2074 type
= purple_xmlnode_get_attrib(result
, "type");
2075 if(purple_strequal(type
, "cancel")) {
2080 iq
= jabber_iq_new_query(js
, JABBER_IQ_SET
, "jabber:iq:search");
2081 query
= purple_xmlnode_get_child(iq
->node
, "query");
2083 purple_xmlnode_insert_child(query
, result
);
2085 jabber_iq_set_callback(iq
, user_search_result_cb
, NULL
);
2086 purple_xmlnode_set_attrib(iq
->node
, "to", dir_server
);
2091 struct user_search_info
{
2093 char *directory_server
;
2096 static void user_search_cancel_cb(struct user_search_info
*usi
, PurpleRequestFields
*fields
)
2098 g_free(usi
->directory_server
);
2102 static void user_search_cb(struct user_search_info
*usi
, PurpleRequestFields
*fields
)
2104 JabberStream
*js
= usi
->js
;
2106 PurpleXmlNode
*query
;
2107 GList
*groups
, *flds
;
2109 iq
= jabber_iq_new_query(js
, JABBER_IQ_SET
, "jabber:iq:search");
2110 query
= purple_xmlnode_get_child(iq
->node
, "query");
2112 for(groups
= purple_request_fields_get_groups(fields
); groups
; groups
= groups
->next
) {
2113 for(flds
= purple_request_field_group_get_fields(groups
->data
);
2114 flds
; flds
= flds
->next
) {
2115 PurpleRequestField
*field
= flds
->data
;
2116 const char *id
= purple_request_field_get_id(field
);
2117 const char *value
= purple_request_field_string_get_value(field
);
2119 if(value
&& (purple_strequal(id
, "first") || purple_strequal(id
, "last") || purple_strequal(id
, "nick") || purple_strequal(id
, "email"))) {
2120 PurpleXmlNode
*y
= purple_xmlnode_new_child(query
, id
);
2121 purple_xmlnode_insert_data(y
, value
, -1);
2126 jabber_iq_set_callback(iq
, user_search_result_cb
, NULL
);
2127 purple_xmlnode_set_attrib(iq
->node
, "to", usi
->directory_server
);
2130 g_free(usi
->directory_server
);
2134 static void user_search_fields_result_cb(JabberStream
*js
, const char *from
,
2135 JabberIqType type
, const char *id
,
2136 PurpleXmlNode
*packet
, gpointer data
)
2138 PurpleXmlNode
*query
, *x
;
2143 if (type
== JABBER_IQ_ERROR
) {
2144 char *msg
= jabber_parse_error(js
, packet
, NULL
);
2147 msg
= g_strdup(_("Unknown error"));
2149 purple_notify_error(js
->gc
, _("Directory Query Failed"),
2150 _("Could not query the directory server."), msg
,
2151 purple_request_cpar_from_connection(js
->gc
));
2158 if(!(query
= purple_xmlnode_get_child(packet
, "query")))
2161 if((x
= purple_xmlnode_get_child_with_namespace(query
, "x", "jabber:x:data"))) {
2162 jabber_x_data_request(js
, x
, user_search_x_data_cb
, g_strdup(from
));
2165 struct user_search_info
*usi
;
2166 PurpleXmlNode
*instnode
;
2167 char *instructions
= NULL
;
2168 PurpleRequestFields
*fields
;
2169 PurpleRequestFieldGroup
*group
;
2170 PurpleRequestField
*field
;
2173 fields
= purple_request_fields_new();
2174 group
= purple_request_field_group_new(NULL
);
2175 purple_request_fields_add_group(fields
, group
);
2177 if((instnode
= purple_xmlnode_get_child(query
, "instructions")))
2179 char *tmp
= purple_xmlnode_get_data(instnode
);
2183 /* Try to translate the message (see static message
2184 list in jabber_user_dir_comments[]) */
2185 instructions
= g_strdup_printf(_("Server Instructions: %s"), _(tmp
));
2192 instructions
= g_strdup(_("Fill in one or more fields to search "
2193 "for any matching XMPP users."));
2196 if(purple_xmlnode_get_child(query
, "first")) {
2197 field
= purple_request_field_string_new("first", _("First Name"),
2199 purple_request_field_group_add_field(group
, field
);
2201 if(purple_xmlnode_get_child(query
, "last")) {
2202 field
= purple_request_field_string_new("last", _("Last Name"),
2204 purple_request_field_group_add_field(group
, field
);
2206 if(purple_xmlnode_get_child(query
, "nick")) {
2207 field
= purple_request_field_string_new("nick", _("Nickname"),
2209 purple_request_field_group_add_field(group
, field
);
2211 if(purple_xmlnode_get_child(query
, "email")) {
2212 field
= purple_request_field_string_new("email", _("Email Address"),
2214 purple_request_field_group_add_field(group
, field
);
2217 usi
= g_new0(struct user_search_info
, 1);
2219 usi
->directory_server
= g_strdup(from
);
2221 purple_request_fields(js
->gc
, _("Search for XMPP users"),
2222 _("Search for XMPP users"), instructions
, fields
,
2223 _("Search"), G_CALLBACK(user_search_cb
),
2224 _("Cancel"), G_CALLBACK(user_search_cancel_cb
),
2225 purple_request_cpar_from_connection(js
->gc
),
2228 g_free(instructions
);
2232 void jabber_user_search(JabberStream
*js
, const char *directory
)
2236 /* XXX: should probably better validate the directory we're given */
2237 if(!directory
|| !*directory
) {
2238 purple_notify_error(js
->gc
, _("Invalid Directory"),
2239 _("Invalid Directory"), NULL
,
2240 purple_request_cpar_from_connection(js
->gc
));
2244 /* If the value provided isn't the disco#info default, persist it. Otherwise,
2245 make sure we aren't persisting an old value */
2246 if(js
->user_directories
&& js
->user_directories
->data
&&
2247 purple_strequal(directory
, js
->user_directories
->data
)) {
2248 purple_account_set_string(purple_connection_get_account(js
->gc
), "user_directory", "");
2251 purple_account_set_string(purple_connection_get_account(js
->gc
), "user_directory", directory
);
2254 iq
= jabber_iq_new_query(js
, JABBER_IQ_GET
, "jabber:iq:search");
2255 purple_xmlnode_set_attrib(iq
->node
, "to", directory
);
2257 jabber_iq_set_callback(iq
, user_search_fields_result_cb
, NULL
);
2262 void jabber_user_search_begin(PurpleProtocolAction
*action
)
2264 PurpleConnection
*gc
= (PurpleConnection
*) action
->connection
;
2265 JabberStream
*js
= purple_connection_get_protocol_data(gc
);
2266 const char *def_val
= purple_account_get_string(purple_connection_get_account(js
->gc
), "user_directory", "");
2267 if(!*def_val
&& js
->user_directories
)
2268 def_val
= js
->user_directories
->data
;
2270 purple_request_input(gc
, _("Enter a User Directory"), _("Enter a User Directory"),
2271 _("Select a user directory to search"),
2274 _("Search Directory"), PURPLE_CALLBACK(jabber_user_search
),
2280 jabber_resource_know_capabilities(const JabberBuddyResource
*jbr
)
2282 return jbr
->caps
.info
!= NULL
;
2286 jabber_resource_has_capability(const JabberBuddyResource
*jbr
, const gchar
*cap
)
2288 const GList
*node
= NULL
;
2289 const JabberCapsNodeExts
*exts
;
2291 if (!jbr
->caps
.info
) {
2292 purple_debug_info("jabber",
2293 "Unable to find caps: nothing known about buddy\n");
2297 node
= g_list_find_custom(jbr
->caps
.info
->features
, cap
, (GCompareFunc
)strcmp
);
2298 if (!node
&& jbr
->caps
.exts
&& jbr
->caps
.info
->exts
) {
2300 exts
= jbr
->caps
.info
->exts
;
2301 /* Walk through all the enabled caps, checking each list for the cap.
2302 * Don't check it twice, though. */
2303 for (ext
= jbr
->caps
.exts
; ext
&& !node
; ext
= ext
->next
) {
2304 GList
*features
= g_hash_table_lookup(exts
->exts
, ext
->data
);
2306 node
= g_list_find_custom(features
, cap
, (GCompareFunc
)strcmp
);
2310 return (node
!= NULL
);
2314 jabber_buddy_has_capability(const JabberBuddy
*jb
, const gchar
*cap
)
2316 JabberBuddyResource
*jbr
= jabber_buddy_find_resource((JabberBuddy
*)jb
, NULL
);
2319 purple_debug_info("jabber",
2320 "Unable to find caps: buddy might be offline\n");
2324 return jabber_resource_has_capability(jbr
, cap
);
2328 jabber_resource_get_identity_category_type(const JabberBuddyResource
*jbr
,
2329 const gchar
*category
)
2331 const GList
*iter
= NULL
;
2333 if (jbr
->caps
.info
) {
2334 for (iter
= jbr
->caps
.info
->identities
; iter
; iter
= g_list_next(iter
)) {
2335 const JabberIdentity
*identity
=
2336 (JabberIdentity
*) iter
->data
;
2338 if (purple_strequal(identity
->category
, category
)) {
2339 return identity
->type
;