source: protocols/skype/skype.c @ 4e0e590

Last change on this file since 4e0e590 was 64bed24, checked in by dequis <dx@…>, at 2015-10-13T05:05:22Z

skype: create groupchat as soon as a message is received

Before this commit, the bee_chat_by_title() call just failed when
receiving a message in a groupchat we didn't know about, which is
probably something skype broke in their api at some point.

I'm fixing this since apparently the only way to access p2p based chats
is through the official skype desktop client (they won't be supported
through msnp24 or skypeweb. It's broken in mobile clients already), so
this plugin is probably the best way to access those.

This breaks the 'msg' test - now all chats are groupchats and there's no
way to tell them apart.

However, in reality, private messages aren't delivered at all over the
api, or at least I never managed to get them working. Probably if you
talk with someone who has a very old patched skype client.

  • Property mode set to 100644
File size: 44.5 KB
Line 
1/*
2 *  skype.c - Skype plugin for BitlBee
3 *
4 *  Copyright (c) 2007-2013 by Miklos Vajna <vmiklos@vmiklos.hu>
5 *
6 *  This program is free software; you can redistribute it and/or modify
7 *  it under the terms of the GNU General Public License as published by
8 *  the Free Software Foundation; either version 2 of the License, or
9 *  (at your option) any later version.
10 *
11 *  This program is distributed in the hope that it will be useful,
12 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 *  GNU General Public License for more details.
15 *
16 *  You should have received a copy of the GNU General Public License
17 *  along with this program; if not, write to the Free Software
18 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
19 *  USA.
20 */
21
22#define _XOPEN_SOURCE
23#include <poll.h>
24#include <stdio.h>
25#include <bitlbee.h>
26#include <ssl_client.h>
27
28#define SKYPE_DEFAULT_SERVER "localhost"
29#define SKYPE_DEFAULT_PORT "2727"
30#define IRC_LINE_SIZE 16384
31#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
32
33/*
34 * Enumerations
35 */
36
37enum {
38        SKYPE_CALL_RINGING = 1,
39        SKYPE_CALL_MISSED,
40        SKYPE_CALL_CANCELLED,
41        SKYPE_CALL_FINISHED,
42        SKYPE_CALL_REFUSED
43};
44
45enum {
46        SKYPE_FILETRANSFER_NEW = 1,
47        SKYPE_FILETRANSFER_TRANSFERRING,
48        SKYPE_FILETRANSFER_COMPLETED,
49        SKYPE_FILETRANSFER_FAILED
50};
51
52/*
53 * Structures
54 */
55
56struct skype_data {
57        struct im_connection *ic;
58        char *username;
59        /* The effective file descriptor. We store it here so any function can
60         * write() to it. */
61        int fd;
62        /* File descriptor returned by bitlbee. we store it so we know when
63         * we're connected and when we aren't. */
64        int bfd;
65        /* ssl_getfd() uses this to get the file desciptor. */
66        void *ssl;
67        /* When we receive a new message id, we query the properties, finally
68         * the chatname. Store the properties here so that we can use
69         * imcb_buddy_msg() when we got the chatname. */
70        char *handle;
71        /* List, because of multiline messages. */
72        GList *body;
73        char *type;
74        /* This is necessary because we send a notification when we get the
75         * handle. So we store the state here and then we can send a
76         * notification about the handle is in a given status. */
77        int call_status;
78        char *call_id;
79        char *call_duration;
80        /* If the call is outgoing or not */
81        int call_out;
82        /* Same for file transfers. */
83        int filetransfer_status;
84        /* Path of the file being transferred. */
85        char *filetransfer_path;
86        /* Using /j #nick we want to have a groupchat with two people. Usually
87         * not (default). */
88        char *groupchat_with;
89        /* The user who invited us to the chat. */
90        char *adder;
91        /* If we are waiting for a confirmation about we changed the topic. */
92        int topic_wait;
93        /* These are used by the info command. */
94        char *info_fullname;
95        char *info_phonehome;
96        char *info_phoneoffice;
97        char *info_phonemobile;
98        char *info_nrbuddies;
99        char *info_tz;
100        char *info_seen;
101        char *info_birthday;
102        char *info_sex;
103        char *info_language;
104        char *info_country;
105        char *info_province;
106        char *info_city;
107        char *info_homepage;
108        char *info_about;
109        /* When a call fails, we get the reason and later we get the failure
110         * event, so store the failure code here till then */
111        int failurereason;
112        /* If this is just an update of an already received message. */
113        int is_edit;
114        /* List of struct skype_group* */
115        GList *groups;
116        /* Pending user which has to be added to the next group which is
117         * created. */
118        char *pending_user;
119        /* If the info command was used, to determine what to do with FULLNAME result. */
120        int is_info;
121};
122
123struct skype_away_state {
124        char *code;
125        char *full_name;
126};
127
128struct skype_buddy_ask_data {
129        struct im_connection *ic;
130        /* This is also used for call IDs for simplicity */
131        char *handle;
132};
133
134struct skype_group {
135        int id;
136        char *name;
137        GList *users;
138};
139
140/*
141 * Tables
142 */
143
144const struct skype_away_state skype_away_state_list[] = {
145        { "AWAY", "Away" },
146        { "NA", "Not available" },
147        { "DND", "Do Not Disturb" },
148        { "INVISIBLE", "Invisible" },
149        { "OFFLINE", "Offline" },
150        { "SKYPEME", "Skype Me" },
151        { "ONLINE", "Online" },
152        { NULL, NULL }
153};
154
155/*
156 * Functions
157 */
158
159int skype_write(struct im_connection *ic, char *buf, int len)
160{
161        struct skype_data *sd = ic->proto_data;
162        struct pollfd pfd[1];
163
164        if (!sd->ssl) {
165                return FALSE;
166        }
167
168        pfd[0].fd = sd->fd;
169        pfd[0].events = POLLOUT;
170
171        /* This poll is necessary or we'll get a SIGPIPE when we write() to
172         * sd->fd. */
173        poll(pfd, 1, 1000);
174        if (pfd[0].revents & POLLHUP) {
175                imc_logout(ic, TRUE);
176                return FALSE;
177        }
178        ssl_write(sd->ssl, buf, len);
179
180        return TRUE;
181}
182
183int skype_printf(struct im_connection *ic, char *fmt, ...)
184{
185        va_list args;
186        char str[IRC_LINE_SIZE];
187
188        va_start(args, fmt);
189        g_vsnprintf(str, IRC_LINE_SIZE, fmt, args);
190        va_end(args);
191
192        return skype_write(ic, str, strlen(str));
193}
194
195static void skype_buddy_ask_yes(void *data)
196{
197        struct skype_buddy_ask_data *bla = data;
198
199        skype_printf(bla->ic, "SET USER %s ISAUTHORIZED TRUE\n",
200                     bla->handle);
201        g_free(bla->handle);
202        g_free(bla);
203}
204
205static void skype_buddy_ask_no(void *data)
206{
207        struct skype_buddy_ask_data *bla = data;
208
209        skype_printf(bla->ic, "SET USER %s ISAUTHORIZED FALSE\n",
210                     bla->handle);
211        g_free(bla->handle);
212        g_free(bla);
213}
214
215void skype_buddy_ask(struct im_connection *ic, char *handle, char *message)
216{
217        struct skype_buddy_ask_data *bla = g_new0(struct skype_buddy_ask_data,
218                                                  1);
219        char *buf;
220
221        bla->ic = ic;
222        bla->handle = g_strdup(handle);
223
224        buf = g_strdup_printf("The user %s wants to add you to his/her buddy list, saying: '%s'.", handle, message);
225        imcb_ask(ic, buf, bla, skype_buddy_ask_yes, skype_buddy_ask_no);
226        g_free(buf);
227}
228
229static void skype_call_ask_yes(void *data)
230{
231        struct skype_buddy_ask_data *bla = data;
232
233        skype_printf(bla->ic, "SET CALL %s STATUS INPROGRESS\n",
234                     bla->handle);
235        g_free(bla->handle);
236        g_free(bla);
237}
238
239static void skype_call_ask_no(void *data)
240{
241        struct skype_buddy_ask_data *bla = data;
242
243        skype_printf(bla->ic, "SET CALL %s STATUS FINISHED\n",
244                     bla->handle);
245        g_free(bla->handle);
246        g_free(bla);
247}
248
249void skype_call_ask(struct im_connection *ic, char *call_id, char *message)
250{
251        struct skype_buddy_ask_data *bla = g_new0(struct skype_buddy_ask_data,
252                                                  1);
253
254        bla->ic = ic;
255        bla->handle = g_strdup(call_id);
256
257        imcb_ask(ic, message, bla, skype_call_ask_yes, skype_call_ask_no);
258}
259
260static char *skype_call_strerror(int err)
261{
262        switch (err) {
263        case 1:
264                return "Miscellaneous error";
265        case 2:
266                return "User or phone number does not exist.";
267        case 3:
268                return "User is offline";
269        case 4:
270                return "No proxy found";
271        case 5:
272                return "Session terminated.";
273        case 6:
274                return "No common codec found.";
275        case 7:
276                return "Sound I/O error.";
277        case 8:
278                return "Problem with remote sound device.";
279        case 9:
280                return "Call blocked by recipient.";
281        case 10:
282                return "Recipient not a friend.";
283        case 11:
284                return "Current user not authorized by recipient.";
285        case 12:
286                return "Sound recording error.";
287        default:
288                return "Unknown error";
289        }
290}
291
292static char *skype_group_by_username(struct im_connection *ic, char *username)
293{
294        struct skype_data *sd = ic->proto_data;
295        int i, j;
296
297        /* NEEDSWORK: we just search for the first group of the user, multiple
298         * groups / user is not yet supported by BitlBee. */
299
300        for (i = 0; i < g_list_length(sd->groups); i++) {
301                struct skype_group *sg = g_list_nth_data(sd->groups, i);
302                for (j = 0; j < g_list_length(sg->users); j++) {
303                        if (!strcmp(g_list_nth_data(sg->users, j), username)) {
304                                return sg->name;
305                        }
306                }
307        }
308        return NULL;
309}
310
311static struct skype_group *skype_group_by_name(struct im_connection *ic, char *name)
312{
313        struct skype_data *sd = ic->proto_data;
314        int i;
315
316        for (i = 0; i < g_list_length(sd->groups); i++) {
317                struct skype_group *sg = g_list_nth_data(sd->groups, i);
318                if (!strcmp(sg->name, name)) {
319                        return sg;
320                }
321        }
322        return NULL;
323}
324
325static struct groupchat *skype_chat_get_or_create(struct im_connection *ic, char *id)
326{
327        struct skype_data *sd = ic->proto_data;
328        struct groupchat *gc = bee_chat_by_title(ic->bee, ic, id);
329
330        if (!gc) {
331                gc = imcb_chat_new(ic, id);
332                imcb_chat_name_hint(gc, id);
333                imcb_chat_add_buddy(gc, sd->username);
334
335                skype_printf(ic, "GET CHAT %s ADDER\n", id);
336                skype_printf(ic, "GET CHAT %s TOPIC\n", id);
337                skype_printf(ic, "GET CHAT %s ACTIVEMEMBERS\n", id);
338        }
339
340        return gc;
341}
342
343static void skype_parse_users(struct im_connection *ic, char *line)
344{
345        char **i, **nicks;
346
347        nicks = g_strsplit(line + 6, ", ", 0);
348        for (i = nicks; *i; i++) {
349                skype_printf(ic, "GET USER %s ONLINESTATUS\n", *i);
350                skype_printf(ic, "GET USER %s FULLNAME\n", *i);
351        }
352        g_strfreev(nicks);
353}
354
355static void skype_parse_user(struct im_connection *ic, char *line)
356{
357        int flags = 0;
358        char *ptr;
359        struct skype_data *sd = ic->proto_data;
360        char *user = strchr(line, ' ');
361        char *status = strrchr(line, ' ');
362
363        status++;
364        ptr = strchr(++user, ' ');
365        if (!ptr) {
366                return;
367        }
368        *ptr = '\0';
369        ptr++;
370        if (!strncmp(ptr, "ONLINESTATUS ", 13)) {
371                if (!strlen(user) || !strcmp(user, sd->username)) {
372                        return;
373                }
374                if (!set_getbool(&ic->acc->set, "test_join")
375                    && !strcmp(user, "echo123")) {
376                        return;
377                }
378                ptr = g_strdup_printf("%s@skype.com", user);
379                imcb_add_buddy(ic, ptr, skype_group_by_username(ic, user));
380                if (strcmp(status, "OFFLINE") && (strcmp(status, "SKYPEOUT") ||
381                                                  !set_getbool(&ic->acc->set, "skypeout_offline"))) {
382                        flags |= OPT_LOGGED_IN;
383                }
384                if (strcmp(status, "ONLINE") && strcmp(status, "SKYPEME")) {
385                        flags |= OPT_AWAY;
386                }
387                imcb_buddy_status(ic, ptr, flags, NULL, NULL);
388                g_free(ptr);
389        } else if (!strncmp(ptr, "RECEIVEDAUTHREQUEST ", 20)) {
390                char *message = ptr + 20;
391                if (strlen(message)) {
392                        skype_buddy_ask(ic, user, message);
393                }
394        } else if (!strncmp(ptr, "BUDDYSTATUS ", 12)) {
395                char *st = ptr + 12;
396                if (!strcmp(st, "3")) {
397                        char *buf = g_strdup_printf("%s@skype.com", user);
398                        imcb_add_buddy(ic, buf, skype_group_by_username(ic, user));
399                        g_free(buf);
400                }
401        } else if (!strncmp(ptr, "MOOD_TEXT ", 10)) {
402                char *buf = g_strdup_printf("%s@skype.com", user);
403                bee_user_t *bu = bee_user_by_handle(ic->bee, ic, buf);
404                g_free(buf);
405                buf = ptr + 10;
406                if (bu) {
407                        imcb_buddy_status(ic, bu->handle, bu->flags, NULL,
408                                          *buf ? buf : NULL);
409                }
410                if (set_getbool(&ic->acc->set, "show_moods")) {
411                        imcb_log(ic, "User `%s' changed mood text to `%s'", user, buf);
412                }
413        } else if (!strncmp(ptr, "FULLNAME ", 9)) {
414                char *name = ptr + 9;
415                if (sd->is_info) {
416                        sd->is_info = FALSE;
417                        sd->info_fullname = g_strdup(name);
418                } else {
419                        char *buf = g_strdup_printf("%s@skype.com", user);
420                        imcb_rename_buddy(ic, buf, name);
421                        g_free(buf);
422                }
423        } else if (!strncmp(ptr, "PHONE_HOME ", 11)) {
424                sd->info_phonehome = g_strdup(ptr + 11);
425        } else if (!strncmp(ptr, "PHONE_OFFICE ", 13)) {
426                sd->info_phoneoffice = g_strdup(ptr + 13);
427        } else if (!strncmp(ptr, "PHONE_MOBILE ", 13)) {
428                sd->info_phonemobile = g_strdup(ptr + 13);
429        } else if (!strncmp(ptr, "NROF_AUTHED_BUDDIES ", 20)) {
430                sd->info_nrbuddies = g_strdup(ptr + 20);
431        } else if (!strncmp(ptr, "TIMEZONE ", 9)) {
432                sd->info_tz = g_strdup(ptr + 9);
433        } else if (!strncmp(ptr, "LASTONLINETIMESTAMP ", 20)) {
434                sd->info_seen = g_strdup(ptr + 20);
435        } else if (!strncmp(ptr, "SEX ", 4)) {
436                sd->info_sex = g_strdup(ptr + 4);
437        } else if (!strncmp(ptr, "LANGUAGE ", 9)) {
438                sd->info_language = g_strdup(ptr + 9);
439        } else if (!strncmp(ptr, "COUNTRY ", 8)) {
440                sd->info_country = g_strdup(ptr + 8);
441        } else if (!strncmp(ptr, "PROVINCE ", 9)) {
442                sd->info_province = g_strdup(ptr + 9);
443        } else if (!strncmp(ptr, "CITY ", 5)) {
444                sd->info_city = g_strdup(ptr + 5);
445        } else if (!strncmp(ptr, "HOMEPAGE ", 9)) {
446                sd->info_homepage = g_strdup(ptr + 9);
447        } else if (!strncmp(ptr, "ABOUT ", 6)) {
448                /* Support multiple about lines. */
449                if (!sd->info_about) {
450                        sd->info_about = g_strdup(ptr + 6);
451                } else {
452                        GString *st = g_string_new(sd->info_about);
453                        g_string_append_printf(st, "\n%s", ptr + 6);
454                        g_free(sd->info_about);
455                        sd->info_about = g_strdup(st->str);
456                        g_string_free(st, TRUE);
457                }
458        } else if (!strncmp(ptr, "BIRTHDAY ", 9)) {
459                sd->info_birthday = g_strdup(ptr + 9);
460
461                GString *st = g_string_new("Contact Information\n");
462                g_string_append_printf(st, "Skype Name: %s\n", user);
463                if (sd->info_fullname) {
464                        if (strlen(sd->info_fullname)) {
465                                g_string_append_printf(st, "Full Name: %s\n",
466                                                       sd->info_fullname);
467                        }
468                        g_free(sd->info_fullname);
469                        sd->info_fullname = NULL;
470                }
471                if (sd->info_phonehome) {
472                        if (strlen(sd->info_phonehome)) {
473                                g_string_append_printf(st, "Home Phone: %s\n",
474                                                       sd->info_phonehome);
475                        }
476                        g_free(sd->info_phonehome);
477                        sd->info_phonehome = NULL;
478                }
479                if (sd->info_phoneoffice) {
480                        if (strlen(sd->info_phoneoffice)) {
481                                g_string_append_printf(st, "Office Phone: %s\n",
482                                                       sd->info_phoneoffice);
483                        }
484                        g_free(sd->info_phoneoffice);
485                        sd->info_phoneoffice = NULL;
486                }
487                if (sd->info_phonemobile) {
488                        if (strlen(sd->info_phonemobile)) {
489                                g_string_append_printf(st, "Mobile Phone: %s\n",
490                                                       sd->info_phonemobile);
491                        }
492                        g_free(sd->info_phonemobile);
493                        sd->info_phonemobile = NULL;
494                }
495                g_string_append_printf(st, "Personal Information\n");
496                if (sd->info_nrbuddies) {
497                        if (strlen(sd->info_nrbuddies)) {
498                                g_string_append_printf(st,
499                                                       "Contacts: %s\n", sd->info_nrbuddies);
500                        }
501                        g_free(sd->info_nrbuddies);
502                        sd->info_nrbuddies = NULL;
503                }
504                if (sd->info_tz) {
505                        if (strlen(sd->info_tz)) {
506                                char ib[256];
507                                time_t t = time(NULL);
508                                t += atoi(sd->info_tz) - (60 * 60 * 24);
509                                struct tm *gt = gmtime(&t);
510                                strftime(ib, 256, "%H:%M:%S", gt);
511                                g_string_append_printf(st,
512                                                       "Local Time: %s\n", ib);
513                        }
514                        g_free(sd->info_tz);
515                        sd->info_tz = NULL;
516                }
517                if (sd->info_seen) {
518                        if (strlen(sd->info_seen)) {
519                                char ib[256];
520                                time_t it = atoi(sd->info_seen);
521                                struct tm *tm = localtime(&it);
522                                strftime(ib, 256, ("%Y. %m. %d. %H:%M"), tm);
523                                g_string_append_printf(st,
524                                                       "Last Seen: %s\n", ib);
525                        }
526                        g_free(sd->info_seen);
527                        sd->info_seen = NULL;
528                }
529                if (sd->info_birthday) {
530                        if (strlen(sd->info_birthday) &&
531                            strcmp(sd->info_birthday, "0")) {
532                                char ib[256];
533                                struct tm tm;
534                                strptime(sd->info_birthday, "%Y%m%d", &tm);
535                                strftime(ib, 256, "%B %d, %Y", &tm);
536                                g_string_append_printf(st,
537                                                       "Birthday: %s\n", ib);
538
539                                strftime(ib, 256, "%Y", &tm);
540                                int year = atoi(ib);
541                                time_t t = time(NULL);
542                                struct tm *lt = localtime(&t);
543                                g_string_append_printf(st,
544                                                       "Age: %d\n", lt->tm_year + 1900 - year);
545                        }
546                        g_free(sd->info_birthday);
547                        sd->info_birthday = NULL;
548                }
549                if (sd->info_sex) {
550                        if (strlen(sd->info_sex)) {
551                                char *iptr = sd->info_sex;
552                                while (*iptr++) {
553                                        *iptr = g_ascii_tolower(*iptr);
554                                }
555                                g_string_append_printf(st,
556                                                       "Gender: %s\n", sd->info_sex);
557                        }
558                        g_free(sd->info_sex);
559                        sd->info_sex = NULL;
560                }
561                if (sd->info_language) {
562                        if (strlen(sd->info_language)) {
563                                char *iptr = strchr(sd->info_language, ' ');
564                                if (iptr) {
565                                        iptr++;
566                                } else {
567                                        iptr = sd->info_language;
568                                }
569                                g_string_append_printf(st,
570                                                       "Language: %s\n", iptr);
571                        }
572                        g_free(sd->info_language);
573                        sd->info_language = NULL;
574                }
575                if (sd->info_country) {
576                        if (strlen(sd->info_country)) {
577                                char *iptr = strchr(sd->info_country, ' ');
578                                if (iptr) {
579                                        iptr++;
580                                } else {
581                                        iptr = sd->info_country;
582                                }
583                                g_string_append_printf(st,
584                                                       "Country: %s\n", iptr);
585                        }
586                        g_free(sd->info_country);
587                        sd->info_country = NULL;
588                }
589                if (sd->info_province) {
590                        if (strlen(sd->info_province)) {
591                                g_string_append_printf(st,
592                                                       "Region: %s\n", sd->info_province);
593                        }
594                        g_free(sd->info_province);
595                        sd->info_province = NULL;
596                }
597                if (sd->info_city) {
598                        if (strlen(sd->info_city)) {
599                                g_string_append_printf(st,
600                                                       "City: %s\n", sd->info_city);
601                        }
602                        g_free(sd->info_city);
603                        sd->info_city = NULL;
604                }
605                if (sd->info_homepage) {
606                        if (strlen(sd->info_homepage)) {
607                                g_string_append_printf(st,
608                                                       "Homepage: %s\n", sd->info_homepage);
609                        }
610                        g_free(sd->info_homepage);
611                        sd->info_homepage = NULL;
612                }
613                if (sd->info_about) {
614                        if (strlen(sd->info_about)) {
615                                g_string_append_printf(st, "%s\n",
616                                                       sd->info_about);
617                        }
618                        g_free(sd->info_about);
619                        sd->info_about = NULL;
620                }
621                imcb_log(ic, "%s", st->str);
622                g_string_free(st, TRUE);
623        }
624}
625
626static void skype_parse_chatmessage_said_emoted(struct im_connection *ic, struct groupchat *gc, char *body)
627{
628        struct skype_data *sd = ic->proto_data;
629        char buf[IRC_LINE_SIZE];
630
631        if (!strcmp(sd->type, "SAID")) {
632                if (!sd->is_edit) {
633                        g_snprintf(buf, IRC_LINE_SIZE, "%s", body);
634                } else {
635                        g_snprintf(buf, IRC_LINE_SIZE, "%s %s", set_getstr(&ic->acc->set, "edit_prefix"), body);
636                        sd->is_edit = 0;
637                }
638        } else {
639                g_snprintf(buf, IRC_LINE_SIZE, "/me %s", body);
640        }
641        if (!gc) {
642                /* Private message */
643                imcb_buddy_msg(ic, sd->handle, buf, 0, 0);
644        } else {
645                /* Groupchat message */
646                imcb_chat_msg(gc, sd->handle, buf, 0, 0);
647        }
648}
649
650static void skype_parse_chatmessage(struct im_connection *ic, char *line)
651{
652        struct skype_data *sd = ic->proto_data;
653        char *id = strchr(line, ' ');
654
655        if (!++id) {
656                return;
657        }
658        char *info = strchr(id, ' ');
659
660        if (!info) {
661                return;
662        }
663        *info = '\0';
664        info++;
665        if (!strcmp(info, "STATUS RECEIVED") || !strncmp(info, "EDITED_TIMESTAMP", 16)) {
666                /* New message ID:
667                 * (1) Request its from field
668                 * (2) Request its body
669                 * (3) Request its type
670                 * (4) Query chatname
671                 */
672                skype_printf(ic, "GET CHATMESSAGE %s FROM_HANDLE\n", id);
673                if (!strcmp(info, "STATUS RECEIVED")) {
674                        skype_printf(ic, "GET CHATMESSAGE %s BODY\n", id);
675                } else {
676                        sd->is_edit = 1;
677                }
678                skype_printf(ic, "GET CHATMESSAGE %s TYPE\n", id);
679                skype_printf(ic, "GET CHATMESSAGE %s CHATNAME\n", id);
680        } else if (!strncmp(info, "FROM_HANDLE ", 12)) {
681                info += 12;
682                /* New from field value. Store
683                 * it, then we can later use it
684                 * when we got the message's
685                 * body. */
686                g_free(sd->handle);
687                sd->handle = g_strdup_printf("%s@skype.com", info);
688        } else if (!strncmp(info, "EDITED_BY ", 10)) {
689                info += 10;
690                /* This is the same as
691                 * FROM_HANDLE, except that we
692                 * never request these lines
693                 * from Skype, we just get
694                 * them. */
695                g_free(sd->handle);
696                sd->handle = g_strdup_printf("%s@skype.com", info);
697        } else if (!strncmp(info, "BODY ", 5)) {
698                info += 5;
699                sd->body = g_list_append(sd->body, g_strdup(info));
700        } else if (!strncmp(info, "TYPE ", 5)) {
701                info += 5;
702                g_free(sd->type);
703                sd->type = g_strdup(info);
704        } else if (!strncmp(info, "CHATNAME ", 9)) {
705                info += 9;
706                if (sd->handle && sd->body && sd->type) {
707                        struct groupchat *gc = skype_chat_get_or_create(ic, info);
708                        int i;
709                        for (i = 0; i < g_list_length(sd->body); i++) {
710                                char *body = g_list_nth_data(sd->body, i);
711                                if (!strcmp(sd->type, "SAID") ||
712                                    !strcmp(sd->type, "EMOTED")) {
713                                        skype_parse_chatmessage_said_emoted(ic, gc, body);
714                                } else if (!strcmp(sd->type, "SETTOPIC") && gc) {
715                                        imcb_chat_topic(gc,
716                                                        sd->handle, body, 0);
717                                } else if (!strcmp(sd->type, "LEFT") && gc) {
718                                        imcb_chat_remove_buddy(gc,
719                                                               sd->handle, NULL);
720                                }
721                        }
722                        g_list_free(sd->body);
723                        sd->body = NULL;
724                }
725        }
726}
727
728static void skype_parse_call(struct im_connection *ic, char *line)
729{
730        struct skype_data *sd = ic->proto_data;
731        char *id = strchr(line, ' ');
732        char buf[IRC_LINE_SIZE];
733
734        if (!++id) {
735                return;
736        }
737        char *info = strchr(id, ' ');
738
739        if (!info) {
740                return;
741        }
742        *info = '\0';
743        info++;
744        if (!strncmp(info, "FAILUREREASON ", 14)) {
745                sd->failurereason = atoi(strchr(info, ' '));
746        } else if (!strcmp(info, "STATUS RINGING")) {
747                if (sd->call_id) {
748                        g_free(sd->call_id);
749                }
750                sd->call_id = g_strdup(id);
751                skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id);
752                sd->call_status = SKYPE_CALL_RINGING;
753        } else if (!strcmp(info, "STATUS MISSED")) {
754                skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id);
755                sd->call_status = SKYPE_CALL_MISSED;
756        } else if (!strcmp(info, "STATUS CANCELLED")) {
757                skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id);
758                sd->call_status = SKYPE_CALL_CANCELLED;
759        } else if (!strcmp(info, "STATUS FINISHED")) {
760                skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id);
761                sd->call_status = SKYPE_CALL_FINISHED;
762        } else if (!strcmp(info, "STATUS REFUSED")) {
763                skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id);
764                sd->call_status = SKYPE_CALL_REFUSED;
765        } else if (!strcmp(info, "STATUS UNPLACED")) {
766                if (sd->call_id) {
767                        g_free(sd->call_id);
768                }
769                /* Save the ID for later usage (Cancel/Finish). */
770                sd->call_id = g_strdup(id);
771                sd->call_out = TRUE;
772        } else if (!strcmp(info, "STATUS FAILED")) {
773                imcb_error(ic, "Call failed: %s",
774                           skype_call_strerror(sd->failurereason));
775                sd->call_id = NULL;
776        } else if (!strncmp(info, "DURATION ", 9)) {
777                if (sd->call_duration) {
778                        g_free(sd->call_duration);
779                }
780                sd->call_duration = g_strdup(info + 9);
781        } else if (!strncmp(info, "PARTNER_HANDLE ", 15)) {
782                info += 15;
783                if (!sd->call_status) {
784                        return;
785                }
786                switch (sd->call_status) {
787                case SKYPE_CALL_RINGING:
788                        if (sd->call_out) {
789                                imcb_log(ic, "You are currently ringing the user %s.", info);
790                        } else {
791                                g_snprintf(buf, IRC_LINE_SIZE,
792                                           "The user %s is currently ringing you.",
793                                           info);
794                                skype_call_ask(ic, sd->call_id, buf);
795                        }
796                        break;
797                case SKYPE_CALL_MISSED:
798                        imcb_log(ic, "You have missed a call from user %s.",
799                                 info);
800                        break;
801                case SKYPE_CALL_CANCELLED:
802                        imcb_log(ic, "You cancelled the call to the user %s.",
803                                 info);
804                        sd->call_status = 0;
805                        sd->call_out = FALSE;
806                        break;
807                case SKYPE_CALL_REFUSED:
808                        if (sd->call_out) {
809                                imcb_log(ic, "The user %s refused the call.",
810                                         info);
811                        } else {
812                                imcb_log(ic,
813                                         "You refused the call from user %s.",
814                                         info);
815                        }
816                        sd->call_out = FALSE;
817                        break;
818                case SKYPE_CALL_FINISHED:
819                        if (sd->call_duration) {
820                                imcb_log(ic,
821                                         "You finished the call to the user %s "
822                                         "(duration: %s seconds).",
823                                         info, sd->call_duration);
824                        } else {
825                                imcb_log(ic,
826                                         "You finished the call to the user %s.",
827                                         info);
828                        }
829                        sd->call_out = FALSE;
830                        break;
831                default:
832                        /* Don't be noisy, ignore other statuses for now. */
833                        break;
834                }
835                sd->call_status = 0;
836        }
837}
838
839static void skype_parse_filetransfer(struct im_connection *ic, char *line)
840{
841        struct skype_data *sd = ic->proto_data;
842        char *id = strchr(line, ' ');
843
844        if (!++id) {
845                return;
846        }
847        char *info = strchr(id, ' ');
848
849        if (!info) {
850                return;
851        }
852        *info = '\0';
853        info++;
854        if (!strcmp(info, "STATUS NEW")) {
855                skype_printf(ic, "GET FILETRANSFER %s PARTNER_HANDLE\n",
856                             id);
857                sd->filetransfer_status = SKYPE_FILETRANSFER_NEW;
858        } else if (!strcmp(info, "STATUS FAILED")) {
859                skype_printf(ic, "GET FILETRANSFER %s PARTNER_HANDLE\n",
860                             id);
861                sd->filetransfer_status = SKYPE_FILETRANSFER_FAILED;
862        } else if (!strcmp(info, "STATUS COMPLETED")) {
863                skype_printf(ic, "GET FILETRANSFER %s PARTNER_HANDLE\n", id);
864                sd->filetransfer_status = SKYPE_FILETRANSFER_COMPLETED;
865        } else if (!strcmp(info, "STATUS TRANSFERRING")) {
866                skype_printf(ic, "GET FILETRANSFER %s PARTNER_HANDLE\n", id);
867                sd->filetransfer_status = SKYPE_FILETRANSFER_TRANSFERRING;
868        } else if (!strncmp(info, "FILEPATH ", 9)) {
869                info += 9;
870                sd->filetransfer_path = g_strdup(info);
871        } else if (!strncmp(info, "PARTNER_HANDLE ", 15)) {
872                info += 15;
873                if (!sd->filetransfer_status) {
874                        return;
875                }
876                switch (sd->filetransfer_status) {
877                case SKYPE_FILETRANSFER_NEW:
878                        imcb_log(ic, "The user %s offered a new file for you.",
879                                 info);
880                        break;
881                case SKYPE_FILETRANSFER_FAILED:
882                        imcb_log(ic, "Failed to transfer file from user %s.",
883                                 info);
884                        break;
885                case SKYPE_FILETRANSFER_COMPLETED:
886                        imcb_log(ic, "File transfer from user %s completed.", info);
887                        break;
888                case SKYPE_FILETRANSFER_TRANSFERRING:
889                        if (sd->filetransfer_path) {
890                                imcb_log(ic, "File transfer from user %s started, saving to %s.", info,
891                                         sd->filetransfer_path);
892                                g_free(sd->filetransfer_path);
893                                sd->filetransfer_path = NULL;
894                        }
895                        break;
896                }
897                sd->filetransfer_status = 0;
898        }
899}
900
901static struct skype_group *skype_group_by_id(struct im_connection *ic, int id)
902{
903        struct skype_data *sd = ic->proto_data;
904        int i;
905
906        for (i = 0; i < g_list_length(sd->groups); i++) {
907                struct skype_group *sg = (struct skype_group *) g_list_nth_data(sd->groups, i);
908
909                if (sg->id == id) {
910                        return sg;
911                }
912        }
913        return NULL;
914}
915
916static void skype_group_free(struct skype_group *sg, gboolean usersonly)
917{
918        int i;
919
920        for (i = 0; i < g_list_length(sg->users); i++) {
921                char *user = g_list_nth_data(sg->users, i);
922                g_free(user);
923        }
924        sg->users = NULL;
925        if (usersonly) {
926                return;
927        }
928        g_free(sg->name);
929        g_free(sg);
930}
931
932/* Update the group of each user in this group */
933static void skype_group_users(struct im_connection *ic, struct skype_group *sg)
934{
935        int i;
936
937        for (i = 0; i < g_list_length(sg->users); i++) {
938                char *user = g_list_nth_data(sg->users, i);
939                char *buf = g_strdup_printf("%s@skype.com", user);
940                imcb_add_buddy(ic, buf, sg->name);
941                g_free(buf);
942        }
943}
944
945static void skype_parse_group(struct im_connection *ic, char *line)
946{
947        struct skype_data *sd = ic->proto_data;
948        char *id = strchr(line, ' ');
949
950        if (!++id) {
951                return;
952        }
953
954        char *info = strchr(id, ' ');
955
956        if (!info) {
957                return;
958        }
959        *info = '\0';
960        info++;
961
962        if (!strncmp(info, "DISPLAYNAME ", 12)) {
963                info += 12;
964
965                /* Name given for a group ID: try to update it or insert a new
966                 * one if not found */
967                struct skype_group *sg = skype_group_by_id(ic, atoi(id));
968                if (sg) {
969                        g_free(sg->name);
970                        sg->name = g_strdup(info);
971                } else {
972                        sg = g_new0(struct skype_group, 1);
973                        sg->id = atoi(id);
974                        sg->name = g_strdup(info);
975                        sd->groups = g_list_append(sd->groups, sg);
976                }
977        } else if (!strncmp(info, "USERS ", 6)) {
978                struct skype_group *sg = skype_group_by_id(ic, atoi(id));
979
980                if (sg) {
981                        char **i;
982                        char **users = g_strsplit(info + 6, ", ", 0);
983
984                        skype_group_free(sg, TRUE);
985                        i = users;
986                        while (*i) {
987                                sg->users = g_list_append(sg->users, g_strdup(*i));
988                                i++;
989                        }
990                        g_strfreev(users);
991                        skype_group_users(ic, sg);
992                } else {
993                        log_message(LOGLVL_ERROR,
994                                    "No skype group with id %s. That's probably a bug.", id);
995                }
996        } else if (!strncmp(info, "NROFUSERS ", 10)) {
997                if (!sd->pending_user) {
998                        /* Number of users changed in this group, query its type to see
999                         * if it's a custom one we should care about. */
1000                        skype_printf(ic, "GET GROUP %s TYPE\n", id);
1001                        return;
1002                }
1003
1004                /* This is a newly created group, we have a single user
1005                 * to add. */
1006                struct skype_group *sg = skype_group_by_id(ic, atoi(id));
1007
1008                if (sg) {
1009                        skype_printf(ic, "ALTER GROUP %d ADDUSER %s\n", sg->id, sd->pending_user);
1010                        g_free(sd->pending_user);
1011                        sd->pending_user = NULL;
1012                } else {
1013                        log_message(LOGLVL_ERROR,
1014                                    "No skype group with id %s. That's probably a bug.", id);
1015                }
1016        } else if (!strcmp(info, "TYPE CUSTOM_GROUP")) {
1017                /* This one is interesting, query its users. */
1018                skype_printf(ic, "GET GROUP %s USERS\n", id);
1019        }
1020}
1021
1022static void skype_parse_chat(struct im_connection *ic, char *line)
1023{
1024        struct skype_data *sd = ic->proto_data;
1025        char buf[IRC_LINE_SIZE];
1026        char *id = strchr(line, ' ');
1027
1028        if (!++id) {
1029                return;
1030        }
1031        struct groupchat *gc;
1032        char *info = strchr(id, ' ');
1033
1034        if (!info) {
1035                return;
1036        }
1037        *info = '\0';
1038        info++;
1039        /* Remove fake chat if we created one in skype_chat_with() */
1040        gc = bee_chat_by_title(ic->bee, ic, "");
1041        if (gc) {
1042                imcb_chat_free(gc);
1043        }
1044        if (!strcmp(info, "STATUS MULTI_SUBSCRIBED")) {
1045                skype_chat_get_or_create(ic, id);
1046        } else if (!strcmp(info, "STATUS DIALOG") && sd->groupchat_with) {
1047                gc = skype_chat_get_or_create(ic, id);
1048                /* According to the docs this
1049                 * is necessary. However it
1050                 * does not seem the situation
1051                 * and it would open an extra
1052                 * window on our client, so
1053                 * just leave it out. */
1054                /*skype_printf(ic, "OPEN CHAT %s\n", id);*/
1055                g_snprintf(buf, IRC_LINE_SIZE, "%s@skype.com",
1056                           sd->groupchat_with);
1057                imcb_chat_add_buddy(gc, buf);
1058                g_free(sd->groupchat_with);
1059                sd->groupchat_with = NULL;
1060        } else if (!strcmp(info, "STATUS UNSUBSCRIBED")) {
1061                gc = bee_chat_by_title(ic->bee, ic, id);
1062                if (gc) {
1063                        gc->data = (void *) FALSE;
1064                }
1065        } else if (!strncmp(info, "ADDER ", 6)) {
1066                info += 6;
1067                g_free(sd->adder);
1068                sd->adder = g_strdup_printf("%s@skype.com", info);
1069        } else if (!strncmp(info, "TOPIC ", 6)) {
1070                info += 6;
1071                gc = bee_chat_by_title(ic->bee, ic, id);
1072                if (gc && (sd->adder || sd->topic_wait)) {
1073                        if (sd->topic_wait) {
1074                                sd->adder = g_strdup(sd->username);
1075                                sd->topic_wait = 0;
1076                        }
1077                        imcb_chat_topic(gc, sd->adder, info, 0);
1078                        g_free(sd->adder);
1079                        sd->adder = NULL;
1080                }
1081        } else if (!strncmp(info, "MEMBERS ", 8) || !strncmp(info, "ACTIVEMEMBERS ", 14)) {
1082                if (!strncmp(info, "MEMBERS ", 8)) {
1083                        info += 8;
1084                } else {
1085                        info += 14;
1086                }
1087                gc = bee_chat_by_title(ic->bee, ic, id);
1088                /* Hack! We set ->data to TRUE
1089                 * while we're on the channel
1090                 * so that we won't rejoin
1091                 * after a /part. */
1092                if (!gc || gc->data) {
1093                        return;
1094                }
1095                char **members = g_strsplit(info, " ", 0);
1096                int i;
1097                for (i = 0; members[i]; i++) {
1098                        if (!strcmp(members[i], sd->username)) {
1099                                continue;
1100                        }
1101                        g_snprintf(buf, IRC_LINE_SIZE, "%s@skype.com",
1102                                   members[i]);
1103                        if (!g_list_find_custom(gc->in_room, buf,
1104                                                (GCompareFunc) strcmp)) {
1105                                imcb_chat_add_buddy(gc, buf);
1106                        }
1107                }
1108                imcb_chat_add_buddy(gc, sd->username);
1109                g_strfreev(members);
1110        }
1111}
1112
1113static void skype_parse_password(struct im_connection *ic, char *line)
1114{
1115        if (!strncmp(line + 9, "OK", 2)) {
1116                imcb_connected(ic);
1117        } else {
1118                imcb_error(ic, "Authentication Failed");
1119                imc_logout(ic, TRUE);
1120        }
1121}
1122
1123static void skype_parse_profile(struct im_connection *ic, char *line)
1124{
1125        imcb_log(ic, "SkypeOut balance value is '%s'.", line + 21);
1126}
1127
1128static void skype_parse_ping(struct im_connection *ic, char *line)
1129{
1130        /* Unused parameter */
1131        line = line;
1132        skype_printf(ic, "PONG\n");
1133}
1134
1135static void skype_parse_chats(struct im_connection *ic, char *line)
1136{
1137        char **i;
1138        char **chats = g_strsplit(line + 6, ", ", 0);
1139
1140        i = chats;
1141        while (*i) {
1142                skype_printf(ic, "GET CHAT %s STATUS\n", *i);
1143                skype_printf(ic, "GET CHAT %s ACTIVEMEMBERS\n", *i);
1144                i++;
1145        }
1146        g_strfreev(chats);
1147}
1148
1149static void skype_parse_groups(struct im_connection *ic, char *line)
1150{
1151        if (!set_getbool(&ic->acc->set, "read_groups")) {
1152                return;
1153        }
1154
1155        char **i;
1156        char **groups = g_strsplit(line + 7, ", ", 0);
1157
1158        i = groups;
1159        while (*i) {
1160                skype_printf(ic, "GET GROUP %s DISPLAYNAME\n", *i);
1161                skype_printf(ic, "GET GROUP %s USERS\n", *i);
1162                i++;
1163        }
1164        g_strfreev(groups);
1165}
1166
1167static void skype_parse_alter_group(struct im_connection *ic, char *line)
1168{
1169        char *id = line + strlen("ALTER GROUP");
1170
1171        if (!++id) {
1172                return;
1173        }
1174
1175        char *info = strchr(id, ' ');
1176
1177        if (!info) {
1178                return;
1179        }
1180        *info = '\0';
1181        info++;
1182
1183        if (!strncmp(info, "ADDUSER ", 8)) {
1184                struct skype_group *sg = skype_group_by_id(ic, atoi(id));
1185
1186                info += 8;
1187                if (sg) {
1188                        char *buf = g_strdup_printf("%s@skype.com", info);
1189                        sg->users = g_list_append(sg->users, g_strdup(info));
1190                        imcb_add_buddy(ic, buf, sg->name);
1191                        g_free(buf);
1192                } else {
1193                        log_message(LOGLVL_ERROR,
1194                                    "No skype group with id %s. That's probably a bug.", id);
1195                }
1196        }
1197}
1198
1199typedef void (*skype_parser)(struct im_connection *ic, char *line);
1200
1201static gboolean skype_read_callback(gpointer data, gint fd,
1202                                    b_input_condition cond)
1203{
1204        struct im_connection *ic = data;
1205        struct skype_data *sd = ic->proto_data;
1206        char buf[IRC_LINE_SIZE];
1207        int st, i;
1208        char **lines, **lineptr, *line;
1209        static struct parse_map {
1210                char *k;
1211                skype_parser v;
1212        } parsers[] = {
1213                { "USERS ", skype_parse_users },
1214                { "USER ", skype_parse_user },
1215                { "CHATMESSAGE ", skype_parse_chatmessage },
1216                { "CALL ", skype_parse_call },
1217                { "FILETRANSFER ", skype_parse_filetransfer },
1218                { "CHAT ", skype_parse_chat },
1219                { "GROUP ", skype_parse_group },
1220                { "PASSWORD ", skype_parse_password },
1221                { "PROFILE PSTN_BALANCE ", skype_parse_profile },
1222                { "PING", skype_parse_ping },
1223                { "CHATS ", skype_parse_chats },
1224                { "GROUPS ", skype_parse_groups },
1225                { "ALTER GROUP ", skype_parse_alter_group },
1226        };
1227
1228        /* Unused parameters */
1229        fd = fd;
1230        cond = cond;
1231
1232        if (!sd || sd->fd == -1) {
1233                return FALSE;
1234        }
1235        /* Read the whole data. */
1236        st = ssl_read(sd->ssl, buf, sizeof(buf));
1237        if (st >= IRC_LINE_SIZE - 1) {
1238                /* As we don't buffer incoming data, if IRC_LINE_SIZE amount of bytes
1239                 * were received, there's a good chance last message was truncated
1240                 * and the next recv() will yield garbage. */
1241                imcb_error(ic, "Unable to handle incoming data from skyped");
1242                st = 0;
1243        }
1244        if (st > 0) {
1245                buf[st] = '\0';
1246                /* Then split it up to lines. */
1247                lines = g_strsplit(buf, "\n", 0);
1248                lineptr = lines;
1249                while ((line = *lineptr)) {
1250                        if (!strlen(line)) {
1251                                break;
1252                        }
1253                        if (set_getbool(&ic->acc->set, "skypeconsole_receive")) {
1254                                imcb_buddy_msg(ic, "skypeconsole", line, 0, 0);
1255                        }
1256                        for (i = 0; i < ARRAY_SIZE(parsers); i++) {
1257                                if (!strncmp(line, parsers[i].k,
1258                                             strlen(parsers[i].k))) {
1259                                        parsers[i].v(ic, line);
1260                                        break;
1261                                }
1262                        }
1263                        lineptr++;
1264                }
1265                g_strfreev(lines);
1266        } else if (st == 0 || (st < 0 && !ssl_sockerr_again(sd->ssl))) {
1267                ssl_disconnect(sd->ssl);
1268                sd->fd = -1;
1269                sd->ssl = NULL;
1270
1271                imcb_error(ic, "Error while reading from server");
1272                imc_logout(ic, TRUE);
1273                return FALSE;
1274        }
1275        return TRUE;
1276}
1277
1278gboolean skype_start_stream(struct im_connection *ic)
1279{
1280        struct skype_data *sd = ic->proto_data;
1281        int st;
1282
1283        if (!sd) {
1284                return FALSE;
1285        }
1286
1287        if (sd->bfd <= 0) {
1288                sd->bfd = b_input_add(sd->fd, B_EV_IO_READ,
1289                                      skype_read_callback, ic);
1290        }
1291
1292        /* Log in */
1293        skype_printf(ic, "USERNAME %s\n", ic->acc->user);
1294        skype_printf(ic, "PASSWORD %s\n", ic->acc->pass);
1295
1296        /* This will download all buddies and groups. */
1297        st = skype_printf(ic, "SEARCH GROUPS CUSTOM\n");
1298        skype_printf(ic, "SEARCH FRIENDS\n");
1299
1300        skype_printf(ic, "SET USERSTATUS ONLINE\n");
1301
1302        /* Auto join to bookmarked chats if requested.*/
1303        if (set_getbool(&ic->acc->set, "auto_join")) {
1304                skype_printf(ic, "SEARCH BOOKMARKEDCHATS\n");
1305                skype_printf(ic, "SEARCH ACTIVECHATS\n");
1306                skype_printf(ic, "SEARCH MISSEDCHATS\n");
1307                skype_printf(ic, "SEARCH RECENTCHATS\n");
1308        }
1309        return st;
1310}
1311
1312gboolean skype_connected(gpointer data, int returncode, void *source, b_input_condition cond)
1313{
1314        struct im_connection *ic = data;
1315        struct skype_data *sd = ic->proto_data;
1316
1317        /* Unused parameter */
1318        cond = cond;
1319
1320        if (!source) {
1321                sd->ssl = NULL;
1322                imcb_error(ic, "Could not connect to server");
1323                imc_logout(ic, TRUE);
1324                return FALSE;
1325        }
1326        imcb_log(ic, "Connected to server, logging in");
1327
1328        return skype_start_stream(ic);
1329}
1330
1331static void skype_login(account_t *acc)
1332{
1333        struct im_connection *ic = imcb_new(acc);
1334        struct skype_data *sd = g_new0(struct skype_data, 1);
1335
1336        ic->proto_data = sd;
1337
1338        imcb_log(ic, "Connecting");
1339        sd->ssl = ssl_connect(set_getstr(&acc->set, "server"),
1340                              set_getint(&acc->set, "port"), FALSE, skype_connected, ic);
1341        sd->fd = sd->ssl ? ssl_getfd(sd->ssl) : -1;
1342        sd->username = g_strdup(acc->user);
1343
1344        sd->ic = ic;
1345
1346        if (set_getbool(&acc->set, "skypeconsole")) {
1347                imcb_add_buddy(ic, "skypeconsole", NULL);
1348        }
1349}
1350
1351static void skype_logout(struct im_connection *ic)
1352{
1353        struct skype_data *sd = ic->proto_data;
1354        int i;
1355
1356        skype_printf(ic, "SET USERSTATUS OFFLINE\n");
1357
1358        while (ic->groupchats) {
1359                imcb_chat_free(ic->groupchats->data);
1360        }
1361
1362        for (i = 0; i < g_list_length(sd->groups); i++) {
1363                struct skype_group *sg = (struct skype_group *) g_list_nth_data(sd->groups, i);
1364                skype_group_free(sg, FALSE);
1365        }
1366
1367        if (sd->ssl) {
1368                ssl_disconnect(sd->ssl);
1369        }
1370
1371        g_free(sd->username);
1372        g_free(sd->handle);
1373        g_free(sd);
1374        ic->proto_data = NULL;
1375}
1376
1377static int skype_buddy_msg(struct im_connection *ic, char *who, char *message,
1378                           int flags)
1379{
1380        char *ptr, *nick;
1381        int st;
1382
1383        /* Unused parameter */
1384        flags = flags;
1385
1386        nick = g_strdup(who);
1387        ptr = strchr(nick, '@');
1388        if (ptr) {
1389                *ptr = '\0';
1390        }
1391
1392        if (!strncmp(who, "skypeconsole", 12)) {
1393                st = skype_printf(ic, "%s\n", message);
1394        } else {
1395                st = skype_printf(ic, "MESSAGE %s %s\n", nick, message);
1396        }
1397        g_free(nick);
1398
1399        return st;
1400}
1401
1402const struct skype_away_state *skype_away_state_by_name(char *name)
1403{
1404        int i;
1405
1406        for (i = 0; skype_away_state_list[i].full_name; i++) {
1407                if (g_strcasecmp(skype_away_state_list[i].full_name, name) == 0) {
1408                        return skype_away_state_list + i;
1409                }
1410        }
1411
1412        return NULL;
1413}
1414
1415static void skype_set_away(struct im_connection *ic, char *state_txt,
1416                           char *message)
1417{
1418        const struct skype_away_state *state;
1419
1420        /* Unused parameter */
1421        message = message;
1422
1423        if (state_txt == NULL) {
1424                state = skype_away_state_by_name("Online");
1425        } else {
1426                state = skype_away_state_by_name(state_txt);
1427        }
1428        skype_printf(ic, "SET USERSTATUS %s\n", state->code);
1429}
1430
1431static GList *skype_away_states(struct im_connection *ic)
1432{
1433        static GList *l;
1434        int i;
1435
1436        /* Unused parameter */
1437        ic = ic;
1438
1439        if (l == NULL) {
1440                for (i = 0; skype_away_state_list[i].full_name; i++) {
1441                        l = g_list_append(l,
1442                                          (void *) skype_away_state_list[i].full_name);
1443                }
1444        }
1445
1446        return l;
1447}
1448
1449static char *skype_set_display_name(set_t *set, char *value)
1450{
1451        account_t *acc = set->data;
1452        struct im_connection *ic = acc->ic;
1453
1454        skype_printf(ic, "SET PROFILE FULLNAME %s\n", value);
1455        return value;
1456}
1457
1458static char *skype_set_mood_text(set_t *set, char *value)
1459{
1460        account_t *acc = set->data;
1461        struct im_connection *ic = acc->ic;
1462
1463        skype_printf(ic, "SET PROFILE MOOD_TEXT %s\n", value);
1464        return value;
1465}
1466
1467static char *skype_set_balance(set_t *set, char *value)
1468{
1469        account_t *acc = set->data;
1470        struct im_connection *ic = acc->ic;
1471
1472        skype_printf(ic, "GET PROFILE PSTN_BALANCE\n");
1473        return value;
1474}
1475
1476static void skype_call(struct im_connection *ic, char *value)
1477{
1478        char *nick = g_strdup(value);
1479        char *ptr = strchr(nick, '@');
1480
1481        if (ptr) {
1482                *ptr = '\0';
1483        }
1484        skype_printf(ic, "CALL %s\n", nick);
1485        g_free(nick);
1486}
1487
1488static void skype_hangup(struct im_connection *ic)
1489{
1490        struct skype_data *sd = ic->proto_data;
1491
1492        if (sd->call_id) {
1493                skype_printf(ic, "SET CALL %s STATUS FINISHED\n",
1494                             sd->call_id);
1495                g_free(sd->call_id);
1496                sd->call_id = 0;
1497        } else {
1498                imcb_error(ic, "There are no active calls currently.");
1499        }
1500}
1501
1502static char *skype_set_call(set_t *set, char *value)
1503{
1504        account_t *acc = set->data;
1505        struct im_connection *ic = acc->ic;
1506
1507        if (value) {
1508                skype_call(ic, value);
1509        } else {
1510                skype_hangup(ic);
1511        }
1512        return value;
1513}
1514
1515static void skype_add_buddy(struct im_connection *ic, char *who, char *group)
1516{
1517        struct skype_data *sd = ic->proto_data;
1518        char *nick, *ptr;
1519
1520        nick = g_strdup(who);
1521        ptr = strchr(nick, '@');
1522        if (ptr) {
1523                *ptr = '\0';
1524        }
1525
1526        if (!group) {
1527                skype_printf(ic, "SET USER %s BUDDYSTATUS 2 Please authorize me\n",
1528                             nick);
1529                g_free(nick);
1530        } else {
1531                struct skype_group *sg = skype_group_by_name(ic, group);
1532
1533                if (!sg) {
1534                        /* No such group, we need to create it, then have to
1535                         * add the user once it's created. */
1536                        skype_printf(ic, "CREATE GROUP %s\n", group);
1537                        sd->pending_user = g_strdup(nick);
1538                } else {
1539                        skype_printf(ic, "ALTER GROUP %d ADDUSER %s\n", sg->id, nick);
1540                }
1541        }
1542}
1543
1544static void skype_remove_buddy(struct im_connection *ic, char *who, char *group)
1545{
1546        char *nick, *ptr;
1547
1548        /* Unused parameter */
1549        group = group;
1550
1551        nick = g_strdup(who);
1552        ptr = strchr(nick, '@');
1553        if (ptr) {
1554                *ptr = '\0';
1555        }
1556        skype_printf(ic, "SET USER %s BUDDYSTATUS 1\n", nick);
1557        g_free(nick);
1558}
1559
1560void skype_chat_msg(struct groupchat *gc, char *message, int flags)
1561{
1562        struct im_connection *ic = gc->ic;
1563
1564        /* Unused parameter */
1565        flags = flags;
1566
1567        skype_printf(ic, "CHATMESSAGE %s %s\n", gc->title, message);
1568}
1569
1570void skype_chat_leave(struct groupchat *gc)
1571{
1572        struct im_connection *ic = gc->ic;
1573
1574        skype_printf(ic, "ALTER CHAT %s LEAVE\n", gc->title);
1575        gc->data = (void *) TRUE;
1576}
1577
1578void skype_chat_invite(struct groupchat *gc, char *who, char *message)
1579{
1580        struct im_connection *ic = gc->ic;
1581        char *ptr, *nick;
1582
1583        nick = g_strdup(who);
1584        ptr = strchr(nick, '@');
1585        if (ptr) {
1586                *ptr = '\0';
1587        }
1588        skype_printf(ic, "ALTER CHAT %s ADDMEMBERS %s\n", gc->title, nick);
1589        g_free(nick);
1590}
1591
1592void skype_chat_topic(struct groupchat *gc, char *message)
1593{
1594        struct im_connection *ic = gc->ic;
1595        struct skype_data *sd = ic->proto_data;
1596
1597        skype_printf(ic, "ALTER CHAT %s SETTOPIC %s\n",
1598                     gc->title, message);
1599        sd->topic_wait = 1;
1600}
1601
1602struct groupchat *skype_chat_with(struct im_connection *ic, char *who)
1603{
1604        struct skype_data *sd = ic->proto_data;
1605        char *ptr, *nick;
1606
1607        nick = g_strdup(who);
1608        ptr = strchr(nick, '@');
1609        if (ptr) {
1610                *ptr = '\0';
1611        }
1612        skype_printf(ic, "CHAT CREATE %s\n", nick);
1613        sd->groupchat_with = g_strdup(nick);
1614        g_free(nick);
1615        /* We create a fake chat for now. We will replace it with a real one in
1616         * the real callback. */
1617        return imcb_chat_new(ic, "");
1618}
1619
1620static void skype_get_info(struct im_connection *ic, char *who)
1621{
1622        struct skype_data *sd = ic->proto_data;
1623        char *ptr, *nick;
1624
1625        nick = g_strdup(who);
1626        ptr = strchr(nick, '@');
1627        if (ptr) {
1628                *ptr = '\0';
1629        }
1630        sd->is_info = TRUE;
1631        skype_printf(ic, "GET USER %s FULLNAME\n", nick);
1632        skype_printf(ic, "GET USER %s PHONE_HOME\n", nick);
1633        skype_printf(ic, "GET USER %s PHONE_OFFICE\n", nick);
1634        skype_printf(ic, "GET USER %s PHONE_MOBILE\n", nick);
1635        skype_printf(ic, "GET USER %s NROF_AUTHED_BUDDIES\n", nick);
1636        skype_printf(ic, "GET USER %s TIMEZONE\n", nick);
1637        skype_printf(ic, "GET USER %s LASTONLINETIMESTAMP\n", nick);
1638        skype_printf(ic, "GET USER %s SEX\n", nick);
1639        skype_printf(ic, "GET USER %s LANGUAGE\n", nick);
1640        skype_printf(ic, "GET USER %s COUNTRY\n", nick);
1641        skype_printf(ic, "GET USER %s PROVINCE\n", nick);
1642        skype_printf(ic, "GET USER %s CITY\n", nick);
1643        skype_printf(ic, "GET USER %s HOMEPAGE\n", nick);
1644        skype_printf(ic, "GET USER %s ABOUT\n", nick);
1645        /*
1646         * Hack: we query the bithday property which is always a single line,
1647         * so we can send the collected properties to the user when we have
1648         * this one.
1649         */
1650        skype_printf(ic, "GET USER %s BIRTHDAY\n", nick);
1651}
1652
1653static void skype_init(account_t *acc)
1654{
1655        set_t *s;
1656
1657        s = set_add(&acc->set, "server", SKYPE_DEFAULT_SERVER, set_eval_account,
1658                    acc);
1659        s->flags |= ACC_SET_OFFLINE_ONLY;
1660
1661        s = set_add(&acc->set, "port", SKYPE_DEFAULT_PORT, set_eval_int, acc);
1662        s->flags |= ACC_SET_OFFLINE_ONLY;
1663
1664        s = set_add(&acc->set, "display_name", NULL, skype_set_display_name,
1665                    acc);
1666        s->flags |= SET_NOSAVE | ACC_SET_ONLINE_ONLY;
1667
1668        s = set_add(&acc->set, "mood_text", NULL, skype_set_mood_text, acc);
1669        s->flags |= SET_NOSAVE | ACC_SET_ONLINE_ONLY;
1670
1671        s = set_add(&acc->set, "call", NULL, skype_set_call, acc);
1672        s->flags |= SET_NOSAVE | ACC_SET_ONLINE_ONLY;
1673
1674        s = set_add(&acc->set, "balance", NULL, skype_set_balance, acc);
1675        s->flags |= SET_NOSAVE | ACC_SET_ONLINE_ONLY;
1676
1677        s = set_add(&acc->set, "skypeout_offline", "true", set_eval_bool, acc);
1678
1679        s = set_add(&acc->set, "skypeconsole", "false", set_eval_bool, acc);
1680        s->flags |= ACC_SET_OFFLINE_ONLY;
1681
1682        s = set_add(&acc->set, "skypeconsole_receive", "false", set_eval_bool,
1683                    acc);
1684        s->flags |= ACC_SET_OFFLINE_ONLY;
1685
1686        s = set_add(&acc->set, "auto_join", "false", set_eval_bool, acc);
1687        s->flags |= ACC_SET_OFFLINE_ONLY;
1688
1689        s = set_add(&acc->set, "test_join", "false", set_eval_bool, acc);
1690        s->flags |= ACC_SET_OFFLINE_ONLY;
1691
1692        s = set_add(&acc->set, "show_moods", "false", set_eval_bool, acc);
1693
1694        s = set_add(&acc->set, "edit_prefix", "EDIT:",
1695                    NULL, acc);
1696
1697        s = set_add(&acc->set, "read_groups", "false", set_eval_bool, acc);
1698}
1699
1700#if BITLBEE_VERSION_CODE > BITLBEE_VER(3, 0, 1)
1701GList *skype_buddy_action_list(bee_user_t *bu)
1702{
1703        static GList *ret;
1704
1705        /* Unused parameter */
1706        bu = bu;
1707
1708        if (ret == NULL) {
1709                static const struct buddy_action ba[2] = {
1710                        { "CALL", "Initiate a call" },
1711                        { "HANGUP", "Hang up a call" },
1712                };
1713                int i;
1714
1715                for (i = 0; i < ARRAY_SIZE(ba); i++) {
1716                        ret = g_list_prepend(ret, (void *) (ba + i));
1717                }
1718        }
1719
1720        return ret;
1721}
1722
1723void *skype_buddy_action(struct bee_user *bu, const char *action, char * const args[], void *data)
1724{
1725        /* Unused parameters */
1726        args = args;
1727        data = data;
1728
1729        if (!g_strcasecmp(action, "CALL")) {
1730                skype_call(bu->ic, bu->handle);
1731        } else if (!g_strcasecmp(action, "HANGUP")) {
1732                skype_hangup(bu->ic);
1733        }
1734
1735        return NULL;
1736}
1737#endif
1738
1739void init_plugin(void)
1740{
1741        struct prpl *ret = g_new0(struct prpl, 1);
1742
1743        ret->name = "skype";
1744        ret->login = skype_login;
1745        ret->init = skype_init;
1746        ret->logout = skype_logout;
1747        ret->buddy_msg = skype_buddy_msg;
1748        ret->get_info = skype_get_info;
1749        ret->away_states = skype_away_states;
1750        ret->set_away = skype_set_away;
1751        ret->add_buddy = skype_add_buddy;
1752        ret->remove_buddy = skype_remove_buddy;
1753        ret->chat_msg = skype_chat_msg;
1754        ret->chat_leave = skype_chat_leave;
1755        ret->chat_invite = skype_chat_invite;
1756        ret->chat_with = skype_chat_with;
1757        ret->handle_cmp = g_strcasecmp;
1758        ret->chat_topic = skype_chat_topic;
1759#if BITLBEE_VERSION_CODE > BITLBEE_VER(3, 0, 1)
1760        ret->buddy_action_list = skype_buddy_action_list;
1761        ret->buddy_action = skype_buddy_action;
1762#endif
1763        register_protocol(ret);
1764}
Note: See TracBrowser for help on using the repository browser.