source: irc_im.c @ 3b0c1c3

Last change on this file since 3b0c1c3 was fa1bc1d, checked in by dequis <dx@…>, at 2018-07-31T04:57:16Z

Hide timestamps from the message body if server-time is enabled

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