source: irc_im.c @ d2d2b80

Last change on this file since d2d2b80 was b8c336b, checked in by dequis <dx@…>, at 2015-05-09T19:50:30Z

Merge branch 'develop' into feat/hip-cat

Conflicts:

protocols/jabber/hipchat.c
protocols/jabber/iq.c
protocols/jabber/jabber.h

  • Property mode set to 100644
File size: 27.2 KB
Line 
1/********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2012 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/* Some glue to put the IRC and the IM stuff together.                  */
8
9/*
10  This program is free software; you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 2 of the License, or
13  (at your option) any later version.
14
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License for more details.
19
20  You should have received a copy of the GNU General Public License with
21  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
22  if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
23  Fifth Floor, Boston, MA  02110-1301  USA
24*/
25
26#include "bitlbee.h"
27#include "dcc.h"
28
29/* IM->IRC callbacks: Simple IM/buddy-related stuff. */
30
31static const struct irc_user_funcs irc_user_im_funcs;
32
33static void bee_irc_imc_connected(struct im_connection *ic)
34{
35        irc_t *irc = (irc_t *) ic->bee->ui_data;
36
37        irc_channel_auto_joins(irc, ic->acc);
38}
39
40static void bee_irc_imc_disconnected(struct im_connection *ic)
41{
42        /* Maybe try to send /QUITs here instead of later on. */
43}
44
45static gboolean bee_irc_user_new(bee_t *bee, bee_user_t *bu)
46{
47        irc_user_t *iu;
48        irc_t *irc = (irc_t *) bee->ui_data;
49        char nick[MAX_NICK_LENGTH + 1], *s;
50
51        memset(nick, 0, MAX_NICK_LENGTH + 1);
52        strncpy(nick, nick_get(bu), MAX_NICK_LENGTH);
53
54        bu->ui_data = iu = irc_user_new(irc, nick);
55        iu->bu = bu;
56
57        if (set_getbool(&irc->b->set, "private")) {
58                iu->last_channel = NULL;
59        } else {
60                iu->last_channel = irc_channel_with_user(irc, iu);
61        }
62
63        if ((s = strchr(bu->handle, '@'))) {
64                iu->host = g_strdup(s + 1);
65                iu->user = g_strndup(bu->handle, s - bu->handle);
66        } else {
67                iu->user = g_strdup(bu->handle);
68                if (bu->ic->acc->server) {
69                        iu->host = g_strdup(bu->ic->acc->server);
70                } else {
71                        char *s;
72                        for (s = bu->ic->acc->tag; g_ascii_isalnum(*s); s++) {
73                                ;
74                        }
75                        /* Only use the tag if we got to the end of the string.
76                           (So allow alphanumerics only. Hopefully not too
77                           restrictive.) */
78                        if (*s) {
79                                iu->host = g_strdup(bu->ic->acc->prpl->name);
80                        } else {
81                                iu->host = g_strdup(bu->ic->acc->tag);
82                        }
83                }
84        }
85
86        while ((s = strchr(iu->user, ' '))) {
87                *s = '_';
88        }
89
90        if (bu->flags & BEE_USER_LOCAL) {
91                char *s = set_getstr(&bee->set, "handle_unknown");
92
93                if (strcmp(s, "add_private") == 0) {
94                        iu->last_channel = NULL;
95                } else if (strcmp(s, "add_channel") == 0) {
96                        iu->last_channel = irc->default_channel;
97                }
98        }
99
100        iu->f = &irc_user_im_funcs;
101
102        return TRUE;
103}
104
105static gboolean bee_irc_user_free(bee_t *bee, bee_user_t *bu)
106{
107        return irc_user_free(bee->ui_data, (irc_user_t *) bu->ui_data);
108}
109
110static gboolean bee_irc_user_status(bee_t *bee, bee_user_t *bu, bee_user_t *old)
111{
112        irc_t *irc = bee->ui_data;
113        irc_user_t *iu = bu->ui_data;
114
115        /* Do this outside the if below since away state can change without
116           the online state changing. */
117        iu->flags &= ~IRC_USER_AWAY;
118        if (bu->flags & BEE_USER_AWAY || !(bu->flags & BEE_USER_ONLINE)) {
119                iu->flags |= IRC_USER_AWAY;
120        }
121
122        if ((bu->flags & BEE_USER_ONLINE) != (old->flags & BEE_USER_ONLINE)) {
123                if (bu->flags & BEE_USER_ONLINE) {
124                        if (g_hash_table_lookup(irc->watches, iu->key)) {
125                                irc_send_num(irc, 600, "%s %s %s %d :%s", iu->nick, iu->user,
126                                             iu->host, (int) time(NULL), "logged online");
127                        }
128                } else {
129                        if (g_hash_table_lookup(irc->watches, iu->key)) {
130                                irc_send_num(irc, 601, "%s %s %s %d :%s", iu->nick, iu->user,
131                                             iu->host, (int) time(NULL), "logged offline");
132                        }
133
134                        /* Send a QUIT since those will also show up in any
135                           query windows the user may have, plus it's only
136                           one QUIT instead of possibly many (in case of
137                           multiple control chans). If there's a channel that
138                           shows offline people, a JOIN will follow. */
139                        if (set_getbool(&bee->set, "offline_user_quits")) {
140                                irc_user_quit(iu, "Leaving...");
141                        }
142                }
143        }
144
145        /* Reset this one since the info may have changed. */
146        iu->away_reply_timeout = 0;
147
148        bee_irc_channel_update(irc, NULL, iu);
149
150        return TRUE;
151}
152
153void bee_irc_channel_update(irc_t *irc, irc_channel_t *ic, irc_user_t *iu)
154{
155        GSList *l;
156
157        if (ic == NULL) {
158                for (l = irc->channels; l; l = l->next) {
159                        ic = l->data;
160                        /* TODO: Just add a type flag or so.. */
161                        if (ic->f == irc->default_channel->f &&
162                            (ic->flags & IRC_CHANNEL_JOINED)) {
163                                bee_irc_channel_update(irc, ic, iu);
164                        }
165                }
166                return;
167        }
168        if (iu == NULL) {
169                for (l = irc->users; l; l = l->next) {
170                        iu = l->data;
171                        if (iu->bu) {
172                                bee_irc_channel_update(irc, ic, l->data);
173                        }
174                }
175                return;
176        }
177
178        if (!irc_channel_wants_user(ic, iu)) {
179                irc_channel_del_user(ic, iu, IRC_CDU_PART, NULL);
180        } else {
181                struct irc_control_channel *icc = ic->data;
182                int mode = 0;
183
184                if (!(iu->bu->flags & BEE_USER_ONLINE)) {
185                        mode = icc->modes[0];
186                } else if (iu->bu->flags & BEE_USER_AWAY) {
187                        mode = icc->modes[1];
188                } else if (iu->bu->flags & BEE_USER_SPECIAL) {
189                        mode = icc->modes[2];
190                } else {
191                        mode = icc->modes[3];
192                }
193
194                if (!mode) {
195                        irc_channel_del_user(ic, iu, IRC_CDU_PART, NULL);
196                } else {
197                        irc_channel_add_user(ic, iu);
198                        irc_channel_user_set_mode(ic, iu, mode);
199                }
200        }
201}
202
203static gboolean bee_irc_user_msg(bee_t *bee, bee_user_t *bu, const char *msg_, time_t sent_at)
204{
205        irc_t *irc = bee->ui_data;
206        irc_user_t *iu = (irc_user_t *) bu->ui_data;
207        const char *dst;
208        char *prefix = NULL;
209        char *wrapped, *ts = NULL;
210        char *msg = g_strdup(msg_);
211        GSList *l;
212
213        if (sent_at > 0 && set_getbool(&irc->b->set, "display_timestamps")) {
214                ts = irc_format_timestamp(irc, sent_at);
215        }
216
217        dst = irc_user_msgdest(iu);
218        if (dst != irc->user->nick) {
219                /* if not messaging directly, call user by name */
220                prefix = g_strdup_printf("%s%s%s", irc->user->nick, set_getstr(&bee->set, "to_char"), ts ? : "");
221        } else {
222                prefix = ts;
223                ts = NULL;      /* don't double-free */
224        }
225
226        for (l = irc_plugins; l; l = l->next) {
227                irc_plugin_t *p = l->data;
228                if (p->filter_msg_in) {
229                        char *s = p->filter_msg_in(iu, msg, 0);
230                        if (s) {
231                                if (s != msg) {
232                                        g_free(msg);
233                                }
234                                msg = s;
235                        } else {
236                                /* Modules can swallow messages. */
237                                goto cleanup;
238                        }
239                }
240        }
241
242        if ((g_strcasecmp(set_getstr(&bee->set, "strip_html"), "always") == 0) ||
243            ((bu->ic->flags & OPT_DOES_HTML) && set_getbool(&bee->set, "strip_html"))) {
244                char *s = g_strdup(msg);
245                strip_html(s);
246                g_free(msg);
247                msg = s;
248        }
249
250        wrapped = word_wrap(msg, 425);
251        irc_send_msg(iu, "PRIVMSG", dst, wrapped, prefix);
252        g_free(wrapped);
253
254cleanup:
255        g_free(prefix);
256        g_free(msg);
257        g_free(ts);
258
259        return TRUE;
260}
261
262static gboolean bee_irc_user_typing(bee_t *bee, bee_user_t *bu, uint32_t flags)
263{
264        irc_t *irc = (irc_t *) bee->ui_data;
265
266        if (set_getbool(&bee->set, "typing_notice")) {
267                irc_send_msg_f((irc_user_t *) bu->ui_data, "PRIVMSG", irc->user->nick,
268                               "\001TYPING %d\001", (flags >> 8) & 3);
269        } else {
270                return FALSE;
271        }
272
273        return TRUE;
274}
275
276static gboolean bee_irc_user_action_response(bee_t *bee, bee_user_t *bu, const char *action, char * const args[],
277                                             void *data)
278{
279        irc_t *irc = (irc_t *) bee->ui_data;
280        GString *msg = g_string_new("\001");
281
282        g_string_append(msg, action);
283        while (*args) {
284                if (strchr(*args, ' ')) {
285                        g_string_append_printf(msg, " \"%s\"", *args);
286                } else {
287                        g_string_append_printf(msg, " %s", *args);
288                }
289                args++;
290        }
291        g_string_append_c(msg, '\001');
292
293        irc_send_msg((irc_user_t *) bu->ui_data, "NOTICE", irc->user->nick, msg->str, NULL);
294
295        g_string_free(msg, TRUE);
296
297        return TRUE;
298}
299
300static gboolean bee_irc_user_nick_update(irc_user_t *iu);
301
302static gboolean bee_irc_user_fullname(bee_t *bee, bee_user_t *bu)
303{
304        irc_user_t *iu = (irc_user_t *) bu->ui_data;
305        char *s;
306
307        if (iu->fullname != iu->nick) {
308                g_free(iu->fullname);
309        }
310        iu->fullname = g_strdup(bu->fullname);
311
312        /* Strip newlines (unlikely, but IRC-unfriendly so they must go)
313           TODO(wilmer): Do the same with away msgs again! */
314        for (s = iu->fullname; *s; s++) {
315                if (g_ascii_isspace(*s)) {
316                        *s = ' ';
317                }
318        }
319
320        if ((bu->ic->flags & OPT_LOGGED_IN) && set_getbool(&bee->set, "display_namechanges")) {
321                /* People don't like this /NOTICE. Meh, let's go back to the old one.
322                char *msg = g_strdup_printf( "<< \002BitlBee\002 - Changed name to `%s' >>", iu->fullname );
323                irc_send_msg( iu, "NOTICE", irc->user->nick, msg, NULL );
324                */
325                imcb_log(bu->ic, "User `%s' changed name to `%s'", iu->nick, iu->fullname);
326        }
327
328        bee_irc_user_nick_update(iu);
329
330        return TRUE;
331}
332
333static gboolean bee_irc_user_nick_hint(bee_t *bee, bee_user_t *bu, const char *hint)
334{
335        bee_irc_user_nick_update((irc_user_t *) bu->ui_data);
336
337        return TRUE;
338}
339
340static gboolean bee_irc_user_group(bee_t *bee, bee_user_t *bu)
341{
342        irc_user_t *iu = (irc_user_t *) bu->ui_data;
343        irc_t *irc = (irc_t *) bee->ui_data;
344        bee_user_flags_t online;
345
346        /* Take the user offline temporarily so we can change the nick (if necessary). */
347        if ((online = bu->flags & BEE_USER_ONLINE)) {
348                bu->flags &= ~BEE_USER_ONLINE;
349        }
350
351        bee_irc_channel_update(irc, NULL, iu);
352        bee_irc_user_nick_update(iu);
353
354        if (online) {
355                bu->flags |= online;
356                bee_irc_channel_update(irc, NULL, iu);
357        }
358
359        return TRUE;
360}
361
362static gboolean bee_irc_user_nick_update(irc_user_t *iu)
363{
364        bee_user_t *bu = iu->bu;
365        char *newnick;
366
367        if (bu->flags & BEE_USER_ONLINE) {
368                /* Ignore if the user is visible already. */
369                return TRUE;
370        }
371
372        if (nick_saved(bu)) {
373                /* The user already assigned a nickname to this person. */
374                return TRUE;
375        }
376
377        newnick = nick_get(bu);
378
379        if (strcmp(iu->nick, newnick) != 0) {
380                nick_dedupe(bu, newnick);
381                irc_user_set_nick(iu, newnick);
382        }
383
384        return TRUE;
385}
386
387void bee_irc_user_nick_reset(irc_user_t *iu)
388{
389        bee_user_t *bu = iu->bu;
390        bee_user_flags_t online;
391
392        if (bu == FALSE) {
393                return;
394        }
395
396        /* In this case, pretend the user is offline. */
397        if ((online = bu->flags & BEE_USER_ONLINE)) {
398                bu->flags &= ~BEE_USER_ONLINE;
399        }
400
401        nick_del(bu);
402        bee_irc_user_nick_update(iu);
403
404        bu->flags |= online;
405}
406
407/* IRC->IM calls */
408
409static gboolean bee_irc_user_privmsg_cb(gpointer data, gint fd, b_input_condition cond);
410
411static gboolean bee_irc_user_privmsg(irc_user_t *iu, const char *msg)
412{
413        const char *away;
414
415        if (iu->bu == NULL) {
416                return FALSE;
417        }
418
419        if ((away = irc_user_get_away(iu)) &&
420            time(NULL) >= iu->away_reply_timeout) {
421                irc_send_num(iu->irc, 301, "%s :%s", iu->nick, away);
422                iu->away_reply_timeout = time(NULL) +
423                                         set_getint(&iu->irc->b->set, "away_reply_timeout");
424        }
425
426        if (iu->pastebuf == NULL) {
427                iu->pastebuf = g_string_new(msg);
428        } else {
429                b_event_remove(iu->pastebuf_timer);
430                g_string_append_printf(iu->pastebuf, "\n%s", msg);
431        }
432
433        if (set_getbool(&iu->irc->b->set, "paste_buffer")) {
434                int delay;
435
436                if ((delay = set_getint(&iu->irc->b->set, "paste_buffer_delay")) <= 5) {
437                        delay *= 1000;
438                }
439
440                iu->pastebuf_timer = b_timeout_add(delay, bee_irc_user_privmsg_cb, iu);
441
442                return TRUE;
443        } else {
444                bee_irc_user_privmsg_cb(iu, 0, 0);
445
446                return TRUE;
447        }
448}
449
450static gboolean bee_irc_user_privmsg_cb(gpointer data, gint fd, b_input_condition cond)
451{
452        irc_user_t *iu = data;
453        char *msg;
454        GSList *l;
455
456        msg = g_string_free(iu->pastebuf, FALSE);
457        iu->pastebuf = NULL;
458        iu->pastebuf_timer = 0;
459
460        for (l = irc_plugins; l; l = l->next) {
461                irc_plugin_t *p = l->data;
462                if (p->filter_msg_out) {
463                        char *s = p->filter_msg_out(iu, msg, 0);
464                        if (s) {
465                                if (s != msg) {
466                                        g_free(msg);
467                                }
468                                msg = s;
469                        } else {
470                                /* Modules can swallow messages. */
471                                iu->pastebuf = NULL;
472                                g_free(msg);
473                                return FALSE;
474                        }
475                }
476        }
477
478        bee_user_msg(iu->irc->b, iu->bu, msg, 0);
479
480        g_free(msg);
481
482        return FALSE;
483}
484
485static gboolean bee_irc_user_ctcp(irc_user_t *iu, char *const *ctcp)
486{
487        if (ctcp[1] && g_strcasecmp(ctcp[0], "DCC") == 0
488            && g_strcasecmp(ctcp[1], "SEND") == 0) {
489                if (iu->bu && iu->bu->ic && iu->bu->ic->acc->prpl->transfer_request) {
490                        file_transfer_t *ft = dcc_request(iu->bu->ic, ctcp);
491                        if (ft) {
492                                iu->bu->ic->acc->prpl->transfer_request(iu->bu->ic, ft, iu->bu->handle);
493                        }
494
495                        return TRUE;
496                }
497        } else if (g_strcasecmp(ctcp[0], "TYPING") == 0) {
498                if (iu->bu && iu->bu->ic && iu->bu->ic->acc->prpl->send_typing && ctcp[1]) {
499                        int st = ctcp[1][0];
500                        if (st >= '0' && st <= '2') {
501                                st <<= 8;
502                                iu->bu->ic->acc->prpl->send_typing(iu->bu->ic, iu->bu->handle, st);
503                        }
504
505                        return TRUE;
506                }
507        } else if (g_strcasecmp(ctcp[0], "HELP") == 0 && iu->bu) {
508                GString *supp = g_string_new("Supported CTCPs:");
509                GList *l;
510
511                if (iu->bu->ic && iu->bu->ic->acc->prpl->transfer_request) {
512                        g_string_append(supp, " DCC SEND,");
513                }
514                if (iu->bu->ic && iu->bu->ic->acc->prpl->send_typing) {
515                        g_string_append(supp, " TYPING,");
516                }
517                if (iu->bu->ic->acc->prpl->buddy_action_list) {
518                        for (l = iu->bu->ic->acc->prpl->buddy_action_list(iu->bu); l; l = l->next) {
519                                struct buddy_action *ba = l->data;
520                                g_string_append_printf(supp, " %s (%s),",
521                                                       ba->name, ba->description);
522                        }
523                }
524                g_string_truncate(supp, supp->len - 1);
525                irc_send_msg_f(iu, "NOTICE", iu->irc->user->nick, "\001HELP %s\001", supp->str);
526                g_string_free(supp, TRUE);
527        } else if (iu->bu && iu->bu->ic && iu->bu->ic->acc->prpl->buddy_action) {
528                iu->bu->ic->acc->prpl->buddy_action(iu->bu, ctcp[0], ctcp + 1, NULL);
529        }
530
531        return FALSE;
532}
533
534static const struct irc_user_funcs irc_user_im_funcs = {
535        bee_irc_user_privmsg,
536        bee_irc_user_ctcp,
537};
538
539
540/* IM->IRC: Groupchats */
541const struct irc_channel_funcs irc_channel_im_chat_funcs;
542
543static gboolean bee_irc_chat_new(bee_t *bee, struct groupchat *c)
544{
545        irc_t *irc = bee->ui_data;
546        irc_channel_t *ic;
547        char *topic;
548        GSList *l;
549        int i;
550
551        /* Try to find a channel that expects to receive a groupchat.
552           This flag is set earlier in our current call trace. */
553        for (l = irc->channels; l; l = l->next) {
554                ic = l->data;
555                if (ic->flags & IRC_CHANNEL_CHAT_PICKME) {
556                        break;
557                }
558        }
559
560        /* If we found none, just generate some stupid name. */
561        if (l == NULL) {
562                for (i = 0; i <= 999; i++) {
563                        char name[16];
564                        sprintf(name, "#chat_%03d", i);
565                        if ((ic = irc_channel_new(irc, name))) {
566                                break;
567                        }
568                }
569        }
570
571        if (ic == NULL) {
572                return FALSE;
573        }
574
575        c->ui_data = ic;
576        ic->data = c;
577
578        if (ic->topic == NULL) {
579                /* New channel with no preset topic - make up a generic one */
580                topic = g_strdup_printf(
581                        "BitlBee groupchat: \"%s\". Please keep in mind that root-commands won't work here. Have fun!",
582                        c->title);
583                irc_channel_set_topic(ic, topic, irc->root);
584        } else {
585                /* Preset topic from the channel we picked */
586                topic = g_strdup(ic->topic);
587        }
588
589        g_free(c->topic);
590        c->topic = topic;         /* Let groupchat borrow this pointer */
591
592        return TRUE;
593}
594
595static gboolean bee_irc_chat_free(bee_t *bee, struct groupchat *c)
596{
597        irc_channel_t *ic = c->ui_data;
598
599        if (ic == NULL) {
600                return FALSE;
601        }
602
603        if (ic->flags & IRC_CHANNEL_JOINED) {
604                irc_channel_printf(ic, "Cleaning up channel, bye!");
605        }
606
607        ic->data = NULL;
608        c->ui_data = NULL;
609        irc_channel_del_user(ic, ic->irc->user, IRC_CDU_KICK, "Chatroom closed by server");
610
611        return TRUE;
612}
613
614static gboolean bee_irc_chat_placeholder_new(bee_t *bee, struct im_connection *ic, const char *handle,
615                                             const char *name, const char *topic)
616{
617        irc_t *irc = bee->ui_data;
618        irc_channel_t *ircc;
619        char *full_name = irc_channel_name_gen(irc, name);
620
621        ircc = irc_channel_new(irc, full_name);
622
623        set_setstr(&ircc->set, "type", "chat");
624        set_setstr(&ircc->set, "chat_type", "placeholder");
625        set_setstr(&ircc->set, "account", ic->acc->tag);
626        set_setstr(&ircc->set, "room", (char *) handle);
627
628        irc_channel_set_topic(ircc, topic, NULL);
629
630        g_free(full_name);
631
632        return TRUE;
633}
634
635static gboolean bee_irc_chat_log(bee_t *bee, struct groupchat *c, const char *text)
636{
637        irc_channel_t *ic = c->ui_data;
638
639        if (ic == NULL) {
640                return FALSE;
641        }
642
643        irc_channel_printf(ic, "%s", text);
644
645        return TRUE;
646}
647
648static gboolean bee_irc_chat_msg(bee_t *bee, struct groupchat *c, bee_user_t *bu, const char *msg, time_t sent_at)
649{
650        irc_t *irc = bee->ui_data;
651        irc_user_t *iu = bu->ui_data;
652        irc_channel_t *ic = c->ui_data;
653        char *wrapped, *ts = NULL;
654
655        if (ic == NULL) {
656                return FALSE;
657        }
658
659        if (sent_at > 0 && set_getbool(&bee->set, "display_timestamps")) {
660                ts = irc_format_timestamp(irc, sent_at);
661        }
662
663        wrapped = word_wrap(msg, 425);
664        irc_send_msg(iu, "PRIVMSG", ic->name, wrapped, ts);
665        g_free(ts);
666        g_free(wrapped);
667
668        return TRUE;
669}
670
671static gboolean bee_irc_chat_add_user(bee_t *bee, struct groupchat *c, bee_user_t *bu)
672{
673        irc_t *irc = bee->ui_data;
674        irc_channel_t *ic = c->ui_data;
675
676        if (ic == NULL) {
677                return FALSE;
678        }
679
680        irc_channel_add_user(ic, bu == bee->user ? irc->user : bu->ui_data);
681
682        return TRUE;
683}
684
685static gboolean bee_irc_chat_remove_user(bee_t *bee, struct groupchat *c, bee_user_t *bu)
686{
687        irc_t *irc = bee->ui_data;
688        irc_channel_t *ic = c->ui_data;
689
690        if (ic == NULL || bu == NULL) {
691                return FALSE;
692        }
693
694        /* TODO: Possible bug here: If a module removes $user here instead of just
695           using imcb_chat_free() and the channel was IRC_CHANNEL_TEMP, we get into
696           a broken state around here. */
697        irc_channel_del_user(ic, bu == bee->user ? irc->user : bu->ui_data, IRC_CDU_PART, NULL);
698
699        return TRUE;
700}
701
702static gboolean bee_irc_chat_topic(bee_t *bee, struct groupchat *c, const char *new, bee_user_t *bu)
703{
704        irc_channel_t *ic = c->ui_data;
705        irc_t *irc = bee->ui_data;
706        irc_user_t *iu;
707
708        if (ic == NULL) {
709                return FALSE;
710        }
711
712        if (bu == NULL) {
713                iu = irc->root;
714        } else if (bu == bee->user) {
715                iu = irc->user;
716        } else {
717                iu = bu->ui_data;
718        }
719
720        irc_channel_set_topic(ic, new, iu);
721
722        return TRUE;
723}
724
725static gboolean bee_irc_chat_name_hint(bee_t *bee, struct groupchat *c, const char *name)
726{
727        return irc_channel_name_hint(c->ui_data, name);
728}
729
730static gboolean bee_irc_chat_invite(bee_t *bee, bee_user_t *bu, const char *name, const char *msg)
731{
732        char *channel, *s;
733        irc_t *irc = bee->ui_data;
734        irc_user_t *iu = bu->ui_data;
735        irc_channel_t *chan;
736
737        if (strchr(CTYPES, name[0])) {
738                channel = g_strdup(name);
739        } else {
740                channel = g_strdup_printf("#%s", name);
741        }
742
743        if ((s = strchr(channel, '@'))) {
744                *s = '\0';
745        }
746
747        if (strlen(channel) > MAX_NICK_LENGTH) {
748                /* If the channel name is very long (like those insane GTalk
749                   UUID names), try if we can use the inviter's nick. */
750                s = g_strdup_printf("#%s", iu->nick);
751                if (irc_channel_by_name(irc, s) == NULL) {
752                        g_free(channel);
753                        channel = s;
754                } else {
755                        g_free(s);
756                }
757        }
758
759        if ((chan = irc_channel_new(irc, channel)) &&
760            set_setstr(&chan->set, "type", "chat") &&
761            set_setstr(&chan->set, "chat_type", "room") &&
762            set_setstr(&chan->set, "account", bu->ic->acc->tag) &&
763            set_setstr(&chan->set, "room", (char *) name)) {
764                /* I'm assuming that if the user didn't "chat add" the room
765                   himself but got invited, it's temporary, so make this a
766                   temporary mapping that is removed as soon as we /PART. */
767                chan->flags |= IRC_CHANNEL_TEMP;
768        } else {
769                irc_channel_free(chan);
770                chan = NULL;
771        }
772        g_free(channel);
773
774        irc_send_msg_f(iu, "PRIVMSG", irc->user->nick, "<< \002BitlBee\002 - Invitation to chatroom %s >>", name);
775        if (msg) {
776                irc_send_msg(iu, "PRIVMSG", irc->user->nick, msg, NULL);
777        }
778        if (chan) {
779                irc_send_msg_f(iu, "PRIVMSG", irc->user->nick, "To join the room, just /join %s", chan->name);
780                irc_send_invite(iu, chan);
781        }
782
783        return TRUE;
784}
785
786/* IRC->IM */
787static gboolean bee_irc_channel_chat_privmsg_cb(gpointer data, gint fd, b_input_condition cond);
788
789static gboolean bee_irc_channel_chat_privmsg(irc_channel_t *ic, const char *msg)
790{
791        struct groupchat *c = ic->data;
792        char *trans = NULL, *s;
793
794        if (c == NULL) {
795                return FALSE;
796        }
797
798        if (set_getbool(&ic->set, "translate_to_nicks")) {
799                char nick[MAX_NICK_LENGTH + 1];
800                irc_user_t *iu;
801
802                strncpy(nick, msg, MAX_NICK_LENGTH);
803                nick[MAX_NICK_LENGTH] = '\0';
804                if ((s = strchr(nick, ':')) || (s = strchr(nick, ','))) {
805                        *s = '\0';
806                        if ((iu = irc_user_by_name(ic->irc, nick)) && iu->bu &&
807                            iu->bu->nick && irc_channel_has_user(ic, iu)) {
808                                trans = g_strconcat(iu->bu->nick, msg + (s - nick), NULL);
809                                msg = trans;
810                        }
811                }
812        }
813
814        if (set_getbool(&ic->irc->b->set, "paste_buffer")) {
815                int delay;
816
817                if (ic->pastebuf == NULL) {
818                        ic->pastebuf = g_string_new(msg);
819                } else {
820                        b_event_remove(ic->pastebuf_timer);
821                        g_string_append_printf(ic->pastebuf, "\n%s", msg);
822                }
823
824                if ((delay = set_getint(&ic->irc->b->set, "paste_buffer_delay")) <= 5) {
825                        delay *= 1000;
826                }
827
828                ic->pastebuf_timer = b_timeout_add(delay, bee_irc_channel_chat_privmsg_cb, ic);
829
830                g_free(trans);
831                return TRUE;
832        } else {
833                bee_chat_msg(ic->irc->b, c, msg, 0);
834        }
835
836        g_free(trans);
837        return TRUE;
838}
839
840static gboolean bee_irc_channel_chat_privmsg_cb(gpointer data, gint fd, b_input_condition cond)
841{
842        irc_channel_t *ic = data;
843
844        if (ic->data) {
845                bee_chat_msg(ic->irc->b, ic->data, ic->pastebuf->str, 0);
846        }
847
848        g_string_free(ic->pastebuf, TRUE);
849        ic->pastebuf = 0;
850        ic->pastebuf_timer = 0;
851
852        return FALSE;
853}
854
855static gboolean bee_irc_channel_chat_join(irc_channel_t *ic)
856{
857        char *acc_s, *room, *chat_type;
858        account_t *acc;
859
860        chat_type = set_getstr(&ic->set, "chat_type");
861
862        if (strcmp(chat_type, "room") != 0 && strcmp(chat_type, "placeholder") != 0) {
863                return TRUE;
864        }
865
866        if ((acc_s = set_getstr(&ic->set, "account")) &&
867            (room = set_getstr(&ic->set, "room")) &&
868            (acc = account_get(ic->irc->b, acc_s)) &&
869            acc->ic && acc->prpl->chat_join) {
870                char *nick;
871
872                if (!(nick = set_getstr(&ic->set, "nick"))) {
873                        nick = ic->irc->user->nick;
874                }
875
876                ic->flags |= IRC_CHANNEL_CHAT_PICKME;
877                acc->prpl->chat_join(acc->ic, room, nick, NULL, &ic->set);
878                ic->flags &= ~IRC_CHANNEL_CHAT_PICKME;
879
880                return FALSE;
881        } else {
882                irc_send_num(ic->irc, 403, "%s :Can't join channel, account offline?", ic->name);
883                return FALSE;
884        }
885}
886
887static gboolean bee_irc_channel_chat_part(irc_channel_t *ic, const char *msg)
888{
889        struct groupchat *c = ic->data;
890
891        if (c && c->ic->acc->prpl->chat_leave) {
892                c->ic->acc->prpl->chat_leave(c);
893        }
894
895        /* Remove the reference. We don't need it anymore. */
896        ic->data = NULL;
897
898        return TRUE;
899}
900
901static gboolean bee_irc_channel_chat_topic(irc_channel_t *ic, const char *new)
902{
903        struct groupchat *c = ic->data;
904
905        if (c == NULL) {
906                return FALSE;
907        }
908
909        if (c->ic->acc->prpl->chat_topic == NULL) {
910                irc_send_num(ic->irc, 482, "%s :IM network does not support channel topics", ic->name);
911        } else {
912                /* TODO: Need more const goodness here, sigh */
913                char *topic = g_strdup(new);
914                c->ic->acc->prpl->chat_topic(c, topic);
915                g_free(topic);
916        }
917
918        /* Whatever happened, the IM module should ack the topic change. */
919        return FALSE;
920}
921
922static gboolean bee_irc_channel_chat_invite(irc_channel_t *ic, irc_user_t *iu)
923{
924        struct groupchat *c = ic->data;
925        bee_user_t *bu = iu->bu;
926
927        if (bu == NULL) {
928                return FALSE;
929        }
930
931        if (c) {
932                if (iu->bu->ic != c->ic) {
933                        irc_send_num(ic->irc, 482, "%s :Can't mix different IM networks in one groupchat", ic->name);
934                } else if (c->ic->acc->prpl->chat_invite) {
935                        c->ic->acc->prpl->chat_invite(c, iu->bu->handle, NULL);
936                } else {
937                        irc_send_num(ic->irc, 482, "%s :IM protocol does not support room invitations", ic->name);
938                }
939        } else if (bu->ic->acc->prpl->chat_with &&
940                   strcmp(set_getstr(&ic->set, "chat_type"), "groupchat") == 0) {
941                ic->flags |= IRC_CHANNEL_CHAT_PICKME;
942                iu->bu->ic->acc->prpl->chat_with(bu->ic, bu->handle);
943                ic->flags &= ~IRC_CHANNEL_CHAT_PICKME;
944        } else {
945                irc_send_num(ic->irc, 482, "%s :IM protocol does not support room invitations", ic->name);
946        }
947
948        return TRUE;
949}
950
951static void bee_irc_channel_chat_kick(irc_channel_t *ic, irc_user_t *iu, const char *msg)
952{
953        struct groupchat *c = ic->data;
954        bee_user_t *bu = iu->bu;
955
956        if ((c == NULL) || (bu == NULL)) {
957                return;
958        }
959
960        if (!c->ic->acc->prpl->chat_kick) {
961                irc_send_num(ic->irc, 482, "%s :IM protocol does not support room kicking", ic->name);
962                return;
963        }
964
965        c->ic->acc->prpl->chat_kick(c, iu->bu->handle, msg);
966}
967
968static char *set_eval_room_account(set_t *set, char *value);
969static char *set_eval_chat_type(set_t *set, char *value);
970
971static gboolean bee_irc_channel_init(irc_channel_t *ic)
972{
973        set_t *s;
974
975        set_add(&ic->set, "account", NULL, set_eval_room_account, ic);
976        set_add(&ic->set, "chat_type", "groupchat", set_eval_chat_type, ic);
977
978        s = set_add(&ic->set, "nick", NULL, NULL, ic);
979        s->flags |= SET_NULL_OK;
980
981        set_add(&ic->set, "room", NULL, NULL, ic);
982        set_add(&ic->set, "translate_to_nicks", "true", set_eval_bool, ic);
983
984        /* chat_type == groupchat */
985        ic->flags |= IRC_CHANNEL_TEMP;
986
987        return TRUE;
988}
989
990static char *set_eval_room_account(set_t *set, char *value)
991{
992        struct irc_channel *ic = set->data;
993        account_t *acc, *oa;
994
995        if (!(acc = account_get(ic->irc->b, value))) {
996                return SET_INVALID;
997        } else if (!acc->prpl->chat_join) {
998                irc_rootmsg(ic->irc, "Named chatrooms not supported on that account.");
999                return SET_INVALID;
1000        }
1001
1002        if (set->value && (oa = account_get(ic->irc->b, set->value)) &&
1003            oa->prpl->chat_free_settings) {
1004                oa->prpl->chat_free_settings(oa, &ic->set);
1005        }
1006
1007        if (acc->prpl->chat_add_settings) {
1008                acc->prpl->chat_add_settings(acc, &ic->set);
1009        }
1010
1011        return g_strdup(acc->tag);
1012}
1013
1014static char *set_eval_chat_type(set_t *set, char *value)
1015{
1016        struct irc_channel *ic = set->data;
1017
1018        ic->flags &= ~(IRC_CHANNEL_TEMP | IRC_CHANNEL_KEEP_PLACEHOLDER);
1019
1020        if (strcmp(value, "groupchat") == 0) {
1021                ic->flags |= IRC_CHANNEL_TEMP;
1022        } else if (strcmp(value, "room") == 0) {
1023                // beep boop
1024        } else if (strcmp(value, "placeholder") == 0) {
1025                ic->flags |= IRC_CHANNEL_TEMP | IRC_CHANNEL_KEEP_PLACEHOLDER;
1026        } else {
1027                return NULL;
1028        }
1029
1030        return value;
1031}
1032
1033static gboolean bee_irc_channel_free(irc_channel_t *ic)
1034{
1035        struct groupchat *c = ic->data;
1036
1037        set_del(&ic->set, "account");
1038        set_del(&ic->set, "chat_type");
1039        set_del(&ic->set, "nick");
1040        set_del(&ic->set, "room");
1041        set_del(&ic->set, "translate_to_nicks");
1042
1043        ic->flags &= ~IRC_CHANNEL_TEMP;
1044
1045        /* That one still points at this channel. Don't. */
1046        if (c) {
1047                c->ui_data = NULL;
1048        }
1049
1050        return TRUE;
1051}
1052
1053const struct irc_channel_funcs irc_channel_im_chat_funcs = {
1054        bee_irc_channel_chat_privmsg,
1055        bee_irc_channel_chat_join,
1056        bee_irc_channel_chat_part,
1057        bee_irc_channel_chat_topic,
1058        bee_irc_channel_chat_invite,
1059        bee_irc_channel_chat_kick,
1060
1061        bee_irc_channel_init,
1062        bee_irc_channel_free,
1063};
1064
1065
1066/* IM->IRC: File transfers */
1067static file_transfer_t *bee_irc_ft_in_start(bee_t *bee, bee_user_t *bu, const char *file_name, size_t file_size)
1068{
1069        return dccs_send_start(bu->ic, (irc_user_t *) bu->ui_data, file_name, file_size);
1070}
1071
1072static gboolean bee_irc_ft_out_start(struct im_connection *ic, file_transfer_t *ft)
1073{
1074        return dccs_recv_start(ft);
1075}
1076
1077static void bee_irc_ft_close(struct im_connection *ic, file_transfer_t *ft)
1078{
1079        return dcc_close(ft);
1080}
1081
1082static void bee_irc_ft_finished(struct im_connection *ic, file_transfer_t *file)
1083{
1084        dcc_file_transfer_t *df = file->priv;
1085
1086        if (file->bytes_transferred >= file->file_size) {
1087                dcc_finish(file);
1088        } else {
1089                df->proto_finished = TRUE;
1090        }
1091}
1092
1093const struct bee_ui_funcs irc_ui_funcs = {
1094        bee_irc_imc_connected,
1095        bee_irc_imc_disconnected,
1096
1097        bee_irc_user_new,
1098        bee_irc_user_free,
1099        bee_irc_user_fullname,
1100        bee_irc_user_nick_hint,
1101        bee_irc_user_group,
1102        bee_irc_user_status,
1103        bee_irc_user_msg,
1104        bee_irc_user_typing,
1105        bee_irc_user_action_response,
1106
1107        bee_irc_chat_new,
1108        bee_irc_chat_free,
1109        bee_irc_chat_placeholder_new,
1110        bee_irc_chat_log,
1111        bee_irc_chat_msg,
1112        bee_irc_chat_add_user,
1113        bee_irc_chat_remove_user,
1114        bee_irc_chat_topic,
1115        bee_irc_chat_name_hint,
1116        bee_irc_chat_invite,
1117
1118        bee_irc_ft_in_start,
1119        bee_irc_ft_out_start,
1120        bee_irc_ft_close,
1121        bee_irc_ft_finished,
1122};
Note: See TracBrowser for help on using the repository browser.