source: protocols/twitter/twitter.c @ b0a89cc

Last change on this file since b0a89cc was 4ffd757, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-08-14T10:21:44Z

Don't send a /QUIT for every Twitter contact when going offline, and show
the twitter_$username /QUIT as a netsplit.

  • Property mode set to 100644
File size: 14.4 KB
RevLine 
[1b221e0]1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Simple module to facilitate twitter functionality.                       *
5*                                                                           *
6*  Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com>                 *
7*                                                                           *
8*  This library is free software; you can redistribute it and/or            *
9*  modify it under the terms of the GNU Lesser General Public               *
10*  License as published by the Free Software Foundation, version            *
11*  2.1.                                                                     *
12*                                                                           *
13*  This library is distributed in the hope that it will be useful,          *
14*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
15*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU        *
16*  Lesser General Public License for more details.                          *
17*                                                                           *
18*  You should have received a copy of the GNU Lesser General Public License *
19*  along with this library; if not, write to the Free Software Foundation,  *
20*  Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA           *
21*                                                                           *
22****************************************************************************/
23
24#include "nogaim.h"
[713d611]25#include "oauth.h"
[1b221e0]26#include "twitter.h"
27#include "twitter_http.h"
28#include "twitter_lib.h"
[bb5ce4d1]29#include "url.h"
[1b221e0]30
31/**
[62d2cfb]32 * Main loop function
33 */
[1b221e0]34gboolean twitter_main_loop(gpointer data, gint fd, b_input_condition cond)
35{
36        struct im_connection *ic = data;
[3e69802]37       
[1b221e0]38        // Check if we are still logged in...
[3bd4a93]39        if (!g_slist_find( twitter_connections, ic ))
[1b221e0]40                return 0;
41
42        // Do stuff..
43        twitter_get_home_timeline(ic, -1);
44
45        // If we are still logged in run this function again after timeout.
46        return (ic->flags & OPT_LOGGED_IN) == OPT_LOGGED_IN;
47}
48
[713d611]49static void twitter_main_loop_start( struct im_connection *ic )
50{
51        struct twitter_data *td = ic->proto_data;
52       
[d6aa6dd]53        imcb_log( ic, "Getting initial statuses" );
[713d611]54
55        // Run this once. After this queue the main loop function.
56        twitter_main_loop(ic, -1, 0);
57
58        // Queue the main_loop
59        // Save the return value, so we can remove the timeout on logout.
60        td->main_loop_id = b_timeout_add(60000, twitter_main_loop, ic);
61}
62
[d6aa6dd]63static void twitter_oauth_start( struct im_connection *ic );
64
65void twitter_login_finish( struct im_connection *ic )
66{
67        struct twitter_data *td = ic->proto_data;
68       
69        if( set_getbool( &ic->acc->set, "oauth" ) && !td->oauth_info )
70                twitter_oauth_start( ic );
71        else if( g_strcasecmp( set_getstr( &ic->acc->set, "mode" ), "one" ) != 0 &&
72                 !( td->flags & TWITTER_HAVE_FRIENDS ) )
73        {
74                imcb_log( ic, "Getting contact list" );
75                twitter_get_statuses_friends( ic, -1 );
76        }
77        else
78                twitter_main_loop_start( ic );
79}
[c2ecadc]80
[3b878a1]81static const struct oauth_service twitter_oauth =
[c2ecadc]82{
83        "http://api.twitter.com/oauth/request_token",
84        "http://api.twitter.com/oauth/access_token",
[c01bbd1]85        "https://api.twitter.com/oauth/authorize",
[c2ecadc]86        .consumer_key = "xsDNKJuNZYkZyMcu914uEA",
87        .consumer_secret = "FCxqcr0pXKzsF9ajmP57S3VQ8V6Drk4o2QYtqMcOszo",
88};
89
[18dbb20]90static gboolean twitter_oauth_callback( struct oauth_info *info );
[713d611]91
92static void twitter_oauth_start( struct im_connection *ic )
93{
[18dbb20]94        struct twitter_data *td = ic->proto_data;
95       
[c42e8b9]96        imcb_log( ic, "Requesting OAuth request token" );
97
[c2ecadc]98        td->oauth_info = oauth_request_token( &twitter_oauth, twitter_oauth_callback, ic );
[713d611]99}
100
[18dbb20]101static gboolean twitter_oauth_callback( struct oauth_info *info )
[713d611]102{
103        struct im_connection *ic = info->data;
[18dbb20]104        struct twitter_data *td;
[713d611]105       
[18dbb20]106        if( !g_slist_find( twitter_connections, ic ) )
107                return FALSE;
108       
109        td = ic->proto_data;
[c42e8b9]110        if( info->stage == OAUTH_REQUEST_TOKEN )
[713d611]111        {
112                char name[strlen(ic->acc->user)+9], *msg;
113               
[c42e8b9]114                if( info->request_token == NULL )
115                {
116                        imcb_error( ic, "OAuth error: %s", info->http->status_string );
117                        imc_logout( ic, TRUE );
[18dbb20]118                        return FALSE;
[c42e8b9]119                }
120               
[ffcdf13]121                sprintf( name, "%s_%s", td->prefix, ic->acc->user );
[713d611]122                msg = g_strdup_printf( "To finish OAuth authentication, please visit "
[c2ecadc]123                                       "%s and respond with the resulting PIN code.",
124                                       info->auth_url );
[713d611]125                imcb_buddy_msg( ic, name, msg, 0, 0 );
126                g_free( msg );
127        }
[c42e8b9]128        else if( info->stage == OAUTH_ACCESS_TOKEN )
129        {
[3b878a1]130                if( info->token == NULL || info->token_secret == NULL )
[c42e8b9]131                {
132                        imcb_error( ic, "OAuth error: %s", info->http->status_string );
133                        imc_logout( ic, TRUE );
[18dbb20]134                        return FALSE;
[c42e8b9]135                }
136               
[288b215]137                /* IM mods didn't do this so far and it's ugly but I should
138                   be able to get away with it... */
139                g_free( ic->acc->pass );
[f4b0911]140                ic->acc->pass = oauth_to_string( info );
[288b215]141               
[d6aa6dd]142                twitter_login_finish( ic );
[c42e8b9]143        }
[18dbb20]144       
145        return TRUE;
[713d611]146}
147
[c2ecadc]148
[e88fbe27]149static char *set_eval_mode( set_t *set, char *value )
150{
151        if( g_strcasecmp( value, "one" ) == 0 ||
152            g_strcasecmp( value, "many" ) == 0 ||
[db57e7c]153            g_strcasecmp( value, "chat" ) == 0 )
[e88fbe27]154                return value;
155        else
156                return NULL;
157}
[1b221e0]158
[9997691]159static gboolean twitter_length_check( struct im_connection *ic, gchar *msg )
160{
161        int max = set_getint( &ic->acc->set, "message_length" ), len;
162       
163        if( max == 0 || ( len = g_utf8_strlen( msg, -1 ) ) <= max )
164                return TRUE;
165       
166        imcb_error( ic, "Maximum message length exceeded: %d > %d", len, max );
167       
168        return FALSE;
169}
170
[1b221e0]171static void twitter_init( account_t *acc )
172{
[62d2cfb]173        set_t *s;
[ffcdf13]174        char *def_url;
175        char *def_oauth;
[e88fbe27]176       
[ffcdf13]177        if( strcmp( acc->prpl->name, "twitter" ) == 0 )
178        {
179                def_url = TWITTER_API_URL;
180                def_oauth = "true";
181        }
182        else /* if( strcmp( acc->prpl->name, "identica" ) == 0 ) */
183        {
184                def_url = IDENTICA_API_URL;
185                def_oauth = "false";
186        }
187       
[b890626]188        s = set_add( &acc->set, "auto_reply_timeout", "10800", set_eval_int, acc );
189       
[ffcdf13]190        s = set_add( &acc->set, "base_url", def_url, NULL, acc );
[bb5ce4d1]191        s->flags |= ACC_SET_OFFLINE_ONLY;
192       
[7b87539]193        s = set_add( &acc->set, "commands", "true", set_eval_bool, acc );
194       
[9997691]195        s = set_add( &acc->set, "message_length", "140", set_eval_int, acc );
196       
[e88fbe27]197        s = set_add( &acc->set, "mode", "one", set_eval_mode, acc );
[2abceca]198        s->flags |= ACC_SET_OFFLINE_ONLY;
[713d611]199       
[ffcdf13]200        s = set_add( &acc->set, "oauth", def_oauth, set_eval_bool, acc );
[1b221e0]201}
202
203/**
204 * Login method. Since the twitter API works with seperate HTTP request we
205 * only save the user and pass to the twitter_data object.
206 */
207static void twitter_login( account_t *acc )
208{
209        struct im_connection *ic = imcb_new( acc );
[bb5ce4d1]210        struct twitter_data *td;
[e88fbe27]211        char name[strlen(acc->user)+9];
[bb5ce4d1]212        url_t url;
[62d2cfb]213
[bb5ce4d1]214        if( !url_set( &url, set_getstr( &ic->acc->set, "base_url" ) ) ||
215            ( url.proto != PROTO_HTTP && url.proto != PROTO_HTTPS ) )
216        {
217                imcb_error( ic, "Incorrect API base URL: %s", set_getstr( &ic->acc->set, "base_url" ) );
218                imc_logout( ic, FALSE );
219                return;
220        }
221       
[d569019]222        twitter_connections = g_slist_append( twitter_connections, ic );
[bb5ce4d1]223        td = g_new0( struct twitter_data, 1 );
[713d611]224        ic->proto_data = td;
225       
[bb5ce4d1]226        td->url_ssl = url.proto == PROTO_HTTPS;
227        td->url_port = url.port;
228        td->url_host = g_strdup( url.host );
229        if( strcmp( url.file, "/" ) != 0 )
230                td->url_path = g_strdup( url.file );
231        else
232                td->url_path = g_strdup( "" );
[ffcdf13]233        if( g_str_has_suffix( url.host, ".com" ) )
234                td->prefix = g_strndup( url.host, strlen( url.host ) - 4 );
235        else
236                td->prefix = g_strdup( url.host );
[bb5ce4d1]237       
[1b221e0]238        td->user = acc->user;
[bb5ce4d1]239        if( strstr( acc->pass, "oauth_token=" ) )
[f4b0911]240                td->oauth_info = oauth_from_string( acc->pass, &twitter_oauth );
[e88fbe27]241       
[ffcdf13]242        sprintf( name, "%s_%s", td->prefix, acc->user );
[e88fbe27]243        imcb_add_buddy( ic, name, NULL );
244        imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL );
[713d611]245       
[d6aa6dd]246        imcb_log( ic, "Connecting" );
247       
248        twitter_login_finish( ic );
[1b221e0]249}
250
251/**
252 * Logout method. Just free the twitter_data.
253 */
254static void twitter_logout( struct im_connection *ic )
255{
256        struct twitter_data *td = ic->proto_data;
257       
258        // Set the status to logged out.
[4ffd757]259        ic->flags &= ~ OPT_LOGGED_IN;
[1b221e0]260
[2abceca]261        // Remove the main_loop function from the function queue.
262        b_event_remove(td->main_loop_id);
263
[37d84b3]264        if(td->home_timeline_gc)
265                imcb_chat_free(td->home_timeline_gc);
[1014cab]266
[1b221e0]267        if( td )
268        {
[18dbb20]269                oauth_info_free( td->oauth_info );
[ffcdf13]270                g_free( td->prefix );
[04a927c]271                g_free( td->url_host );
272                g_free( td->url_path );
[508c340]273                g_free( td->pass );
[1b221e0]274                g_free( td );
275        }
[62d2cfb]276
277        twitter_connections = g_slist_remove( twitter_connections, ic );
[1b221e0]278}
279
[7b87539]280static void twitter_handle_command( struct im_connection *ic, char *message );
281
[1b221e0]282/**
283 *
284 */
285static int twitter_buddy_msg( struct im_connection *ic, char *who, char *message, int away )
286{
[c42e8b9]287        struct twitter_data *td = ic->proto_data;
[ffcdf13]288        int plen = strlen( td->prefix );
[c42e8b9]289       
[ffcdf13]290        if (g_strncasecmp(who, td->prefix, plen) == 0 && who[plen] == '_' &&
291            g_strcasecmp(who + plen + 1, ic->acc->user) == 0)
[c42e8b9]292        {
[c2ecadc]293                if( set_getbool( &ic->acc->set, "oauth" ) &&
294                    td->oauth_info && td->oauth_info->token == NULL )
[18dbb20]295                {
[64f8c425]296                        char pin[strlen(message)+1], *s;
297                       
298                        strcpy( pin, message );
299                        for( s = pin + sizeof( pin ) - 2; s > pin && isspace( *s ); s -- )
300                                *s = '\0';
301                        for( s = pin; *s && isspace( *s ); s ++ ) {}
302                       
303                        if( !oauth_access_token( s, td->oauth_info ) )
[c2ecadc]304                        {
305                                imcb_error( ic, "OAuth error: %s", "Failed to send access token request" );
306                                imc_logout( ic, TRUE );
307                                return FALSE;
308                        }
[18dbb20]309                }
[7b87539]310                else
311                        twitter_handle_command(ic, message);
[c42e8b9]312        }
[e88fbe27]313        else
[c42e8b9]314        {
[e88fbe27]315                twitter_direct_messages_new(ic, who, message);
[c42e8b9]316        }
[1b221e0]317        return( 0 );
318}
319
320/**
321 *
322 */
323static void twitter_set_my_name( struct im_connection *ic, char *info )
324{
325}
326
327static void twitter_get_info(struct im_connection *ic, char *who) 
328{
329}
330
331static void twitter_add_buddy( struct im_connection *ic, char *who, char *group )
332{
[7d53efb]333        twitter_friendships_create_destroy(ic, who, 1);
[1b221e0]334}
335
336static void twitter_remove_buddy( struct im_connection *ic, char *who, char *group )
337{
[7d53efb]338        twitter_friendships_create_destroy(ic, who, 0);
[1b221e0]339}
340
341static void twitter_chat_msg( struct groupchat *c, char *message, int flags )
342{
[7b87539]343        if( c && message )
344                twitter_handle_command( c->ic, message );
[1b221e0]345}
346
347static void twitter_chat_invite( struct groupchat *c, char *who, char *message )
348{
349}
350
351static void twitter_chat_leave( struct groupchat *c )
352{
[16592d8]353        struct twitter_data *td = c->ic->proto_data;
354       
355        if( c != td->home_timeline_gc )
356                return; /* WTF? */
357       
358        /* If the user leaves the channel: Fine. Rejoin him/her once new
359           tweets come in. */
360        imcb_chat_free(td->home_timeline_gc);
361        td->home_timeline_gc = NULL;
[1b221e0]362}
363
364static struct groupchat *twitter_chat_with( struct im_connection *ic, char *who )
365{
366        return NULL;
367}
368
369static void twitter_keepalive( struct im_connection *ic )
370{
371}
372
373static void twitter_add_permit( struct im_connection *ic, char *who )
374{
375}
376
377static void twitter_rem_permit( struct im_connection *ic, char *who )
378{
379}
380
381static void twitter_add_deny( struct im_connection *ic, char *who )
382{
383}
384
385static void twitter_rem_deny( struct im_connection *ic, char *who )
386{
387}
388
389static int twitter_send_typing( struct im_connection *ic, char *who, int typing )
390{
391        return( 1 );
392}
393
394//static char *twitter_set_display_name( set_t *set, char *value )
395//{
396//      return value;
397//}
[7b87539]398
[203a2d2]399static void twitter_buddy_data_add( struct bee_user *bu )
400{
401        bu->data = g_new0( struct twitter_user_data, 1 );
402}
403
404static void twitter_buddy_data_free( struct bee_user *bu )
405{
406        g_free( bu->data );
407}
408
[7b87539]409static void twitter_handle_command( struct im_connection *ic, char *message )
410{
411        struct twitter_data *td = ic->proto_data;
412        char *cmds, **cmd;
413       
414        cmds = g_strdup( message );
415        cmd = split_command_parts( cmds );
416       
417        if( cmd[0] == NULL )
418        {
419                g_free( cmds );
420                return;
421        }
[203a2d2]422        else if( !set_getbool( &ic->acc->set, "commands" ) )
[7b87539]423        {
424                /* Not supporting commands. */
425        }
426        else if( g_strcasecmp( cmd[0], "undo" ) == 0 )
427        {
428                guint64 id;
429               
430                if( cmd[1] )
[b890626]431                        id = g_ascii_strtoull( cmd[1], NULL, 10 );
[7b87539]432                else
433                        id = td->last_status_id;
434               
435                /* TODO: User feedback. */
436                if( id )
437                        twitter_status_destroy( ic, id );
438               
439                g_free( cmds );
440                return;
441        }
[b890626]442        else if( g_strcasecmp( cmd[0], "follow" ) == 0 && cmd[1] )
443        {
444                twitter_add_buddy( ic, cmd[1], NULL );
445                g_free( cmds );
446                return;
447        }
448        else if( g_strcasecmp( cmd[0], "unfollow" ) == 0 && cmd[1] )
449        {
450                twitter_remove_buddy( ic, cmd[1], NULL );
451                g_free( cmds );
452                return;
453        }
454        else if( g_strcasecmp( cmd[0], "rt" ) == 0 && cmd[1] )
455        {
456                struct twitter_user_data *tud;
457                bee_user_t *bu;
458                guint64 id;
459               
460                if( ( bu = bee_user_by_handle( ic->bee, ic, cmd[1] ) ) &&
461                    ( tud = bu->data ) && tud->last_id )
462                        id = tud->last_id;
463                else
464                        id = g_ascii_strtoull( cmd[1], NULL, 10 );
465               
466                td->last_status_id = 0;
467                if( id )
468                        twitter_status_retweet( ic, id );
469               
470                g_free( cmds );
471                return;
472        }
[7b87539]473        else if( g_strcasecmp( cmd[0], "post" ) == 0 )
474        {
475                message += 5;
476        }
477       
478        {
[b890626]479                guint64 in_reply_to = 0;
[7b87539]480                char *s, *new = NULL;
481                bee_user_t *bu;
482               
483                if( !twitter_length_check( ic, message ) )
484                {
485                        g_free( cmds );
486                        return;
487                }
488               
489                s = cmd[0] + strlen( cmd[0] ) - 1;
490                if( s > cmd[0] && ( *s == ':' || *s == ',' ) )
491                {
492                        *s = '\0';
493                       
494                        if( ( bu = bee_user_by_handle( ic->bee, ic, cmd[0] ) ) )
495                        {
[b890626]496                                struct twitter_user_data *tud = bu->data;
497                               
[7b87539]498                                new = g_strdup_printf( "@%s %s", bu->handle,
499                                                       message + ( s - cmd[0] ) + 2 );
500                                message = new;
[b890626]501                               
502                                if( time( NULL ) < tud->last_time +
503                                    set_getint( &ic->acc->set, "auto_reply_timeout" ) )
504                                        in_reply_to = tud->last_id;
[7b87539]505                        }
506                }
507               
[b890626]508                /* If the user runs undo between this request and its response
509                   this would delete the second-last Tweet. Prevent that. */
510                td->last_status_id = 0;
511                twitter_post_status( ic, message, in_reply_to );
[7b87539]512                g_free( new );
513        }
514        g_free( cmds );
515}
[1b221e0]516
517void twitter_initmodule()
518{
519        struct prpl *ret = g_new0(struct prpl, 1);
520       
521        ret->name = "twitter";
522        ret->login = twitter_login;
523        ret->init = twitter_init;
524        ret->logout = twitter_logout;
525        ret->buddy_msg = twitter_buddy_msg;
526        ret->get_info = twitter_get_info;
527        ret->set_my_name = twitter_set_my_name;
528        ret->add_buddy = twitter_add_buddy;
529        ret->remove_buddy = twitter_remove_buddy;
530        ret->chat_msg = twitter_chat_msg;
531        ret->chat_invite = twitter_chat_invite;
532        ret->chat_leave = twitter_chat_leave;
533        ret->chat_with = twitter_chat_with;
534        ret->keepalive = twitter_keepalive;
535        ret->add_permit = twitter_add_permit;
536        ret->rem_permit = twitter_rem_permit;
537        ret->add_deny = twitter_add_deny;
538        ret->rem_deny = twitter_rem_deny;
539        ret->send_typing = twitter_send_typing;
[203a2d2]540        ret->buddy_data_add = twitter_buddy_data_add;
541        ret->buddy_data_free = twitter_buddy_data_free;
[1b221e0]542        ret->handle_cmp = g_strcasecmp;
[ffcdf13]543       
544        register_protocol(ret);
[1b221e0]545
[ffcdf13]546        /* And an identi.ca variant: */
547        ret = g_memdup(ret, sizeof(struct prpl));
548        ret->name = "identica";
[1b221e0]549        register_protocol(ret);
[62d2cfb]550
551        // Initialise the twitter_connections GSList.
552        twitter_connections = NULL;
[1b221e0]553}
Note: See TracBrowser for help on using the repository browser.