source: irc_im.c @ c82e4ca

Last change on this file since c82e4ca was 5c90890, checked in by dequis <dx@…>, at 2018-07-12T08:54:12Z

Stop using the irc->users linked list, use the hash table instead

irc_user_new() mentions that the reason this list is kept is for easy
iteration. Luckily, this is the future, and GHashTableIter exists now.

The main point of this is to get rid of the g_slist_insert_sorted() in
irc_user_set_nick() which is a particularly slow part of loading large
user lists, and scales poorly

In a test with discord, the GUILD_SYNC event is now 4 times faster, on
top of the improvements of the other bee_user hash tables patch.
Combining both patches it went from 136 to 6 seconds for 50k members.

  • Property mode set to 100644
File size: 28.8 KB
Line 
1/********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2012 Wilmer van der Gaast and others                *
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;
22  if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
23  Fifth Floor, Boston, MA  02110-1301  USA
24*/
25
26#include "bitlbee.h"
27#include "dcc.h"
28
29/* IM->IRC callbacks: Simple IM/buddy-related stuff. */
30
31static const struct irc_user_funcs irc_user_im_funcs;
32
33static void bee_irc_imc_connected(struct im_connection *ic)
34{
35        irc_t *irc = (irc_t *) ic->bee->ui_data;
36
37        irc_channel_auto_joins(irc, ic->acc);
38}
39
40static void bee_irc_imc_disconnected(struct im_connection *ic)
41{
42        /* Maybe try to send /QUITs here instead of later on. */
43}
44
45static gboolean bee_irc_user_new(bee_t *bee, bee_user_t *bu)
46{
47        irc_user_t *iu;
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);
52        strncpy(nick, nick_get(bu), MAX_NICK_LENGTH);
53
54        bu->ui_data = iu = irc_user_new(irc, nick);
55        iu->bu = bu;
56
57        if (set_getbool(&irc->b->set, "private")) {
58                iu->last_channel = NULL;
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 {
71                        char *s;
72                        for (s = bu->ic->acc->tag; g_ascii_isalnum(*s); s++) {
73                                ;
74                        }
75                        /* Only use the tag if we got to the end of the string.
76                           (So allow alphanumerics only. Hopefully not too
77                           restrictive.) */
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                        }
83                }
84        }
85
86        /* Sanitize */
87        str_reject_chars(iu->user, " ", '_');
88        str_reject_chars(iu->host, " ", '_');
89
90        if (bu->flags & BEE_USER_LOCAL) {
91                char *s = set_getstr(&bu->ic->acc->set, "handle_unknown") ? :
92                          set_getstr(&bee->set, "handle_unknown");
93
94                if (g_strcasecmp(s, "add_private") == 0) {
95                        iu->last_channel = NULL;
96                } else if (g_strcasecmp(s, "add_channel") == 0) {
97                        iu->last_channel = irc->default_channel;
98                }
99        }
100
101        iu->f = &irc_user_im_funcs;
102
103        return TRUE;
104}
105
106static gboolean bee_irc_user_free(bee_t *bee, bee_user_t *bu)
107{
108        return irc_user_free(bee->ui_data, (irc_user_t *) bu->ui_data);
109}
110
111static gboolean bee_irc_user_status(bee_t *bee, bee_user_t *bu, bee_user_t *old)
112{
113        irc_t *irc = bee->ui_data;
114        irc_user_t *iu = bu->ui_data;
115
116        /* Do this outside the if below since away state can change without
117           the online state changing. */
118        iu->flags &= ~IRC_USER_AWAY;
119        if (bu->flags & BEE_USER_AWAY || !(bu->flags & BEE_USER_ONLINE)) {
120                iu->flags |= IRC_USER_AWAY;
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
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. */
140                        if (set_getbool(&bee->set, "offline_user_quits")) {
141                                irc_user_quit(iu, "Leaving...");
142                        }
143                }
144        }
145
146        /* Reset this one since the info may have changed. */
147        iu->away_reply_timeout = 0;
148
149        bee_irc_channel_update(irc, NULL, iu);
150
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         */
156        if ((irc->caps & CAP_AWAY_NOTIFY) &&
157            ((bu->flags & BEE_USER_AWAY) != (old->flags & BEE_USER_AWAY) ||
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))) {
161                irc_send_away_notify(iu);
162        }
163
164        return TRUE;
165}
166
167void bee_irc_channel_update(irc_t *irc, irc_channel_t *ic, irc_user_t *iu)
168{
169        GSList *l;
170
171        if (ic == NULL) {
172                for (l = irc->channels; l; l = l->next) {
173                        ic = l->data;
174                        /* TODO: Just add a type flag or so.. */
175                        if (ic->f == irc->default_channel->f &&
176                            (ic->flags & IRC_CHANNEL_JOINED)) {
177                                bee_irc_channel_update(irc, ic, iu);
178                        }
179                }
180                return;
181        }
182        if (iu == NULL) {
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;
189                        if (iu->bu) {
190                                bee_irc_channel_update(irc, ic, iu);
191                        }
192                }
193                return;
194        }
195
196        if (!irc_channel_wants_user(ic, iu)) {
197                irc_channel_del_user(ic, iu, IRC_CDU_PART, NULL);
198        } else {
199                struct irc_control_channel *icc = ic->data;
200                int mode = 0;
201
202                if (!(iu->bu->flags & BEE_USER_ONLINE)) {
203                        mode = icc->modes[0];
204                } else if (iu->bu->flags & BEE_USER_AWAY) {
205                        mode = icc->modes[1];
206                } else if (iu->bu->flags & BEE_USER_SPECIAL) {
207                        mode = icc->modes[2];
208                } else {
209                        mode = icc->modes[3];
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);
217                }
218        }
219}
220
221static gboolean bee_irc_user_msg(bee_t *bee, bee_user_t *bu, const char *msg_, guint32 flags, time_t sent_at)
222{
223        irc_t *irc = bee->ui_data;
224        irc_user_t *iu = (irc_user_t *) bu->ui_data;
225        irc_user_t *src_iu = iu;
226        irc_user_t *dst_iu = irc->user;
227        const char *dst;
228        char *prefix = NULL;
229        char *wrapped, *ts = NULL;
230        char *msg = g_strdup(msg_);
231        char *message_type = "PRIVMSG";
232        GSList *l;
233
234        if (sent_at > 0 && set_getbool(&irc->b->set, "display_timestamps")) {
235                ts = irc_format_timestamp(irc, sent_at);
236        }
237
238        dst = irc_user_msgdest(iu);
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 ? : "");
277        } else {
278                prefix = ts;
279                ts = NULL;      /* don't double-free */
280        }
281
282        for (l = irc_plugins; l; l = l->next) {
283                irc_plugin_t *p = l->data;
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                                }
290                                msg = s;
291                        } else {
292                                /* Modules can swallow messages. */
293                                goto cleanup;
294                        }
295                }
296        }
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);
303                msg = s;
304        }
305
306        wrapped = word_wrap(msg, IRC_WORD_WRAP);
307        irc_send_msg(src_iu, message_type, dst, wrapped, prefix);
308        g_free(wrapped);
309
310cleanup:
311        g_free(prefix);
312        g_free(msg);
313        g_free(ts);
314
315        return TRUE;
316}
317
318static gboolean bee_irc_user_typing(bee_t *bee, bee_user_t *bu, guint32 flags)
319{
320        irc_t *irc = (irc_t *) bee->ui_data;
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 {
326                return FALSE;
327        }
328
329        return TRUE;
330}
331
332static gboolean bee_irc_user_action_response(bee_t *bee, bee_user_t *bu, const char *action, char * const args[],
333                                             void *data)
334{
335        irc_t *irc = (irc_t *) bee->ui_data;
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
349        irc_send_msg((irc_user_t *) bu->ui_data, "NOTICE", irc->user->nick, msg->str, NULL);
350
351        g_string_free(msg, TRUE);
352
353        return TRUE;
354}
355
356static gboolean bee_irc_user_nick_update(irc_user_t *iu, gboolean offline_only);
357
358static gboolean bee_irc_user_fullname(bee_t *bee, bee_user_t *bu)
359{
360        irc_user_t *iu = (irc_user_t *) bu->ui_data;
361        char *s;
362
363        if (iu->fullname != iu->nick) {
364                g_free(iu->fullname);
365        }
366        iu->fullname = g_strdup(bu->fullname);
367
368        /* Strip newlines (unlikely, but IRC-unfriendly so they must go)
369           TODO(wilmer): Do the same with away msgs again! */
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")) {
377                /* People don't like this /NOTICE. Meh, let's go back to the old one.
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 );
380                */
381                imcb_log(bu->ic, "User `%s' changed name to `%s'", iu->nick, iu->fullname);
382        }
383
384        bee_irc_user_nick_update(iu, TRUE);
385
386        return TRUE;
387}
388
389static gboolean bee_irc_user_nick_hint(bee_t *bee, bee_user_t *bu, const char *hint)
390{
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);
399
400        return TRUE;
401}
402
403static gboolean bee_irc_user_group(bee_t *bee, bee_user_t *bu)
404{
405        irc_user_t *iu = (irc_user_t *) bu->ui_data;
406        irc_t *irc = (irc_t *) bee->ui_data;
407
408        bee_irc_channel_update(irc, NULL, iu);
409        bee_irc_user_nick_update(iu, FALSE);
410
411        return TRUE;
412}
413
414static gboolean bee_irc_user_nick_update(irc_user_t *iu, gboolean offline_only)
415{
416        bee_user_t *bu = iu->bu;
417        char *newnick;
418
419        if (offline_only && bu->flags & BEE_USER_ONLINE) {
420                /* Ignore if the user is visible already. */
421                return TRUE;
422        }
423
424        if (nick_saved(bu)) {
425                /* The user already assigned a nickname to this person. */
426                return TRUE;
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
436        return TRUE;
437}
438
439void bee_irc_user_nick_reset(irc_user_t *iu)
440{
441        bee_user_t *bu = iu->bu;
442
443        if (bu == FALSE) {
444                return;
445        }
446
447        nick_del(bu);
448        bee_irc_user_nick_update(iu, FALSE);
449
450}
451
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
481/* IRC->IM calls */
482
483static gboolean bee_irc_user_privmsg_cb(gpointer data, gint fd, b_input_condition cond);
484
485static gboolean bee_irc_user_privmsg(irc_user_t *iu, const char *msg)
486{
487        const char *away;
488
489        if (iu->bu == NULL) {
490                return FALSE;
491        }
492
493        if (iu->last_channel == NULL &&
494            (away = irc_user_get_away(iu)) &&
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);
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);
509        }
510
511        if (set_getbool(&iu->irc->b->set, "paste_buffer")) {
512                int delay;
513
514                if ((delay = set_getint(&iu->irc->b->set, "paste_buffer_delay")) <= 5) {
515                        delay *= 1000;
516                }
517
518                iu->pastebuf_timer = b_timeout_add(delay, bee_irc_user_privmsg_cb, iu);
519
520                return TRUE;
521        } else {
522                bee_irc_user_privmsg_cb(iu, 0, 0);
523
524                return TRUE;
525        }
526}
527
528static gboolean bee_irc_user_privmsg_cb(gpointer data, gint fd, b_input_condition cond)
529{
530        irc_user_t *iu = data;
531        char *msg;
532        GSList *l;
533
534        msg = g_string_free(iu->pastebuf, FALSE);
535        iu->pastebuf = NULL;
536        iu->pastebuf_timer = 0;
537
538        for (l = irc_plugins; l; l = l->next) {
539                irc_plugin_t *p = l->data;
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                                }
546                                msg = s;
547                        } else {
548                                /* Modules can swallow messages. */
549                                iu->pastebuf = NULL;
550                                g_free(msg);
551                                return FALSE;
552                        }
553                }
554        }
555
556        bee_user_msg(iu->irc->b, iu->bu, msg, 0);
557
558        g_free(msg);
559
560        return FALSE;
561}
562
563static gboolean bee_irc_user_ctcp(irc_user_t *iu, char *const *ctcp)
564{
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
573                        return TRUE;
574                }
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]) {
577                        int st = ctcp[1][0];
578                        if (st >= '0' && st <= '2') {
579                                st <<= 8;
580                                iu->bu->ic->acc->prpl->send_typing(iu->bu->ic, iu->bu->handle, st);
581                        }
582
583                        return TRUE;
584                }
585        } else if (g_strcasecmp(ctcp[0], "HELP") == 0 && iu->bu) {
586                GString *supp = g_string_new("Supported CTCPs:");
587                GList *l;
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) {
597                                struct buddy_action *ba = l->data;
598                                g_string_append_printf(supp, " %s (%s),",
599                                                       ba->name, ba->description);
600                        }
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);
607        }
608
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
617
618/* IM->IRC: Groupchats */
619const struct irc_channel_funcs irc_channel_im_chat_funcs;
620
621static gboolean bee_irc_chat_new(bee_t *bee, struct groupchat *c)
622{
623        irc_t *irc = bee->ui_data;
624        irc_channel_t *ic;
625        char *topic;
626        GSList *l;
627        int i;
628
629        /* Try to find a channel that expects to receive a groupchat.
630           This flag is set earlier in our current call trace. */
631        for (l = irc->channels; l; l = l->next) {
632                ic = l->data;
633                if (ic->flags & IRC_CHANNEL_CHAT_PICKME) {
634                        break;
635                }
636        }
637
638        /* If we found none, just generate some stupid name. */
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                }
647        }
648
649        if (ic == NULL) {
650                return FALSE;
651        }
652
653        c->ui_data = ic;
654        ic->data = c;
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
662        return TRUE;
663}
664
665static gboolean bee_irc_chat_free(bee_t *bee, struct groupchat *c)
666{
667        irc_channel_t *ic = c->ui_data;
668
669        if (ic == NULL) {
670                return FALSE;
671        }
672
673        ic->data = NULL;
674        c->ui_data = NULL;
675        irc_channel_del_user(ic, ic->irc->user, IRC_CDU_KICK, "Chatroom closed by server");
676
677        return TRUE;
678}
679
680static gboolean bee_irc_chat_log(bee_t *bee, struct groupchat *c, const char *text)
681{
682        irc_channel_t *ic = c->ui_data;
683
684        if (ic == NULL) {
685                return FALSE;
686        }
687
688        irc_channel_printf(ic, "%s", text);
689
690        return TRUE;
691}
692
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)
694{
695        irc_t *irc = bee->ui_data;
696        irc_user_t *iu = flags & OPT_SELFMESSAGE ? irc->user : bu->ui_data;
697        irc_channel_t *ic = c->ui_data;
698        char *wrapped, *ts = NULL;
699
700        if (ic == NULL) {
701                return FALSE;
702        }
703
704        if (sent_at > 0 && set_getbool(&bee->set, "display_timestamps")) {
705                ts = irc_format_timestamp(irc, sent_at);
706        }
707
708        wrapped = word_wrap(msg, IRC_WORD_WRAP);
709        irc_send_msg(iu, "PRIVMSG", ic->name, wrapped, ts);
710        g_free(ts);
711        g_free(wrapped);
712
713        return TRUE;
714}
715
716static gboolean bee_irc_chat_add_user(bee_t *bee, struct groupchat *c, bee_user_t *bu)
717{
718        irc_t *irc = bee->ui_data;
719        irc_channel_t *ic = c->ui_data;
720
721        if (ic == NULL) {
722                return FALSE;
723        }
724
725        irc_channel_add_user(ic, bu == bee->user ? irc->user : bu->ui_data);
726
727        return TRUE;
728}
729
730static gboolean bee_irc_chat_remove_user(bee_t *bee, struct groupchat *c, bee_user_t *bu, const char *reason)
731{
732        irc_t *irc = bee->ui_data;
733        irc_channel_t *ic = c->ui_data;
734
735        if (ic == NULL || bu == NULL) {
736                return FALSE;
737        }
738
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. */
742        irc_channel_del_user(ic, bu == bee->user ? irc->user : bu->ui_data, IRC_CDU_PART, reason);
743
744        return TRUE;
745}
746
747static gboolean bee_irc_chat_topic(bee_t *bee, struct groupchat *c, const char *new, bee_user_t *bu)
748{
749        irc_channel_t *ic = c->ui_data;
750        irc_t *irc = bee->ui_data;
751        irc_user_t *iu;
752
753        if (ic == NULL) {
754                return FALSE;
755        }
756
757        if (bu == NULL) {
758                iu = irc->root;
759        } else if (bu == bee->user) {
760                iu = irc->user;
761        } else {
762                iu = bu->ui_data;
763        }
764
765        irc_channel_set_topic(ic, new, iu);
766
767        return TRUE;
768}
769
770static gboolean bee_irc_chat_name_hint(bee_t *bee, struct groupchat *c, const char *name)
771{
772        return irc_channel_name_hint(c->ui_data, name);
773}
774
775static gboolean bee_irc_chat_invite(bee_t *bee, bee_user_t *bu, const char *name, const char *msg)
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;
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, '@'))) {
789                *s = '\0';
790        }
791
792        if (strlen(channel) > MAX_NICK_LENGTH) {
793                /* If the channel name is very long (like those insane GTalk
794                   UUID names), try if we can use the inviter's nick. */
795                s = g_strdup_printf("#%s", iu->nick);
796                if (irc_channel_by_name(irc, s) == NULL) {
797                        g_free(channel);
798                        channel = s;
799                } else {
800                        g_free(s);
801                }
802        }
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)) {
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;
813        } else {
814                irc_channel_free(chan);
815                chan = NULL;
816        }
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) {
821                irc_send_msg(iu, "PRIVMSG", irc->user->nick, msg, NULL);
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
828        return TRUE;
829}
830
831/* IRC->IM */
832static gboolean bee_irc_channel_chat_privmsg_cb(gpointer data, gint fd, b_input_condition cond);
833
834static gboolean bee_irc_channel_chat_privmsg(irc_channel_t *ic, const char *msg)
835{
836        struct groupchat *c = ic->data;
837        char *trans = NULL, *s;
838
839        if (c == NULL) {
840                return FALSE;
841        }
842
843        if (set_getbool(&ic->set, "translate_to_nicks")) {
844                char nick[MAX_NICK_LENGTH + 1];
845                irc_user_t *iu;
846
847                strncpy(nick, msg, MAX_NICK_LENGTH);
848                nick[MAX_NICK_LENGTH] = '\0';
849                if ((s = strchr(nick, ':')) || (s = strchr(nick, ','))) {
850                        *s = '\0';
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);
854                                msg = trans;
855                        }
856                }
857        }
858
859        if (set_getbool(&ic->irc->b->set, "paste_buffer")) {
860                int delay;
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);
867                }
868
869                if ((delay = set_getint(&ic->irc->b->set, "paste_buffer_delay")) <= 5) {
870                        delay *= 1000;
871                }
872
873                ic->pastebuf_timer = b_timeout_add(delay, bee_irc_channel_chat_privmsg_cb, ic);
874
875                g_free(trans);
876                return TRUE;
877        } else {
878                bee_chat_msg(ic->irc->b, c, msg, 0);
879        }
880
881        g_free(trans);
882        return TRUE;
883}
884
885static gboolean bee_irc_channel_chat_privmsg_cb(gpointer data, gint fd, b_input_condition cond)
886{
887        irc_channel_t *ic = data;
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);
894        ic->pastebuf = 0;
895        ic->pastebuf_timer = 0;
896
897        return FALSE;
898}
899
900static gboolean bee_irc_channel_chat_join(irc_channel_t *ic)
901{
902        char *acc_s, *room;
903        account_t *acc;
904
905        if (strcmp(set_getstr(&ic->set, "chat_type"), "room") != 0) {
906                return TRUE;
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)) &&
912            acc->ic && (acc->ic->flags & OPT_LOGGED_IN) &&
913            acc->prpl->chat_join) {
914                char *nick;
915                struct groupchat *gc;
916
917                if (!(nick = set_getstr(&ic->set, "nick"))) {
918                        nick = ic->irc->user->nick;
919                }
920
921                ic->flags |= IRC_CHANNEL_CHAT_PICKME;
922                gc = acc->prpl->chat_join(acc->ic, room, nick, NULL, &ic->set);
923                ic->flags &= ~IRC_CHANNEL_CHAT_PICKME;
924
925                if (!gc) {
926                        irc_send_num(ic->irc, 403, "%s :Error joining channel (check control channel?)", ic->name);
927                }
928
929                return FALSE;
930        } else {
931                irc_send_num(ic->irc, 403, "%s :Can't join channel, account offline?", ic->name);
932                return FALSE;
933        }
934}
935
936static gboolean bee_irc_channel_chat_part(irc_channel_t *ic, const char *msg)
937{
938        struct groupchat *c = ic->data;
939
940        if (c && c->ic->acc->prpl->chat_leave) {
941                c->ic->acc->prpl->chat_leave(c);
942        }
943
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        }
949
950        return TRUE;
951}
952
953static gboolean bee_irc_channel_chat_topic(irc_channel_t *ic, const char *new)
954{
955        struct groupchat *c = ic->data;
956
957        if (c == NULL) {
958                return FALSE;
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 {
964                /* TODO: Need more const goodness here, sigh */
965                char *topic = g_strdup(new);
966                c->ic->acc->prpl->chat_topic(c, topic);
967                g_free(topic);
968        }
969
970        /* Whatever happened, the IM module should ack the topic change. */
971        return FALSE;
972}
973
974static gboolean bee_irc_channel_chat_invite(irc_channel_t *ic, irc_user_t *iu)
975{
976        struct groupchat *c = ic->data;
977        bee_user_t *bu = iu->bu;
978
979        if (bu == NULL) {
980                return FALSE;
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) {
993                ic->flags |= IRC_CHANNEL_CHAT_PICKME;
994                iu->bu->ic->acc->prpl->chat_with(bu->ic, bu->handle);
995                ic->flags &= ~IRC_CHANNEL_CHAT_PICKME;
996        } else {
997                irc_send_num(ic->irc, 482, "%s :IM protocol does not support room invitations", ic->name);
998        }
999
1000        return TRUE;
1001}
1002
1003static void bee_irc_channel_chat_kick(irc_channel_t *ic, irc_user_t *iu, const char *msg)
1004{
1005        struct groupchat *c = ic->data;
1006        bee_user_t *bu = iu->bu;
1007
1008        if ((c == NULL) || (bu == NULL)) {
1009                return;
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);
1014                return;
1015        }
1016
1017        c->ic->acc->prpl->chat_kick(c, iu->bu->handle, msg);
1018}
1019
1020static char *set_eval_room_account(set_t *set, char *value);
1021static char *set_eval_chat_type(set_t *set, char *value);
1022
1023static gboolean bee_irc_channel_init(irc_channel_t *ic)
1024{
1025        set_t *s;
1026
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);
1029
1030        s = set_add(&ic->set, "nick", NULL, NULL, ic);
1031        s->flags |= SET_NULL_OK;
1032
1033        set_add(&ic->set, "room", NULL, NULL, ic);
1034        set_add(&ic->set, "translate_to_nicks", "true", set_eval_bool, ic);
1035
1036        /* chat_type == groupchat */
1037        ic->flags |= IRC_CHANNEL_TEMP;
1038
1039        return TRUE;
1040}
1041
1042static char *set_eval_room_account(set_t *set, char *value)
1043{
1044        struct irc_channel *ic = set->data;
1045        account_t *acc, *oa;
1046
1047        if (!(acc = account_get(ic->irc->b, value))) {
1048                return SET_INVALID;
1049        } else if (!acc->prpl->chat_join && acc->prpl != &protocol_missing) {
1050                irc_rootmsg(ic->irc, "Named chatrooms not supported on that account.");
1051                return SET_INVALID;
1052        }
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);
1064}
1065
1066static char *set_eval_chat_type(set_t *set, char *value)
1067{
1068        struct irc_channel *ic = set->data;
1069
1070        if (strcmp(value, "groupchat") == 0) {
1071                ic->flags |= IRC_CHANNEL_TEMP;
1072        } else if (strcmp(value, "room") == 0) {
1073                ic->flags &= ~IRC_CHANNEL_TEMP;
1074        } else {
1075                return NULL;
1076        }
1077
1078        return value;
1079}
1080
1081static gboolean bee_irc_channel_free(irc_channel_t *ic)
1082{
1083        struct groupchat *c = ic->data;
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
1091        ic->flags &= ~IRC_CHANNEL_TEMP;
1092
1093        /* That one still points at this channel. Don't. */
1094        if (c) {
1095                c->ui_data = NULL;
1096        }
1097
1098        return TRUE;
1099}
1100
1101const struct irc_channel_funcs irc_channel_im_chat_funcs = {
1102        bee_irc_channel_chat_privmsg,
1103        bee_irc_channel_chat_join,
1104        bee_irc_channel_chat_part,
1105        bee_irc_channel_chat_topic,
1106        bee_irc_channel_chat_invite,
1107        bee_irc_channel_chat_kick,
1108
1109        bee_irc_channel_init,
1110        bee_irc_channel_free,
1111};
1112
1113
1114/* IM->IRC: File transfers */
1115static file_transfer_t *bee_irc_ft_in_start(bee_t *bee, bee_user_t *bu, const char *file_name, size_t file_size)
1116{
1117        return dccs_send_start(bu->ic, (irc_user_t *) bu->ui_data, file_name, file_size);
1118}
1119
1120static gboolean bee_irc_ft_out_start(struct im_connection *ic, file_transfer_t *ft)
1121{
1122        return dccs_recv_start(ft);
1123}
1124
1125static void bee_irc_ft_close(struct im_connection *ic, file_transfer_t *ft)
1126{
1127        return dcc_close(ft);
1128}
1129
1130static void bee_irc_ft_finished(struct im_connection *ic, file_transfer_t *file)
1131{
1132        dcc_file_transfer_t *df = file->priv;
1133
1134        if (file->bytes_transferred >= file->file_size) {
1135                dcc_finish(file);
1136        } else {
1137                df->proto_finished = TRUE;
1138        }
1139}
1140
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
1148const struct bee_ui_funcs irc_ui_funcs = {
1149        bee_irc_imc_connected,
1150        bee_irc_imc_disconnected,
1151
1152        bee_irc_user_new,
1153        bee_irc_user_free,
1154        bee_irc_user_fullname,
1155        bee_irc_user_nick_hint,
1156        bee_irc_user_group,
1157        bee_irc_user_status,
1158        bee_irc_user_msg,
1159        bee_irc_user_typing,
1160        bee_irc_user_action_response,
1161
1162        bee_irc_chat_new,
1163        bee_irc_chat_free,
1164        bee_irc_chat_log,
1165        bee_irc_chat_msg,
1166        bee_irc_chat_add_user,
1167        bee_irc_chat_remove_user,
1168        bee_irc_chat_topic,
1169        bee_irc_chat_name_hint,
1170        bee_irc_chat_invite,
1171
1172        bee_irc_ft_in_start,
1173        bee_irc_ft_out_start,
1174        bee_irc_ft_close,
1175        bee_irc_ft_finished,
1176
1177        bee_irc_log,
1178        bee_irc_user_nick_change,
1179};
Note: See TracBrowser for help on using the repository browser.