source: irc_im.c @ 8167346

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

Try to join long spaceless lines in paste_buffer without a newline

Fixes trac ticket 1302

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

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