Mercurial > pidgin.yaz
view libpurple/protocols/jabber/chat.c @ 29093:65a34cce02e3
jabber: Fix up the remaining issues and add a test case that interoperates with gsasl. Woot.
author | Paul Aurich <paul@darkrain42.org> |
---|---|
date | Tue, 17 Nov 2009 19:39:36 +0000 |
parents | a335d1bab20e |
children | 9ae3e70a327b |
line wrap: on
line source
/* * purple - Jabber Protocol Plugin * * Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * source distribution. * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ #include "internal.h" #include "debug.h" #include "prpl.h" /* for proto_chat_entry */ #include "notify.h" #include "request.h" #include "roomlist.h" #include "util.h" #include "chat.h" #include "iq.h" #include "message.h" #include "presence.h" #include "xdata.h" #include "data.h" GList *jabber_chat_info(PurpleConnection *gc) { GList *m = NULL; struct proto_chat_entry *pce; pce = g_new0(struct proto_chat_entry, 1); pce->label = _("_Room:"); pce->identifier = "room"; pce->required = TRUE; m = g_list_append(m, pce); pce = g_new0(struct proto_chat_entry, 1); pce->label = _("_Server:"); pce->identifier = "server"; pce->required = TRUE; m = g_list_append(m, pce); pce = g_new0(struct proto_chat_entry, 1); pce->label = _("_Handle:"); pce->identifier = "handle"; pce->required = TRUE; m = g_list_append(m, pce); pce = g_new0(struct proto_chat_entry, 1); pce->label = _("_Password:"); pce->identifier = "password"; pce->secret = TRUE; m = g_list_append(m, pce); return m; } GHashTable *jabber_chat_info_defaults(PurpleConnection *gc, const char *chat_name) { GHashTable *defaults; JabberStream *js = gc->proto_data; defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free); g_hash_table_insert(defaults, "handle", g_strdup(js->user->node)); if (js->chat_servers) g_hash_table_insert(defaults, "server", g_strdup(js->chat_servers->data)); if (chat_name != NULL) { JabberID *jid = jabber_id_new(chat_name); if(jid) { g_hash_table_insert(defaults, "room", g_strdup(jid->node)); if(jid->domain) g_hash_table_replace(defaults, "server", g_strdup(jid->domain)); if(jid->resource) g_hash_table_replace(defaults, "handle", g_strdup(jid->resource)); jabber_id_free(jid); } } return defaults; } JabberChat *jabber_chat_find(JabberStream *js, const char *room, const char *server) { JabberChat *chat = NULL; g_return_val_if_fail(room != NULL, NULL); g_return_val_if_fail(server != NULL, NULL); if(NULL != js->chats) { char *room_jid = g_strdup_printf("%s@%s", room, server); chat = g_hash_table_lookup(js->chats, room_jid); g_free(room_jid); } return chat; } struct _find_by_id_data { int id; JabberChat *chat; }; static void find_by_id_foreach_cb(gpointer key, gpointer value, gpointer user_data) { JabberChat *chat = value; struct _find_by_id_data *fbid = user_data; if(chat->id == fbid->id) fbid->chat = chat; } JabberChat *jabber_chat_find_by_id(JabberStream *js, int id) { JabberChat *chat; struct _find_by_id_data *fbid = g_new0(struct _find_by_id_data, 1); fbid->id = id; g_hash_table_foreach(js->chats, find_by_id_foreach_cb, fbid); chat = fbid->chat; g_free(fbid); return chat; } JabberChat *jabber_chat_find_by_conv(PurpleConversation *conv) { PurpleAccount *account = purple_conversation_get_account(conv); PurpleConnection *gc = purple_account_get_connection(account); JabberStream *js; int id; if (!gc) return NULL; js = gc->proto_data; id = purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)); return jabber_chat_find_by_id(js, id); } void jabber_chat_invite(PurpleConnection *gc, int id, const char *msg, const char *name) { JabberStream *js = gc->proto_data; JabberChat *chat; xmlnode *message, *body, *x, *invite; char *room_jid; chat = jabber_chat_find_by_id(js, id); if(!chat) return; message = xmlnode_new("message"); room_jid = g_strdup_printf("%s@%s", chat->room, chat->server); if(chat->muc) { xmlnode_set_attrib(message, "to", room_jid); x = xmlnode_new_child(message, "x"); xmlnode_set_namespace(x, "http://jabber.org/protocol/muc#user"); invite = xmlnode_new_child(x, "invite"); xmlnode_set_attrib(invite, "to", name); body = xmlnode_new_child(invite, "reason"); xmlnode_insert_data(body, msg, -1); } else { xmlnode_set_attrib(message, "to", name); /* * Putting the reason into the body was an 'undocumented protocol, * ...not part of "groupchat 1.0"'. * http://xmpp.org/extensions/attic/jep-0045-1.16.html#invite * * Left here for compatibility. */ body = xmlnode_new_child(message, "body"); xmlnode_insert_data(body, msg, -1); x = xmlnode_new_child(message, "x"); xmlnode_set_attrib(x, "jid", room_jid); /* The better place for it! XEP-0249 style. */ xmlnode_set_attrib(x, "reason", msg); xmlnode_set_namespace(x, "jabber:x:conference"); } jabber_send(js, message); xmlnode_free(message); g_free(room_jid); } void jabber_chat_member_free(JabberChatMember *jcm); char *jabber_get_chat_name(GHashTable *data) { char *room, *server, *chat_name = NULL; room = g_hash_table_lookup(data, "room"); server = g_hash_table_lookup(data, "server"); if (room && server) { chat_name = g_strdup_printf("%s@%s", room, server); } return chat_name; } static void insert_in_hash_table(gpointer key, gpointer value, gpointer user_data) { GHashTable *hash_table = (GHashTable *)user_data; g_hash_table_insert(hash_table, g_strdup(key), g_strdup(value)); } static JabberChat *jabber_chat_new(JabberStream *js, const char *room, const char *server, const char *handle, const char *password, GHashTable *data) { JabberChat *chat; char *jid; if (jabber_chat_find(js, room, server) != NULL) return NULL; chat = g_new0(JabberChat, 1); chat->js = js; chat->room = g_strdup(room); chat->server = g_strdup(server); chat->handle = g_strdup(handle); /* Copy the data hash table to chat->components */ chat->components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); if (data == NULL) { g_hash_table_insert(chat->components, g_strdup("handle"), g_strdup(handle)); g_hash_table_insert(chat->components, g_strdup("room"), g_strdup(room)); g_hash_table_insert(chat->components, g_strdup("server"), g_strdup(server)); /* g_hash_table_insert(chat->components, g_strdup("password"), g_strdup(server)); */ } else { g_hash_table_foreach(data, insert_in_hash_table, chat->components); } chat->members = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)jabber_chat_member_free); jid = g_strdup_printf("%s@%s", room, server); g_hash_table_insert(js->chats, jid, chat); return chat; } JabberChat *jabber_join_chat(JabberStream *js, const char *room, const char *server, const char *handle, const char *password, GHashTable *data) { JabberChat *chat; PurpleConnection *gc; PurpleAccount *account; PurpleStatus *status; xmlnode *presence, *x; JabberBuddyState state; char *msg; int priority; char *jid; chat = jabber_chat_new(js, room, server, handle, password, data); if (chat == NULL) return NULL; gc = js->gc; account = purple_connection_get_account(gc); status = purple_account_get_active_status(account); purple_status_to_jabber(status, &state, &msg, &priority); presence = jabber_presence_create_js(js, state, msg, priority); g_free(msg); jid = g_strdup_printf("%s@%s/%s", room, server, handle); xmlnode_set_attrib(presence, "to", jid); g_free(jid); x = xmlnode_new_child(presence, "x"); xmlnode_set_namespace(x, "http://jabber.org/protocol/muc"); if (password && *password) { xmlnode *p = xmlnode_new_child(x, "password"); xmlnode_insert_data(p, password, -1); } jabber_send(js, presence); xmlnode_free(presence); return chat; } void jabber_chat_join(PurpleConnection *gc, GHashTable *data) { char *room, *server, *handle, *passwd; JabberID *jid; JabberStream *js = gc->proto_data; char *tmp; room = g_hash_table_lookup(data, "room"); server = g_hash_table_lookup(data, "server"); handle = g_hash_table_lookup(data, "handle"); passwd = g_hash_table_lookup(data, "password"); if(!room || !server) return; if(!handle) handle = js->user->node; if(!jabber_nodeprep_validate(room)) { char *buf = g_strdup_printf(_("%s is not a valid room name"), room); purple_notify_error(gc, _("Invalid Room Name"), _("Invalid Room Name"), buf); purple_serv_got_join_chat_failed(gc, data); g_free(buf); return; } else if(!jabber_domain_validate(server)) { char *buf = g_strdup_printf(_("%s is not a valid server name"), server); purple_notify_error(gc, _("Invalid Server Name"), _("Invalid Server Name"), buf); purple_serv_got_join_chat_failed(gc, data); g_free(buf); return; } else if(!jabber_resourceprep_validate(handle)) { char *buf = g_strdup_printf(_("%s is not a valid room handle"), handle); purple_notify_error(gc, _("Invalid Room Handle"), _("Invalid Room Handle"), buf); purple_serv_got_join_chat_failed(gc, data); g_free(buf); return; } /* Normalize the room and server parameters */ tmp = g_strdup_printf("%s@%s", room, server); jid = jabber_id_new(tmp); g_free(tmp); if (jid == NULL) { /* TODO: Error message */ g_return_if_reached(); } /* * Now that we've done all that nice core-interface stuff, let's join * this room! */ jabber_join_chat(js, jid->node, jid->domain, handle, passwd, data); jabber_id_free(jid); } void jabber_chat_leave(PurpleConnection *gc, int id) { JabberStream *js = gc->proto_data; JabberChat *chat = jabber_chat_find_by_id(js, id); if(!chat) return; jabber_chat_part(chat, NULL); chat->left = TRUE; } void jabber_chat_destroy(JabberChat *chat) { JabberStream *js = chat->js; char *room_jid = g_strdup_printf("%s@%s", chat->room, chat->server); g_hash_table_remove(js->chats, room_jid); g_free(room_jid); } void jabber_chat_free(JabberChat *chat) { if(chat->config_dialog_handle) purple_request_close(chat->config_dialog_type, chat->config_dialog_handle); g_free(chat->room); g_free(chat->server); g_free(chat->handle); g_hash_table_destroy(chat->members); g_hash_table_destroy(chat->components); g_free(chat); } gboolean jabber_chat_find_buddy(PurpleConversation *conv, const char *name) { return purple_conv_chat_find_user(PURPLE_CONV_CHAT(conv), name); } char *jabber_chat_buddy_real_name(PurpleConnection *gc, int id, const char *who) { JabberStream *js = gc->proto_data; JabberChat *chat; JabberChatMember *jcm; chat = jabber_chat_find_by_id(js, id); if(!chat) return NULL; jcm = g_hash_table_lookup(chat->members, who); if (jcm != NULL && jcm->jid) return g_strdup(jcm->jid); return g_strdup_printf("%s@%s/%s", chat->room, chat->server, who); } static void jabber_chat_room_configure_x_data_cb(JabberStream *js, xmlnode *result, gpointer data) { JabberChat *chat = data; xmlnode *query; JabberIq *iq; char *to = g_strdup_printf("%s@%s", chat->room, chat->server); iq = jabber_iq_new_query(js, JABBER_IQ_SET, "http://jabber.org/protocol/muc#owner"); xmlnode_set_attrib(iq->node, "to", to); g_free(to); query = xmlnode_get_child(iq->node, "query"); xmlnode_insert_child(query, result); jabber_iq_send(iq); } static void jabber_chat_room_configure_cb(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *packet, gpointer data) { xmlnode *query, *x; char *msg; JabberChat *chat; JabberID *jid; if (!from) return; if (type == JABBER_IQ_RESULT) { jid = jabber_id_new(from); if(!jid) return; chat = jabber_chat_find(js, jid->node, jid->domain); jabber_id_free(jid); if(!chat) return; if(!(query = xmlnode_get_child(packet, "query"))) return; for(x = xmlnode_get_child(query, "x"); x; x = xmlnode_get_next_twin(x)) { const char *xmlns; if(!(xmlns = xmlnode_get_namespace(x))) continue; if(!strcmp(xmlns, "jabber:x:data")) { chat->config_dialog_type = PURPLE_REQUEST_FIELDS; chat->config_dialog_handle = jabber_x_data_request(js, x, jabber_chat_room_configure_x_data_cb, chat); return; } } } else if (type == JABBER_IQ_ERROR) { char *msg = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Configuration error"), _("Configuration error"), msg); if(msg) g_free(msg); return; } msg = g_strdup_printf("Unable to configure room %s", from); purple_notify_info(js->gc, _("Unable to configure"), _("Unable to configure"), msg); g_free(msg); } void jabber_chat_request_room_configure(JabberChat *chat) { JabberIq *iq; char *room_jid; if(!chat) return; chat->config_dialog_handle = NULL; if(!chat->muc) { purple_notify_error(chat->js->gc, _("Room Configuration Error"), _("Room Configuration Error"), _("This room is not capable of being configured")); return; } iq = jabber_iq_new_query(chat->js, JABBER_IQ_GET, "http://jabber.org/protocol/muc#owner"); room_jid = g_strdup_printf("%s@%s", chat->room, chat->server); xmlnode_set_attrib(iq->node, "to", room_jid); jabber_iq_set_callback(iq, jabber_chat_room_configure_cb, NULL); jabber_iq_send(iq); g_free(room_jid); } void jabber_chat_create_instant_room(JabberChat *chat) { JabberIq *iq; xmlnode *query, *x; char *room_jid; if(!chat) return; chat->config_dialog_handle = NULL; iq = jabber_iq_new_query(chat->js, JABBER_IQ_SET, "http://jabber.org/protocol/muc#owner"); query = xmlnode_get_child(iq->node, "query"); x = xmlnode_new_child(query, "x"); room_jid = g_strdup_printf("%s@%s", chat->room, chat->server); xmlnode_set_attrib(iq->node, "to", room_jid); xmlnode_set_namespace(x, "jabber:x:data"); xmlnode_set_attrib(x, "type", "submit"); jabber_iq_send(iq); g_free(room_jid); } static void jabber_chat_register_x_data_result_cb(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *packet, gpointer data) { if (type == JABBER_IQ_ERROR) { char *msg = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Registration error"), _("Registration error"), msg); if(msg) g_free(msg); return; } } static void jabber_chat_register_x_data_cb(JabberStream *js, xmlnode *result, gpointer data) { JabberChat *chat = data; xmlnode *query; JabberIq *iq; char *to = g_strdup_printf("%s@%s", chat->room, chat->server); iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:register"); xmlnode_set_attrib(iq->node, "to", to); g_free(to); query = xmlnode_get_child(iq->node, "query"); xmlnode_insert_child(query, result); jabber_iq_set_callback(iq, jabber_chat_register_x_data_result_cb, NULL); jabber_iq_send(iq); } static void jabber_chat_register_cb(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *packet, gpointer data) { xmlnode *query, *x; char *msg; JabberChat *chat; JabberID *jid; if (!from) return; if (type == JABBER_IQ_RESULT) { jid = jabber_id_new(from); if(!jid) return; chat = jabber_chat_find(js, jid->node, jid->domain); jabber_id_free(jid); if(!chat) return; if(!(query = xmlnode_get_child(packet, "query"))) return; for(x = xmlnode_get_child(query, "x"); x; x = xmlnode_get_next_twin(x)) { const char *xmlns; if(!(xmlns = xmlnode_get_namespace(x))) continue; if(!strcmp(xmlns, "jabber:x:data")) { jabber_x_data_request(js, x, jabber_chat_register_x_data_cb, chat); return; } } } else if (type == JABBER_IQ_ERROR) { char *msg = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Registration error"), _("Registration error"), msg); if(msg) g_free(msg); return; } msg = g_strdup_printf("Unable to configure room %s", from); purple_notify_info(js->gc, _("Unable to configure"), _("Unable to configure"), msg); g_free(msg); } void jabber_chat_register(JabberChat *chat) { JabberIq *iq; char *room_jid; if(!chat) return; room_jid = g_strdup_printf("%s@%s", chat->room, chat->server); iq = jabber_iq_new_query(chat->js, JABBER_IQ_GET, "jabber:iq:register"); xmlnode_set_attrib(iq->node, "to", room_jid); g_free(room_jid); jabber_iq_set_callback(iq, jabber_chat_register_cb, NULL); jabber_iq_send(iq); } /* merge this with the function below when we get everyone on the same page wrt /commands */ void jabber_chat_change_topic(JabberChat *chat, const char *topic) { JabberMessage *jm; jm = g_new0(JabberMessage, 1); jm->js = chat->js; jm->type = JABBER_MESSAGE_GROUPCHAT; jm->to = g_strdup_printf("%s@%s", chat->room, chat->server); if (topic && *topic) jm->subject = g_strdup(topic); else jm->subject = g_strdup(""); jabber_message_send(jm); jabber_message_free(jm); } void jabber_chat_set_topic(PurpleConnection *gc, int id, const char *topic) { JabberStream *js = purple_connection_get_protocol_data(gc); JabberChat *chat = jabber_chat_find_by_id(js, id); if(!chat) return; jabber_chat_change_topic(chat, topic); } gboolean jabber_chat_change_nick(JabberChat *chat, const char *nick) { xmlnode *presence; char *full_jid; PurpleAccount *account; PurpleStatus *status; JabberBuddyState state; char *msg; int priority; if(!chat->muc) { purple_conv_chat_write(PURPLE_CONV_CHAT(chat->conv), "", _("Nick changing not supported in non-MUC chatrooms"), PURPLE_MESSAGE_SYSTEM, time(NULL)); return FALSE; } account = purple_connection_get_account(chat->js->gc); status = purple_account_get_active_status(account); purple_status_to_jabber(status, &state, &msg, &priority); presence = jabber_presence_create_js(chat->js, state, msg, priority); full_jid = g_strdup_printf("%s@%s/%s", chat->room, chat->server, nick); xmlnode_set_attrib(presence, "to", full_jid); g_free(full_jid); g_free(msg); jabber_send(chat->js, presence); xmlnode_free(presence); return TRUE; } void jabber_chat_part(JabberChat *chat, const char *msg) { char *room_jid; xmlnode *presence; room_jid = g_strdup_printf("%s@%s/%s", chat->room, chat->server, chat->handle); presence = xmlnode_new("presence"); xmlnode_set_attrib(presence, "to", room_jid); xmlnode_set_attrib(presence, "type", "unavailable"); if(msg) { xmlnode *status = xmlnode_new_child(presence, "status"); xmlnode_insert_data(status, msg, -1); } jabber_send(chat->js, presence); xmlnode_free(presence); g_free(room_jid); } static void roomlist_disco_result_cb(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *packet, gpointer data) { xmlnode *query; xmlnode *item; if(!js->roomlist) return; if (type == JABBER_IQ_ERROR) { char *err = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Error"), _("Error retrieving room list"), err); purple_roomlist_set_in_progress(js->roomlist, FALSE); purple_roomlist_unref(js->roomlist); js->roomlist = NULL; g_free(err); return; } if(!(query = xmlnode_get_child(packet, "query"))) { char *err = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Error"), _("Error retrieving room list"), err); purple_roomlist_set_in_progress(js->roomlist, FALSE); purple_roomlist_unref(js->roomlist); js->roomlist = NULL; g_free(err); return; } for(item = xmlnode_get_child(query, "item"); item; item = xmlnode_get_next_twin(item)) { const char *name; PurpleRoomlistRoom *room; JabberID *jid; if(!(jid = jabber_id_new(xmlnode_get_attrib(item, "jid")))) continue; name = xmlnode_get_attrib(item, "name"); room = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM, jid->node, NULL); purple_roomlist_room_add_field(js->roomlist, room, jid->node); purple_roomlist_room_add_field(js->roomlist, room, jid->domain); purple_roomlist_room_add_field(js->roomlist, room, name ? name : ""); purple_roomlist_room_add(js->roomlist, room); jabber_id_free(jid); } purple_roomlist_set_in_progress(js->roomlist, FALSE); purple_roomlist_unref(js->roomlist); js->roomlist = NULL; } static void roomlist_cancel_cb(JabberStream *js, const char *server) { if(js->roomlist) { purple_roomlist_set_in_progress(js->roomlist, FALSE); purple_roomlist_unref(js->roomlist); js->roomlist = NULL; } } static void roomlist_ok_cb(JabberStream *js, const char *server) { JabberIq *iq; if(!js->roomlist) return; if(!server || !*server) { purple_notify_error(js->gc, _("Invalid Server"), _("Invalid Server"), NULL); purple_roomlist_set_in_progress(js->roomlist, FALSE); return; } purple_roomlist_set_in_progress(js->roomlist, TRUE); iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#items"); xmlnode_set_attrib(iq->node, "to", server); jabber_iq_set_callback(iq, roomlist_disco_result_cb, NULL); jabber_iq_send(iq); } char *jabber_roomlist_room_serialize(PurpleRoomlistRoom *room) { return g_strdup_printf("%s@%s", (char*)room->fields->data, (char*)room->fields->next->data); } PurpleRoomlist *jabber_roomlist_get_list(PurpleConnection *gc) { JabberStream *js = gc->proto_data; GList *fields = NULL; PurpleRoomlistField *f; if(js->roomlist) purple_roomlist_unref(js->roomlist); js->roomlist = purple_roomlist_new(purple_connection_get_account(js->gc)); f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "", "room", TRUE); fields = g_list_append(fields, f); f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "", "server", TRUE); fields = g_list_append(fields, f); f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Description"), "description", FALSE); fields = g_list_append(fields, f); purple_roomlist_set_fields(js->roomlist, fields); purple_request_input(gc, _("Enter a Conference Server"), _("Enter a Conference Server"), _("Select a conference server to query"), js->chat_servers ? js->chat_servers->data : NULL, FALSE, FALSE, NULL, _("Find Rooms"), PURPLE_CALLBACK(roomlist_ok_cb), _("Cancel"), PURPLE_CALLBACK(roomlist_cancel_cb), purple_connection_get_account(gc), NULL, NULL, js); return js->roomlist; } void jabber_roomlist_cancel(PurpleRoomlist *list) { PurpleConnection *gc; JabberStream *js; gc = purple_account_get_connection(list->account); js = gc->proto_data; purple_roomlist_set_in_progress(list, FALSE); if (js->roomlist == list) { js->roomlist = NULL; purple_roomlist_unref(list); } } void jabber_chat_member_free(JabberChatMember *jcm) { g_free(jcm->handle); g_free(jcm->jid); g_free(jcm); } void jabber_chat_track_handle(JabberChat *chat, const char *handle, const char *jid, const char *affiliation, const char *role) { JabberChatMember *jcm = g_new0(JabberChatMember, 1); jcm->handle = g_strdup(handle); jcm->jid = g_strdup(jid); g_hash_table_replace(chat->members, jcm->handle, jcm); /* XXX: keep track of role and affiliation */ } void jabber_chat_remove_handle(JabberChat *chat, const char *handle) { g_hash_table_remove(chat->members, handle); } gboolean jabber_chat_ban_user(JabberChat *chat, const char *who, const char *why) { JabberChatMember *jcm; const char *jid; char *to; JabberIq *iq; xmlnode *query, *item, *reason; jcm = g_hash_table_lookup(chat->members, who); if (jcm && jcm->jid) jid = jcm->jid; else if (g_utf8_strchr(who, -1, '@') != NULL) jid = who; else return FALSE; iq = jabber_iq_new_query(chat->js, JABBER_IQ_SET, "http://jabber.org/protocol/muc#admin"); to = g_strdup_printf("%s@%s", chat->room, chat->server); xmlnode_set_attrib(iq->node, "to", to); g_free(to); query = xmlnode_get_child(iq->node, "query"); item = xmlnode_new_child(query, "item"); xmlnode_set_attrib(item, "jid", jid); xmlnode_set_attrib(item, "affiliation", "outcast"); if(why) { reason = xmlnode_new_child(item, "reason"); xmlnode_insert_data(reason, why, -1); } jabber_iq_send(iq); return TRUE; } gboolean jabber_chat_affiliate_user(JabberChat *chat, const char *who, const char *affiliation) { JabberChatMember *jcm; const char *jid; char *to; JabberIq *iq; xmlnode *query, *item; jcm = g_hash_table_lookup(chat->members, who); if (jcm && jcm->jid) jid = jcm->jid; else if (g_utf8_strchr(who, -1, '@') != NULL) jid = who; else return FALSE; iq = jabber_iq_new_query(chat->js, JABBER_IQ_SET, "http://jabber.org/protocol/muc#admin"); to = g_strdup_printf("%s@%s", chat->room, chat->server); xmlnode_set_attrib(iq->node, "to", to); g_free(to); query = xmlnode_get_child(iq->node, "query"); item = xmlnode_new_child(query, "item"); xmlnode_set_attrib(item, "jid", jid); xmlnode_set_attrib(item, "affiliation", affiliation); jabber_iq_send(iq); return TRUE; } static void jabber_chat_affiliation_list_cb(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *packet, gpointer data) { JabberChat *chat; xmlnode *query, *item; int chat_id = GPOINTER_TO_INT(data); GString *buf; if(!(chat = jabber_chat_find_by_id(js, chat_id))) return; if (type == JABBER_IQ_ERROR) return; if(!(query = xmlnode_get_child(packet, "query"))) return; buf = g_string_new(_("Affiliations:")); item = xmlnode_get_child(query, "item"); if (item) { for( ; item; item = xmlnode_get_next_twin(item)) { const char *jid = xmlnode_get_attrib(item, "jid"); const char *affiliation = xmlnode_get_attrib(item, "affiliation"); if (jid && affiliation) g_string_append_printf(buf, "\n%s %s", jid, affiliation); } } else { buf = g_string_append_c(buf, '\n'); buf = g_string_append_len(buf, _("No users found"), -1); } purple_conv_chat_write(PURPLE_CONV_CHAT(chat->conv), "", buf->str, PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG, time(NULL)); g_string_free(buf, TRUE); } gboolean jabber_chat_affiliation_list(JabberChat *chat, const char *affiliation) { JabberIq *iq; char *room_jid; xmlnode *query, *item; iq = jabber_iq_new_query(chat->js, JABBER_IQ_GET, "http://jabber.org/protocol/muc#admin"); room_jid = g_strdup_printf("%s@%s", chat->room, chat->server); xmlnode_set_attrib(iq->node, "to", room_jid); query = xmlnode_get_child(iq->node, "query"); item = xmlnode_new_child(query, "item"); xmlnode_set_attrib(item, "affiliation", affiliation); jabber_iq_set_callback(iq, jabber_chat_affiliation_list_cb, GINT_TO_POINTER(chat->id)); jabber_iq_send(iq); return TRUE; } gboolean jabber_chat_role_user(JabberChat *chat, const char *who, const char *role) { char *to; JabberIq *iq; xmlnode *query, *item; JabberChatMember *jcm; jcm = g_hash_table_lookup(chat->members, who); if (!jcm || !jcm->handle) return FALSE; iq = jabber_iq_new_query(chat->js, JABBER_IQ_SET, "http://jabber.org/protocol/muc#admin"); to = g_strdup_printf("%s@%s", chat->room, chat->server); xmlnode_set_attrib(iq->node, "to", to); g_free(to); query = xmlnode_get_child(iq->node, "query"); item = xmlnode_new_child(query, "item"); xmlnode_set_attrib(item, "nick", jcm->handle); xmlnode_set_attrib(item, "role", role); jabber_iq_send(iq); return TRUE; } static void jabber_chat_role_list_cb(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *packet, gpointer data) { JabberChat *chat; xmlnode *query, *item; int chat_id = GPOINTER_TO_INT(data); GString *buf; if(!(chat = jabber_chat_find_by_id(js, chat_id))) return; if (type == JABBER_IQ_ERROR) return; if(!(query = xmlnode_get_child(packet, "query"))) return; buf = g_string_new(_("Roles:")); item = xmlnode_get_child(query, "item"); if (item) { for( ; item; item = xmlnode_get_next_twin(item)) { const char *jid = xmlnode_get_attrib(item, "jid"); const char *role = xmlnode_get_attrib(item, "role"); if (jid && role) g_string_append_printf(buf, "\n%s %s", jid, role); } } else { buf = g_string_append_c(buf, '\n'); buf = g_string_append_len(buf, _("No users found"), -1); } purple_conv_chat_write(PURPLE_CONV_CHAT(chat->conv), "", buf->str, PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG, time(NULL)); g_string_free(buf, TRUE); } gboolean jabber_chat_role_list(JabberChat *chat, const char *role) { JabberIq *iq; char *room_jid; xmlnode *query, *item; iq = jabber_iq_new_query(chat->js, JABBER_IQ_GET, "http://jabber.org/protocol/muc#admin"); room_jid = g_strdup_printf("%s@%s", chat->room, chat->server); xmlnode_set_attrib(iq->node, "to", room_jid); query = xmlnode_get_child(iq->node, "query"); item = xmlnode_new_child(query, "item"); xmlnode_set_attrib(item, "role", role); jabber_iq_set_callback(iq, jabber_chat_role_list_cb, GINT_TO_POINTER(chat->id)); jabber_iq_send(iq); return TRUE; } gboolean jabber_chat_kick_user(JabberChat *chat, const char *who, const char *why) { JabberIq *iq; JabberChatMember *jcm = g_hash_table_lookup(chat->members, who); char *to; xmlnode *query, *item, *reason; if(!jcm || !jcm->jid) return FALSE; iq = jabber_iq_new_query(chat->js, JABBER_IQ_SET, "http://jabber.org/protocol/muc#admin"); to = g_strdup_printf("%s@%s", chat->room, chat->server); xmlnode_set_attrib(iq->node, "to", to); g_free(to); query = xmlnode_get_child(iq->node, "query"); item = xmlnode_new_child(query, "item"); xmlnode_set_attrib(item, "jid", jcm->jid); xmlnode_set_attrib(item, "role", "none"); if(why) { reason = xmlnode_new_child(item, "reason"); xmlnode_insert_data(reason, why, -1); } jabber_iq_send(iq); return TRUE; } static void jabber_chat_disco_traffic_cb(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *packet, gpointer data) { JabberChat *chat; #if 0 xmlnode *query, *x; #endif int chat_id = GPOINTER_TO_INT(data); if(!(chat = jabber_chat_find_by_id(js, chat_id))) return; /* defaults, in case the conference server doesn't * support this request */ chat->xhtml = TRUE; /* disabling this until more MUC servers support * announcing this */ #if 0 if (type == JABBER_IQ_ERROR) { return; } if(!(query = xmlnode_get_child(packet, "query"))) return; chat->xhtml = FALSE; for(x = xmlnode_get_child(query, "feature"); x; x = xmlnode_get_next_twin(x)) { const char *var = xmlnode_get_attrib(x, "var"); if(var && !strcmp(var, "http://jabber.org/protocol/xhtml-im")) { chat->xhtml = TRUE; } } #endif } void jabber_chat_disco_traffic(JabberChat *chat) { JabberIq *iq; xmlnode *query; char *room_jid; room_jid = g_strdup_printf("%s@%s", chat->room, chat->server); iq = jabber_iq_new_query(chat->js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#info"); xmlnode_set_attrib(iq->node, "to", room_jid); query = xmlnode_get_child(iq->node, "query"); xmlnode_set_attrib(query, "node", "http://jabber.org/protocol/muc#traffic"); jabber_iq_set_callback(iq, jabber_chat_disco_traffic_cb, GINT_TO_POINTER(chat->id)); jabber_iq_send(iq); g_free(room_jid); } typedef struct { const gchar *cap; gboolean *all_support; JabberBuddy *jb; } JabberChatCapsData; static void jabber_chat_all_participants_have_capability_foreach(gpointer key, gpointer value, gpointer user_data) { const gchar *cap = ((JabberChatCapsData *) user_data)->cap; gboolean *all_support = ((JabberChatCapsData *) user_data)->all_support; JabberBuddy *jb = ((JabberChatCapsData *) user_data)->jb; JabberChatMember *member = (JabberChatMember *) value; const gchar *resource = member->handle; JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource); if (jbr) { *all_support &= jabber_resource_has_capability(jbr, cap); } else { *all_support = FALSE; } } gboolean jabber_chat_all_participants_have_capability(const JabberChat *chat, const gchar *cap) { gchar *chat_jid = NULL; JabberBuddy *jb = NULL; gboolean all_support = TRUE; JabberChatCapsData data; chat_jid = g_strdup_printf("%s@%s", chat->room, chat->server); jb = jabber_buddy_find(chat->js, chat_jid, FALSE); if (jb) { data.cap = cap; data.all_support = &all_support; data.jb = jb; g_hash_table_foreach(chat->members, jabber_chat_all_participants_have_capability_foreach, &data); } else { all_support = FALSE; } g_free(chat_jid); return all_support; } guint jabber_chat_get_num_participants(const JabberChat *chat) { return g_hash_table_size(chat->members); }