source: irc_im.c @ 345577b

Last change on this file since 345577b was 345577b, checked in by dequis <dx@…>, at 2015-10-30T10:27:20Z

IRC self-message support (messages sent by yourself from other clients)

This adds an OPT_SELFMESSAGE flag that can be passed to imcb_buddy_msg()
or imcb_chat_msg() to indicate that the protocol knows that the message
being sent is a self message.

This needs to be explicit since the old behavior is to silently drop
these messages, which also removed server echoes.

This commit doesn't break API/ABI, the flags parameters that were added
are all internal (between protocols and UI code)

On the irc protocol side, the situation isn't very nice, since some
clients put these messages in the wrong window. Irssi, hexchat and mirc
get this wrong. Irssi 0.8.18 has a fix for it, and the others have
scripts to patch it.

But meanwhile, there's a "self_messages" global setting that lets users
disable this, or get them as normal messages / notices with a "->"
prefix, which loosely imitates the workaround used by the ZNC
"privmsg_prefix" module.

  • 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_, guint32 flags, time_t sent_at)
204{
205        irc_t *irc = bee->ui_data;
206        irc_user_t *iu = (irc_user_t *) bu->ui_data;
207        irc_user_t *src_iu = iu;
208        irc_user_t *dst_iu = irc->user;
209        const char *dst;
210        char *prefix = NULL;
211        char *wrapped, *ts = NULL;
212        char *msg = g_strdup(msg_);
213        char *message_type = "PRIVMSG";
214        GSList *l;
215
216        if (sent_at > 0 && set_getbool(&irc->b->set, "display_timestamps")) {
217                ts = irc_format_timestamp(irc, sent_at);
218        }
219
220        dst = irc_user_msgdest(iu);
221
222        if (flags & OPT_SELFMESSAGE) {
223                char *setting = set_getstr(&irc->b->set, "self_messages");
224
225                if (is_bool(setting)) {
226                        if (bool2int(setting)) {
227                                /* set to true, send it with src/dst flipped */
228                               
229                                dst_iu = iu;
230                                src_iu = irc->user;
231
232                                if (dst == irc->user->nick) {
233                                        dst = dst_iu->nick;
234                                }
235                        } else {
236                                /* set to false, skip the message completely */
237                                goto cleanup;
238                        }
239                } else if (g_strncasecmp(setting, "prefix", 6) == 0) {
240                        /* third state, prefix, loosely imitates the znc privmsg_prefix module */
241
242                        g_free(msg);
243                        if (g_strncasecmp(msg_, "/me ", 4) == 0) {
244                                msg = g_strdup_printf("/me -> %s", msg_ + 4);
245                        } else {
246                                msg = g_strdup_printf("-> %s", msg_);
247                        }
248
249                        if (g_strcasecmp(setting, "prefix_notice") == 0) {
250                                message_type = "NOTICE";
251                        }
252                }
253
254        }
255
256        if (dst != dst_iu->nick) {
257                /* if not messaging directly (control channel), call user by name */
258                prefix = g_strdup_printf("%s%s%s", dst_iu->nick, set_getstr(&bee->set, "to_char"), ts ? : "");
259        } else {
260                prefix = ts;
261                ts = NULL;      /* don't double-free */
262        }
263
264        for (l = irc_plugins; l; l = l->next) {
265                irc_plugin_t *p = l->data;
266                if (p->filter_msg_in) {
267                        char *s = p->filter_msg_in(iu, msg, 0);
268                        if (s) {
269                                if (s != msg) {
270                                        g_free(msg);
271                                }
272                                msg = s;
273                        } else {
274                                /* Modules can swallow messages. */
275                                goto cleanup;
276                        }
277                }
278        }
279
280        if ((g_strcasecmp(set_getstr(&bee->set, "strip_html"), "always") == 0) ||
281            ((bu->ic->flags & OPT_DOES_HTML) && set_getbool(&bee->set, "strip_html"))) {
282                char *s = g_strdup(msg);
283                strip_html(s);
284                g_free(msg);
285                msg = s;
286        }
287
288        wrapped = word_wrap(msg, 425);
289        irc_send_msg(src_iu, message_type, dst, wrapped, prefix);
290        g_free(wrapped);
291
292cleanup:
293        g_free(prefix);
294        g_free(msg);
295        g_free(ts);
296
297        return TRUE;
298}
299
300static gboolean bee_irc_user_typing(bee_t *bee, bee_user_t *bu, guint32 flags)
301{
302        irc_t *irc = (irc_t *) bee->ui_data;
303
304        if (set_getbool(&bee->set, "typing_notice")) {
305                irc_send_msg_f((irc_user_t *) bu->ui_data, "PRIVMSG", irc->user->nick,
306                               "\001TYPING %d\001", (flags >> 8) & 3);
307        } else {
308                return FALSE;
309        }
310
311        return TRUE;
312}
313
314static gboolean bee_irc_user_action_response(bee_t *bee, bee_user_t *bu, const char *action, char * const args[],
315                                             void *data)
316{
317        irc_t *irc = (irc_t *) bee->ui_data;
318        GString *msg = g_string_new("\001");
319
320        g_string_append(msg, action);
321        while (*args) {
322                if (strchr(*args, ' ')) {
323                        g_string_append_printf(msg, " \"%s\"", *args);
324                } else {
325                        g_string_append_printf(msg, " %s", *args);
326                }
327                args++;
328        }
329        g_string_append_c(msg, '\001');
330
331        irc_send_msg((irc_user_t *) bu->ui_data, "NOTICE", irc->user->nick, msg->str, NULL);
332
333        g_string_free(msg, TRUE);
334
335        return TRUE;
336}
337
338static gboolean bee_irc_user_nick_update(irc_user_t *iu);
339
340static gboolean bee_irc_user_fullname(bee_t *bee, bee_user_t *bu)
341{
342        irc_user_t *iu = (irc_user_t *) bu->ui_data;
343        char *s;
344
345        if (iu->fullname != iu->nick) {
346                g_free(iu->fullname);
347        }
348        iu->fullname = g_strdup(bu->fullname);
349
350        /* Strip newlines (unlikely, but IRC-unfriendly so they must go)
351           TODO(wilmer): Do the same with away msgs again! */
352        for (s = iu->fullname; *s; s++) {
353                if (g_ascii_isspace(*s)) {
354                        *s = ' ';
355                }
356        }
357
358        if ((bu->ic->flags & OPT_LOGGED_IN) && set_getbool(&bee->set, "display_namechanges")) {
359                /* People don't like this /NOTICE. Meh, let's go back to the old one.
360                char *msg = g_strdup_printf( "<< \002BitlBee\002 - Changed name to `%s' >>", iu->fullname );
361                irc_send_msg( iu, "NOTICE", irc->user->nick, msg, NULL );
362                */
363                imcb_log(bu->ic, "User `%s' changed name to `%s'", iu->nick, iu->fullname);
364        }
365
366        bee_irc_user_nick_update(iu);
367
368        return TRUE;
369}
370
371static gboolean bee_irc_user_nick_hint(bee_t *bee, bee_user_t *bu, const char *hint)
372{
373        bee_irc_user_nick_update((irc_user_t *) bu->ui_data);
374
375        return TRUE;
376}
377
378static gboolean bee_irc_user_group(bee_t *bee, bee_user_t *bu)
379{
380        irc_user_t *iu = (irc_user_t *) bu->ui_data;
381        irc_t *irc = (irc_t *) bee->ui_data;
382        bee_user_flags_t online;
383
384        /* Take the user offline temporarily so we can change the nick (if necessary). */
385        if ((online = bu->flags & BEE_USER_ONLINE)) {
386                bu->flags &= ~BEE_USER_ONLINE;
387        }
388
389        bee_irc_channel_update(irc, NULL, iu);
390        bee_irc_user_nick_update(iu);
391
392        if (online) {
393                bu->flags |= online;
394                bee_irc_channel_update(irc, NULL, iu);
395        }
396
397        return TRUE;
398}
399
400static gboolean bee_irc_user_nick_update(irc_user_t *iu)
401{
402        bee_user_t *bu = iu->bu;
403        char *newnick;
404
405        if (bu->flags & BEE_USER_ONLINE) {
406                /* Ignore if the user is visible already. */
407                return TRUE;
408        }
409
410        if (nick_saved(bu)) {
411                /* The user already assigned a nickname to this person. */
412                return TRUE;
413        }
414
415        newnick = nick_get(bu);
416
417        if (strcmp(iu->nick, newnick) != 0) {
418                nick_dedupe(bu, newnick);
419                irc_user_set_nick(iu, newnick);
420        }
421
422        return TRUE;
423}
424
425void bee_irc_user_nick_reset(irc_user_t *iu)
426{
427        bee_user_t *bu = iu->bu;
428        bee_user_flags_t online;
429
430        if (bu == FALSE) {
431                return;
432        }
433
434        /* In this case, pretend the user is offline. */
435        if ((online = bu->flags & BEE_USER_ONLINE)) {
436                bu->flags &= ~BEE_USER_ONLINE;
437        }
438
439        nick_del(bu);
440        bee_irc_user_nick_update(iu);
441
442        bu->flags |= online;
443}
444
445/* IRC->IM calls */
446
447static gboolean bee_irc_user_privmsg_cb(gpointer data, gint fd, b_input_condition cond);
448
449static gboolean bee_irc_user_privmsg(irc_user_t *iu, const char *msg)
450{
451        const char *away;
452
453        if (iu->bu == NULL) {
454                return FALSE;
455        }
456
457        if ((away = irc_user_get_away(iu)) &&
458            time(NULL) >= iu->away_reply_timeout) {
459                irc_send_num(iu->irc, 301, "%s :%s", iu->nick, away);
460                iu->away_reply_timeout = time(NULL) +
461                                         set_getint(&iu->irc->b->set, "away_reply_timeout");
462        }
463
464        if (iu->pastebuf == NULL) {
465                iu->pastebuf = g_string_new(msg);
466        } else {
467                b_event_remove(iu->pastebuf_timer);
468                g_string_append_printf(iu->pastebuf, "\n%s", msg);
469        }
470
471        if (set_getbool(&iu->irc->b->set, "paste_buffer")) {
472                int delay;
473
474                if ((delay = set_getint(&iu->irc->b->set, "paste_buffer_delay")) <= 5) {
475                        delay *= 1000;
476                }
477
478                iu->pastebuf_timer = b_timeout_add(delay, bee_irc_user_privmsg_cb, iu);
479
480                return TRUE;
481        } else {
482                bee_irc_user_privmsg_cb(iu, 0, 0);
483
484                return TRUE;
485        }
486}
487
488static gboolean bee_irc_user_privmsg_cb(gpointer data, gint fd, b_input_condition cond)
489{
490        irc_user_t *iu = data;
491        char *msg;
492        GSList *l;
493
494        msg = g_string_free(iu->pastebuf, FALSE);
495        iu->pastebuf = NULL;
496        iu->pastebuf_timer = 0;
497
498        for (l = irc_plugins; l; l = l->next) {
499                irc_plugin_t *p = l->data;
500                if (p->filter_msg_out) {
501                        char *s = p->filter_msg_out(iu, msg, 0);
502                        if (s) {
503                                if (s != msg) {
504                                        g_free(msg);
505                                }
506                                msg = s;
507                        } else {
508                                /* Modules can swallow messages. */
509                                iu->pastebuf = NULL;
510                                g_free(msg);
511                                return FALSE;
512                        }
513                }
514        }
515
516        bee_user_msg(iu->irc->b, iu->bu, msg, 0);
517
518        g_free(msg);
519
520        return FALSE;
521}
522
523static gboolean bee_irc_user_ctcp(irc_user_t *iu, char *const *ctcp)
524{
525        if (ctcp[1] && g_strcasecmp(ctcp[0], "DCC") == 0
526            && g_strcasecmp(ctcp[1], "SEND") == 0) {
527                if (iu->bu && iu->bu->ic && iu->bu->ic->acc->prpl->transfer_request) {
528                        file_transfer_t *ft = dcc_request(iu->bu->ic, ctcp);
529                        if (ft) {
530                                iu->bu->ic->acc->prpl->transfer_request(iu->bu->ic, ft, iu->bu->handle);
531                        }
532
533                        return TRUE;
534                }
535        } else if (g_strcasecmp(ctcp[0], "TYPING") == 0) {
536                if (iu->bu && iu->bu->ic && iu->bu->ic->acc->prpl->send_typing && ctcp[1]) {
537                        int st = ctcp[1][0];
538                        if (st >= '0' && st <= '2') {
539                                st <<= 8;
540                                iu->bu->ic->acc->prpl->send_typing(iu->bu->ic, iu->bu->handle, st);
541                        }
542
543                        return TRUE;
544                }
545        } else if (g_strcasecmp(ctcp[0], "HELP") == 0 && iu->bu) {
546                GString *supp = g_string_new("Supported CTCPs:");
547                GList *l;
548
549                if (iu->bu->ic && iu->bu->ic->acc->prpl->transfer_request) {
550                        g_string_append(supp, " DCC SEND,");
551                }
552                if (iu->bu->ic && iu->bu->ic->acc->prpl->send_typing) {
553                        g_string_append(supp, " TYPING,");
554                }
555                if (iu->bu->ic->acc->prpl->buddy_action_list) {
556                        for (l = iu->bu->ic->acc->prpl->buddy_action_list(iu->bu); l; l = l->next) {
557                                struct buddy_action *ba = l->data;
558                                g_string_append_printf(supp, " %s (%s),",
559                                                       ba->name, ba->description);
560                        }
561                }
562                g_string_truncate(supp, supp->len - 1);
563                irc_send_msg_f(iu, "NOTICE", iu->irc->user->nick, "\001HELP %s\001", supp->str);
564                g_string_free(supp, TRUE);
565        } else if (iu->bu && iu->bu->ic && iu->bu->ic->acc->prpl->buddy_action) {
566                iu->bu->ic->acc->prpl->buddy_action(iu->bu, ctcp[0], ctcp + 1, NULL);
567        }
568
569        return FALSE;
570}
571
572static const struct irc_user_funcs irc_user_im_funcs = {
573        bee_irc_user_privmsg,
574        bee_irc_user_ctcp,
575};
576
577
578/* IM->IRC: Groupchats */
579const struct irc_channel_funcs irc_channel_im_chat_funcs;
580
581static gboolean bee_irc_chat_new(bee_t *bee, struct groupchat *c)
582{
583        irc_t *irc = bee->ui_data;
584        irc_channel_t *ic;
585        char *topic;
586        GSList *l;
587        int i;
588
589        /* Try to find a channel that expects to receive a groupchat.
590           This flag is set earlier in our current call trace. */
591        for (l = irc->channels; l; l = l->next) {
592                ic = l->data;
593                if (ic->flags & IRC_CHANNEL_CHAT_PICKME) {
594                        break;
595                }
596        }
597
598        /* If we found none, just generate some stupid name. */
599        if (l == NULL) {
600                for (i = 0; i <= 999; i++) {
601                        char name[16];
602                        sprintf(name, "#chat_%03d", i);
603                        if ((ic = irc_channel_new(irc, name))) {
604                                break;
605                        }
606                }
607        }
608
609        if (ic == NULL) {
610                return FALSE;
611        }
612
613        c->ui_data = ic;
614        ic->data = c;
615
616        topic = g_strdup_printf(
617                "BitlBee groupchat: \"%s\". Please keep in mind that root-commands won't work here. Have fun!",
618                c->title);
619        irc_channel_set_topic(ic, topic, irc->root);
620        g_free(topic);
621
622        return TRUE;
623}
624
625static gboolean bee_irc_chat_free(bee_t *bee, struct groupchat *c)
626{
627        irc_channel_t *ic = c->ui_data;
628
629        if (ic == NULL) {
630                return FALSE;
631        }
632
633        if (ic->flags & IRC_CHANNEL_JOINED) {
634                irc_channel_printf(ic, "Cleaning up channel, bye!");
635        }
636
637        ic->data = NULL;
638        c->ui_data = NULL;
639        irc_channel_del_user(ic, ic->irc->user, IRC_CDU_KICK, "Chatroom closed by server");
640
641        return TRUE;
642}
643
644static gboolean bee_irc_chat_log(bee_t *bee, struct groupchat *c, const char *text)
645{
646        irc_channel_t *ic = c->ui_data;
647
648        if (ic == NULL) {
649                return FALSE;
650        }
651
652        irc_channel_printf(ic, "%s", text);
653
654        return TRUE;
655}
656
657static gboolean bee_irc_chat_msg(bee_t *bee, struct groupchat *c, bee_user_t *bu, const char *msg, guint32 flags, time_t sent_at)
658{
659        irc_t *irc = bee->ui_data;
660        irc_user_t *iu = flags & OPT_SELFMESSAGE ? irc->user : bu->ui_data;
661        irc_channel_t *ic = c->ui_data;
662        char *wrapped, *ts = NULL;
663
664        if (ic == NULL) {
665                return FALSE;
666        }
667
668        if (sent_at > 0 && set_getbool(&bee->set, "display_timestamps")) {
669                ts = irc_format_timestamp(irc, sent_at);
670        }
671
672        wrapped = word_wrap(msg, 425);
673        irc_send_msg(iu, "PRIVMSG", ic->name, wrapped, ts);
674        g_free(ts);
675        g_free(wrapped);
676
677        return TRUE;
678}
679
680static gboolean bee_irc_chat_add_user(bee_t *bee, struct groupchat *c, bee_user_t *bu)
681{
682        irc_t *irc = bee->ui_data;
683        irc_channel_t *ic = c->ui_data;
684
685        if (ic == NULL) {
686                return FALSE;
687        }
688
689        irc_channel_add_user(ic, bu == bee->user ? irc->user : bu->ui_data);
690
691        return TRUE;
692}
693
694static gboolean bee_irc_chat_remove_user(bee_t *bee, struct groupchat *c, bee_user_t *bu, const char *reason)
695{
696        irc_t *irc = bee->ui_data;
697        irc_channel_t *ic = c->ui_data;
698
699        if (ic == NULL || bu == NULL) {
700                return FALSE;
701        }
702
703        /* TODO: Possible bug here: If a module removes $user here instead of just
704           using imcb_chat_free() and the channel was IRC_CHANNEL_TEMP, we get into
705           a broken state around here. */
706        irc_channel_del_user(ic, bu == bee->user ? irc->user : bu->ui_data, IRC_CDU_PART, reason);
707
708        return TRUE;
709}
710
711static gboolean bee_irc_chat_topic(bee_t *bee, struct groupchat *c, const char *new, bee_user_t *bu)
712{
713        irc_channel_t *ic = c->ui_data;
714        irc_t *irc = bee->ui_data;
715        irc_user_t *iu;
716
717        if (ic == NULL) {
718                return FALSE;
719        }
720
721        if (bu == NULL) {
722                iu = irc->root;
723        } else if (bu == bee->user) {
724                iu = irc->user;
725        } else {
726                iu = bu->ui_data;
727        }
728
729        irc_channel_set_topic(ic, new, iu);
730
731        return TRUE;
732}
733
734static gboolean bee_irc_chat_name_hint(bee_t *bee, struct groupchat *c, const char *name)
735{
736        return irc_channel_name_hint(c->ui_data, name);
737}
738
739static gboolean bee_irc_chat_invite(bee_t *bee, bee_user_t *bu, const char *name, const char *msg)
740{
741        char *channel, *s;
742        irc_t *irc = bee->ui_data;
743        irc_user_t *iu = bu->ui_data;
744        irc_channel_t *chan;
745
746        if (strchr(CTYPES, name[0])) {
747                channel = g_strdup(name);
748        } else {
749                channel = g_strdup_printf("#%s", name);
750        }
751
752        if ((s = strchr(channel, '@'))) {
753                *s = '\0';
754        }
755
756        if (strlen(channel) > MAX_NICK_LENGTH) {
757                /* If the channel name is very long (like those insane GTalk
758                   UUID names), try if we can use the inviter's nick. */
759                s = g_strdup_printf("#%s", iu->nick);
760                if (irc_channel_by_name(irc, s) == NULL) {
761                        g_free(channel);
762                        channel = s;
763                } else {
764                        g_free(s);
765                }
766        }
767
768        if ((chan = irc_channel_new(irc, channel)) &&
769            set_setstr(&chan->set, "type", "chat") &&
770            set_setstr(&chan->set, "chat_type", "room") &&
771            set_setstr(&chan->set, "account", bu->ic->acc->tag) &&
772            set_setstr(&chan->set, "room", (char *) name)) {
773                /* I'm assuming that if the user didn't "chat add" the room
774                   himself but got invited, it's temporary, so make this a
775                   temporary mapping that is removed as soon as we /PART. */
776                chan->flags |= IRC_CHANNEL_TEMP;
777        } else {
778                irc_channel_free(chan);
779                chan = NULL;
780        }
781        g_free(channel);
782
783        irc_send_msg_f(iu, "PRIVMSG", irc->user->nick, "<< \002BitlBee\002 - Invitation to chatroom %s >>", name);
784        if (msg) {
785                irc_send_msg(iu, "PRIVMSG", irc->user->nick, msg, NULL);
786        }
787        if (chan) {
788                irc_send_msg_f(iu, "PRIVMSG", irc->user->nick, "To join the room, just /join %s", chan->name);
789                irc_send_invite(iu, chan);
790        }
791
792        return TRUE;
793}
794
795/* IRC->IM */
796static gboolean bee_irc_channel_chat_privmsg_cb(gpointer data, gint fd, b_input_condition cond);
797
798static gboolean bee_irc_channel_chat_privmsg(irc_channel_t *ic, const char *msg)
799{
800        struct groupchat *c = ic->data;
801        char *trans = NULL, *s;
802
803        if (c == NULL) {
804                return FALSE;
805        }
806
807        if (set_getbool(&ic->set, "translate_to_nicks")) {
808                char nick[MAX_NICK_LENGTH + 1];
809                irc_user_t *iu;
810
811                strncpy(nick, msg, MAX_NICK_LENGTH);
812                nick[MAX_NICK_LENGTH] = '\0';
813                if ((s = strchr(nick, ':')) || (s = strchr(nick, ','))) {
814                        *s = '\0';
815                        if ((iu = irc_user_by_name(ic->irc, nick)) && iu->bu &&
816                            iu->bu->nick && irc_channel_has_user(ic, iu)) {
817                                trans = g_strconcat(iu->bu->nick, msg + (s - nick), NULL);
818                                msg = trans;
819                        }
820                }
821        }
822
823        if (set_getbool(&ic->irc->b->set, "paste_buffer")) {
824                int delay;
825
826                if (ic->pastebuf == NULL) {
827                        ic->pastebuf = g_string_new(msg);
828                } else {
829                        b_event_remove(ic->pastebuf_timer);
830                        g_string_append_printf(ic->pastebuf, "\n%s", msg);
831                }
832
833                if ((delay = set_getint(&ic->irc->b->set, "paste_buffer_delay")) <= 5) {
834                        delay *= 1000;
835                }
836
837                ic->pastebuf_timer = b_timeout_add(delay, bee_irc_channel_chat_privmsg_cb, ic);
838
839                g_free(trans);
840                return TRUE;
841        } else {
842                bee_chat_msg(ic->irc->b, c, msg, 0);
843        }
844
845        g_free(trans);
846        return TRUE;
847}
848
849static gboolean bee_irc_channel_chat_privmsg_cb(gpointer data, gint fd, b_input_condition cond)
850{
851        irc_channel_t *ic = data;
852
853        if (ic->data) {
854                bee_chat_msg(ic->irc->b, ic->data, ic->pastebuf->str, 0);
855        }
856
857        g_string_free(ic->pastebuf, TRUE);
858        ic->pastebuf = 0;
859        ic->pastebuf_timer = 0;
860
861        return FALSE;
862}
863
864static gboolean bee_irc_channel_chat_join(irc_channel_t *ic)
865{
866        char *acc_s, *room;
867        account_t *acc;
868
869        if (strcmp(set_getstr(&ic->set, "chat_type"), "room") != 0) {
870                return TRUE;
871        }
872
873        if ((acc_s = set_getstr(&ic->set, "account")) &&
874            (room = set_getstr(&ic->set, "room")) &&
875            (acc = account_get(ic->irc->b, acc_s)) &&
876            acc->ic && (acc->ic->flags & OPT_LOGGED_IN) &&
877            acc->prpl->chat_join) {
878                char *nick;
879
880                if (!(nick = set_getstr(&ic->set, "nick"))) {
881                        nick = ic->irc->user->nick;
882                }
883
884                ic->flags |= IRC_CHANNEL_CHAT_PICKME;
885                acc->prpl->chat_join(acc->ic, room, nick, NULL, &ic->set);
886                ic->flags &= ~IRC_CHANNEL_CHAT_PICKME;
887
888                return FALSE;
889        } else {
890                irc_send_num(ic->irc, 403, "%s :Can't join channel, account offline?", ic->name);
891                return FALSE;
892        }
893}
894
895static gboolean bee_irc_channel_chat_part(irc_channel_t *ic, const char *msg)
896{
897        struct groupchat *c = ic->data;
898
899        if (c && c->ic->acc->prpl->chat_leave) {
900                c->ic->acc->prpl->chat_leave(c);
901        }
902
903        if (!(ic->flags & IRC_CHANNEL_TEMP)) {
904                /* Remove the reference.
905                 * We only need it for temp channels that are being freed */
906                ic->data = NULL;
907        }
908
909        return TRUE;
910}
911
912static gboolean bee_irc_channel_chat_topic(irc_channel_t *ic, const char *new)
913{
914        struct groupchat *c = ic->data;
915
916        if (c == NULL) {
917                return FALSE;
918        }
919
920        if (c->ic->acc->prpl->chat_topic == NULL) {
921                irc_send_num(ic->irc, 482, "%s :IM network does not support channel topics", ic->name);
922        } else {
923                /* TODO: Need more const goodness here, sigh */
924                char *topic = g_strdup(new);
925                c->ic->acc->prpl->chat_topic(c, topic);
926                g_free(topic);
927        }
928
929        /* Whatever happened, the IM module should ack the topic change. */
930        return FALSE;
931}
932
933static gboolean bee_irc_channel_chat_invite(irc_channel_t *ic, irc_user_t *iu)
934{
935        struct groupchat *c = ic->data;
936        bee_user_t *bu = iu->bu;
937
938        if (bu == NULL) {
939                return FALSE;
940        }
941
942        if (c) {
943                if (iu->bu->ic != c->ic) {
944                        irc_send_num(ic->irc, 482, "%s :Can't mix different IM networks in one groupchat", ic->name);
945                } else if (c->ic->acc->prpl->chat_invite) {
946                        c->ic->acc->prpl->chat_invite(c, iu->bu->handle, NULL);
947                } else {
948                        irc_send_num(ic->irc, 482, "%s :IM protocol does not support room invitations", ic->name);
949                }
950        } else if (bu->ic->acc->prpl->chat_with &&
951                   strcmp(set_getstr(&ic->set, "chat_type"), "groupchat") == 0) {
952                ic->flags |= IRC_CHANNEL_CHAT_PICKME;
953                iu->bu->ic->acc->prpl->chat_with(bu->ic, bu->handle);
954                ic->flags &= ~IRC_CHANNEL_CHAT_PICKME;
955        } else {
956                irc_send_num(ic->irc, 482, "%s :IM protocol does not support room invitations", ic->name);
957        }
958
959        return TRUE;
960}
961
962static void bee_irc_channel_chat_kick(irc_channel_t *ic, irc_user_t *iu, const char *msg)
963{
964        struct groupchat *c = ic->data;
965        bee_user_t *bu = iu->bu;
966
967        if ((c == NULL) || (bu == NULL)) {
968                return;
969        }
970
971        if (!c->ic->acc->prpl->chat_kick) {
972                irc_send_num(ic->irc, 482, "%s :IM protocol does not support room kicking", ic->name);
973                return;
974        }
975
976        c->ic->acc->prpl->chat_kick(c, iu->bu->handle, msg);
977}
978
979static char *set_eval_room_account(set_t *set, char *value);
980static char *set_eval_chat_type(set_t *set, char *value);
981
982static gboolean bee_irc_channel_init(irc_channel_t *ic)
983{
984        set_t *s;
985
986        set_add(&ic->set, "account", NULL, set_eval_room_account, ic);
987        set_add(&ic->set, "chat_type", "groupchat", set_eval_chat_type, ic);
988
989        s = set_add(&ic->set, "nick", NULL, NULL, ic);
990        s->flags |= SET_NULL_OK;
991
992        set_add(&ic->set, "room", NULL, NULL, ic);
993        set_add(&ic->set, "translate_to_nicks", "true", set_eval_bool, ic);
994
995        /* chat_type == groupchat */
996        ic->flags |= IRC_CHANNEL_TEMP;
997
998        return TRUE;
999}
1000
1001static char *set_eval_room_account(set_t *set, char *value)
1002{
1003        struct irc_channel *ic = set->data;
1004        account_t *acc, *oa;
1005
1006        if (!(acc = account_get(ic->irc->b, value))) {
1007                return SET_INVALID;
1008        } else if (!acc->prpl->chat_join) {
1009                irc_rootmsg(ic->irc, "Named chatrooms not supported on that account.");
1010                return SET_INVALID;
1011        }
1012
1013        if (set->value && (oa = account_get(ic->irc->b, set->value)) &&
1014            oa->prpl->chat_free_settings) {
1015                oa->prpl->chat_free_settings(oa, &ic->set);
1016        }
1017
1018        if (acc->prpl->chat_add_settings) {
1019                acc->prpl->chat_add_settings(acc, &ic->set);
1020        }
1021
1022        return g_strdup(acc->tag);
1023}
1024
1025static char *set_eval_chat_type(set_t *set, char *value)
1026{
1027        struct irc_channel *ic = set->data;
1028
1029        if (strcmp(value, "groupchat") == 0) {
1030                ic->flags |= IRC_CHANNEL_TEMP;
1031        } else if (strcmp(value, "room") == 0) {
1032                ic->flags &= ~IRC_CHANNEL_TEMP;
1033        } else {
1034                return NULL;
1035        }
1036
1037        return value;
1038}
1039
1040static gboolean bee_irc_channel_free(irc_channel_t *ic)
1041{
1042        struct groupchat *c = ic->data;
1043
1044        set_del(&ic->set, "account");
1045        set_del(&ic->set, "chat_type");
1046        set_del(&ic->set, "nick");
1047        set_del(&ic->set, "room");
1048        set_del(&ic->set, "translate_to_nicks");
1049
1050        ic->flags &= ~IRC_CHANNEL_TEMP;
1051
1052        /* That one still points at this channel. Don't. */
1053        if (c) {
1054                c->ui_data = NULL;
1055        }
1056
1057        return TRUE;
1058}
1059
1060const struct irc_channel_funcs irc_channel_im_chat_funcs = {
1061        bee_irc_channel_chat_privmsg,
1062        bee_irc_channel_chat_join,
1063        bee_irc_channel_chat_part,
1064        bee_irc_channel_chat_topic,
1065        bee_irc_channel_chat_invite,
1066        bee_irc_channel_chat_kick,
1067
1068        bee_irc_channel_init,
1069        bee_irc_channel_free,
1070};
1071
1072
1073/* IM->IRC: File transfers */
1074static file_transfer_t *bee_irc_ft_in_start(bee_t *bee, bee_user_t *bu, const char *file_name, size_t file_size)
1075{
1076        return dccs_send_start(bu->ic, (irc_user_t *) bu->ui_data, file_name, file_size);
1077}
1078
1079static gboolean bee_irc_ft_out_start(struct im_connection *ic, file_transfer_t *ft)
1080{
1081        return dccs_recv_start(ft);
1082}
1083
1084static void bee_irc_ft_close(struct im_connection *ic, file_transfer_t *ft)
1085{
1086        return dcc_close(ft);
1087}
1088
1089static void bee_irc_ft_finished(struct im_connection *ic, file_transfer_t *file)
1090{
1091        dcc_file_transfer_t *df = file->priv;
1092
1093        if (file->bytes_transferred >= file->file_size) {
1094                dcc_finish(file);
1095        } else {
1096                df->proto_finished = TRUE;
1097        }
1098}
1099
1100const struct bee_ui_funcs irc_ui_funcs = {
1101        bee_irc_imc_connected,
1102        bee_irc_imc_disconnected,
1103
1104        bee_irc_user_new,
1105        bee_irc_user_free,
1106        bee_irc_user_fullname,
1107        bee_irc_user_nick_hint,
1108        bee_irc_user_group,
1109        bee_irc_user_status,
1110        bee_irc_user_msg,
1111        bee_irc_user_typing,
1112        bee_irc_user_action_response,
1113
1114        bee_irc_chat_new,
1115        bee_irc_chat_free,
1116        bee_irc_chat_log,
1117        bee_irc_chat_msg,
1118        bee_irc_chat_add_user,
1119        bee_irc_chat_remove_user,
1120        bee_irc_chat_topic,
1121        bee_irc_chat_name_hint,
1122        bee_irc_chat_invite,
1123
1124        bee_irc_ft_in_start,
1125        bee_irc_ft_out_start,
1126        bee_irc_ft_close,
1127        bee_irc_ft_finished,
1128};
Note: See TracBrowser for help on using the repository browser.