source: irc_im.c @ 830864d

Last change on this file since 830864d was 830864d, checked in by dequis <dx@…>, at 2015-03-15T09:31:18Z

WIP placeholder channels with hipchat implementation

i was going to clean this up and split in two commits but uhhh...
maybe some other day, i'm tired now

not very tested and i'm not 100% happy about the design, but sucks way
less than what i had in the hip-cat branch

feedback still appreciated.

this adds channels to the channel list without creating groupchats for
them, allowing users to /join them. what the hip-cat branch did before
but with proper api and hopefully less dumb behavior

it still 'leaks' them intentionally, just like it did before, but now it
prevents saving them to the xml so yay

also slightly improved channel name generation, refactored
bee_irc_chat_name_hint into three or four functions, and so on

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