libpurple/protocols/gg/gg.c

branch
release-2.x.y
changeset 43264
50facee54d1d
parent 43263
b9cf92c8b16b
equal deleted inserted replaced
43263:b9cf92c8b16b 43264:50facee54d1d
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
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 */
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
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
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) {{{ */
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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 */
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 */
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
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 */
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
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
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
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
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 */
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 = g_string_free(message, FALSE);
1150
1151 if (got_image) {
1152 info->pending_richtext_messages = g_list_append(info->pending_richtext_messages, msg);
1153 return;
1154 }
1155 }
1156
1157 purple_debug_info("gg", "ggp_recv_message_handler: msg from (%s): %s (class = %d; rcpt_count = %d)\n",
1158 from, msg, ev->event.msg.msgclass,
1159 ev->event.msg.recipients_count);
1160
1161 if (ev->event.msg.recipients_count == 0) {
1162 serv_got_im(gc, from, msg, 0, ev->event.msg.time);
1163 } else {
1164 const char *chat_name;
1165 int chat_id;
1166 char *buddy_name;
1167
1168 chat_name = ggp_confer_find_by_participants(gc,
1169 ev->event.msg.recipients,
1170 ev->event.msg.recipients_count);
1171
1172 if (chat_name == NULL) {
1173 chat_name = ggp_confer_add_new(gc, NULL);
1174 serv_got_joined_chat(gc, info->chats_count, chat_name);
1175
1176 ggp_confer_participants_add_uin(gc, chat_name,
1177 ev->event.msg.sender);
1178
1179 ggp_confer_participants_add(gc, chat_name,
1180 ev->event.msg.recipients,
1181 ev->event.msg.recipients_count);
1182 }
1183 conv = ggp_confer_find_by_name(gc, chat_name);
1184 chat_id = purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv));
1185
1186 buddy_name = ggp_buddy_get_name(gc, ev->event.msg.sender);
1187 serv_got_chat_in(gc, chat_id, buddy_name,
1188 PURPLE_MESSAGE_RECV, msg, ev->event.msg.time);
1189 g_free(buddy_name);
1190 }
1191 g_free(msg);
1192 g_free(from);
1193 }
1194
1195 static void ggp_send_image_handler(PurpleConnection *gc, const struct gg_event *ev)
1196 {
1197 GGPInfo *info = gc->proto_data;
1198 PurpleStoredImage *image;
1199 gint imgid = GPOINTER_TO_INT(g_hash_table_lookup(info->pending_images, GINT_TO_POINTER(ev->event.image_request.crc32)));
1200
1201 purple_debug_info("gg", "ggp_send_image_handler: image request received, crc32: %u, imgid: %d\n", ev->event.image_request.crc32, imgid);
1202
1203 if(imgid)
1204 {
1205 if((image = purple_imgstore_find_by_id(imgid))) {
1206 gint image_size = purple_imgstore_get_size(image);
1207 gconstpointer image_bin = purple_imgstore_get_data(image);
1208 const char *image_filename = purple_imgstore_get_filename(image);
1209
1210 purple_debug_info("gg", "ggp_send_image_handler: sending image imgid: %i, crc: %u\n", imgid, ev->event.image_request.crc32);
1211 gg_image_reply(info->session, (unsigned long int)ev->event.image_request.sender, image_filename, image_bin, image_size);
1212 purple_imgstore_unref(image);
1213 } else {
1214 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);
1215 }
1216 g_hash_table_remove(info->pending_images, GINT_TO_POINTER(ev->event.image_request.crc32));
1217 }
1218 }
1219
1220 static void ggp_typing_notification_handler(PurpleConnection *gc, uin_t uin, int length) {
1221 gchar *from;
1222
1223 from = g_strdup_printf("%u", uin);
1224 if (length)
1225 serv_got_typing(gc, from, 0, PURPLE_TYPING);
1226 else
1227 serv_got_typing_stopped(gc, from);
1228 g_free(from);
1229 }
1230
1231 /**
1232 * Handling of XML events.
1233 *
1234 * @param gc PurpleConnection.
1235 * @param data Raw XML contents.
1236 *
1237 * @see http://toxygen.net/libgadu/protocol/#ch1.13
1238 */
1239 static void ggp_xml_event_handler(PurpleConnection *gc, char *data)
1240 {
1241 xmlnode *xml = NULL;
1242 xmlnode *xmlnode_next_event;
1243
1244 xml = xmlnode_from_str(data, -1);
1245 if (xml == NULL)
1246 goto out;
1247
1248 xmlnode_next_event = xmlnode_get_child(xml, "event");
1249 while (xmlnode_next_event != NULL)
1250 {
1251 xmlnode *xmlnode_current_event = xmlnode_next_event;
1252
1253 xmlnode *xmlnode_type;
1254 char *event_type_raw;
1255 int event_type = 0;
1256
1257 xmlnode *xmlnode_sender;
1258 char *event_sender_raw;
1259 uin_t event_sender = 0;
1260
1261 xmlnode_next_event = xmlnode_get_next_twin(xmlnode_next_event);
1262
1263 xmlnode_type = xmlnode_get_child(xmlnode_current_event, "type");
1264 if (xmlnode_type == NULL)
1265 continue;
1266 event_type_raw = xmlnode_get_data(xmlnode_type);
1267 if (event_type_raw != NULL)
1268 event_type = atoi(event_type_raw);
1269 g_free(event_type_raw);
1270
1271 xmlnode_sender = xmlnode_get_child(xmlnode_current_event, "sender");
1272 if (xmlnode_sender != NULL)
1273 {
1274 event_sender_raw = xmlnode_get_data(xmlnode_sender);
1275 if (event_sender_raw != NULL)
1276 event_sender = ggp_str_to_uin(event_sender_raw);
1277 g_free(event_sender_raw);
1278 }
1279
1280 switch (event_type)
1281 {
1282 case 28: /* avatar update */
1283 purple_debug_info("gg",
1284 "ggp_xml_event_handler: avatar updated (uid: %u)\n",
1285 event_sender);
1286 ggp_update_buddy_avatar(gc, event_sender);
1287 break;
1288 default:
1289 purple_debug_error("gg",
1290 "ggp_xml_event_handler: unsupported event type=%d from=%u\n",
1291 event_type, event_sender);
1292 }
1293 }
1294
1295 out:
1296 if (xml)
1297 xmlnode_free(xml);
1298 }
1299
1300 static void ggp_callback_recv(gpointer _gc, gint fd, PurpleInputCondition cond)
1301 {
1302 PurpleConnection *gc = _gc;
1303 GGPInfo *info = gc->proto_data;
1304 struct gg_event *ev;
1305 int i;
1306
1307 if (!(ev = gg_watch_fd(info->session))) {
1308 purple_debug_error("gg",
1309 "ggp_callback_recv: gg_watch_fd failed -- CRITICAL!\n");
1310 purple_connection_error_reason (gc,
1311 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1312 _("Unable to read from socket"));
1313 return;
1314 }
1315
1316 purple_input_remove(gc->inpa);
1317 gc->inpa = purple_input_add(info->session->fd,
1318 ggp_tcpsocket_inputcond_gg_to_purple(info->session->check),
1319 ggp_callback_recv, gc);
1320
1321 switch (ev->type) {
1322 case GG_EVENT_NONE:
1323 /* Nothing happened. */
1324 break;
1325 case GG_EVENT_MSG:
1326 ggp_recv_message_handler(gc, ev);
1327 break;
1328 case GG_EVENT_ACK:
1329 /* Changing %u to %i fixes compiler warning */
1330 purple_debug_info("gg",
1331 "ggp_callback_recv: message sent to: %i, delivery status=%d, seq=%d\n",
1332 ev->event.ack.recipient, ev->event.ack.status,
1333 ev->event.ack.seq);
1334 break;
1335 case GG_EVENT_IMAGE_REPLY:
1336 ggp_recv_image_handler(gc, ev);
1337 break;
1338 case GG_EVENT_IMAGE_REQUEST:
1339 ggp_send_image_handler(gc, ev);
1340 break;
1341 case GG_EVENT_NOTIFY:
1342 case GG_EVENT_NOTIFY_DESCR:
1343 {
1344 struct gg_notify_reply *n;
1345 char *descr;
1346
1347 purple_debug_info("gg", "notify_pre: (%d) status: %d\n",
1348 ev->event.notify->uin,
1349 GG_S(ev->event.notify->status));
1350
1351 n = (ev->type == GG_EVENT_NOTIFY) ? ev->event.notify
1352 : ev->event.notify_descr.notify;
1353
1354 for (; n->uin; n++) {
1355 descr = (ev->type == GG_EVENT_NOTIFY) ? NULL
1356 : ev->event.notify_descr.descr;
1357
1358 purple_debug_info("gg",
1359 "notify: (%d) status: %d; descr: %s\n",
1360 n->uin, GG_S(n->status), descr ? descr : "(null)");
1361
1362 ggp_generic_status_handler(gc,
1363 n->uin, GG_S(n->status), descr);
1364 }
1365 }
1366 break;
1367 case GG_EVENT_NOTIFY60:
1368 for (i = 0; ev->event.notify60[i].uin; i++) {
1369 purple_debug_info("gg",
1370 "notify60: (%d) status=%d; version=%d; descr=%s\n",
1371 ev->event.notify60[i].uin,
1372 GG_S(ev->event.notify60[i].status),
1373 ev->event.notify60[i].version,
1374 ev->event.notify60[i].descr ? ev->event.notify60[i].descr : "(null)");
1375
1376 ggp_generic_status_handler(gc, ev->event.notify60[i].uin,
1377 GG_S(ev->event.notify60[i].status),
1378 ev->event.notify60[i].descr);
1379 }
1380 break;
1381 case GG_EVENT_STATUS:
1382 purple_debug_info("gg", "status: (%d) status=%d; descr=%s\n",
1383 ev->event.status.uin, GG_S(ev->event.status.status),
1384 ev->event.status.descr ? ev->event.status.descr : "(null)");
1385
1386 ggp_generic_status_handler(gc, ev->event.status.uin,
1387 GG_S(ev->event.status.status), ev->event.status.descr);
1388 break;
1389 case GG_EVENT_STATUS60:
1390 purple_debug_info("gg",
1391 "status60: (%d) status=%d; version=%d; descr=%s\n",
1392 ev->event.status60.uin, GG_S(ev->event.status60.status),
1393 ev->event.status60.version,
1394 ev->event.status60.descr ? ev->event.status60.descr : "(null)");
1395
1396 ggp_generic_status_handler(gc, ev->event.status60.uin,
1397 GG_S(ev->event.status60.status), ev->event.status60.descr);
1398 break;
1399 case GG_EVENT_PUBDIR50_SEARCH_REPLY:
1400 ggp_pubdir_reply_handler(gc, ev->event.pubdir50);
1401 break;
1402 case GG_EVENT_TYPING_NOTIFICATION:
1403 ggp_typing_notification_handler(gc, ev->event.typing_notification.uin,
1404 ev->event.typing_notification.length);
1405 break;
1406 case GG_EVENT_XML_EVENT:
1407 purple_debug_info("gg", "GG_EVENT_XML_EVENT\n");
1408 ggp_xml_event_handler(gc, ev->event.xml_event.data);
1409 break;
1410 default:
1411 purple_debug_error("gg",
1412 "unsupported event type=%d\n", ev->type);
1413 break;
1414 }
1415
1416 gg_free_event(ev);
1417 }
1418
1419 static void ggp_async_login_handler(gpointer _gc, gint fd, PurpleInputCondition cond)
1420 {
1421 PurpleConnection *gc = _gc;
1422 GGPInfo *info;
1423 struct gg_event *ev;
1424
1425 g_return_if_fail(PURPLE_CONNECTION_IS_VALID(gc));
1426
1427 info = gc->proto_data;
1428
1429 purple_debug_info("gg", "login_handler: session: check = %d; state = %d;\n",
1430 info->session->check, info->session->state);
1431
1432 switch (info->session->state) {
1433 case GG_STATE_RESOLVING:
1434 purple_debug_info("gg", "GG_STATE_RESOLVING\n");
1435 break;
1436 case GG_STATE_RESOLVING_GG:
1437 purple_debug_info("gg", "GG_STATE_RESOLVING_GG\n");
1438 break;
1439 case GG_STATE_CONNECTING_HUB:
1440 purple_debug_info("gg", "GG_STATE_CONNECTING_HUB\n");
1441 break;
1442 case GG_STATE_READING_DATA:
1443 purple_debug_info("gg", "GG_STATE_READING_DATA\n");
1444 break;
1445 case GG_STATE_CONNECTING_GG:
1446 purple_debug_info("gg", "GG_STATE_CONNECTING_GG\n");
1447 break;
1448 case GG_STATE_READING_KEY:
1449 purple_debug_info("gg", "GG_STATE_READING_KEY\n");
1450 break;
1451 case GG_STATE_READING_REPLY:
1452 purple_debug_info("gg", "GG_STATE_READING_REPLY\n");
1453 break;
1454 case GG_STATE_TLS_NEGOTIATION:
1455 purple_debug_info("gg", "GG_STATE_TLS_NEGOTIATION\n");
1456 break;
1457 default:
1458 purple_debug_error("gg", "unknown state = %d\n",
1459 info->session->state);
1460 break;
1461 }
1462
1463 if (!(ev = gg_watch_fd(info->session))) {
1464 purple_debug_error("gg", "login_handler: gg_watch_fd failed!\n");
1465 purple_connection_error_reason (gc,
1466 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1467 _("Unable to read from socket"));
1468 return;
1469 }
1470 purple_debug_info("gg", "login_handler: session->fd = %d\n", info->session->fd);
1471 purple_debug_info("gg", "login_handler: session: check = %d; state = %d;\n",
1472 info->session->check, info->session->state);
1473
1474 purple_input_remove(gc->inpa);
1475
1476 /** XXX I think that this shouldn't be done if ev->type is GG_EVENT_CONN_FAILED or GG_EVENT_CONN_SUCCESS -datallah */
1477 if (info->session->fd >= 0)
1478 gc->inpa = purple_input_add(info->session->fd,
1479 ggp_tcpsocket_inputcond_gg_to_purple(info->session->check),
1480 ggp_async_login_handler, gc);
1481
1482 switch (ev->type) {
1483 case GG_EVENT_NONE:
1484 /* Nothing happened. */
1485 purple_debug_info("gg", "GG_EVENT_NONE\n");
1486 break;
1487 case GG_EVENT_CONN_SUCCESS:
1488 {
1489 purple_debug_info("gg", "GG_EVENT_CONN_SUCCESS\n");
1490 purple_input_remove(gc->inpa);
1491 gc->inpa = purple_input_add(info->session->fd,
1492 ggp_tcpsocket_inputcond_gg_to_purple(info->session->check),
1493 ggp_callback_recv, gc);
1494
1495 ggp_buddylist_send(gc);
1496 purple_connection_update_progress(gc, _("Connected"), 1, 2);
1497 purple_connection_set_state(gc, PURPLE_CONNECTED);
1498 }
1499 break;
1500 case GG_EVENT_CONN_FAILED:
1501 purple_input_remove(gc->inpa);
1502 gc->inpa = 0;
1503 purple_connection_error_reason (gc,
1504 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1505 _("Connection failed"));
1506 break;
1507 case GG_EVENT_MSG:
1508 if (ev->event.msg.sender == 0)
1509 /* system messages are mostly ads */
1510 purple_debug_info("gg", "System message:\n%s\n",
1511 ev->event.msg.message);
1512 else
1513 purple_debug_warning("gg", "GG_EVENT_MSG: message from user %u "
1514 "unexpected while connecting:\n%s\n",
1515 ev->event.msg.sender,
1516 ev->event.msg.message);
1517 break;
1518 default:
1519 purple_debug_error("gg", "strange event: %d\n", ev->type);
1520 break;
1521 }
1522
1523 gg_free_event(ev);
1524 }
1525
1526 /* ---------------------------------------------------------------------- */
1527 /* ----- PurplePluginProtocolInfo ----------------------------------------- */
1528 /* ---------------------------------------------------------------------- */
1529
1530 static const char *ggp_list_icon(PurpleAccount *account, PurpleBuddy *buddy)
1531 {
1532 return "gadu-gadu";
1533 }
1534
1535 static char *ggp_status_text(PurpleBuddy *b)
1536 {
1537 PurpleStatus *status;
1538 const char *msg;
1539 char *text;
1540 char *tmp;
1541
1542 status = purple_presence_get_active_status(
1543 purple_buddy_get_presence(b));
1544 msg = purple_status_get_attr_string(status, "message");
1545
1546 if (msg == NULL)
1547 return NULL;
1548
1549 tmp = purple_markup_strip_html(msg);
1550 text = g_markup_escape_text(tmp, -1);
1551 g_free(tmp);
1552
1553 return text;
1554 }
1555
1556 static void ggp_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full)
1557 {
1558 PurpleStatus *status;
1559 char *text, *tmp;
1560 const char *msg, *name, *alias;
1561
1562 g_return_if_fail(b != NULL);
1563
1564 status = purple_presence_get_active_status(purple_buddy_get_presence(b));
1565 msg = purple_status_get_attr_string(status, "message");
1566 name = purple_status_get_name(status);
1567 alias = purple_buddy_get_alias(b);
1568
1569 purple_notify_user_info_add_pair (user_info, _("Alias"), alias);
1570
1571 if (msg != NULL) {
1572 text = g_markup_escape_text(msg, -1);
1573 if (PURPLE_BUDDY_IS_ONLINE(b)) {
1574 tmp = g_strdup_printf("%s: %s", name, text);
1575 purple_notify_user_info_add_pair(user_info, _("Status"), tmp);
1576 g_free(tmp);
1577 } else {
1578 purple_notify_user_info_add_pair(user_info, _("Message"), text);
1579 }
1580 g_free(text);
1581 /* We don't want to duplicate 'Status: Offline'. */
1582 } else if (PURPLE_BUDDY_IS_ONLINE(b)) {
1583 purple_notify_user_info_add_pair(user_info, _("Status"), name);
1584 }
1585 }
1586
1587 static GList *ggp_status_types(PurpleAccount *account)
1588 {
1589 PurpleStatusType *type;
1590 GList *types = NULL;
1591
1592 type = purple_status_type_new_with_attrs(
1593 PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE, TRUE, FALSE,
1594 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1595 NULL);
1596 types = g_list_append(types, type);
1597
1598 /*
1599 * Without this selecting Invisible as own status doesn't
1600 * work. It's not used and not needed to show status of buddies.
1601 */
1602 type = purple_status_type_new_with_attrs(
1603 PURPLE_STATUS_INVISIBLE, NULL, NULL, TRUE, TRUE, FALSE,
1604 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1605 NULL);
1606 types = g_list_append(types, type);
1607
1608 type = purple_status_type_new_with_attrs(
1609 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1610 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1611 NULL);
1612 types = g_list_append(types, type);
1613
1614 /*
1615 * New statuses for GG 8.0 like PoGGadaj ze mna (not yet because
1616 * libpurple can't support Chatty status) and Nie przeszkadzac
1617 */
1618 type = purple_status_type_new_with_attrs(
1619 PURPLE_STATUS_UNAVAILABLE, NULL, NULL, TRUE, TRUE, FALSE,
1620 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1621 NULL);
1622 types = g_list_append(types, type);
1623
1624 /*
1625 * This status is necessary to display guys who are blocking *us*.
1626 */
1627 type = purple_status_type_new_with_attrs(
1628 PURPLE_STATUS_INVISIBLE, "blocked", _("Blocked"), TRUE, FALSE, FALSE,
1629 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING), NULL);
1630 types = g_list_append(types, type);
1631
1632 type = purple_status_type_new_with_attrs(
1633 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE,
1634 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1635 NULL);
1636 types = g_list_append(types, type);
1637
1638 return types;
1639 }
1640
1641 static GList *ggp_blist_node_menu(PurpleBlistNode *node)
1642 {
1643 PurpleMenuAction *act;
1644 GList *m = NULL;
1645 PurpleAccount *account;
1646 GGPInfo *info;
1647
1648 if (!PURPLE_BLIST_NODE_IS_BUDDY(node))
1649 return NULL;
1650
1651 account = purple_buddy_get_account((PurpleBuddy *) node);
1652 info = purple_account_get_connection(account)->proto_data;
1653 if (info->chats) {
1654 act = purple_menu_action_new(_("Add to chat"),
1655 PURPLE_CALLBACK(ggp_bmenu_add_to_chat),
1656 NULL, NULL);
1657 m = g_list_append(m, act);
1658 }
1659
1660 return m;
1661 }
1662
1663 static GList *ggp_chat_info(PurpleConnection *gc)
1664 {
1665 GList *m = NULL;
1666 struct proto_chat_entry *pce;
1667
1668 pce = g_new0(struct proto_chat_entry, 1);
1669 pce->label = _("Chat _name:");
1670 pce->identifier = "name";
1671 pce->required = TRUE;
1672 m = g_list_append(m, pce);
1673
1674 return m;
1675 }
1676
1677 static void ggp_login_to(PurpleAccount *account, uint32_t server)
1678 {
1679 PurpleConnection *gc;
1680 PurplePresence *presence;
1681 PurpleStatus *status;
1682 struct gg_login_params *glp;
1683 GGPInfo *info;
1684 const gchar *encryption_type;
1685
1686 if (ggp_setup_proxy(account) == -1)
1687 return;
1688
1689 gc = purple_account_get_connection(account);
1690 glp = g_new0(struct gg_login_params, 1);
1691 info = gc->proto_data;
1692 g_return_if_fail(info);
1693
1694 /* Probably this should be moved to *_new() function. */
1695 info->session = NULL;
1696 info->chats = NULL;
1697 info->chats_count = 0;
1698 info->token = NULL;
1699 info->searches = ggp_search_new();
1700 info->pending_richtext_messages = NULL;
1701 info->pending_images = g_hash_table_new(g_direct_hash, g_direct_equal);
1702 info->status_broadcasting = purple_account_get_bool(account, "status_broadcasting", TRUE);
1703
1704 glp->uin = ggp_get_uin(account);
1705 glp->password = (char *)purple_account_get_password(account);
1706 glp->image_size = 255;
1707
1708 presence = purple_account_get_presence(account);
1709 status = purple_presence_get_active_status(presence);
1710
1711 glp->encoding = GG_ENCODING_UTF8;
1712 glp->protocol_features = (GG_FEATURE_STATUS80|GG_FEATURE_DND_FFC
1713 |GG_FEATURE_TYPING_NOTIFICATION);
1714
1715 glp->async = 1;
1716 glp->status = ggp_to_gg_status(status, &glp->status_descr);
1717
1718 encryption_type = purple_account_get_string(account, "encryption", "none");
1719 purple_debug_info("gg", "Requested encryption type: %s\n", encryption_type);
1720 if (purple_strequal(encryption_type, "opportunistic_tls"))
1721 glp->tls = 1;
1722 else
1723 glp->tls = 0;
1724 purple_debug_info("gg", "TLS enabled: %d\n", glp->tls);
1725
1726 if (!info->status_broadcasting)
1727 glp->status = glp->status|GG_STATUS_FRIENDS_MASK;
1728 glp->server_addr = server;
1729
1730 info->session = gg_login(glp);
1731 g_free(glp);
1732
1733 purple_connection_update_progress(gc, _("Connecting"), 0, 2);
1734 if (info->session == NULL) {
1735 purple_connection_error_reason (gc,
1736 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1737 _("Connection failed"));
1738 return;
1739 }
1740 gc->inpa = purple_input_add(info->session->fd,
1741 ggp_tcpsocket_inputcond_gg_to_purple(info->session->check),
1742 ggp_async_login_handler, gc);
1743 }
1744
1745 static void
1746 ggp_login_resolved(GSList *hosts, gpointer _account, const char *error_message)
1747 {
1748 PurpleAccount *account = _account;
1749 PurpleConnection *gc;
1750 GGPInfo *info;
1751 uint32_t server_addr = 0;
1752
1753 gc = purple_account_get_connection(account);
1754 info = gc->proto_data;
1755 g_return_if_fail(info);
1756 info->dns_query = NULL;
1757
1758 while (hosts && (hosts = g_slist_delete_link(hosts, hosts))) {
1759 struct sockaddr *addr = hosts->data;
1760
1761 if (addr->sa_family == AF_INET && server_addr == 0) {
1762 struct sockaddr_in *addrv4 = (struct sockaddr_in *)addr;
1763
1764 server_addr = addrv4->sin_addr.s_addr;
1765 }
1766
1767 g_free(hosts->data);
1768 hosts = g_slist_delete_link(hosts, hosts);
1769 }
1770
1771 if (server_addr == 0) {
1772 gchar *tmp = g_strdup_printf(
1773 _("Unable to resolve hostname: %s"), error_message);
1774 purple_connection_error_reason(gc,
1775 /* should this be a settings error? */
1776 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
1777 g_free(tmp);
1778 return;
1779 }
1780
1781 ggp_login_to(account, server_addr);
1782 }
1783
1784 static void
1785 ggp_login(PurpleAccount *account)
1786 {
1787 PurpleConnection *gc;
1788 GGPInfo *info;
1789 const char *address;
1790
1791 gc = purple_account_get_connection(account);
1792 info = g_new0(GGPInfo, 1);
1793 gc->proto_data = info;
1794
1795 address = purple_account_get_string(account, "gg_server", "");
1796 if (address == NULL || address[0] == '\0') {
1797 purple_debug_info("gg", "Trying to retrieve address from gg appmsg service\n");
1798 ggp_login_to(account, 0);
1799 return;
1800 }
1801
1802 purple_debug_info("gg", "Using gg server given by user (%s)\n", address);
1803 info->dns_query = purple_dnsquery_a_account(account, address, 8074,
1804 ggp_login_resolved, account);
1805 }
1806
1807 static void ggp_close(PurpleConnection *gc)
1808 {
1809 if (gc == NULL) {
1810 purple_debug_info("gg", "gc == NULL\n");
1811 return;
1812 }
1813
1814 if (gc->proto_data) {
1815 PurpleAccount *account = purple_connection_get_account(gc);
1816 PurpleStatus *status;
1817 GGPInfo *info = gc->proto_data;
1818
1819 if (info->dns_query)
1820 purple_dnsquery_destroy(info->dns_query);
1821
1822 status = purple_account_get_active_status(account);
1823
1824 if (info->session != NULL) {
1825 ggp_set_status(account, status);
1826 gg_logoff(info->session);
1827 gg_free_session(info->session);
1828 }
1829
1830 purple_account_set_bool(account, "status_broadcasting", info->status_broadcasting);
1831
1832 /* Immediately close any notifications on this handle since that process depends
1833 * upon the contents of info->searches, which we are about to destroy.
1834 */
1835 purple_notify_close_with_handle(gc);
1836
1837 ggp_search_destroy(info->searches);
1838 g_list_free(info->pending_richtext_messages);
1839 g_hash_table_destroy(info->pending_images);
1840 g_free(info);
1841 gc->proto_data = NULL;
1842 }
1843
1844 if (gc->inpa > 0)
1845 purple_input_remove(gc->inpa);
1846
1847 purple_debug_info("gg", "Connection closed.\n");
1848 }
1849
1850 static int ggp_send_im(PurpleConnection *gc, const char *who, const char *msg,
1851 PurpleMessageFlags flags)
1852 {
1853 GGPInfo *info = gc->proto_data;
1854 char *tmp, *plain;
1855 int ret = 1;
1856 unsigned char format[1024];
1857 unsigned int format_length = sizeof(struct gg_msg_richtext);
1858 gint pos = 0;
1859 GData *attribs;
1860 const char *start, *end = NULL, *last;
1861
1862 if (msg == NULL || *msg == '\0') {
1863 return 0;
1864 }
1865
1866 last = msg;
1867
1868 /* Check if the message is richtext */
1869 /* TODO: Check formatting, too */
1870 if(purple_markup_find_tag("img", last, &start, &end, &attribs)) {
1871
1872 GString *string_buffer = g_string_new(NULL);
1873 struct gg_msg_richtext fmt;
1874
1875 do {
1876 PurpleStoredImage *image;
1877 const char *id;
1878
1879 /* Add text before the image */
1880 if(start - last) {
1881 pos = pos + g_utf8_strlen(last, start - last);
1882 g_string_append_len(string_buffer, last, start - last);
1883 }
1884
1885 if((id = g_datalist_get_data(&attribs, "id")) && (image = purple_imgstore_find_by_id(atoi(id)))) {
1886 struct gg_msg_richtext_format actformat;
1887 struct gg_msg_richtext_image actimage;
1888 gint image_size = purple_imgstore_get_size(image);
1889 gconstpointer image_bin = purple_imgstore_get_data(image);
1890 const char *image_filename = purple_imgstore_get_filename(image);
1891 uint32_t crc32 = gg_crc32(0, image_bin, image_size);
1892
1893 g_hash_table_insert(info->pending_images, GINT_TO_POINTER(crc32), GINT_TO_POINTER(atoi(id)));
1894 purple_imgstore_ref(image);
1895 purple_debug_info("gg", "ggp_send_im_richtext: got crc: %u for imgid: %i\n", crc32, atoi(id));
1896
1897 actformat.font = GG_FONT_IMAGE;
1898 actformat.position = pos;
1899
1900 actimage.unknown1 = 0x0109;
1901 actimage.size = gg_fix32(image_size);
1902 actimage.crc32 = gg_fix32(crc32);
1903
1904 if (actimage.size > 255000) {
1905 purple_debug_warning("gg", "ggp_send_im_richtext: image over 255kb!\n");
1906 } else {
1907 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);
1908
1909 memcpy(format + format_length, &actformat, sizeof(actformat));
1910 format_length += sizeof(actformat);
1911 memcpy(format + format_length, &actimage, sizeof(actimage));
1912 format_length += sizeof(actimage);
1913 }
1914 } else {
1915 purple_debug_error("gg", "ggp_send_im_richtext: image not found in the image store!");
1916 }
1917
1918 last = end + 1;
1919 g_datalist_clear(&attribs);
1920
1921 } while(purple_markup_find_tag("img", last, &start, &end, &attribs));
1922
1923 /* Add text after the images */
1924 if(last && *last) {
1925 /* this is currently not used, but might be useful later? */
1926 /* pos = pos + g_utf8_strlen(last, -1); */
1927 g_string_append(string_buffer, last);
1928 }
1929
1930 fmt.flag = 2;
1931 fmt.length = format_length - sizeof(fmt);
1932 memcpy(format, &fmt, sizeof(fmt));
1933
1934 purple_debug_info("gg", "ggp_send_im: richtext msg = %s\n", string_buffer->str);
1935 plain = purple_unescape_html(string_buffer->str);
1936 g_string_free(string_buffer, TRUE);
1937 } else {
1938 purple_debug_info("gg", "ggp_send_im: msg = %s\n", msg);
1939 plain = purple_unescape_html(msg);
1940 }
1941
1942 /*
1943 tmp = charset_convert(plain, "UTF-8", "CP1250");
1944 */
1945 tmp = g_strdup_printf("%s", plain);
1946
1947 if (tmp && (format_length - sizeof(struct gg_msg_richtext))) {
1948 if(gg_send_message_richtext(info->session, GG_CLASS_CHAT, ggp_str_to_uin(who), (unsigned char *)tmp, format, format_length) < 0) {
1949 ret = -1;
1950 } else {
1951 ret = 1;
1952 }
1953 } else if (NULL == tmp || *tmp == 0) {
1954 ret = 0;
1955 } else if (strlen(tmp) > GG_MSG_MAXSIZE) {
1956 ret = -E2BIG;
1957 } else if (gg_send_message(info->session, GG_CLASS_CHAT,
1958 ggp_str_to_uin(who), (unsigned char *)tmp) < 0) {
1959 ret = -1;
1960 } else {
1961 ret = 1;
1962 }
1963
1964 g_free(plain);
1965 g_free(tmp);
1966
1967 return ret;
1968 }
1969
1970 static unsigned int ggp_send_typing(PurpleConnection *gc, const char *name, PurpleTypingState state)
1971 {
1972 int dummy_length; // we don't send real length of typed message
1973
1974 if (state == PURPLE_TYPED) // not supported
1975 return 1;
1976
1977 if (state == PURPLE_TYPING)
1978 dummy_length = (int)g_random_int();
1979 else // PURPLE_NOT_TYPING
1980 dummy_length = 0;
1981
1982 gg_typing_notification(
1983 ((GGPInfo*)gc->proto_data)->session,
1984 ggp_str_to_uin(name),
1985 dummy_length);
1986
1987 return 1; // wait 1 second before another notification
1988 }
1989
1990 static void ggp_get_info(PurpleConnection *gc, const char *name)
1991 {
1992 GGPInfo *info = gc->proto_data;
1993 GGPSearchForm *form;
1994 guint32 seq;
1995
1996 form = ggp_search_form_new(GGP_SEARCH_TYPE_INFO);
1997
1998 form->user_data = info;
1999 form->uin = g_strdup(name);
2000
2001 seq = ggp_search_start(gc, form);
2002 ggp_search_add(info->searches, seq, form);
2003 purple_debug_info("gg", "ggp_get_info(): Added seq %u", seq);
2004 }
2005
2006 static int ggp_to_gg_status(PurpleStatus *status, char **msg)
2007 {
2008 const char *status_id = purple_status_get_id(status);
2009 int new_status, new_status_descr;
2010 const char *new_msg;
2011
2012 g_return_val_if_fail(msg != NULL, 0);
2013
2014 purple_debug_info("gg", "ggp_to_gg_status: Requested status = %s\n",
2015 status_id);
2016
2017 if (purple_strequal(status_id, "available")) {
2018 new_status = GG_STATUS_AVAIL;
2019 new_status_descr = GG_STATUS_AVAIL_DESCR;
2020 } else if (purple_strequal(status_id, "away")) {
2021 new_status = GG_STATUS_BUSY;
2022 new_status_descr = GG_STATUS_BUSY_DESCR;
2023 } else if (purple_strequal(status_id, "unavailable")) {
2024 new_status = GG_STATUS_DND;
2025 new_status_descr = GG_STATUS_DND_DESCR;
2026 } else if (purple_strequal(status_id, "invisible")) {
2027 new_status = GG_STATUS_INVISIBLE;
2028 new_status_descr = GG_STATUS_INVISIBLE_DESCR;
2029 } else if (purple_strequal(status_id, "offline")) {
2030 new_status = GG_STATUS_NOT_AVAIL;
2031 new_status_descr = GG_STATUS_NOT_AVAIL_DESCR;
2032 } else {
2033 new_status = GG_STATUS_AVAIL;
2034 new_status_descr = GG_STATUS_AVAIL_DESCR;
2035 purple_debug_info("gg",
2036 "ggp_set_status: unknown status requested (status_id=%s)\n",
2037 status_id);
2038 }
2039
2040 new_msg = purple_status_get_attr_string(status, "message");
2041
2042 if(new_msg) {
2043 /*
2044 char *tmp = purple_markup_strip_html(new_msg);
2045 *msg = charset_convert(tmp, "UTF-8", "CP1250");
2046 g_free(tmp);
2047 */
2048 *msg = purple_markup_strip_html(new_msg);
2049
2050 return new_status_descr;
2051 } else {
2052 *msg = NULL;
2053 return new_status;
2054 }
2055 }
2056
2057 static void ggp_set_status(PurpleAccount *account, PurpleStatus *status)
2058 {
2059 PurpleConnection *gc;
2060 GGPInfo *info;
2061 int new_status;
2062 char *new_msg = NULL;
2063
2064 if (!purple_status_is_active(status))
2065 return;
2066
2067 gc = purple_account_get_connection(account);
2068 info = gc->proto_data;
2069
2070 new_status = ggp_to_gg_status(status, &new_msg);
2071
2072 if (!info->status_broadcasting)
2073 new_status = new_status|GG_STATUS_FRIENDS_MASK;
2074
2075 if (new_msg == NULL) {
2076 gg_change_status(info->session, new_status);
2077 } else {
2078 gg_change_status_descr(info->session, new_status, new_msg);
2079 g_free(new_msg);
2080 }
2081
2082 ggp_status_fake_to_self(account);
2083
2084 }
2085
2086 static void ggp_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2087 {
2088 PurpleAccount *account;
2089 GGPInfo *info = gc->proto_data;
2090 const gchar *name = purple_buddy_get_name(buddy);
2091
2092 gg_add_notify(info->session, ggp_str_to_uin(name));
2093
2094 account = purple_connection_get_account(gc);
2095 if (purple_strequal(purple_account_get_username(account), name)) {
2096 ggp_status_fake_to_self(account);
2097 }
2098 }
2099
2100 static void ggp_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy,
2101 PurpleGroup *group)
2102 {
2103 GGPInfo *info = gc->proto_data;
2104
2105 gg_remove_notify(info->session, ggp_str_to_uin(purple_buddy_get_name(buddy)));
2106 }
2107
2108 static void ggp_join_chat(PurpleConnection *gc, GHashTable *data)
2109 {
2110 GGPInfo *info = gc->proto_data;
2111 GGPChat *chat;
2112 char *chat_name;
2113 GList *l;
2114 PurpleConversation *conv;
2115 PurpleAccount *account = purple_connection_get_account(gc);
2116
2117 chat_name = g_hash_table_lookup(data, "name");
2118
2119 if (chat_name == NULL)
2120 return;
2121
2122 purple_debug_info("gg", "joined %s chat\n", chat_name);
2123
2124 for (l = info->chats; l != NULL; l = l->next) {
2125 chat = l->data;
2126
2127 if (chat != NULL && g_utf8_collate(chat->name, chat_name) == 0) {
2128 purple_notify_error(gc, _("Chat error"),
2129 _("This chat name is already in use"), NULL);
2130 return;
2131 }
2132 }
2133
2134 ggp_confer_add_new(gc, chat_name);
2135 conv = serv_got_joined_chat(gc, info->chats_count, chat_name);
2136 purple_conv_chat_add_user(PURPLE_CONV_CHAT(conv),
2137 purple_account_get_username(account), NULL,
2138 PURPLE_CBFLAGS_NONE, TRUE);
2139 }
2140
2141 static char *ggp_get_chat_name(GHashTable *data) {
2142 return g_strdup(g_hash_table_lookup(data, "name"));
2143 }
2144
2145 static int ggp_chat_send(PurpleConnection *gc, int id, const char *message, PurpleMessageFlags flags)
2146 {
2147 PurpleConversation *conv;
2148 GGPInfo *info = gc->proto_data;
2149 GGPChat *chat = NULL;
2150 GList *l;
2151 /* char *msg, *plain; */
2152 gchar *msg;
2153 uin_t *uins;
2154 int count = 0;
2155
2156 if ((conv = purple_find_chat(gc, id)) == NULL)
2157 return -EINVAL;
2158
2159 for (l = info->chats; l != NULL; l = l->next) {
2160 chat = l->data;
2161
2162 if (g_utf8_collate(chat->name, conv->name) == 0) {
2163 break;
2164 }
2165
2166 chat = NULL;
2167 }
2168
2169 if (chat == NULL) {
2170 purple_debug_error("gg",
2171 "ggp_chat_send: Hm... that's strange. No such chat?\n");
2172 return -EINVAL;
2173 }
2174
2175 uins = g_new0(uin_t, g_list_length(chat->participants));
2176
2177 for (l = chat->participants; l != NULL; l = l->next) {
2178 uin_t uin = GPOINTER_TO_INT(l->data);
2179
2180 uins[count++] = uin;
2181 }
2182
2183 /*
2184 plain = purple_unescape_html(message);
2185 msg = charset_convert(plain, "UTF-8", "CP1250");
2186 g_free(plain);
2187 */
2188 msg = purple_unescape_html(message);
2189 gg_send_message_confer(info->session, GG_CLASS_CHAT, count, uins,
2190 (unsigned char *)msg);
2191 g_free(msg);
2192 g_free(uins);
2193
2194 serv_got_chat_in(gc, id,
2195 purple_account_get_username(purple_connection_get_account(gc)),
2196 flags, message, time(NULL));
2197
2198 return 0;
2199 }
2200
2201 static void ggp_keepalive(PurpleConnection *gc)
2202 {
2203 GGPInfo *info = gc->proto_data;
2204
2205 /* purple_debug_info("gg", "Keeping connection alive....\n"); */
2206
2207 if (gg_ping(info->session) < 0) {
2208 purple_debug_info("gg", "Not connected to the server "
2209 "or gg_session is not correct\n");
2210 purple_connection_error_reason (gc,
2211 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
2212 _("Not connected to the server"));
2213 }
2214 }
2215
2216 static GList *ggp_actions(PurplePlugin *plugin, gpointer context)
2217 {
2218 GList *m = NULL;
2219 PurplePluginAction *act;
2220
2221 act = purple_plugin_action_new(_("Find buddies..."),
2222 ggp_find_buddies);
2223 m = g_list_append(m, act);
2224
2225 act = purple_plugin_action_new(_("Change status broadcasting"),
2226 ggp_action_change_status_broadcasting);
2227 m = g_list_append(m, act);
2228
2229 m = g_list_append(m, NULL);
2230
2231 act = purple_plugin_action_new(_("Save buddylist to file..."),
2232 ggp_action_buddylist_save);
2233 m = g_list_append(m, act);
2234
2235 act = purple_plugin_action_new(_("Load buddylist from file..."),
2236 ggp_action_buddylist_load);
2237 m = g_list_append(m, act);
2238
2239 return m;
2240 }
2241
2242 static gboolean ggp_offline_message(const PurpleBuddy *buddy)
2243 {
2244 return TRUE;
2245 }
2246
2247 static gboolean ggp_load(PurplePlugin *plugin)
2248 {
2249 purple_debug_info("gg", "Loading Gadu-Gadu protocol plugin with "
2250 "libgadu %s...\n", gg_libgadu_version());
2251
2252 gg_is_gpl_compliant();
2253
2254 return TRUE;
2255 }
2256
2257 static PurplePluginProtocolInfo prpl_info =
2258 {
2259 OPT_PROTO_IM_IMAGE,
2260 NULL, /* user_splits */
2261 NULL, /* protocol_options */
2262 {"png", 32, 32, 96, 96, 0, PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */
2263 ggp_list_icon, /* list_icon */
2264 NULL, /* list_emblem */
2265 ggp_status_text, /* status_text */
2266 ggp_tooltip_text, /* tooltip_text */
2267 ggp_status_types, /* status_types */
2268 ggp_blist_node_menu, /* blist_node_menu */
2269 ggp_chat_info, /* chat_info */
2270 NULL, /* chat_info_defaults */
2271 ggp_login, /* login */
2272 ggp_close, /* close */
2273 ggp_send_im, /* send_im */
2274 NULL, /* set_info */
2275 ggp_send_typing, /* send_typing */
2276 ggp_get_info, /* get_info */
2277 ggp_set_status, /* set_away */
2278 NULL, /* set_idle */
2279 NULL, /* change_passwd */
2280 ggp_add_buddy, /* add_buddy */
2281 NULL, /* add_buddies */
2282 ggp_remove_buddy, /* remove_buddy */
2283 NULL, /* remove_buddies */
2284 NULL, /* add_permit */
2285 ggp_add_deny, /* add_deny */
2286 NULL, /* rem_permit */
2287 ggp_rem_deny, /* rem_deny */
2288 NULL, /* set_permit_deny */
2289 ggp_join_chat, /* join_chat */
2290 NULL, /* reject_chat */
2291 ggp_get_chat_name, /* get_chat_name */
2292 NULL, /* chat_invite */
2293 NULL, /* chat_leave */
2294 NULL, /* chat_whisper */
2295 ggp_chat_send, /* chat_send */
2296 ggp_keepalive, /* keepalive */
2297 NULL, /* register_user */
2298 NULL, /* get_cb_info */
2299 NULL, /* get_cb_away */
2300 NULL, /* alias_buddy */
2301 NULL, /* group_buddy */
2302 NULL, /* rename_group */
2303 NULL, /* buddy_free */
2304 NULL, /* convo_closed */
2305 NULL, /* normalize */
2306 NULL, /* set_buddy_icon */
2307 NULL, /* remove_group */
2308 NULL, /* get_cb_real_name */
2309 NULL, /* set_chat_topic */
2310 NULL, /* find_blist_chat */
2311 NULL, /* roomlist_get_list */
2312 NULL, /* roomlist_cancel */
2313 NULL, /* roomlist_expand_category */
2314 NULL, /* can_receive_file */
2315 NULL, /* send_file */
2316 NULL, /* new_xfer */
2317 ggp_offline_message, /* offline_message */
2318 NULL, /* whiteboard_prpl_ops */
2319 NULL, /* send_raw */
2320 NULL, /* roomlist_room_serialize */
2321 NULL, /* unregister_user */
2322 NULL, /* send_attention */
2323 NULL, /* get_attention_types */
2324 sizeof(PurplePluginProtocolInfo), /* struct_size */
2325 NULL, /* get_account_text_table */
2326 NULL, /* initiate_media */
2327 NULL, /* can_do_media */
2328 NULL, /* get_moods */
2329 NULL, /* set_public_alias */
2330 NULL, /* get_public_alias */
2331 NULL, /* add_buddy_with_invite */
2332 NULL, /* add_buddies_with_invite */
2333 NULL, /* get_cb_alias */
2334 NULL, /* chat_can_receive_file */
2335 NULL, /* chat_send_file */
2336 };
2337
2338 static PurplePluginInfo info = {
2339 PURPLE_PLUGIN_MAGIC, /* magic */
2340 PURPLE_MAJOR_VERSION, /* major_version */
2341 PURPLE_MINOR_VERSION, /* minor_version */
2342 PURPLE_PLUGIN_PROTOCOL, /* plugin type */
2343 NULL, /* ui_requirement */
2344 0, /* flags */
2345 NULL, /* dependencies */
2346 PURPLE_PRIORITY_DEFAULT, /* priority */
2347
2348 "prpl-gg", /* id */
2349 "Gadu-Gadu", /* name */
2350 DISPLAY_VERSION, /* version */
2351
2352 N_("Gadu-Gadu Protocol Plugin"), /* summary */
2353 N_("Polish popular IM"), /* description */
2354 "boler@sourceforge.net", /* author */
2355 PURPLE_WEBSITE, /* homepage */
2356
2357 ggp_load, /* load */
2358 NULL, /* unload */
2359 NULL, /* destroy */
2360
2361 NULL, /* ui_info */
2362 &prpl_info, /* extra_info */
2363 NULL, /* prefs_info */
2364 ggp_actions, /* actions */
2365
2366 /* padding */
2367 NULL,
2368 NULL,
2369 NULL,
2370 NULL
2371 };
2372
2373 static void
2374 purple_gg_debug_handler(int level, const char * format, va_list args)
2375 {
2376 PurpleDebugLevel purple_level;
2377 char msgbuff[1000];
2378 int ret;
2379
2380 /* Don't use glib's printf family, since it might not support
2381 * system-specific formatting modifiers (like %Iu for size on win32). */
2382 ret = vsnprintf(msgbuff, sizeof(msgbuff) / sizeof(char), format, args);
2383
2384 if (ret <= 0) {
2385 purple_debug_fatal("gg",
2386 "failed to printf the following message: %s",
2387 format ? format : "(null)\n");
2388
2389 return;
2390 }
2391
2392 /* This is pretty pointless since the GG_DEBUG levels don't correspond to
2393 * the purple ones */
2394 switch (level) {
2395 case GG_DEBUG_FUNCTION:
2396 purple_level = PURPLE_DEBUG_INFO;
2397 break;
2398 case GG_DEBUG_MISC:
2399 case GG_DEBUG_NET:
2400 case GG_DEBUG_DUMP:
2401 case GG_DEBUG_TRAFFIC:
2402 default:
2403 purple_level = PURPLE_DEBUG_MISC;
2404 break;
2405 }
2406
2407 purple_debug(purple_level, "gg", "%s", msgbuff);
2408 }
2409
2410 static void init_plugin(PurplePlugin *plugin)
2411 {
2412 PurpleAccountOption *option;
2413 GList *encryption_options = NULL;
2414
2415 option = purple_account_option_string_new(_("Nickname"),
2416 "nick", _("Gadu-Gadu User"));
2417 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
2418 option);
2419
2420 option = purple_account_option_string_new(_("GG server"),
2421 "gg_server", "");
2422 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
2423 option);
2424
2425 #define ADD_VALUE(list, desc, v) { \
2426 PurpleKeyValuePair *kvp = g_new0(PurpleKeyValuePair, 1); \
2427 kvp->key = g_strdup((desc)); \
2428 kvp->value = g_strdup((v)); \
2429 list = g_list_append(list, kvp); \
2430 }
2431
2432 ADD_VALUE(encryption_options, _("Don't use encryption"), "none");
2433 ADD_VALUE(encryption_options, _("Use encryption if available"),
2434 "opportunistic_tls");
2435 #if 0
2436 /* TODO */
2437 ADD_VALUE(encryption_options, _("Require encryption"), "require_tls");
2438 #endif
2439
2440 option = purple_account_option_list_new(_("Connection security"),
2441 "encryption", encryption_options);
2442 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
2443 option);
2444
2445 my_protocol = plugin;
2446
2447 gg_debug_handler = purple_gg_debug_handler;
2448 }
2449
2450 PURPLE_INIT_PLUGIN(gg, init_plugin, info);
2451
2452 /* vim: set ts=8 sts=0 sw=8 noet: */

mercurial