source: irc_im.c @ f7cc734

Last change on this file since f7cc734 was f7cc734, checked in by dequis <dx@…>, at 2018-07-31T04:41:25Z

irc: implement server-time capability

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