source: irc_im.c @ 7486853

Last change on this file since 7486853 was df291a6, checked in by dequis <dx@…>, at 2016-12-26T00:18:55Z

Remove "Cleaning up channel, bye!" message

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