2 * purple - Jabber Protocol Plugin
4 * Purple is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
30 #include "google/google.h"
31 #include "google/google_roster.h"
38 /* Take a list of strings and join them with a ", " separator */
39 static gchar
*roster_groups_join(GSList
*list
)
41 GString
*out
= g_string_new(NULL
);
42 for ( ; list
; list
= list
->next
) {
43 out
= g_string_append(out
, (const char *)list
->data
);
45 out
= g_string_append(out
, ", ");
48 return g_string_free(out
, FALSE
);
51 static void roster_request_cb(JabberStream
*js
, const char *from
,
52 JabberIqType type
, const char *id
,
53 PurpleXmlNode
*packet
, gpointer data
)
57 if (type
== JABBER_IQ_ERROR
) {
59 * This shouldn't happen in any real circumstances and
60 * likely constitutes a server-side misconfiguration (i.e.
61 * explicitly not loading mod_roster...)
63 purple_debug_error("jabber", "Error retrieving roster!?\n");
64 jabber_stream_set_state(js
, JABBER_STREAM_CONNECTED
);
68 query
= purple_xmlnode_get_child(packet
, "query");
70 jabber_stream_set_state(js
, JABBER_STREAM_CONNECTED
);
74 jabber_roster_parse(js
, from
, type
, id
, query
);
75 jabber_stream_set_state(js
, JABBER_STREAM_CONNECTED
);
78 void jabber_roster_request(JabberStream
*js
)
83 iq
= jabber_iq_new_query(js
, JABBER_IQ_GET
, "jabber:iq:roster");
84 query
= purple_xmlnode_get_child(iq
->node
, "query");
86 if (js
->server_caps
& JABBER_CAP_GOOGLE_ROSTER
) {
87 purple_xmlnode_set_attrib(query
, "xmlns:gr", NS_GOOGLE_ROSTER
);
88 purple_xmlnode_set_attrib(query
, "gr:ext", "2");
91 jabber_iq_set_callback(iq
, roster_request_cb
, NULL
);
95 static void remove_purple_buddies(JabberStream
*js
, const char *jid
)
99 buddies
= purple_blist_find_buddies(purple_connection_get_account(js
->gc
), jid
);
101 g_slist_free_full(buddies
, (GDestroyNotify
)purple_blist_remove_buddy
);
104 static void add_purple_buddy_to_groups(JabberStream
*js
, const char *jid
,
105 const char *alias
, GSList
*groups
)
108 PurpleAccount
*account
= purple_connection_get_account(js
->gc
);
110 buddies
= purple_blist_find_buddies(purple_connection_get_account(js
->gc
), jid
);
114 groups
= g_slist_append(groups
, g_strdup(JABBER_ROSTER_DEFAULT_GROUP
));
116 /* TODO: What should we do here? Removing the local buddies
117 * is wrong, but so is letting the group state get out of sync with
120 g_slist_free(buddies
);
126 PurpleBuddy
*b
= buddies
->data
;
127 PurpleGroup
*g
= purple_buddy_get_group(b
);
129 buddies
= g_slist_delete_link(buddies
, buddies
);
131 /* XMPP groups are case-sensitive, but libpurple groups are
132 * case-insensitive. We treat a buddy in both "Friends" and "friends"
133 * as only being in one group, but if we push changes about the buddy
134 * to the server, the buddy will be dropped from one of the groups.
135 * Not optimal, but better than the alternative, I think.
137 l
= g_slist_find_custom(groups
, purple_group_get_name(g
),
138 (GCompareFunc
)purple_utf8_strcasecmp
);
139 if (!l
&& g
== purple_blist_get_default_group()) {
140 l
= g_slist_find_custom(groups
, JABBER_ROSTER_DEFAULT_GROUP
,
141 (GCompareFunc
)purple_utf8_strcasecmp
);
143 if (!l
&& g
== purple_blist_get_default_group()) {
144 l
= g_slist_find_custom(groups
,
145 _purple_blist_get_localized_default_group_name(),
146 (GCompareFunc
)purple_utf8_strcasecmp
);
150 /* The buddy is already on the local list. Update info. */
151 const char *servernick
, *balias
;
153 /* Previously stored serverside / buddy-supplied alias */
154 if((servernick
= purple_blist_node_get_string((PurpleBlistNode
*)b
, "servernick")))
155 purple_serv_got_alias(js
->gc
, jid
, servernick
);
157 /* Alias from our roster retrieval */
158 balias
= purple_buddy_get_local_alias(b
);
159 if(alias
&& !purple_strequal(alias
, balias
))
160 purple_serv_got_private_alias(js
->gc
, jid
, alias
);
162 groups
= g_slist_delete_link(groups
, l
);
164 /* This buddy isn't in the group on the server anymore */
165 purple_debug_info("jabber", "jabber_roster_parse(): "
166 "Removing %s from group '%s' on the local list",
167 purple_buddy_get_name(b
),
168 jabber_roster_group_get_global_name(g
));
169 purple_blist_remove_buddy(b
);
174 char *tmp
= roster_groups_join(groups
);
175 purple_debug_info("jabber", "jabber_roster_parse(): Adding %s to "
176 "groups: %s\n", jid
, tmp
);
181 PurpleGroup
*g
= purple_blist_find_group(groups
->data
);
182 PurpleBuddy
*b
= purple_buddy_new(account
, jid
, alias
);
185 g
= purple_group_new(groups
->data
);
186 purple_blist_add_group(g
, NULL
);
189 purple_blist_add_buddy(b
, NULL
, g
, NULL
);
190 purple_buddy_set_local_alias(b
, alias
);
192 g_free(groups
->data
);
193 groups
= g_slist_delete_link(groups
, groups
);
197 void jabber_roster_parse(JabberStream
*js
, const char *from
,
198 JabberIqType type
, const char *id
, PurpleXmlNode
*query
)
200 PurpleXmlNode
*item
, *group
;
202 if (!jabber_is_own_account(js
, from
)) {
203 purple_debug_warning("jabber", "Received bogon roster push from %s\n",
208 js
->currently_parsing_roster_push
= TRUE
;
210 for(item
= purple_xmlnode_get_child(query
, "item"); item
; item
= purple_xmlnode_get_next_twin(item
))
212 const char *jid
, *name
, *subscription
, *ask
;
215 subscription
= purple_xmlnode_get_attrib(item
, "subscription");
216 jid
= purple_xmlnode_get_attrib(item
, "jid");
217 name
= purple_xmlnode_get_attrib(item
, "name");
218 ask
= purple_xmlnode_get_attrib(item
, "ask");
223 if(!(jb
= jabber_buddy_find(js
, jid
, TRUE
)))
227 if (purple_strequal(subscription
, "remove"))
228 jb
->subscription
= JABBER_SUB_REMOVE
;
229 else if (jb
== js
->user_jb
)
230 jb
->subscription
= JABBER_SUB_BOTH
;
231 else if (purple_strequal(subscription
, "none"))
232 jb
->subscription
= JABBER_SUB_NONE
;
233 else if (purple_strequal(subscription
, "to"))
234 jb
->subscription
= JABBER_SUB_TO
;
235 else if (purple_strequal(subscription
, "from"))
236 jb
->subscription
= JABBER_SUB_FROM
;
237 else if (purple_strequal(subscription
, "both"))
238 jb
->subscription
= JABBER_SUB_BOTH
;
241 if(purple_strequal(ask
, "subscribe"))
242 jb
->subscription
|= JABBER_SUB_PENDING
;
244 jb
->subscription
&= ~JABBER_SUB_PENDING
;
246 if(jb
->subscription
& JABBER_SUB_REMOVE
) {
247 remove_purple_buddies(js
, jid
);
249 GSList
*groups
= NULL
;
251 if (js
->server_caps
& JABBER_CAP_GOOGLE_ROSTER
)
252 if (!jabber_google_roster_incoming(js
, item
))
255 for(group
= purple_xmlnode_get_child(item
, "group"); group
; group
= purple_xmlnode_get_next_twin(group
)) {
256 char *group_name
= purple_xmlnode_get_data(group
);
258 if (group_name
== NULL
|| *group_name
== '\0')
259 /* Changing this string? Look in add_purple_buddy_to_groups */
260 group_name
= g_strdup(JABBER_ROSTER_DEFAULT_GROUP
);
263 * See the note in add_purple_buddy_to_groups; the core handles
264 * names case-insensitively and this is required to not
265 * end up with duplicates if a buddy is in, e.g.,
268 if (g_slist_find_custom(groups
, group_name
, (GCompareFunc
)purple_utf8_strcasecmp
))
271 groups
= g_slist_prepend(groups
, group_name
);
274 add_purple_buddy_to_groups(js
, jid
, name
, groups
);
275 if (jb
== js
->user_jb
)
276 jabber_presence_fake_to_self(js
, NULL
);
280 if (type
== JABBER_IQ_SET
) {
281 JabberIq
*ack
= jabber_iq_new(js
, JABBER_IQ_RESULT
);
282 jabber_iq_set_id(ack
, id
);
286 js
->currently_parsing_roster_push
= FALSE
;
289 /* jabber_roster_update frees the GSList* passed in */
290 static void jabber_roster_update(JabberStream
*js
, const char *name
,
297 PurpleXmlNode
*query
, *item
, *group
;
300 if (js
->currently_parsing_roster_push
)
303 if(!(b
= purple_blist_find_buddy(purple_connection_get_account(js
->gc
), name
)))
307 char *tmp
= roster_groups_join(groups
);
309 purple_debug_info("jabber", "jabber_roster_update(%s): [Source: "
310 "groups]: groups: %s\n", name
, tmp
);
313 GSList
*buddies
= purple_blist_find_buddies(purple_connection_get_account(js
->gc
), name
);
320 g
= purple_buddy_get_group(b
);
321 groups
= g_slist_append(groups
,
322 (char *)jabber_roster_group_get_global_name(g
));
323 buddies
= g_slist_remove(buddies
, b
);
326 tmp
= roster_groups_join(groups
);
327 purple_debug_info("jabber", "jabber_roster_update(%s): [Source: local blist]: groups: %s\n",
332 iq
= jabber_iq_new_query(js
, JABBER_IQ_SET
, "jabber:iq:roster");
334 query
= purple_xmlnode_get_child(iq
->node
, "query");
335 item
= purple_xmlnode_new_child(query
, "item");
337 purple_xmlnode_set_attrib(item
, "jid", name
);
339 balias
= purple_buddy_get_local_alias(b
);
340 purple_xmlnode_set_attrib(item
, "name", balias
? balias
: "");
342 for(l
= groups
; l
; l
= l
->next
) {
343 group
= purple_xmlnode_new_child(item
, "group");
344 purple_xmlnode_insert_data(group
, l
->data
, -1);
347 g_slist_free(groups
);
349 if (js
->server_caps
& JABBER_CAP_GOOGLE_ROSTER
) {
350 jabber_google_roster_outgoing(js
, query
, item
);
351 purple_xmlnode_set_attrib(query
, "xmlns:gr", NS_GOOGLE_ROSTER
);
352 purple_xmlnode_set_attrib(query
, "gr:ext", "2");
357 void jabber_roster_add_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
,
358 PurpleGroup
*group
, const char *message
)
360 JabberStream
*js
= purple_connection_get_protocol_data(gc
);
364 JabberBuddyResource
*jbr
;
367 /* If we haven't received the roster yet, ignore any adds */
368 if (js
->state
!= JABBER_STREAM_CONNECTED
)
371 name
= purple_buddy_get_name(buddy
);
372 jid
= jabber_id_new(name
);
374 /* TODO: Remove the buddy from the list? */
378 /* Adding a chat room or a chat user to the roster is *not* supported. */
379 if (jid
->node
&& jabber_chat_find(js
, jid
->node
, jid
->domain
) != NULL
) {
381 * This is the same thing Bonjour does. If it causes problems, move
382 * it to an idle callback.
384 purple_debug_warning("jabber", "Cowardly refusing to add a MUC user "
385 "to your buddy list and removing the buddy. "
386 "Buddies can only be added by real (non-MUC) "
388 purple_blist_remove_buddy(buddy
);
393 who
= jabber_id_get_bare_jid(jid
);
394 if (jid
->resource
!= NULL
) {
396 * If the buddy name added contains a resource, strip that off and
399 purple_buddy_set_name(buddy
, who
);
402 jb
= jabber_buddy_find(js
, who
, FALSE
);
404 purple_debug_info("jabber", "jabber_roster_add_buddy(): Adding %s\n", who
);
406 jabber_roster_update(js
, who
, NULL
);
408 if (jb
== js
->user_jb
) {
409 jabber_presence_fake_to_self(js
, NULL
);
410 } else if(!jb
|| !(jb
->subscription
& JABBER_SUB_TO
)) {
411 jabber_presence_subscription_set(js
, who
, "subscribe");
412 } else if((jbr
=jabber_buddy_find_resource(jb
, NULL
))) {
413 purple_protocol_got_user_status(purple_connection_get_account(gc
), who
,
414 jabber_buddy_state_get_status_id(jbr
->state
),
415 "priority", jbr
->priority
, jbr
->status
? "message" : NULL
, jbr
->status
, NULL
);
421 void jabber_roster_alias_change(PurpleConnection
*gc
, const char *name
, const char *alias
)
423 PurpleBuddy
*b
= purple_blist_find_buddy(purple_connection_get_account(gc
), name
);
426 purple_buddy_set_local_alias(b
, alias
);
428 purple_debug_info("jabber", "jabber_roster_alias_change(): Aliased %s to %s\n",
429 name
, alias
? alias
: "(null)");
431 jabber_roster_update(purple_connection_get_protocol_data(gc
), name
, NULL
);
435 void jabber_roster_group_change(PurpleConnection
*gc
, const char *name
,
436 const char *old_group
, const char *new_group
)
438 GSList
*buddies
, *groups
= NULL
;
441 if(!old_group
|| !new_group
|| purple_strequal(old_group
, new_group
))
444 buddies
= purple_blist_find_buddies(purple_connection_get_account(gc
), name
);
447 groups
= g_slist_append(groups
, (char*)new_group
);
448 buddies
= g_slist_remove(buddies
, b
);
451 purple_debug_info("jabber", "jabber_roster_group_change(): Moving %s from %s to %s\n",
452 name
, old_group
, new_group
);
454 jabber_roster_update(purple_connection_get_protocol_data(gc
), name
, groups
);
457 void jabber_roster_group_rename(PurpleConnection
*gc
, const char *old_name
,
458 PurpleGroup
*group
, GList
*moved_buddies
)
461 const char *gname
= jabber_roster_group_get_global_name(group
);
462 for(l
= moved_buddies
; l
; l
= l
->next
) {
463 PurpleBuddy
*buddy
= l
->data
;
464 jabber_roster_group_change(gc
, purple_buddy_get_name(buddy
), old_name
, gname
);
468 void jabber_roster_remove_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
,
469 PurpleGroup
*group
) {
470 const char *name
= purple_buddy_get_name(buddy
);
471 GSList
*buddies
= purple_blist_find_buddies(purple_connection_get_account(gc
), name
);
473 buddies
= g_slist_remove(buddies
, buddy
);
474 if(buddies
!= NULL
) {
475 PurpleBuddy
*tmpbuddy
;
476 PurpleGroup
*tmpgroup
;
477 GSList
*groups
= NULL
;
480 tmpbuddy
= buddies
->data
;
481 tmpgroup
= purple_buddy_get_group(tmpbuddy
);
482 groups
= g_slist_append(groups
,
483 (char *)jabber_roster_group_get_global_name(tmpgroup
));
484 buddies
= g_slist_remove(buddies
, tmpbuddy
);
487 purple_debug_info("jabber", "jabber_roster_remove_buddy(): "
488 "Removing %s from %s", purple_buddy_get_name(buddy
),
489 jabber_roster_group_get_global_name(group
));
491 jabber_roster_update(purple_connection_get_protocol_data(gc
), name
, groups
);
493 JabberIq
*iq
= jabber_iq_new_query(purple_connection_get_protocol_data(gc
), JABBER_IQ_SET
,
495 PurpleXmlNode
*query
= purple_xmlnode_get_child(iq
->node
, "query");
496 PurpleXmlNode
*item
= purple_xmlnode_new_child(query
, "item");
498 purple_xmlnode_set_attrib(item
, "jid", name
);
499 purple_xmlnode_set_attrib(item
, "subscription", "remove");
501 purple_debug_info("jabber", "jabber_roster_remove_buddy(): Removing %s\n",
502 purple_buddy_get_name(buddy
));
509 jabber_roster_group_get_global_name(PurpleGroup
*group
)
511 const gchar
*name
= NULL
;
514 name
= purple_group_get_name(group
);
517 name
= JABBER_ROSTER_DEFAULT_GROUP
;
518 else if (purple_strequal(name
, PURPLE_BLIST_DEFAULT_GROUP_NAME
))
519 name
= JABBER_ROSTER_DEFAULT_GROUP
;
520 else if (purple_strequal(name
, _purple_blist_get_localized_default_group_name()))
521 name
= JABBER_ROSTER_DEFAULT_GROUP
;