src/protocols/jabber/buddy.c

branch
cpw.khc.msnp14
changeset 20472
6a6d2ef151e6
parent 13912
463b4fa9f067
parent 20469
b2836a24d81e
child 20473
91e1b3a49d10
equal deleted inserted replaced
13912:463b4fa9f067 20472:6a6d2ef151e6
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 char *user_info;
536 char *cdata;
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 = g_strdup(gaim_account_get_user_info(gc->account))) != NULL)
547 x_vc_data = xmlnode_from_str(user_info, -1);
548 else
549 user_info = g_strdup("");
550
551 /*
552 * Set up GSLists for edit with labels from "template," data from user info
553 */
554 for(vc_tp = vcard_template_data; vc_tp->label != NULL; ++vc_tp) {
555 xmlnode *data_node;
556 if((vc_tp->label)[0] == '\0')
557 continue;
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 else
568 cdata = NULL;
569
570 if(strcmp(vc_tp->tag, "DESC") == 0) {
571 field = gaim_request_field_string_new(vc_tp->tag,
572 _(vc_tp->label), cdata,
573 TRUE);
574 } else {
575 field = gaim_request_field_string_new(vc_tp->tag,
576 _(vc_tp->label), cdata,
577 FALSE);
578 }
579
580 gaim_request_field_group_add_field(group, field);
581 }
582
583 if(x_vc_data != NULL)
584 xmlnode_free(x_vc_data);
585
586 g_free(user_info);
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 info_text = g_string_append(info_text, jbi->vcard_text);
692
693 gaim_notify_userinfo(jbi->js->gc, jbi->jid, info_text->str, NULL, NULL);
694
695 while(jbi->vcard_imgids) {
696 gaim_imgstore_unref(GPOINTER_TO_INT(jbi->vcard_imgids->data));
697 jbi->vcard_imgids = g_slist_delete_link(jbi->vcard_imgids, jbi->vcard_imgids);
698 }
699
700 g_string_free(info_text, TRUE);
701
702 gaim_timeout_remove(jbi->timeout_handle);
703 g_free(jbi->jid);
704 g_hash_table_destroy(jbi->resources);
705 g_free(jbi->vcard_text);
706 g_free(jbi);
707 }
708
709 static void jabber_buddy_info_remove_id(JabberBuddyInfo *jbi, const char *id)
710 {
711 GSList *l = jbi->ids;
712
713 if(!id)
714 return;
715
716 while(l) {
717 if(!strcmp(id, l->data)) {
718 jbi->ids = g_slist_remove(jbi->ids, l->data);
719 return;
720 }
721 l = l->next;
722 }
723 }
724
725 static void jabber_vcard_parse(JabberStream *js, xmlnode *packet, gpointer data)
726 {
727 const char *type, *id, *from;
728 JabberBuddy *jb;
729 GString *info_text;
730 char *bare_jid;
731 char *text;
732 xmlnode *vcard;
733 GaimBuddy *b;
734 JabberBuddyInfo *jbi = data;
735
736 from = xmlnode_get_attrib(packet, "from");
737 type = xmlnode_get_attrib(packet, "type");
738 id = xmlnode_get_attrib(packet, "id");
739
740 jabber_buddy_info_remove_id(jbi, id);
741
742 if(!jbi)
743 return;
744
745 if(!from)
746 return;
747
748 if(!(jb = jabber_buddy_find(js, from, TRUE)))
749 return;
750
751 /* XXX: handle the error case */
752
753 bare_jid = jabber_get_bare_jid(from);
754
755 b = gaim_find_buddy(js->gc->account, bare_jid);
756
757 info_text = g_string_new("");
758
759 if((vcard = xmlnode_get_child(packet, "vCard")) ||
760 (vcard = xmlnode_get_child_with_namespace(packet, "query", "vcard-temp"))) {
761 xmlnode *child;
762 for(child = vcard->child; child; child = child->next)
763 {
764 xmlnode *child2;
765
766 if(child->type != XMLNODE_TYPE_TAG)
767 continue;
768
769 text = xmlnode_get_data(child);
770 if(text && !strcmp(child->name, "FN")) {
771 g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
772 _("Full Name"), text);
773 } else if(!strcmp(child->name, "N")) {
774 for(child2 = child->child; child2; child2 = child2->next)
775 {
776 char *text2;
777
778 if(child2->type != XMLNODE_TYPE_TAG)
779 continue;
780
781 text2 = xmlnode_get_data(child2);
782 if(text2 && !strcmp(child2->name, "FAMILY")) {
783 g_string_append_printf(info_text,
784 "<b>%s:</b> %s<br/>",
785 _("Family Name"), text2);
786 } else if(text2 && !strcmp(child2->name, "GIVEN")) {
787 g_string_append_printf(info_text,
788 "<b>%s:</b> %s<br/>",
789 _("Given Name"), text2);
790 } else if(text2 && !strcmp(child2->name, "MIDDLE")) {
791 g_string_append_printf(info_text,
792 "<b>%s:</b> %s<br/>",
793 _("Middle Name"), text2);
794 }
795 g_free(text2);
796 }
797 } else if(text && !strcmp(child->name, "NICKNAME")) {
798 serv_got_alias(js->gc, from, text);
799 if(b) {
800 gaim_blist_node_set_string((GaimBlistNode*)b, "servernick", text);
801 }
802 g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
803 _("Nickname"), text);
804 } else if(text && !strcmp(child->name, "BDAY")) {
805 g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
806 _("Birthday"), text);
807 } else if(!strcmp(child->name, "ADR")) {
808 gboolean address_line_added = FALSE;
809
810 for(child2 = child->child; child2; child2 = child2->next)
811 {
812 char *text2;
813
814 if(child2->type != XMLNODE_TYPE_TAG)
815 continue;
816
817 text2 = xmlnode_get_data(child2);
818 if (text2 == NULL)
819 continue;
820
821 /* We do this here so that it's not added if all the child
822 * elements are empty. */
823 if (!address_line_added)
824 {
825 g_string_append_printf(info_text, "<b>%s:</b><br/>",
826 _("Address"));
827 address_line_added = TRUE;
828 }
829
830 if(!strcmp(child2->name, "POBOX")) {
831 g_string_append_printf(info_text,
832 "&nbsp;<b>%s:</b> %s<br/>",
833 _("P.O. Box"), text2);
834 } else if(!strcmp(child2->name, "EXTADR")) {
835 g_string_append_printf(info_text,
836 "&nbsp;<b>%s:</b> %s<br/>",
837 _("Extended Address"), text2);
838 } else if(!strcmp(child2->name, "STREET")) {
839 g_string_append_printf(info_text,
840 "&nbsp;<b>%s:</b> %s<br/>",
841 _("Street Address"), text2);
842 } else if(!strcmp(child2->name, "LOCALITY")) {
843 g_string_append_printf(info_text,
844 "&nbsp;<b>%s:</b> %s<br/>",
845 _("Locality"), text2);
846 } else if(!strcmp(child2->name, "REGION")) {
847 g_string_append_printf(info_text,
848 "&nbsp;<b>%s:</b> %s<br/>",
849 _("Region"), text2);
850 } else if(!strcmp(child2->name, "PCODE")) {
851 g_string_append_printf(info_text,
852 "&nbsp;<b>%s:</b> %s<br/>",
853 _("Postal Code"), text2);
854 } else if(!strcmp(child2->name, "CTRY")
855 || !strcmp(child2->name, "COUNTRY")) {
856 g_string_append_printf(info_text,
857 "&nbsp;<b>%s:</b> %s<br/>",
858 _("Country"), text2);
859 }
860 g_free(text2);
861 }
862 } else if(!strcmp(child->name, "TEL")) {
863 char *number;
864 if((child2 = xmlnode_get_child(child, "NUMBER"))) {
865 /* show what kind of number it is */
866 number = xmlnode_get_data(child2);
867 if(number) {
868 g_string_append_printf(info_text,
869 "<b>%s:</b> %s<br/>", _("Telephone"), number);
870 g_free(number);
871 }
872 } else if((number = xmlnode_get_data(child))) {
873 /* lots of clients (including gaim) do this, but it's
874 * out of spec */
875 g_string_append_printf(info_text,
876 "<b>%s:</b> %s<br/>", _("Telephone"), number);
877 g_free(number);
878 }
879 } else if(!strcmp(child->name, "EMAIL")) {
880 char *userid;
881 if((child2 = xmlnode_get_child(child, "USERID"))) {
882 /* show what kind of email it is */
883 userid = xmlnode_get_data(child2);
884 if(userid) {
885 g_string_append_printf(info_text,
886 "<b>%s:</b> <a href='mailto:%s'>%s</a><br/>",
887 _("E-Mail"), userid, userid);
888 g_free(userid);
889 }
890 } else if((userid = xmlnode_get_data(child))) {
891 /* lots of clients (including gaim) do this, but it's
892 * out of spec */
893 g_string_append_printf(info_text,
894 "<b>%s:</b> <a href='mailto:%s'>%s</a><br/>",
895 _("E-Mail"), userid, userid);
896 g_free(userid);
897 }
898 } else if(!strcmp(child->name, "ORG")) {
899 for(child2 = child->child; child2; child2 = child2->next)
900 {
901 char *text2;
902
903 if(child2->type != XMLNODE_TYPE_TAG)
904 continue;
905
906 text2 = xmlnode_get_data(child2);
907 if(text2 && !strcmp(child2->name, "ORGNAME")) {
908 g_string_append_printf(info_text,
909 "<b>%s:</b> %s<br/>",
910 _("Organization Name"), text2);
911 } else if(text2 && !strcmp(child2->name, "ORGUNIT")) {
912 g_string_append_printf(info_text,
913 "<b>%s:</b> %s<br/>",
914 _("Organization Unit"), text2);
915 }
916 g_free(text2);
917 }
918 } else if(text && !strcmp(child->name, "TITLE")) {
919 g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
920 _("Title"), text);
921 } else if(text && !strcmp(child->name, "ROLE")) {
922 g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
923 _("Role"), text);
924 } else if(text && !strcmp(child->name, "DESC")) {
925 g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
926 _("Description"), text);
927 } else if(!strcmp(child->name, "PHOTO") ||
928 !strcmp(child->name, "LOGO")) {
929 char *bintext = NULL;
930 xmlnode *binval;
931
932 if( ((binval = xmlnode_get_child(child, "BINVAL")) &&
933 (bintext = xmlnode_get_data(binval))) ||
934 (bintext = xmlnode_get_data(child))) {
935 gsize size;
936 guchar *data;
937 int i;
938 unsigned char hashval[20];
939 char *p, hash[41];
940 gboolean photo = (strcmp(child->name, "PHOTO") == 0);
941
942 data = gaim_base64_decode(bintext, &size);
943
944 jbi->vcard_imgids = g_slist_prepend(jbi->vcard_imgids, GINT_TO_POINTER(gaim_imgstore_add(data, size, "logo.png")));
945 g_string_append_printf(info_text,
946 "<b>%s:</b> <img id='%d'><br/>",
947 photo ? _("Photo") : _("Logo"),
948 GPOINTER_TO_INT(jbi->vcard_imgids->data));
949
950 gaim_buddy_icons_set_for_user(js->gc->account, bare_jid,
951 data, size);
952
953 gaim_cipher_digest_region("sha1", (guchar *)data, size,
954 sizeof(hashval), hashval, NULL);
955 p = hash;
956 for(i=0; i<20; i++, p+=2)
957 snprintf(p, 3, "%02x", hashval[i]);
958 gaim_blist_node_set_string((GaimBlistNode*)b, "avatar_hash", hash);
959
960 g_free(data);
961 g_free(bintext);
962 }
963 }
964 g_free(text);
965 }
966 }
967
968 jbi->vcard_text = gaim_strdup_withhtml(info_text->str);
969 g_string_free(info_text, TRUE);
970 g_free(bare_jid);
971
972 jabber_buddy_info_show_if_ready(jbi);
973 }
974
975
976 static void jabber_buddy_info_resource_free(gpointer data)
977 {
978 JabberBuddyInfoResource *jbri = data;
979 g_free(jbri);
980 }
981
982 static void jabber_version_parse(JabberStream *js, xmlnode *packet, gpointer data)
983 {
984 JabberBuddyInfo *jbi = data;
985 const char *type, *id, *from;
986 xmlnode *query;
987 char *resource_name;
988
989 g_return_if_fail(jbi != NULL);
990
991 type = xmlnode_get_attrib(packet, "type");
992 id = xmlnode_get_attrib(packet, "id");
993 from = xmlnode_get_attrib(packet, "from");
994
995 jabber_buddy_info_remove_id(jbi, id);
996
997 if(!from)
998 return;
999
1000 resource_name = jabber_get_resource(from);
1001
1002 if(resource_name) {
1003 if(type && !strcmp(type, "result")) {
1004 if((query = xmlnode_get_child(packet, "query"))) {
1005 JabberBuddyResource *jbr = jabber_buddy_find_resource(jbi->jb, resource_name);
1006 if(jbr) {
1007 xmlnode *node;
1008 if((node = xmlnode_get_child(query, "name"))) {
1009 jbr->client.name = xmlnode_get_data(node);
1010 }
1011 if((node = xmlnode_get_child(query, "version"))) {
1012 jbr->client.version = xmlnode_get_data(node);
1013 }
1014 if((node = xmlnode_get_child(query, "os"))) {
1015 jbr->client.os = xmlnode_get_data(node);
1016 }
1017 }
1018 }
1019 }
1020 g_free(resource_name);
1021 }
1022
1023 jabber_buddy_info_show_if_ready(jbi);
1024 }
1025
1026 static void jabber_last_parse(JabberStream *js, xmlnode *packet, gpointer data)
1027 {
1028 JabberBuddyInfo *jbi = data;
1029 xmlnode *query;
1030 char *resource_name;
1031 const char *type, *id, *from, *seconds;
1032
1033 g_return_if_fail(jbi != NULL);
1034
1035 type = xmlnode_get_attrib(packet, "type");
1036 id = xmlnode_get_attrib(packet, "id");
1037 from = xmlnode_get_attrib(packet, "from");
1038
1039 jabber_buddy_info_remove_id(jbi, id);
1040
1041 if(!from)
1042 return;
1043
1044 resource_name = jabber_get_resource(from);
1045
1046 if(resource_name) {
1047 if(type && !strcmp(type, "result")) {
1048 if((query = xmlnode_get_child(packet, "query"))) {
1049 seconds = xmlnode_get_attrib(query, "seconds");
1050 if(seconds) {
1051 char *end = NULL;
1052 long sec = strtol(seconds, &end, 10);
1053 if(end != seconds) {
1054 JabberBuddyInfoResource *jbir = g_hash_table_lookup(jbi->resources, resource_name);
1055 if(jbir) {
1056 jbir->idle_seconds = sec;
1057 }
1058 }
1059 }
1060 }
1061 }
1062 g_free(resource_name);
1063 }
1064
1065 jabber_buddy_info_show_if_ready(jbi);
1066 }
1067
1068 static gboolean jabber_buddy_get_info_timeout(gpointer data)
1069 {
1070 JabberBuddyInfo *jbi = data;
1071
1072 /* remove the pending callbacks */
1073 while(jbi->ids) {
1074 char *id = jbi->ids->data;
1075 jabber_iq_remove_callback_by_id(jbi->js, id);
1076 g_free(id);
1077 jbi->ids = g_slist_remove(jbi->ids, id);
1078 }
1079
1080 jbi->timeout_handle = 0;
1081
1082 jabber_buddy_info_show_if_ready(jbi);
1083
1084 return FALSE;
1085 }
1086
1087 static void jabber_buddy_get_info_for_jid(JabberStream *js, const char *jid)
1088 {
1089 JabberIq *iq;
1090 xmlnode *vcard;
1091 GList *resources;
1092 JabberBuddy *jb;
1093 JabberBuddyInfo *jbi;
1094
1095 jb = jabber_buddy_find(js, jid, TRUE);
1096
1097 /* invalid JID */
1098 if(!jb)
1099 return;
1100
1101 jbi = g_new0(JabberBuddyInfo, 1);
1102 jbi->jid = g_strdup(jid);
1103 jbi->js = js;
1104 jbi->jb = jb;
1105 jbi->resources = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, jabber_buddy_info_resource_free);
1106
1107 iq = jabber_iq_new(js, JABBER_IQ_GET);
1108
1109 xmlnode_set_attrib(iq->node, "to", jid);
1110 vcard = xmlnode_new_child(iq->node, "vCard");
1111 xmlnode_set_namespace(vcard, "vcard-temp");
1112
1113 jabber_iq_set_callback(iq, jabber_vcard_parse, jbi);
1114 jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1115
1116 jabber_iq_send(iq);
1117
1118 for(resources = jb->resources; resources; resources = resources->next)
1119 {
1120 JabberBuddyResource *jbr = resources->data;
1121 JabberBuddyInfoResource *jbir = g_new0(JabberBuddyInfoResource, 1);
1122 char *full_jid;
1123 if(strrchr(jid, '/')) {
1124 full_jid = g_strdup(jid);
1125 } else {
1126 full_jid = g_strdup_printf("%s/%s", jid, jbr->name);
1127 }
1128
1129 g_hash_table_insert(jbi->resources, g_strdup(jbr->name), jbir);
1130
1131 if(!jbr->client.name) {
1132 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:version");
1133 xmlnode_set_attrib(iq->node, "to", full_jid);
1134 jabber_iq_set_callback(iq, jabber_version_parse, jbi);
1135 jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1136 jabber_iq_send(iq);
1137 }
1138
1139 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:last");
1140 xmlnode_set_attrib(iq->node, "to", full_jid);
1141 jabber_iq_set_callback(iq, jabber_last_parse, jbi);
1142 jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1143 jabber_iq_send(iq);
1144
1145 g_free(full_jid);
1146 }
1147
1148 jbi->timeout_handle = gaim_timeout_add(30000, jabber_buddy_get_info_timeout, jbi);
1149 }
1150
1151 void jabber_buddy_get_info(GaimConnection *gc, const char *who)
1152 {
1153 JabberStream *js = gc->proto_data;
1154 char *bare_jid = jabber_get_bare_jid(who);
1155
1156 if(bare_jid) {
1157 jabber_buddy_get_info_for_jid(js, bare_jid);
1158 g_free(bare_jid);
1159 }
1160 }
1161
1162 void jabber_buddy_get_info_chat(GaimConnection *gc, int id,
1163 const char *resource)
1164 {
1165 JabberStream *js = gc->proto_data;
1166 JabberChat *chat = jabber_chat_find_by_id(js, id);
1167 char *full_jid;
1168
1169 if(!chat)
1170 return;
1171
1172 full_jid = g_strdup_printf("%s@%s/%s", chat->room, chat->server, resource);
1173 jabber_buddy_get_info_for_jid(js, full_jid);
1174 g_free(full_jid);
1175 }
1176
1177
1178 static void jabber_buddy_set_invisibility(JabberStream *js, const char *who,
1179 gboolean invisible)
1180 {
1181 GaimPresence *gpresence;
1182 GaimAccount *account;
1183 GaimStatus *status;
1184 JabberBuddy *jb = jabber_buddy_find(js, who, TRUE);
1185 xmlnode *presence;
1186 JabberBuddyState state;
1187 const char *msg;
1188 int priority;
1189
1190 account = gaim_connection_get_account(js->gc);
1191 gpresence = gaim_account_get_presence(account);
1192 status = gaim_presence_get_active_status(gpresence);
1193
1194 gaim_status_to_jabber(status, &state, &msg, &priority);
1195 presence = jabber_presence_create(state, msg, priority);
1196
1197 xmlnode_set_attrib(presence, "to", who);
1198 if(invisible) {
1199 xmlnode_set_attrib(presence, "type", "invisible");
1200 jb->invisible |= JABBER_INVIS_BUDDY;
1201 } else {
1202 jb->invisible &= ~JABBER_INVIS_BUDDY;
1203 }
1204
1205 jabber_send(js, presence);
1206 xmlnode_free(presence);
1207 }
1208
1209 static void jabber_buddy_make_invisible(GaimBlistNode *node, gpointer data)
1210 {
1211 GaimBuddy *buddy;
1212 GaimConnection *gc;
1213 JabberStream *js;
1214
1215 g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
1216
1217 buddy = (GaimBuddy *) node;
1218 gc = gaim_account_get_connection(buddy->account);
1219 js = gc->proto_data;
1220
1221 jabber_buddy_set_invisibility(js, buddy->name, TRUE);
1222 }
1223
1224 static void jabber_buddy_make_visible(GaimBlistNode *node, gpointer data)
1225 {
1226 GaimBuddy *buddy;
1227 GaimConnection *gc;
1228 JabberStream *js;
1229
1230 g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
1231
1232 buddy = (GaimBuddy *) node;
1233 gc = gaim_account_get_connection(buddy->account);
1234 js = gc->proto_data;
1235
1236 jabber_buddy_set_invisibility(js, buddy->name, FALSE);
1237 }
1238
1239 static void jabber_buddy_cancel_presence_notification(GaimBlistNode *node,
1240 gpointer data)
1241 {
1242 GaimBuddy *buddy;
1243 GaimConnection *gc;
1244 JabberStream *js;
1245
1246 g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
1247
1248 buddy = (GaimBuddy *) node;
1249 gc = gaim_account_get_connection(buddy->account);
1250 js = gc->proto_data;
1251
1252 /* I wonder if we should prompt the user before doing this */
1253 jabber_presence_subscription_set(js, buddy->name, "unsubscribed");
1254 }
1255
1256 static void jabber_buddy_rerequest_auth(GaimBlistNode *node, gpointer data)
1257 {
1258 GaimBuddy *buddy;
1259 GaimConnection *gc;
1260 JabberStream *js;
1261
1262 g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
1263
1264 buddy = (GaimBuddy *) node;
1265 gc = gaim_account_get_connection(buddy->account);
1266 js = gc->proto_data;
1267
1268 jabber_presence_subscription_set(js, buddy->name, "subscribe");
1269 }
1270
1271
1272 static void jabber_buddy_unsubscribe(GaimBlistNode *node, gpointer data)
1273 {
1274 GaimBuddy *buddy;
1275 GaimConnection *gc;
1276 JabberStream *js;
1277
1278 g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
1279
1280 buddy = (GaimBuddy *) node;
1281 gc = gaim_account_get_connection(buddy->account);
1282 js = gc->proto_data;
1283
1284 jabber_presence_subscription_set(js, buddy->name, "unsubscribe");
1285 }
1286
1287
1288 static GList *jabber_buddy_menu(GaimBuddy *buddy)
1289 {
1290 GaimConnection *gc = gaim_account_get_connection(buddy->account);
1291 JabberStream *js = gc->proto_data;
1292 JabberBuddy *jb = jabber_buddy_find(js, buddy->name, TRUE);
1293
1294 GList *m = NULL;
1295 GaimMenuAction *act;
1296
1297 if(!jb)
1298 return m;
1299
1300 /* XXX: fix the NOT ME below */
1301
1302 if(js->protocol_version == JABBER_PROTO_0_9 /* && NOT ME */) {
1303 if(jb->invisible & JABBER_INVIS_BUDDY) {
1304 act = gaim_menu_action_new(_("Un-hide From"),
1305 GAIM_CALLBACK(jabber_buddy_make_visible),
1306 NULL, NULL);
1307 } else {
1308 act = gaim_menu_action_new(_("Temporarily Hide From"),
1309 GAIM_CALLBACK(jabber_buddy_make_invisible),
1310 NULL, NULL);
1311 }
1312 m = g_list_append(m, act);
1313 }
1314
1315 if(jb->subscription & JABBER_SUB_FROM /* && NOT ME */) {
1316 act = gaim_menu_action_new(_("Cancel Presence Notification"),
1317 GAIM_CALLBACK(jabber_buddy_cancel_presence_notification),
1318 NULL, NULL);
1319 m = g_list_append(m, act);
1320 }
1321
1322 if(!(jb->subscription & JABBER_SUB_TO)) {
1323 act = gaim_menu_action_new(_("(Re-)Request authorization"),
1324 GAIM_CALLBACK(jabber_buddy_rerequest_auth),
1325 NULL, NULL);
1326 m = g_list_append(m, act);
1327
1328 } else /* if(NOT ME) */{
1329
1330 /* shouldn't this just happen automatically when the buddy is
1331 removed? */
1332 act = gaim_menu_action_new(_("Unsubscribe"),
1333 GAIM_CALLBACK(jabber_buddy_unsubscribe),
1334 NULL, NULL);
1335 m = g_list_append(m, act);
1336 }
1337
1338 return m;
1339 }
1340
1341 GList *
1342 jabber_blist_node_menu(GaimBlistNode *node)
1343 {
1344 if(GAIM_BLIST_NODE_IS_BUDDY(node)) {
1345 return jabber_buddy_menu((GaimBuddy *) node);
1346 } else {
1347 return NULL;
1348 }
1349 }
1350
1351
1352 const char *
1353 jabber_buddy_state_get_name(JabberBuddyState state)
1354 {
1355 switch(state) {
1356 case JABBER_BUDDY_STATE_UNKNOWN:
1357 return _("Unknown");
1358 case JABBER_BUDDY_STATE_ERROR:
1359 return _("Error");
1360 case JABBER_BUDDY_STATE_UNAVAILABLE:
1361 return _("Offline");
1362 case JABBER_BUDDY_STATE_ONLINE:
1363 return _("Available");
1364 case JABBER_BUDDY_STATE_CHAT:
1365 return _("Chatty");
1366 case JABBER_BUDDY_STATE_AWAY:
1367 return _("Away");
1368 case JABBER_BUDDY_STATE_XA:
1369 return _("Extended Away");
1370 case JABBER_BUDDY_STATE_DND:
1371 return _("Do Not Disturb");
1372 }
1373
1374 return _("Unknown");
1375 }
1376
1377 JabberBuddyState jabber_buddy_status_id_get_state(const char *id) {
1378 if(!id)
1379 return JABBER_BUDDY_STATE_UNKNOWN;
1380 if(!strcmp(id, "available"))
1381 return JABBER_BUDDY_STATE_ONLINE;
1382 if(!strcmp(id, "freeforchat"))
1383 return JABBER_BUDDY_STATE_CHAT;
1384 if(!strcmp(id, "away"))
1385 return JABBER_BUDDY_STATE_AWAY;
1386 if(!strcmp(id, "extended_away"))
1387 return JABBER_BUDDY_STATE_XA;
1388 if(!strcmp(id, "dnd"))
1389 return JABBER_BUDDY_STATE_DND;
1390 if(!strcmp(id, "offline"))
1391 return JABBER_BUDDY_STATE_UNAVAILABLE;
1392 if(!strcmp(id, "error"))
1393 return JABBER_BUDDY_STATE_ERROR;
1394
1395 return JABBER_BUDDY_STATE_UNKNOWN;
1396 }
1397
1398 JabberBuddyState jabber_buddy_show_get_state(const char *id) {
1399 if(!id)
1400 return JABBER_BUDDY_STATE_UNKNOWN;
1401 if(!strcmp(id, "available"))
1402 return JABBER_BUDDY_STATE_ONLINE;
1403 if(!strcmp(id, "chat"))
1404 return JABBER_BUDDY_STATE_CHAT;
1405 if(!strcmp(id, "away"))
1406 return JABBER_BUDDY_STATE_AWAY;
1407 if(!strcmp(id, "xa"))
1408 return JABBER_BUDDY_STATE_XA;
1409 if(!strcmp(id, "dnd"))
1410 return JABBER_BUDDY_STATE_DND;
1411 if(!strcmp(id, "offline"))
1412 return JABBER_BUDDY_STATE_UNAVAILABLE;
1413 if(!strcmp(id, "error"))
1414 return JABBER_BUDDY_STATE_ERROR;
1415
1416 return JABBER_BUDDY_STATE_UNKNOWN;
1417 }
1418
1419 const char *jabber_buddy_state_get_show(JabberBuddyState state) {
1420 switch(state) {
1421 case JABBER_BUDDY_STATE_CHAT:
1422 return "chat";
1423 case JABBER_BUDDY_STATE_AWAY:
1424 return "away";
1425 case JABBER_BUDDY_STATE_XA:
1426 return "xa";
1427 case JABBER_BUDDY_STATE_DND:
1428 return "dnd";
1429 case JABBER_BUDDY_STATE_ONLINE:
1430 return "available";
1431 case JABBER_BUDDY_STATE_UNKNOWN:
1432 case JABBER_BUDDY_STATE_ERROR:
1433 return NULL;
1434 case JABBER_BUDDY_STATE_UNAVAILABLE:
1435 return "offline";
1436 }
1437 return NULL;
1438 }
1439
1440 const char *jabber_buddy_state_get_status_id(JabberBuddyState state) {
1441 switch(state) {
1442 case JABBER_BUDDY_STATE_CHAT:
1443 return "freeforchat";
1444 case JABBER_BUDDY_STATE_AWAY:
1445 return "away";
1446 case JABBER_BUDDY_STATE_XA:
1447 return "extended_away";
1448 case JABBER_BUDDY_STATE_DND:
1449 return "dnd";
1450 case JABBER_BUDDY_STATE_ONLINE:
1451 return "available";
1452 case JABBER_BUDDY_STATE_UNKNOWN:
1453 return "available";
1454 case JABBER_BUDDY_STATE_ERROR:
1455 return "error";
1456 case JABBER_BUDDY_STATE_UNAVAILABLE:
1457 return "offline";
1458 }
1459 return NULL;
1460 }
1461
1462 static void user_search_result_add_buddy_cb(GaimConnection *gc, GList *row, void *user_data)
1463 {
1464 /* XXX find out the jid */
1465 gaim_blist_request_add_buddy(gaim_connection_get_account(gc),
1466 g_list_nth_data(row, 0), NULL, NULL);
1467 }
1468
1469 static void user_search_result_cb(JabberStream *js, xmlnode *packet, gpointer data)
1470 {
1471 GaimNotifySearchResults *results;
1472 GaimNotifySearchColumn *column;
1473 xmlnode *x, *query, *item, *field;
1474
1475 /* XXX error checking? */
1476 if(!(query = xmlnode_get_child(packet, "query")))
1477 return;
1478
1479 results = gaim_notify_searchresults_new();
1480 if((x = xmlnode_get_child_with_namespace(query, "x", "jabber:x:data"))) {
1481 xmlnode *reported;
1482 gaim_debug_info("jabber", "new-skool\n");
1483 if((reported = xmlnode_get_child(x, "reported"))) {
1484 xmlnode *field = xmlnode_get_child(reported, "field");
1485 while(field) {
1486 /* XXX keep track of this order, use it below */
1487 const char *var = xmlnode_get_attrib(field, "var");
1488 const char *label = xmlnode_get_attrib(field, "label");
1489 if(var) {
1490 column = gaim_notify_searchresults_column_new(label ? label : var);
1491 gaim_notify_searchresults_column_add(results, column);
1492 }
1493 field = xmlnode_get_next_twin(field);
1494 }
1495 }
1496 item = xmlnode_get_child(x, "item");
1497 while(item) {
1498 GList *row = NULL;
1499 field = xmlnode_get_child(item, "field");
1500 while(field) {
1501 xmlnode *valuenode = xmlnode_get_child(field, "value");
1502 if(valuenode) {
1503 char *value = xmlnode_get_data(valuenode);
1504 row = g_list_append(row, value);
1505 }
1506 field = xmlnode_get_next_twin(field);
1507 }
1508 gaim_notify_searchresults_row_add(results, row);
1509
1510 item = xmlnode_get_next_twin(item);
1511 }
1512 } else {
1513 /* old skool */
1514 gaim_debug_info("jabber", "old-skool\n");
1515
1516 column = gaim_notify_searchresults_column_new(_("JID"));
1517 gaim_notify_searchresults_column_add(results, column);
1518 column = gaim_notify_searchresults_column_new(_("First Name"));
1519 gaim_notify_searchresults_column_add(results, column);
1520 column = gaim_notify_searchresults_column_new(_("Last Name"));
1521 gaim_notify_searchresults_column_add(results, column);
1522 column = gaim_notify_searchresults_column_new(_("Nickname"));
1523 gaim_notify_searchresults_column_add(results, column);
1524 column = gaim_notify_searchresults_column_new(_("E-Mail"));
1525 gaim_notify_searchresults_column_add(results, column);
1526
1527 for(item = xmlnode_get_child(query, "item"); item; item = xmlnode_get_next_twin(item)) {
1528 const char *jid;
1529 xmlnode *node;
1530 GList *row = NULL;
1531
1532 if(!(jid = xmlnode_get_attrib(item, "jid")))
1533 continue;
1534
1535 row = g_list_append(row, g_strdup(jid));
1536 node = xmlnode_get_child(item, "first");
1537 row = g_list_append(row, node ? xmlnode_get_data(node) : NULL);
1538 node = xmlnode_get_child(item, "last");
1539 row = g_list_append(row, node ? xmlnode_get_data(node) : NULL);
1540 node = xmlnode_get_child(item, "nick");
1541 row = g_list_append(row, node ? xmlnode_get_data(node) : NULL);
1542 node = xmlnode_get_child(item, "email");
1543 row = g_list_append(row, node ? xmlnode_get_data(node) : NULL);
1544 gaim_debug_info("jabber", "row=%d\n", row);
1545 gaim_notify_searchresults_row_add(results, row);
1546 }
1547 }
1548
1549 gaim_notify_searchresults_button_add(results, GAIM_NOTIFY_BUTTON_ADD,
1550 user_search_result_add_buddy_cb);
1551
1552 gaim_notify_searchresults(js->gc, NULL, NULL, _("The following are the results of your search"), results, NULL, NULL);
1553 }
1554
1555 static void user_search_x_data_cb(JabberStream *js, xmlnode *result, gpointer data)
1556 {
1557 xmlnode *query;
1558 JabberIq *iq;
1559 char *dir_server = data;
1560
1561 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:search");
1562 query = xmlnode_get_child(iq->node, "query");
1563
1564 xmlnode_insert_child(query, result);
1565
1566 jabber_iq_set_callback(iq, user_search_result_cb, NULL);
1567 xmlnode_set_attrib(iq->node, "to", dir_server);
1568 jabber_iq_send(iq);
1569 g_free(dir_server);
1570 }
1571
1572 struct user_search_info {
1573 JabberStream *js;
1574 char *directory_server;
1575 };
1576
1577 static void user_search_cancel_cb(struct user_search_info *usi, GaimRequestFields *fields)
1578 {
1579 g_free(usi->directory_server);
1580 g_free(usi);
1581 }
1582
1583 static void user_search_cb(struct user_search_info *usi, GaimRequestFields *fields)
1584 {
1585 JabberStream *js = usi->js;
1586 JabberIq *iq;
1587 xmlnode *query;
1588 GList *groups, *flds;
1589
1590 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:search");
1591 query = xmlnode_get_child(iq->node, "query");
1592
1593 for(groups = gaim_request_fields_get_groups(fields); groups; groups = groups->next) {
1594 for(flds = gaim_request_field_group_get_fields(groups->data);
1595 flds; flds = flds->next) {
1596 GaimRequestField *field = flds->data;
1597 const char *id = gaim_request_field_get_id(field);
1598 const char *value = gaim_request_field_string_get_value(field);
1599
1600 if(value && (!strcmp(id, "first") || !strcmp(id, "last") || !strcmp(id, "nick") || !strcmp(id, "email"))) {
1601 xmlnode *y = xmlnode_new_child(query, id);
1602 xmlnode_insert_data(y, value, -1);
1603 }
1604 }
1605 }
1606
1607 jabber_iq_set_callback(iq, user_search_result_cb, NULL);
1608 xmlnode_set_attrib(iq->node, "to", usi->directory_server);
1609 jabber_iq_send(iq);
1610
1611 g_free(usi->directory_server);
1612 g_free(usi);
1613 }
1614
1615 #if 0
1616 /* This is for gettext only -- it will see this even though there's an #if 0. */
1617
1618 /*
1619 * An incomplete list of server generated original language search
1620 * comments for Jabber User Directories
1621 *
1622 * See discussion thread "Search comment for Jabber is not translatable"
1623 * in gaim-i18n@lists.sourceforge.net (March 2006)
1624 */
1625 static const char * jabber_user_dir_comments [] = {
1626 /* current comment from Jabber User Directory users.jabber.org */
1627 N_("Find a contact by entering the search criteria in the given fields. "
1628 "Note: Each field supports wild card searches (%)"),
1629 NULL
1630 };
1631 #endif
1632
1633 static void user_search_fields_result_cb(JabberStream *js, xmlnode *packet, gpointer data)
1634 {
1635 xmlnode *query, *x;
1636 const char *from, *type;
1637
1638 if(!(from = xmlnode_get_attrib(packet, "from")))
1639 return;
1640
1641 if(!(type = xmlnode_get_attrib(packet, "type")) || !strcmp(type, "error")) {
1642 char *msg = jabber_parse_error(js, packet);
1643
1644 if(!msg)
1645 msg = g_strdup(_("Unknown error"));
1646
1647 gaim_notify_error(js->gc, _("Directory Query Failed"),
1648 _("Could not query the directory server."), msg);
1649 g_free(msg);
1650
1651 return;
1652 }
1653
1654
1655 if(!(query = xmlnode_get_child(packet, "query")))
1656 return;
1657
1658 if((x = xmlnode_get_child_with_namespace(query, "x", "jabber:x:data"))) {
1659 jabber_x_data_request(js, x, user_search_x_data_cb, g_strdup(from));
1660 return;
1661 } else {
1662 struct user_search_info *usi;
1663 xmlnode *instnode;
1664 char *instructions = NULL;
1665 GaimRequestFields *fields;
1666 GaimRequestFieldGroup *group;
1667 GaimRequestField *field;
1668
1669 /* old skool */
1670 fields = gaim_request_fields_new();
1671 group = gaim_request_field_group_new(NULL);
1672 gaim_request_fields_add_group(fields, group);
1673
1674 if((instnode = xmlnode_get_child(query, "instructions")))
1675 {
1676 char *tmp = xmlnode_get_data(instnode);
1677
1678 if(tmp)
1679 {
1680 /* Try to translate the message (see static message
1681 list in jabber_user_dir_comments[]) */
1682 instructions = g_strdup_printf(_("Server Instructions: %s"), _(tmp));
1683 g_free(tmp);
1684 }
1685 }
1686
1687 if(!instructions)
1688 {
1689 instructions = g_strdup(_("Fill in one or more fields to search "
1690 "for any matching Jabber users."));
1691 }
1692
1693 if(xmlnode_get_child(query, "first")) {
1694 field = gaim_request_field_string_new("first", _("First Name"),
1695 NULL, FALSE);
1696 gaim_request_field_group_add_field(group, field);
1697 }
1698 if(xmlnode_get_child(query, "last")) {
1699 field = gaim_request_field_string_new("last", _("Last Name"),
1700 NULL, FALSE);
1701 gaim_request_field_group_add_field(group, field);
1702 }
1703 if(xmlnode_get_child(query, "nick")) {
1704 field = gaim_request_field_string_new("nick", _("Nickname"),
1705 NULL, FALSE);
1706 gaim_request_field_group_add_field(group, field);
1707 }
1708 if(xmlnode_get_child(query, "email")) {
1709 field = gaim_request_field_string_new("email", _("E-Mail Address"),
1710 NULL, FALSE);
1711 gaim_request_field_group_add_field(group, field);
1712 }
1713
1714 usi = g_new0(struct user_search_info, 1);
1715 usi->js = js;
1716 usi->directory_server = g_strdup(from);
1717
1718 gaim_request_fields(js->gc, _("Search for Jabber users"),
1719 _("Search for Jabber users"), instructions, fields,
1720 _("Search"), G_CALLBACK(user_search_cb),
1721 _("Cancel"), G_CALLBACK(user_search_cancel_cb), usi);
1722
1723 g_free(instructions);
1724 }
1725 }
1726
1727 static void jabber_user_search_ok(JabberStream *js, const char *directory)
1728 {
1729 JabberIq *iq;
1730
1731 /* XXX: should probably better validate the directory we're given */
1732 if(!directory || !*directory) {
1733 gaim_notify_error(js->gc, _("Invalid Directory"), _("Invalid Directory"), NULL);
1734 return;
1735 }
1736
1737 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:search");
1738 xmlnode_set_attrib(iq->node, "to", directory);
1739
1740 jabber_iq_set_callback(iq, user_search_fields_result_cb, NULL);
1741
1742 jabber_iq_send(iq);
1743 }
1744
1745 void jabber_user_search_begin(GaimPluginAction *action)
1746 {
1747 GaimConnection *gc = (GaimConnection *) action->context;
1748 JabberStream *js = gc->proto_data;
1749
1750 gaim_request_input(gc, _("Enter a User Directory"), _("Enter a User Directory"),
1751 _("Select a user directory to search"),
1752 js->user_directories ? js->user_directories->data : "users.jabber.org",
1753 FALSE, FALSE, NULL,
1754 _("Search Directory"), GAIM_CALLBACK(jabber_user_search_ok),
1755 _("Cancel"), NULL, js);
1756 }
1757
1758
1759

mercurial