Mercurial > pidgin
diff src/protocols/jabber/buddy.c @ 7014:67c4e9d39242
[gaim-migrate @ 7577]
Here it is, the bulk of the new Jabber prpl.
Left to do:
- Implement registration
- Implement password changing
- Keep track of conversation threads (since I apparently have to)
- Fix the bugs that always magically appear in code after I commit
committer: Tailor Script <tailor@pidgin.im>
author | Nathan Walp <nwalp@pidgin.im> |
---|---|
date | Mon, 29 Sep 2003 15:23:19 +0000 |
parents | |
children | 9f1a6c97108d |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/jabber/buddy.c Mon Sep 29 15:23:19 2003 +0000 @@ -0,0 +1,845 @@ +/* + * gaim - Jabber Protocol Plugin + * + * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include "internal.h" +#include "debug.h" +#include "multi.h" +#include "notify.h" +#include "request.h" +#include "util.h" + +#include "buddy.h" +#include "chat.h" +#include "jabber.h" +#include "iq.h" +#include "presence.h" +#include "xmlnode.h" + + +JabberBuddy *jabber_buddy_find(JabberStream *js, const char *name, + gboolean create) +{ + JabberBuddy *jb; + JabberID *jid = jabber_id_new(name); + char *realname; + + if(!jid) + return NULL; + + if(jid->node) + realname = g_strdup_printf("%s@%s", jid->node, jid->domain); + else + realname = g_strdup(jid->domain); + + jb = g_hash_table_lookup(js->buddies, realname); + + if(!jb && create) { + jb = g_new0(JabberBuddy, 1); + g_hash_table_insert(js->buddies, g_strdup(realname), jb); + } + g_free(realname); + + jabber_id_free(jid); + + return jb; +} + + +JabberBuddyResource *jabber_buddy_find_resource(JabberBuddy *jb, + const char *resource) +{ + JabberBuddyResource *jbr = NULL; + GList *l; + + if(!jb) + return NULL; + + for(l = jb->resources; l; l = l->next) + { + if(!jbr && !resource) { + jbr = l->data; + } else if(!resource) { + if(((JabberBuddyResource *)l->data)->priority >= jbr->priority) + jbr = l->data; + } else if(((JabberBuddyResource *)l->data)->name) { + if(!strcmp(((JabberBuddyResource *)l->data)->name, resource)) { + jbr = l->data; + break; + } + } + } + + return jbr; +} + +void jabber_buddy_track_resource(JabberBuddy *jb, const char *resource, + int priority, int state, const char *status) +{ + JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource); + + if(!jbr) { + jbr = g_new0(JabberBuddyResource, 1); + jbr->name = g_strdup(resource); + jbr->capabilities = JABBER_CAP_XHTML; + jb->resources = g_list_append(jb->resources, jbr); + } + jbr->priority = priority; + jbr->state = state; + if(jbr->status) + g_free(jbr->status); + jbr->status = g_strdup(status); +} + +void jabber_buddy_remove_resource(JabberBuddy *jb, const char *resource) +{ + JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource); + + if(!jbr) + return; + + jb->resources = g_list_remove(jb->resources, jbr); + + g_free(jbr->name); + if(jbr->status) + g_free(jbr->status); + g_free(jbr); +} + +const char *jabber_buddy_get_status_msg(JabberBuddy *jb) +{ + JabberBuddyResource *jbr; + + if(!jb) + return NULL; + + jbr = jabber_buddy_find_resource(jb, NULL); + + if(!jbr) + return NULL; + + return jbr->status; +} + +/******* + * This is the old vCard stuff taken from the old prpl. vCards, by definition + * are a temporary thing until jabber can get its act together and come up + * with a format for user information, hence the namespace of 'vcard-temp' + * + * Since I don't feel like putting that much work into something that's + * _supposed_ to go away, i'm going to just copy the kludgy old code here, + * and make it purdy when jabber comes up with a standards-track JEP to + * replace vcard-temp + * --Nathan + *******/ + +/*---------------------------------------*/ +/* Jabber "set info" (vCard) support */ +/*---------------------------------------*/ + +/* + * V-Card format: + * + * <vCard prodid='' version='' xmlns=''> + * <FN></FN> + * <N> + * <FAMILY/> + * <GIVEN/> + * </N> + * <NICKNAME/> + * <URL/> + * <ADR> + * <STREET/> + * <EXTADD/> + * <LOCALITY/> + * <REGION/> + * <PCODE/> + * <COUNTRY/> + * </ADR> + * <TEL/> + * <EMAIL/> + * <ORG> + * <ORGNAME/> + * <ORGUNIT/> + * </ORG> + * <TITLE/> + * <ROLE/> + * <DESC/> + * <BDAY/> + * </vCard> + * + * See also: + * + * http://docs.jabber.org/proto/html/vcard-temp.html + * http://www.vcard-xml.org/dtd/vCard-XML-v2-20010520.dtd + */ + +/* + * Cross-reference user-friendly V-Card entry labels to vCard XML tags + * and attributes. + * + * Order is (or should be) unimportant. For example: we have no way of + * knowing in what order real data will arrive. + * + * Format: Label, Pre-set text, "visible" flag, "editable" flag, XML tag + * name, XML tag's parent tag "path" (relative to vCard node). + * + * List is terminated by a NULL label pointer. + * + * Entries with no label text, but with XML tag and parent tag + * entries, are used by V-Card XML construction routines to + * "automagically" construct the appropriate XML node tree. + * + * Thoughts on future direction/expansion + * + * This is a "simple" vCard. + * + * It is possible for nodes other than the "vCard" node to have + * attributes. Should that prove necessary/desirable, add an + * "attributes" pointer to the vcard_template struct, create the + * necessary tag_attr structs, and add 'em to the vcard_dflt_data + * array. + * + * The above changes will (obviously) require changes to the vCard + * construction routines. + */ + +struct vcard_template { + char *label; /* label text pointer */ + char *text; /* entry text pointer */ + int visible; /* should entry field be "visible?" */ + int editable; /* should entry field be editable? */ + char *tag; /* tag text */ + char *ptag; /* parent tag "path" text */ + char *url; /* vCard display format if URL */ +} vcard_template_data[] = { + {N_("Full Name"), NULL, TRUE, TRUE, "FN", NULL, NULL}, + {N_("Family Name"), NULL, TRUE, TRUE, "FAMILY", "N", NULL}, + {N_("Given Name"), NULL, TRUE, TRUE, "GIVEN", "N", NULL}, + {N_("Nickname"), NULL, TRUE, TRUE, "NICKNAME", NULL, NULL}, + {N_("URL"), NULL, TRUE, TRUE, "URL", NULL, "<A HREF=\"%s\">%s</A>"}, + {N_("Street Address"), NULL, TRUE, TRUE, "STREET", "ADR", NULL}, + {N_("Extended Address"), NULL, TRUE, TRUE, "EXTADD", "ADR", NULL}, + {N_("Locality"), NULL, TRUE, TRUE, "LOCALITY", "ADR", NULL}, + {N_("Region"), NULL, TRUE, TRUE, "REGION", "ADR", NULL}, + {N_("Postal Code"), NULL, TRUE, TRUE, "PCODE", "ADR", NULL}, + {N_("Country"), NULL, TRUE, TRUE, "COUNTRY", "ADR", NULL}, + {N_("Telephone"), NULL, TRUE, TRUE, "TELEPHONE", NULL, NULL}, + {N_("Email"), NULL, TRUE, TRUE, "EMAIL", NULL, "<A HREF=\"mailto:%s\">%s</A>"}, + {N_("Organization Name"), NULL, TRUE, TRUE, "ORGNAME", "ORG", NULL}, + {N_("Organization Unit"), NULL, TRUE, TRUE, "ORGUNIT", "ORG", NULL}, + {N_("Title"), NULL, TRUE, TRUE, "TITLE", NULL, NULL}, + {N_("Role"), NULL, TRUE, TRUE, "ROLE", NULL, NULL}, + {N_("Birthday"), NULL, TRUE, TRUE, "BDAY", NULL, NULL}, + {N_("Description"), NULL, TRUE, TRUE, "DESC", NULL, NULL}, + {"", NULL, TRUE, TRUE, "N", NULL, NULL}, + {"", NULL, TRUE, TRUE, "ADR", NULL, NULL}, + {"", NULL, TRUE, TRUE, "ORG", NULL, NULL}, + {NULL, NULL, 0, 0, NULL, NULL, NULL} +}; + +/* + * The "vCard" tag's attibute list... + */ +struct tag_attr { + char *attr; + char *value; +} vcard_tag_attr_list[] = { + {"prodid", "-//HandGen//NONSGML vGen v1.0//EN"}, + {"version", "2.0", }, + {"xmlns", "vcard-temp", }, + {NULL, NULL}, +}; + + +/* + * Insert a tag node into an xmlnode tree, recursively inserting parent tag + * nodes as necessary + * + * Returns pointer to inserted node + * + * Note to hackers: this code is designed to be re-entrant (it's recursive--it + * calls itself), so don't put any "static"s in here! + */ +static xmlnode *insert_tag_to_parent_tag(xmlnode *start, const char *parent_tag, const char *new_tag) +{ + xmlnode *x = NULL; + + /* + * If the parent tag wasn't specified, see if we can get it + * from the vCard template struct. + */ + if(parent_tag == NULL) { + struct vcard_template *vc_tp = vcard_template_data; + + while(vc_tp->label != NULL) { + if(strcmp(vc_tp->tag, new_tag) == 0) { + parent_tag = vc_tp->ptag; + break; + } + ++vc_tp; + } + } + + /* + * If we have a parent tag... + */ + if(parent_tag != NULL ) { + /* + * Try to get the parent node for a tag + */ + if((x = xmlnode_get_child(start, parent_tag)) == NULL) { + /* + * Descend? + */ + char *grand_parent = g_strdup(parent_tag); + char *parent; + + if((parent = strrchr(grand_parent, '/')) != NULL) { + *(parent++) = '\0'; + x = insert_tag_to_parent_tag(start, grand_parent, parent); + } else { + x = xmlnode_new_child(start, grand_parent); + } + g_free(grand_parent); + } else { + /* + * We found *something* to be the parent node. + * Note: may be the "root" node! + */ + xmlnode *y; + if((y = xmlnode_get_child(x, new_tag)) != NULL) { + return(y); + } + } + } + + /* + * insert the new tag into its parent node + */ + return(xmlnode_new_child((x == NULL? start : x), new_tag)); +} + +/* + * Send vCard info to Jabber server + */ +void jabber_set_info(GaimConnection *gc, const char *info) +{ + JabberIq *iq; + JabberStream *js = gc->proto_data; + xmlnode *vc_node; + + + /* + * Send only if there's actually any *information* to send + */ + vc_node = xmlnode_from_str(info, -1); + + if(vc_node) { + if (vc_node->name && + !g_ascii_strncasecmp(vc_node->name, "vcard", 5)) { + iq = jabber_iq_new(js, JABBER_IQ_SET); + xmlnode_insert_child(iq->node, vc_node); + jabber_iq_send(iq); + } else { + xmlnode_free(vc_node); + } + } +} + +/* + * This is the callback from the "ok clicked" for "set vCard" + * + * Formats GSList data into XML-encoded string and returns a pointer + * to said string. + * + * g_free()'ing the returned string space is the responsibility of + * the caller. + */ +static void +jabber_format_info(GaimConnection *gc, GaimRequestFields *fields) +{ + GaimAccount *account; + xmlnode *vc_node; + GaimRequestField *field; + const char *text; + char *p; + const struct vcard_template *vc_tp; + struct tag_attr *tag_attr; + + vc_node = xmlnode_new("vCard"); + + for(tag_attr = vcard_tag_attr_list; tag_attr->attr != NULL; ++tag_attr) + xmlnode_set_attrib(vc_node, tag_attr->attr, tag_attr->value); + + for (vc_tp = vcard_template_data; vc_tp->label != NULL; vc_tp++) { + if (*vc_tp->label == '\0') + continue; + + field = gaim_request_fields_get_field(fields, vc_tp->tag); + text = gaim_request_field_string_get_value(field); + + gaim_debug(GAIM_DEBUG_INFO, "jabber", + "Setting %s to '%s'\n", vc_tp->tag, text); + + if (text != NULL && *text != '\0') { + xmlnode *xp; + + if ((xp = insert_tag_to_parent_tag(vc_node, + NULL, vc_tp->tag)) != NULL) { + + xmlnode_insert_data(xp, text, -1); + } + } + } + + p = xmlnode_to_str(vc_node); + xmlnode_free(vc_node); + + account = gaim_connection_get_account(gc); + + if (account != NULL) { + gaim_account_set_user_info(account, p); + + if (gc != NULL) + serv_set_info(gc, p); + } + + g_free(p); +} + +/* + * This gets executed by the proto action + * + * Creates a new GaimRequestFields struct, gets the XML-formatted user_info + * string (if any) into GSLists for the (multi-entry) edit dialog and + * calls the set_vcard dialog. + */ +void jabber_setup_set_info(GaimConnection *gc) +{ + GaimRequestFields *fields; + GaimRequestFieldGroup *group; + GaimRequestField *field; + const struct vcard_template *vc_tp; + char *user_info; + char *cdata; + xmlnode *x_vc_data = NULL; + + fields = gaim_request_fields_new(); + group = gaim_request_field_group_new(NULL); + gaim_request_fields_add_group(fields, group); + + /* + * Get existing, XML-formatted, user info + */ + if((user_info = g_strdup(gaim_account_get_user_info(gc->account))) != NULL) + x_vc_data = xmlnode_from_str(user_info, -1); + else + user_info = g_strdup(""); + + /* + * Set up GSLists for edit with labels from "template," data from user info + */ + for(vc_tp = vcard_template_data; vc_tp->label != NULL; ++vc_tp) { + xmlnode *data_node; + if((vc_tp->label)[0] == '\0') + continue; + if(vc_tp->ptag == NULL) { + data_node = xmlnode_get_child(x_vc_data, vc_tp->tag); + } else { + gchar *tag = g_strdup_printf("%s/%s", vc_tp->ptag, vc_tp->tag); + data_node = xmlnode_get_child(x_vc_data, tag); + g_free(tag); + } + if(data_node) + cdata = xmlnode_get_data(data_node); + else + cdata = NULL; + + if(strcmp(vc_tp->tag, "DESC") == 0) { + field = gaim_request_field_string_new(vc_tp->tag, + _(vc_tp->label), cdata, + TRUE); + } else { + field = gaim_request_field_string_new(vc_tp->tag, + _(vc_tp->label), cdata, + FALSE); + } + + gaim_request_field_group_add_field(group, field); + } + + if(x_vc_data != NULL) + xmlnode_free(x_vc_data); + + g_free(user_info); + + gaim_request_fields(gc, _("Edit Jabber vCard"), + _("Edit Jabber vCard"), + _("All items below are optional. Enter only the " + "information with which you feel comfortable."), + fields, + _("Save"), G_CALLBACK(jabber_format_info), + _("Cancel"), NULL, + gc); +} + +/*---------------------------------------*/ +/* End Jabber "set info" (vCard) support */ +/*---------------------------------------*/ + +/****** + * end of that ancient crap that needs to die + ******/ + + +static void jabber_vcard_parse(JabberStream *js, xmlnode *packet) +{ + GList *resources; + const char *from = xmlnode_get_attrib(packet, "from"); + JabberBuddy *jb; + JabberBuddyResource *jbr; + GString *info_text; + const char *resource_name; + char *title; + xmlnode *vcard; + + if(!from) + return; + + resource_name = jabber_get_resource(from); + + jb = jabber_buddy_find(js, from, TRUE); + info_text = g_string_new(""); + + g_string_append_printf(info_text, "<b>%s:</b> %s<br/>\n", _("Jabber ID"), + from); + + if(resource_name) { + jbr = jabber_buddy_find_resource(jb, resource_name); + if(jbr) { + char *purdy = strdup_withhtml(jbr->status); + g_string_append_printf(info_text, "<b>%s:</b> %s%s%s<br/>\n", + _("Status"), jabber_get_state_string(jbr->state), + purdy ? ": " : "", + purdy ? purdy : ""); + g_free(purdy); + } else { + g_string_append_printf(info_text, "<b>%s:</b> %s<br/>\n", + _("Status"), _("Unknown")); + } + } else { + for(resources = jb->resources; resources; resources = resources->next) { + char *purdy; + jbr = resources->data; + purdy = strdup_withhtml(jbr->status); + g_string_append_printf(info_text, "<b>%s:</b> %s<br/>\n", + _("Resource"), jbr->name); + g_string_append_printf(info_text, "<b>%s:</b> %s%s%s<br/><br/>\n", + _("Status"), jabber_get_state_string(jbr->state), + purdy ? ": " : "", + purdy ? purdy : ""); + g_free(purdy); + } + } + + if((vcard = xmlnode_get_child(packet, "vCard"))) { + xmlnode *child; + for(child = vcard->child; child; child = child->next) + { + xmlnode *child2; + char *text; + + if(child->type != NODE_TYPE_TAG) + continue; + + text = xmlnode_get_data(child); + if(text && !strcmp(child->name, "FN")) { + g_string_append_printf(info_text, "<b>%s:</b> %s<br/>\n", + _("Full Name"), text); + } else if(!strcmp(child->name, "N")) { + for(child2 = child->child; child2; child2 = child2->next) + { + char *text2; + + if(child2->type != NODE_TYPE_TAG) + continue; + + text2 = xmlnode_get_data(child2); + if(text2 && !strcmp(child2->name, "FAMILY")) { + g_string_append_printf(info_text, + "<b>%s:</b> %s<br/>\n", + _("Family Name"), text2); + } else if(text2 && !strcmp(child2->name, "GIVEN")) { + g_string_append_printf(info_text, + "<b>%s:</b> %s<br/>\n", + _("Given Name"), text2); + } else if(text2 && !strcmp(child2->name, "MIDDLE")) { + g_string_append_printf(info_text, + "<b>%s:</b> %s<br/>\n", + _("Middle Name"), text2); + } + g_free(text2); + } + } else if(text && !strcmp(child->name, "NICKNAME")) { + serv_got_alias(js->gc, from, text); + g_string_append_printf(info_text, "<b>%s:</b> %s<br/>\n", + _("Nickname"), text); + } else if(text && !strcmp(child->name, "BDAY")) { + g_string_append_printf(info_text, "<b>%s:</b> %s<br/>\n", + _("Birthday"), text); + } else if(!strcmp(child->name, "ADR")) { + /* show which address it is */ + if(child->child) + g_string_append_printf(info_text, "<b>%s:</b><br/>\n", + _("Address")); + for(child2 = child->child; child2; child2 = child2->next) + { + char *text2; + + if(child2->type != NODE_TYPE_TAG) + continue; + + text2 = xmlnode_get_data(child2); + if(text2 && !strcmp(child2->name, "POBOX")) { + g_string_append_printf(info_text, + " <b>%s:</b> %s<br/>\n", + _("P.O. Box"), text2); + } else if(text2 && !strcmp(child2->name, "EXTADR")) { + g_string_append_printf(info_text, + " <b>%s:</b> %s<br/>\n", + _("Extended Address"), text2); + } else if(text2 && !strcmp(child2->name, "STREET")) { + g_string_append_printf(info_text, + " <b>%s:</b> %s<br/>\n", + _("Street Address"), text2); + } else if(text2 && !strcmp(child2->name, "LOCALITY")) { + g_string_append_printf(info_text, + " <b>%s:</b> %s<br/>\n", + _("Locality"), text2); + } else if(text2 && !strcmp(child2->name, "REGION")) { + g_string_append_printf(info_text, + " <b>%s:</b> %s<br/>\n", + _("Region"), text2); + } else if(text2 && !strcmp(child2->name, "PCODE")) { + g_string_append_printf(info_text, + " <b>%s:</b> %s<br/>\n", + _("Postal Code"), text2); + } else if(text2 && (!strcmp(child2->name, "CTRY") + || !strcmp(child2->name, "COUNTRY"))) { + g_string_append_printf(info_text, + " <b>%s:</b> %s<br/>\n", + _("Country"), text2); + } + g_free(text2); + } + } else if(!strcmp(child->name, "TEL")) { + char *number; + if((child2 = xmlnode_get_child(child, "NUMBER"))) { + /* show what kind of number it is */ + number = xmlnode_get_data(child2); + if(number) { + g_string_append_printf(info_text, + "<b>%s:</b> %s<br/>\n", _("Telephone"), number); + g_free(number); + } + } else if((number = xmlnode_get_data(child))) { + /* lots of clients (including gaim) do this, but it's + * out of spec */ + g_string_append_printf(info_text, + "<b>%s:</b> %s<br/>\n", _("Telephone"), number); + g_free(number); + } + } else if(!strcmp(child->name, "EMAIL")) { + char *userid; + if((child2 = xmlnode_get_child(child, "USERID"))) { + /* show what kind of email it is */ + userid = xmlnode_get_data(child2); + if(userid) { + g_string_append_printf(info_text, + "<b>%s:</b> <a href='mailto:%s'>%s</a><br/>\n", + _("Email"), userid, userid); + g_free(userid); + } + } else if((userid = xmlnode_get_data(child))) { + /* lots of clients (including gaim) do this, but it's + * out of spec */ + g_string_append_printf(info_text, + "<b>%s:</b> <a href='mailto:%s'>%s</a><br/>\n", + _("Email"), userid, userid); + g_free(userid); + } + } else if(!strcmp(child->name, "ORG")) { + for(child2 = child->child; child2; child2 = child2->next) + { + char *text2; + + if(child2->type != NODE_TYPE_TAG) + continue; + + text2 = xmlnode_get_data(child2); + if(text2 && !strcmp(child2->name, "ORGNAME")) { + g_string_append_printf(info_text, + "<b>%s:</b> %s<br/>\n", + _("Organization Name"), text2); + } else if(text2 && !strcmp(child2->name, "ORGUNIT")) { + g_string_append_printf(info_text, + "<b>%s:</b> %s<br/>\n", + _("Organization Unit"), text2); + } + g_free(text2); + } + } else if(text && !strcmp(child->name, "TITLE")) { + g_string_append_printf(info_text, "<b>%s:</b> %s<br/>\n", + _("Title"), text); + } else if(text && !strcmp(child->name, "ROLE")) { + g_string_append_printf(info_text, "<b>%s:</b> %s<br/>\n", + _("Role"), text); + } else if(text && !strcmp(child->name, "DESC")) { + g_string_append_printf(info_text, "<b>%s:</b> %s<br/>\n", + _("Description"), text); + } + g_free(text); + } + } + + title = g_strdup_printf("User info for %s", from); + + gaim_notify_formatted(NULL, title, _("Jabber Profile"), + NULL, info_text->str, NULL, NULL); + + g_free(title); + g_string_free(info_text, TRUE); +} + +void jabber_buddy_get_info(GaimConnection *gc, const char *who) +{ + JabberStream *js = gc->proto_data; + JabberIq *iq; + xmlnode *vcard; + + iq = jabber_iq_new(js, JABBER_IQ_GET); + + xmlnode_set_attrib(iq->node, "to", who); + vcard = xmlnode_new_child(iq->node, "vCard"); + xmlnode_set_attrib(vcard, "xmlns", "vcard-temp"); + + jabber_iq_set_callback(iq, jabber_vcard_parse); + + jabber_iq_send(iq); +} + +void jabber_buddy_get_info_chat(GaimConnection *gc, int id, + const char *resource) +{ + JabberStream *js = gc->proto_data; + JabberChat *chat = jabber_chat_find_by_id(js, id); + char *full_jid; + + if(!chat) + return; + + full_jid = g_strdup_printf("%s@%s/%s", chat->room, chat->server, resource); + jabber_buddy_get_info(gc, full_jid); + g_free(full_jid); +} + +static void jabber_buddy_set_invisibility(JabberStream *js, const char *who, + gboolean invisible) +{ + JabberBuddy *jb = jabber_buddy_find(js, who, TRUE); + xmlnode *presence; + + presence = jabber_presence_create(js->gc->away_state, js->gc->away); + xmlnode_set_attrib(presence, "to", who); + if(invisible) { + xmlnode_set_attrib(presence, "type", "invisible"); + jb->invisible |= JABBER_INVIS_BUDDY; + } else { + jb->invisible &= ~JABBER_INVIS_BUDDY; + } + + jabber_send(js, presence); + xmlnode_free(presence); +} + +static void jabber_buddy_make_invisible(GaimConnection *gc, const char *name) +{ + JabberStream *js = gc->proto_data; + jabber_buddy_set_invisibility(js, name, TRUE); +} + +static void jabber_buddy_make_visible(GaimConnection *gc, const char *name) +{ + JabberStream *js = gc->proto_data; + jabber_buddy_set_invisibility(js, name, FALSE); +} + +static void jabber_buddy_cancel_presence_notification(GaimConnection *gc, + const char *name) +{ + JabberStream *js = gc->proto_data; + + /* I wonder if we should prompt the user before doing this */ + jabber_presence_subscription_set(js, name, "unsubscribed"); +} + +static void jabber_buddy_rerequest_auth(GaimConnection *gc, const char *name) +{ + JabberStream *js = gc->proto_data; + + jabber_presence_subscription_set(js, name, "subscribe"); +} + +GList *jabber_buddy_menu(GaimConnection *gc, const char *name) +{ + GList *m = NULL; + struct proto_buddy_menu *pbm; + JabberStream *js = gc->proto_data; + JabberBuddy *jb = jabber_buddy_find(js, name, TRUE); + + pbm = g_new0(struct proto_buddy_menu, 1); + if(jb->invisible & JABBER_INVIS_BUDDY) { + pbm->label = _("Un-hide From"); + pbm->callback = jabber_buddy_make_visible; + } else { + pbm->label = _("Temporarily Hide From"); + pbm->callback = jabber_buddy_make_invisible; + } + pbm->gc = gc; + m = g_list_append(m, pbm); + + if(jb->subscription & JABBER_SUB_FROM) { + pbm = g_new0(struct proto_buddy_menu, 1); + pbm->label = _("Cancel Presence Notification"); + pbm->callback = jabber_buddy_cancel_presence_notification; + pbm->gc = gc; + m = g_list_append(m, pbm); + } + + if(!(jb->subscription & JABBER_SUB_TO)) { + pbm = g_new0(struct proto_buddy_menu, 1); + pbm->label = _("Re-request authorization"); + pbm->callback = jabber_buddy_rerequest_auth; + pbm->gc = gc; + m = g_list_append(m, pbm); + } + + return m; +}