source: protocols/twitter/twitter.c @ 9c9a29c

Last change on this file since 9c9a29c was 9c9a29c, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-10-16T05:17:46Z

Don't initialize the twitter_connections list to NULL in twitter_init().
This fixes a weird bug where Twitter updates often stop after a while
when running in daemon mode with multiple users connected.

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