source: protocols/twitter/twitter.c @ 7989d40d

Last change on this file since 7989d40d was ffcdf13, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-07-17T15:06:56Z

When using non-Twitter Twitter API services, prefix the channel and contact
name with that service name, not always Twitter. This is especially useful
when having multiple accounts on different sites with the same username.

Also adding an "identica" protocol entry for convenience.

Based on a patch from kensanata, bug #648.

  • Property mode set to 100644
File size: 12.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/**
32 * Main loop function
33 */
34gboolean twitter_main_loop(gpointer data, gint fd, b_input_condition cond)
35{
36        struct im_connection *ic = data;
37       
38        // Check if we are still logged in...
39        if (!g_slist_find( twitter_connections, ic ))
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
49static void twitter_main_loop_start( struct im_connection *ic )
50{
51        struct twitter_data *td = ic->proto_data;
52       
53        imcb_log( ic, "Getting initial statuses" );
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
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}
80
81static const struct oauth_service twitter_oauth =
82{
83        "http://api.twitter.com/oauth/request_token",
84        "http://api.twitter.com/oauth/access_token",
85        "https://api.twitter.com/oauth/authorize",
86        .consumer_key = "xsDNKJuNZYkZyMcu914uEA",
87        .consumer_secret = "FCxqcr0pXKzsF9ajmP57S3VQ8V6Drk4o2QYtqMcOszo",
88};
89
90static gboolean twitter_oauth_callback( struct oauth_info *info );
91
92static void twitter_oauth_start( struct im_connection *ic )
93{
94        struct twitter_data *td = ic->proto_data;
95       
96        imcb_log( ic, "Requesting OAuth request token" );
97
98        td->oauth_info = oauth_request_token( &twitter_oauth, twitter_oauth_callback, ic );
99}
100
101static gboolean twitter_oauth_callback( struct oauth_info *info )
102{
103        struct im_connection *ic = info->data;
104        struct twitter_data *td;
105       
106        if( !g_slist_find( twitter_connections, ic ) )
107                return FALSE;
108       
109        td = ic->proto_data;
110        if( info->stage == OAUTH_REQUEST_TOKEN )
111        {
112                char name[strlen(ic->acc->user)+9], *msg;
113               
114                if( info->request_token == NULL )
115                {
116                        imcb_error( ic, "OAuth error: %s", info->http->status_string );
117                        imc_logout( ic, TRUE );
118                        return FALSE;
119                }
120               
121                sprintf( name, "%s_%s", td->prefix, ic->acc->user );
122                msg = g_strdup_printf( "To finish OAuth authentication, please visit "
123                                       "%s and respond with the resulting PIN code.",
124                                       info->auth_url );
125                imcb_buddy_msg( ic, name, msg, 0, 0 );
126                g_free( msg );
127        }
128        else if( info->stage == OAUTH_ACCESS_TOKEN )
129        {
130                if( info->token == NULL || info->token_secret == NULL )
131                {
132                        imcb_error( ic, "OAuth error: %s", info->http->status_string );
133                        imc_logout( ic, TRUE );
134                        return FALSE;
135                }
136               
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 );
140                ic->acc->pass = oauth_to_string( info );
141               
142                twitter_login_finish( ic );
143        }
144       
145        return TRUE;
146}
147
148
149static char *set_eval_mode( set_t *set, char *value )
150{
151        if( g_strcasecmp( value, "one" ) == 0 ||
152            g_strcasecmp( value, "many" ) == 0 ||
153            g_strcasecmp( value, "chat" ) == 0 )
154                return value;
155        else
156                return NULL;
157}
158
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
171static void twitter_init( account_t *acc )
172{
173        set_t *s;
174        char *def_url;
175        char *def_oauth;
176       
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       
188        s = set_add( &acc->set, "base_url", def_url, NULL, acc );
189        s->flags |= ACC_SET_OFFLINE_ONLY;
190       
191        s = set_add( &acc->set, "message_length", "140", set_eval_int, acc );
192       
193        s = set_add( &acc->set, "mode", "one", set_eval_mode, acc );
194        s->flags |= ACC_SET_OFFLINE_ONLY;
195       
196        s = set_add( &acc->set, "oauth", def_oauth, set_eval_bool, acc );
197}
198
199/**
200 * Login method. Since the twitter API works with seperate HTTP request we
201 * only save the user and pass to the twitter_data object.
202 */
203static void twitter_login( account_t *acc )
204{
205        struct im_connection *ic = imcb_new( acc );
206        struct twitter_data *td;
207        char name[strlen(acc->user)+9];
208        url_t url;
209
210        if( !url_set( &url, set_getstr( &ic->acc->set, "base_url" ) ) ||
211            ( url.proto != PROTO_HTTP && url.proto != PROTO_HTTPS ) )
212        {
213                imcb_error( ic, "Incorrect API base URL: %s", set_getstr( &ic->acc->set, "base_url" ) );
214                imc_logout( ic, FALSE );
215                return;
216        }
217       
218        twitter_connections = g_slist_append( twitter_connections, ic );
219        td = g_new0( struct twitter_data, 1 );
220        ic->proto_data = td;
221       
222        td->url_ssl = url.proto == PROTO_HTTPS;
223        td->url_port = url.port;
224        td->url_host = g_strdup( url.host );
225        if( strcmp( url.file, "/" ) != 0 )
226                td->url_path = g_strdup( url.file );
227        else
228                td->url_path = g_strdup( "" );
229        if( g_str_has_suffix( url.host, ".com" ) )
230                td->prefix = g_strndup( url.host, strlen( url.host ) - 4 );
231        else
232                td->prefix = g_strdup( url.host );
233       
234        td->user = acc->user;
235        if( strstr( acc->pass, "oauth_token=" ) )
236                td->oauth_info = oauth_from_string( acc->pass, &twitter_oauth );
237       
238        sprintf( name, "%s_%s", td->prefix, acc->user );
239        imcb_add_buddy( ic, name, NULL );
240        imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL );
241       
242        imcb_log( ic, "Connecting" );
243       
244        twitter_login_finish( ic );
245}
246
247/**
248 * Logout method. Just free the twitter_data.
249 */
250static void twitter_logout( struct im_connection *ic )
251{
252        struct twitter_data *td = ic->proto_data;
253       
254        // Set the status to logged out.
255        ic->flags = 0;
256
257        // Remove the main_loop function from the function queue.
258        b_event_remove(td->main_loop_id);
259
260        if(td->home_timeline_gc)
261                imcb_chat_free(td->home_timeline_gc);
262
263        if( td )
264        {
265                oauth_info_free( td->oauth_info );
266                g_free( td->prefix );
267                g_free( td->url_host );
268                g_free( td->url_path );
269                g_free( td->pass );
270                g_free( td );
271        }
272
273        twitter_connections = g_slist_remove( twitter_connections, ic );
274}
275
276/**
277 *
278 */
279static int twitter_buddy_msg( struct im_connection *ic, char *who, char *message, int away )
280{
281        struct twitter_data *td = ic->proto_data;
282        int plen = strlen( td->prefix );
283       
284        if (g_strncasecmp(who, td->prefix, plen) == 0 && who[plen] == '_' &&
285            g_strcasecmp(who + plen + 1, ic->acc->user) == 0)
286        {
287                if( set_getbool( &ic->acc->set, "oauth" ) &&
288                    td->oauth_info && td->oauth_info->token == NULL )
289                {
290                        char pin[strlen(message)+1], *s;
291                       
292                        strcpy( pin, message );
293                        for( s = pin + sizeof( pin ) - 2; s > pin && isspace( *s ); s -- )
294                                *s = '\0';
295                        for( s = pin; *s && isspace( *s ); s ++ ) {}
296                       
297                        if( !oauth_access_token( s, td->oauth_info ) )
298                        {
299                                imcb_error( ic, "OAuth error: %s", "Failed to send access token request" );
300                                imc_logout( ic, TRUE );
301                                return FALSE;
302                        }
303                }
304                else if( twitter_length_check(ic, message) )
305                        twitter_post_status(ic, message);
306        }
307        else
308        {
309                twitter_direct_messages_new(ic, who, message);
310        }
311        return( 0 );
312}
313
314/**
315 *
316 */
317static void twitter_set_my_name( struct im_connection *ic, char *info )
318{
319}
320
321static void twitter_get_info(struct im_connection *ic, char *who) 
322{
323}
324
325static void twitter_add_buddy( struct im_connection *ic, char *who, char *group )
326{
327        twitter_friendships_create_destroy(ic, who, 1);
328}
329
330static void twitter_remove_buddy( struct im_connection *ic, char *who, char *group )
331{
332        twitter_friendships_create_destroy(ic, who, 0);
333}
334
335static void twitter_chat_msg( struct groupchat *c, char *message, int flags )
336{
337        if( c && message && twitter_length_check( c->ic, message ) )
338        {
339                char *s, *new = NULL;
340               
341                if( ( s = strchr( message, ':' ) ) ||
342                    ( s = strchr( message, ',' ) ) )
343                {
344                        bee_user_t *bu;
345                       
346                        new = g_strdup( message );
347                        new[s-message] = '\0';
348                        if( ( bu = bee_user_by_handle( c->ic->bee, c->ic, new ) ) )
349                        {
350                                sprintf( new, "@%s", bu->handle );
351                                new[s-message+1] = ' ';
352                                message = new;
353                        }
354                }
355               
356                twitter_post_status( c->ic, message );
357                g_free( new );
358        }
359}
360
361static void twitter_chat_invite( struct groupchat *c, char *who, char *message )
362{
363}
364
365static void twitter_chat_leave( struct groupchat *c )
366{
367        struct twitter_data *td = c->ic->proto_data;
368       
369        if( c != td->home_timeline_gc )
370                return; /* WTF? */
371       
372        /* If the user leaves the channel: Fine. Rejoin him/her once new
373           tweets come in. */
374        imcb_chat_free(td->home_timeline_gc);
375        td->home_timeline_gc = NULL;
376}
377
378static struct groupchat *twitter_chat_with( struct im_connection *ic, char *who )
379{
380        return NULL;
381}
382
383static void twitter_keepalive( struct im_connection *ic )
384{
385}
386
387static void twitter_add_permit( struct im_connection *ic, char *who )
388{
389}
390
391static void twitter_rem_permit( struct im_connection *ic, char *who )
392{
393}
394
395static void twitter_add_deny( struct im_connection *ic, char *who )
396{
397}
398
399static void twitter_rem_deny( struct im_connection *ic, char *who )
400{
401}
402
403static int twitter_send_typing( struct im_connection *ic, char *who, int typing )
404{
405        return( 1 );
406}
407
408//static char *twitter_set_display_name( set_t *set, char *value )
409//{
410//      return value;
411//}
412
413void twitter_initmodule()
414{
415        struct prpl *ret = g_new0(struct prpl, 1);
416       
417        ret->name = "twitter";
418        ret->login = twitter_login;
419        ret->init = twitter_init;
420        ret->logout = twitter_logout;
421        ret->buddy_msg = twitter_buddy_msg;
422        ret->get_info = twitter_get_info;
423        ret->set_my_name = twitter_set_my_name;
424        ret->add_buddy = twitter_add_buddy;
425        ret->remove_buddy = twitter_remove_buddy;
426        ret->chat_msg = twitter_chat_msg;
427        ret->chat_invite = twitter_chat_invite;
428        ret->chat_leave = twitter_chat_leave;
429        ret->chat_with = twitter_chat_with;
430        ret->keepalive = twitter_keepalive;
431        ret->add_permit = twitter_add_permit;
432        ret->rem_permit = twitter_rem_permit;
433        ret->add_deny = twitter_add_deny;
434        ret->rem_deny = twitter_rem_deny;
435        ret->send_typing = twitter_send_typing;
436        ret->handle_cmp = g_strcasecmp;
437       
438        register_protocol(ret);
439
440        /* And an identi.ca variant: */
441        ret = g_memdup(ret, sizeof(struct prpl));
442        ret->name = "identica";
443        register_protocol(ret);
444
445        // Initialise the twitter_connections GSList.
446        twitter_connections = NULL;
447}
Note: See TracBrowser for help on using the repository browser.