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 while (jbi
->vcard_images
) {
884 g_object_unref(jbi
->vcard_images
->data
);
885 jbi
->vcard_images
= g_slist_delete_link(jbi
->vcard_images
, jbi
->vcard_images
);
888 jbi
->js
->pending_buddy_info_requests
= g_slist_remove(jbi
->js
->pending_buddy_info_requests
, jbi
);
890 jabber_buddy_info_destroy(jbi
);
893 static void jabber_buddy_info_remove_id(JabberBuddyInfo
*jbi
, const char *id
)
895 GSList
*l
= jbi
->ids
;
903 if(purple_strequal(id
, comp_id
)) {
904 jbi
->ids
= g_slist_remove(jbi
->ids
, comp_id
);
913 set_own_vcard_cb(gpointer data
)
915 JabberStream
*js
= data
;
916 PurpleAccount
*account
= purple_connection_get_account(js
->gc
);
920 jabber_set_info(js
->gc
, purple_account_get_user_info(account
));
925 static void jabber_vcard_save_mine(JabberStream
*js
, const char *from
,
926 JabberIqType type
, const char *id
,
927 PurpleXmlNode
*packet
, gpointer data
)
929 PurpleXmlNode
*vcard
, *photo
, *binval
;
930 char *txt
, *vcard_hash
= NULL
;
931 PurpleAccount
*account
;
933 if (type
== JABBER_IQ_ERROR
) {
934 PurpleXmlNode
*error
;
935 purple_debug_warning("jabber", "Server returned error while retrieving vCard\n");
937 error
= purple_xmlnode_get_child(packet
, "error");
938 if (!error
|| !purple_xmlnode_get_child(error
, "item-not-found"))
942 account
= purple_connection_get_account(js
->gc
);
944 if((vcard
= purple_xmlnode_get_child(packet
, "vCard")) ||
945 (vcard
= purple_xmlnode_get_child_with_namespace(packet
, "query", "vcard-temp")))
947 txt
= purple_xmlnode_to_str(vcard
, NULL
);
948 purple_account_set_user_info(account
, txt
);
951 /* if we have no vCard, then lets not overwrite what we might have locally */
954 js
->vcard_fetched
= TRUE
;
956 if (vcard
&& (photo
= purple_xmlnode_get_child(vcard
, "PHOTO")) &&
957 (binval
= purple_xmlnode_get_child(photo
, "BINVAL"))) {
959 char *bintext
= purple_xmlnode_get_data(binval
);
961 guchar
*data
= g_base64_decode(bintext
, &size
);
965 vcard_hash
= g_compute_checksum_for_data(
966 G_CHECKSUM_SHA1
, data
, size
);
972 /* Republish our vcard if the photo is different than the server's */
973 if (js
->initial_avatar_hash
&& !purple_strequal(vcard_hash
, js
->initial_avatar_hash
)) {
975 * Google Talk has developed the behavior that it will not accept
976 * a vcard set in the first 10 seconds (or so) of the connection;
977 * it returns an error (namespaces trimmed):
978 * <error code="500" type="wait"><internal-server-error/></error>.
981 js
->vcard_timer
= g_timeout_add_seconds(10, set_own_vcard_cb
,
984 jabber_set_info(js
->gc
, purple_account_get_user_info(account
));
985 } else if (vcard_hash
) {
986 /* A photo is in the vCard. Advertise its hash */
987 js
->avatar_hash
= vcard_hash
;
990 /* Send presence to update vcard-temp:x:update */
991 jabber_presence_send(js
, FALSE
);
997 void jabber_vcard_fetch_mine(JabberStream
*js
)
999 JabberIq
*iq
= jabber_iq_new(js
, JABBER_IQ_GET
);
1001 PurpleXmlNode
*vcard
= purple_xmlnode_new_child(iq
->node
, "vCard");
1002 purple_xmlnode_set_namespace(vcard
, "vcard-temp");
1003 jabber_iq_set_callback(iq
, jabber_vcard_save_mine
, NULL
);
1008 static void jabber_vcard_parse(JabberStream
*js
, const char *from
,
1009 JabberIqType type
, const char *id
,
1010 PurpleXmlNode
*packet
, gpointer data
)
1014 char *serverside_alias
= NULL
;
1015 PurpleXmlNode
*vcard
;
1016 PurpleAccount
*account
;
1017 JabberBuddyInfo
*jbi
= data
;
1018 PurpleNotifyUserInfo
*user_info
;
1020 g_return_if_fail(jbi
!= NULL
);
1022 jabber_buddy_info_remove_id(jbi
, id
);
1024 if (type
== JABBER_IQ_ERROR
) {
1025 purple_debug_info("jabber", "Got error response for vCard\n");
1026 jabber_buddy_info_show_if_ready(jbi
);
1030 user_info
= jbi
->user_info
;
1031 account
= purple_connection_get_account(js
->gc
);
1032 bare_jid
= jabber_get_bare_jid(from
? from
: purple_account_get_username(account
));
1034 /* TODO: Is the query xmlns='vcard-temp' version of this still necessary? */
1035 if((vcard
= purple_xmlnode_get_child(packet
, "vCard")) ||
1036 (vcard
= purple_xmlnode_get_child_with_namespace(packet
, "query", "vcard-temp"))) {
1037 PurpleXmlNode
*child
;
1038 for(child
= vcard
->child
; child
; child
= child
->next
)
1040 PurpleXmlNode
*child2
;
1042 if(child
->type
!= PURPLE_XMLNODE_TYPE_TAG
)
1045 text
= purple_xmlnode_get_data(child
);
1046 if(text
&& purple_strequal(child
->name
, "FN")) {
1047 if (!serverside_alias
)
1048 serverside_alias
= g_strdup(text
);
1050 purple_notify_user_info_add_pair_plaintext(user_info
, _("Full Name"), text
);
1051 } else if(purple_strequal(child
->name
, "N")) {
1052 for(child2
= child
->child
; child2
; child2
= child2
->next
)
1056 if(child2
->type
!= PURPLE_XMLNODE_TYPE_TAG
)
1059 text2
= purple_xmlnode_get_data(child2
);
1060 if(text2
&& purple_strequal(child2
->name
, "FAMILY")) {
1061 purple_notify_user_info_add_pair_plaintext(user_info
, _("Family Name"), text2
);
1062 } else if(text2
&& purple_strequal(child2
->name
, "GIVEN")) {
1063 purple_notify_user_info_add_pair_plaintext(user_info
, _("Given Name"), text2
);
1064 } else if(text2
&& purple_strequal(child2
->name
, "MIDDLE")) {
1065 purple_notify_user_info_add_pair_plaintext(user_info
, _("Middle Name"), text2
);
1069 } else if(text
&& purple_strequal(child
->name
, "NICKNAME")) {
1070 /* Prefer the Nickcname to the Full Name as the serverside alias if it's not just part of the jid.
1071 * Ignore it if it's part of the jid. */
1072 if (strstr(bare_jid
, text
) == NULL
) {
1073 g_free(serverside_alias
);
1074 serverside_alias
= g_strdup(text
);
1076 purple_notify_user_info_add_pair_plaintext(user_info
, _("Nickname"), text
);
1078 } else if(text
&& purple_strequal(child
->name
, "BDAY")) {
1079 purple_notify_user_info_add_pair_plaintext(user_info
, _("Birthday"), text
);
1080 } else if(purple_strequal(child
->name
, "ADR")) {
1081 gboolean address_line_added
= FALSE
;
1083 for(child2
= child
->child
; child2
; child2
= child2
->next
)
1087 if(child2
->type
!= PURPLE_XMLNODE_TYPE_TAG
)
1090 text2
= purple_xmlnode_get_data(child2
);
1094 /* We do this here so that it's not added if all the child
1095 * elements are empty. */
1096 if (!address_line_added
)
1098 purple_notify_user_info_add_section_header(user_info
, _("Address"));
1099 address_line_added
= TRUE
;
1102 if(purple_strequal(child2
->name
, "POBOX")) {
1103 purple_notify_user_info_add_pair_plaintext(user_info
, _("P.O. Box"), text2
);
1104 } else if (purple_strequal(child2
->name
, "EXTADD") || purple_strequal(child2
->name
, "EXTADR")) {
1106 * EXTADD is correct, EXTADR is generated by other
1107 * clients. The next time someone reads this, remove
1110 purple_notify_user_info_add_pair_plaintext(user_info
, _("Extended Address"), text2
);
1111 } else if(purple_strequal(child2
->name
, "STREET")) {
1112 purple_notify_user_info_add_pair_plaintext(user_info
, _("Street Address"), text2
);
1113 } else if(purple_strequal(child2
->name
, "LOCALITY")) {
1114 purple_notify_user_info_add_pair_plaintext(user_info
, _("Locality"), text2
);
1115 } else if(purple_strequal(child2
->name
, "REGION")) {
1116 purple_notify_user_info_add_pair_plaintext(user_info
, _("Region"), text2
);
1117 } else if(purple_strequal(child2
->name
, "PCODE")) {
1118 purple_notify_user_info_add_pair_plaintext(user_info
, _("Postal Code"), text2
);
1119 } else if(purple_strequal(child2
->name
, "CTRY")
1120 || purple_strequal(child2
->name
, "COUNTRY")) {
1121 purple_notify_user_info_add_pair_plaintext(user_info
, _("Country"), text2
);
1126 if (address_line_added
)
1127 purple_notify_user_info_add_section_break(user_info
);
1129 } else if(purple_strequal(child
->name
, "TEL")) {
1131 if((child2
= purple_xmlnode_get_child(child
, "NUMBER"))) {
1132 /* show what kind of number it is */
1133 number
= purple_xmlnode_get_data(child2
);
1135 purple_notify_user_info_add_pair_plaintext(user_info
, _("Telephone"), number
);
1138 } else if((number
= purple_xmlnode_get_data(child
))) {
1139 /* lots of clients (including purple) do this, but it's
1141 purple_notify_user_info_add_pair_plaintext(user_info
, _("Telephone"), number
);
1144 } else if(purple_strequal(child
->name
, "EMAIL")) {
1145 char *userid
, *escaped
;
1146 if((child2
= purple_xmlnode_get_child(child
, "USERID"))) {
1147 /* show what kind of email it is */
1148 userid
= purple_xmlnode_get_data(child2
);
1151 escaped
= g_markup_escape_text(userid
, -1);
1152 mailto
= g_strdup_printf("<a href=\"mailto:%s\">%s</a>", escaped
, escaped
);
1153 purple_notify_user_info_add_pair_html(user_info
, _("Email"), mailto
);
1159 } else if((userid
= purple_xmlnode_get_data(child
))) {
1160 /* lots of clients (including purple) do this, but it's
1164 escaped
= g_markup_escape_text(userid
, -1);
1165 mailto
= g_strdup_printf("<a href=\"mailto:%s\">%s</a>", escaped
, escaped
);
1166 purple_notify_user_info_add_pair_html(user_info
, _("Email"), mailto
);
1172 } else if(purple_strequal(child
->name
, "ORG")) {
1173 for(child2
= child
->child
; child2
; child2
= child2
->next
)
1177 if(child2
->type
!= PURPLE_XMLNODE_TYPE_TAG
)
1180 text2
= purple_xmlnode_get_data(child2
);
1181 if(text2
&& purple_strequal(child2
->name
, "ORGNAME")) {
1182 purple_notify_user_info_add_pair_plaintext(user_info
, _("Organization Name"), text2
);
1183 } else if(text2
&& purple_strequal(child2
->name
, "ORGUNIT")) {
1184 purple_notify_user_info_add_pair_plaintext(user_info
, _("Organization Unit"), text2
);
1188 } else if(text
&& purple_strequal(child
->name
, "TITLE")) {
1189 purple_notify_user_info_add_pair_plaintext(user_info
, _("Job Title"), text
);
1190 } else if(text
&& purple_strequal(child
->name
, "ROLE")) {
1191 purple_notify_user_info_add_pair_plaintext(user_info
, _("Role"), text
);
1192 } else if(text
&& purple_strequal(child
->name
, "DESC")) {
1193 purple_notify_user_info_add_pair_plaintext(user_info
, _("Description"), text
);
1194 } else if(purple_strequal(child
->name
, "PHOTO") ||
1195 purple_strequal(child
->name
, "LOGO")) {
1196 char *bintext
= NULL
;
1197 PurpleXmlNode
*binval
;
1199 if ((binval
= purple_xmlnode_get_child(child
, "BINVAL")) &&
1200 (bintext
= purple_xmlnode_get_data(binval
))) {
1203 gboolean photo
= purple_strequal(child
->name
, "PHOTO");
1205 data
= g_base64_decode(bintext
, &size
);
1212 img
= purple_image_new_from_data(data
, size
);
1213 img_id
= purple_image_store_add(img
);
1215 jbi
->vcard_images
= g_slist_prepend(jbi
->vcard_images
, img
);
1216 img_text
= g_strdup_printf("<img src='"
1217 PURPLE_IMAGE_STORE_PROTOCOL
"%u'>", img_id
);
1219 purple_notify_user_info_add_pair_html(user_info
, (photo
? _("Photo") : _("Logo")), img_text
);
1221 hash
= g_compute_checksum_for_data(G_CHECKSUM_SHA1
, data
, size
);
1222 purple_buddy_icons_set_for_user(account
, bare_jid
, data
, size
, hash
);
1233 if (serverside_alias
) {
1235 /* If we found a serverside alias, set it and tell the core */
1236 purple_serv_got_alias(js
->gc
, bare_jid
, serverside_alias
);
1237 b
= purple_blist_find_buddy(account
, bare_jid
);
1239 purple_blist_node_set_string((PurpleBlistNode
*)b
, "servernick", serverside_alias
);
1242 g_free(serverside_alias
);
1247 jabber_buddy_info_show_if_ready(jbi
);
1250 static void jabber_buddy_info_resource_free(gpointer data
)
1252 JabberBuddyInfoResource
*jbri
= data
;
1256 static guint
jbir_hash(gconstpointer v
)
1259 return g_str_hash(v
);
1264 static gboolean
jbir_equal(gconstpointer v1
, gconstpointer v2
)
1266 const gchar
*resource_1
= v1
;
1267 const gchar
*resource_2
= v2
;
1269 return purple_strequal(resource_1
, resource_2
);
1272 static void jabber_version_parse(JabberStream
*js
, const char *from
,
1273 JabberIqType type
, const char *id
,
1274 PurpleXmlNode
*packet
, gpointer data
)
1276 JabberBuddyInfo
*jbi
= data
;
1277 PurpleXmlNode
*query
;
1278 char *resource_name
;
1280 g_return_if_fail(jbi
!= NULL
);
1282 jabber_buddy_info_remove_id(jbi
, id
);
1287 resource_name
= jabber_get_resource(from
);
1290 if (type
== JABBER_IQ_RESULT
) {
1291 if((query
= purple_xmlnode_get_child(packet
, "query"))) {
1292 JabberBuddyResource
*jbr
= jabber_buddy_find_resource(jbi
->jb
, resource_name
);
1294 PurpleXmlNode
*node
;
1295 if((node
= purple_xmlnode_get_child(query
, "name"))) {
1296 jbr
->client
.name
= purple_xmlnode_get_data(node
);
1298 if((node
= purple_xmlnode_get_child(query
, "version"))) {
1299 jbr
->client
.version
= purple_xmlnode_get_data(node
);
1301 if((node
= purple_xmlnode_get_child(query
, "os"))) {
1302 jbr
->client
.os
= purple_xmlnode_get_data(node
);
1307 g_free(resource_name
);
1310 jabber_buddy_info_show_if_ready(jbi
);
1313 static void jabber_last_parse(JabberStream
*js
, const char *from
,
1314 JabberIqType type
, const char *id
,
1315 PurpleXmlNode
*packet
, gpointer data
)
1317 JabberBuddyInfo
*jbi
= data
;
1318 PurpleXmlNode
*query
;
1319 char *resource_name
;
1320 const char *seconds
;
1322 g_return_if_fail(jbi
!= NULL
);
1324 jabber_buddy_info_remove_id(jbi
, id
);
1329 resource_name
= jabber_get_resource(from
);
1332 if (type
== JABBER_IQ_RESULT
) {
1333 if((query
= purple_xmlnode_get_child(packet
, "query"))) {
1334 seconds
= purple_xmlnode_get_attrib(query
, "seconds");
1337 long sec
= strtol(seconds
, &end
, 10);
1338 JabberBuddy
*jb
= NULL
;
1339 char *resource
= NULL
;
1340 char *buddy_name
= NULL
;
1341 JabberBuddyResource
*jbr
= NULL
;
1343 if(end
!= seconds
) {
1344 JabberBuddyInfoResource
*jbir
= g_hash_table_lookup(jbi
->resources
, resource_name
);
1346 jbir
->idle_seconds
= sec
;
1349 /* Update the idle time of the buddy resource, if we got it.
1350 This will correct the value when a server doesn't mark
1351 delayed presence and we got the presence when signing on */
1352 jb
= jabber_buddy_find(js
, from
, FALSE
);
1354 resource
= jabber_get_resource(from
);
1355 buddy_name
= jabber_get_bare_jid(from
);
1356 /* if the resource already has an idle time set, we
1357 must have gotten it originally from a presence. In
1358 this case we update it. Otherwise don't update it, to
1359 avoid setting an idle and not getting informed about
1360 the resource getting unidle */
1361 if (resource
&& buddy_name
) {
1362 jbr
= jabber_buddy_find_resource(jb
, resource
);
1366 jbr
->idle
= time(NULL
) - sec
;
1372 jabber_buddy_find_resource(jb
, NULL
)) {
1373 purple_protocol_got_user_idle(purple_connection_get_account(js
->gc
),
1374 buddy_name
, jbr
->idle
, jbr
->idle
);
1385 g_free(resource_name
);
1388 jabber_buddy_info_show_if_ready(jbi
);
1391 static void jabber_last_offline_parse(JabberStream
*js
, const char *from
,
1392 JabberIqType type
, const char *id
,
1393 PurpleXmlNode
*packet
, gpointer data
)
1395 JabberBuddyInfo
*jbi
= data
;
1396 PurpleXmlNode
*query
;
1397 const char *seconds
;
1399 g_return_if_fail(jbi
!= NULL
);
1401 jabber_buddy_info_remove_id(jbi
, id
);
1403 if (type
== JABBER_IQ_RESULT
) {
1404 if((query
= purple_xmlnode_get_child(packet
, "query"))) {
1405 seconds
= purple_xmlnode_get_attrib(query
, "seconds");
1408 long sec
= strtol(seconds
, &end
, 10);
1409 if(end
!= seconds
) {
1410 jbi
->last_seconds
= sec
;
1413 jbi
->last_message
= purple_xmlnode_get_data(query
);
1417 jabber_buddy_info_show_if_ready(jbi
);
1420 static void jabber_time_parse(JabberStream
*js
, const char *from
,
1421 JabberIqType type
, const char *id
,
1422 PurpleXmlNode
*packet
, gpointer data
)
1424 JabberBuddyInfo
*jbi
= data
;
1425 JabberBuddyResource
*jbr
;
1426 char *resource_name
;
1428 g_return_if_fail(jbi
!= NULL
);
1430 jabber_buddy_info_remove_id(jbi
, id
);
1435 resource_name
= jabber_get_resource(from
);
1436 jbr
= resource_name
? jabber_buddy_find_resource(jbi
->jb
, resource_name
) : NULL
;
1437 g_free(resource_name
);
1439 if (type
== JABBER_IQ_RESULT
) {
1440 PurpleXmlNode
*time
= purple_xmlnode_get_child(packet
, "time");
1441 PurpleXmlNode
*tzo
= time
? purple_xmlnode_get_child(time
, "tzo") : NULL
;
1442 char *tzo_data
= tzo
? purple_xmlnode_get_data(tzo
) : NULL
;
1446 if (tzo_data
[0] == 'Z' && tzo_data
[1] == '\0') {
1449 gboolean offset_positive
= (tzo_data
[0] == '+');
1451 if (((*c
== '+' || *c
== '-') && (c
= c
+ 1)) &&
1452 sscanf(c
, "%02d:%02d", &hours
, &minutes
) == 2) {
1453 jbr
->tz_off
= 60*60*hours
+ 60*minutes
;
1454 if (!offset_positive
)
1457 purple_debug_info("jabber", "Ignoring malformed timezone %s",
1467 jabber_buddy_info_show_if_ready(jbi
);
1470 void jabber_buddy_remove_all_pending_buddy_info_requests(JabberStream
*js
)
1472 if (js
->pending_buddy_info_requests
)
1474 JabberBuddyInfo
*jbi
;
1475 GSList
*l
= js
->pending_buddy_info_requests
;
1479 g_slist_free(jbi
->ids
);
1480 jabber_buddy_info_destroy(jbi
);
1485 g_slist_free(js
->pending_buddy_info_requests
);
1486 js
->pending_buddy_info_requests
= NULL
;
1490 static gboolean
jabber_buddy_get_info_timeout(gpointer data
)
1492 JabberBuddyInfo
*jbi
= data
;
1494 /* remove the pending callbacks */
1496 char *id
= jbi
->ids
->data
;
1497 jabber_iq_remove_callback_by_id(jbi
->js
, id
);
1498 jbi
->ids
= g_slist_remove(jbi
->ids
, id
);
1502 jbi
->js
->pending_buddy_info_requests
= g_slist_remove(jbi
->js
->pending_buddy_info_requests
, jbi
);
1503 jbi
->timeout_handle
= 0;
1505 jabber_buddy_info_show_if_ready(jbi
);
1510 static gboolean
_client_is_blacklisted(JabberBuddyResource
*jbr
, const char *ns
)
1512 /* can't be blacklisted if we don't know what you're running yet */
1513 if(!jbr
->client
.name
)
1516 if(purple_strequal(ns
, NS_LAST_ACTIVITY
)) {
1517 if(purple_strequal(jbr
->client
.name
, "Trillian")) {
1518 /* verified by nwalp 2007/05/09 */
1519 if(purple_strequal(jbr
->client
.version
, "3.1.0.121") ||
1520 /* verified by nwalp 2007/09/19 */
1521 purple_strequal(jbr
->client
.version
, "3.1.7.0")) {
1531 dispatch_queries_for_resource(JabberStream
*js
, JabberBuddyInfo
*jbi
,
1532 gboolean is_bare_jid
, const char *jid
,
1533 JabberBuddyResource
*jbr
)
1536 JabberBuddyInfoResource
*jbir
;
1537 char *full_jid
= NULL
;
1540 if (is_bare_jid
&& jbr
->name
) {
1541 full_jid
= g_strdup_printf("%s/%s", jid
, jbr
->name
);
1546 jbir
= g_new0(JabberBuddyInfoResource
, 1);
1547 g_hash_table_insert(jbi
->resources
, g_strdup(jbr
->name
), jbir
);
1549 if(!jbr
->client
.name
) {
1550 iq
= jabber_iq_new_query(js
, JABBER_IQ_GET
, "jabber:iq:version");
1551 purple_xmlnode_set_attrib(iq
->node
, "to", to
);
1552 jabber_iq_set_callback(iq
, jabber_version_parse
, jbi
);
1553 jbi
->ids
= g_slist_prepend(jbi
->ids
, g_strdup(iq
->id
));
1557 /* this is to fix the feeling of irritation I get when trying
1558 * to get info on a friend running Trillian, which doesn't
1559 * respond (with an error or otherwise) to jabber:iq:last
1560 * requests. There are a number of Trillian users in my
1562 if(!_client_is_blacklisted(jbr
, NS_LAST_ACTIVITY
)) {
1563 iq
= jabber_iq_new_query(js
, JABBER_IQ_GET
, NS_LAST_ACTIVITY
);
1564 purple_xmlnode_set_attrib(iq
->node
, "to", to
);
1565 jabber_iq_set_callback(iq
, jabber_last_parse
, jbi
);
1566 jbi
->ids
= g_slist_prepend(jbi
->ids
, g_strdup(iq
->id
));
1570 if (jbr
->tz_off
== PURPLE_NO_TZ_OFF
&&
1572 jabber_resource_has_capability(jbr
, NS_ENTITY_TIME
))) {
1573 PurpleXmlNode
*child
;
1574 iq
= jabber_iq_new(js
, JABBER_IQ_GET
);
1575 purple_xmlnode_set_attrib(iq
->node
, "to", to
);
1576 child
= purple_xmlnode_new_child(iq
->node
, "time");
1577 purple_xmlnode_set_namespace(child
, NS_ENTITY_TIME
);
1578 jabber_iq_set_callback(iq
, jabber_time_parse
, jbi
);
1579 jbi
->ids
= g_slist_prepend(jbi
->ids
, g_strdup(iq
->id
));
1586 static void jabber_buddy_get_info_for_jid(JabberStream
*js
, const char *jid
)
1589 PurpleXmlNode
*vcard
;
1592 JabberBuddyInfo
*jbi
;
1594 gboolean is_bare_jid
;
1596 jb
= jabber_buddy_find(js
, jid
, TRUE
);
1602 slash
= strchr(jid
, '/');
1603 is_bare_jid
= (slash
== NULL
);
1605 jbi
= g_new0(JabberBuddyInfo
, 1);
1606 jbi
->jid
= g_strdup(jid
);
1609 jbi
->resources
= g_hash_table_new_full(jbir_hash
, jbir_equal
, g_free
, jabber_buddy_info_resource_free
);
1610 jbi
->user_info
= purple_notify_user_info_new();
1612 iq
= jabber_iq_new(js
, JABBER_IQ_GET
);
1614 purple_xmlnode_set_attrib(iq
->node
, "to", jid
);
1615 vcard
= purple_xmlnode_new_child(iq
->node
, "vCard");
1616 purple_xmlnode_set_namespace(vcard
, "vcard-temp");
1618 jabber_iq_set_callback(iq
, jabber_vcard_parse
, jbi
);
1619 jbi
->ids
= g_slist_prepend(jbi
->ids
, g_strdup(iq
->id
));
1624 if (jb
->resources
) {
1625 for(resources
= jb
->resources
; resources
; resources
= resources
->next
) {
1626 JabberBuddyResource
*jbr
= resources
->data
;
1627 dispatch_queries_for_resource(js
, jbi
, is_bare_jid
, jid
, jbr
);
1630 /* user is offline, send a jabber:iq:last to find out last time online */
1631 iq
= jabber_iq_new_query(js
, JABBER_IQ_GET
, NS_LAST_ACTIVITY
);
1632 purple_xmlnode_set_attrib(iq
->node
, "to", jid
);
1633 jabber_iq_set_callback(iq
, jabber_last_offline_parse
, jbi
);
1634 jbi
->ids
= g_slist_prepend(jbi
->ids
, g_strdup(iq
->id
));
1638 JabberBuddyResource
*jbr
= jabber_buddy_find_resource(jb
, slash
+ 1);
1640 dispatch_queries_for_resource(js
, jbi
, is_bare_jid
, jid
, jbr
);
1642 purple_debug_warning("jabber", "jabber_buddy_get_info_for_jid() "
1643 "was passed JID %s, but there is no corresponding "
1644 "JabberBuddyResource!\n", jid
);
1647 js
->pending_buddy_info_requests
= g_slist_prepend(js
->pending_buddy_info_requests
, jbi
);
1648 jbi
->timeout_handle
= g_timeout_add_seconds(30, jabber_buddy_get_info_timeout
, jbi
);
1651 void jabber_buddy_get_info(PurpleConnection
*gc
, const char *who
)
1653 JabberStream
*js
= purple_connection_get_protocol_data(gc
);
1654 JabberID
*jid
= jabber_id_new(who
);
1659 if (jid
->node
&& jabber_chat_find(js
, jid
->node
, jid
->domain
)) {
1660 /* For a conversation, include the resource (indicates the user). */
1661 jabber_buddy_get_info_for_jid(js
, who
);
1663 char *bare_jid
= jabber_get_bare_jid(who
);
1664 jabber_buddy_get_info_for_jid(js
, bare_jid
);
1668 jabber_id_free(jid
);
1671 static void jabber_buddy_set_invisibility(JabberStream
*js
, const char *who
,
1674 PurplePresence
*gpresence
;
1675 PurpleAccount
*account
;
1676 PurpleStatus
*status
;
1677 JabberBuddy
*jb
= jabber_buddy_find(js
, who
, TRUE
);
1678 PurpleXmlNode
*presence
;
1679 JabberBuddyState state
;
1683 account
= purple_connection_get_account(js
->gc
);
1684 gpresence
= purple_account_get_presence(account
);
1685 status
= purple_presence_get_active_status(gpresence
);
1687 purple_status_to_jabber(status
, &state
, &msg
, &priority
);
1688 presence
= jabber_presence_create_js(js
, state
, msg
, priority
);
1692 purple_xmlnode_set_attrib(presence
, "to", who
);
1694 purple_xmlnode_set_attrib(presence
, "type", "invisible");
1695 jb
->invisible
|= JABBER_INVIS_BUDDY
;
1697 jb
->invisible
&= ~JABBER_INVIS_BUDDY
;
1700 jabber_send(js
, presence
);
1701 purple_xmlnode_free(presence
);
1704 static void jabber_buddy_make_invisible(PurpleBlistNode
*node
, gpointer data
)
1707 PurpleConnection
*gc
;
1710 g_return_if_fail(PURPLE_IS_BUDDY(node
));
1712 buddy
= (PurpleBuddy
*) node
;
1713 gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1714 js
= purple_connection_get_protocol_data(gc
);
1716 jabber_buddy_set_invisibility(js
, purple_buddy_get_name(buddy
), TRUE
);
1719 static void jabber_buddy_make_visible(PurpleBlistNode
*node
, gpointer data
)
1722 PurpleConnection
*gc
;
1725 g_return_if_fail(PURPLE_IS_BUDDY(node
));
1727 buddy
= (PurpleBuddy
*) node
;
1728 gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1729 js
= purple_connection_get_protocol_data(gc
);
1731 jabber_buddy_set_invisibility(js
, purple_buddy_get_name(buddy
), FALSE
);
1734 static void cancel_presence_notification(gpointer data
)
1737 PurpleConnection
*gc
;
1741 gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1742 js
= purple_connection_get_protocol_data(gc
);
1744 jabber_presence_subscription_set(js
, purple_buddy_get_name(buddy
), "unsubscribed");
1748 jabber_buddy_cancel_presence_notification(PurpleBlistNode
*node
,
1752 PurpleAccount
*account
;
1753 PurpleConnection
*gc
;
1757 g_return_if_fail(PURPLE_IS_BUDDY(node
));
1759 buddy
= (PurpleBuddy
*) node
;
1760 name
= purple_buddy_get_name(buddy
);
1761 account
= purple_buddy_get_account(buddy
);
1762 gc
= purple_account_get_connection(account
);
1764 msg
= g_strdup_printf(_("%s will no longer be able to see your status "
1765 "updates. Do you want to continue?"), name
);
1766 purple_request_yes_no(gc
, NULL
, _("Cancel Presence Notification"),
1767 msg
, 0 /* Yes */, purple_request_cpar_from_account(account
), buddy
,
1768 cancel_presence_notification
, NULL
/* Do nothing */);
1772 static void jabber_buddy_rerequest_auth(PurpleBlistNode
*node
, gpointer data
)
1775 PurpleConnection
*gc
;
1778 g_return_if_fail(PURPLE_IS_BUDDY(node
));
1780 buddy
= (PurpleBuddy
*) node
;
1781 gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1782 js
= purple_connection_get_protocol_data(gc
);
1784 jabber_presence_subscription_set(js
, purple_buddy_get_name(buddy
), "subscribe");
1788 static void jabber_buddy_unsubscribe(PurpleBlistNode
*node
, gpointer data
)
1791 PurpleConnection
*gc
;
1794 g_return_if_fail(PURPLE_IS_BUDDY(node
));
1796 buddy
= (PurpleBuddy
*) node
;
1797 gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1798 js
= purple_connection_get_protocol_data(gc
);
1800 jabber_presence_subscription_set(js
, purple_buddy_get_name(buddy
), "unsubscribe");
1803 static void jabber_buddy_login(PurpleBlistNode
*node
, gpointer data
) {
1804 if(PURPLE_IS_BUDDY(node
)) {
1805 /* simply create a directed presence of the current status */
1806 PurpleBuddy
*buddy
= (PurpleBuddy
*) node
;
1807 PurpleConnection
*gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1808 JabberStream
*js
= purple_connection_get_protocol_data(gc
);
1809 PurpleAccount
*account
= purple_connection_get_account(gc
);
1810 PurplePresence
*gpresence
= purple_account_get_presence(account
);
1811 PurpleStatus
*status
= purple_presence_get_active_status(gpresence
);
1812 PurpleXmlNode
*presence
;
1813 JabberBuddyState state
;
1817 purple_status_to_jabber(status
, &state
, &msg
, &priority
);
1818 presence
= jabber_presence_create_js(js
, state
, msg
, priority
);
1822 purple_xmlnode_set_attrib(presence
, "to", purple_buddy_get_name(buddy
));
1824 jabber_send(js
, presence
);
1825 purple_xmlnode_free(presence
);
1829 static void jabber_buddy_logout(PurpleBlistNode
*node
, gpointer data
) {
1830 if(PURPLE_IS_BUDDY(node
)) {
1831 /* simply create a directed unavailable presence */
1832 PurpleBuddy
*buddy
= (PurpleBuddy
*) node
;
1833 PurpleConnection
*gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1834 JabberStream
*js
= purple_connection_get_protocol_data(gc
);
1835 PurpleXmlNode
*presence
;
1837 presence
= jabber_presence_create_js(js
, JABBER_BUDDY_STATE_UNAVAILABLE
, NULL
, 0);
1839 purple_xmlnode_set_attrib(presence
, "to", purple_buddy_get_name(buddy
));
1841 jabber_send(js
, presence
);
1842 purple_xmlnode_free(presence
);
1846 static GList
*jabber_buddy_menu(PurpleBuddy
*buddy
)
1848 PurpleConnection
*gc
= purple_account_get_connection(purple_buddy_get_account(buddy
));
1849 JabberStream
*js
= purple_connection_get_protocol_data(gc
);
1850 const char *name
= purple_buddy_get_name(buddy
);
1851 JabberBuddy
*jb
= jabber_buddy_find(js
, name
, TRUE
);
1855 PurpleActionMenu
*act
;
1860 if (js
->protocol_version
.major
== 0 && js
->protocol_version
.minor
== 9 &&
1861 jb
!= js
->user_jb
) {
1862 if(jb
->invisible
& JABBER_INVIS_BUDDY
) {
1863 act
= purple_action_menu_new(_("Un-hide From"),
1864 PURPLE_CALLBACK(jabber_buddy_make_visible
),
1867 act
= purple_action_menu_new(_("Temporarily Hide From"),
1868 PURPLE_CALLBACK(jabber_buddy_make_invisible
),
1871 m
= g_list_append(m
, act
);
1874 if(jb
->subscription
& JABBER_SUB_FROM
&& jb
!= js
->user_jb
) {
1875 act
= purple_action_menu_new(_("Cancel Presence Notification"),
1876 PURPLE_CALLBACK(jabber_buddy_cancel_presence_notification
),
1878 m
= g_list_append(m
, act
);
1881 if(!(jb
->subscription
& JABBER_SUB_TO
)) {
1882 act
= purple_action_menu_new(_("(Re-)Request authorization"),
1883 PURPLE_CALLBACK(jabber_buddy_rerequest_auth
),
1885 m
= g_list_append(m
, act
);
1887 } else if (jb
!= js
->user_jb
) {
1889 /* shouldn't this just happen automatically when the buddy is
1891 act
= purple_action_menu_new(_("Unsubscribe"),
1892 PURPLE_CALLBACK(jabber_buddy_unsubscribe
),
1894 m
= g_list_append(m
, act
);
1897 if (js
->googletalk
) {
1898 act
= purple_action_menu_new(_("Initiate _Chat"),
1899 PURPLE_CALLBACK(google_buddy_node_chat
),
1901 m
= g_list_append(m
, act
);
1905 * This if-condition implements parts of XEP-0100: Gateway Interaction
1907 * According to stpeter, there is no way to know if a jid on the roster is a gateway without sending a disco#info.
1908 * However, since the gateway might appear offline to us, we cannot get that information. Therefore, I just assume
1909 * that gateways on the roster can be identified by having no '@' in their jid. This is a faily safe assumption, since
1910 * people don't tend to have a server or other service there.
1912 * TODO: Use disco#info...
1914 if (strchr(name
, '@') == NULL
) {
1915 act
= purple_action_menu_new(_("Log In"),
1916 PURPLE_CALLBACK(jabber_buddy_login
),
1918 m
= g_list_append(m
, act
);
1919 act
= purple_action_menu_new(_("Log Out"),
1920 PURPLE_CALLBACK(jabber_buddy_logout
),
1922 m
= g_list_append(m
, act
);
1925 /* add all ad hoc commands to the action menu */
1926 for(jbrs
= jb
->resources
; jbrs
; jbrs
= g_list_next(jbrs
)) {
1927 JabberBuddyResource
*jbr
= jbrs
->data
;
1931 for(commands
= jbr
->commands
; commands
; commands
= g_list_next(commands
)) {
1932 JabberAdHocCommands
*cmd
= commands
->data
;
1933 act
= purple_action_menu_new(cmd
->name
, PURPLE_CALLBACK(jabber_adhoc_execute_action
), cmd
, NULL
);
1934 m
= g_list_append(m
, act
);
1942 jabber_blist_node_menu(PurpleBlistNode
*node
)
1944 if(PURPLE_IS_BUDDY(node
)) {
1945 return jabber_buddy_menu((PurpleBuddy
*) node
);
1952 static void user_search_result_add_buddy_cb(PurpleConnection
*gc
, GList
*row
, void *user_data
)
1954 /* XXX find out the jid */
1955 purple_blist_request_add_buddy(purple_connection_get_account(gc
),
1956 g_list_nth_data(row
, 0), NULL
, NULL
);
1959 static void user_search_result_cb(JabberStream
*js
, const char *from
,
1960 JabberIqType type
, const char *id
,
1961 PurpleXmlNode
*packet
, gpointer data
)
1963 PurpleNotifySearchResults
*results
;
1964 PurpleNotifySearchColumn
*column
;
1965 PurpleXmlNode
*x
, *query
, *item
, *field
;
1967 /* XXX error checking? */
1968 if(!(query
= purple_xmlnode_get_child(packet
, "query")))
1971 results
= purple_notify_searchresults_new();
1972 if((x
= purple_xmlnode_get_child_with_namespace(query
, "x", "jabber:x:data"))) {
1973 PurpleXmlNode
*reported
;
1974 GSList
*column_vars
= NULL
;
1976 purple_debug_info("jabber", "new-skool\n");
1978 if((reported
= purple_xmlnode_get_child(x
, "reported"))) {
1979 PurpleXmlNode
*field
= purple_xmlnode_get_child(reported
, "field");
1981 const char *var
= purple_xmlnode_get_attrib(field
, "var");
1982 const char *label
= purple_xmlnode_get_attrib(field
, "label");
1984 column
= purple_notify_searchresults_column_new(label
? label
: var
);
1985 purple_notify_searchresults_column_add(results
, column
);
1986 column_vars
= g_slist_append(column_vars
, (char *)var
);
1988 field
= purple_xmlnode_get_next_twin(field
);
1992 item
= purple_xmlnode_get_child(x
, "item");
1996 PurpleXmlNode
*valuenode
;
1999 for (l
= column_vars
; l
!= NULL
; l
= l
->next
) {
2001 * Build a row containing the strings that correspond
2002 * to each column of the search results.
2004 for (field
= purple_xmlnode_get_child(item
, "field");
2006 field
= purple_xmlnode_get_next_twin(field
))
2008 if ((var
= purple_xmlnode_get_attrib(field
, "var")) &&
2009 purple_strequal(var
, l
->data
) &&
2010 (valuenode
= purple_xmlnode_get_child(field
, "value")))
2012 char *value
= purple_xmlnode_get_data(valuenode
);
2013 row
= g_list_append(row
, value
);
2018 /* No data for this column */
2019 row
= g_list_append(row
, NULL
);
2021 purple_notify_searchresults_row_add(results
, row
);
2022 item
= purple_xmlnode_get_next_twin(item
);
2025 g_slist_free(column_vars
);
2028 purple_debug_info("jabber", "old-skool\n");
2030 column
= purple_notify_searchresults_column_new(_("JID"));
2031 purple_notify_searchresults_column_add(results
, column
);
2032 column
= purple_notify_searchresults_column_new(_("First Name"));
2033 purple_notify_searchresults_column_add(results
, column
);
2034 column
= purple_notify_searchresults_column_new(_("Last Name"));
2035 purple_notify_searchresults_column_add(results
, column
);
2036 column
= purple_notify_searchresults_column_new(_("Nickname"));
2037 purple_notify_searchresults_column_add(results
, column
);
2038 column
= purple_notify_searchresults_column_new(_("Email"));
2039 purple_notify_searchresults_column_add(results
, column
);
2041 for(item
= purple_xmlnode_get_child(query
, "item"); item
; item
= purple_xmlnode_get_next_twin(item
)) {
2043 PurpleXmlNode
*node
;
2046 if(!(jid
= purple_xmlnode_get_attrib(item
, "jid")))
2049 row
= g_list_append(row
, g_strdup(jid
));
2050 node
= purple_xmlnode_get_child(item
, "first");
2051 row
= g_list_append(row
, node
? purple_xmlnode_get_data(node
) : NULL
);
2052 node
= purple_xmlnode_get_child(item
, "last");
2053 row
= g_list_append(row
, node
? purple_xmlnode_get_data(node
) : NULL
);
2054 node
= purple_xmlnode_get_child(item
, "nick");
2055 row
= g_list_append(row
, node
? purple_xmlnode_get_data(node
) : NULL
);
2056 node
= purple_xmlnode_get_child(item
, "email");
2057 row
= g_list_append(row
, node
? purple_xmlnode_get_data(node
) : NULL
);
2058 purple_debug_info("jabber", "row=%p\n", row
);
2059 purple_notify_searchresults_row_add(results
, row
);
2063 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_ADD
,
2064 user_search_result_add_buddy_cb
);
2066 purple_notify_searchresults(js
->gc
, NULL
, NULL
, _("The following are the results of your search"), results
, NULL
, NULL
);
2069 static void user_search_x_data_cb(JabberStream
*js
, PurpleXmlNode
*result
, gpointer data
)
2071 PurpleXmlNode
*query
;
2073 char *dir_server
= data
;
2076 /* if they've cancelled the search, we're
2077 * just going to get an error if we send
2078 * a cancel, so skip it */
2079 type
= purple_xmlnode_get_attrib(result
, "type");
2080 if(purple_strequal(type
, "cancel")) {
2085 iq
= jabber_iq_new_query(js
, JABBER_IQ_SET
, "jabber:iq:search");
2086 query
= purple_xmlnode_get_child(iq
->node
, "query");
2088 purple_xmlnode_insert_child(query
, result
);
2090 jabber_iq_set_callback(iq
, user_search_result_cb
, NULL
);
2091 purple_xmlnode_set_attrib(iq
->node
, "to", dir_server
);
2096 struct user_search_info
{
2098 char *directory_server
;
2101 static void user_search_cancel_cb(struct user_search_info
*usi
, PurpleRequestFields
*fields
)
2103 g_free(usi
->directory_server
);
2107 static void user_search_cb(struct user_search_info
*usi
, PurpleRequestFields
*fields
)
2109 JabberStream
*js
= usi
->js
;
2111 PurpleXmlNode
*query
;
2112 GList
*groups
, *flds
;
2114 iq
= jabber_iq_new_query(js
, JABBER_IQ_SET
, "jabber:iq:search");
2115 query
= purple_xmlnode_get_child(iq
->node
, "query");
2117 for(groups
= purple_request_fields_get_groups(fields
); groups
; groups
= groups
->next
) {
2118 for(flds
= purple_request_field_group_get_fields(groups
->data
);
2119 flds
; flds
= flds
->next
) {
2120 PurpleRequestField
*field
= flds
->data
;
2121 const char *id
= purple_request_field_get_id(field
);
2122 const char *value
= purple_request_field_string_get_value(field
);
2124 if(value
&& (purple_strequal(id
, "first") || purple_strequal(id
, "last") || purple_strequal(id
, "nick") || purple_strequal(id
, "email"))) {
2125 PurpleXmlNode
*y
= purple_xmlnode_new_child(query
, id
);
2126 purple_xmlnode_insert_data(y
, value
, -1);
2131 jabber_iq_set_callback(iq
, user_search_result_cb
, NULL
);
2132 purple_xmlnode_set_attrib(iq
->node
, "to", usi
->directory_server
);
2135 g_free(usi
->directory_server
);
2139 static void user_search_fields_result_cb(JabberStream
*js
, const char *from
,
2140 JabberIqType type
, const char *id
,
2141 PurpleXmlNode
*packet
, gpointer data
)
2143 PurpleXmlNode
*query
, *x
;
2148 if (type
== JABBER_IQ_ERROR
) {
2149 char *msg
= jabber_parse_error(js
, packet
, NULL
);
2152 msg
= g_strdup(_("Unknown error"));
2154 purple_notify_error(js
->gc
, _("Directory Query Failed"),
2155 _("Could not query the directory server."), msg
,
2156 purple_request_cpar_from_connection(js
->gc
));
2163 if(!(query
= purple_xmlnode_get_child(packet
, "query")))
2166 if((x
= purple_xmlnode_get_child_with_namespace(query
, "x", "jabber:x:data"))) {
2167 jabber_x_data_request(js
, x
, user_search_x_data_cb
, g_strdup(from
));
2170 struct user_search_info
*usi
;
2171 PurpleXmlNode
*instnode
;
2172 char *instructions
= NULL
;
2173 PurpleRequestFields
*fields
;
2174 PurpleRequestFieldGroup
*group
;
2175 PurpleRequestField
*field
;
2178 fields
= purple_request_fields_new();
2179 group
= purple_request_field_group_new(NULL
);
2180 purple_request_fields_add_group(fields
, group
);
2182 if((instnode
= purple_xmlnode_get_child(query
, "instructions")))
2184 char *tmp
= purple_xmlnode_get_data(instnode
);
2188 /* Try to translate the message (see static message
2189 list in jabber_user_dir_comments[]) */
2190 instructions
= g_strdup_printf(_("Server Instructions: %s"), _(tmp
));
2197 instructions
= g_strdup(_("Fill in one or more fields to search "
2198 "for any matching XMPP users."));
2201 if(purple_xmlnode_get_child(query
, "first")) {
2202 field
= purple_request_field_string_new("first", _("First Name"),
2204 purple_request_field_group_add_field(group
, field
);
2206 if(purple_xmlnode_get_child(query
, "last")) {
2207 field
= purple_request_field_string_new("last", _("Last Name"),
2209 purple_request_field_group_add_field(group
, field
);
2211 if(purple_xmlnode_get_child(query
, "nick")) {
2212 field
= purple_request_field_string_new("nick", _("Nickname"),
2214 purple_request_field_group_add_field(group
, field
);
2216 if(purple_xmlnode_get_child(query
, "email")) {
2217 field
= purple_request_field_string_new("email", _("Email Address"),
2219 purple_request_field_group_add_field(group
, field
);
2222 usi
= g_new0(struct user_search_info
, 1);
2224 usi
->directory_server
= g_strdup(from
);
2226 purple_request_fields(js
->gc
, _("Search for XMPP users"),
2227 _("Search for XMPP users"), instructions
, fields
,
2228 _("Search"), G_CALLBACK(user_search_cb
),
2229 _("Cancel"), G_CALLBACK(user_search_cancel_cb
),
2230 purple_request_cpar_from_connection(js
->gc
),
2233 g_free(instructions
);
2237 void jabber_user_search(JabberStream
*js
, const char *directory
)
2241 /* XXX: should probably better validate the directory we're given */
2242 if(!directory
|| !*directory
) {
2243 purple_notify_error(js
->gc
, _("Invalid Directory"),
2244 _("Invalid Directory"), NULL
,
2245 purple_request_cpar_from_connection(js
->gc
));
2249 /* If the value provided isn't the disco#info default, persist it. Otherwise,
2250 make sure we aren't persisting an old value */
2251 if(js
->user_directories
&& js
->user_directories
->data
&&
2252 purple_strequal(directory
, js
->user_directories
->data
)) {
2253 purple_account_set_string(purple_connection_get_account(js
->gc
), "user_directory", "");
2256 purple_account_set_string(purple_connection_get_account(js
->gc
), "user_directory", directory
);
2259 iq
= jabber_iq_new_query(js
, JABBER_IQ_GET
, "jabber:iq:search");
2260 purple_xmlnode_set_attrib(iq
->node
, "to", directory
);
2262 jabber_iq_set_callback(iq
, user_search_fields_result_cb
, NULL
);
2267 void jabber_user_search_begin(PurpleProtocolAction
*action
)
2269 PurpleConnection
*gc
= (PurpleConnection
*) action
->connection
;
2270 JabberStream
*js
= purple_connection_get_protocol_data(gc
);
2271 const char *def_val
= purple_account_get_string(purple_connection_get_account(js
->gc
), "user_directory", "");
2272 if(!*def_val
&& js
->user_directories
)
2273 def_val
= js
->user_directories
->data
;
2275 purple_request_input(gc
, _("Enter a User Directory"), _("Enter a User Directory"),
2276 _("Select a user directory to search"),
2279 _("Search Directory"), PURPLE_CALLBACK(jabber_user_search
),
2285 jabber_resource_know_capabilities(const JabberBuddyResource
*jbr
)
2287 return jbr
->caps
.info
!= NULL
;
2291 jabber_resource_has_capability(const JabberBuddyResource
*jbr
, const gchar
*cap
)
2293 const GList
*node
= NULL
;
2294 const JabberCapsNodeExts
*exts
;
2296 if (!jbr
->caps
.info
) {
2297 purple_debug_info("jabber",
2298 "Unable to find caps: nothing known about buddy\n");
2302 node
= g_list_find_custom(jbr
->caps
.info
->features
, cap
, (GCompareFunc
)strcmp
);
2303 if (!node
&& jbr
->caps
.exts
&& jbr
->caps
.info
->exts
) {
2305 exts
= jbr
->caps
.info
->exts
;
2306 /* Walk through all the enabled caps, checking each list for the cap.
2307 * Don't check it twice, though. */
2308 for (ext
= jbr
->caps
.exts
; ext
&& !node
; ext
= ext
->next
) {
2309 GList
*features
= g_hash_table_lookup(exts
->exts
, ext
->data
);
2311 node
= g_list_find_custom(features
, cap
, (GCompareFunc
)strcmp
);
2315 return (node
!= NULL
);
2319 jabber_buddy_has_capability(const JabberBuddy
*jb
, const gchar
*cap
)
2321 JabberBuddyResource
*jbr
= jabber_buddy_find_resource((JabberBuddy
*)jb
, NULL
);
2324 purple_debug_info("jabber",
2325 "Unable to find caps: buddy might be offline\n");
2329 return jabber_resource_has_capability(jbr
, cap
);
2333 jabber_resource_get_identity_category_type(const JabberBuddyResource
*jbr
,
2334 const gchar
*category
)
2336 const GList
*iter
= NULL
;
2338 if (jbr
->caps
.info
) {
2339 for (iter
= jbr
->caps
.info
->identities
; iter
; iter
= g_list_next(iter
)) {
2340 const JabberIdentity
*identity
=
2341 (JabberIdentity
*) iter
->data
;
2343 if (purple_strequal(identity
->category
, category
)) {
2344 return identity
->type
;