source: irc_im.c @ 1c7b646

Last change on this file since 1c7b646 was 8167346, checked in by dequis <dx@…>, at 2018-03-11T19:28:38Z

Try to join long spaceless lines in paste_buffer without a newline

Fixes trac ticket 1302

The main use case for this is pasting long URLs and not breaking them

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