1 /**
2 * @file gg.c Gadu-Gadu protocol plugin
3 *
4 * purple
5 *
6 * Copyright (C) 2005 Bartosz Oler <bartosz@bzimage.us>
7 *
8 * Some parts of the code are adapted or taken from the previous implementation
9 * of this plugin written by Arkadiusz Miskiewicz <misiek@pld.org.pl>
10 * Some parts Copyright (C) 2009 Krzysztof Klinikowski <grommasher@gmail.com>
11 *
12 * Thanks to Google's Summer of Code Program.
13 *
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
27 */
28
29 #include "internal.h"
30
31 #include "plugin.h"
32 #include "version.h"
33 #include "notify.h"
34 #include "status.h"
35 #include "blist.h"
36 #include "accountopt.h"
37 #include "debug.h"
38 #include "glibcompat.h"
39 #include "util.h"
40 #include "request.h"
41 #include "xmlnode.h"
42
43 #include <libgadu.h>
44
45 #include "gg.h"
46 #include "confer.h"
47 #include "search.h"
48 #include "buddylist.h"
49 #include "gg-utils.h"
50
51 #define DISABLE_AVATARS 1
52
53 static PurplePlugin *my_protocol = NULL;
54
55 /* Prototypes */
56 static void ggp_set_status(PurpleAccount *account, PurpleStatus *status);
57 static int ggp_to_gg_status(PurpleStatus *status, char **msg);
58
59 /* ---------------------------------------------------------------------- */
60 /* ----- EXTERNAL CALLBACKS --------------------------------------------- */
61 /* ---------------------------------------------------------------------- */
62
63
64 /* ----- HELPERS -------------------------------------------------------- */
65
66 static PurpleInputCondition
ggp_tcpsocket_inputcond_gg_to_purple(enum gg_check_t check)67 ggp_tcpsocket_inputcond_gg_to_purple(enum gg_check_t check)
68 {
69 PurpleInputCondition cond = 0;
70
71 if (check & GG_CHECK_READ)
72 cond |= PURPLE_INPUT_READ;
73 if (check & GG_CHECK_WRITE)
74 cond |= PURPLE_INPUT_WRITE;
75
76 return cond;
77 }
78
79 /**
80 * Set up libgadu's proxy.
81 *
82 * @param account Account for which to set up the proxy.
83 *
84 * @return Zero if proxy setup is valid, otherwise -1.
85 */
ggp_setup_proxy(PurpleAccount * account)86 static int ggp_setup_proxy(PurpleAccount *account)
87 {
88 PurpleProxyInfo *gpi;
89
90 gpi = purple_proxy_get_setup(account);
91
92 if ((purple_proxy_info_get_type(gpi) != PURPLE_PROXY_NONE) &&
93 (purple_proxy_info_get_host(gpi) == NULL ||
94 purple_proxy_info_get_port(gpi) <= 0)) {
95
96 gg_proxy_enabled = 0;
97 purple_notify_error(NULL, NULL, _("Invalid proxy settings"),
98 _("Either the host name or port number specified for your given proxy type is invalid."));
99 return -1;
100 } else if (purple_proxy_info_get_type(gpi) != PURPLE_PROXY_NONE) {
101 gg_proxy_enabled = 1;
102 gg_proxy_host = g_strdup(purple_proxy_info_get_host(gpi));
103 gg_proxy_port = purple_proxy_info_get_port(gpi);
104 gg_proxy_username = g_strdup(purple_proxy_info_get_username(gpi));
105 gg_proxy_password = g_strdup(purple_proxy_info_get_password(gpi));
106 } else {
107 gg_proxy_enabled = 0;
108 }
109
110 return 0;
111 }
112
113 /* }}} */
114
115 /* ---------------------------------------------------------------------- */
116
ggp_callback_buddylist_save_ok(PurpleConnection * gc,const char * filename)117 static void ggp_callback_buddylist_save_ok(PurpleConnection *gc, const char *filename)
118 {
119 PurpleAccount *account = purple_connection_get_account(gc);
120
121 char *buddylist = ggp_buddylist_dump(account);
122
123 purple_debug_info("gg", "Saving...\n");
124 purple_debug_info("gg", "file = %s\n", filename);
125
126 if (buddylist == NULL) {
127 purple_notify_info(account, _("Save Buddylist..."),
128 _("Your buddylist is empty, nothing was written to the file."),
129 NULL);
130 return;
131 }
132
133 if(purple_util_write_data_to_file_absolute(filename, buddylist, -1)) {
134 purple_notify_info(account, _("Save Buddylist..."),
135 _("Buddylist saved successfully!"), NULL);
136 } else {
137 gchar *primary = g_strdup_printf(
138 _("Couldn't write buddy list for %s to %s"),
139 purple_account_get_username(account), filename);
140 purple_notify_error(account, _("Save Buddylist..."),
141 primary, NULL);
142 g_free(primary);
143 }
144
145 g_free(buddylist);
146 }
147
ggp_callback_buddylist_load_ok(PurpleConnection * gc,gchar * file)148 static void ggp_callback_buddylist_load_ok(PurpleConnection *gc, gchar *file)
149 {
150 PurpleAccount *account = purple_connection_get_account(gc);
151 GError *error = NULL;
152 char *buddylist = NULL;
153 gsize length;
154
155 purple_debug_info("gg", "file_name = %s\n", file);
156
157 if (!g_file_get_contents(file, &buddylist, &length, &error)) {
158 purple_notify_error(account,
159 _("Couldn't load buddylist"),
160 _("Couldn't load buddylist"),
161 error->message);
162
163 purple_debug_error("gg",
164 "Couldn't load buddylist. file = %s; error = %s\n",
165 file, error->message);
166
167 g_error_free(error);
168
169 return;
170 }
171
172 ggp_buddylist_load(gc, buddylist);
173 g_free(buddylist);
174
175 purple_notify_info(account,
176 _("Load Buddylist..."),
177 _("Buddylist loaded successfully!"), NULL);
178 }
179 /* }}} */
180
181 /*
182 */
183 /* static void ggp_action_buddylist_save(PurplePluginAction *action) {{{ */
ggp_action_buddylist_save(PurplePluginAction * action)184 static void ggp_action_buddylist_save(PurplePluginAction *action)
185 {
186 PurpleConnection *gc = (PurpleConnection *)action->context;
187
188 purple_request_file(action, _("Save buddylist..."), NULL, TRUE,
189 G_CALLBACK(ggp_callback_buddylist_save_ok), NULL,
190 purple_connection_get_account(gc), NULL, NULL,
191 gc);
192 }
193
ggp_action_buddylist_load(PurplePluginAction * action)194 static void ggp_action_buddylist_load(PurplePluginAction *action)
195 {
196 PurpleConnection *gc = (PurpleConnection *)action->context;
197
198 purple_request_file(action, _("Load buddylist from file..."), NULL,
199 FALSE,
200 G_CALLBACK(ggp_callback_buddylist_load_ok), NULL,
201 purple_connection_get_account(gc), NULL, NULL,
202 gc);
203 }
204
205 /* ----- PUBLIC DIRECTORY SEARCH ---------------------------------------- */
206
ggp_callback_show_next(PurpleConnection * gc,GList * row,gpointer user_data)207 static void ggp_callback_show_next(PurpleConnection *gc, GList *row, gpointer user_data)
208 {
209 GGPInfo *info = gc->proto_data;
210 GGPSearchForm *form = user_data;
211 guint32 seq;
212
213 form->page_number++;
214
215 ggp_search_remove(info->searches, form->seq);
216 purple_debug_info("gg", "ggp_callback_show_next(): Removed seq %u\n",
217 form->seq);
218
219 seq = ggp_search_start(gc, form);
220 ggp_search_add(info->searches, seq, form);
221 purple_debug_info("gg", "ggp_callback_show_next(): Added seq %u\n",
222 seq);
223 }
224
ggp_callback_add_buddy(PurpleConnection * gc,GList * row,gpointer user_data)225 static void ggp_callback_add_buddy(PurpleConnection *gc, GList *row, gpointer user_data)
226 {
227 purple_blist_request_add_buddy(purple_connection_get_account(gc),
228 g_list_nth_data(row, 0), NULL, NULL);
229 }
230
ggp_callback_im(PurpleConnection * gc,GList * row,gpointer user_data)231 static void ggp_callback_im(PurpleConnection *gc, GList *row, gpointer user_data)
232 {
233 PurpleAccount *account;
234 PurpleConversation *conv;
235 char *name;
236
237 account = purple_connection_get_account(gc);
238
239 name = g_list_nth_data(row, 0);
240 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, name);
241 purple_conversation_present(conv);
242 }
243
ggp_callback_find_buddies(PurpleConnection * gc,PurpleRequestFields * fields)244 static void ggp_callback_find_buddies(PurpleConnection *gc, PurpleRequestFields *fields)
245 {
246 GGPInfo *info = gc->proto_data;
247 GGPSearchForm *form;
248 guint32 seq;
249
250 form = ggp_search_form_new(GGP_SEARCH_TYPE_FULL);
251
252 form->user_data = info;
253 form->lastname = g_strdup(
254 purple_request_fields_get_string(fields, "lastname"));
255 form->firstname = g_strdup(
256 purple_request_fields_get_string(fields, "firstname"));
257 form->nickname = g_strdup(
258 purple_request_fields_get_string(fields, "nickname"));
259 form->city = g_strdup(
260 purple_request_fields_get_string(fields, "city"));
261 form->birthyear = g_strdup(
262 purple_request_fields_get_string(fields, "year"));
263
264 switch (purple_request_fields_get_choice(fields, "gender")) {
265 case 1:
266 form->gender = g_strdup(GG_PUBDIR50_GENDER_MALE);
267 break;
268 case 2:
269 form->gender = g_strdup(GG_PUBDIR50_GENDER_FEMALE);
270 break;
271 default:
272 form->gender = NULL;
273 break;
274 }
275
276 form->active = purple_request_fields_get_bool(fields, "active")
277 ? g_strdup(GG_PUBDIR50_ACTIVE_TRUE) : NULL;
278
279 seq = ggp_search_start(gc, form);
280 ggp_search_add(info->searches, seq, form);
281 purple_debug_info("gg", "ggp_callback_find_buddies(): Added seq %u\n",
282 seq);
283 }
284
ggp_find_buddies(PurplePluginAction * action)285 static void ggp_find_buddies(PurplePluginAction *action)
286 {
287 PurpleConnection *gc = (PurpleConnection *)action->context;
288
289 PurpleRequestFields *fields;
290 PurpleRequestFieldGroup *group;
291 PurpleRequestField *field;
292
293 fields = purple_request_fields_new();
294 group = purple_request_field_group_new(NULL);
295 purple_request_fields_add_group(fields, group);
296
297 field = purple_request_field_string_new("lastname",
298 _("Last name"), NULL, FALSE);
299 purple_request_field_string_set_masked(field, FALSE);
300 purple_request_field_group_add_field(group, field);
301
302 field = purple_request_field_string_new("firstname",
303 _("First name"), NULL, FALSE);
304 purple_request_field_string_set_masked(field, FALSE);
305 purple_request_field_group_add_field(group, field);
306
307 field = purple_request_field_string_new("nickname",
308 _("Nickname"), NULL, FALSE);
309 purple_request_field_string_set_masked(field, FALSE);
310 purple_request_field_group_add_field(group, field);
311
312 field = purple_request_field_string_new("city",
313 _("City"), NULL, FALSE);
314 purple_request_field_string_set_masked(field, FALSE);
315 purple_request_field_group_add_field(group, field);
316
317 field = purple_request_field_string_new("year",
318 _("Year of birth"), NULL, FALSE);
319 purple_request_field_group_add_field(group, field);
320
321 field = purple_request_field_choice_new("gender", _("Gender"), 0);
322 purple_request_field_choice_add(field, _("Male or female"));
323 purple_request_field_choice_add(field, _("Male"));
324 purple_request_field_choice_add(field, _("Female"));
325 purple_request_field_group_add_field(group, field);
326
327 field = purple_request_field_bool_new("active",
328 _("Only online"), FALSE);
329 purple_request_field_group_add_field(group, field);
330
331 purple_request_fields(gc,
332 _("Find buddies"),
333 _("Find buddies"),
334 _("Please, enter your search criteria below"),
335 fields,
336 _("OK"), G_CALLBACK(ggp_callback_find_buddies),
337 _("Cancel"), NULL,
338 purple_connection_get_account(gc), NULL, NULL,
339 gc);
340 }
341
342 /* ----- CHANGE STATUS BROADCASTING ------------------------------------------------ */
343
ggp_action_change_status_broadcasting_ok(PurpleConnection * gc,PurpleRequestFields * fields)344 static void ggp_action_change_status_broadcasting_ok(PurpleConnection *gc, PurpleRequestFields *fields)
345 {
346 GGPInfo *info = gc->proto_data;
347 int selected_field;
348 PurpleAccount *account = purple_connection_get_account(gc);
349 PurpleStatus *status;
350
351 selected_field = purple_request_fields_get_choice(fields, "status_broadcasting");
352
353 if (selected_field == 0)
354 info->status_broadcasting = TRUE;
355 else
356 info->status_broadcasting = FALSE;
357
358 status = purple_account_get_active_status(account);
359
360 ggp_set_status(account, status);
361 }
362
ggp_action_change_status_broadcasting(PurplePluginAction * action)363 static void ggp_action_change_status_broadcasting(PurplePluginAction *action)
364 {
365 PurpleConnection *gc = (PurpleConnection *)action->context;
366 GGPInfo *info = gc->proto_data;
367
368 PurpleRequestFields *fields;
369 PurpleRequestFieldGroup *group;
370 PurpleRequestField *field;
371
372 fields = purple_request_fields_new();
373 group = purple_request_field_group_new(NULL);
374 purple_request_fields_add_group(fields, group);
375
376 field = purple_request_field_choice_new("status_broadcasting", _("Show status to:"), 0);
377 purple_request_field_choice_add(field, _("All people"));
378 purple_request_field_choice_add(field, _("Only buddies"));
379 purple_request_field_group_add_field(group, field);
380
381 if (info->status_broadcasting)
382 purple_request_field_choice_set_default_value(field, 0);
383 else
384 purple_request_field_choice_set_default_value(field, 1);
385
386 purple_request_fields(gc,
387 _("Change status broadcasting"),
388 _("Change status broadcasting"),
389 _("Please, select who can see your status"),
390 fields,
391 _("OK"), G_CALLBACK(ggp_action_change_status_broadcasting_ok),
392 _("Cancel"), NULL,
393 purple_connection_get_account(gc), NULL, NULL,
394 gc);
395 }
396
397 /* ----- CONFERENCES ---------------------------------------------------- */
398
ggp_callback_add_to_chat_ok(PurpleBuddy * buddy,PurpleRequestFields * fields)399 static void ggp_callback_add_to_chat_ok(PurpleBuddy *buddy, PurpleRequestFields *fields)
400 {
401 PurpleConnection *conn;
402 PurpleRequestField *field;
403 GList *sel;
404
405 conn = purple_account_get_connection(purple_buddy_get_account(buddy));
406
407 g_return_if_fail(conn != NULL);
408
409 field = purple_request_fields_get_field(fields, "name");
410 sel = purple_request_field_list_get_selected(field);
411
412 if (sel == NULL) {
413 purple_debug_error("gg", "No chat selected\n");
414 return;
415 }
416
417 ggp_confer_participants_add_uin(conn, sel->data,
418 ggp_str_to_uin(purple_buddy_get_name(buddy)));
419 }
420
ggp_bmenu_add_to_chat(PurpleBlistNode * node,gpointer ignored)421 static void ggp_bmenu_add_to_chat(PurpleBlistNode *node, gpointer ignored)
422 {
423 PurpleBuddy *buddy;
424 PurpleConnection *gc;
425 GGPInfo *info;
426
427 PurpleRequestFields *fields;
428 PurpleRequestFieldGroup *group;
429 PurpleRequestField *field;
430
431 GList *l;
432 gchar *msg;
433
434 buddy = (PurpleBuddy *)node;
435 gc = purple_account_get_connection(purple_buddy_get_account(buddy));
436 info = gc->proto_data;
437
438 fields = purple_request_fields_new();
439 group = purple_request_field_group_new(NULL);
440 purple_request_fields_add_group(fields, group);
441
442 field = purple_request_field_list_new("name", "Chat name");
443 for (l = info->chats; l != NULL; l = l->next) {
444 GGPChat *chat = l->data;
445 purple_request_field_list_add(field, chat->name, chat->name);
446 }
447 purple_request_field_group_add_field(group, field);
448
449 msg = g_strdup_printf(_("Select a chat for buddy: %s"),
450 purple_buddy_get_alias(buddy));
451 purple_request_fields(gc,
452 _("Add to chat..."),
453 _("Add to chat..."),
454 msg,
455 fields,
456 _("Add"), G_CALLBACK(ggp_callback_add_to_chat_ok),
457 _("Cancel"), NULL,
458 purple_connection_get_account(gc), NULL, NULL,
459 buddy);
460 g_free(msg);
461 }
462
463 /* ----- BLOCK BUDDIES -------------------------------------------------- */
464
ggp_add_deny(PurpleConnection * gc,const char * who)465 static void ggp_add_deny(PurpleConnection *gc, const char *who)
466 {
467 GGPInfo *info = gc->proto_data;
468 uin_t uin = ggp_str_to_uin(who);
469
470 purple_debug_info("gg", "ggp_add_deny: %u\n", uin);
471
472 gg_remove_notify_ex(info->session, uin, GG_USER_NORMAL);
473 gg_add_notify_ex(info->session, uin, GG_USER_BLOCKED);
474 }
475
ggp_rem_deny(PurpleConnection * gc,const char * who)476 static void ggp_rem_deny(PurpleConnection *gc, const char *who)
477 {
478 GGPInfo *info = gc->proto_data;
479 uin_t uin = ggp_str_to_uin(who);
480
481 purple_debug_info("gg", "ggp_rem_deny: %u\n", uin);
482
483 gg_remove_notify_ex(info->session, uin, GG_USER_BLOCKED);
484 gg_add_notify_ex(info->session, uin, GG_USER_NORMAL);
485 }
486
487 /* ---------------------------------------------------------------------- */
488 /* ----- INTERNAL CALLBACKS --------------------------------------------- */
489 /* ---------------------------------------------------------------------- */
490
491 #if !DISABLE_AVATARS
492
493 struct gg_fetch_avatar_data
494 {
495 PurpleConnection *gc;
496 gchar *uin;
497 gchar *avatar_url;
498 };
499
500
gg_fetch_avatar_cb(PurpleUtilFetchUrlData * url_data,gpointer user_data,const gchar * data,size_t len,const gchar * error_message)501 static void gg_fetch_avatar_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
502 const gchar *data, size_t len, const gchar *error_message) {
503 struct gg_fetch_avatar_data *d = user_data;
504 PurpleAccount *account;
505 PurpleBuddy *buddy;
506 gpointer buddy_icon_data;
507
508 purple_debug_info("gg", "gg_fetch_avatar_cb: got avatar image for %s\n",
509 d->uin);
510
511 /* FIXME: This shouldn't be necessary */
512 if (!PURPLE_CONNECTION_IS_VALID(d->gc)) {
513 g_free(d->uin);
514 g_free(d->avatar_url);
515 g_free(d);
516 g_return_if_reached();
517 }
518
519 account = purple_connection_get_account(d->gc);
520 buddy = purple_find_buddy(account, d->uin);
521
522 if (buddy == NULL)
523 goto out;
524
525 buddy_icon_data = g_memdup2(data, len);
526
527 purple_buddy_icons_set_for_user(account, purple_buddy_get_name(buddy),
528 buddy_icon_data, len, d->avatar_url);
529 purple_debug_info("gg", "gg_fetch_avatar_cb: UIN %s should have avatar "
530 "now\n", d->uin);
531
532 out:
533 g_free(d->uin);
534 g_free(d->avatar_url);
535 g_free(d);
536 }
537
gg_get_avatar_url_cb(PurpleUtilFetchUrlData * url_data,gpointer user_data,const gchar * url_text,size_t len,const gchar * error_message)538 static void gg_get_avatar_url_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
539 const gchar *url_text, size_t len, const gchar *error_message) {
540 struct gg_fetch_avatar_data *data;
541 PurpleConnection *gc = user_data;
542 PurpleAccount *account;
543 PurpleBuddy *buddy;
544 const char *uin;
545 const char *is_blank;
546 const char *checksum;
547
548 gchar *bigavatar = NULL;
549 xmlnode *xml = NULL;
550 xmlnode *xmlnode_users;
551 xmlnode *xmlnode_user;
552 xmlnode *xmlnode_avatars;
553 xmlnode *xmlnode_avatar;
554 xmlnode *xmlnode_bigavatar;
555
556 g_return_if_fail(PURPLE_CONNECTION_IS_VALID(gc));
557 account = purple_connection_get_account(gc);
558
559 if (error_message != NULL)
560 purple_debug_error("gg", "gg_get_avatars_cb error: %s\n", error_message);
561 else if (len > 0 && url_text && *url_text) {
562 xml = xmlnode_from_str(url_text, -1);
563 if (xml == NULL)
564 goto out;
565
566 xmlnode_users = xmlnode_get_child(xml, "users");
567 if (xmlnode_users == NULL)
568 goto out;
569
570 xmlnode_user = xmlnode_get_child(xmlnode_users, "user");
571 if (xmlnode_user == NULL)
572 goto out;
573
574 uin = xmlnode_get_attrib(xmlnode_user, "uin");
575
576 xmlnode_avatars = xmlnode_get_child(xmlnode_user, "avatars");
577 if (xmlnode_avatars == NULL)
578 goto out;
579
580 xmlnode_avatar = xmlnode_get_child(xmlnode_avatars, "avatar");
581 if (xmlnode_avatar == NULL)
582 goto out;
583
584 xmlnode_bigavatar = xmlnode_get_child(xmlnode_avatar, "originBigAvatar");
585 if (xmlnode_bigavatar == NULL)
586 goto out;
587
588 is_blank = xmlnode_get_attrib(xmlnode_avatar, "blank");
589 bigavatar = xmlnode_get_data(xmlnode_bigavatar);
590
591 purple_debug_info("gg", "gg_get_avatar_url_cb: UIN %s, IS_BLANK %s, "
592 "URL %s\n",
593 uin ? uin : "(null)", is_blank ? is_blank : "(null)",
594 bigavatar ? bigavatar : "(null)");
595
596 if (uin != NULL && bigavatar != NULL) {
597 buddy = purple_find_buddy(account, uin);
598 if (buddy == NULL)
599 goto out;
600
601 checksum = purple_buddy_icons_get_checksum_for_user(buddy);
602
603 if (purple_strequal(is_blank, "1")) {
604 purple_buddy_icons_set_for_user(account,
605 purple_buddy_get_name(buddy), NULL, 0, NULL);
606 } else if (!purple_strequal(checksum, bigavatar)) {
607 data = g_new0(struct gg_fetch_avatar_data, 1);
608 data->gc = gc;
609 data->uin = g_strdup(uin);
610 data->avatar_url = g_strdup(bigavatar);
611
612 purple_debug_info("gg", "gg_get_avatar_url_cb: "
613 "requesting avatar for %s\n", uin);
614 url_data = purple_util_fetch_url_request_len_with_account(account,
615 bigavatar, TRUE, "Mozilla/4.0 (compatible; MSIE 5.0)",
616 FALSE, NULL, FALSE, -1, gg_fetch_avatar_cb, data);
617 }
618 }
619 }
620
621 out:
622 if (xml)
623 xmlnode_free(xml);
624 g_free(bigavatar);
625 }
626
627 #endif
628
629 /**
630 * Try to update avatar of the buddy.
631 *
632 * @param gc PurpleConnection
633 * @param uin UIN of the buddy.
634 */
ggp_update_buddy_avatar(PurpleConnection * gc,uin_t uin)635 static void ggp_update_buddy_avatar(PurpleConnection *gc, uin_t uin)
636 {
637 #if DISABLE_AVATARS
638 purple_debug_warning("gg", "ggp_update_buddy_avatar: disabled, please "
639 "update to 3.0.0, when available\n");
640 #else
641 gchar *avatarurl;
642 PurpleUtilFetchUrlData *url_data;
643
644 purple_debug_info("gg", "ggp_update_buddy_avatar(gc, %u)\n", uin);
645
646 avatarurl = g_strdup_printf("http://api.gadu-gadu.pl/avatars/%u/0.xml", uin);
647
648 url_data = purple_util_fetch_url_request_len_with_account(
649 purple_connection_get_account(gc), avatarurl, TRUE,
650 "Mozilla/4.0 (compatible; MSIE 5.5)", FALSE, NULL, FALSE, -1,
651 gg_get_avatar_url_cb, gc);
652
653 g_free(avatarurl);
654 #endif
655 }
656
657 /**
658 * Handle change of the status of the buddy.
659 *
660 * @param gc PurpleConnection
661 * @param uin UIN of the buddy.
662 * @param status ID of the status.
663 * @param descr Description.
664 */
ggp_generic_status_handler(PurpleConnection * gc,uin_t uin,int status,const char * descr)665 static void ggp_generic_status_handler(PurpleConnection *gc, uin_t uin,
666 int status, const char *descr)
667 {
668 gchar *from;
669 const char *st;
670 char *status_msg = NULL;
671
672 ggp_update_buddy_avatar(gc, uin);
673
674 from = g_strdup_printf("%u", uin);
675
676 switch (status) {
677 case GG_STATUS_NOT_AVAIL:
678 case GG_STATUS_NOT_AVAIL_DESCR:
679 st = purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE);
680 break;
681 case GG_STATUS_FFC:
682 case GG_STATUS_FFC_DESCR:
683 st = purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE);
684 break;
685 case GG_STATUS_AVAIL:
686 case GG_STATUS_AVAIL_DESCR:
687 st = purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE);
688 break;
689 case GG_STATUS_BUSY:
690 case GG_STATUS_BUSY_DESCR:
691 st = purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY);
692 break;
693 case GG_STATUS_DND:
694 case GG_STATUS_DND_DESCR:
695 st = purple_primitive_get_id_from_type(PURPLE_STATUS_UNAVAILABLE);
696 break;
697 case GG_STATUS_BLOCKED:
698 /* user is blocking us.... */
699 st = "blocked";
700 break;
701 default:
702 st = purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE);
703 purple_debug_info("gg",
704 "GG_EVENT_NOTIFY: Unknown status: %d\n", status);
705 break;
706 }
707
708 if (descr != NULL) {
709 status_msg = g_strdup(descr);
710 g_strstrip(status_msg);
711 if (status_msg[0] == '\0') {
712 g_free(status_msg);
713 status_msg = NULL;
714 }
715 }
716
717 purple_debug_info("gg", "status of %u is %s [%s]\n", uin, st,
718 status_msg ? status_msg : "");
719 if (status_msg == NULL) {
720 purple_prpl_got_user_status(purple_connection_get_account(gc),
721 from, st, NULL);
722 } else {
723 purple_prpl_got_user_status(purple_connection_get_account(gc),
724 from, st, "message", status_msg, NULL);
725 g_free(status_msg);
726 }
727 g_free(from);
728 }
729
ggp_sr_close_cb(gpointer user_data)730 static void ggp_sr_close_cb(gpointer user_data)
731 {
732 GGPSearchForm *form = user_data;
733 GGPInfo *info = form->user_data;
734
735 ggp_search_remove(info->searches, form->seq);
736 purple_debug_info("gg", "ggp_sr_close_cb(): Removed seq %u\n",
737 form->seq);
738 ggp_search_form_destroy(form);
739 }
740
741 /**
742 * Translate a status' ID to a more user-friendly name.
743 *
744 * @param id The ID of the status.
745 *
746 * @return The user-friendly name of the status.
747 */
ggp_status_by_id(unsigned int id)748 static const char *ggp_status_by_id(unsigned int id)
749 {
750 const char *st;
751
752 purple_debug_info("gg", "ggp_status_by_id: %d\n", id);
753 switch (id) {
754 case GG_STATUS_NOT_AVAIL:
755 case GG_STATUS_NOT_AVAIL_DESCR:
756 st = _("Offline");
757 break;
758 case GG_STATUS_AVAIL:
759 case GG_STATUS_AVAIL_DESCR:
760 st = _("Available");
761 break;
762 case GG_STATUS_FFC:
763 case GG_STATUS_FFC_DESCR:
764 return _("Chatty");
765 case GG_STATUS_DND:
766 case GG_STATUS_DND_DESCR:
767 return _("Do Not Disturb");
768 case GG_STATUS_BUSY:
769 case GG_STATUS_BUSY_DESCR:
770 st = _("Away");
771 break;
772 default:
773 st = _("Unknown");
774 break;
775 }
776
777 return st;
778 }
779
ggp_pubdir_handle_info(PurpleConnection * gc,gg_pubdir50_t req,GGPSearchForm * form)780 static void ggp_pubdir_handle_info(PurpleConnection *gc, gg_pubdir50_t req,
781 GGPSearchForm *form)
782 {
783 PurpleNotifyUserInfo *user_info;
784 PurpleBuddy *buddy;
785 char *val, *who;
786
787 user_info = purple_notify_user_info_new();
788
789 val = ggp_search_get_result(req, 0, GG_PUBDIR50_STATUS);
790 /* XXX: Use of ggp_str_to_uin() is an ugly hack! */
791 purple_notify_user_info_add_pair(user_info, _("Status"), ggp_status_by_id(ggp_str_to_uin(val)));
792 g_free(val);
793
794 who = ggp_search_get_result(req, 0, GG_PUBDIR50_UIN);
795 purple_notify_user_info_add_pair(user_info, _("UIN"), who);
796
797 val = ggp_search_get_result(req, 0, GG_PUBDIR50_FIRSTNAME);
798 purple_notify_user_info_add_pair(user_info, _("First Name"), val);
799 g_free(val);
800
801 val = ggp_search_get_result(req, 0, GG_PUBDIR50_NICKNAME);
802 purple_notify_user_info_add_pair(user_info, _("Nickname"), val);
803 g_free(val);
804
805 val = ggp_search_get_result(req, 0, GG_PUBDIR50_CITY);
806 purple_notify_user_info_add_pair(user_info, _("City"), val);
807 g_free(val);
808
809 val = ggp_search_get_result(req, 0, GG_PUBDIR50_BIRTHYEAR);
810 if (strncmp(val, "0", 1)) {
811 purple_notify_user_info_add_pair(user_info, _("Birth Year"), val);
812 }
813 g_free(val);
814
815 /*
816 * Include a status message, if exists and buddy is in the blist.
817 */
818 buddy = purple_find_buddy(purple_connection_get_account(gc), who);
819 if (NULL != buddy) {
820 PurpleStatus *status;
821 const char *msg;
822 char *text;
823
824 status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
825 msg = purple_status_get_attr_string(status, "message");
826
827 if (msg != NULL) {
828 text = g_markup_escape_text(msg, -1);
829 purple_notify_user_info_add_pair(user_info, _("Message"), text);
830 g_free(text);
831 }
832 }
833
834 purple_notify_userinfo(gc, who, user_info, ggp_sr_close_cb, form);
835 g_free(who);
836 purple_notify_user_info_destroy(user_info);
837 }
838
ggp_pubdir_handle_full(PurpleConnection * gc,gg_pubdir50_t req,GGPSearchForm * form)839 static void ggp_pubdir_handle_full(PurpleConnection *gc, gg_pubdir50_t req,
840 GGPSearchForm *form)
841 {
842 PurpleNotifySearchResults *results;
843 PurpleNotifySearchColumn *column;
844 int res_count;
845 int start;
846 int i;
847
848 g_return_if_fail(form != NULL);
849
850 res_count = gg_pubdir50_count(req);
851 res_count = (res_count > PUBDIR_RESULTS_MAX) ? PUBDIR_RESULTS_MAX : res_count;
852 if (form->page_size == 0)
853 form->page_size = res_count;
854
855 results = purple_notify_searchresults_new();
856
857 if (results == NULL) {
858 purple_debug_error("gg", "ggp_pubdir_reply_handler: "
859 "Unable to display the search results.\n");
860 purple_notify_error(gc, NULL,
861 _("Unable to display the search results."),
862 NULL);
863 if (form->window == NULL)
864 ggp_sr_close_cb(form);
865 return;
866 }
867
868 column = purple_notify_searchresults_column_new(_("UIN"));
869 purple_notify_searchresults_column_add(results, column);
870
871 column = purple_notify_searchresults_column_new(_("First Name"));
872 purple_notify_searchresults_column_add(results, column);
873
874 column = purple_notify_searchresults_column_new(_("Nickname"));
875 purple_notify_searchresults_column_add(results, column);
876
877 column = purple_notify_searchresults_column_new(_("City"));
878 purple_notify_searchresults_column_add(results, column);
879
880 column = purple_notify_searchresults_column_new(_("Birth Year"));
881 purple_notify_searchresults_column_add(results, column);
882
883 purple_debug_info("gg", "Going with %d entries\n", res_count);
884
885 start = (int)ggp_str_to_uin(gg_pubdir50_get(req, 0, GG_PUBDIR50_START));
886 purple_debug_info("gg", "start = %d\n", start);
887
888 for (i = 0; i < res_count; i++) {
889 GList *row = NULL;
890 char *birth = ggp_search_get_result(req, i, GG_PUBDIR50_BIRTHYEAR);
891
892 /* TODO: Status will be displayed as an icon. */
893 /* row = g_list_append(row, ggp_search_get_result(req, i, GG_PUBDIR50_STATUS)); */
894 row = g_list_append(row, ggp_search_get_result(req, i,
895 GG_PUBDIR50_UIN));
896 row = g_list_append(row, ggp_search_get_result(req, i,
897 GG_PUBDIR50_FIRSTNAME));
898 row = g_list_append(row, ggp_search_get_result(req, i,
899 GG_PUBDIR50_NICKNAME));
900 row = g_list_append(row, ggp_search_get_result(req, i,
901 GG_PUBDIR50_CITY));
902 row = g_list_append(row,
903 (birth && strncmp(birth, "0", 1)) ? birth : g_strdup("-"));
904
905 purple_notify_searchresults_row_add(results, row);
906 }
907
908 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_CONTINUE,
909 ggp_callback_show_next);
910 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD,
911 ggp_callback_add_buddy);
912 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM,
913 ggp_callback_im);
914
915 if (form->window == NULL) {
916 void *h = purple_notify_searchresults(gc,
917 _("Gadu-Gadu Public Directory"),
918 _("Search results"), NULL, results,
919 (PurpleNotifyCloseCallback)ggp_sr_close_cb,
920 form);
921
922 if (h == NULL) {
923 purple_debug_error("gg", "ggp_pubdir_reply_handler: "
924 "Unable to display the search results.\n");
925 purple_notify_error(gc, NULL,
926 _("Unable to display the search results."),
927 NULL);
928 return;
929 }
930
931 form->window = h;
932 } else {
933 purple_notify_searchresults_new_rows(gc, results, form->window);
934 }
935 }
936
ggp_pubdir_reply_handler(PurpleConnection * gc,gg_pubdir50_t req)937 static void ggp_pubdir_reply_handler(PurpleConnection *gc, gg_pubdir50_t req)
938 {
939 GGPInfo *info = gc->proto_data;
940 GGPSearchForm *form;
941 int res_count;
942 guint32 seq;
943
944 seq = gg_pubdir50_seq(req);
945 form = ggp_search_get(info->searches, seq);
946 purple_debug_info("gg",
947 "ggp_pubdir_reply_handler(): seq %u --> form %p\n", seq, form);
948 /*
949 * this can happen when user will request more results
950 * and close the results window before they arrive.
951 */
952 g_return_if_fail(form != NULL);
953
954 res_count = gg_pubdir50_count(req);
955 if (res_count < 1) {
956 purple_debug_info("gg", "GG_EVENT_PUBDIR50_SEARCH_REPLY: Nothing found\n");
957 purple_notify_error(gc, NULL,
958 _("No matching users found"),
959 _("There are no users matching your search criteria."));
960 if (form->window == NULL)
961 ggp_sr_close_cb(form);
962 return;
963 }
964
965 switch (form->search_type) {
966 case GGP_SEARCH_TYPE_INFO:
967 ggp_pubdir_handle_info(gc, req, form);
968 break;
969 case GGP_SEARCH_TYPE_FULL:
970 ggp_pubdir_handle_full(gc, req, form);
971 break;
972 default:
973 purple_debug_warning("gg", "Unknown search_type!\n");
974 break;
975 }
976 }
977
ggp_recv_image_handler(PurpleConnection * gc,const struct gg_event * ev)978 static void ggp_recv_image_handler(PurpleConnection *gc, const struct gg_event *ev)
979 {
980 gint imgid = 0;
981 GGPInfo *info = gc->proto_data;
982 GList *entry = g_list_first(info->pending_richtext_messages);
983 gchar *handlerid = g_strdup_printf("IMGID_HANDLER-%i", ev->event.image_reply.crc32);
984
985 imgid = purple_imgstore_add_with_id(
986 g_memdup2(ev->event.image_reply.image, ev->event.image_reply.size),
987 ev->event.image_reply.size,
988 ev->event.image_reply.filename);
989
990 purple_debug_info("gg", "ggp_recv_image_handler: got image with crc32: %u\n", ev->event.image_reply.crc32);
991
992 while(entry) {
993 if (strstr((gchar *)entry->data, handlerid) != NULL) {
994 gchar **split = g_strsplit((gchar *)entry->data, handlerid, 3);
995 gchar *text = g_strdup_printf("%s%i%s", split[0], imgid, split[1]);
996 purple_debug_info("gg", "ggp_recv_image_handler: found message matching crc32: %s\n", (gchar *)entry->data);
997 g_strfreev(split);
998 info->pending_richtext_messages = g_list_remove(info->pending_richtext_messages, entry->data);
999 /* We don't have any more images to download */
1000 if (strstr(text, "<IMG ID=\"IMGID_HANDLER") == NULL) {
1001 gchar *buf = g_strdup_printf("%lu", (unsigned long int)ev->event.image_reply.sender);
1002 serv_got_im(gc, buf, text, PURPLE_MESSAGE_IMAGES, time(NULL));
1003 g_free(buf);
1004 purple_debug_info("gg", "ggp_recv_image_handler: richtext message: %s\n", text);
1005 g_free(text);
1006 break;
1007 }
1008 info->pending_richtext_messages = g_list_append(info->pending_richtext_messages, text);
1009 break;
1010 }
1011 entry = g_list_next(entry);
1012 }
1013 g_free(handlerid);
1014
1015 return;
1016 }
1017
1018
1019 /**
1020 * Dispatch a message received from a buddy.
1021 *
1022 * @param gc PurpleConnection.
1023 * @param ev Gadu-Gadu event structure.
1024 *
1025 * Image receiving, some code borrowed from Kadu http://www.kadu.net
1026 */
ggp_recv_message_handler(PurpleConnection * gc,const struct gg_event * ev)1027 static void ggp_recv_message_handler(PurpleConnection *gc, const struct gg_event *ev)
1028 {
1029 GGPInfo *info = gc->proto_data;
1030 PurpleConversation *conv;
1031 gchar *from;
1032 gchar *msg;
1033 gchar *tmp;
1034
1035 if (ev->event.msg.message == NULL)
1036 {
1037 purple_debug_warning("gg", "ggp_recv_message_handler: NULL as message pointer\n");
1038 return;
1039 }
1040
1041 from = g_strdup_printf("%lu", (unsigned long int)ev->event.msg.sender);
1042
1043 /*
1044 tmp = charset_convert((const char *)ev->event.msg.message,
1045 "CP1250", "UTF-8");
1046 */
1047 tmp = g_strdup_printf("%s", ev->event.msg.message);
1048 purple_str_strip_char(tmp, '\r');
1049 msg = g_markup_escape_text(tmp, -1);
1050 g_free(tmp);
1051
1052 /* We got richtext message */
1053 if (ev->event.msg.formats_length)
1054 {
1055 gboolean got_image = FALSE, bold = FALSE, italic = FALSE, under = FALSE;
1056 char *cformats = (char *)ev->event.msg.formats;
1057 char *cformats_end = cformats + ev->event.msg.formats_length;
1058 gint increased_len = 0;
1059 struct gg_msg_richtext_format *actformat;
1060 struct gg_msg_richtext_image *actimage;
1061 GString *message = g_string_new(msg);
1062 gchar *handlerid;
1063
1064 purple_debug_info("gg", "ggp_recv_message_handler: richtext msg from (%s): %s %i formats\n", from, msg, ev->event.msg.formats_length);
1065
1066 while (cformats < cformats_end)
1067 {
1068 gint byteoffset;
1069 actformat = (struct gg_msg_richtext_format *)cformats;
1070 cformats += sizeof(struct gg_msg_richtext_format);
1071 byteoffset = g_utf8_offset_to_pointer(message->str, actformat->position + increased_len) - message->str;
1072
1073 if(actformat->position == 0 && actformat->font == 0) {
1074 purple_debug_warning("gg", "ggp_recv_message_handler: bogus formatting (inc: %i)\n", increased_len);
1075 continue;
1076 }
1077 purple_debug_info("gg", "ggp_recv_message_handler: format at pos: %i, image:%i, bold:%i, italic: %i, under:%i (inc: %i)\n",
1078 actformat->position,
1079 (actformat->font & GG_FONT_IMAGE) != 0,
1080 (actformat->font & GG_FONT_BOLD) != 0,
1081 (actformat->font & GG_FONT_ITALIC) != 0,
1082 (actformat->font & GG_FONT_UNDERLINE) != 0,
1083 increased_len);
1084
1085 if (actformat->font & GG_FONT_IMAGE) {
1086 got_image = TRUE;
1087 actimage = (struct gg_msg_richtext_image*)(cformats);
1088 cformats += sizeof(struct gg_msg_richtext_image);
1089 purple_debug_info("gg", "ggp_recv_message_handler: image received, size: %d, crc32: %i\n", actimage->size, actimage->crc32);
1090
1091 /* Checking for errors, image size shouldn't be
1092 * larger than 255.000 bytes */
1093 if (actimage->size > 255000) {
1094 purple_debug_warning("gg", "ggp_recv_message_handler: received image large than 255 kb\n");
1095 continue;
1096 }
1097
1098 gg_image_request(info->session, ev->event.msg.sender,
1099 actimage->size, actimage->crc32);
1100
1101 handlerid = g_strdup_printf("<IMG ID=\"IMGID_HANDLER-%i\">", actimage->crc32);
1102 g_string_insert(message, byteoffset, handlerid);
1103 increased_len += strlen(handlerid);
1104 g_free(handlerid);
1105 continue;
1106 }
1107
1108 if (actformat->font & GG_FONT_BOLD) {
1109 if (bold == FALSE) {
1110 g_string_insert(message, byteoffset, "<b>");
1111 increased_len += 3;
1112 bold = TRUE;
1113 }
1114 } else if (bold) {
1115 g_string_insert(message, byteoffset, "</b>");
1116 increased_len += 4;
1117 bold = FALSE;
1118 }
1119
1120 if (actformat->font & GG_FONT_ITALIC) {
1121 if (italic == FALSE) {
1122 g_string_insert(message, byteoffset, "<i>");
1123 increased_len += 3;
1124 italic = TRUE;
1125 }
1126 } else if (italic) {
1127 g_string_insert(message, byteoffset, "</i>");
1128 increased_len += 4;
1129 italic = FALSE;
1130 }
1131
1132 if (actformat->font & GG_FONT_UNDERLINE) {
1133 if (under == FALSE) {
1134 g_string_insert(message, byteoffset, "<u>");
1135 increased_len += 3;
1136 under = TRUE;
1137 }
1138 } else if (under) {
1139 g_string_insert(message, byteoffset, "</u>");
1140 increased_len += 4;
1141 under = FALSE;
1142 }
1143
1144 if (actformat->font & GG_FONT_COLOR) {
1145 cformats += sizeof(struct gg_msg_richtext_color);
1146 }
1147 }
1148
1149 msg = message->str;
1150 g_string_free(message, FALSE);
1151
1152 if (got_image) {
1153 info->pending_richtext_messages = g_list_append(info->pending_richtext_messages, msg);
1154 return;
1155 }
1156 }
1157
1158 purple_debug_info("gg", "ggp_recv_message_handler: msg from (%s): %s (class = %d; rcpt_count = %d)\n",
1159 from, msg, ev->event.msg.msgclass,
1160 ev->event.msg.recipients_count);
1161
1162 if (ev->event.msg.recipients_count == 0) {
1163 serv_got_im(gc, from, msg, 0, ev->event.msg.time);
1164 } else {
1165 const char *chat_name;
1166 int chat_id;
1167 char *buddy_name;
1168
1169 chat_name = ggp_confer_find_by_participants(gc,
1170 ev->event.msg.recipients,
1171 ev->event.msg.recipients_count);
1172
1173 if (chat_name == NULL) {
1174 chat_name = ggp_confer_add_new(gc, NULL);
1175 serv_got_joined_chat(gc, info->chats_count, chat_name);
1176
1177 ggp_confer_participants_add_uin(gc, chat_name,
1178 ev->event.msg.sender);
1179
1180 ggp_confer_participants_add(gc, chat_name,
1181 ev->event.msg.recipients,
1182 ev->event.msg.recipients_count);
1183 }
1184 conv = ggp_confer_find_by_name(gc, chat_name);
1185 chat_id = purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv));
1186
1187 buddy_name = ggp_buddy_get_name(gc, ev->event.msg.sender);
1188 serv_got_chat_in(gc, chat_id, buddy_name,
1189 PURPLE_MESSAGE_RECV, msg, ev->event.msg.time);
1190 g_free(buddy_name);
1191 }
1192 g_free(msg);
1193 g_free(from);
1194 }
1195
ggp_send_image_handler(PurpleConnection * gc,const struct gg_event * ev)1196 static void ggp_send_image_handler(PurpleConnection *gc, const struct gg_event *ev)
1197 {
1198 GGPInfo *info = gc->proto_data;
1199 PurpleStoredImage *image;
1200 gint imgid = GPOINTER_TO_INT(g_hash_table_lookup(info->pending_images, GINT_TO_POINTER(ev->event.image_request.crc32)));
1201
1202 purple_debug_info("gg", "ggp_send_image_handler: image request received, crc32: %u, imgid: %d\n", ev->event.image_request.crc32, imgid);
1203
1204 if(imgid)
1205 {
1206 if((image = purple_imgstore_find_by_id(imgid))) {
1207 gint image_size = purple_imgstore_get_size(image);
1208 gconstpointer image_bin = purple_imgstore_get_data(image);
1209 const char *image_filename = purple_imgstore_get_filename(image);
1210
1211 purple_debug_info("gg", "ggp_send_image_handler: sending image imgid: %i, crc: %u\n", imgid, ev->event.image_request.crc32);
1212 gg_image_reply(info->session, (unsigned long int)ev->event.image_request.sender, image_filename, image_bin, image_size);
1213 purple_imgstore_unref(image);
1214 } else {
1215 purple_debug_error("gg", "ggp_send_image_handler: image imgid: %i, crc: %u in hash but not found in imgstore!\n", imgid, ev->event.image_request.crc32);
1216 }
1217 g_hash_table_remove(info->pending_images, GINT_TO_POINTER(ev->event.image_request.crc32));
1218 }
1219 }
1220
ggp_typing_notification_handler(PurpleConnection * gc,uin_t uin,int length)1221 static void ggp_typing_notification_handler(PurpleConnection *gc, uin_t uin, int length) {
1222 gchar *from;
1223
1224 from = g_strdup_printf("%u", uin);
1225 if (length)
1226 serv_got_typing(gc, from, 0, PURPLE_TYPING);
1227 else
1228 serv_got_typing_stopped(gc, from);
1229 g_free(from);
1230 }
1231
1232 /**
1233 * Handling of XML events.
1234 *
1235 * @param gc PurpleConnection.
1236 * @param data Raw XML contents.
1237 *
1238 * @see http://toxygen.net/libgadu/protocol/#ch1.13
1239 */
ggp_xml_event_handler(PurpleConnection * gc,char * data)1240 static void ggp_xml_event_handler(PurpleConnection *gc, char *data)
1241 {
1242 xmlnode *xml = NULL;
1243 xmlnode *xmlnode_next_event;
1244
1245 xml = xmlnode_from_str(data, -1);
1246 if (xml == NULL)
1247 goto out;
1248
1249 xmlnode_next_event = xmlnode_get_child(xml, "event");
1250 while (xmlnode_next_event != NULL)
1251 {
1252 xmlnode *xmlnode_current_event = xmlnode_next_event;
1253
1254 xmlnode *xmlnode_type;
1255 char *event_type_raw;
1256 int event_type = 0;
1257
1258 xmlnode *xmlnode_sender;
1259 char *event_sender_raw;
1260 uin_t event_sender = 0;
1261
1262 xmlnode_next_event = xmlnode_get_next_twin(xmlnode_next_event);
1263
1264 xmlnode_type = xmlnode_get_child(xmlnode_current_event, "type");
1265 if (xmlnode_type == NULL)
1266 continue;
1267 event_type_raw = xmlnode_get_data(xmlnode_type);
1268 if (event_type_raw != NULL)
1269 event_type = atoi(event_type_raw);
1270 g_free(event_type_raw);
1271
1272 xmlnode_sender = xmlnode_get_child(xmlnode_current_event, "sender");
1273 if (xmlnode_sender != NULL)
1274 {
1275 event_sender_raw = xmlnode_get_data(xmlnode_sender);
1276 if (event_sender_raw != NULL)
1277 event_sender = ggp_str_to_uin(event_sender_raw);
1278 g_free(event_sender_raw);
1279 }
1280
1281 switch (event_type)
1282 {
1283 case 28: /* avatar update */
1284 purple_debug_info("gg",
1285 "ggp_xml_event_handler: avatar updated (uid: %u)\n",
1286 event_sender);
1287 ggp_update_buddy_avatar(gc, event_sender);
1288 break;
1289 default:
1290 purple_debug_error("gg",
1291 "ggp_xml_event_handler: unsupported event type=%d from=%u\n",
1292 event_type, event_sender);
1293 }
1294 }
1295
1296 out:
1297 if (xml)
1298 xmlnode_free(xml);
1299 }
1300
ggp_callback_recv(gpointer _gc,gint fd,PurpleInputCondition cond)1301 static void ggp_callback_recv(gpointer _gc, gint fd, PurpleInputCondition cond)
1302 {
1303 PurpleConnection *gc = _gc;
1304 GGPInfo *info = gc->proto_data;
1305 struct gg_event *ev;
1306 int i;
1307
1308 if (!(ev = gg_watch_fd(info->session))) {
1309 purple_debug_error("gg",
1310 "ggp_callback_recv: gg_watch_fd failed -- CRITICAL!\n");
1311 purple_connection_error_reason (gc,
1312 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1313 _("Unable to read from socket"));
1314 return;
1315 }
1316
1317 purple_input_remove(gc->inpa);
1318 gc->inpa = purple_input_add(info->session->fd,
1319 ggp_tcpsocket_inputcond_gg_to_purple(info->session->check),
1320 ggp_callback_recv, gc);
1321
1322 switch (ev->type) {
1323 case GG_EVENT_NONE:
1324 /* Nothing happened. */
1325 break;
1326 case GG_EVENT_MSG:
1327 ggp_recv_message_handler(gc, ev);
1328 break;
1329 case GG_EVENT_ACK:
1330 /* Changing %u to %i fixes compiler warning */
1331 purple_debug_info("gg",
1332 "ggp_callback_recv: message sent to: %i, delivery status=%d, seq=%d\n",
1333 ev->event.ack.recipient, ev->event.ack.status,
1334 ev->event.ack.seq);
1335 break;
1336 case GG_EVENT_IMAGE_REPLY:
1337 ggp_recv_image_handler(gc, ev);
1338 break;
1339 case GG_EVENT_IMAGE_REQUEST:
1340 ggp_send_image_handler(gc, ev);
1341 break;
1342 case GG_EVENT_NOTIFY:
1343 case GG_EVENT_NOTIFY_DESCR:
1344 {
1345 struct gg_notify_reply *n;
1346 char *descr;
1347
1348 purple_debug_info("gg", "notify_pre: (%d) status: %d\n",
1349 ev->event.notify->uin,
1350 GG_S(ev->event.notify->status));
1351
1352 n = (ev->type == GG_EVENT_NOTIFY) ? ev->event.notify
1353 : ev->event.notify_descr.notify;
1354
1355 for (; n->uin; n++) {
1356 descr = (ev->type == GG_EVENT_NOTIFY) ? NULL
1357 : ev->event.notify_descr.descr;
1358
1359 purple_debug_info("gg",
1360 "notify: (%d) status: %d; descr: %s\n",
1361 n->uin, GG_S(n->status), descr ? descr : "(null)");
1362
1363 ggp_generic_status_handler(gc,
1364 n->uin, GG_S(n->status), descr);
1365 }
1366 }
1367 break;
1368 case GG_EVENT_NOTIFY60:
1369 for (i = 0; ev->event.notify60[i].uin; i++) {
1370 purple_debug_info("gg",
1371 "notify60: (%d) status=%d; version=%d; descr=%s\n",
1372 ev->event.notify60[i].uin,
1373 GG_S(ev->event.notify60[i].status),
1374 ev->event.notify60[i].version,
1375 ev->event.notify60[i].descr ? ev->event.notify60[i].descr : "(null)");
1376
1377 ggp_generic_status_handler(gc, ev->event.notify60[i].uin,
1378 GG_S(ev->event.notify60[i].status),
1379 ev->event.notify60[i].descr);
1380 }
1381 break;
1382 case GG_EVENT_STATUS:
1383 purple_debug_info("gg", "status: (%d) status=%d; descr=%s\n",
1384 ev->event.status.uin, GG_S(ev->event.status.status),
1385 ev->event.status.descr ? ev->event.status.descr : "(null)");
1386
1387 ggp_generic_status_handler(gc, ev->event.status.uin,
1388 GG_S(ev->event.status.status), ev->event.status.descr);
1389 break;
1390 case GG_EVENT_STATUS60:
1391 purple_debug_info("gg",
1392 "status60: (%d) status=%d; version=%d; descr=%s\n",
1393 ev->event.status60.uin, GG_S(ev->event.status60.status),
1394 ev->event.status60.version,
1395 ev->event.status60.descr ? ev->event.status60.descr : "(null)");
1396
1397 ggp_generic_status_handler(gc, ev->event.status60.uin,
1398 GG_S(ev->event.status60.status), ev->event.status60.descr);
1399 break;
1400 case GG_EVENT_PUBDIR50_SEARCH_REPLY:
1401 ggp_pubdir_reply_handler(gc, ev->event.pubdir50);
1402 break;
1403 case GG_EVENT_TYPING_NOTIFICATION:
1404 ggp_typing_notification_handler(gc, ev->event.typing_notification.uin,
1405 ev->event.typing_notification.length);
1406 break;
1407 case GG_EVENT_XML_EVENT:
1408 purple_debug_info("gg", "GG_EVENT_XML_EVENT\n");
1409 ggp_xml_event_handler(gc, ev->event.xml_event.data);
1410 break;
1411 default:
1412 purple_debug_error("gg",
1413 "unsupported event type=%d\n", ev->type);
1414 break;
1415 }
1416
1417 gg_free_event(ev);
1418 }
1419
ggp_async_login_handler(gpointer _gc,gint fd,PurpleInputCondition cond)1420 static void ggp_async_login_handler(gpointer _gc, gint fd, PurpleInputCondition cond)
1421 {
1422 PurpleConnection *gc = _gc;
1423 GGPInfo *info;
1424 struct gg_event *ev;
1425
1426 g_return_if_fail(PURPLE_CONNECTION_IS_VALID(gc));
1427
1428 info = gc->proto_data;
1429
1430 purple_debug_info("gg", "login_handler: session: check = %d; state = %d;\n",
1431 info->session->check, info->session->state);
1432
1433 switch (info->session->state) {
1434 case GG_STATE_RESOLVING:
1435 purple_debug_info("gg", "GG_STATE_RESOLVING\n");
1436 break;
1437 case GG_STATE_RESOLVING_GG:
1438 purple_debug_info("gg", "GG_STATE_RESOLVING_GG\n");
1439 break;
1440 case GG_STATE_CONNECTING_HUB:
1441 purple_debug_info("gg", "GG_STATE_CONNECTING_HUB\n");
1442 break;
1443 case GG_STATE_READING_DATA:
1444 purple_debug_info("gg", "GG_STATE_READING_DATA\n");
1445 break;
1446 case GG_STATE_CONNECTING_GG:
1447 purple_debug_info("gg", "GG_STATE_CONNECTING_GG\n");
1448 break;
1449 case GG_STATE_READING_KEY:
1450 purple_debug_info("gg", "GG_STATE_READING_KEY\n");
1451 break;
1452 case GG_STATE_READING_REPLY:
1453 purple_debug_info("gg", "GG_STATE_READING_REPLY\n");
1454 break;
1455 case GG_STATE_TLS_NEGOTIATION:
1456 purple_debug_info("gg", "GG_STATE_TLS_NEGOTIATION\n");
1457 break;
1458 default:
1459 purple_debug_error("gg", "unknown state = %d\n",
1460 info->session->state);
1461 break;
1462 }
1463
1464 if (!(ev = gg_watch_fd(info->session))) {
1465 purple_debug_error("gg", "login_handler: gg_watch_fd failed!\n");
1466 purple_connection_error_reason (gc,
1467 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1468 _("Unable to read from socket"));
1469 return;
1470 }
1471 purple_debug_info("gg", "login_handler: session->fd = %d\n", info->session->fd);
1472 purple_debug_info("gg", "login_handler: session: check = %d; state = %d;\n",
1473 info->session->check, info->session->state);
1474
1475 purple_input_remove(gc->inpa);
1476
1477 /** XXX I think that this shouldn't be done if ev->type is GG_EVENT_CONN_FAILED or GG_EVENT_CONN_SUCCESS -datallah */
1478 if (info->session->fd >= 0)
1479 gc->inpa = purple_input_add(info->session->fd,
1480 ggp_tcpsocket_inputcond_gg_to_purple(info->session->check),
1481 ggp_async_login_handler, gc);
1482
1483 switch (ev->type) {
1484 case GG_EVENT_NONE:
1485 /* Nothing happened. */
1486 purple_debug_info("gg", "GG_EVENT_NONE\n");
1487 break;
1488 case GG_EVENT_CONN_SUCCESS:
1489 {
1490 purple_debug_info("gg", "GG_EVENT_CONN_SUCCESS\n");
1491 purple_input_remove(gc->inpa);
1492 gc->inpa = purple_input_add(info->session->fd,
1493 ggp_tcpsocket_inputcond_gg_to_purple(info->session->check),
1494 ggp_callback_recv, gc);
1495
1496 ggp_buddylist_send(gc);
1497 purple_connection_update_progress(gc, _("Connected"), 1, 2);
1498 purple_connection_set_state(gc, PURPLE_CONNECTED);
1499 }
1500 break;
1501 case GG_EVENT_CONN_FAILED:
1502 purple_input_remove(gc->inpa);
1503 gc->inpa = 0;
1504 purple_connection_error_reason (gc,
1505 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1506 _("Connection failed"));
1507 break;
1508 case GG_EVENT_MSG:
1509 if (ev->event.msg.sender == 0)
1510 /* system messages are mostly ads */
1511 purple_debug_info("gg", "System message:\n%s\n",
1512 ev->event.msg.message);
1513 else
1514 purple_debug_warning("gg", "GG_EVENT_MSG: message from user %u "
1515 "unexpected while connecting:\n%s\n",
1516 ev->event.msg.sender,
1517 ev->event.msg.message);
1518 break;
1519 default:
1520 purple_debug_error("gg", "strange event: %d\n", ev->type);
1521 break;
1522 }
1523
1524 gg_free_event(ev);
1525 }
1526
1527 /* ---------------------------------------------------------------------- */
1528 /* ----- PurplePluginProtocolInfo ----------------------------------------- */
1529 /* ---------------------------------------------------------------------- */
1530
ggp_list_icon(PurpleAccount * account,PurpleBuddy * buddy)1531 static const char *ggp_list_icon(PurpleAccount *account, PurpleBuddy *buddy)
1532 {
1533 return "gadu-gadu";
1534 }
1535
ggp_status_text(PurpleBuddy * b)1536 static char *ggp_status_text(PurpleBuddy *b)
1537 {
1538 PurpleStatus *status;
1539 const char *msg;
1540 char *text;
1541 char *tmp;
1542
1543 status = purple_presence_get_active_status(
1544 purple_buddy_get_presence(b));
1545 msg = purple_status_get_attr_string(status, "message");
1546
1547 if (msg == NULL)
1548 return NULL;
1549
1550 tmp = purple_markup_strip_html(msg);
1551 text = g_markup_escape_text(tmp, -1);
1552 g_free(tmp);
1553
1554 return text;
1555 }
1556
ggp_tooltip_text(PurpleBuddy * b,PurpleNotifyUserInfo * user_info,gboolean full)1557 static void ggp_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full)
1558 {
1559 PurpleStatus *status;
1560 char *text, *tmp;
1561 const char *msg, *name, *alias;
1562
1563 g_return_if_fail(b != NULL);
1564
1565 status = purple_presence_get_active_status(purple_buddy_get_presence(b));
1566 msg = purple_status_get_attr_string(status, "message");
1567 name = purple_status_get_name(status);
1568 alias = purple_buddy_get_alias(b);
1569
1570 purple_notify_user_info_add_pair (user_info, _("Alias"), alias);
1571
1572 if (msg != NULL) {
1573 text = g_markup_escape_text(msg, -1);
1574 if (PURPLE_BUDDY_IS_ONLINE(b)) {
1575 tmp = g_strdup_printf("%s: %s", name, text);
1576 purple_notify_user_info_add_pair(user_info, _("Status"), tmp);
1577 g_free(tmp);
1578 } else {
1579 purple_notify_user_info_add_pair(user_info, _("Message"), text);
1580 }
1581 g_free(text);
1582 /* We don't want to duplicate 'Status: Offline'. */
1583 } else if (PURPLE_BUDDY_IS_ONLINE(b)) {
1584 purple_notify_user_info_add_pair(user_info, _("Status"), name);
1585 }
1586 }
1587
ggp_status_types(PurpleAccount * account)1588 static GList *ggp_status_types(PurpleAccount *account)
1589 {
1590 PurpleStatusType *type;
1591 GList *types = NULL;
1592
1593 type = purple_status_type_new_with_attrs(
1594 PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE, TRUE, FALSE,
1595 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1596 NULL);
1597 types = g_list_append(types, type);
1598
1599 /*
1600 * Without this selecting Invisible as own status doesn't
1601 * work. It's not used and not needed to show status of buddies.
1602 */
1603 type = purple_status_type_new_with_attrs(
1604 PURPLE_STATUS_INVISIBLE, NULL, NULL, TRUE, TRUE, FALSE,
1605 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1606 NULL);
1607 types = g_list_append(types, type);
1608
1609 type = purple_status_type_new_with_attrs(
1610 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1611 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1612 NULL);
1613 types = g_list_append(types, type);
1614
1615 /*
1616 * New statuses for GG 8.0 like PoGGadaj ze mna (not yet because
1617 * libpurple can't support Chatty status) and Nie przeszkadzac
1618 */
1619 type = purple_status_type_new_with_attrs(
1620 PURPLE_STATUS_UNAVAILABLE, NULL, NULL, TRUE, TRUE, FALSE,
1621 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1622 NULL);
1623 types = g_list_append(types, type);
1624
1625 /*
1626 * This status is necessary to display guys who are blocking *us*.
1627 */
1628 type = purple_status_type_new_with_attrs(
1629 PURPLE_STATUS_INVISIBLE, "blocked", _("Blocked"), TRUE, FALSE, FALSE,
1630 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING), NULL);
1631 types = g_list_append(types, type);
1632
1633 type = purple_status_type_new_with_attrs(
1634 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE,
1635 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1636 NULL);
1637 types = g_list_append(types, type);
1638
1639 return types;
1640 }
1641
ggp_blist_node_menu(PurpleBlistNode * node)1642 static GList *ggp_blist_node_menu(PurpleBlistNode *node)
1643 {
1644 PurpleMenuAction *act;
1645 GList *m = NULL;
1646 PurpleAccount *account;
1647 GGPInfo *info;
1648
1649 if (!PURPLE_BLIST_NODE_IS_BUDDY(node))
1650 return NULL;
1651
1652 account = purple_buddy_get_account((PurpleBuddy *) node);
1653 info = purple_account_get_connection(account)->proto_data;
1654 if (info->chats) {
1655 act = purple_menu_action_new(_("Add to chat"),
1656 PURPLE_CALLBACK(ggp_bmenu_add_to_chat),
1657 NULL, NULL);
1658 m = g_list_append(m, act);
1659 }
1660
1661 return m;
1662 }
1663
ggp_chat_info(PurpleConnection * gc)1664 static GList *ggp_chat_info(PurpleConnection *gc)
1665 {
1666 GList *m = NULL;
1667 struct proto_chat_entry *pce;
1668
1669 pce = g_new0(struct proto_chat_entry, 1);
1670 pce->label = _("Chat _name:");
1671 pce->identifier = "name";
1672 pce->required = TRUE;
1673 m = g_list_append(m, pce);
1674
1675 return m;
1676 }
1677
ggp_login_to(PurpleAccount * account,uint32_t server)1678 static void ggp_login_to(PurpleAccount *account, uint32_t server)
1679 {
1680 PurpleConnection *gc;
1681 PurplePresence *presence;
1682 PurpleStatus *status;
1683 struct gg_login_params *glp;
1684 GGPInfo *info;
1685 const gchar *encryption_type;
1686
1687 if (ggp_setup_proxy(account) == -1)
1688 return;
1689
1690 gc = purple_account_get_connection(account);
1691 glp = g_new0(struct gg_login_params, 1);
1692 info = gc->proto_data;
1693 g_return_if_fail(info);
1694
1695 /* Probably this should be moved to *_new() function. */
1696 info->session = NULL;
1697 info->chats = NULL;
1698 info->chats_count = 0;
1699 info->token = NULL;
1700 info->searches = ggp_search_new();
1701 info->pending_richtext_messages = NULL;
1702 info->pending_images = g_hash_table_new(g_direct_hash, g_direct_equal);
1703 info->status_broadcasting = purple_account_get_bool(account, "status_broadcasting", TRUE);
1704
1705 glp->uin = ggp_get_uin(account);
1706 glp->password = (char *)purple_account_get_password(account);
1707 glp->image_size = 255;
1708
1709 presence = purple_account_get_presence(account);
1710 status = purple_presence_get_active_status(presence);
1711
1712 glp->encoding = GG_ENCODING_UTF8;
1713 glp->protocol_features = (GG_FEATURE_STATUS80|GG_FEATURE_DND_FFC
1714 |GG_FEATURE_TYPING_NOTIFICATION);
1715
1716 glp->async = 1;
1717 glp->status = ggp_to_gg_status(status, &glp->status_descr);
1718
1719 encryption_type = purple_account_get_string(account, "encryption", "none");
1720 purple_debug_info("gg", "Requested encryption type: %s\n", encryption_type);
1721 if (purple_strequal(encryption_type, "opportunistic_tls"))
1722 glp->tls = 1;
1723 else
1724 glp->tls = 0;
1725 purple_debug_info("gg", "TLS enabled: %d\n", glp->tls);
1726
1727 if (!info->status_broadcasting)
1728 glp->status = glp->status|GG_STATUS_FRIENDS_MASK;
1729 glp->server_addr = server;
1730
1731 info->session = gg_login(glp);
1732 g_free(glp);
1733
1734 purple_connection_update_progress(gc, _("Connecting"), 0, 2);
1735 if (info->session == NULL) {
1736 purple_connection_error_reason (gc,
1737 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1738 _("Connection failed"));
1739 return;
1740 }
1741 gc->inpa = purple_input_add(info->session->fd,
1742 ggp_tcpsocket_inputcond_gg_to_purple(info->session->check),
1743 ggp_async_login_handler, gc);
1744 }
1745
1746 static void
ggp_login_resolved(GSList * hosts,gpointer _account,const char * error_message)1747 ggp_login_resolved(GSList *hosts, gpointer _account, const char *error_message)
1748 {
1749 PurpleAccount *account = _account;
1750 PurpleConnection *gc;
1751 GGPInfo *info;
1752 uint32_t server_addr = 0;
1753
1754 gc = purple_account_get_connection(account);
1755 info = gc->proto_data;
1756 g_return_if_fail(info);
1757 info->dns_query = NULL;
1758
1759 while (hosts && (hosts = g_slist_delete_link(hosts, hosts))) {
1760 struct sockaddr *addr = hosts->data;
1761
1762 if (addr->sa_family == AF_INET && server_addr == 0) {
1763 struct sockaddr_in *addrv4 = (struct sockaddr_in *)addr;
1764
1765 server_addr = addrv4->sin_addr.s_addr;
1766 }
1767
1768 g_free(hosts->data);
1769 hosts = g_slist_delete_link(hosts, hosts);
1770 }
1771
1772 if (server_addr == 0) {
1773 gchar *tmp = g_strdup_printf(
1774 _("Unable to resolve hostname: %s"), error_message);
1775 purple_connection_error_reason(gc,
1776 /* should this be a settings error? */
1777 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
1778 g_free(tmp);
1779 return;
1780 }
1781
1782 ggp_login_to(account, server_addr);
1783 }
1784
1785 static void
ggp_login(PurpleAccount * account)1786 ggp_login(PurpleAccount *account)
1787 {
1788 PurpleConnection *gc;
1789 GGPInfo *info;
1790 const char *address;
1791
1792 gc = purple_account_get_connection(account);
1793 info = g_new0(GGPInfo, 1);
1794 gc->proto_data = info;
1795
1796 address = purple_account_get_string(account, "gg_server", "");
1797 if (address == NULL || address[0] == '\0') {
1798 purple_debug_info("gg", "Trying to retrieve address from gg appmsg service\n");
1799 ggp_login_to(account, 0);
1800 return;
1801 }
1802
1803 purple_debug_info("gg", "Using gg server given by user (%s)\n", address);
1804 info->dns_query = purple_dnsquery_a_account(account, address, 8074,
1805 ggp_login_resolved, account);
1806 }
1807
ggp_close(PurpleConnection * gc)1808 static void ggp_close(PurpleConnection *gc)
1809 {
1810 if (gc == NULL) {
1811 purple_debug_info("gg", "gc == NULL\n");
1812 return;
1813 }
1814
1815 if (gc->proto_data) {
1816 PurpleAccount *account = purple_connection_get_account(gc);
1817 PurpleStatus *status;
1818 GGPInfo *info = gc->proto_data;
1819
1820 if (info->dns_query)
1821 purple_dnsquery_destroy(info->dns_query);
1822
1823 status = purple_account_get_active_status(account);
1824
1825 if (info->session != NULL) {
1826 ggp_set_status(account, status);
1827 gg_logoff(info->session);
1828 gg_free_session(info->session);
1829 }
1830
1831 purple_account_set_bool(account, "status_broadcasting", info->status_broadcasting);
1832
1833 /* Immediately close any notifications on this handle since that process depends
1834 * upon the contents of info->searches, which we are about to destroy.
1835 */
1836 purple_notify_close_with_handle(gc);
1837
1838 ggp_search_destroy(info->searches);
1839 g_list_free(info->pending_richtext_messages);
1840 g_hash_table_destroy(info->pending_images);
1841 g_free(info);
1842 gc->proto_data = NULL;
1843 }
1844
1845 if (gc->inpa > 0)
1846 purple_input_remove(gc->inpa);
1847
1848 purple_debug_info("gg", "Connection closed.\n");
1849 }
1850
ggp_send_im(PurpleConnection * gc,const char * who,const char * msg,PurpleMessageFlags flags)1851 static int ggp_send_im(PurpleConnection *gc, const char *who, const char *msg,
1852 PurpleMessageFlags flags)
1853 {
1854 GGPInfo *info = gc->proto_data;
1855 char *tmp, *plain;
1856 int ret = 1;
1857 unsigned char format[1024];
1858 unsigned int format_length = sizeof(struct gg_msg_richtext);
1859 gint pos = 0;
1860 GData *attribs;
1861 const char *start, *end = NULL, *last;
1862
1863 if (msg == NULL || *msg == '\0') {
1864 return 0;
1865 }
1866
1867 last = msg;
1868
1869 /* Check if the message is richtext */
1870 /* TODO: Check formatting, too */
1871 if(purple_markup_find_tag("img", last, &start, &end, &attribs)) {
1872
1873 GString *string_buffer = g_string_new(NULL);
1874 struct gg_msg_richtext fmt;
1875
1876 do {
1877 PurpleStoredImage *image;
1878 const char *id;
1879
1880 /* Add text before the image */
1881 if(start - last) {
1882 pos = pos + g_utf8_strlen(last, start - last);
1883 g_string_append_len(string_buffer, last, start - last);
1884 }
1885
1886 if((id = g_datalist_get_data(&attribs, "id")) && (image = purple_imgstore_find_by_id(atoi(id)))) {
1887 struct gg_msg_richtext_format actformat;
1888 struct gg_msg_richtext_image actimage;
1889 gint image_size = purple_imgstore_get_size(image);
1890 gconstpointer image_bin = purple_imgstore_get_data(image);
1891 const char *image_filename = purple_imgstore_get_filename(image);
1892 uint32_t crc32 = gg_crc32(0, image_bin, image_size);
1893
1894 g_hash_table_insert(info->pending_images, GINT_TO_POINTER(crc32), GINT_TO_POINTER(atoi(id)));
1895 purple_imgstore_ref(image);
1896 purple_debug_info("gg", "ggp_send_im_richtext: got crc: %u for imgid: %i\n", crc32, atoi(id));
1897
1898 actformat.font = GG_FONT_IMAGE;
1899 actformat.position = pos;
1900
1901 actimage.unknown1 = 0x0109;
1902 actimage.size = gg_fix32(image_size);
1903 actimage.crc32 = gg_fix32(crc32);
1904
1905 if (actimage.size > 255000) {
1906 purple_debug_warning("gg", "ggp_send_im_richtext: image over 255kb!\n");
1907 } else {
1908 purple_debug_info("gg", "ggp_send_im_richtext: adding images to richtext, size: %i, crc32: %u, name: %s\n", actimage.size, actimage.crc32, image_filename);
1909
1910 memcpy(format + format_length, &actformat, sizeof(actformat));
1911 format_length += sizeof(actformat);
1912 memcpy(format + format_length, &actimage, sizeof(actimage));
1913 format_length += sizeof(actimage);
1914 }
1915 } else {
1916 purple_debug_error("gg", "ggp_send_im_richtext: image not found in the image store!");
1917 }
1918
1919 last = end + 1;
1920 g_datalist_clear(&attribs);
1921
1922 } while(purple_markup_find_tag("img", last, &start, &end, &attribs));
1923
1924 /* Add text after the images */
1925 if(last && *last) {
1926 /* this is currently not used, but might be useful later? */
1927 /* pos = pos + g_utf8_strlen(last, -1); */
1928 g_string_append(string_buffer, last);
1929 }
1930
1931 fmt.flag = 2;
1932 fmt.length = format_length - sizeof(fmt);
1933 memcpy(format, &fmt, sizeof(fmt));
1934
1935 purple_debug_info("gg", "ggp_send_im: richtext msg = %s\n", string_buffer->str);
1936 plain = purple_unescape_html(string_buffer->str);
1937 g_string_free(string_buffer, TRUE);
1938 } else {
1939 purple_debug_info("gg", "ggp_send_im: msg = %s\n", msg);
1940 plain = purple_unescape_html(msg);
1941 }
1942
1943 /*
1944 tmp = charset_convert(plain, "UTF-8", "CP1250");
1945 */
1946 tmp = g_strdup_printf("%s", plain);
1947
1948 if (tmp && (format_length - sizeof(struct gg_msg_richtext))) {
1949 if(gg_send_message_richtext(info->session, GG_CLASS_CHAT, ggp_str_to_uin(who), (unsigned char *)tmp, format, format_length) < 0) {
1950 ret = -1;
1951 } else {
1952 ret = 1;
1953 }
1954 } else if (NULL == tmp || *tmp == 0) {
1955 ret = 0;
1956 } else if (strlen(tmp) > GG_MSG_MAXSIZE) {
1957 ret = -E2BIG;
1958 } else if (gg_send_message(info->session, GG_CLASS_CHAT,
1959 ggp_str_to_uin(who), (unsigned char *)tmp) < 0) {
1960 ret = -1;
1961 } else {
1962 ret = 1;
1963 }
1964
1965 g_free(plain);
1966 g_free(tmp);
1967
1968 return ret;
1969 }
1970
ggp_send_typing(PurpleConnection * gc,const char * name,PurpleTypingState state)1971 static unsigned int ggp_send_typing(PurpleConnection *gc, const char *name, PurpleTypingState state)
1972 {
1973 int dummy_length; // we don't send real length of typed message
1974
1975 if (state == PURPLE_TYPED) // not supported
1976 return 1;
1977
1978 if (state == PURPLE_TYPING)
1979 dummy_length = (int)g_random_int();
1980 else // PURPLE_NOT_TYPING
1981 dummy_length = 0;
1982
1983 gg_typing_notification(
1984 ((GGPInfo*)gc->proto_data)->session,
1985 ggp_str_to_uin(name),
1986 dummy_length);
1987
1988 return 1; // wait 1 second before another notification
1989 }
1990
ggp_get_info(PurpleConnection * gc,const char * name)1991 static void ggp_get_info(PurpleConnection *gc, const char *name)
1992 {
1993 GGPInfo *info = gc->proto_data;
1994 GGPSearchForm *form;
1995 guint32 seq;
1996
1997 form = ggp_search_form_new(GGP_SEARCH_TYPE_INFO);
1998
1999 form->user_data = info;
2000 form->uin = g_strdup(name);
2001
2002 seq = ggp_search_start(gc, form);
2003 ggp_search_add(info->searches, seq, form);
2004 purple_debug_info("gg", "ggp_get_info(): Added seq %u", seq);
2005 }
2006
ggp_to_gg_status(PurpleStatus * status,char ** msg)2007 static int ggp_to_gg_status(PurpleStatus *status, char **msg)
2008 {
2009 const char *status_id = purple_status_get_id(status);
2010 int new_status, new_status_descr;
2011 const char *new_msg;
2012
2013 g_return_val_if_fail(msg != NULL, 0);
2014
2015 purple_debug_info("gg", "ggp_to_gg_status: Requested status = %s\n",
2016 status_id);
2017
2018 if (purple_strequal(status_id, "available")) {
2019 new_status = GG_STATUS_AVAIL;
2020 new_status_descr = GG_STATUS_AVAIL_DESCR;
2021 } else if (purple_strequal(status_id, "away")) {
2022 new_status = GG_STATUS_BUSY;
2023 new_status_descr = GG_STATUS_BUSY_DESCR;
2024 } else if (purple_strequal(status_id, "unavailable")) {
2025 new_status = GG_STATUS_DND;
2026 new_status_descr = GG_STATUS_DND_DESCR;
2027 } else if (purple_strequal(status_id, "invisible")) {
2028 new_status = GG_STATUS_INVISIBLE;
2029 new_status_descr = GG_STATUS_INVISIBLE_DESCR;
2030 } else if (purple_strequal(status_id, "offline")) {
2031 new_status = GG_STATUS_NOT_AVAIL;
2032 new_status_descr = GG_STATUS_NOT_AVAIL_DESCR;
2033 } else {
2034 new_status = GG_STATUS_AVAIL;
2035 new_status_descr = GG_STATUS_AVAIL_DESCR;
2036 purple_debug_info("gg",
2037 "ggp_set_status: unknown status requested (status_id=%s)\n",
2038 status_id);
2039 }
2040
2041 new_msg = purple_status_get_attr_string(status, "message");
2042
2043 if(new_msg) {
2044 /*
2045 char *tmp = purple_markup_strip_html(new_msg);
2046 *msg = charset_convert(tmp, "UTF-8", "CP1250");
2047 g_free(tmp);
2048 */
2049 *msg = purple_markup_strip_html(new_msg);
2050
2051 return new_status_descr;
2052 } else {
2053 *msg = NULL;
2054 return new_status;
2055 }
2056 }
2057
ggp_set_status(PurpleAccount * account,PurpleStatus * status)2058 static void ggp_set_status(PurpleAccount *account, PurpleStatus *status)
2059 {
2060 PurpleConnection *gc;
2061 GGPInfo *info;
2062 int new_status;
2063 char *new_msg = NULL;
2064
2065 if (!purple_status_is_active(status))
2066 return;
2067
2068 gc = purple_account_get_connection(account);
2069 info = gc->proto_data;
2070
2071 new_status = ggp_to_gg_status(status, &new_msg);
2072
2073 if (!info->status_broadcasting)
2074 new_status = new_status|GG_STATUS_FRIENDS_MASK;
2075
2076 if (new_msg == NULL) {
2077 gg_change_status(info->session, new_status);
2078 } else {
2079 gg_change_status_descr(info->session, new_status, new_msg);
2080 g_free(new_msg);
2081 }
2082
2083 ggp_status_fake_to_self(account);
2084
2085 }
2086
ggp_add_buddy(PurpleConnection * gc,PurpleBuddy * buddy,PurpleGroup * group)2087 static void ggp_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2088 {
2089 PurpleAccount *account;
2090 GGPInfo *info = gc->proto_data;
2091 const gchar *name = purple_buddy_get_name(buddy);
2092
2093 gg_add_notify(info->session, ggp_str_to_uin(name));
2094
2095 account = purple_connection_get_account(gc);
2096 if (purple_strequal(purple_account_get_username(account), name)) {
2097 ggp_status_fake_to_self(account);
2098 }
2099 }
2100
ggp_remove_buddy(PurpleConnection * gc,PurpleBuddy * buddy,PurpleGroup * group)2101 static void ggp_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy,
2102 PurpleGroup *group)
2103 {
2104 GGPInfo *info = gc->proto_data;
2105
2106 gg_remove_notify(info->session, ggp_str_to_uin(purple_buddy_get_name(buddy)));
2107 }
2108
ggp_join_chat(PurpleConnection * gc,GHashTable * data)2109 static void ggp_join_chat(PurpleConnection *gc, GHashTable *data)
2110 {
2111 GGPInfo *info = gc->proto_data;
2112 GGPChat *chat;
2113 char *chat_name;
2114 GList *l;
2115 PurpleConversation *conv;
2116 PurpleAccount *account = purple_connection_get_account(gc);
2117
2118 chat_name = g_hash_table_lookup(data, "name");
2119
2120 if (chat_name == NULL)
2121 return;
2122
2123 purple_debug_info("gg", "joined %s chat\n", chat_name);
2124
2125 for (l = info->chats; l != NULL; l = l->next) {
2126 chat = l->data;
2127
2128 if (chat != NULL && g_utf8_collate(chat->name, chat_name) == 0) {
2129 purple_notify_error(gc, _("Chat error"),
2130 _("This chat name is already in use"), NULL);
2131 return;
2132 }
2133 }
2134
2135 ggp_confer_add_new(gc, chat_name);
2136 conv = serv_got_joined_chat(gc, info->chats_count, chat_name);
2137 purple_conv_chat_add_user(PURPLE_CONV_CHAT(conv),
2138 purple_account_get_username(account), NULL,
2139 PURPLE_CBFLAGS_NONE, TRUE);
2140 }
2141
ggp_get_chat_name(GHashTable * data)2142 static char *ggp_get_chat_name(GHashTable *data) {
2143 return g_strdup(g_hash_table_lookup(data, "name"));
2144 }
2145
ggp_chat_send(PurpleConnection * gc,int id,const char * message,PurpleMessageFlags flags)2146 static int ggp_chat_send(PurpleConnection *gc, int id, const char *message, PurpleMessageFlags flags)
2147 {
2148 PurpleConversation *conv;
2149 GGPInfo *info = gc->proto_data;
2150 GGPChat *chat = NULL;
2151 GList *l;
2152 /* char *msg, *plain; */
2153 gchar *msg;
2154 uin_t *uins;
2155 int count = 0;
2156
2157 if ((conv = purple_find_chat(gc, id)) == NULL)
2158 return -EINVAL;
2159
2160 for (l = info->chats; l != NULL; l = l->next) {
2161 chat = l->data;
2162
2163 if (g_utf8_collate(chat->name, conv->name) == 0) {
2164 break;
2165 }
2166
2167 chat = NULL;
2168 }
2169
2170 if (chat == NULL) {
2171 purple_debug_error("gg",
2172 "ggp_chat_send: Hm... that's strange. No such chat?\n");
2173 return -EINVAL;
2174 }
2175
2176 uins = g_new0(uin_t, g_list_length(chat->participants));
2177
2178 for (l = chat->participants; l != NULL; l = l->next) {
2179 uin_t uin = GPOINTER_TO_INT(l->data);
2180
2181 uins[count++] = uin;
2182 }
2183
2184 /*
2185 plain = purple_unescape_html(message);
2186 msg = charset_convert(plain, "UTF-8", "CP1250");
2187 g_free(plain);
2188 */
2189 msg = purple_unescape_html(message);
2190 gg_send_message_confer(info->session, GG_CLASS_CHAT, count, uins,
2191 (unsigned char *)msg);
2192 g_free(msg);
2193 g_free(uins);
2194
2195 serv_got_chat_in(gc, id,
2196 purple_account_get_username(purple_connection_get_account(gc)),
2197 flags, message, time(NULL));
2198
2199 return 0;
2200 }
2201
ggp_keepalive(PurpleConnection * gc)2202 static void ggp_keepalive(PurpleConnection *gc)
2203 {
2204 GGPInfo *info = gc->proto_data;
2205
2206 /* purple_debug_info("gg", "Keeping connection alive....\n"); */
2207
2208 if (gg_ping(info->session) < 0) {
2209 purple_debug_info("gg", "Not connected to the server "
2210 "or gg_session is not correct\n");
2211 purple_connection_error_reason (gc,
2212 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
2213 _("Not connected to the server"));
2214 }
2215 }
2216
ggp_actions(PurplePlugin * plugin,gpointer context)2217 static GList *ggp_actions(PurplePlugin *plugin, gpointer context)
2218 {
2219 GList *m = NULL;
2220 PurplePluginAction *act;
2221
2222 act = purple_plugin_action_new(_("Find buddies..."),
2223 ggp_find_buddies);
2224 m = g_list_append(m, act);
2225
2226 act = purple_plugin_action_new(_("Change status broadcasting"),
2227 ggp_action_change_status_broadcasting);
2228 m = g_list_append(m, act);
2229
2230 m = g_list_append(m, NULL);
2231
2232 act = purple_plugin_action_new(_("Save buddylist to file..."),
2233 ggp_action_buddylist_save);
2234 m = g_list_append(m, act);
2235
2236 act = purple_plugin_action_new(_("Load buddylist from file..."),
2237 ggp_action_buddylist_load);
2238 m = g_list_append(m, act);
2239
2240 return m;
2241 }
2242
ggp_offline_message(const PurpleBuddy * buddy)2243 static gboolean ggp_offline_message(const PurpleBuddy *buddy)
2244 {
2245 return TRUE;
2246 }
2247
ggp_load(PurplePlugin * plugin)2248 static gboolean ggp_load(PurplePlugin *plugin)
2249 {
2250 purple_debug_info("gg", "Loading Gadu-Gadu protocol plugin with "
2251 "libgadu %s...\n", gg_libgadu_version());
2252
2253 gg_is_gpl_compliant();
2254
2255 return TRUE;
2256 }
2257
2258 static PurplePluginProtocolInfo prpl_info =
2259 {
2260 OPT_PROTO_IM_IMAGE,
2261 NULL, /* user_splits */
2262 NULL, /* protocol_options */
2263 {"png", 32, 32, 96, 96, 0, PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */
2264 ggp_list_icon, /* list_icon */
2265 NULL, /* list_emblem */
2266 ggp_status_text, /* status_text */
2267 ggp_tooltip_text, /* tooltip_text */
2268 ggp_status_types, /* status_types */
2269 ggp_blist_node_menu, /* blist_node_menu */
2270 ggp_chat_info, /* chat_info */
2271 NULL, /* chat_info_defaults */
2272 ggp_login, /* login */
2273 ggp_close, /* close */
2274 ggp_send_im, /* send_im */
2275 NULL, /* set_info */
2276 ggp_send_typing, /* send_typing */
2277 ggp_get_info, /* get_info */
2278 ggp_set_status, /* set_away */
2279 NULL, /* set_idle */
2280 NULL, /* change_passwd */
2281 ggp_add_buddy, /* add_buddy */
2282 NULL, /* add_buddies */
2283 ggp_remove_buddy, /* remove_buddy */
2284 NULL, /* remove_buddies */
2285 NULL, /* add_permit */
2286 ggp_add_deny, /* add_deny */
2287 NULL, /* rem_permit */
2288 ggp_rem_deny, /* rem_deny */
2289 NULL, /* set_permit_deny */
2290 ggp_join_chat, /* join_chat */
2291 NULL, /* reject_chat */
2292 ggp_get_chat_name, /* get_chat_name */
2293 NULL, /* chat_invite */
2294 NULL, /* chat_leave */
2295 NULL, /* chat_whisper */
2296 ggp_chat_send, /* chat_send */
2297 ggp_keepalive, /* keepalive */
2298 NULL, /* register_user */
2299 NULL, /* get_cb_info */
2300 NULL, /* get_cb_away */
2301 NULL, /* alias_buddy */
2302 NULL, /* group_buddy */
2303 NULL, /* rename_group */
2304 NULL, /* buddy_free */
2305 NULL, /* convo_closed */
2306 NULL, /* normalize */
2307 NULL, /* set_buddy_icon */
2308 NULL, /* remove_group */
2309 NULL, /* get_cb_real_name */
2310 NULL, /* set_chat_topic */
2311 NULL, /* find_blist_chat */
2312 NULL, /* roomlist_get_list */
2313 NULL, /* roomlist_cancel */
2314 NULL, /* roomlist_expand_category */
2315 NULL, /* can_receive_file */
2316 NULL, /* send_file */
2317 NULL, /* new_xfer */
2318 ggp_offline_message, /* offline_message */
2319 NULL, /* whiteboard_prpl_ops */
2320 NULL, /* send_raw */
2321 NULL, /* roomlist_room_serialize */
2322 NULL, /* unregister_user */
2323 NULL, /* send_attention */
2324 NULL, /* get_attention_types */
2325 sizeof(PurplePluginProtocolInfo), /* struct_size */
2326 NULL, /* get_account_text_table */
2327 NULL, /* initiate_media */
2328 NULL, /* can_do_media */
2329 NULL, /* get_moods */
2330 NULL, /* set_public_alias */
2331 NULL, /* get_public_alias */
2332 NULL, /* add_buddy_with_invite */
2333 NULL, /* add_buddies_with_invite */
2334 NULL, /* get_cb_alias */
2335 NULL, /* chat_can_receive_file */
2336 NULL, /* chat_send_file */
2337 };
2338
2339 static PurplePluginInfo info = {
2340 PURPLE_PLUGIN_MAGIC, /* magic */
2341 PURPLE_MAJOR_VERSION, /* major_version */
2342 PURPLE_MINOR_VERSION, /* minor_version */
2343 PURPLE_PLUGIN_PROTOCOL, /* plugin type */
2344 NULL, /* ui_requirement */
2345 0, /* flags */
2346 NULL, /* dependencies */
2347 PURPLE_PRIORITY_DEFAULT, /* priority */
2348
2349 "prpl-gg", /* id */
2350 "Gadu-Gadu", /* name */
2351 DISPLAY_VERSION, /* version */
2352
2353 N_("Gadu-Gadu Protocol Plugin"), /* summary */
2354 N_("Polish popular IM"), /* description */
2355 "boler@sourceforge.net", /* author */
2356 PURPLE_WEBSITE, /* homepage */
2357
2358 ggp_load, /* load */
2359 NULL, /* unload */
2360 NULL, /* destroy */
2361
2362 NULL, /* ui_info */
2363 &prpl_info, /* extra_info */
2364 NULL, /* prefs_info */
2365 ggp_actions, /* actions */
2366
2367 /* padding */
2368 NULL,
2369 NULL,
2370 NULL,
2371 NULL
2372 };
2373
2374 static void
purple_gg_debug_handler(int level,const char * format,va_list args)2375 purple_gg_debug_handler(int level, const char * format, va_list args)
2376 {
2377 PurpleDebugLevel purple_level;
2378 char msgbuff[1000];
2379 int ret;
2380
2381 /* Don't use glib's printf family, since it might not support
2382 * system-specific formatting modifiers (like %Iu for size on win32). */
2383 ret = vsnprintf(msgbuff, sizeof(msgbuff) / sizeof(char), format, args);
2384
2385 if (ret <= 0) {
2386 purple_debug_fatal("gg",
2387 "failed to printf the following message: %s",
2388 format ? format : "(null)\n");
2389
2390 return;
2391 }
2392
2393 /* This is pretty pointless since the GG_DEBUG levels don't correspond to
2394 * the purple ones */
2395 switch (level) {
2396 case GG_DEBUG_FUNCTION:
2397 purple_level = PURPLE_DEBUG_INFO;
2398 break;
2399 case GG_DEBUG_MISC:
2400 case GG_DEBUG_NET:
2401 case GG_DEBUG_DUMP:
2402 case GG_DEBUG_TRAFFIC:
2403 default:
2404 purple_level = PURPLE_DEBUG_MISC;
2405 break;
2406 }
2407
2408 purple_debug(purple_level, "gg", "%s", msgbuff);
2409 }
2410
init_plugin(PurplePlugin * plugin)2411 static void init_plugin(PurplePlugin *plugin)
2412 {
2413 PurpleAccountOption *option;
2414 GList *encryption_options = NULL;
2415
2416 option = purple_account_option_string_new(_("Nickname"),
2417 "nick", _("Gadu-Gadu User"));
2418 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
2419 option);
2420
2421 option = purple_account_option_string_new(_("GG server"),
2422 "gg_server", "");
2423 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
2424 option);
2425
2426 #define ADD_VALUE(list, desc, v) { \
2427 PurpleKeyValuePair *kvp = g_new0(PurpleKeyValuePair, 1); \
2428 kvp->key = g_strdup((desc)); \
2429 kvp->value = g_strdup((v)); \
2430 list = g_list_append(list, kvp); \
2431 }
2432
2433 ADD_VALUE(encryption_options, _("Don't use encryption"), "none");
2434 ADD_VALUE(encryption_options, _("Use encryption if available"),
2435 "opportunistic_tls");
2436 #if 0
2437 /* TODO */
2438 ADD_VALUE(encryption_options, _("Require encryption"), "require_tls");
2439 #endif
2440
2441 option = purple_account_option_list_new(_("Connection security"),
2442 "encryption", encryption_options);
2443 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
2444 option);
2445
2446 my_protocol = plugin;
2447
2448 gg_debug_handler = purple_gg_debug_handler;
2449 }
2450
2451 PURPLE_INIT_PLUGIN(gg, init_plugin, info);
2452
2453 /* vim: set ts=8 sts=0 sw=8 noet: */
2454