comparison libgaim/protocols/jabber/buddy.c @ 14192:60b1bc8dbf37

[gaim-migrate @ 16863] Renamed 'core' to 'libgaim' committer: Tailor Script <tailor@pidgin.im>
author Evan Schoenberg <evan.s@dreskin.net>
date Sat, 19 Aug 2006 01:50:10 +0000
parents
children 7df4ab213577
comparison
equal deleted inserted replaced
14191:009db0b357b5 14192:60b1bc8dbf37
1 /*
2 * gaim - Jabber Protocol Plugin
3 *
4 * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 *
20 */
21 #include "internal.h"
22 #include "cipher.h"
23 #include "debug.h"
24 #include "imgstore.h"
25 #include "prpl.h"
26 #include "notify.h"
27 #include "request.h"
28 #include "util.h"
29 #include "xmlnode.h"
30
31 #include "buddy.h"
32 #include "chat.h"
33 #include "jabber.h"
34 #include "iq.h"
35 #include "presence.h"
36 #include "xdata.h"
37
38 typedef struct {
39 long idle_seconds;
40 } JabberBuddyInfoResource;
41
42 typedef struct {
43 JabberStream *js;
44 JabberBuddy *jb;
45 char *jid;
46 GSList *ids;
47 GHashTable *resources;
48 int timeout_handle;
49 char *vcard_text;
50 GSList *vcard_imgids;
51 } JabberBuddyInfo;
52
53 void jabber_buddy_free(JabberBuddy *jb)
54 {
55 g_return_if_fail(jb != NULL);
56
57 if(jb->error_msg)
58 g_free(jb->error_msg);
59 while(jb->resources)
60 jabber_buddy_resource_free(jb->resources->data);
61
62 g_free(jb);
63 }
64
65 JabberBuddy *jabber_buddy_find(JabberStream *js, const char *name,
66 gboolean create)
67 {
68 JabberBuddy *jb;
69 const char *realname;
70
71 if(!(realname = jabber_normalize(js->gc->account, name)))
72 return NULL;
73
74 jb = g_hash_table_lookup(js->buddies, realname);
75
76 if(!jb && create) {
77 jb = g_new0(JabberBuddy, 1);
78 g_hash_table_insert(js->buddies, g_strdup(realname), jb);
79 }
80
81 return jb;
82 }
83
84
85 JabberBuddyResource *jabber_buddy_find_resource(JabberBuddy *jb,
86 const char *resource)
87 {
88 JabberBuddyResource *jbr = NULL;
89 GList *l;
90
91 if(!jb)
92 return NULL;
93
94 for(l = jb->resources; l; l = l->next)
95 {
96 if(!jbr && !resource) {
97 jbr = l->data;
98 } else if(!resource) {
99 if(((JabberBuddyResource *)l->data)->priority >= jbr->priority)
100 jbr = l->data;
101 } else if(((JabberBuddyResource *)l->data)->name) {
102 if(!strcmp(((JabberBuddyResource *)l->data)->name, resource)) {
103 jbr = l->data;
104 break;
105 }
106 }
107 }
108
109 return jbr;
110 }
111
112 JabberBuddyResource *jabber_buddy_track_resource(JabberBuddy *jb, const char *resource,
113 int priority, JabberBuddyState state, const char *status)
114 {
115 JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource);
116
117 if(!jbr) {
118 jbr = g_new0(JabberBuddyResource, 1);
119 jbr->jb = jb;
120 jbr->name = g_strdup(resource);
121 jbr->capabilities = JABBER_CAP_XHTML;
122 jb->resources = g_list_append(jb->resources, jbr);
123 }
124 jbr->priority = priority;
125 jbr->state = state;
126 if(jbr->status)
127 g_free(jbr->status);
128 if (status)
129 jbr->status = g_markup_escape_text(status, -1);
130 else
131 jbr->status = NULL;
132
133 return jbr;
134 }
135
136 void jabber_buddy_resource_free(JabberBuddyResource *jbr)
137 {
138 g_return_if_fail(jbr != NULL);
139
140 jbr->jb->resources = g_list_remove(jbr->jb->resources, jbr);
141
142 g_free(jbr->name);
143 g_free(jbr->status);
144 g_free(jbr->thread_id);
145 g_free(jbr->client.name);
146 g_free(jbr->client.version);
147 g_free(jbr->client.os);
148 g_free(jbr);
149 }
150
151 void jabber_buddy_remove_resource(JabberBuddy *jb, const char *resource)
152 {
153 JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource);
154
155 if(!jbr)
156 return;
157
158 jabber_buddy_resource_free(jbr);
159 }
160
161 const char *jabber_buddy_get_status_msg(JabberBuddy *jb)
162 {
163 JabberBuddyResource *jbr;
164
165 if(!jb)
166 return NULL;
167
168 jbr = jabber_buddy_find_resource(jb, NULL);
169
170 if(!jbr)
171 return NULL;
172
173 return jbr->status;
174 }
175
176 /*******
177 * This is the old vCard stuff taken from the old prpl. vCards, by definition
178 * are a temporary thing until jabber can get its act together and come up
179 * with a format for user information, hence the namespace of 'vcard-temp'
180 *
181 * Since I don't feel like putting that much work into something that's
182 * _supposed_ to go away, i'm going to just copy the kludgy old code here,
183 * and make it purdy when jabber comes up with a standards-track JEP to
184 * replace vcard-temp
185 * --Nathan
186 *******/
187
188 /*---------------------------------------*/
189 /* Jabber "set info" (vCard) support */
190 /*---------------------------------------*/
191
192 /*
193 * V-Card format:
194 *
195 * <vCard prodid='' version='' xmlns=''>
196 * <FN></FN>
197 * <N>
198 * <FAMILY/>
199 * <GIVEN/>
200 * </N>
201 * <NICKNAME/>
202 * <URL/>
203 * <ADR>
204 * <STREET/>
205 * <EXTADD/>
206 * <LOCALITY/>
207 * <REGION/>
208 * <PCODE/>
209 * <COUNTRY/>
210 * </ADR>
211 * <TEL/>
212 * <EMAIL/>
213 * <ORG>
214 * <ORGNAME/>
215 * <ORGUNIT/>
216 * </ORG>
217 * <TITLE/>
218 * <ROLE/>
219 * <DESC/>
220 * <BDAY/>
221 * </vCard>
222 *
223 * See also:
224 *
225 * http://docs.jabber.org/proto/html/vcard-temp.html
226 * http://www.vcard-xml.org/dtd/vCard-XML-v2-20010520.dtd
227 */
228
229 /*
230 * Cross-reference user-friendly V-Card entry labels to vCard XML tags
231 * and attributes.
232 *
233 * Order is (or should be) unimportant. For example: we have no way of
234 * knowing in what order real data will arrive.
235 *
236 * Format: Label, Pre-set text, "visible" flag, "editable" flag, XML tag
237 * name, XML tag's parent tag "path" (relative to vCard node).
238 *
239 * List is terminated by a NULL label pointer.
240 *
241 * Entries with no label text, but with XML tag and parent tag
242 * entries, are used by V-Card XML construction routines to
243 * "automagically" construct the appropriate XML node tree.
244 *
245 * Thoughts on future direction/expansion
246 *
247 * This is a "simple" vCard.
248 *
249 * It is possible for nodes other than the "vCard" node to have
250 * attributes. Should that prove necessary/desirable, add an
251 * "attributes" pointer to the vcard_template struct, create the
252 * necessary tag_attr structs, and add 'em to the vcard_dflt_data
253 * array.
254 *
255 * The above changes will (obviously) require changes to the vCard
256 * construction routines.
257 */
258
259 struct vcard_template {
260 char *label; /* label text pointer */
261 char *text; /* entry text pointer */
262 int visible; /* should entry field be "visible?" */
263 int editable; /* should entry field be editable? */
264 char *tag; /* tag text */
265 char *ptag; /* parent tag "path" text */
266 char *url; /* vCard display format if URL */
267 } vcard_template_data[] = {
268 {N_("Full Name"), NULL, TRUE, TRUE, "FN", NULL, NULL},
269 {N_("Family Name"), NULL, TRUE, TRUE, "FAMILY", "N", NULL},
270 {N_("Given Name"), NULL, TRUE, TRUE, "GIVEN", "N", NULL},
271 {N_("Nickname"), NULL, TRUE, TRUE, "NICKNAME", NULL, NULL},
272 {N_("URL"), NULL, TRUE, TRUE, "URL", NULL, "<A HREF=\"%s\">%s</A>"},
273 {N_("Street Address"), NULL, TRUE, TRUE, "STREET", "ADR", NULL},
274 {N_("Extended Address"), NULL, TRUE, TRUE, "EXTADD", "ADR", NULL},
275 {N_("Locality"), NULL, TRUE, TRUE, "LOCALITY", "ADR", NULL},
276 {N_("Region"), NULL, TRUE, TRUE, "REGION", "ADR", NULL},
277 {N_("Postal Code"), NULL, TRUE, TRUE, "PCODE", "ADR", NULL},
278 {N_("Country"), NULL, TRUE, TRUE, "CTRY", "ADR", NULL},
279 {N_("Telephone"), NULL, TRUE, TRUE, "NUMBER", "TEL", NULL},
280 {N_("E-Mail"), NULL, TRUE, TRUE, "USERID", "EMAIL", "<A HREF=\"mailto:%s\">%s</A>"},
281 {N_("Organization Name"), NULL, TRUE, TRUE, "ORGNAME", "ORG", NULL},
282 {N_("Organization Unit"), NULL, TRUE, TRUE, "ORGUNIT", "ORG", NULL},
283 {N_("Title"), NULL, TRUE, TRUE, "TITLE", NULL, NULL},
284 {N_("Role"), NULL, TRUE, TRUE, "ROLE", NULL, NULL},
285 {N_("Birthday"), NULL, TRUE, TRUE, "BDAY", NULL, NULL},
286 {N_("Description"), NULL, TRUE, TRUE, "DESC", NULL, NULL},
287 {"", NULL, TRUE, TRUE, "N", NULL, NULL},
288 {"", NULL, TRUE, TRUE, "ADR", NULL, NULL},
289 {"", NULL, TRUE, TRUE, "ORG", NULL, NULL},
290 {NULL, NULL, 0, 0, NULL, NULL, NULL}
291 };
292
293 /*
294 * The "vCard" tag's attribute list...
295 */
296 struct tag_attr {
297 char *attr;
298 char *value;
299 } vcard_tag_attr_list[] = {
300 {"prodid", "-//HandGen//NONSGML vGen v1.0//EN"},
301 {"version", "2.0", },
302 {"xmlns", "vcard-temp", },
303 {NULL, NULL},
304 };
305
306
307 /*
308 * Insert a tag node into an xmlnode tree, recursively inserting parent tag
309 * nodes as necessary
310 *
311 * Returns pointer to inserted node
312 *
313 * Note to hackers: this code is designed to be re-entrant (it's recursive--it
314 * calls itself), so don't put any "static"s in here!
315 */
316 static xmlnode *insert_tag_to_parent_tag(xmlnode *start, const char *parent_tag, const char *new_tag)
317 {
318 xmlnode *x = NULL;
319
320 /*
321 * If the parent tag wasn't specified, see if we can get it
322 * from the vCard template struct.
323 */
324 if(parent_tag == NULL) {
325 struct vcard_template *vc_tp = vcard_template_data;
326
327 while(vc_tp->label != NULL) {
328 if(strcmp(vc_tp->tag, new_tag) == 0) {
329 parent_tag = vc_tp->ptag;
330 break;
331 }
332 ++vc_tp;
333 }
334 }
335
336 /*
337 * If we have a parent tag...
338 */
339 if(parent_tag != NULL ) {
340 /*
341 * Try to get the parent node for a tag
342 */
343 if((x = xmlnode_get_child(start, parent_tag)) == NULL) {
344 /*
345 * Descend?
346 */
347 char *grand_parent = g_strdup(parent_tag);
348 char *parent;
349
350 if((parent = strrchr(grand_parent, '/')) != NULL) {
351 *(parent++) = '\0';
352 x = insert_tag_to_parent_tag(start, grand_parent, parent);
353 } else {
354 x = xmlnode_new_child(start, grand_parent);
355 }
356 g_free(grand_parent);
357 } else {
358 /*
359 * We found *something* to be the parent node.
360 * Note: may be the "root" node!
361 */
362 xmlnode *y;
363 if((y = xmlnode_get_child(x, new_tag)) != NULL) {
364 return(y);
365 }
366 }
367 }
368
369 /*
370 * insert the new tag into its parent node
371 */
372 return(xmlnode_new_child((x == NULL? start : x), new_tag));
373 }
374
375 /*
376 * Send vCard info to Jabber server
377 */
378 void jabber_set_info(GaimConnection *gc, const char *info)
379 {
380 JabberIq *iq;
381 JabberStream *js = gc->proto_data;
382 xmlnode *vc_node;
383 char *avatar_file = NULL;
384 struct tag_attr *tag_attr;
385
386 if(js->avatar_hash)
387 g_free(js->avatar_hash);
388 js->avatar_hash = NULL;
389
390 /*
391 * Send only if there's actually any *information* to send
392 */
393 vc_node = info ? xmlnode_from_str(info, -1) : NULL;
394 avatar_file = gaim_buddy_icons_get_full_path(gaim_account_get_buddy_icon(gc->account));
395
396 if(!vc_node && avatar_file) {
397 vc_node = xmlnode_new("vCard");
398 for(tag_attr = vcard_tag_attr_list; tag_attr->attr != NULL; ++tag_attr)
399 xmlnode_set_attrib(vc_node, tag_attr->attr, tag_attr->value);
400 }
401
402 if(vc_node) {
403 if (vc_node->name &&
404 !g_ascii_strncasecmp(vc_node->name, "vCard", 5)) {
405 GError *error = NULL;
406 gchar *avatar_data_tmp;
407 guchar *avatar_data;
408 gsize avatar_len;
409
410 if(avatar_file && g_file_get_contents(avatar_file, &avatar_data_tmp, &avatar_len, &error)) {
411 xmlnode *photo, *binval;
412 gchar *enc;
413 int i;
414 unsigned char hashval[20];
415 char *p, hash[41];
416
417 avatar_data = (guchar *) avatar_data_tmp;
418 photo = xmlnode_new_child(vc_node, "PHOTO");
419 binval = xmlnode_new_child(photo, "BINVAL");
420 enc = gaim_base64_encode(avatar_data, avatar_len);
421
422 gaim_cipher_digest_region("sha1", (guchar *)avatar_data,
423 avatar_len, sizeof(hashval),
424 hashval, NULL);
425
426 p = hash;
427 for(i=0; i<20; i++, p+=2)
428 snprintf(p, 3, "%02x", hashval[i]);
429 js->avatar_hash = g_strdup(hash);
430
431 xmlnode_insert_data(binval, enc, -1);
432 g_free(enc);
433 g_free(avatar_data);
434 } else if (error != NULL) {
435 g_error_free(error);
436 }
437 g_free(avatar_file);
438
439 iq = jabber_iq_new(js, JABBER_IQ_SET);
440 xmlnode_insert_child(iq->node, vc_node);
441 jabber_iq_send(iq);
442 } else {
443 xmlnode_free(vc_node);
444 }
445 }
446 }
447
448 void jabber_set_buddy_icon(GaimConnection *gc, const char *iconfile)
449 {
450 GaimPresence *gpresence;
451 GaimStatus *status;
452
453 jabber_set_info(gc, gaim_account_get_user_info(gc->account));
454
455 gpresence = gaim_account_get_presence(gc->account);
456 status = gaim_presence_get_active_status(gpresence);
457 jabber_presence_send(gc->account, status);
458 }
459
460 /*
461 * This is the callback from the "ok clicked" for "set vCard"
462 *
463 * Formats GSList data into XML-encoded string and returns a pointer
464 * to said string.
465 *
466 * g_free()'ing the returned string space is the responsibility of
467 * the caller.
468 */
469 static void
470 jabber_format_info(GaimConnection *gc, GaimRequestFields *fields)
471 {
472 xmlnode *vc_node;
473 GaimRequestField *field;
474 const char *text;
475 char *p;
476 const struct vcard_template *vc_tp;
477 struct tag_attr *tag_attr;
478
479 vc_node = xmlnode_new("vCard");
480
481 for(tag_attr = vcard_tag_attr_list; tag_attr->attr != NULL; ++tag_attr)
482 xmlnode_set_attrib(vc_node, tag_attr->attr, tag_attr->value);
483
484 for (vc_tp = vcard_template_data; vc_tp->label != NULL; vc_tp++) {
485 if (*vc_tp->label == '\0')
486 continue;
487
488 field = gaim_request_fields_get_field(fields, vc_tp->tag);
489 text = gaim_request_field_string_get_value(field);
490
491
492 if (text != NULL && *text != '\0') {
493 xmlnode *xp;
494
495 gaim_debug(GAIM_DEBUG_INFO, "jabber",
496 "Setting %s to '%s'\n", vc_tp->tag, text);
497
498 if ((xp = insert_tag_to_parent_tag(vc_node,
499 NULL, vc_tp->tag)) != NULL) {
500
501 xmlnode_insert_data(xp, text, -1);
502 }
503 }
504 }
505
506 p = xmlnode_to_str(vc_node, NULL);
507 xmlnode_free(vc_node);
508
509 if (gc != NULL) {
510 GaimAccount *account = gaim_connection_get_account(gc);
511
512 if (account != NULL) {
513 gaim_account_set_user_info(account, p);
514 serv_set_info(gc, p);
515 }
516 }
517
518 g_free(p);
519 }
520
521 /*
522 * This gets executed by the proto action
523 *
524 * Creates a new GaimRequestFields struct, gets the XML-formatted user_info
525 * string (if any) into GSLists for the (multi-entry) edit dialog and
526 * calls the set_vcard dialog.
527 */
528 void jabber_setup_set_info(GaimPluginAction *action)
529 {
530 GaimConnection *gc = (GaimConnection *) action->context;
531 GaimRequestFields *fields;
532 GaimRequestFieldGroup *group;
533 GaimRequestField *field;
534 const struct vcard_template *vc_tp;
535 const char *user_info;
536 char *cdata = NULL;
537 xmlnode *x_vc_data = NULL;
538
539 fields = gaim_request_fields_new();
540 group = gaim_request_field_group_new(NULL);
541 gaim_request_fields_add_group(fields, group);
542
543 /*
544 * Get existing, XML-formatted, user info
545 */
546 if((user_info = gaim_account_get_user_info(gc->account)) != NULL)
547 x_vc_data = xmlnode_from_str(user_info, -1);
548
549 /*
550 * Set up GSLists for edit with labels from "template," data from user info
551 */
552 for(vc_tp = vcard_template_data; vc_tp->label != NULL; ++vc_tp) {
553 xmlnode *data_node;
554 if((vc_tp->label)[0] == '\0')
555 continue;
556
557 if (x_vc_data != NULL) {
558 if(vc_tp->ptag == NULL) {
559 data_node = xmlnode_get_child(x_vc_data, vc_tp->tag);
560 } else {
561 gchar *tag = g_strdup_printf("%s/%s", vc_tp->ptag, vc_tp->tag);
562 data_node = xmlnode_get_child(x_vc_data, tag);
563 g_free(tag);
564 }
565 if(data_node)
566 cdata = xmlnode_get_data(data_node);
567 }
568
569 if(strcmp(vc_tp->tag, "DESC") == 0) {
570 field = gaim_request_field_string_new(vc_tp->tag,
571 _(vc_tp->label), cdata,
572 TRUE);
573 } else {
574 field = gaim_request_field_string_new(vc_tp->tag,
575 _(vc_tp->label), cdata,
576 FALSE);
577 }
578
579 g_free(cdata);
580 cdata = NULL;
581
582 gaim_request_field_group_add_field(group, field);
583 }
584
585 if(x_vc_data != NULL)
586 xmlnode_free(x_vc_data);
587
588 gaim_request_fields(gc, _("Edit Jabber vCard"),
589 _("Edit Jabber vCard"),
590 _("All items below are optional. Enter only the "
591 "information with which you feel comfortable."),
592 fields,
593 _("Save"), G_CALLBACK(jabber_format_info),
594 _("Cancel"), NULL,
595 gc);
596 }
597
598 /*---------------------------------------*/
599 /* End Jabber "set info" (vCard) support */
600 /*---------------------------------------*/
601
602 /******
603 * end of that ancient crap that needs to die
604 ******/
605
606 static void jabber_buddy_info_show_if_ready(JabberBuddyInfo *jbi)
607 {
608 GString *info_text;
609 char *resource_name;
610 JabberBuddyResource *jbr;
611 JabberBuddyInfoResource *jbir;
612 GList *resources;
613
614 /* not yet */
615 if(jbi->ids)
616 return;
617
618 info_text = g_string_new("");
619 resource_name = jabber_get_resource(jbi->jid);
620
621 if(resource_name) {
622 jbr = jabber_buddy_find_resource(jbi->jb, resource_name);
623 jbir = g_hash_table_lookup(jbi->resources, resource_name);
624 if(jbr) {
625 char *purdy = NULL;
626 if(jbr->status)
627 purdy = gaim_strdup_withhtml(jbr->status);
628 g_string_append_printf(info_text, "<b>%s:</b> %s%s%s<br/>",
629 _("Status"), jabber_buddy_state_get_name(jbr->state),
630 purdy ? ": " : "",
631 purdy ? purdy : "");
632 if(purdy)
633 g_free(purdy);
634 } else {
635 g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
636 _("Status"), _("Unknown"));
637 }
638 if(jbir) {
639 if(jbir->idle_seconds > 0) {
640 g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
641 _("Idle"), gaim_str_seconds_to_string(jbir->idle_seconds));
642 }
643 }
644 if(jbr && jbr->client.name) {
645 g_string_append_printf(info_text, "<b>%s:</b> %s %s<br/>",
646 _("Client:"), jbr->client.name,
647 jbr->client.version ? jbr->client.version : "");
648 if(jbr->client.os) {
649 g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
650 _("Operating System"), jbr->client.os);
651 }
652 }
653 } else {
654 for(resources = jbi->jb->resources; resources; resources = resources->next) {
655 char *purdy = NULL;
656 jbr = resources->data;
657 if(jbr->status)
658 purdy = gaim_strdup_withhtml(jbr->status);
659 g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
660 _("Resource"), jbr->name);
661 g_string_append_printf(info_text, "<b>%s:</b> %s%s%s<br/>",
662 _("Status"), jabber_buddy_state_get_name(jbr->state),
663 purdy ? ": " : "",
664 purdy ? purdy : "");
665 if(purdy)
666 g_free(purdy);
667
668 jbir = g_hash_table_lookup(jbi->resources, jbr->name);
669 if(jbir) {
670 if(jbir->idle_seconds > 0) {
671 g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
672 _("Idle"), gaim_str_seconds_to_string(jbir->idle_seconds));
673 }
674 }
675 if(jbr->client.name) {
676 g_string_append_printf(info_text, "<b>%s:</b> %s %s<br/>",
677 _("Client"), jbr->client.name,
678 jbr->client.version ? jbr->client.version : "");
679 if(jbr->client.os) {
680 g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
681 _("Operating System"), jbr->client.os);
682 }
683 }
684
685 g_string_append_printf(info_text, "<br/>");
686 }
687 }
688
689 g_free(resource_name);
690
691 if (jbi->vcard_text != NULL)
692 info_text = g_string_append(info_text, jbi->vcard_text);
693
694 gaim_notify_userinfo(jbi->js->gc, jbi->jid, info_text->str, NULL, NULL);
695
696 while(jbi->vcard_imgids) {
697 gaim_imgstore_unref(GPOINTER_TO_INT(jbi->vcard_imgids->data));
698 jbi->vcard_imgids = g_slist_delete_link(jbi->vcard_imgids, jbi->vcard_imgids);
699 }
700
701 g_string_free(info_text, TRUE);
702
703 if (jbi->timeout_handle > 0)
704 gaim_timeout_remove(jbi->timeout_handle);
705
706 g_free(jbi->jid);
707 g_hash_table_destroy(jbi->resources);
708 g_free(jbi->vcard_text);
709 g_free(jbi);
710 }
711
712 static void jabber_buddy_info_remove_id(JabberBuddyInfo *jbi, const char *id)
713 {
714 GSList *l = jbi->ids;
715
716 if(!id)
717 return;
718
719 while(l) {
720 if(!strcmp(id, l->data)) {
721 jbi->ids = g_slist_remove(jbi->ids, l->data);
722 return;
723 }
724 l = l->next;
725 }
726 }
727
728 static void jabber_vcard_parse(JabberStream *js, xmlnode *packet, gpointer data)
729 {
730 const char *id, *from;
731 GString *info_text;
732 char *bare_jid;
733 char *text;
734 xmlnode *vcard;
735 GaimBuddy *b;
736 JabberBuddyInfo *jbi = data;
737
738 from = xmlnode_get_attrib(packet, "from");
739 id = xmlnode_get_attrib(packet, "id");
740
741 if(!jbi)
742 return;
743
744 jabber_buddy_info_remove_id(jbi, id);
745
746 if(!from)
747 return;
748
749 if(!jabber_buddy_find(js, from, FALSE))
750 return;
751
752 /* XXX: handle the error case */
753
754 bare_jid = jabber_get_bare_jid(from);
755
756 b = gaim_find_buddy(js->gc->account, bare_jid);
757
758 info_text = g_string_new("");
759
760 if((vcard = xmlnode_get_child(packet, "vCard")) ||
761 (vcard = xmlnode_get_child_with_namespace(packet, "query", "vcard-temp"))) {
762 xmlnode *child;
763 for(child = vcard->child; child; child = child->next)
764 {
765 xmlnode *child2;
766
767 if(child->type != XMLNODE_TYPE_TAG)
768 continue;
769
770 text = xmlnode_get_data(child);
771 if(text && !strcmp(child->name, "FN")) {
772 g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
773 _("Full Name"), text);
774 } else if(!strcmp(child->name, "N")) {
775 for(child2 = child->child; child2; child2 = child2->next)
776 {
777 char *text2;
778
779 if(child2->type != XMLNODE_TYPE_TAG)
780 continue;
781
782 text2 = xmlnode_get_data(child2);
783 if(text2 && !strcmp(child2->name, "FAMILY")) {
784 g_string_append_printf(info_text,
785 "<b>%s:</b> %s<br/>",
786 _("Family Name"), text2);
787 } else if(text2 && !strcmp(child2->name, "GIVEN")) {
788 g_string_append_printf(info_text,
789 "<b>%s:</b> %s<br/>",
790 _("Given Name"), text2);
791 } else if(text2 && !strcmp(child2->name, "MIDDLE")) {
792 g_string_append_printf(info_text,
793 "<b>%s:</b> %s<br/>",
794 _("Middle Name"), text2);
795 }
796 g_free(text2);
797 }
798 } else if(text && !strcmp(child->name, "NICKNAME")) {
799 serv_got_alias(js->gc, from, text);
800 if(b) {
801 gaim_blist_node_set_string((GaimBlistNode*)b, "servernick", text);
802 }
803 g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
804 _("Nickname"), text);
805 } else if(text && !strcmp(child->name, "BDAY")) {
806 g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
807 _("Birthday"), text);
808 } else if(!strcmp(child->name, "ADR")) {
809 gboolean address_line_added = FALSE;
810
811 for(child2 = child->child; child2; child2 = child2->next)
812 {
813 char *text2;
814
815 if(child2->type != XMLNODE_TYPE_TAG)
816 continue;
817
818 text2 = xmlnode_get_data(child2);
819 if (text2 == NULL)
820 continue;
821
822 /* We do this here so that it's not added if all the child
823 * elements are empty. */
824 if (!address_line_added)
825 {
826 g_string_append_printf(info_text, "<b>%s:</b><br/>",
827 _("Address"));
828 address_line_added = TRUE;
829 }
830
831 if(!strcmp(child2->name, "POBOX")) {
832 g_string_append_printf(info_text,
833 "&nbsp;<b>%s:</b> %s<br/>",
834 _("P.O. Box"), text2);
835 } else if(!strcmp(child2->name, "EXTADR")) {
836 g_string_append_printf(info_text,
837 "&nbsp;<b>%s:</b> %s<br/>",
838 _("Extended Address"), text2);
839 } else if(!strcmp(child2->name, "STREET")) {
840 g_string_append_printf(info_text,
841 "&nbsp;<b>%s:</b> %s<br/>",
842 _("Street Address"), text2);
843 } else if(!strcmp(child2->name, "LOCALITY")) {
844 g_string_append_printf(info_text,
845 "&nbsp;<b>%s:</b> %s<br/>",
846 _("Locality"), text2);
847 } else if(!strcmp(child2->name, "REGION")) {
848 g_string_append_printf(info_text,
849 "&nbsp;<b>%s:</b> %s<br/>",
850 _("Region"), text2);
851 } else if(!strcmp(child2->name, "PCODE")) {
852 g_string_append_printf(info_text,
853 "&nbsp;<b>%s:</b> %s<br/>",
854 _("Postal Code"), text2);
855 } else if(!strcmp(child2->name, "CTRY")
856 || !strcmp(child2->name, "COUNTRY")) {
857 g_string_append_printf(info_text,
858 "&nbsp;<b>%s:</b> %s<br/>",
859 _("Country"), text2);
860 }
861 g_free(text2);
862 }
863 } else if(!strcmp(child->name, "TEL")) {
864 char *number;
865 if((child2 = xmlnode_get_child(child, "NUMBER"))) {
866 /* show what kind of number it is */
867 number = xmlnode_get_data(child2);
868 if(number) {
869 g_string_append_printf(info_text,
870 "<b>%s:</b> %s<br/>", _("Telephone"), number);
871 g_free(number);
872 }
873 } else if((number = xmlnode_get_data(child))) {
874 /* lots of clients (including gaim) do this, but it's
875 * out of spec */
876 g_string_append_printf(info_text,
877 "<b>%s:</b> %s<br/>", _("Telephone"), number);
878 g_free(number);
879 }
880 } else if(!strcmp(child->name, "EMAIL")) {
881 char *userid;
882 if((child2 = xmlnode_get_child(child, "USERID"))) {
883 /* show what kind of email it is */
884 userid = xmlnode_get_data(child2);
885 if(userid) {
886 g_string_append_printf(info_text,
887 "<b>%s:</b> <a href='mailto:%s'>%s</a><br/>",
888 _("E-Mail"), userid, userid);
889 g_free(userid);
890 }
891 } else if((userid = xmlnode_get_data(child))) {
892 /* lots of clients (including gaim) do this, but it's
893 * out of spec */
894 g_string_append_printf(info_text,
895 "<b>%s:</b> <a href='mailto:%s'>%s</a><br/>",
896 _("E-Mail"), userid, userid);
897 g_free(userid);
898 }
899 } else if(!strcmp(child->name, "ORG")) {
900 for(child2 = child->child; child2; child2 = child2->next)
901 {
902 char *text2;
903
904 if(child2->type != XMLNODE_TYPE_TAG)
905 continue;
906
907 text2 = xmlnode_get_data(child2);
908 if(text2 && !strcmp(child2->name, "ORGNAME")) {
909 g_string_append_printf(info_text,
910 "<b>%s:</b> %s<br/>",
911 _("Organization Name"), text2);
912 } else if(text2 && !strcmp(child2->name, "ORGUNIT")) {
913 g_string_append_printf(info_text,
914 "<b>%s:</b> %s<br/>",
915 _("Organization Unit"), text2);
916 }
917 g_free(text2);
918 }
919 } else if(text && !strcmp(child->name, "TITLE")) {
920 g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
921 _("Title"), text);
922 } else if(text && !strcmp(child->name, "ROLE")) {
923 g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
924 _("Role"), text);
925 } else if(text && !strcmp(child->name, "DESC")) {
926 g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
927 _("Description"), text);
928 } else if(!strcmp(child->name, "PHOTO") ||
929 !strcmp(child->name, "LOGO")) {
930 char *bintext = NULL;
931 xmlnode *binval;
932
933 if( ((binval = xmlnode_get_child(child, "BINVAL")) &&
934 (bintext = xmlnode_get_data(binval))) ||
935 (bintext = xmlnode_get_data(child))) {
936 gsize size;
937 guchar *data;
938 int i;
939 unsigned char hashval[20];
940 char *p, hash[41];
941 gboolean photo = (strcmp(child->name, "PHOTO") == 0);
942
943 data = gaim_base64_decode(bintext, &size);
944
945 jbi->vcard_imgids = g_slist_prepend(jbi->vcard_imgids, GINT_TO_POINTER(gaim_imgstore_add(data, size, "logo.png")));
946 g_string_append_printf(info_text,
947 "<b>%s:</b> <img id='%d'><br/>",
948 photo ? _("Photo") : _("Logo"),
949 GPOINTER_TO_INT(jbi->vcard_imgids->data));
950
951 gaim_buddy_icons_set_for_user(js->gc->account, bare_jid,
952 data, size);
953
954 gaim_cipher_digest_region("sha1", (guchar *)data, size,
955 sizeof(hashval), hashval, NULL);
956 p = hash;
957 for(i=0; i<20; i++, p+=2)
958 snprintf(p, 3, "%02x", hashval[i]);
959 gaim_blist_node_set_string((GaimBlistNode*)b, "avatar_hash", hash);
960
961 g_free(data);
962 g_free(bintext);
963 }
964 }
965 g_free(text);
966 }
967 }
968
969 jbi->vcard_text = gaim_strdup_withhtml(info_text->str);
970 g_string_free(info_text, TRUE);
971 g_free(bare_jid);
972
973 jabber_buddy_info_show_if_ready(jbi);
974 }
975
976
977 static void jabber_buddy_info_resource_free(gpointer data)
978 {
979 JabberBuddyInfoResource *jbri = data;
980 g_free(jbri);
981 }
982
983 static void jabber_version_parse(JabberStream *js, xmlnode *packet, gpointer data)
984 {
985 JabberBuddyInfo *jbi = data;
986 const char *type, *id, *from;
987 xmlnode *query;
988 char *resource_name;
989
990 g_return_if_fail(jbi != NULL);
991
992 type = xmlnode_get_attrib(packet, "type");
993 id = xmlnode_get_attrib(packet, "id");
994 from = xmlnode_get_attrib(packet, "from");
995
996 jabber_buddy_info_remove_id(jbi, id);
997
998 if(!from)
999 return;
1000
1001 resource_name = jabber_get_resource(from);
1002
1003 if(resource_name) {
1004 if(type && !strcmp(type, "result")) {
1005 if((query = xmlnode_get_child(packet, "query"))) {
1006 JabberBuddyResource *jbr = jabber_buddy_find_resource(jbi->jb, resource_name);
1007 if(jbr) {
1008 xmlnode *node;
1009 if((node = xmlnode_get_child(query, "name"))) {
1010 jbr->client.name = xmlnode_get_data(node);
1011 }
1012 if((node = xmlnode_get_child(query, "version"))) {
1013 jbr->client.version = xmlnode_get_data(node);
1014 }
1015 if((node = xmlnode_get_child(query, "os"))) {
1016 jbr->client.os = xmlnode_get_data(node);
1017 }
1018 }
1019 }
1020 }
1021 g_free(resource_name);
1022 }
1023
1024 jabber_buddy_info_show_if_ready(jbi);
1025 }
1026
1027 static void jabber_last_parse(JabberStream *js, xmlnode *packet, gpointer data)
1028 {
1029 JabberBuddyInfo *jbi = data;
1030 xmlnode *query;
1031 char *resource_name;
1032 const char *type, *id, *from, *seconds;
1033
1034 g_return_if_fail(jbi != NULL);
1035
1036 type = xmlnode_get_attrib(packet, "type");
1037 id = xmlnode_get_attrib(packet, "id");
1038 from = xmlnode_get_attrib(packet, "from");
1039
1040 jabber_buddy_info_remove_id(jbi, id);
1041
1042 if(!from)
1043 return;
1044
1045 resource_name = jabber_get_resource(from);
1046
1047 if(resource_name) {
1048 if(type && !strcmp(type, "result")) {
1049 if((query = xmlnode_get_child(packet, "query"))) {
1050 seconds = xmlnode_get_attrib(query, "seconds");
1051 if(seconds) {
1052 char *end = NULL;
1053 long sec = strtol(seconds, &end, 10);
1054 if(end != seconds) {
1055 JabberBuddyInfoResource *jbir = g_hash_table_lookup(jbi->resources, resource_name);
1056 if(jbir) {
1057 jbir->idle_seconds = sec;
1058 }
1059 }
1060 }
1061 }
1062 }
1063 g_free(resource_name);
1064 }
1065
1066 jabber_buddy_info_show_if_ready(jbi);
1067 }
1068
1069 static gboolean jabber_buddy_get_info_timeout(gpointer data)
1070 {
1071 JabberBuddyInfo *jbi = data;
1072
1073 /* remove the pending callbacks */
1074 while(jbi->ids) {
1075 char *id = jbi->ids->data;
1076 jabber_iq_remove_callback_by_id(jbi->js, id);
1077 g_free(id);
1078 jbi->ids = g_slist_remove(jbi->ids, id);
1079 }
1080
1081 jbi->timeout_handle = 0;
1082
1083 jabber_buddy_info_show_if_ready(jbi);
1084
1085 return FALSE;
1086 }
1087
1088 static void jabber_buddy_get_info_for_jid(JabberStream *js, const char *jid)
1089 {
1090 JabberIq *iq;
1091 xmlnode *vcard;
1092 GList *resources;
1093 JabberBuddy *jb;
1094 JabberBuddyInfo *jbi;
1095
1096 jb = jabber_buddy_find(js, jid, TRUE);
1097
1098 /* invalid JID */
1099 if(!jb)
1100 return;
1101
1102 jbi = g_new0(JabberBuddyInfo, 1);
1103 jbi->jid = g_strdup(jid);
1104 jbi->js = js;
1105 jbi->jb = jb;
1106 jbi->resources = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, jabber_buddy_info_resource_free);
1107
1108 iq = jabber_iq_new(js, JABBER_IQ_GET);
1109
1110 xmlnode_set_attrib(iq->node, "to", jid);
1111 vcard = xmlnode_new_child(iq->node, "vCard");
1112 xmlnode_set_namespace(vcard, "vcard-temp");
1113
1114 jabber_iq_set_callback(iq, jabber_vcard_parse, jbi);
1115 jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1116
1117 jabber_iq_send(iq);
1118
1119 for(resources = jb->resources; resources; resources = resources->next)
1120 {
1121 JabberBuddyResource *jbr = resources->data;
1122 JabberBuddyInfoResource *jbir;
1123 char *full_jid;
1124
1125 if ((strchr(jid, '/') == NULL) && (jbr->name != NULL)) {
1126 full_jid = g_strdup_printf("%s/%s", jid, jbr->name);
1127 } else {
1128 full_jid = g_strdup(jid);
1129 }
1130
1131 if (jbr->name != NULL)
1132 {
1133 jbir = g_new0(JabberBuddyInfoResource, 1);
1134 g_hash_table_insert(jbi->resources, g_strdup(jbr->name), jbir);
1135 }
1136
1137 if(!jbr->client.name) {
1138 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:version");
1139 xmlnode_set_attrib(iq->node, "to", full_jid);
1140 jabber_iq_set_callback(iq, jabber_version_parse, jbi);
1141 jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1142 jabber_iq_send(iq);
1143 }
1144
1145 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:last");
1146 xmlnode_set_attrib(iq->node, "to", full_jid);
1147 jabber_iq_set_callback(iq, jabber_last_parse, jbi);
1148 jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1149 jabber_iq_send(iq);
1150
1151 g_free(full_jid);
1152 }
1153
1154 jbi->timeout_handle = gaim_timeout_add(30000, jabber_buddy_get_info_timeout, jbi);
1155 }
1156
1157 void jabber_buddy_get_info(GaimConnection *gc, const char *who)
1158 {
1159 JabberStream *js = gc->proto_data;
1160 char *bare_jid = jabber_get_bare_jid(who);
1161
1162 if(bare_jid) {
1163 jabber_buddy_get_info_for_jid(js, bare_jid);
1164 g_free(bare_jid);
1165 }
1166 }
1167
1168 void jabber_buddy_get_info_chat(GaimConnection *gc, int id,
1169 const char *resource)
1170 {
1171 JabberStream *js = gc->proto_data;
1172 JabberChat *chat = jabber_chat_find_by_id(js, id);
1173 char *full_jid;
1174
1175 if(!chat)
1176 return;
1177
1178 full_jid = g_strdup_printf("%s@%s/%s", chat->room, chat->server, resource);
1179 jabber_buddy_get_info_for_jid(js, full_jid);
1180 g_free(full_jid);
1181 }
1182
1183
1184 static void jabber_buddy_set_invisibility(JabberStream *js, const char *who,
1185 gboolean invisible)
1186 {
1187 GaimPresence *gpresence;
1188 GaimAccount *account;
1189 GaimStatus *status;
1190 JabberBuddy *jb = jabber_buddy_find(js, who, TRUE);
1191 xmlnode *presence;
1192 JabberBuddyState state;
1193 const char *msg;
1194 int priority;
1195
1196 account = gaim_connection_get_account(js->gc);
1197 gpresence = gaim_account_get_presence(account);
1198 status = gaim_presence_get_active_status(gpresence);
1199
1200 gaim_status_to_jabber(status, &state, &msg, &priority);
1201 presence = jabber_presence_create(state, msg, priority);
1202
1203 xmlnode_set_attrib(presence, "to", who);
1204 if(invisible) {
1205 xmlnode_set_attrib(presence, "type", "invisible");
1206 jb->invisible |= JABBER_INVIS_BUDDY;
1207 } else {
1208 jb->invisible &= ~JABBER_INVIS_BUDDY;
1209 }
1210
1211 jabber_send(js, presence);
1212 xmlnode_free(presence);
1213 }
1214
1215 static void jabber_buddy_make_invisible(GaimBlistNode *node, gpointer data)
1216 {
1217 GaimBuddy *buddy;
1218 GaimConnection *gc;
1219 JabberStream *js;
1220
1221 g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
1222
1223 buddy = (GaimBuddy *) node;
1224 gc = gaim_account_get_connection(buddy->account);
1225 js = gc->proto_data;
1226
1227 jabber_buddy_set_invisibility(js, buddy->name, TRUE);
1228 }
1229
1230 static void jabber_buddy_make_visible(GaimBlistNode *node, gpointer data)
1231 {
1232 GaimBuddy *buddy;
1233 GaimConnection *gc;
1234 JabberStream *js;
1235
1236 g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
1237
1238 buddy = (GaimBuddy *) node;
1239 gc = gaim_account_get_connection(buddy->account);
1240 js = gc->proto_data;
1241
1242 jabber_buddy_set_invisibility(js, buddy->name, FALSE);
1243 }
1244
1245 static void jabber_buddy_cancel_presence_notification(GaimBlistNode *node,
1246 gpointer data)
1247 {
1248 GaimBuddy *buddy;
1249 GaimConnection *gc;
1250 JabberStream *js;
1251
1252 g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
1253
1254 buddy = (GaimBuddy *) node;
1255 gc = gaim_account_get_connection(buddy->account);
1256 js = gc->proto_data;
1257
1258 /* I wonder if we should prompt the user before doing this */
1259 jabber_presence_subscription_set(js, buddy->name, "unsubscribed");
1260 }
1261
1262 static void jabber_buddy_rerequest_auth(GaimBlistNode *node, gpointer data)
1263 {
1264 GaimBuddy *buddy;
1265 GaimConnection *gc;
1266 JabberStream *js;
1267
1268 g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
1269
1270 buddy = (GaimBuddy *) node;
1271 gc = gaim_account_get_connection(buddy->account);
1272 js = gc->proto_data;
1273
1274 jabber_presence_subscription_set(js, buddy->name, "subscribe");
1275 }
1276
1277
1278 static void jabber_buddy_unsubscribe(GaimBlistNode *node, gpointer data)
1279 {
1280 GaimBuddy *buddy;
1281 GaimConnection *gc;
1282 JabberStream *js;
1283
1284 g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
1285
1286 buddy = (GaimBuddy *) node;
1287 gc = gaim_account_get_connection(buddy->account);
1288 js = gc->proto_data;
1289
1290 jabber_presence_subscription_set(js, buddy->name, "unsubscribe");
1291 }
1292
1293
1294 static GList *jabber_buddy_menu(GaimBuddy *buddy)
1295 {
1296 GaimConnection *gc = gaim_account_get_connection(buddy->account);
1297 JabberStream *js = gc->proto_data;
1298 JabberBuddy *jb = jabber_buddy_find(js, buddy->name, TRUE);
1299
1300 GList *m = NULL;
1301 GaimMenuAction *act;
1302
1303 if(!jb)
1304 return m;
1305
1306 /* XXX: fix the NOT ME below */
1307
1308 if(js->protocol_version == JABBER_PROTO_0_9 /* && NOT ME */) {
1309 if(jb->invisible & JABBER_INVIS_BUDDY) {
1310 act = gaim_menu_action_new(_("Un-hide From"),
1311 GAIM_CALLBACK(jabber_buddy_make_visible),
1312 NULL, NULL);
1313 } else {
1314 act = gaim_menu_action_new(_("Temporarily Hide From"),
1315 GAIM_CALLBACK(jabber_buddy_make_invisible),
1316 NULL, NULL);
1317 }
1318 m = g_list_append(m, act);
1319 }
1320
1321 if(jb->subscription & JABBER_SUB_FROM /* && NOT ME */) {
1322 act = gaim_menu_action_new(_("Cancel Presence Notification"),
1323 GAIM_CALLBACK(jabber_buddy_cancel_presence_notification),
1324 NULL, NULL);
1325 m = g_list_append(m, act);
1326 }
1327
1328 if(!(jb->subscription & JABBER_SUB_TO)) {
1329 act = gaim_menu_action_new(_("(Re-)Request authorization"),
1330 GAIM_CALLBACK(jabber_buddy_rerequest_auth),
1331 NULL, NULL);
1332 m = g_list_append(m, act);
1333
1334 } else /* if(NOT ME) */{
1335
1336 /* shouldn't this just happen automatically when the buddy is
1337 removed? */
1338 act = gaim_menu_action_new(_("Unsubscribe"),
1339 GAIM_CALLBACK(jabber_buddy_unsubscribe),
1340 NULL, NULL);
1341 m = g_list_append(m, act);
1342 }
1343
1344 return m;
1345 }
1346
1347 GList *
1348 jabber_blist_node_menu(GaimBlistNode *node)
1349 {
1350 if(GAIM_BLIST_NODE_IS_BUDDY(node)) {
1351 return jabber_buddy_menu((GaimBuddy *) node);
1352 } else {
1353 return NULL;
1354 }
1355 }
1356
1357
1358 const char *
1359 jabber_buddy_state_get_name(JabberBuddyState state)
1360 {
1361 switch(state) {
1362 case JABBER_BUDDY_STATE_UNKNOWN:
1363 return _("Unknown");
1364 case JABBER_BUDDY_STATE_ERROR:
1365 return _("Error");
1366 case JABBER_BUDDY_STATE_UNAVAILABLE:
1367 return _("Offline");
1368 case JABBER_BUDDY_STATE_ONLINE:
1369 return _("Available");
1370 case JABBER_BUDDY_STATE_CHAT:
1371 return _("Chatty");
1372 case JABBER_BUDDY_STATE_AWAY:
1373 return _("Away");
1374 case JABBER_BUDDY_STATE_XA:
1375 return _("Extended Away");
1376 case JABBER_BUDDY_STATE_DND:
1377 return _("Do Not Disturb");
1378 }
1379
1380 return _("Unknown");
1381 }
1382
1383 JabberBuddyState jabber_buddy_status_id_get_state(const char *id) {
1384 if(!id)
1385 return JABBER_BUDDY_STATE_UNKNOWN;
1386 if(!strcmp(id, "available"))
1387 return JABBER_BUDDY_STATE_ONLINE;
1388 if(!strcmp(id, "freeforchat"))
1389 return JABBER_BUDDY_STATE_CHAT;
1390 if(!strcmp(id, "away"))
1391 return JABBER_BUDDY_STATE_AWAY;
1392 if(!strcmp(id, "extended_away"))
1393 return JABBER_BUDDY_STATE_XA;
1394 if(!strcmp(id, "dnd"))
1395 return JABBER_BUDDY_STATE_DND;
1396 if(!strcmp(id, "offline"))
1397 return JABBER_BUDDY_STATE_UNAVAILABLE;
1398 if(!strcmp(id, "error"))
1399 return JABBER_BUDDY_STATE_ERROR;
1400
1401 return JABBER_BUDDY_STATE_UNKNOWN;
1402 }
1403
1404 JabberBuddyState jabber_buddy_show_get_state(const char *id) {
1405 if(!id)
1406 return JABBER_BUDDY_STATE_UNKNOWN;
1407 if(!strcmp(id, "available"))
1408 return JABBER_BUDDY_STATE_ONLINE;
1409 if(!strcmp(id, "chat"))
1410 return JABBER_BUDDY_STATE_CHAT;
1411 if(!strcmp(id, "away"))
1412 return JABBER_BUDDY_STATE_AWAY;
1413 if(!strcmp(id, "xa"))
1414 return JABBER_BUDDY_STATE_XA;
1415 if(!strcmp(id, "dnd"))
1416 return JABBER_BUDDY_STATE_DND;
1417 if(!strcmp(id, "offline"))
1418 return JABBER_BUDDY_STATE_UNAVAILABLE;
1419 if(!strcmp(id, "error"))
1420 return JABBER_BUDDY_STATE_ERROR;
1421
1422 return JABBER_BUDDY_STATE_UNKNOWN;
1423 }
1424
1425 const char *jabber_buddy_state_get_show(JabberBuddyState state) {
1426 switch(state) {
1427 case JABBER_BUDDY_STATE_CHAT:
1428 return "chat";
1429 case JABBER_BUDDY_STATE_AWAY:
1430 return "away";
1431 case JABBER_BUDDY_STATE_XA:
1432 return "xa";
1433 case JABBER_BUDDY_STATE_DND:
1434 return "dnd";
1435 case JABBER_BUDDY_STATE_ONLINE:
1436 return "available";
1437 case JABBER_BUDDY_STATE_UNKNOWN:
1438 case JABBER_BUDDY_STATE_ERROR:
1439 return NULL;
1440 case JABBER_BUDDY_STATE_UNAVAILABLE:
1441 return "offline";
1442 }
1443 return NULL;
1444 }
1445
1446 const char *jabber_buddy_state_get_status_id(JabberBuddyState state) {
1447 switch(state) {
1448 case JABBER_BUDDY_STATE_CHAT:
1449 return "freeforchat";
1450 case JABBER_BUDDY_STATE_AWAY:
1451 return "away";
1452 case JABBER_BUDDY_STATE_XA:
1453 return "extended_away";
1454 case JABBER_BUDDY_STATE_DND:
1455 return "dnd";
1456 case JABBER_BUDDY_STATE_ONLINE:
1457 return "available";
1458 case JABBER_BUDDY_STATE_UNKNOWN:
1459 return "available";
1460 case JABBER_BUDDY_STATE_ERROR:
1461 return "error";
1462 case JABBER_BUDDY_STATE_UNAVAILABLE:
1463 return "offline";
1464 }
1465 return NULL;
1466 }
1467
1468 static void user_search_result_add_buddy_cb(GaimConnection *gc, GList *row, void *user_data)
1469 {
1470 /* XXX find out the jid */
1471 gaim_blist_request_add_buddy(gaim_connection_get_account(gc),
1472 g_list_nth_data(row, 0), NULL, NULL);
1473 }
1474
1475 static void user_search_result_cb(JabberStream *js, xmlnode *packet, gpointer data)
1476 {
1477 GaimNotifySearchResults *results;
1478 GaimNotifySearchColumn *column;
1479 xmlnode *x, *query, *item, *field;
1480
1481 /* XXX error checking? */
1482 if(!(query = xmlnode_get_child(packet, "query")))
1483 return;
1484
1485 results = gaim_notify_searchresults_new();
1486 if((x = xmlnode_get_child_with_namespace(query, "x", "jabber:x:data"))) {
1487 xmlnode *reported;
1488 gaim_debug_info("jabber", "new-skool\n");
1489 if((reported = xmlnode_get_child(x, "reported"))) {
1490 xmlnode *field = xmlnode_get_child(reported, "field");
1491 while(field) {
1492 /* XXX keep track of this order, use it below */
1493 const char *var = xmlnode_get_attrib(field, "var");
1494 const char *label = xmlnode_get_attrib(field, "label");
1495 if(var) {
1496 column = gaim_notify_searchresults_column_new(label ? label : var);
1497 gaim_notify_searchresults_column_add(results, column);
1498 }
1499 field = xmlnode_get_next_twin(field);
1500 }
1501 }
1502 item = xmlnode_get_child(x, "item");
1503 while(item) {
1504 GList *row = NULL;
1505 field = xmlnode_get_child(item, "field");
1506 while(field) {
1507 xmlnode *valuenode = xmlnode_get_child(field, "value");
1508 if(valuenode) {
1509 char *value = xmlnode_get_data(valuenode);
1510 row = g_list_append(row, value);
1511 }
1512 field = xmlnode_get_next_twin(field);
1513 }
1514 gaim_notify_searchresults_row_add(results, row);
1515
1516 item = xmlnode_get_next_twin(item);
1517 }
1518 } else {
1519 /* old skool */
1520 gaim_debug_info("jabber", "old-skool\n");
1521
1522 column = gaim_notify_searchresults_column_new(_("JID"));
1523 gaim_notify_searchresults_column_add(results, column);
1524 column = gaim_notify_searchresults_column_new(_("First Name"));
1525 gaim_notify_searchresults_column_add(results, column);
1526 column = gaim_notify_searchresults_column_new(_("Last Name"));
1527 gaim_notify_searchresults_column_add(results, column);
1528 column = gaim_notify_searchresults_column_new(_("Nickname"));
1529 gaim_notify_searchresults_column_add(results, column);
1530 column = gaim_notify_searchresults_column_new(_("E-Mail"));
1531 gaim_notify_searchresults_column_add(results, column);
1532
1533 for(item = xmlnode_get_child(query, "item"); item; item = xmlnode_get_next_twin(item)) {
1534 const char *jid;
1535 xmlnode *node;
1536 GList *row = NULL;
1537
1538 if(!(jid = xmlnode_get_attrib(item, "jid")))
1539 continue;
1540
1541 row = g_list_append(row, g_strdup(jid));
1542 node = xmlnode_get_child(item, "first");
1543 row = g_list_append(row, node ? xmlnode_get_data(node) : NULL);
1544 node = xmlnode_get_child(item, "last");
1545 row = g_list_append(row, node ? xmlnode_get_data(node) : NULL);
1546 node = xmlnode_get_child(item, "nick");
1547 row = g_list_append(row, node ? xmlnode_get_data(node) : NULL);
1548 node = xmlnode_get_child(item, "email");
1549 row = g_list_append(row, node ? xmlnode_get_data(node) : NULL);
1550 gaim_debug_info("jabber", "row=%d\n", row);
1551 gaim_notify_searchresults_row_add(results, row);
1552 }
1553 }
1554
1555 gaim_notify_searchresults_button_add(results, GAIM_NOTIFY_BUTTON_ADD,
1556 user_search_result_add_buddy_cb);
1557
1558 gaim_notify_searchresults(js->gc, NULL, NULL, _("The following are the results of your search"), results, NULL, NULL);
1559 }
1560
1561 static void user_search_x_data_cb(JabberStream *js, xmlnode *result, gpointer data)
1562 {
1563 xmlnode *query;
1564 JabberIq *iq;
1565 char *dir_server = data;
1566
1567 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:search");
1568 query = xmlnode_get_child(iq->node, "query");
1569
1570 xmlnode_insert_child(query, result);
1571
1572 jabber_iq_set_callback(iq, user_search_result_cb, NULL);
1573 xmlnode_set_attrib(iq->node, "to", dir_server);
1574 jabber_iq_send(iq);
1575 g_free(dir_server);
1576 }
1577
1578 struct user_search_info {
1579 JabberStream *js;
1580 char *directory_server;
1581 };
1582
1583 static void user_search_cancel_cb(struct user_search_info *usi, GaimRequestFields *fields)
1584 {
1585 g_free(usi->directory_server);
1586 g_free(usi);
1587 }
1588
1589 static void user_search_cb(struct user_search_info *usi, GaimRequestFields *fields)
1590 {
1591 JabberStream *js = usi->js;
1592 JabberIq *iq;
1593 xmlnode *query;
1594 GList *groups, *flds;
1595
1596 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:search");
1597 query = xmlnode_get_child(iq->node, "query");
1598
1599 for(groups = gaim_request_fields_get_groups(fields); groups; groups = groups->next) {
1600 for(flds = gaim_request_field_group_get_fields(groups->data);
1601 flds; flds = flds->next) {
1602 GaimRequestField *field = flds->data;
1603 const char *id = gaim_request_field_get_id(field);
1604 const char *value = gaim_request_field_string_get_value(field);
1605
1606 if(value && (!strcmp(id, "first") || !strcmp(id, "last") || !strcmp(id, "nick") || !strcmp(id, "email"))) {
1607 xmlnode *y = xmlnode_new_child(query, id);
1608 xmlnode_insert_data(y, value, -1);
1609 }
1610 }
1611 }
1612
1613 jabber_iq_set_callback(iq, user_search_result_cb, NULL);
1614 xmlnode_set_attrib(iq->node, "to", usi->directory_server);
1615 jabber_iq_send(iq);
1616
1617 g_free(usi->directory_server);
1618 g_free(usi);
1619 }
1620
1621 #if 0
1622 /* This is for gettext only -- it will see this even though there's an #if 0. */
1623
1624 /*
1625 * An incomplete list of server generated original language search
1626 * comments for Jabber User Directories
1627 *
1628 * See discussion thread "Search comment for Jabber is not translatable"
1629 * in gaim-i18n@lists.sourceforge.net (March 2006)
1630 */
1631 static const char * jabber_user_dir_comments [] = {
1632 /* current comment from Jabber User Directory users.jabber.org */
1633 N_("Find a contact by entering the search criteria in the given fields. "
1634 "Note: Each field supports wild card searches (%)"),
1635 NULL
1636 };
1637 #endif
1638
1639 static void user_search_fields_result_cb(JabberStream *js, xmlnode *packet, gpointer data)
1640 {
1641 xmlnode *query, *x;
1642 const char *from, *type;
1643
1644 if(!(from = xmlnode_get_attrib(packet, "from")))
1645 return;
1646
1647 if(!(type = xmlnode_get_attrib(packet, "type")) || !strcmp(type, "error")) {
1648 char *msg = jabber_parse_error(js, packet);
1649
1650 if(!msg)
1651 msg = g_strdup(_("Unknown error"));
1652
1653 gaim_notify_error(js->gc, _("Directory Query Failed"),
1654 _("Could not query the directory server."), msg);
1655 g_free(msg);
1656
1657 return;
1658 }
1659
1660
1661 if(!(query = xmlnode_get_child(packet, "query")))
1662 return;
1663
1664 if((x = xmlnode_get_child_with_namespace(query, "x", "jabber:x:data"))) {
1665 jabber_x_data_request(js, x, user_search_x_data_cb, g_strdup(from));
1666 return;
1667 } else {
1668 struct user_search_info *usi;
1669 xmlnode *instnode;
1670 char *instructions = NULL;
1671 GaimRequestFields *fields;
1672 GaimRequestFieldGroup *group;
1673 GaimRequestField *field;
1674
1675 /* old skool */
1676 fields = gaim_request_fields_new();
1677 group = gaim_request_field_group_new(NULL);
1678 gaim_request_fields_add_group(fields, group);
1679
1680 if((instnode = xmlnode_get_child(query, "instructions")))
1681 {
1682 char *tmp = xmlnode_get_data(instnode);
1683
1684 if(tmp)
1685 {
1686 /* Try to translate the message (see static message
1687 list in jabber_user_dir_comments[]) */
1688 instructions = g_strdup_printf(_("Server Instructions: %s"), _(tmp));
1689 g_free(tmp);
1690 }
1691 }
1692
1693 if(!instructions)
1694 {
1695 instructions = g_strdup(_("Fill in one or more fields to search "
1696 "for any matching Jabber users."));
1697 }
1698
1699 if(xmlnode_get_child(query, "first")) {
1700 field = gaim_request_field_string_new("first", _("First Name"),
1701 NULL, FALSE);
1702 gaim_request_field_group_add_field(group, field);
1703 }
1704 if(xmlnode_get_child(query, "last")) {
1705 field = gaim_request_field_string_new("last", _("Last Name"),
1706 NULL, FALSE);
1707 gaim_request_field_group_add_field(group, field);
1708 }
1709 if(xmlnode_get_child(query, "nick")) {
1710 field = gaim_request_field_string_new("nick", _("Nickname"),
1711 NULL, FALSE);
1712 gaim_request_field_group_add_field(group, field);
1713 }
1714 if(xmlnode_get_child(query, "email")) {
1715 field = gaim_request_field_string_new("email", _("E-Mail Address"),
1716 NULL, FALSE);
1717 gaim_request_field_group_add_field(group, field);
1718 }
1719
1720 usi = g_new0(struct user_search_info, 1);
1721 usi->js = js;
1722 usi->directory_server = g_strdup(from);
1723
1724 gaim_request_fields(js->gc, _("Search for Jabber users"),
1725 _("Search for Jabber users"), instructions, fields,
1726 _("Search"), G_CALLBACK(user_search_cb),
1727 _("Cancel"), G_CALLBACK(user_search_cancel_cb), usi);
1728
1729 g_free(instructions);
1730 }
1731 }
1732
1733 static void jabber_user_search_ok(JabberStream *js, const char *directory)
1734 {
1735 JabberIq *iq;
1736
1737 /* XXX: should probably better validate the directory we're given */
1738 if(!directory || !*directory) {
1739 gaim_notify_error(js->gc, _("Invalid Directory"), _("Invalid Directory"), NULL);
1740 return;
1741 }
1742
1743 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:search");
1744 xmlnode_set_attrib(iq->node, "to", directory);
1745
1746 jabber_iq_set_callback(iq, user_search_fields_result_cb, NULL);
1747
1748 jabber_iq_send(iq);
1749 }
1750
1751 void jabber_user_search_begin(GaimPluginAction *action)
1752 {
1753 GaimConnection *gc = (GaimConnection *) action->context;
1754 JabberStream *js = gc->proto_data;
1755
1756 gaim_request_input(gc, _("Enter a User Directory"), _("Enter a User Directory"),
1757 _("Select a user directory to search"),
1758 js->user_directories ? js->user_directories->data : "users.jabber.org",
1759 FALSE, FALSE, NULL,
1760 _("Search Directory"), GAIM_CALLBACK(jabber_user_search_ok),
1761 _("Cancel"), NULL, js);
1762 }
1763
1764
1765