/* * gaim - Jabber Protocol Plugin * * Copyright (C) 2003, Nathan Walp * * 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" /* 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" GList *jabber_chat_info(GaimConnection *gc) { GList *m = NULL; struct proto_chat_entry *pce; JabberStream *js = gc->proto_data; pce = g_new0(struct proto_chat_entry, 1); pce->label = _("_Room:"); pce->identifier = "room"; m = g_list_append(m, pce); pce = g_new0(struct proto_chat_entry, 1); pce->label = _("_Server:"); pce->identifier = "server"; pce->def = js->chat_servers ? js->chat_servers->data : "conference.jabber.org"; m = g_list_append(m, pce); pce = g_new0(struct proto_chat_entry, 1); pce->label = _("_Handle:"); pce->identifier = "handle"; pce->def = js->user->node; 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; } JabberChat *jabber_chat_find(JabberStream *js, const char *room, const char *server) { JabberChat *chat; char *room_jid; room_jid = g_strdup_printf("%s@%s", room, server); chat = g_hash_table_lookup(js->chats, jabber_normalize(NULL, room_jid)); g_free(room_jid); return chat; } struct _find_by_id_data { int id; JabberChat *chat; }; 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(GaimConversation *conv) { GaimAccount *account = gaim_conversation_get_account(conv); GaimConnection *gc = gaim_account_get_connection(account); JabberStream *js = gc->proto_data; int id = gaim_conv_chat_get_id(GAIM_CONV_CHAT(conv)); return jabber_chat_find_by_id(js, id); } void jabber_chat_invite(GaimConnection *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_attrib(x, "xmlns", "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); 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); xmlnode_set_attrib(x, "xmlns", "jabber:x:conference"); } jabber_send(js, message); xmlnode_free(message); g_free(room_jid); } void jabber_chat_member_free(JabberChatMember *jcm); void jabber_chat_join(GaimConnection *gc, GHashTable *data) { JabberChat *chat; char *room, *server, *handle, *passwd; xmlnode *presence, *x; char *tmp, *room_jid, *full_jid; JabberStream *js = gc->proto_data; 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); gaim_notify_error(gc, _("Invalid Room Name"), _("Invalid Room Name"), buf); g_free(buf); return; } else if(!jabber_nameprep_validate(server)) { char *buf = g_strdup_printf(_("%s is not a valid server name"), server); gaim_notify_error(gc, _("Invalid Server Name"), _("Invalid Server Name"), buf); g_free(buf); return; } else if(!jabber_resourceprep_validate(handle)) { char *buf = g_strdup_printf(_("%s is not a valid room handle"), handle); gaim_notify_error(gc, _("Invalid Room Handle"), _("Invalid Room Handle"), buf); } if(jabber_chat_find(js, room, server)) return; tmp = g_strdup_printf("%s@%s", room, server); room_jid = g_strdup(jabber_normalize(NULL, tmp)); g_free(tmp); chat = g_new0(JabberChat, 1); chat->js = gc->proto_data; chat->room = g_strdup(room); chat->server = g_strdup(server); chat->handle = g_strdup(handle); chat->members = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)jabber_chat_member_free); g_hash_table_insert(js->chats, room_jid, chat); presence = jabber_presence_create(gc->away_state, gc->away); full_jid = g_strdup_printf("%s/%s", room_jid, handle); xmlnode_set_attrib(presence, "to", full_jid); g_free(full_jid); x = xmlnode_new_child(presence, "x"); xmlnode_set_attrib(x, "xmlns", "http://jabber.org/protocol/muc"); if(passwd && *passwd) { xmlnode *password = xmlnode_new_child(x, "password"); xmlnode_insert_data(password, passwd, -1); } jabber_send(js, presence); xmlnode_free(presence); } void jabber_chat_leave(GaimConnection *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->conv = NULL; } 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, jabber_normalize(NULL, room_jid)); g_free(room_jid); } void jabber_chat_free(JabberChat *chat) { if(chat->config_dialog_handle) gaim_request_close(chat->config_dialog_type, chat->config_dialog_handle); g_free(chat->room); g_free(chat->server); g_free(chat); } gboolean jabber_chat_find_buddy(GaimConversation *conv, const char *name) { return gaim_conv_chat_find_user(GAIM_CONV_CHAT(conv), name); } char *jabber_chat_buddy_real_name(GaimConnection *gc, int id, const char *who) { JabberStream *js = gc->proto_data; JabberChat *chat; chat = jabber_chat_find_by_id(js, id); if(!chat) return NULL; 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, xmlnode *packet, gpointer data) { xmlnode *query, *x; const char *type = xmlnode_get_attrib(packet, "type"); const char *from = xmlnode_get_attrib(packet, "from"); char *msg; JabberChat *chat; JabberID *jid; if(!type || !from) return; if(!strcmp(type, "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_attrib(x, "xmlns"))) continue; if(!strcmp(xmlns, "jabber:x:data")) { chat->config_dialog_type = GAIM_REQUEST_FIELDS; chat->config_dialog_handle = jabber_x_data_request(js, x, jabber_chat_room_configure_x_data_cb, chat); return; } } } else if(!strcmp(type, "error")) { char *msg = jabber_parse_error(js, packet); gaim_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); gaim_notify_info(js->gc, _("Unable to configure"), _("Unable to configure"), msg); g_free(msg); } void jabber_chat_request_room_configure(JabberChat *chat) { JabberIq *iq; xmlnode *query; char *room_jid; if(!chat) return; chat->config_dialog_handle = NULL; if(!chat->muc) { gaim_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_SET, "http://jabber.org/protocol/muc#owner"); query = xmlnode_get_child(iq->node, "query"); 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_attrib(x, "xmlns", "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, xmlnode *packet, gpointer data) { const char *type = xmlnode_get_attrib(packet, "type"); if(type && !strcmp(type, "error")) { char *msg = jabber_parse_error(js, packet); gaim_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, xmlnode *packet, gpointer data) { xmlnode *query, *x; const char *type = xmlnode_get_attrib(packet, "type"); const char *from = xmlnode_get_attrib(packet, "from"); char *msg; JabberChat *chat; JabberID *jid; if(!type || !from) return; if(!strcmp(type, "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_attrib(x, "xmlns"))) continue; if(!strcmp(xmlns, "jabber:x:data")) { jabber_x_data_request(js, x, jabber_chat_register_x_data_cb, chat); return; } } } else if(!strcmp(type, "error")) { char *msg = jabber_parse_error(js, packet); gaim_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); gaim_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) { if(topic && *topic) { JabberMessage *jm; jm = g_new0(JabberMessage, 1); jm->js = chat->js; jm->type = JABBER_MESSAGE_GROUPCHAT; jm->subject = gaim_markup_strip_html(topic); jm->to = g_strdup_printf("%s@%s", chat->room, chat->server); jabber_message_send(jm); jabber_message_free(jm); } else { const char *cur = gaim_conv_chat_get_topic(GAIM_CONV_CHAT(chat->conv)); char *buf; if(cur) buf = g_strdup_printf(_("current topic is: %s"), cur); else buf = g_strdup(_("No topic is set")); gaim_conv_chat_write(GAIM_CONV_CHAT(chat->conv), "", buf, GAIM_MESSAGE_SYSTEM | GAIM_MESSAGE_NO_LOG, time(NULL)); g_free(buf); } } void jabber_chat_set_topic(GaimConnection *gc, int id, const char *topic) { JabberStream *js = gc->proto_data; JabberChat *chat = jabber_chat_find_by_id(js, id); if(!chat) return; jabber_chat_change_topic(chat, topic); } void jabber_chat_change_nick(JabberChat *chat, const char *nick) { xmlnode *presence; char *full_jid; if(!chat->muc) { gaim_conv_chat_write(GAIM_CONV_CHAT(chat->conv), "", _("Nick changing not supported in non-MUC chatrooms"), GAIM_MESSAGE_SYSTEM, time(NULL)); return; } presence = jabber_presence_create(chat->js->gc->away_state, chat->js->gc->away); full_jid = g_strdup_printf("%s@%s/%s", chat->room, chat->server, nick); xmlnode_set_attrib(presence, "to", full_jid); g_free(full_jid); jabber_send(chat->js, presence); xmlnode_free(presence); } 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, xmlnode *packet, gpointer data) { xmlnode *query; xmlnode *item; const char *type; if(!js->roomlist) return; if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "result")) { char *err = jabber_parse_error(js,packet); gaim_notify_error(js->gc, _("Roomlist Error"), _("Error retreiving roomlist"), err); gaim_roomlist_set_in_progress(js->roomlist, FALSE); gaim_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); gaim_notify_error(js->gc, _("Roomlist Error"), _("Error retreiving roomlist"), err); gaim_roomlist_set_in_progress(js->roomlist, FALSE); gaim_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; GaimRoomlistRoom *room; JabberID *jid; if(!(jid = jabber_id_new(xmlnode_get_attrib(item, "jid")))) continue; name = xmlnode_get_attrib(item, "name"); room = gaim_roomlist_room_new(GAIM_ROOMLIST_ROOMTYPE_ROOM, jid->node, NULL); gaim_roomlist_room_add_field(js->roomlist, room, jid->node); gaim_roomlist_room_add_field(js->roomlist, room, jid->domain); gaim_roomlist_room_add_field(js->roomlist, room, name ? name : ""); gaim_roomlist_room_add(js->roomlist, room); jabber_id_free(jid); } gaim_roomlist_set_in_progress(js->roomlist, FALSE); gaim_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) { gaim_notify_error(js->gc, _("Invalid Server"), _("Invalid Server"), NULL); return; } gaim_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); } GaimRoomlist *jabber_roomlist_get_list(GaimConnection *gc) { JabberStream *js = gc->proto_data; GList *fields = NULL; GaimRoomlistField *f; if(js->roomlist) gaim_roomlist_unref(js->roomlist); js->roomlist = gaim_roomlist_new(gaim_connection_get_account(gc)); f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, "", "room", TRUE); fields = g_list_append(fields, f); f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, "", "server", TRUE); fields = g_list_append(fields, f); f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, _("Description"), "description", FALSE); fields = g_list_append(fields, f); gaim_roomlist_set_fields(js->roomlist, fields); gaim_request_input(gc, _("Enter a Conference Server"), _("Enter a Conference Server"), _("Select a conference server to query"), js->chat_servers ? js->chat_servers->data : "conference.jabber.org", FALSE, FALSE, NULL, _("Find Rooms"), G_CALLBACK(roomlist_ok_cb), _("Cancel"), NULL, js); return js->roomlist; } void jabber_roomlist_cancel(GaimRoomlist *list) { GaimConnection *gc; JabberStream *js; gc = gaim_account_get_connection(list->account); js = gc->proto_data; gaim_roomlist_set_in_progress(list, FALSE); if (js->roomlist == list) { js->roomlist = NULL; gaim_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) { 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, "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_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; }