source: irc_im.c @ 5545c81

Last change on this file since 5545c81 was 61fc056, checked in by dequis <dx@…>, at 2018-08-26T20:56:09Z

irc_im: fix away_reply_timeout getting reset too often

The hangouts plugin regularly sends redundant user status updates, since
that's how it works. The 'bee_irc_user_status' has some checks to not do
things on redundant changes, but they apply to other parts of the
function, not to the line that sets 'iu->away_reply_timeout = 0;'

This commit moves a bunch of the checks used for CAP_AWAY_NOTIFY to the
top of the function so it returns early if nothing was changed. It also
simplifies the flags check to plain equality, which should be good
enough.

As a side effect of this change, bee_irc_channel_update() won't be
called redundantly anymore. That function iterates over all channels,
so maybe in some workflows this improves performance? I doubt it.
There's also a risk that the redundant calls were actually needed.
I have no way to determine this, so i'll let future-dx deal with it.

Bug reported by needo, offending line pointed out by digitalcircuit,
thanks both!

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