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"
37 #include "useravatar.h"
40 #include "adhoccommands.h"
41 #include "google/google.h"
45 } JabberBuddyInfoResource
;
52 GHashTable
*resources
;
55 PurpleNotifyUserInfo
*user_info
;
61 jabber_buddy_resource_free(JabberBuddyResource
*jbr
)
63 g_return_if_fail(jbr
!= NULL
);
65 jbr
->jb
->resources
= g_list_remove(jbr
->jb
->resources
, jbr
);
67 while(jbr
->commands
) {
68 JabberAdHocCommands
*cmd
= jbr
->commands
->data
;
73 jbr
->commands
= g_list_delete_link(jbr
->commands
, jbr
->commands
);
76 while (jbr
->caps
.exts
) {
77 g_free(jbr
->caps
.exts
->data
);
78 jbr
->caps
.exts
= g_list_delete_link(jbr
->caps
.exts
, jbr
->caps
.exts
);
83 g_free(jbr
->thread_id
);
84 g_free(jbr
->client
.name
);
85 g_free(jbr
->client
.version
);
86 g_free(jbr
->client
.os
);
90 void jabber_buddy_free(JabberBuddy
*jb
)
92 g_return_if_fail(jb
!= NULL
);
94 g_free(jb
->error_msg
);
96 jabber_buddy_resource_free(jb
->resources
->data
);
101 JabberBuddy
*jabber_buddy_find(JabberStream
*js
, const char *name
,
107 if (js
->buddies
== NULL
)
110 if(!(realname
= jabber_get_bare_jid(name
)))
113 jb
= g_hash_table_lookup(js
->buddies
, realname
);
116 jb
= g_new0(JabberBuddy
, 1);
117 g_hash_table_insert(js
->buddies
, realname
, jb
);
124 /* Returns -1 if a is a higher priority resource than b, or is
125 * "more available" than b. 0 if they're the same, and 1 if b is
126 * higher priority/more available than a.
128 static gint
resource_compare_cb(gconstpointer a
, gconstpointer b
)
130 const JabberBuddyResource
*jbra
= a
;
131 const JabberBuddyResource
*jbrb
= b
;
132 JabberBuddyState state_a
, state_b
;
134 if (jbra
->priority
!= jbrb
->priority
)
135 return jbra
->priority
> jbrb
->priority
? -1 : 1;
137 /* Fold the states for easier comparison */
138 /* TODO: Differentiate online/chat and away/dnd? */
139 switch (jbra
->state
) {
140 case JABBER_BUDDY_STATE_ONLINE
:
141 case JABBER_BUDDY_STATE_CHAT
:
142 state_a
= JABBER_BUDDY_STATE_ONLINE
;
144 case JABBER_BUDDY_STATE_AWAY
:
145 case JABBER_BUDDY_STATE_DND
:
146 state_a
= JABBER_BUDDY_STATE_AWAY
;
148 case JABBER_BUDDY_STATE_XA
:
149 state_a
= JABBER_BUDDY_STATE_XA
;
151 case JABBER_BUDDY_STATE_UNAVAILABLE
:
152 state_a
= JABBER_BUDDY_STATE_UNAVAILABLE
;
155 state_a
= JABBER_BUDDY_STATE_UNKNOWN
;
159 switch (jbrb
->state
) {
160 case JABBER_BUDDY_STATE_ONLINE
:
161 case JABBER_BUDDY_STATE_CHAT
:
162 state_b
= JABBER_BUDDY_STATE_ONLINE
;
164 case JABBER_BUDDY_STATE_AWAY
:
165 case JABBER_BUDDY_STATE_DND
:
166 state_b
= JABBER_BUDDY_STATE_AWAY
;
168 case JABBER_BUDDY_STATE_XA
:
169 state_b
= JABBER_BUDDY_STATE_XA
;
171 case JABBER_BUDDY_STATE_UNAVAILABLE
:
172 state_b
= JABBER_BUDDY_STATE_UNAVAILABLE
;
175 state_b
= JABBER_BUDDY_STATE_UNKNOWN
;
179 if (state_a
== state_b
) {
180 if (jbra
->idle
== jbrb
->idle
)
182 else if ((jbra
->idle
&& !jbrb
->idle
) ||
183 (jbra
->idle
&& jbrb
->idle
&& jbra
->idle
< jbrb
->idle
))
189 if (state_a
== JABBER_BUDDY_STATE_ONLINE
)
191 else if (state_a
== JABBER_BUDDY_STATE_AWAY
&&
192 (state_b
== JABBER_BUDDY_STATE_XA
||
193 state_b
== JABBER_BUDDY_STATE_UNAVAILABLE
||
194 state_b
== JABBER_BUDDY_STATE_UNKNOWN
))
196 else if (state_a
== JABBER_BUDDY_STATE_XA
&&
197 (state_b
== JABBER_BUDDY_STATE_UNAVAILABLE
||
198 state_b
== JABBER_BUDDY_STATE_UNKNOWN
))
200 else if (state_a
== JABBER_BUDDY_STATE_UNAVAILABLE
&&
201 state_b
== JABBER_BUDDY_STATE_UNKNOWN
)
207 JabberBuddyResource
*jabber_buddy_find_resource(JabberBuddy
*jb
,
208 const char *resource
)
215 if (resource
== NULL
)
216 return jb
->resources
? jb
->resources
->data
: NULL
;
218 for (l
= jb
->resources
; l
; l
= l
->next
)
220 JabberBuddyResource
*jbr
= l
->data
;
221 if (jbr
->name
&& g_str_equal(resource
, jbr
->name
))
228 JabberBuddyResource
*jabber_buddy_track_resource(JabberBuddy
*jb
, const char *resource
,
229 int priority
, JabberBuddyState state
, const char *status
)
231 /* TODO: Optimization: Only reinsert if priority+state changed */
232 JabberBuddyResource
*jbr
= jabber_buddy_find_resource(jb
, resource
);
234 jb
->resources
= g_list_remove(jb
->resources
, jbr
);
236 jbr
= g_new0(JabberBuddyResource
, 1);
238 jbr
->name
= g_strdup(resource
);
239 jbr
->capabilities
= JABBER_CAP_NONE
;
240 jbr
->tz_off
= PURPLE_NO_TZ_OFF
;
242 jbr
->priority
= priority
;
245 jbr
->status
= g_strdup(status
);
247 jb
->resources
= g_list_insert_sorted(jb
->resources
, jbr
,
248 resource_compare_cb
);
252 void jabber_buddy_remove_resource(JabberBuddy
*jb
, const char *resource
)
254 JabberBuddyResource
*jbr
= jabber_buddy_find_resource(jb
, resource
);
259 jabber_buddy_resource_free(jbr
);
263 * This is the old vCard stuff taken from the old prpl. vCards, by definition
264 * are a temporary thing until jabber can get its act together and come up
265 * with a format for user information, hence the namespace of 'vcard-temp'
267 * Since I don't feel like putting that much work into something that's
268 * _supposed_ to go away, i'm going to just copy the kludgy old code here,
269 * and make it purdy when jabber comes up with a standards-track JEP to
274 /*---------------------------------------*/
275 /* Jabber "set info" (vCard) support */
276 /*---------------------------------------*/
281 * <vCard prodid='' version='' xmlns=''>
311 * http://docs.jabber.org/proto/html/vcard-temp.html
312 * http://www.vcard-xml.org/dtd/vCard-XML-v2-20010520.dtd
316 * Cross-reference user-friendly V-Card entry labels to vCard XML tags
319 * Order is (or should be) unimportant. For example: we have no way of
320 * knowing in what order real data will arrive.
322 * Format: Label, Pre-set text, "visible" flag, "editable" flag, XML tag
323 * name, XML tag's parent tag "path" (relative to vCard node).
325 * List is terminated by a NULL label pointer.
327 * Entries with no label text, but with XML tag and parent tag
328 * entries, are used by V-Card XML construction routines to
329 * "automagically" construct the appropriate XML node tree.
331 * Thoughts on future direction/expansion
333 * This is a "simple" vCard.
335 * It is possible for nodes other than the "vCard" node to have
336 * attributes. Should that prove necessary/desirable, add an
337 * "attributes" pointer to the vcard_template struct, create the
338 * necessary tag_attr structs, and add 'em to the vcard_dflt_data
341 * The above changes will (obviously) require changes to the vCard
342 * construction routines.
345 struct vcard_template
{
346 char *label
; /* label text pointer */
347 char *tag
; /* tag text */
348 char *ptag
; /* parent tag "path" text */
349 } const vcard_template_data
[] = {
350 {N_("Full Name"), "FN", NULL
},
351 {N_("Family Name"), "FAMILY", "N"},
352 {N_("Given Name"), "GIVEN", "N"},
353 {N_("Nickname"), "NICKNAME", NULL
},
354 {N_("URL"), "URL", NULL
},
355 {N_("Street Address"), "STREET", "ADR"},
356 {N_("Extended Address"), "EXTADD", "ADR"},
357 {N_("Locality"), "LOCALITY", "ADR"},
358 {N_("Region"), "REGION", "ADR"},
359 {N_("Postal Code"), "PCODE", "ADR"},
360 {N_("Country"), "CTRY", "ADR"},
361 {N_("Telephone"), "NUMBER", "TEL"},
362 {N_("Email"), "USERID", "EMAIL"},
363 {N_("Organization Name"), "ORGNAME", "ORG"},
364 {N_("Organization Unit"), "ORGUNIT", "ORG"},
365 {N_("Job Title"), "TITLE", NULL
},
366 {N_("Role"), "ROLE", NULL
},
367 {N_("Birthday"), "BDAY", NULL
},
368 {N_("Description"), "DESC", NULL
},
376 * The "vCard" tag's attribute list...
381 } const vcard_tag_attr_list
[] = {
382 {"prodid", "-//HandGen//NONSGML vGen v1.0//EN"},
383 {"version", "2.0", },
384 {"xmlns", "vcard-temp", },
390 * Insert a tag node into an PurpleXmlNode tree, recursively inserting parent tag
393 * Returns pointer to inserted node
395 * Note to hackers: this code is designed to be re-entrant (it's recursive--it
396 * calls itself), so don't put any "static"s in here!
398 static PurpleXmlNode
*insert_tag_to_parent_tag(PurpleXmlNode
*start
, const char *parent_tag
, const char *new_tag
)
400 PurpleXmlNode
*x
= NULL
;
403 * If the parent tag wasn't specified, see if we can get it
404 * from the vCard template struct.
406 if(parent_tag
== NULL
) {
407 const struct vcard_template
*vc_tp
= vcard_template_data
;
409 while(vc_tp
->label
!= NULL
) {
410 if(strcmp(vc_tp
->tag
, new_tag
) == 0) {
411 parent_tag
= vc_tp
->ptag
;
419 * If we have a parent tag...
421 if(parent_tag
!= NULL
) {
423 * Try to get the parent node for a tag
425 if((x
= purple_xmlnode_get_child(start
, parent_tag
)) == NULL
) {
429 char *grand_parent
= g_strdup(parent_tag
);
432 if((parent
= strrchr(grand_parent
, '/')) != NULL
) {
434 x
= insert_tag_to_parent_tag(start
, grand_parent
, parent
);
436 x
= purple_xmlnode_new_child(start
, grand_parent
);
438 g_free(grand_parent
);
441 * We found *something* to be the parent node.
442 * Note: may be the "root" node!
445 if((y
= purple_xmlnode_get_child(x
, new_tag
)) != NULL
) {
452 * insert the new tag into its parent node
454 return(purple_xmlnode_new_child((x
== NULL
? start
: x
), new_tag
));
458 * Send vCard info to Jabber server
460 void jabber_set_info(PurpleConnection
*gc
, const char *info
)
464 JabberStream
*js
= purple_connection_get_protocol_data(gc
);
465 PurpleXmlNode
*vc_node
;
466 const struct tag_attr
*tag_attr
;
468 /* if we haven't grabbed the remote vcard yet, we can't
469 * assume that what we have here is correct */
470 if(!js
->vcard_fetched
) {
472 g_free(js
->initial_avatar_hash
);
473 image
= purple_buddy_icons_find_account_icon(purple_connection_get_account(gc
));
475 js
->initial_avatar_hash
= jabber_calculate_data_hash(
476 purple_image_get_data(image
),
477 purple_image_get_size(image
), "sha1");
478 g_object_unref(image
);
480 js
->initial_avatar_hash
= NULL
;
485 if (js
->vcard_timer
) {
486 purple_timeout_remove(js
->vcard_timer
);
490 g_free(js
->avatar_hash
);
491 js
->avatar_hash
= NULL
;
494 * Send only if there's actually any *information* to send
496 vc_node
= info
? purple_xmlnode_from_str(info
, -1) : NULL
;
498 if (vc_node
&& (!vc_node
->name
||
499 g_ascii_strncasecmp(vc_node
->name
, "vCard", 5))) {
500 purple_xmlnode_free(vc_node
);
504 if ((img
= purple_buddy_icons_find_account_icon(purple_connection_get_account(gc
)))) {
505 gconstpointer avatar_data
;
507 PurpleXmlNode
*photo
, *binval
, *type
;
511 vc_node
= purple_xmlnode_new("vCard");
512 for(tag_attr
= vcard_tag_attr_list
; tag_attr
->attr
!= NULL
; ++tag_attr
)
513 purple_xmlnode_set_attrib(vc_node
, tag_attr
->attr
, tag_attr
->value
);
516 avatar_data
= purple_image_get_data(img
);
517 avatar_len
= purple_image_get_size(img
);
518 /* Get rid of an old PHOTO if one exists.
519 * TODO: This may want to be modified to remove all old PHOTO
520 * children, at the moment some people have managed to get
521 * multiple PHOTO entries in their vCard. */
522 if((photo
= purple_xmlnode_get_child(vc_node
, "PHOTO"))) {
523 purple_xmlnode_free(photo
);
525 photo
= purple_xmlnode_new_child(vc_node
, "PHOTO");
526 type
= purple_xmlnode_new_child(photo
, "TYPE");
527 purple_xmlnode_insert_data(type
, "image/png", -1);
528 binval
= purple_xmlnode_new_child(photo
, "BINVAL");
529 enc
= purple_base64_encode(avatar_data
, avatar_len
);
532 jabber_calculate_data_hash(avatar_data
, avatar_len
, "sha1");
534 purple_xmlnode_insert_data(binval
, enc
, -1);
537 } else if (vc_node
) {
538 PurpleXmlNode
*photo
;
539 /* TODO: Remove all PHOTO children? (see above note) */
540 if ((photo
= purple_xmlnode_get_child(vc_node
, "PHOTO"))) {
541 purple_xmlnode_free(photo
);
545 if (vc_node
!= NULL
) {
546 iq
= jabber_iq_new(js
, JABBER_IQ_SET
);
547 purple_xmlnode_insert_child(iq
->node
, vc_node
);
550 /* Send presence to update vcard-temp:x:update */
551 jabber_presence_send(js
, FALSE
);
555 void jabber_set_buddy_icon(PurpleConnection
*gc
, PurpleImage
*img
)
557 PurpleAccount
*account
= purple_connection_get_account(gc
);
559 /* Publish the avatar as specified in XEP-0084 */
560 jabber_avatar_set(purple_connection_get_protocol_data(gc
), img
);
561 /* Set the image in our vCard */
562 jabber_set_info(gc
, purple_account_get_user_info(account
));
564 /* TODO: Fake image to ourselves, since a number of servers do not echo
565 * back our presence to us. To do this without uselessly copying the data
566 * of the image, we need purple_buddy_icons_set_for_user_image (i.e. takes
567 * an existing icon/stored image). */
571 * This is the callback from the "ok clicked" for "set vCard"
573 * Sets the vCard with data from PurpleRequestFields.
576 jabber_format_info(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
578 PurpleXmlNode
*vc_node
;
579 PurpleRequestField
*field
;
582 const struct vcard_template
*vc_tp
;
583 const struct tag_attr
*tag_attr
;
585 vc_node
= purple_xmlnode_new("vCard");
587 for(tag_attr
= vcard_tag_attr_list
; tag_attr
->attr
!= NULL
; ++tag_attr
)
588 purple_xmlnode_set_attrib(vc_node
, tag_attr
->attr
, tag_attr
->value
);
590 for (vc_tp
= vcard_template_data
; vc_tp
->label
!= NULL
; vc_tp
++) {
591 if (*vc_tp
->label
== '\0')
594 field
= purple_request_fields_get_field(fields
, vc_tp
->tag
);
595 text
= purple_request_field_string_get_value(field
);
598 if (text
!= NULL
&& *text
!= '\0') {
601 purple_debug_info("jabber", "Setting %s to '%s'\n", vc_tp
->tag
, text
);
603 if ((xp
= insert_tag_to_parent_tag(vc_node
,
604 NULL
, vc_tp
->tag
)) != NULL
) {
606 purple_xmlnode_insert_data(xp
, text
, -1);
611 p
= purple_xmlnode_to_str(vc_node
, NULL
);
612 purple_xmlnode_free(vc_node
);
614 purple_account_set_user_info(purple_connection_get_account(gc
), p
);
615 purple_serv_set_info(gc
, p
);
621 * This gets executed by the proto action
623 * Creates a new PurpleRequestFields struct, gets the XML-formatted user_info
624 * string (if any) into GSLists for the (multi-entry) edit dialog and
625 * calls the set_vcard dialog.
627 void jabber_setup_set_info(PurpleProtocolAction
*action
)
629 PurpleConnection
*gc
= (PurpleConnection
*) action
->connection
;
630 PurpleRequestFields
*fields
;
631 PurpleRequestFieldGroup
*group
;
632 PurpleRequestField
*field
;
633 const struct vcard_template
*vc_tp
;
634 const char *user_info
;
636 PurpleXmlNode
*x_vc_data
= NULL
;
638 fields
= purple_request_fields_new();
639 group
= purple_request_field_group_new(NULL
);
640 purple_request_fields_add_group(fields
, group
);
643 * Get existing, XML-formatted, user info
645 if((user_info
= purple_account_get_user_info(purple_connection_get_account(gc
))) != NULL
)
646 x_vc_data
= purple_xmlnode_from_str(user_info
, -1);
649 * Set up GSLists for edit with labels from "template," data from user info
651 for(vc_tp
= vcard_template_data
; vc_tp
->label
!= NULL
; ++vc_tp
) {
652 PurpleXmlNode
*data_node
;
653 if((vc_tp
->label
)[0] == '\0')
656 if (x_vc_data
!= NULL
) {
657 if(vc_tp
->ptag
== NULL
) {
658 data_node
= purple_xmlnode_get_child(x_vc_data
, vc_tp
->tag
);
660 gchar
*tag
= g_strdup_printf("%s/%s", vc_tp
->ptag
, vc_tp
->tag
);
661 data_node
= purple_xmlnode_get_child(x_vc_data
, tag
);
665 cdata
= purple_xmlnode_get_data(data_node
);
668 if(strcmp(vc_tp
->tag
, "DESC") == 0) {
669 field
= purple_request_field_string_new(vc_tp
->tag
,
670 _(vc_tp
->label
), cdata
,
673 field
= purple_request_field_string_new(vc_tp
->tag
,
674 _(vc_tp
->label
), cdata
,
681 purple_request_field_group_add_field(group
, field
);
684 if(x_vc_data
!= NULL
)
685 purple_xmlnode_free(x_vc_data
);
687 purple_request_fields(gc
, _("Edit XMPP vCard"),
688 _("Edit XMPP vCard"),
689 _("All items below are optional. Enter only the "
690 "information with which you feel comfortable."),
692 _("Save"), G_CALLBACK(jabber_format_info
),
694 purple_request_cpar_from_connection(gc
),
698 /*---------------------------------------*/
699 /* End Jabber "set info" (vCard) support */
700 /*---------------------------------------*/
703 * end of that ancient crap that needs to die
706 static void jabber_buddy_info_destroy(JabberBuddyInfo
*jbi
)
708 /* Remove the timeout, which would otherwise trigger jabber_buddy_get_info_timeout() */
709 if (jbi
->timeout_handle
> 0)
710 purple_timeout_remove(jbi
->timeout_handle
);
713 g_hash_table_destroy(jbi
->resources
);
714 g_free(jbi
->last_message
);
715 purple_notify_user_info_destroy(jbi
->user_info
);
720 add_jbr_info(JabberBuddyInfo
*jbi
, const char *resource
,
721 JabberBuddyResource
*jbr
)
723 JabberBuddyInfoResource
*jbir
;
724 PurpleNotifyUserInfo
*user_info
;
726 jbir
= g_hash_table_lookup(jbi
->resources
, resource
);
727 user_info
= jbi
->user_info
;
729 if (jbr
&& jbr
->client
.name
) {
731 g_strdup_printf("%s%s%s", jbr
->client
.name
,
732 (jbr
->client
.version
? " " : ""),
733 (jbr
->client
.version
? jbr
->client
.version
: ""));
734 /* TODO: Check whether it's correct to call prepend_pair_html,
735 or if we should be using prepend_pair_plaintext */
736 purple_notify_user_info_prepend_pair_html(user_info
, _("Client"), tmp
);
739 if (jbr
->client
.os
) {
740 /* TODO: Check whether it's correct to call prepend_pair_html,
741 or if we should be using prepend_pair_plaintext */
742 purple_notify_user_info_prepend_pair_html(user_info
, _("Operating System"), jbr
->client
.os
);
746 if (jbr
&& jbr
->tz_off
!= PURPLE_NO_TZ_OFF
) {
751 now_t
+= jbr
->tz_off
;
752 now
= gmtime(&now_t
);
755 g_strdup_printf("%s %c%02d%02d", purple_time_format(now
),
756 jbr
->tz_off
< 0 ? '-' : '+',
757 abs(jbr
->tz_off
/ (60*60)),
758 abs((jbr
->tz_off
% (60*60)) / 60));
759 purple_notify_user_info_prepend_pair_plaintext(user_info
, _("Local Time"), timestamp
);
763 if (jbir
&& jbir
->idle_seconds
> 0) {
764 char *idle
= purple_str_seconds_to_string(jbir
->idle_seconds
);
765 purple_notify_user_info_prepend_pair_plaintext(user_info
, _("Idle"), idle
);
773 const char *status_name
= jabber_buddy_state_get_name(jbr
->state
);
776 tmp
= purple_markup_escape_text(jbr
->status
, -1);
777 purdy
= purple_strdup_withhtml(tmp
);
780 if (purple_strequal(status_name
, purdy
))
784 tmp
= g_strdup_printf("%s%s%s", (status_name
? status_name
: ""),
785 ((status_name
&& purdy
) ? ": " : ""),
786 (purdy
? purdy
: ""));
787 purple_notify_user_info_prepend_pair_html(user_info
, _("Status"), tmp
);
789 g_snprintf(priority
, sizeof(priority
), "%d", jbr
->priority
);
790 purple_notify_user_info_prepend_pair_plaintext(user_info
, _("Priority"), priority
);
795 purple_notify_user_info_prepend_pair_plaintext(user_info
, _("Status"), _("Unknown"));
799 static void jabber_buddy_info_show_if_ready(JabberBuddyInfo
*jbi
)
802 JabberBuddyResource
*jbr
;
804 PurpleNotifyUserInfo
*user_info
;
810 user_info
= jbi
->user_info
;
811 resource_name
= jabber_get_resource(jbi
->jid
);
813 /* If we have one or more pairs from the vcard, put a section break above it */
814 if (g_queue_get_length(purple_notify_user_info_get_entries(user_info
)))
815 purple_notify_user_info_prepend_section_break(user_info
);
817 /* Add the information about the user's resource(s) */
819 jbr
= jabber_buddy_find_resource(jbi
->jb
, resource_name
);
820 add_jbr_info(jbi
, resource_name
, jbr
);
822 /* TODO: This is in priority-ascending order (lowest prio first), because
823 * everything is prepended. Is that ok? */
824 for (resources
= jbi
->jb
->resources
; resources
; resources
= resources
->next
) {
825 jbr
= resources
->data
;
827 /* put a section break between resources, this is not needed if
828 we are at the first, because one was already added for the vcard
830 if (resources
!= jbi
->jb
->resources
)
831 purple_notify_user_info_prepend_section_break(user_info
);
833 add_jbr_info(jbi
, jbr
->name
, jbr
);
836 /* TODO: Check whether it's correct to call prepend_pair_html,
837 or if we should be using prepend_pair_plaintext */
838 purple_notify_user_info_prepend_pair_html(user_info
, _("Resource"), jbr
->name
);
843 if (!jbi
->jb
->resources
) {
844 /* the buddy is offline */
845 gboolean is_domain
= jabber_jid_is_domain(jbi
->jid
);
847 if (jbi
->last_seconds
> 0) {
848 char *last
= purple_str_seconds_to_string(jbi
->last_seconds
);
849 gchar
*message
= NULL
;
850 const gchar
*title
= NULL
;
856 title
= _("Logged Off");
857 message
= g_strdup_printf(_("%s ago"), last
);
859 purple_notify_user_info_prepend_pair_plaintext(user_info
, title
, message
);
866 g_strdup_printf("%s%s%s", _("Offline"),
867 jbi
->last_message
? ": " : "",
868 jbi
->last_message
? jbi
->last_message
: "");
869 /* TODO: Check whether it's correct to call prepend_pair_html,
870 or if we should be using prepend_pair_plaintext */
871 purple_notify_user_info_prepend_pair_html(user_info
, _("Status"), status
);
876 g_free(resource_name
);
878 purple_notify_userinfo(jbi
->js
->gc
, jbi
->jid
, user_info
, NULL
, NULL
);
880 while (jbi
->vcard_images
) {
881 g_object_unref(jbi
->vcard_images
->data
);
882 jbi
->vcard_images
= g_slist_delete_link(jbi
->vcard_images
, jbi
->vcard_images
);
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
)
892 GSList
*l
= jbi
->ids
;
900 if(!strcmp(id
, comp_id
)) {
901 jbi
->ids
= g_slist_remove(jbi
->ids
, comp_id
);
910 set_own_vcard_cb(gpointer data
)
912 JabberStream
*js
= data
;
913 PurpleAccount
*account
= purple_connection_get_account(js
->gc
);
917 jabber_set_info(js
->gc
, purple_account_get_user_info(account
));
922 static void jabber_vcard_save_mine(JabberStream
*js
, const char *from
,
923 JabberIqType type
, const char *id
,
924 PurpleXmlNode
*packet
, gpointer data
)
926 PurpleXmlNode
*vcard
, *photo
, *binval
;
927 char *txt
, *vcard_hash
= NULL
;
928 PurpleAccount
*account
;
930 if (type
== JABBER_IQ_ERROR
) {
931 PurpleXmlNode
*error
;
932 purple_debug_warning("jabber", "Server returned error while retrieving vCard\n");
934 error
= purple_xmlnode_get_child(packet
, "error");
935 if (!error
|| !purple_xmlnode_get_child(error
, "item-not-found"))
939 account
= purple_connection_get_account(js
->gc
);
941 if((vcard
= purple_xmlnode_get_child(packet
, "vCard")) ||
942 (vcard
= purple_xmlnode_get_child_with_namespace(packet
, "query", "vcard-temp")))
944 txt
= purple_xmlnode_to_str(vcard
, NULL
);
945 purple_account_set_user_info(account
, txt
);
948 /* if we have no vCard, then lets not overwrite what we might have locally */
951 js
->vcard_fetched
= TRUE
;
953 if (vcard
&& (photo
= purple_xmlnode_get_child(vcard
, "PHOTO")) &&
954 (binval
= purple_xmlnode_get_child(photo
, "BINVAL"))) {
956 char *bintext
= purple_xmlnode_get_data(binval
);
958 guchar
*data
= purple_base64_decode(bintext
, &size
);
962 vcard_hash
= jabber_calculate_data_hash(data
, size
, "sha1");
968 /* Republish our vcard if the photo is different than the server's */
969 if (js
->initial_avatar_hash
&& !purple_strequal(vcard_hash
, js
->initial_avatar_hash
)) {
971 * Google Talk has developed the behavior that it will not accept
972 * a vcard set in the first 10 seconds (or so) of the connection;
973 * it returns an error (namespaces trimmed):
974 * <error code="500" type="wait"><internal-server-error/></error>.
977 js
->vcard_timer
= purple_timeout_add_seconds(10, set_own_vcard_cb
,
980 jabber_set_info(js
->gc
, purple_account_get_user_info(account
));
981 } else if (vcard_hash
) {
982 /* A photo is in the vCard. Advertise its hash */
983 js
->avatar_hash
= vcard_hash
;
986 /* Send presence to update vcard-temp:x:update */
987 jabber_presence_send(js
, FALSE
);
993 void jabber_vcard_fetch_mine(JabberStream
*js
)
995 JabberIq
*iq
= jabber_iq_new(js
, JABBER_IQ_GET
);
997 PurpleXmlNode
*vcard
= purple_xmlnode_new_child(iq
->node
, "vCard");
998 purple_xmlnode_set_namespace(vcard
, "vcard-temp");
999 jabber_iq_set_callback(iq
, jabber_vcard_save_mine
, NULL
);
1004 static void jabber_vcard_parse(JabberStream
*js
, const char *from
,
1005 JabberIqType type
, const char *id
,
1006 PurpleXmlNode
*packet
, gpointer data
)
1010 char *serverside_alias
= NULL
;
1011 PurpleXmlNode
*vcard
;
1012 PurpleAccount
*account
;
1013 JabberBuddyInfo
*jbi
= data
;
1014 PurpleNotifyUserInfo
*user_info
;
1016 g_return_if_fail(jbi
!= NULL
);
1018 jabber_buddy_info_remove_id(jbi
, id
);
1020 if (type
== JABBER_IQ_ERROR
) {
1021 purple_debug_info("jabber", "Got error response for vCard\n");
1022 jabber_buddy_info_show_if_ready(jbi
);
1026 user_info
= jbi
->user_info
;
1027 account
= purple_connection_get_account(js
->gc
);
1028 bare_jid
= jabber_get_bare_jid(from
? from
: purple_account_get_username(account
));
1030 /* TODO: Is the query xmlns='vcard-temp' version of this still necessary? */
1031 if((vcard
= purple_xmlnode_get_child(packet
, "vCard")) ||
1032 (vcard
= purple_xmlnode_get_child_with_namespace(packet
, "query", "vcard-temp"))) {
1033 PurpleXmlNode
*child
;
1034 for(child
= vcard
->child
; child
; child
= child
->next
)
1036 PurpleXmlNode
*child2
;
1038 if(child
->type
!= PURPLE_XMLNODE_TYPE_TAG
)
1041 text
= purple_xmlnode_get_data(child
);
1042 if(text
&& !strcmp(child
->name
, "FN")) {
1043 if (!serverside_alias
)
1044 serverside_alias
= g_strdup(text
);
1046 purple_notify_user_info_add_pair_plaintext(user_info
, _("Full Name"), text
);
1047 } else if(!strcmp(child
->name
, "N")) {
1048 for(child2
= child
->child
; child2
; child2
= child2
->next
)
1052 if(child2
->type
!= PURPLE_XMLNODE_TYPE_TAG
)
1055 text2
= purple_xmlnode_get_data(child2
);
1056 if(text2
&& !strcmp(child2
->name
, "FAMILY")) {
1057 purple_notify_user_info_add_pair_plaintext(user_info
, _("Family Name"), text2
);
1058 } else if(text2
&& !strcmp(child2
->name
, "GIVEN")) {
1059 purple_notify_user_info_add_pair_plaintext(user_info
, _("Given Name"), text2
);
1060 } else if(text2
&& !strcmp(child2
->name
, "MIDDLE")) {
1061 purple_notify_user_info_add_pair_plaintext(user_info
, _("Middle Name"), text2
);
1065 } else if(text
&& !strcmp(child
->name
, "NICKNAME")) {
1066 /* Prefer the Nickcname to the Full Name as the serverside alias if it's not just part of the jid.
1067 * Ignore it if it's part of the jid. */
1068 if (strstr(bare_jid
, text
) == NULL
) {
1069 g_free(serverside_alias
);
1070 serverside_alias
= g_strdup(text
);
1072 purple_notify_user_info_add_pair_plaintext(user_info
, _("Nickname"), text
);
1074 } else if(text
&& !strcmp(child
->name
, "BDAY")) {
1075 purple_notify_user_info_add_pair_plaintext(user_info
, _("Birthday"), text
);
1076 } else if(!strcmp(child
->name
, "ADR")) {
1077 gboolean address_line_added
= FALSE
;
1079 for(child2
= child
->child
; child2
; child2
= child2
->next
)
1083 if(child2
->type
!= PURPLE_XMLNODE_TYPE_TAG
)
1086 text2
= purple_xmlnode_get_data(child2
);
1090 /* We do this here so that it's not added if all the child
1091 * elements are empty. */
1092 if (!address_line_added
)
1094 purple_notify_user_info_add_section_header(user_info
, _("Address"));
1095 address_line_added
= TRUE
;
1098 if(!strcmp(child2
->name
, "POBOX")) {
1099 purple_notify_user_info_add_pair_plaintext(user_info
, _("P.O. Box"), text2
);
1100 } else if (g_str_equal(child2
->name
, "EXTADD") || g_str_equal(child2
->name
, "EXTADR")) {
1102 * EXTADD is correct, EXTADR is generated by other
1103 * clients. The next time someone reads this, remove
1106 purple_notify_user_info_add_pair_plaintext(user_info
, _("Extended Address"), text2
);
1107 } else if(!strcmp(child2
->name
, "STREET")) {
1108 purple_notify_user_info_add_pair_plaintext(user_info
, _("Street Address"), text2
);
1109 } else if(!strcmp(child2
->name
, "LOCALITY")) {
1110 purple_notify_user_info_add_pair_plaintext(user_info
, _("Locality"), text2
);
1111 } else if(!strcmp(child2
->name
, "REGION")) {
1112 purple_notify_user_info_add_pair_plaintext(user_info
, _("Region"), text2
);
1113 } else if(!strcmp(child2
->name
, "PCODE")) {
1114 purple_notify_user_info_add_pair_plaintext(user_info
, _("Postal Code"), text2
);
1115 } else if(!strcmp(child2
->name
, "CTRY")
1116 || !strcmp(child2
->name
, "COUNTRY")) {
1117 purple_notify_user_info_add_pair_plaintext(user_info
, _("Country"), text2
);
1122 if (address_line_added
)
1123 purple_notify_user_info_add_section_break(user_info
);
1125 } else if(!strcmp(child
->name
, "TEL")) {
1127 if((child2
= purple_xmlnode_get_child(child
, "NUMBER"))) {
1128 /* show what kind of number it is */
1129 number
= purple_xmlnode_get_data(child2
);
1131 purple_notify_user_info_add_pair_plaintext(user_info
, _("Telephone"), number
);
1134 } else if((number
= purple_xmlnode_get_data(child
))) {
1135 /* lots of clients (including purple) do this, but it's
1137 purple_notify_user_info_add_pair_plaintext(user_info
, _("Telephone"), number
);
1140 } else if(!strcmp(child
->name
, "EMAIL")) {
1141 char *userid
, *escaped
;
1142 if((child2
= purple_xmlnode_get_child(child
, "USERID"))) {
1143 /* show what kind of email it is */
1144 userid
= purple_xmlnode_get_data(child2
);
1147 escaped
= g_markup_escape_text(userid
, -1);
1148 mailto
= g_strdup_printf("<a href=\"mailto:%s\">%s</a>", escaped
, escaped
);
1149 purple_notify_user_info_add_pair_html(user_info
, _("Email"), mailto
);
1155 } else if((userid
= purple_xmlnode_get_data(child
))) {
1156 /* lots of clients (including purple) do this, but it's
1160 escaped
= g_markup_escape_text(userid
, -1);
1161 mailto
= g_strdup_printf("<a href=\"mailto:%s\">%s</a>", escaped
, escaped
);
1162 purple_notify_user_info_add_pair_html(user_info
, _("Email"), mailto
);
1168 } else if(!strcmp(child
->name
, "ORG")) {
1169 for(child2
= child
->child
; child2
; child2
= child2
->next
)
1173 if(child2
->type
!= PURPLE_XMLNODE_TYPE_TAG
)
1176 text2
= purple_xmlnode_get_data(child2
);
1177 if(text2
&& !strcmp(child2
->name
, "ORGNAME")) {
1178 purple_notify_user_info_add_pair_plaintext(user_info
, _("Organization Name"), text2
);
1179 } else if(text2
&& !strcmp(child2
->name
, "ORGUNIT")) {
1180 purple_notify_user_info_add_pair_plaintext(user_info
, _("Organization Unit"), text2
);
1184 } else if(text
&& !strcmp(child
->name
, "TITLE")) {
1185 purple_notify_user_info_add_pair_plaintext(user_info
, _("Job Title"), text
);
1186 } else if(text
&& !strcmp(child
->name
, "ROLE")) {
1187 purple_notify_user_info_add_pair_plaintext(user_info
, _("Role"), text
);
1188 } else if(text
&& !strcmp(child
->name
, "DESC")) {
1189 purple_notify_user_info_add_pair_plaintext(user_info
, _("Description"), text
);
1190 } else if(!strcmp(child
->name
, "PHOTO") ||
1191 !strcmp(child
->name
, "LOGO")) {
1192 char *bintext
= NULL
;
1193 PurpleXmlNode
*binval
;
1195 if ((binval
= purple_xmlnode_get_child(child
, "BINVAL")) &&
1196 (bintext
= purple_xmlnode_get_data(binval
))) {
1199 gboolean photo
= (strcmp(child
->name
, "PHOTO") == 0);
1201 data
= purple_base64_decode(bintext
, &size
);
1208 img
= purple_image_new_from_data(g_memdup(data
, size
), size
);
1209 img_id
= purple_image_store_add(img
);
1211 jbi
->vcard_images
= g_slist_prepend(jbi
->vcard_images
, img
);
1212 img_text
= g_strdup_printf("<img src='"
1213 PURPLE_IMAGE_STORE_PROTOCOL
"%u'>", img_id
);
1215 purple_notify_user_info_add_pair_html(user_info
, (photo
? _("Photo") : _("Logo")), img_text
);
1217 hash
= jabber_calculate_data_hash(data
, size
, "sha1");
1218 purple_buddy_icons_set_for_user(account
, bare_jid
, data
, size
, hash
);
1229 if (serverside_alias
) {
1231 /* If we found a serverside alias, set it and tell the core */
1232 purple_serv_got_alias(js
->gc
, bare_jid
, serverside_alias
);
1233 b
= purple_blist_find_buddy(account
, bare_jid
);
1235 purple_blist_node_set_string((PurpleBlistNode
*)b
, "servernick", serverside_alias
);
1238 g_free(serverside_alias
);
1243 jabber_buddy_info_show_if_ready(jbi
);
1246 static void jabber_buddy_info_resource_free(gpointer data
)
1248 JabberBuddyInfoResource
*jbri
= data
;
1252 static guint
jbir_hash(gconstpointer v
)
1255 return g_str_hash(v
);
1260 static gboolean
jbir_equal(gconstpointer v1
, gconstpointer v2
)
1262 const gchar
*resource_1
= v1
;
1263 const gchar
*resource_2
= v2
;
1265 return purple_strequal(resource_1
, resource_2
);
1268 static void jabber_version_parse(JabberStream
*js
, const char *from
,
1269 JabberIqType type
, const char *id
,
1270 PurpleXmlNode
*packet
, gpointer data
)
1272 JabberBuddyInfo
*jbi
= data
;
1273 PurpleXmlNode
*query
;
1274 char *resource_name
;
1276 g_return_if_fail(jbi
!= NULL
);
1278 jabber_buddy_info_remove_id(jbi
, id
);
1283 resource_name
= jabber_get_resource(from
);
1286 if (type
== JABBER_IQ_RESULT
) {
1287 if((query
= purple_xmlnode_get_child(packet
, "query"))) {
1288 JabberBuddyResource
*jbr
= jabber_buddy_find_resource(jbi
->jb
, resource_name
);
1290 PurpleXmlNode
*node
;
1291 if((node
= purple_xmlnode_get_child(query
, "name"))) {
1292 jbr
->client
.name
= purple_xmlnode_get_data(node
);
1294 if((node
= purple_xmlnode_get_child(query
, "version"))) {
1295 jbr
->client
.version
= purple_xmlnode_get_data(node
);
1297 if((node
= purple_xmlnode_get_child(query
, "os"))) {
1298 jbr
->client
.os
= purple_xmlnode_get_data(node
);
1303 g_free(resource_name
);
1306 jabber_buddy_info_show_if_ready(jbi
);
1309 static void jabber_last_parse(JabberStream
*js
, const char *from
,
1310 JabberIqType type
, const char *id
,
1311 PurpleXmlNode
*packet
, gpointer data
)
1313 JabberBuddyInfo
*jbi
= data
;
1314 PurpleXmlNode
*query
;
1315 char *resource_name
;
1316 const char *seconds
;
1318 g_return_if_fail(jbi
!= NULL
);
1320 jabber_buddy_info_remove_id(jbi
, id
);
1325 resource_name
= jabber_get_resource(from
);
1328 if (type
== JABBER_IQ_RESULT
) {
1329 if((query
= purple_xmlnode_get_child(packet
, "query"))) {
1330 seconds
= purple_xmlnode_get_attrib(query
, "seconds");
1333 long sec
= strtol(seconds
, &end
, 10);
1334 JabberBuddy
*jb
= NULL
;
1335 char *resource
= NULL
;
1336 char *buddy_name
= NULL
;
1337 JabberBuddyResource
*jbr
= NULL
;
1339 if(end
!= seconds
) {
1340 JabberBuddyInfoResource
*jbir
= g_hash_table_lookup(jbi
->resources
, resource_name
);
1342 jbir
->idle_seconds
= sec
;
1345 /* Update the idle time of the buddy resource, if we got it.
1346 This will correct the value when a server doesn't mark
1347 delayed presence and we got the presence when signing on */
1348 jb
= jabber_buddy_find(js
, from
, FALSE
);
1350 resource
= jabber_get_resource(from
);
1351 buddy_name
= jabber_get_bare_jid(from
);
1352 /* if the resource already has an idle time set, we
1353 must have gotten it originally from a presence. In
1354 this case we update it. Otherwise don't update it, to
1355 avoid setting an idle and not getting informed about
1356 the resource getting unidle */
1357 if (resource
&& buddy_name
) {
1358 jbr
= jabber_buddy_find_resource(jb
, resource
);
1362 jbr
->idle
= time(NULL
) - sec
;
1368 jabber_buddy_find_resource(jb
, NULL
)) {
1369 purple_protocol_got_user_idle(purple_connection_get_account(js
->gc
),
1370 buddy_name
, jbr
->idle
, jbr
->idle
);
1381 g_free(resource_name
);
1384 jabber_buddy_info_show_if_ready(jbi
);
1387 static void jabber_last_offline_parse(JabberStream
*js
, const char *from
,
1388 JabberIqType type
, const char *id
,
1389 PurpleXmlNode
*packet
, gpointer data
)
1391 JabberBuddyInfo
*jbi
= data
;
1392 PurpleXmlNode
*query
;
1393 const char *seconds
;
1395 g_return_if_fail(jbi
!= NULL
);
1397 jabber_buddy_info_remove_id(jbi
, id
);
1399 if (type
== JABBER_IQ_RESULT
) {
1400 if((query
= purple_xmlnode_get_child(packet
, "query"))) {
1401 seconds
= purple_xmlnode_get_attrib(query
, "seconds");
1404 long sec
= strtol(seconds
, &end
, 10);
1405 if(end
!= seconds
) {
1406 jbi
->last_seconds
= sec
;
1409 jbi
->last_message
= purple_xmlnode_get_data(query
);
1413 jabber_buddy_info_show_if_ready(jbi
);
1416 static void jabber_time_parse(JabberStream
*js
, const char *from
,
1417 JabberIqType type
, const char *id
,
1418 PurpleXmlNode
*packet
, gpointer data
)
1420 JabberBuddyInfo
*jbi
= data
;
1421 JabberBuddyResource
*jbr
;
1422 char *resource_name
;
1424 g_return_if_fail(jbi
!= NULL
);
1426 jabber_buddy_info_remove_id(jbi
, id
);
1431 resource_name
= jabber_get_resource(from
);
1432 jbr
= resource_name
? jabber_buddy_find_resource(jbi
->jb
, resource_name
) : NULL
;
1433 g_free(resource_name
);
1435 if (type
== JABBER_IQ_RESULT
) {
1436 PurpleXmlNode
*time
= purple_xmlnode_get_child(packet
, "time");
1437 PurpleXmlNode
*tzo
= time
? purple_xmlnode_get_child(time
, "tzo") : NULL
;
1438 char *tzo_data
= tzo
? purple_xmlnode_get_data(tzo
) : NULL
;
1442 if (tzo_data
[0] == 'Z' && tzo_data
[1] == '\0') {
1445 gboolean offset_positive
= (tzo_data
[0] == '+');
1447 if (((*c
== '+' || *c
== '-') && (c
= c
+ 1)) &&
1448 sscanf(c
, "%02d:%02d", &hours
, &minutes
) == 2) {
1449 jbr
->tz_off
= 60*60*hours
+ 60*minutes
;
1450 if (!offset_positive
)
1453 purple_debug_info("jabber", "Ignoring malformed timezone %s",
1463 jabber_buddy_info_show_if_ready(jbi
);
1466 void jabber_buddy_remove_all_pending_buddy_info_requests(JabberStream
*js
)
1468 if (js
->pending_buddy_info_requests
)
1470 JabberBuddyInfo
*jbi
;
1471 GSList
*l
= js
->pending_buddy_info_requests
;
1475 g_slist_free(jbi
->ids
);
1476 jabber_buddy_info_destroy(jbi
);
1481 g_slist_free(js
->pending_buddy_info_requests
);
1482 js
->pending_buddy_info_requests
= NULL
;
1486 static gboolean
jabber_buddy_get_info_timeout(gpointer data
)
1488 JabberBuddyInfo
*jbi
= data
;
1490 /* remove the pending callbacks */
1492 char *id
= jbi
->ids
->data
;
1493 jabber_iq_remove_callback_by_id(jbi
->js
, id
);
1494 jbi
->ids
= g_slist_remove(jbi
->ids
, id
);
1498 jbi
->js
->pending_buddy_info_requests
= g_slist_remove(jbi
->js
->pending_buddy_info_requests
, jbi
);
1499 jbi
->timeout_handle
= 0;
1501 jabber_buddy_info_show_if_ready(jbi
);
1506 static gboolean
_client_is_blacklisted(JabberBuddyResource
*jbr
, const char *ns
)
1508 /* can't be blacklisted if we don't know what you're running yet */
1509 if(!jbr
->client
.name
)
1512 if(!strcmp(ns
, NS_LAST_ACTIVITY
)) {
1513 if(!strcmp(jbr
->client
.name
, "Trillian")) {
1514 /* verified by nwalp 2007/05/09 */
1515 if(!strcmp(jbr
->client
.version
, "3.1.0.121") ||
1516 /* verified by nwalp 2007/09/19 */
1517 !strcmp(jbr
->client
.version
, "3.1.7.0")) {
1527 dispatch_queries_for_resource(JabberStream
*js
, JabberBuddyInfo
*jbi
,
1528 gboolean is_bare_jid
, const char *jid
,
1529 JabberBuddyResource
*jbr
)
1532 JabberBuddyInfoResource
*jbir
;
1533 char *full_jid
= NULL
;
1536 if (is_bare_jid
&& jbr
->name
) {
1537 full_jid
= g_strdup_printf("%s/%s", jid
, jbr
->name
);
1542 jbir
= g_new0(JabberBuddyInfoResource
, 1);
1543 g_hash_table_insert(jbi
->resources
, g_strdup(jbr
->name
), jbir
);
1545 if(!jbr
->client
.name
) {
1546 iq
= jabber_iq_new_query(js
, JABBER_IQ_GET
, "jabber:iq:version");
1547 purple_xmlnode_set_attrib(iq
->node
, "to", to
);
1548 jabber_iq_set_callback(iq
, jabber_version_parse
, jbi
);
1549 jbi
->ids
= g_slist_prepend(jbi
->ids
, g_strdup(iq
->id
));
1553 /* this is to fix the feeling of irritation I get when trying
1554 * to get info on a friend running Trillian, which doesn't
1555 * respond (with an error or otherwise) to jabber:iq:last
1556 * requests. There are a number of Trillian users in my
1558 if(!_client_is_blacklisted(jbr
, NS_LAST_ACTIVITY
)) {
1559 iq
= jabber_iq_new_query(js
, JABBER_IQ_GET
, NS_LAST_ACTIVITY
);
1560 purple_xmlnode_set_attrib(iq
->node
, "to", to
);
1561 jabber_iq_set_callback(iq
, jabber_last_parse
, jbi
);
1562 jbi
->ids
= g_slist_prepend(jbi
->ids
, g_strdup(iq
->id
));
1566 if (jbr
->tz_off
== PURPLE_NO_TZ_OFF
&&
1568 jabber_resource_has_capability(jbr
, NS_ENTITY_TIME
))) {
1569 PurpleXmlNode
*child
;
1570 iq
= jabber_iq_new(js
, JABBER_IQ_GET
);
1571 purple_xmlnode_set_attrib(iq
->node
, "to", to
);
1572 child
= purple_xmlnode_new_child(iq
->node
, "time");
1573 purple_xmlnode_set_namespace(child
, NS_ENTITY_TIME
);
1574 jabber_iq_set_callback(iq
, jabber_time_parse
, jbi
);
1575 jbi
->ids
= g_slist_prepend(jbi
->ids
, g_strdup(iq
->id
));
1582 static void jabber_buddy_get_info_for_jid(JabberStream
*js
, const char *jid
)
1585 PurpleXmlNode
*vcard
;
1588 JabberBuddyInfo
*jbi
;
1590 gboolean is_bare_jid
;
1592 jb
= jabber_buddy_find(js
, jid
, TRUE
);
1598 slash
= strchr(jid
, '/');
1599 is_bare_jid
= (slash
== NULL
);
1601 jbi
= g_new0(JabberBuddyInfo
, 1);
1602 jbi
->jid
= g_strdup(jid
);
1605 jbi
->resources
= g_hash_table_new_full(jbir_hash
, jbir_equal
, g_free
, jabber_buddy_info_resource_free
);
1606 jbi
->user_info
= purple_notify_user_info_new();
1608 iq
= jabber_iq_new(js
, JABBER_IQ_GET
);
1610 purple_xmlnode_set_attrib(iq
->node
, "to", jid
);
1611 vcard
= purple_xmlnode_new_child(iq
->node
, "vCard");
1612 purple_xmlnode_set_namespace(vcard
, "vcard-temp");
1614 jabber_iq_set_callback(iq
, jabber_vcard_parse
, jbi
);
1615 jbi
->ids
= g_slist_prepend(jbi
->ids
, g_strdup(iq
->id
));
1620 if (jb
->resources
) {
1621 for(resources
= jb
->resources
; resources
; resources
= resources
->next
) {
1622 JabberBuddyResource
*jbr
= resources
->data
;
1623 dispatch_queries_for_resource(js
, jbi
, is_bare_jid
, jid
, jbr
);
1626 /* user is offline, send a jabber:iq:last to find out last time online */
1627 iq
= jabber_iq_new_query(js
, JABBER_IQ_GET
, NS_LAST_ACTIVITY
);
1628 purple_xmlnode_set_attrib(iq
->node
, "to", jid
);
1629 jabber_iq_set_callback(iq
, jabber_last_offline_parse
, jbi
);
1630 jbi
->ids
= g_slist_prepend(jbi
->ids
, g_strdup(iq
->id
));
1634 JabberBuddyResource
*jbr
= jabber_buddy_find_resource(jb
, slash
+ 1);
1636 dispatch_queries_for_resource(js
, jbi
, is_bare_jid
, jid
, jbr
);
1638 purple_debug_warning("jabber", "jabber_buddy_get_info_for_jid() "
1639 "was passed JID %s, but there is no corresponding "
1640 "JabberBuddyResource!\n", jid
);
1643 js
->pending_buddy_info_requests
= g_slist_prepend(js
->pending_buddy_info_requests
, jbi
);
1644 jbi
->timeout_handle
= purple_timeout_add_seconds(30, jabber_buddy_get_info_timeout
, jbi
);
1647 void jabber_buddy_get_info(PurpleConnection
*gc
, const char *who
)
1649 JabberStream
*js
= purple_connection_get_protocol_data(gc
);
1650 JabberID
*jid
= jabber_id_new(who
);
1655 if (jid
->node
&& jabber_chat_find(js
, jid
->node
, jid
->domain
)) {
1656 /* For a conversation, include the resource (indicates the user). */
1657 jabber_buddy_get_info_for_jid(js
, who
);
1659 char *bare_jid
= jabber_get_bare_jid(who
);
1660 jabber_buddy_get_info_for_jid(js
, bare_jid
);
1664 jabber_id_free(jid
);
1667 static void jabber_buddy_set_invisibility(JabberStream
*js
, const char *who
,
1670 PurplePresence
*gpresence
;
1671 PurpleAccount
*account
;
1672 PurpleStatus
*status
;
1673 JabberBuddy
*jb
= jabber_buddy_find(js
, who
, TRUE
);
1674 PurpleXmlNode
*presence
;
1675 JabberBuddyState state
;
1679 account
= purple_connection_get_account(js
->gc
);
1680 gpresence
= purple_account_get_presence(account
);
1681 status
= purple_presence_get_active_status(gpresence
);
1683 purple_status_to_jabber(status
, &state
, &msg
, &priority
);
1684 presence
= jabber_presence_create_js(js
, state
, msg
, priority
);
1688 purple_xmlnode_set_attrib(presence
, "to", who
);
1690 purple_xmlnode_set_attrib(presence
, "type", "invisible");
1691 jb
->invisible
|= JABBER_INVIS_BUDDY
;
1693 jb
->invisible
&= ~JABBER_INVIS_BUDDY
;
1696 jabber_send(js
, presence
);
1697 purple_xmlnode_free(presence
);
1700 static void jabber_buddy_make_invisible(PurpleBlistNode
*node
, gpointer data
)
1703 PurpleConnection
*gc
;
1706 g_return_if_fail(PURPLE_IS_BUDDY(node
));
1708 buddy
= (PurpleBuddy
*) node
;
1709 gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1710 js
= purple_connection_get_protocol_data(gc
);
1712 jabber_buddy_set_invisibility(js
, purple_buddy_get_name(buddy
), TRUE
);
1715 static void jabber_buddy_make_visible(PurpleBlistNode
*node
, gpointer data
)
1718 PurpleConnection
*gc
;
1721 g_return_if_fail(PURPLE_IS_BUDDY(node
));
1723 buddy
= (PurpleBuddy
*) node
;
1724 gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1725 js
= purple_connection_get_protocol_data(gc
);
1727 jabber_buddy_set_invisibility(js
, purple_buddy_get_name(buddy
), FALSE
);
1730 static void cancel_presence_notification(gpointer data
)
1733 PurpleConnection
*gc
;
1737 gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1738 js
= purple_connection_get_protocol_data(gc
);
1740 jabber_presence_subscription_set(js
, purple_buddy_get_name(buddy
), "unsubscribed");
1744 jabber_buddy_cancel_presence_notification(PurpleBlistNode
*node
,
1748 PurpleAccount
*account
;
1749 PurpleConnection
*gc
;
1753 g_return_if_fail(PURPLE_IS_BUDDY(node
));
1755 buddy
= (PurpleBuddy
*) node
;
1756 name
= purple_buddy_get_name(buddy
);
1757 account
= purple_buddy_get_account(buddy
);
1758 gc
= purple_account_get_connection(account
);
1760 msg
= g_strdup_printf(_("%s will no longer be able to see your status "
1761 "updates. Do you want to continue?"), name
);
1762 purple_request_yes_no(gc
, NULL
, _("Cancel Presence Notification"),
1763 msg
, 0 /* Yes */, purple_request_cpar_from_account(account
), buddy
,
1764 cancel_presence_notification
, NULL
/* Do nothing */);
1768 static void jabber_buddy_rerequest_auth(PurpleBlistNode
*node
, gpointer data
)
1771 PurpleConnection
*gc
;
1774 g_return_if_fail(PURPLE_IS_BUDDY(node
));
1776 buddy
= (PurpleBuddy
*) node
;
1777 gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1778 js
= purple_connection_get_protocol_data(gc
);
1780 jabber_presence_subscription_set(js
, purple_buddy_get_name(buddy
), "subscribe");
1784 static void jabber_buddy_unsubscribe(PurpleBlistNode
*node
, gpointer data
)
1787 PurpleConnection
*gc
;
1790 g_return_if_fail(PURPLE_IS_BUDDY(node
));
1792 buddy
= (PurpleBuddy
*) node
;
1793 gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1794 js
= purple_connection_get_protocol_data(gc
);
1796 jabber_presence_subscription_set(js
, purple_buddy_get_name(buddy
), "unsubscribe");
1799 static void jabber_buddy_login(PurpleBlistNode
*node
, gpointer data
) {
1800 if(PURPLE_IS_BUDDY(node
)) {
1801 /* simply create a directed presence of the current status */
1802 PurpleBuddy
*buddy
= (PurpleBuddy
*) node
;
1803 PurpleConnection
*gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1804 JabberStream
*js
= purple_connection_get_protocol_data(gc
);
1805 PurpleAccount
*account
= purple_connection_get_account(gc
);
1806 PurplePresence
*gpresence
= purple_account_get_presence(account
);
1807 PurpleStatus
*status
= purple_presence_get_active_status(gpresence
);
1808 PurpleXmlNode
*presence
;
1809 JabberBuddyState state
;
1813 purple_status_to_jabber(status
, &state
, &msg
, &priority
);
1814 presence
= jabber_presence_create_js(js
, state
, msg
, priority
);
1818 purple_xmlnode_set_attrib(presence
, "to", purple_buddy_get_name(buddy
));
1820 jabber_send(js
, presence
);
1821 purple_xmlnode_free(presence
);
1825 static void jabber_buddy_logout(PurpleBlistNode
*node
, gpointer data
) {
1826 if(PURPLE_IS_BUDDY(node
)) {
1827 /* simply create a directed unavailable presence */
1828 PurpleBuddy
*buddy
= (PurpleBuddy
*) node
;
1829 PurpleConnection
*gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1830 JabberStream
*js
= purple_connection_get_protocol_data(gc
);
1831 PurpleXmlNode
*presence
;
1833 presence
= jabber_presence_create_js(js
, JABBER_BUDDY_STATE_UNAVAILABLE
, NULL
, 0);
1835 purple_xmlnode_set_attrib(presence
, "to", purple_buddy_get_name(buddy
));
1837 jabber_send(js
, presence
);
1838 purple_xmlnode_free(presence
);
1842 static GList
*jabber_buddy_menu(PurpleBuddy
*buddy
)
1844 PurpleConnection
*gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1845 JabberStream
*js
= purple_connection_get_protocol_data(gc
);
1846 const char *name
= purple_buddy_get_name(buddy
);
1847 JabberBuddy
*jb
= jabber_buddy_find(js
, name
, TRUE
);
1851 PurpleMenuAction
*act
;
1856 if (js
->protocol_version
.major
== 0 && js
->protocol_version
.minor
== 9 &&
1857 jb
!= js
->user_jb
) {
1858 if(jb
->invisible
& JABBER_INVIS_BUDDY
) {
1859 act
= purple_menu_action_new(_("Un-hide From"),
1860 PURPLE_CALLBACK(jabber_buddy_make_visible
),
1863 act
= purple_menu_action_new(_("Temporarily Hide From"),
1864 PURPLE_CALLBACK(jabber_buddy_make_invisible
),
1867 m
= g_list_append(m
, act
);
1870 if(jb
->subscription
& JABBER_SUB_FROM
&& jb
!= js
->user_jb
) {
1871 act
= purple_menu_action_new(_("Cancel Presence Notification"),
1872 PURPLE_CALLBACK(jabber_buddy_cancel_presence_notification
),
1874 m
= g_list_append(m
, act
);
1877 if(!(jb
->subscription
& JABBER_SUB_TO
)) {
1878 act
= purple_menu_action_new(_("(Re-)Request authorization"),
1879 PURPLE_CALLBACK(jabber_buddy_rerequest_auth
),
1881 m
= g_list_append(m
, act
);
1883 } else if (jb
!= js
->user_jb
) {
1885 /* shouldn't this just happen automatically when the buddy is
1887 act
= purple_menu_action_new(_("Unsubscribe"),
1888 PURPLE_CALLBACK(jabber_buddy_unsubscribe
),
1890 m
= g_list_append(m
, act
);
1893 if (js
->googletalk
) {
1894 act
= purple_menu_action_new(_("Initiate _Chat"),
1895 PURPLE_CALLBACK(google_buddy_node_chat
),
1897 m
= g_list_append(m
, act
);
1901 * This if-condition implements parts of XEP-0100: Gateway Interaction
1903 * According to stpeter, there is no way to know if a jid on the roster is a gateway without sending a disco#info.
1904 * However, since the gateway might appear offline to us, we cannot get that information. Therefore, I just assume
1905 * that gateways on the roster can be identified by having no '@' in their jid. This is a faily safe assumption, since
1906 * people don't tend to have a server or other service there.
1908 * TODO: Use disco#info...
1910 if (strchr(name
, '@') == NULL
) {
1911 act
= purple_menu_action_new(_("Log In"),
1912 PURPLE_CALLBACK(jabber_buddy_login
),
1914 m
= g_list_append(m
, act
);
1915 act
= purple_menu_action_new(_("Log Out"),
1916 PURPLE_CALLBACK(jabber_buddy_logout
),
1918 m
= g_list_append(m
, act
);
1921 /* add all ad hoc commands to the action menu */
1922 for(jbrs
= jb
->resources
; jbrs
; jbrs
= g_list_next(jbrs
)) {
1923 JabberBuddyResource
*jbr
= jbrs
->data
;
1927 for(commands
= jbr
->commands
; commands
; commands
= g_list_next(commands
)) {
1928 JabberAdHocCommands
*cmd
= commands
->data
;
1929 act
= purple_menu_action_new(cmd
->name
, PURPLE_CALLBACK(jabber_adhoc_execute_action
), cmd
, NULL
);
1930 m
= g_list_append(m
, act
);
1938 jabber_blist_node_menu(PurpleBlistNode
*node
)
1940 if(PURPLE_IS_BUDDY(node
)) {
1941 return jabber_buddy_menu((PurpleBuddy
*) node
);
1948 static void user_search_result_add_buddy_cb(PurpleConnection
*gc
, GList
*row
, void *user_data
)
1950 /* XXX find out the jid */
1951 purple_blist_request_add_buddy(purple_connection_get_account(gc
),
1952 g_list_nth_data(row
, 0), NULL
, NULL
);
1955 static void user_search_result_cb(JabberStream
*js
, const char *from
,
1956 JabberIqType type
, const char *id
,
1957 PurpleXmlNode
*packet
, gpointer data
)
1959 PurpleNotifySearchResults
*results
;
1960 PurpleNotifySearchColumn
*column
;
1961 PurpleXmlNode
*x
, *query
, *item
, *field
;
1963 /* XXX error checking? */
1964 if(!(query
= purple_xmlnode_get_child(packet
, "query")))
1967 results
= purple_notify_searchresults_new();
1968 if((x
= purple_xmlnode_get_child_with_namespace(query
, "x", "jabber:x:data"))) {
1969 PurpleXmlNode
*reported
;
1970 GSList
*column_vars
= NULL
;
1972 purple_debug_info("jabber", "new-skool\n");
1974 if((reported
= purple_xmlnode_get_child(x
, "reported"))) {
1975 PurpleXmlNode
*field
= purple_xmlnode_get_child(reported
, "field");
1977 const char *var
= purple_xmlnode_get_attrib(field
, "var");
1978 const char *label
= purple_xmlnode_get_attrib(field
, "label");
1980 column
= purple_notify_searchresults_column_new(label
? label
: var
);
1981 purple_notify_searchresults_column_add(results
, column
);
1982 column_vars
= g_slist_append(column_vars
, (char *)var
);
1984 field
= purple_xmlnode_get_next_twin(field
);
1988 item
= purple_xmlnode_get_child(x
, "item");
1992 PurpleXmlNode
*valuenode
;
1995 for (l
= column_vars
; l
!= NULL
; l
= l
->next
) {
1997 * Build a row containing the strings that correspond
1998 * to each column of the search results.
2000 for (field
= purple_xmlnode_get_child(item
, "field");
2002 field
= purple_xmlnode_get_next_twin(field
))
2004 if ((var
= purple_xmlnode_get_attrib(field
, "var")) &&
2005 !strcmp(var
, l
->data
) &&
2006 (valuenode
= purple_xmlnode_get_child(field
, "value")))
2008 char *value
= purple_xmlnode_get_data(valuenode
);
2009 row
= g_list_append(row
, value
);
2014 /* No data for this column */
2015 row
= g_list_append(row
, NULL
);
2017 purple_notify_searchresults_row_add(results
, row
);
2018 item
= purple_xmlnode_get_next_twin(item
);
2021 g_slist_free(column_vars
);
2024 purple_debug_info("jabber", "old-skool\n");
2026 column
= purple_notify_searchresults_column_new(_("JID"));
2027 purple_notify_searchresults_column_add(results
, column
);
2028 column
= purple_notify_searchresults_column_new(_("First Name"));
2029 purple_notify_searchresults_column_add(results
, column
);
2030 column
= purple_notify_searchresults_column_new(_("Last Name"));
2031 purple_notify_searchresults_column_add(results
, column
);
2032 column
= purple_notify_searchresults_column_new(_("Nickname"));
2033 purple_notify_searchresults_column_add(results
, column
);
2034 column
= purple_notify_searchresults_column_new(_("Email"));
2035 purple_notify_searchresults_column_add(results
, column
);
2037 for(item
= purple_xmlnode_get_child(query
, "item"); item
; item
= purple_xmlnode_get_next_twin(item
)) {
2039 PurpleXmlNode
*node
;
2042 if(!(jid
= purple_xmlnode_get_attrib(item
, "jid")))
2045 row
= g_list_append(row
, g_strdup(jid
));
2046 node
= purple_xmlnode_get_child(item
, "first");
2047 row
= g_list_append(row
, node
? purple_xmlnode_get_data(node
) : NULL
);
2048 node
= purple_xmlnode_get_child(item
, "last");
2049 row
= g_list_append(row
, node
? purple_xmlnode_get_data(node
) : NULL
);
2050 node
= purple_xmlnode_get_child(item
, "nick");
2051 row
= g_list_append(row
, node
? purple_xmlnode_get_data(node
) : NULL
);
2052 node
= purple_xmlnode_get_child(item
, "email");
2053 row
= g_list_append(row
, node
? purple_xmlnode_get_data(node
) : NULL
);
2054 purple_debug_info("jabber", "row=%p\n", row
);
2055 purple_notify_searchresults_row_add(results
, row
);
2059 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_ADD
,
2060 user_search_result_add_buddy_cb
);
2062 purple_notify_searchresults(js
->gc
, NULL
, NULL
, _("The following are the results of your search"), results
, NULL
, NULL
);
2065 static void user_search_x_data_cb(JabberStream
*js
, PurpleXmlNode
*result
, gpointer data
)
2067 PurpleXmlNode
*query
;
2069 char *dir_server
= data
;
2072 /* if they've cancelled the search, we're
2073 * just going to get an error if we send
2074 * a cancel, so skip it */
2075 type
= purple_xmlnode_get_attrib(result
, "type");
2076 if(type
&& !strcmp(type
, "cancel")) {
2081 iq
= jabber_iq_new_query(js
, JABBER_IQ_SET
, "jabber:iq:search");
2082 query
= purple_xmlnode_get_child(iq
->node
, "query");
2084 purple_xmlnode_insert_child(query
, result
);
2086 jabber_iq_set_callback(iq
, user_search_result_cb
, NULL
);
2087 purple_xmlnode_set_attrib(iq
->node
, "to", dir_server
);
2092 struct user_search_info
{
2094 char *directory_server
;
2097 static void user_search_cancel_cb(struct user_search_info
*usi
, PurpleRequestFields
*fields
)
2099 g_free(usi
->directory_server
);
2103 static void user_search_cb(struct user_search_info
*usi
, PurpleRequestFields
*fields
)
2105 JabberStream
*js
= usi
->js
;
2107 PurpleXmlNode
*query
;
2108 GList
*groups
, *flds
;
2110 iq
= jabber_iq_new_query(js
, JABBER_IQ_SET
, "jabber:iq:search");
2111 query
= purple_xmlnode_get_child(iq
->node
, "query");
2113 for(groups
= purple_request_fields_get_groups(fields
); groups
; groups
= groups
->next
) {
2114 for(flds
= purple_request_field_group_get_fields(groups
->data
);
2115 flds
; flds
= flds
->next
) {
2116 PurpleRequestField
*field
= flds
->data
;
2117 const char *id
= purple_request_field_get_id(field
);
2118 const char *value
= purple_request_field_string_get_value(field
);
2120 if(value
&& (!strcmp(id
, "first") || !strcmp(id
, "last") || !strcmp(id
, "nick") || !strcmp(id
, "email"))) {
2121 PurpleXmlNode
*y
= purple_xmlnode_new_child(query
, id
);
2122 purple_xmlnode_insert_data(y
, value
, -1);
2127 jabber_iq_set_callback(iq
, user_search_result_cb
, NULL
);
2128 purple_xmlnode_set_attrib(iq
->node
, "to", usi
->directory_server
);
2131 g_free(usi
->directory_server
);
2136 /* This is for gettext only -- it will see this even though there's an #if 0. */
2139 * An incomplete list of server generated original language search
2140 * comments for Jabber User Directories
2142 * See discussion thread "Search comment for Jabber is not translatable"
2143 * in purple-i18n@lists.sourceforge.net (March 2006)
2145 static const char * jabber_user_dir_comments
[] = {
2146 /* current comment from Jabber User Directory users.jabber.org */
2147 N_("Find a contact by entering the search criteria in the given fields. "
2148 "Note: Each field supports wild card searches (%)"),
2153 static void user_search_fields_result_cb(JabberStream
*js
, const char *from
,
2154 JabberIqType type
, const char *id
,
2155 PurpleXmlNode
*packet
, gpointer data
)
2157 PurpleXmlNode
*query
, *x
;
2162 if (type
== JABBER_IQ_ERROR
) {
2163 char *msg
= jabber_parse_error(js
, packet
, NULL
);
2166 msg
= g_strdup(_("Unknown error"));
2168 purple_notify_error(js
->gc
, _("Directory Query Failed"),
2169 _("Could not query the directory server."), msg
,
2170 purple_request_cpar_from_connection(js
->gc
));
2177 if(!(query
= purple_xmlnode_get_child(packet
, "query")))
2180 if((x
= purple_xmlnode_get_child_with_namespace(query
, "x", "jabber:x:data"))) {
2181 jabber_x_data_request(js
, x
, user_search_x_data_cb
, g_strdup(from
));
2184 struct user_search_info
*usi
;
2185 PurpleXmlNode
*instnode
;
2186 char *instructions
= NULL
;
2187 PurpleRequestFields
*fields
;
2188 PurpleRequestFieldGroup
*group
;
2189 PurpleRequestField
*field
;
2192 fields
= purple_request_fields_new();
2193 group
= purple_request_field_group_new(NULL
);
2194 purple_request_fields_add_group(fields
, group
);
2196 if((instnode
= purple_xmlnode_get_child(query
, "instructions")))
2198 char *tmp
= purple_xmlnode_get_data(instnode
);
2202 /* Try to translate the message (see static message
2203 list in jabber_user_dir_comments[]) */
2204 instructions
= g_strdup_printf(_("Server Instructions: %s"), _(tmp
));
2211 instructions
= g_strdup(_("Fill in one or more fields to search "
2212 "for any matching XMPP users."));
2215 if(purple_xmlnode_get_child(query
, "first")) {
2216 field
= purple_request_field_string_new("first", _("First Name"),
2218 purple_request_field_group_add_field(group
, field
);
2220 if(purple_xmlnode_get_child(query
, "last")) {
2221 field
= purple_request_field_string_new("last", _("Last Name"),
2223 purple_request_field_group_add_field(group
, field
);
2225 if(purple_xmlnode_get_child(query
, "nick")) {
2226 field
= purple_request_field_string_new("nick", _("Nickname"),
2228 purple_request_field_group_add_field(group
, field
);
2230 if(purple_xmlnode_get_child(query
, "email")) {
2231 field
= purple_request_field_string_new("email", _("Email Address"),
2233 purple_request_field_group_add_field(group
, field
);
2236 usi
= g_new0(struct user_search_info
, 1);
2238 usi
->directory_server
= g_strdup(from
);
2240 purple_request_fields(js
->gc
, _("Search for XMPP users"),
2241 _("Search for XMPP users"), instructions
, fields
,
2242 _("Search"), G_CALLBACK(user_search_cb
),
2243 _("Cancel"), G_CALLBACK(user_search_cancel_cb
),
2244 purple_request_cpar_from_connection(js
->gc
),
2247 g_free(instructions
);
2251 void jabber_user_search(JabberStream
*js
, const char *directory
)
2255 /* XXX: should probably better validate the directory we're given */
2256 if(!directory
|| !*directory
) {
2257 purple_notify_error(js
->gc
, _("Invalid Directory"),
2258 _("Invalid Directory"), NULL
,
2259 purple_request_cpar_from_connection(js
->gc
));
2263 /* If the value provided isn't the disco#info default, persist it. Otherwise,
2264 make sure we aren't persisting an old value */
2265 if(js
->user_directories
&& js
->user_directories
->data
&&
2266 !strcmp(directory
, js
->user_directories
->data
)) {
2267 purple_account_set_string(purple_connection_get_account(js
->gc
), "user_directory", "");
2270 purple_account_set_string(purple_connection_get_account(js
->gc
), "user_directory", directory
);
2273 iq
= jabber_iq_new_query(js
, JABBER_IQ_GET
, "jabber:iq:search");
2274 purple_xmlnode_set_attrib(iq
->node
, "to", directory
);
2276 jabber_iq_set_callback(iq
, user_search_fields_result_cb
, NULL
);
2281 void jabber_user_search_begin(PurpleProtocolAction
*action
)
2283 PurpleConnection
*gc
= (PurpleConnection
*) action
->connection
;
2284 JabberStream
*js
= purple_connection_get_protocol_data(gc
);
2285 const char *def_val
= purple_account_get_string(purple_connection_get_account(js
->gc
), "user_directory", "");
2286 if(!*def_val
&& js
->user_directories
)
2287 def_val
= js
->user_directories
->data
;
2289 purple_request_input(gc
, _("Enter a User Directory"), _("Enter a User Directory"),
2290 _("Select a user directory to search"),
2293 _("Search Directory"), PURPLE_CALLBACK(jabber_user_search
),
2299 jabber_resource_know_capabilities(const JabberBuddyResource
*jbr
)
2301 return jbr
->caps
.info
!= NULL
;
2305 jabber_resource_has_capability(const JabberBuddyResource
*jbr
, const gchar
*cap
)
2307 const GList
*node
= NULL
;
2308 const JabberCapsNodeExts
*exts
;
2310 if (!jbr
->caps
.info
) {
2311 purple_debug_info("jabber",
2312 "Unable to find caps: nothing known about buddy\n");
2316 node
= g_list_find_custom(jbr
->caps
.info
->features
, cap
, (GCompareFunc
)strcmp
);
2317 if (!node
&& jbr
->caps
.exts
&& jbr
->caps
.info
->exts
) {
2319 exts
= jbr
->caps
.info
->exts
;
2320 /* Walk through all the enabled caps, checking each list for the cap.
2321 * Don't check it twice, though. */
2322 for (ext
= jbr
->caps
.exts
; ext
&& !node
; ext
= ext
->next
) {
2323 GList
*features
= g_hash_table_lookup(exts
->exts
, ext
->data
);
2325 node
= g_list_find_custom(features
, cap
, (GCompareFunc
)strcmp
);
2329 return (node
!= NULL
);
2333 jabber_buddy_has_capability(const JabberBuddy
*jb
, const gchar
*cap
)
2335 JabberBuddyResource
*jbr
= jabber_buddy_find_resource((JabberBuddy
*)jb
, NULL
);
2338 purple_debug_info("jabber",
2339 "Unable to find caps: buddy might be offline\n");
2343 return jabber_resource_has_capability(jbr
, cap
);
2347 jabber_resource_get_identity_category_type(const JabberBuddyResource
*jbr
,
2348 const gchar
*category
)
2350 const GList
*iter
= NULL
;
2352 if (jbr
->caps
.info
) {
2353 for (iter
= jbr
->caps
.info
->identities
; iter
; iter
= g_list_next(iter
)) {
2354 const JabberIdentity
*identity
=
2355 (JabberIdentity
*) iter
->data
;
2357 if (strcmp(identity
->category
, category
) == 0) {
2358 return identity
->type
;