source: protocols/twitter/twitter.c @ 93cc86f

Last change on this file since 93cc86f was 93cc86f, checked in by Wilmer van der Gaast <wilmer@…>, at 2011-03-08T06:24:34Z

Twitter: Warn the user if the OAuth username and the configured username
don't match. This is not a real problem but can be confusing if you don't
expect it.

  • Property mode set to 100644
File size: 15.0 KB
Line 
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"
25#include "oauth.h"
26#include "twitter.h"
27#include "twitter_http.h"
28#include "twitter_lib.h"
29#include "url.h"
30
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               
40GSList *twitter_connections = NULL;
41
42/**
43 * Main loop function
44 */
45gboolean twitter_main_loop(gpointer data, gint fd, b_input_condition cond)
46{
47        struct im_connection *ic = data;
48       
49        // Check if we are still logged in...
50        if (!g_slist_find( twitter_connections, ic ))
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
60static void twitter_main_loop_start( struct im_connection *ic )
61{
62        struct twitter_data *td = ic->proto_data;
63       
64        imcb_log( ic, "Getting initial statuses" );
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
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}
91
92static const struct oauth_service twitter_oauth =
93{
94        "http://api.twitter.com/oauth/request_token",
95        "http://api.twitter.com/oauth/access_token",
96        "https://api.twitter.com/oauth/authorize",
97        .consumer_key = "xsDNKJuNZYkZyMcu914uEA",
98        .consumer_secret = "FCxqcr0pXKzsF9ajmP57S3VQ8V6Drk4o2QYtqMcOszo",
99};
100
101static gboolean twitter_oauth_callback( struct oauth_info *info );
102
103static void twitter_oauth_start( struct im_connection *ic )
104{
105        struct twitter_data *td = ic->proto_data;
106       
107        imcb_log( ic, "Requesting OAuth request token" );
108
109        td->oauth_info = oauth_request_token( &twitter_oauth, twitter_oauth_callback, ic );
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;
114}
115
116static gboolean twitter_oauth_callback( struct oauth_info *info )
117{
118        struct im_connection *ic = info->data;
119        struct twitter_data *td;
120       
121        if( !g_slist_find( twitter_connections, ic ) )
122                return FALSE;
123       
124        td = ic->proto_data;
125        if( info->stage == OAUTH_REQUEST_TOKEN )
126        {
127                char name[strlen(ic->acc->user)+9], *msg;
128               
129                if( info->request_token == NULL )
130                {
131                        imcb_error( ic, "OAuth error: %s", info->http->status_string );
132                        imc_logout( ic, TRUE );
133                        return FALSE;
134                }
135               
136                sprintf( name, "%s_%s", td->prefix, ic->acc->user );
137                msg = g_strdup_printf( "To finish OAuth authentication, please visit "
138                                       "%s and respond with the resulting PIN code.",
139                                       info->auth_url );
140                imcb_buddy_msg( ic, name, msg, 0, 0 );
141                g_free( msg );
142        }
143        else if( info->stage == OAUTH_ACCESS_TOKEN )
144        {
145                if( info->token == NULL || info->token_secret == NULL )
146                {
147                        imcb_error( ic, "OAuth error: %s", info->http->status_string );
148                        imc_logout( ic, TRUE );
149                        return FALSE;
150                }
151                else
152                {
153                        const char *sn = oauth_params_get( &info->params, "screen_name" );
154                       
155                        if( sn != NULL && ic->acc->prpl->handle_cmp( sn, ic->acc->user ) != 0 )
156                        {
157                                imcb_log( ic, "Warning: You logged in via OAuth as %s "
158                                          "instead of %s.", sn, ic->acc->user );
159                        }
160                }
161               
162                /* IM mods didn't do this so far and it's ugly but I should
163                   be able to get away with it... */
164                g_free( ic->acc->pass );
165                ic->acc->pass = oauth_to_string( info );
166               
167                twitter_login_finish( ic );
168        }
169       
170        return TRUE;
171}
172
173
174static char *set_eval_mode( set_t *set, char *value )
175{
176        if( g_strcasecmp( value, "one" ) == 0 ||
177            g_strcasecmp( value, "many" ) == 0 ||
178            g_strcasecmp( value, "chat" ) == 0 )
179                return value;
180        else
181                return NULL;
182}
183
184static gboolean twitter_length_check( struct im_connection *ic, gchar *msg )
185{
186        int max = set_getint( &ic->acc->set, "message_length" ), len;
187       
188        if( max == 0 || ( len = g_utf8_strlen( msg, -1 ) ) <= max )
189                return TRUE;
190       
191        imcb_error( ic, "Maximum message length exceeded: %d > %d", len, max );
192       
193        return FALSE;
194}
195
196static void twitter_init( account_t *acc )
197{
198        set_t *s;
199        char *def_url;
200        char *def_oauth;
201       
202        if( strcmp( acc->prpl->name, "twitter" ) == 0 )
203        {
204                def_url = TWITTER_API_URL;
205                def_oauth = "true";
206        }
207        else /* if( strcmp( acc->prpl->name, "identica" ) == 0 ) */
208        {
209                def_url = IDENTICA_API_URL;
210                def_oauth = "false";
211        }
212       
213        s = set_add( &acc->set, "auto_reply_timeout", "10800", set_eval_int, acc );
214       
215        s = set_add( &acc->set, "base_url", def_url, NULL, acc );
216        s->flags |= ACC_SET_OFFLINE_ONLY;
217       
218        s = set_add( &acc->set, "commands", "true", set_eval_bool, acc );
219       
220        s = set_add( &acc->set, "message_length", "140", set_eval_int, acc );
221       
222        s = set_add( &acc->set, "mode", "chat", set_eval_mode, acc );
223        s->flags |= ACC_SET_OFFLINE_ONLY;
224       
225        s = set_add( &acc->set, "oauth", def_oauth, set_eval_bool, acc );
226}
227
228/**
229 * Login method. Since the twitter API works with seperate HTTP request we
230 * only save the user and pass to the twitter_data object.
231 */
232static void twitter_login( account_t *acc )
233{
234        struct im_connection *ic = imcb_new( acc );
235        struct twitter_data *td;
236        char name[strlen(acc->user)+9];
237        url_t url;
238
239        if( !url_set( &url, set_getstr( &ic->acc->set, "base_url" ) ) ||
240            ( url.proto != PROTO_HTTP && url.proto != PROTO_HTTPS ) )
241        {
242                imcb_error( ic, "Incorrect API base URL: %s", set_getstr( &ic->acc->set, "base_url" ) );
243                imc_logout( ic, FALSE );
244                return;
245        }
246       
247        twitter_connections = g_slist_append( twitter_connections, ic );
248        td = g_new0( struct twitter_data, 1 );
249        ic->proto_data = td;
250       
251        td->url_ssl = url.proto == PROTO_HTTPS;
252        td->url_port = url.port;
253        td->url_host = g_strdup( url.host );
254        if( strcmp( url.file, "/" ) != 0 )
255                td->url_path = g_strdup( url.file );
256        else
257                td->url_path = g_strdup( "" );
258        if( g_str_has_suffix( url.host, ".com" ) )
259                td->prefix = g_strndup( url.host, strlen( url.host ) - 4 );
260        else
261                td->prefix = g_strdup( url.host );
262       
263        td->user = acc->user;
264        if( strstr( acc->pass, "oauth_token=" ) )
265                td->oauth_info = oauth_from_string( acc->pass, &twitter_oauth );
266       
267        sprintf( name, "%s_%s", td->prefix, acc->user );
268        imcb_add_buddy( ic, name, NULL );
269        imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL );
270       
271        imcb_log( ic, "Connecting" );
272       
273        twitter_login_finish( ic );
274}
275
276/**
277 * Logout method. Just free the twitter_data.
278 */
279static void twitter_logout( struct im_connection *ic )
280{
281        struct twitter_data *td = ic->proto_data;
282       
283        // Set the status to logged out.
284        ic->flags &= ~ OPT_LOGGED_IN;
285
286        // Remove the main_loop function from the function queue.
287        b_event_remove(td->main_loop_id);
288
289        if(td->home_timeline_gc)
290                imcb_chat_free(td->home_timeline_gc);
291
292        if( td )
293        {
294                oauth_info_free( td->oauth_info );
295                g_free( td->prefix );
296                g_free( td->url_host );
297                g_free( td->url_path );
298                g_free( td->pass );
299                g_free( td );
300        }
301
302        twitter_connections = g_slist_remove( twitter_connections, ic );
303}
304
305static void twitter_handle_command( struct im_connection *ic, char *message );
306
307/**
308 *
309 */
310static int twitter_buddy_msg( struct im_connection *ic, char *who, char *message, int away )
311{
312        struct twitter_data *td = ic->proto_data;
313        int plen = strlen( td->prefix );
314       
315        if (g_strncasecmp(who, td->prefix, plen) == 0 && who[plen] == '_' &&
316            g_strcasecmp(who + plen + 1, ic->acc->user) == 0)
317        {
318                if( set_getbool( &ic->acc->set, "oauth" ) &&
319                    td->oauth_info && td->oauth_info->token == NULL )
320                {
321                        char pin[strlen(message)+1], *s;
322                       
323                        strcpy( pin, message );
324                        for( s = pin + sizeof( pin ) - 2; s > pin && isspace( *s ); s -- )
325                                *s = '\0';
326                        for( s = pin; *s && isspace( *s ); s ++ ) {}
327                       
328                        if( !oauth_access_token( s, td->oauth_info ) )
329                        {
330                                imcb_error( ic, "OAuth error: %s", "Failed to send access token request" );
331                                imc_logout( ic, TRUE );
332                                return FALSE;
333                        }
334                }
335                else
336                        twitter_handle_command(ic, message);
337        }
338        else
339        {
340                twitter_direct_messages_new(ic, who, message);
341        }
342        return( 0 );
343}
344
345/**
346 *
347 */
348static void twitter_set_my_name( struct im_connection *ic, char *info )
349{
350}
351
352static void twitter_get_info(struct im_connection *ic, char *who) 
353{
354}
355
356static void twitter_add_buddy( struct im_connection *ic, char *who, char *group )
357{
358        twitter_friendships_create_destroy(ic, who, 1);
359}
360
361static void twitter_remove_buddy( struct im_connection *ic, char *who, char *group )
362{
363        twitter_friendships_create_destroy(ic, who, 0);
364}
365
366static void twitter_chat_msg( struct groupchat *c, char *message, int flags )
367{
368        if( c && message )
369                twitter_handle_command( c->ic, message );
370}
371
372static void twitter_chat_invite( struct groupchat *c, char *who, char *message )
373{
374}
375
376static void twitter_chat_leave( struct groupchat *c )
377{
378        struct twitter_data *td = c->ic->proto_data;
379       
380        if( c != td->home_timeline_gc )
381                return; /* WTF? */
382       
383        /* If the user leaves the channel: Fine. Rejoin him/her once new
384           tweets come in. */
385        imcb_chat_free(td->home_timeline_gc);
386        td->home_timeline_gc = NULL;
387}
388
389static void twitter_keepalive( struct im_connection *ic )
390{
391}
392
393static void twitter_add_permit( struct im_connection *ic, char *who )
394{
395}
396
397static void twitter_rem_permit( struct im_connection *ic, char *who )
398{
399}
400
401static void twitter_add_deny( struct im_connection *ic, char *who )
402{
403}
404
405static void twitter_rem_deny( struct im_connection *ic, char *who )
406{
407}
408
409//static char *twitter_set_display_name( set_t *set, char *value )
410//{
411//      return value;
412//}
413
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
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        }
437        else if( !set_getbool( &ic->acc->set, "commands" ) )
438        {
439                /* Not supporting commands. */
440        }
441        else if( g_strcasecmp( cmd[0], "undo" ) == 0 )
442        {
443                guint64 id;
444               
445                if( cmd[1] )
446                        id = g_ascii_strtoull( cmd[1], NULL, 10 );
447                else
448                        id = td->last_status_id;
449               
450                /* TODO: User feedback. */
451                if( id )
452                        twitter_status_destroy( ic, id );
453                else
454                        twitter_msg( ic, "Could not undo last action" );
455               
456                g_free( cmds );
457                return;
458        }
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 );
486                else
487                        twitter_msg( ic, "User `%s' does not exist or didn't "
488                                         "post any statuses recently", cmd[1] );
489               
490                g_free( cmds );
491                return;
492        }
493        else if( g_strcasecmp( cmd[0], "post" ) == 0 )
494        {
495                message += 5;
496        }
497       
498        {
499                guint64 in_reply_to = 0;
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                        {
516                                struct twitter_user_data *tud = bu->data;
517                               
518                                new = g_strdup_printf( "@%s %s", bu->handle,
519                                                       message + ( s - cmd[0] ) + 2 );
520                                message = new;
521                               
522                                if( time( NULL ) < tud->last_time +
523                                    set_getint( &ic->acc->set, "auto_reply_timeout" ) )
524                                        in_reply_to = tud->last_id;
525                        }
526                }
527               
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 );
532                g_free( new );
533        }
534        g_free( cmds );
535}
536
537void twitter_initmodule()
538{
539        struct prpl *ret = g_new0(struct prpl, 1);
540       
541        ret->options = OPT_NOOTR;
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->keepalive = twitter_keepalive;
555        ret->add_permit = twitter_add_permit;
556        ret->rem_permit = twitter_rem_permit;
557        ret->add_deny = twitter_add_deny;
558        ret->rem_deny = twitter_rem_deny;
559        ret->buddy_data_add = twitter_buddy_data_add;
560        ret->buddy_data_free = twitter_buddy_data_free;
561        ret->handle_cmp = g_strcasecmp;
562       
563        register_protocol(ret);
564
565        /* And an identi.ca variant: */
566        ret = g_memdup(ret, sizeof(struct prpl));
567        ret->name = "identica";
568        register_protocol(ret);
569}
Note: See TracBrowser for help on using the repository browser.