source: protocols/twitter/twitter.c @ b41d5d2

Last change on this file since b41d5d2 was 748bcdd, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-11-21T15:23:54Z

Time out if logging in takes too long (2m for now). Except for Twitter
OAuth login, which requires user action. This mostly solves problems with
OSCAR login silently failing, but may also be useful in other places.

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