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
);
200 void jabber_roster_parse(JabberStream
*js
, const char *from
,
201 JabberIqType type
, const char *id
, PurpleXmlNode
*query
)
203 PurpleXmlNode
*item
, *group
;
205 if (!jabber_is_own_account(js
, from
)) {
206 purple_debug_warning("jabber", "Received bogon roster push from %s\n",
211 js
->currently_parsing_roster_push
= TRUE
;
213 for(item
= purple_xmlnode_get_child(query
, "item"); item
; item
= purple_xmlnode_get_next_twin(item
))
215 const char *jid
, *name
, *subscription
, *ask
;
218 subscription
= purple_xmlnode_get_attrib(item
, "subscription");
219 jid
= purple_xmlnode_get_attrib(item
, "jid");
220 name
= purple_xmlnode_get_attrib(item
, "name");
221 ask
= purple_xmlnode_get_attrib(item
, "ask");
226 if(!(jb
= jabber_buddy_find(js
, jid
, TRUE
)))
230 if (purple_strequal(subscription
, "remove"))
231 jb
->subscription
= JABBER_SUB_REMOVE
;
232 else if (jb
== js
->user_jb
)
233 jb
->subscription
= JABBER_SUB_BOTH
;
234 else if (purple_strequal(subscription
, "none"))
235 jb
->subscription
= JABBER_SUB_NONE
;
236 else if (purple_strequal(subscription
, "to"))
237 jb
->subscription
= JABBER_SUB_TO
;
238 else if (purple_strequal(subscription
, "from"))
239 jb
->subscription
= JABBER_SUB_FROM
;
240 else if (purple_strequal(subscription
, "both"))
241 jb
->subscription
= JABBER_SUB_BOTH
;
244 if(purple_strequal(ask
, "subscribe"))
245 jb
->subscription
|= JABBER_SUB_PENDING
;
247 jb
->subscription
&= ~JABBER_SUB_PENDING
;
249 if(jb
->subscription
& JABBER_SUB_REMOVE
) {
250 remove_purple_buddies(js
, jid
);
252 GSList
*groups
= NULL
;
254 if (js
->server_caps
& JABBER_CAP_GOOGLE_ROSTER
)
255 if (!jabber_google_roster_incoming(js
, item
))
258 for(group
= purple_xmlnode_get_child(item
, "group"); group
; group
= purple_xmlnode_get_next_twin(group
)) {
259 char *group_name
= purple_xmlnode_get_data(group
);
261 if (group_name
== NULL
|| *group_name
== '\0')
262 /* Changing this string? Look in add_purple_buddy_to_groups */
263 group_name
= g_strdup(JABBER_ROSTER_DEFAULT_GROUP
);
266 * See the note in add_purple_buddy_to_groups; the core handles
267 * names case-insensitively and this is required to not
268 * end up with duplicates if a buddy is in, e.g.,
271 if (g_slist_find_custom(groups
, group_name
, (GCompareFunc
)purple_utf8_strcasecmp
))
274 groups
= g_slist_prepend(groups
, group_name
);
277 add_purple_buddy_to_groups(js
, jid
, name
, groups
);
278 if (jb
== js
->user_jb
)
279 jabber_presence_fake_to_self(js
, NULL
);
283 if (type
== JABBER_IQ_SET
) {
284 JabberIq
*ack
= jabber_iq_new(js
, JABBER_IQ_RESULT
);
285 jabber_iq_set_id(ack
, id
);
289 js
->currently_parsing_roster_push
= FALSE
;
292 /* jabber_roster_update frees the GSList* passed in */
293 static void jabber_roster_update(JabberStream
*js
, const char *name
,
300 PurpleXmlNode
*query
, *item
, *group
;
303 if (js
->currently_parsing_roster_push
)
306 if(!(b
= purple_blist_find_buddy(purple_connection_get_account(js
->gc
), name
)))
310 char *tmp
= roster_groups_join(groups
);
312 purple_debug_info("jabber", "jabber_roster_update(%s): [Source: "
313 "groups]: groups: %s\n", name
, tmp
);
316 GSList
*buddies
= purple_blist_find_buddies(purple_connection_get_account(js
->gc
), name
);
323 g
= purple_buddy_get_group(b
);
324 groups
= g_slist_append(groups
,
325 (char *)jabber_roster_group_get_global_name(g
));
326 buddies
= g_slist_remove(buddies
, b
);
329 tmp
= roster_groups_join(groups
);
330 purple_debug_info("jabber", "jabber_roster_update(%s): [Source: local blist]: groups: %s\n",
335 iq
= jabber_iq_new_query(js
, JABBER_IQ_SET
, "jabber:iq:roster");
337 query
= purple_xmlnode_get_child(iq
->node
, "query");
338 item
= purple_xmlnode_new_child(query
, "item");
340 purple_xmlnode_set_attrib(item
, "jid", name
);
342 balias
= purple_buddy_get_local_alias(b
);
343 purple_xmlnode_set_attrib(item
, "name", balias
? balias
: "");
345 for(l
= groups
; l
; l
= l
->next
) {
346 group
= purple_xmlnode_new_child(item
, "group");
347 purple_xmlnode_insert_data(group
, l
->data
, -1);
350 g_slist_free(groups
);
352 if (js
->server_caps
& JABBER_CAP_GOOGLE_ROSTER
) {
353 jabber_google_roster_outgoing(js
, query
, item
);
354 purple_xmlnode_set_attrib(query
, "xmlns:gr", NS_GOOGLE_ROSTER
);
355 purple_xmlnode_set_attrib(query
, "gr:ext", "2");
360 void jabber_roster_add_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
,
361 PurpleGroup
*group
, const char *message
)
363 JabberStream
*js
= purple_connection_get_protocol_data(gc
);
367 JabberBuddyResource
*jbr
;
370 /* If we haven't received the roster yet, ignore any adds */
371 if (js
->state
!= JABBER_STREAM_CONNECTED
)
374 name
= purple_buddy_get_name(buddy
);
375 jid
= jabber_id_new(name
);
377 /* TODO: Remove the buddy from the list? */
381 /* Adding a chat room or a chat user to the roster is *not* supported. */
382 if (jid
->node
&& jabber_chat_find(js
, jid
->node
, jid
->domain
) != NULL
) {
384 * This is the same thing Bonjour does. If it causes problems, move
385 * it to an idle callback.
387 purple_debug_warning("jabber", "Cowardly refusing to add a MUC user "
388 "to your buddy list and removing the buddy. "
389 "Buddies can only be added by real (non-MUC) "
391 purple_blist_remove_buddy(buddy
);
396 who
= jabber_id_get_bare_jid(jid
);
397 if (jid
->resource
!= NULL
) {
399 * If the buddy name added contains a resource, strip that off and
402 purple_buddy_set_name(buddy
, who
);
405 jb
= jabber_buddy_find(js
, who
, FALSE
);
407 purple_debug_info("jabber", "jabber_roster_add_buddy(): Adding %s\n", who
);
409 jabber_roster_update(js
, who
, NULL
);
411 if (jb
== js
->user_jb
) {
412 jabber_presence_fake_to_self(js
, NULL
);
413 } else if(!jb
|| !(jb
->subscription
& JABBER_SUB_TO
)) {
414 jabber_presence_subscription_set(js
, who
, "subscribe");
415 } else if((jbr
=jabber_buddy_find_resource(jb
, NULL
))) {
416 purple_protocol_got_user_status(purple_connection_get_account(gc
), who
,
417 jabber_buddy_state_get_status_id(jbr
->state
),
418 "priority", jbr
->priority
, jbr
->status
? "message" : NULL
, jbr
->status
, NULL
);
424 void jabber_roster_alias_change(PurpleConnection
*gc
, const char *name
, const char *alias
)
426 PurpleBuddy
*b
= purple_blist_find_buddy(purple_connection_get_account(gc
), name
);
429 purple_buddy_set_local_alias(b
, alias
);
431 purple_debug_info("jabber", "jabber_roster_alias_change(): Aliased %s to %s\n",
432 name
, alias
? alias
: "(null)");
434 jabber_roster_update(purple_connection_get_protocol_data(gc
), name
, NULL
);
438 void jabber_roster_group_change(PurpleConnection
*gc
, const char *name
,
439 const char *old_group
, const char *new_group
)
441 GSList
*buddies
, *groups
= NULL
;
444 if(!old_group
|| !new_group
|| purple_strequal(old_group
, new_group
))
447 buddies
= purple_blist_find_buddies(purple_connection_get_account(gc
), name
);
450 groups
= g_slist_append(groups
, (char*)new_group
);
451 buddies
= g_slist_remove(buddies
, b
);
454 purple_debug_info("jabber", "jabber_roster_group_change(): Moving %s from %s to %s\n",
455 name
, old_group
, new_group
);
457 jabber_roster_update(purple_connection_get_protocol_data(gc
), name
, groups
);
460 void jabber_roster_group_rename(PurpleConnection
*gc
, const char *old_name
,
461 PurpleGroup
*group
, GList
*moved_buddies
)
464 const char *gname
= jabber_roster_group_get_global_name(group
);
465 for(l
= moved_buddies
; l
; l
= l
->next
) {
466 PurpleBuddy
*buddy
= l
->data
;
467 jabber_roster_group_change(gc
, purple_buddy_get_name(buddy
), old_name
, gname
);
471 void jabber_roster_remove_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
,
472 PurpleGroup
*group
) {
473 const char *name
= purple_buddy_get_name(buddy
);
474 GSList
*buddies
= purple_blist_find_buddies(purple_connection_get_account(gc
), name
);
476 buddies
= g_slist_remove(buddies
, buddy
);
477 if(buddies
!= NULL
) {
478 PurpleBuddy
*tmpbuddy
;
479 PurpleGroup
*tmpgroup
;
480 GSList
*groups
= NULL
;
483 tmpbuddy
= buddies
->data
;
484 tmpgroup
= purple_buddy_get_group(tmpbuddy
);
485 groups
= g_slist_append(groups
,
486 (char *)jabber_roster_group_get_global_name(tmpgroup
));
487 buddies
= g_slist_remove(buddies
, tmpbuddy
);
490 purple_debug_info("jabber", "jabber_roster_remove_buddy(): "
491 "Removing %s from %s", purple_buddy_get_name(buddy
),
492 jabber_roster_group_get_global_name(group
));
494 jabber_roster_update(purple_connection_get_protocol_data(gc
), name
, groups
);
496 JabberIq
*iq
= jabber_iq_new_query(purple_connection_get_protocol_data(gc
), JABBER_IQ_SET
,
498 PurpleXmlNode
*query
= purple_xmlnode_get_child(iq
->node
, "query");
499 PurpleXmlNode
*item
= purple_xmlnode_new_child(query
, "item");
501 purple_xmlnode_set_attrib(item
, "jid", name
);
502 purple_xmlnode_set_attrib(item
, "subscription", "remove");
504 purple_debug_info("jabber", "jabber_roster_remove_buddy(): Removing %s\n",
505 purple_buddy_get_name(buddy
));
512 jabber_roster_group_get_global_name(PurpleGroup
*group
)
514 const gchar
*name
= NULL
;
517 name
= purple_group_get_name(group
);
520 name
= JABBER_ROSTER_DEFAULT_GROUP
;
521 else if (purple_strequal(name
, PURPLE_BLIST_DEFAULT_GROUP_NAME
))
522 name
= JABBER_ROSTER_DEFAULT_GROUP
;
523 else if (purple_strequal(name
, _purple_blist_get_localized_default_group_name()))
524 name
= JABBER_ROSTER_DEFAULT_GROUP
;