source: protocols/twitter/twitter.c @ 665c24f

Last change on this file since 665c24f was 665c24f, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-09-30T05:28:36Z

Some simple error msgs on failed Twitter commands (undo and rt).

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