source: irc_im.c @ 80c2f3c

Last change on this file since 80c2f3c was 80c2f3c, checked in by dequis <dx@…>, at 2015-11-20T15:51:45Z

IRCv3 away-notify capability

Neat lightweight notifications of the awayness of contacts.

In practice, this means weechat/hexchat users can see away people in
their nick list and change show_users to 'online,special,away' to avoid
the mode spam completely.

These are also sent on online/offline changes, since offline_user_quits
can be turned off, and you'd need something when they come back.

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