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 for(l
= buddies
; l
; l
= l
->next
)
102 purple_blist_remove_buddy(l
->data
);
104 g_slist_free(buddies
);
107 static void add_purple_buddy_to_groups(JabberStream
*js
, const char *jid
,
108 const char *alias
, GSList
*groups
)
111 PurpleAccount
*account
= purple_connection_get_account(js
->gc
);
113 buddies
= purple_blist_find_buddies(purple_connection_get_account(js
->gc
), jid
);
117 groups
= g_slist_append(groups
, g_strdup(JABBER_ROSTER_DEFAULT_GROUP
));
119 /* TODO: What should we do here? Removing the local buddies
120 * is wrong, but so is letting the group state get out of sync with
123 g_slist_free(buddies
);
129 PurpleBuddy
*b
= buddies
->data
;
130 PurpleGroup
*g
= purple_buddy_get_group(b
);
132 buddies
= g_slist_delete_link(buddies
, buddies
);
134 /* XMPP groups are case-sensitive, but libpurple groups are
135 * case-insensitive. We treat a buddy in both "Friends" and "friends"
136 * as only being in one group, but if we push changes about the buddy
137 * to the server, the buddy will be dropped from one of the groups.
138 * Not optimal, but better than the alternative, I think.
140 l
= g_slist_find_custom(groups
, purple_group_get_name(g
),
141 (GCompareFunc
)purple_utf8_strcasecmp
);
142 if (!l
&& g
== purple_blist_get_default_group()) {
143 l
= g_slist_find_custom(groups
, JABBER_ROSTER_DEFAULT_GROUP
,
144 (GCompareFunc
)purple_utf8_strcasecmp
);
146 if (!l
&& g
== purple_blist_get_default_group()) {
147 l
= g_slist_find_custom(groups
,
148 _purple_blist_get_localized_default_group_name(),
149 (GCompareFunc
)purple_utf8_strcasecmp
);
153 /* The buddy is already on the local list. Update info. */
154 const char *servernick
, *balias
;
156 /* Previously stored serverside / buddy-supplied alias */
157 if((servernick
= purple_blist_node_get_string((PurpleBlistNode
*)b
, "servernick")))
158 purple_serv_got_alias(js
->gc
, jid
, servernick
);
160 /* Alias from our roster retrieval */
161 balias
= purple_buddy_get_local_alias(b
);
162 if(alias
&& !purple_strequal(alias
, balias
))
163 purple_serv_got_private_alias(js
->gc
, jid
, alias
);
165 groups
= g_slist_delete_link(groups
, l
);
167 /* This buddy isn't in the group on the server anymore */
168 purple_debug_info("jabber", "jabber_roster_parse(): "
169 "Removing %s from group '%s' on the local list",
170 purple_buddy_get_name(b
),
171 jabber_roster_group_get_global_name(g
));
172 purple_blist_remove_buddy(b
);
177 char *tmp
= roster_groups_join(groups
);
178 purple_debug_info("jabber", "jabber_roster_parse(): Adding %s to "
179 "groups: %s\n", jid
, tmp
);
184 PurpleGroup
*g
= purple_blist_find_group(groups
->data
);
185 PurpleBuddy
*b
= purple_buddy_new(account
, jid
, alias
);
188 g
= purple_group_new(groups
->data
);
189 purple_blist_add_group(g
, NULL
);
192 purple_blist_add_buddy(b
, NULL
, g
, NULL
);
193 purple_buddy_set_local_alias(b
, alias
);
195 g_free(groups
->data
);
196 groups
= g_slist_delete_link(groups
, groups
);
199 g_slist_free(buddies
);
202 void jabber_roster_parse(JabberStream
*js
, const char *from
,
203 JabberIqType type
, const char *id
, PurpleXmlNode
*query
)
205 PurpleXmlNode
*item
, *group
;
210 if (!jabber_is_own_account(js
, from
)) {
211 purple_debug_warning("jabber", "Received bogon roster push from %s\n",
216 js
->currently_parsing_roster_push
= TRUE
;
218 for(item
= purple_xmlnode_get_child(query
, "item"); item
; item
= purple_xmlnode_get_next_twin(item
))
220 const char *jid
, *name
, *subscription
, *ask
;
223 subscription
= purple_xmlnode_get_attrib(item
, "subscription");
224 jid
= purple_xmlnode_get_attrib(item
, "jid");
225 name
= purple_xmlnode_get_attrib(item
, "name");
226 ask
= purple_xmlnode_get_attrib(item
, "ask");
231 if(!(jb
= jabber_buddy_find(js
, jid
, TRUE
)))
235 if (g_str_equal(subscription
, "remove"))
236 jb
->subscription
= JABBER_SUB_REMOVE
;
237 else if (jb
== js
->user_jb
)
238 jb
->subscription
= JABBER_SUB_BOTH
;
239 else if (g_str_equal(subscription
, "none"))
240 jb
->subscription
= JABBER_SUB_NONE
;
241 else if (g_str_equal(subscription
, "to"))
242 jb
->subscription
= JABBER_SUB_TO
;
243 else if (g_str_equal(subscription
, "from"))
244 jb
->subscription
= JABBER_SUB_FROM
;
245 else if (g_str_equal(subscription
, "both"))
246 jb
->subscription
= JABBER_SUB_BOTH
;
249 if(purple_strequal(ask
, "subscribe"))
250 jb
->subscription
|= JABBER_SUB_PENDING
;
252 jb
->subscription
&= ~JABBER_SUB_PENDING
;
254 if(jb
->subscription
& JABBER_SUB_REMOVE
) {
255 remove_purple_buddies(js
, jid
);
257 GSList
*groups
= NULL
;
259 if (js
->server_caps
& JABBER_CAP_GOOGLE_ROSTER
)
260 if (!jabber_google_roster_incoming(js
, item
))
263 for(group
= purple_xmlnode_get_child(item
, "group"); group
; group
= purple_xmlnode_get_next_twin(group
)) {
264 char *group_name
= purple_xmlnode_get_data(group
);
266 if (group_name
== NULL
|| *group_name
== '\0')
267 /* Changing this string? Look in add_purple_buddy_to_groups */
268 group_name
= g_strdup(JABBER_ROSTER_DEFAULT_GROUP
);
271 * See the note in add_purple_buddy_to_groups; the core handles
272 * names case-insensitively and this is required to not
273 * end up with duplicates if a buddy is in, e.g.,
276 if (g_slist_find_custom(groups
, group_name
, (GCompareFunc
)purple_utf8_strcasecmp
))
279 groups
= g_slist_prepend(groups
, group_name
);
282 add_purple_buddy_to_groups(js
, jid
, name
, groups
);
283 if (jb
== js
->user_jb
)
284 jabber_presence_fake_to_self(js
, NULL
);
289 ver
= purple_xmlnode_get_attrib(query
, "ver");
291 PurpleAccount
*account
= purple_connection_get_account(js
->gc
);
292 purple_account_set_string(account
, "roster_ver", ver
);
296 if (type
== JABBER_IQ_SET
) {
297 JabberIq
*ack
= jabber_iq_new(js
, JABBER_IQ_RESULT
);
298 jabber_iq_set_id(ack
, id
);
302 js
->currently_parsing_roster_push
= FALSE
;
305 /* jabber_roster_update frees the GSList* passed in */
306 static void jabber_roster_update(JabberStream
*js
, const char *name
,
313 PurpleXmlNode
*query
, *item
, *group
;
316 if (js
->currently_parsing_roster_push
)
319 if(!(b
= purple_blist_find_buddy(purple_connection_get_account(js
->gc
), name
)))
323 char *tmp
= roster_groups_join(groups
);
325 purple_debug_info("jabber", "jabber_roster_update(%s): [Source: "
326 "groups]: groups: %s\n", name
, tmp
);
329 GSList
*buddies
= purple_blist_find_buddies(purple_connection_get_account(js
->gc
), name
);
336 g
= purple_buddy_get_group(b
);
337 groups
= g_slist_append(groups
,
338 (char *)jabber_roster_group_get_global_name(g
));
339 buddies
= g_slist_remove(buddies
, b
);
342 tmp
= roster_groups_join(groups
);
343 purple_debug_info("jabber", "jabber_roster_update(%s): [Source: local blist]: groups: %s\n",
348 iq
= jabber_iq_new_query(js
, JABBER_IQ_SET
, "jabber:iq:roster");
350 query
= purple_xmlnode_get_child(iq
->node
, "query");
351 item
= purple_xmlnode_new_child(query
, "item");
353 purple_xmlnode_set_attrib(item
, "jid", name
);
355 balias
= purple_buddy_get_local_alias(b
);
356 purple_xmlnode_set_attrib(item
, "name", balias
? balias
: "");
358 for(l
= groups
; l
; l
= l
->next
) {
359 group
= purple_xmlnode_new_child(item
, "group");
360 purple_xmlnode_insert_data(group
, l
->data
, -1);
363 g_slist_free(groups
);
365 if (js
->server_caps
& JABBER_CAP_GOOGLE_ROSTER
) {
366 jabber_google_roster_outgoing(js
, query
, item
);
367 purple_xmlnode_set_attrib(query
, "xmlns:gr", NS_GOOGLE_ROSTER
);
368 purple_xmlnode_set_attrib(query
, "gr:ext", "2");
373 void jabber_roster_add_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
,
374 PurpleGroup
*group
, const char *message
)
376 JabberStream
*js
= purple_connection_get_protocol_data(gc
);
380 JabberBuddyResource
*jbr
;
383 /* If we haven't received the roster yet, ignore any adds */
384 if (js
->state
!= JABBER_STREAM_CONNECTED
)
387 name
= purple_buddy_get_name(buddy
);
388 jid
= jabber_id_new(name
);
390 /* TODO: Remove the buddy from the list? */
394 /* Adding a chat room or a chat user to the roster is *not* supported. */
395 if (jid
->node
&& jabber_chat_find(js
, jid
->node
, jid
->domain
) != NULL
) {
397 * This is the same thing Bonjour does. If it causes problems, move
398 * it to an idle callback.
400 purple_debug_warning("jabber", "Cowardly refusing to add a MUC user "
401 "to your buddy list and removing the buddy. "
402 "Buddies can only be added by real (non-MUC) "
404 purple_blist_remove_buddy(buddy
);
409 who
= jabber_id_get_bare_jid(jid
);
410 if (jid
->resource
!= NULL
) {
412 * If the buddy name added contains a resource, strip that off and
415 purple_buddy_set_name(buddy
, who
);
418 jb
= jabber_buddy_find(js
, who
, FALSE
);
420 purple_debug_info("jabber", "jabber_roster_add_buddy(): Adding %s\n", who
);
422 jabber_roster_update(js
, who
, NULL
);
424 if (jb
== js
->user_jb
) {
425 jabber_presence_fake_to_self(js
, NULL
);
426 } else if(!jb
|| !(jb
->subscription
& JABBER_SUB_TO
)) {
427 jabber_presence_subscription_set(js
, who
, "subscribe");
428 } else if((jbr
=jabber_buddy_find_resource(jb
, NULL
))) {
429 purple_protocol_got_user_status(purple_connection_get_account(gc
), who
,
430 jabber_buddy_state_get_status_id(jbr
->state
),
431 "priority", jbr
->priority
, jbr
->status
? "message" : NULL
, jbr
->status
, NULL
);
437 void jabber_roster_alias_change(PurpleConnection
*gc
, const char *name
, const char *alias
)
439 PurpleBuddy
*b
= purple_blist_find_buddy(purple_connection_get_account(gc
), name
);
442 purple_buddy_set_local_alias(b
, alias
);
444 purple_debug_info("jabber", "jabber_roster_alias_change(): Aliased %s to %s\n",
445 name
, alias
? alias
: "(null)");
447 jabber_roster_update(purple_connection_get_protocol_data(gc
), name
, NULL
);
451 void jabber_roster_group_change(PurpleConnection
*gc
, const char *name
,
452 const char *old_group
, const char *new_group
)
454 GSList
*buddies
, *groups
= NULL
;
457 if(!old_group
|| !new_group
|| !strcmp(old_group
, new_group
))
460 buddies
= purple_blist_find_buddies(purple_connection_get_account(gc
), name
);
463 groups
= g_slist_append(groups
, (char*)new_group
);
464 buddies
= g_slist_remove(buddies
, b
);
467 purple_debug_info("jabber", "jabber_roster_group_change(): Moving %s from %s to %s\n",
468 name
, old_group
, new_group
);
470 jabber_roster_update(purple_connection_get_protocol_data(gc
), name
, groups
);
473 void jabber_roster_group_rename(PurpleConnection
*gc
, const char *old_name
,
474 PurpleGroup
*group
, GList
*moved_buddies
)
477 const char *gname
= jabber_roster_group_get_global_name(group
);
478 for(l
= moved_buddies
; l
; l
= l
->next
) {
479 PurpleBuddy
*buddy
= l
->data
;
480 jabber_roster_group_change(gc
, purple_buddy_get_name(buddy
), old_name
, gname
);
484 void jabber_roster_remove_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
,
485 PurpleGroup
*group
) {
486 const char *name
= purple_buddy_get_name(buddy
);
487 GSList
*buddies
= purple_blist_find_buddies(purple_connection_get_account(gc
), name
);
489 buddies
= g_slist_remove(buddies
, buddy
);
490 if(buddies
!= NULL
) {
491 PurpleBuddy
*tmpbuddy
;
492 PurpleGroup
*tmpgroup
;
493 GSList
*groups
= NULL
;
496 tmpbuddy
= buddies
->data
;
497 tmpgroup
= purple_buddy_get_group(tmpbuddy
);
498 groups
= g_slist_append(groups
,
499 (char *)jabber_roster_group_get_global_name(tmpgroup
));
500 buddies
= g_slist_remove(buddies
, tmpbuddy
);
503 purple_debug_info("jabber", "jabber_roster_remove_buddy(): "
504 "Removing %s from %s", purple_buddy_get_name(buddy
),
505 jabber_roster_group_get_global_name(group
));
507 jabber_roster_update(purple_connection_get_protocol_data(gc
), name
, groups
);
509 JabberIq
*iq
= jabber_iq_new_query(purple_connection_get_protocol_data(gc
), JABBER_IQ_SET
,
511 PurpleXmlNode
*query
= purple_xmlnode_get_child(iq
->node
, "query");
512 PurpleXmlNode
*item
= purple_xmlnode_new_child(query
, "item");
514 purple_xmlnode_set_attrib(item
, "jid", name
);
515 purple_xmlnode_set_attrib(item
, "subscription", "remove");
517 purple_debug_info("jabber", "jabber_roster_remove_buddy(): Removing %s\n",
518 purple_buddy_get_name(buddy
));
525 jabber_roster_group_get_global_name(PurpleGroup
*group
)
527 const gchar
*name
= NULL
;
530 name
= purple_group_get_name(group
);
533 name
= JABBER_ROSTER_DEFAULT_GROUP
;
534 else if (g_strcmp0(name
, PURPLE_BLIST_DEFAULT_GROUP_NAME
) == 0)
535 name
= JABBER_ROSTER_DEFAULT_GROUP
;
536 else if (g_strcmp0(name
, _purple_blist_get_localized_default_group_name()) == 0)
537 name
= JABBER_ROSTER_DEFAULT_GROUP
;