source: irc_im.c @ af359b4

Last change on this file since af359b4 was 7821ee8, checked in by jgeboski <jgeboski@…>, at 2015-01-29T19:24:17Z

irc_commands: implemented KICK support

With similar commands being supported, such as INVITE, the KICK command
should be supported as well. The key motivation behind supporting KICK
is having for having a way to remove users from group chats. As of now,
there is no way for a bitlbee user to remove a user from a group chat.
With no current KICK implementation, it made using this command a prime
candidate for the UI side of this implementation. In addition, the KICK
command has been supported in the control channel as well. This is to
keep the INVITE/KICK pair consistent.

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