source: irc_im.c @ 242f280

Last change on this file since 242f280 was 4543356c, checked in by Shane Synan <digitalcircuit36939@…>, at 2016-02-04T08:17:46Z

Send away-notify after join/part from IRC channel

Move irc_send_away_notify below bee_irc_channel_update to delay
sending the updated away-notify status until after any nicks have
joined/quit. Otherwise, some IRC clients will drop the status
messages as they go to nicks that the client doesn't know about.

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