Ticket #663: twitter-mentions.diff

File twitter-mentions.diff, 34.0 KB (added by wilmer, at 2011-06-11T11:20:35Z)
  • twitter.c

    diff -uNr twitter/twitter.c twitter-meh/twitter.c
    old new  
    2929#include "url.h"
    3030
    3131#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 );                        \
     32        do {                                            \
     33                struct twitter_data *td = ic->proto_data;   \
     34                if( td->timeline_gc )                       \
     35                        imcb_chat_log( td->timeline_gc, fmt ); \
     36                else                                        \
     37                        imcb_log( ic, fmt );                    \
    3838        } while( 0 );
    3939
    4040GSList *twitter_connections = NULL;
     
    5151                return 0;
    5252
    5353        // Do stuff..
    54         twitter_get_home_timeline(ic, -1);
     54        twitter_get_timeline(ic, -1);
    5555
    5656        // If we are still logged in run this function again after timeout.
    5757        return (ic->flags & OPT_LOGGED_IN) == OPT_LOGGED_IN;
     
    6868
    6969        // Queue the main_loop
    7070        // 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);
     71        td->main_loop_id =
     72            b_timeout_add(set_getint(&ic->acc->set, "fetch_every") * 1000, twitter_main_loop, ic);
    7273}
    7374
    7475static void twitter_oauth_start(struct im_connection *ic);
     
    7778{
    7879        struct twitter_data *td = ic->proto_data;
    7980
     81        td->timeline_doing = FALSE;
     82
    8083        if (set_getbool(&ic->acc->set, "oauth") && !td->oauth_info)
    8184                twitter_oauth_start(ic);
    8285        else if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "one") != 0 &&
     
    219222
    220223        s = set_add(&acc->set, "auto_reply_timeout", "10800", set_eval_int, acc);
    221224
     225        s = set_add(&acc->set, "fetch_every", "60", set_eval_int, acc);
     226        s->flags |= ACC_SET_OFFLINE_ONLY;
     227
    222228        s = set_add(&acc->set, "base_url", def_url, NULL, acc);
    223229        s->flags |= ACC_SET_OFFLINE_ONLY;
    224230
     
    226232
    227233        s = set_add(&acc->set, "message_length", "140", set_eval_int, acc);
    228234
     235        s = set_add(&acc->set, "include_mentions", "true", set_eval_bool, acc);
     236
    229237        s = set_add(&acc->set, "mode", "chat", set_eval_mode, acc);
    230238        s->flags |= ACC_SET_OFFLINE_ONLY;
    231239
     
    299307        // Remove the main_loop function from the function queue.
    300308        b_event_remove(td->main_loop_id);
    301309
    302         if (td->home_timeline_gc)
    303                 imcb_chat_free(td->home_timeline_gc);
     310        if (td->timeline_gc)
     311                imcb_chat_free(td->timeline_gc);
    304312
    305313        if (td) {
    306314                oauth_info_free(td->oauth_info);
     
    386394{
    387395        struct twitter_data *td = c->ic->proto_data;
    388396
    389         if (c != td->home_timeline_gc)
     397        if (c != td->timeline_gc)
    390398                return;         /* WTF? */
    391399
    392400        /* If the user leaves the channel: Fine. Rejoin him/her once new
    393401           tweets come in. */
    394         imcb_chat_free(td->home_timeline_gc);
    395         td->home_timeline_gc = NULL;
     402        imcb_chat_free(td->timeline_gc);
     403        td->timeline_gc = NULL;
    396404}
    397405
    398406static void twitter_keepalive(struct im_connection *ic)
  • twitter.c.orig

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

    diff -uNr twitter/twitter.h twitter-meh/twitter.h
    old new  
    4444        char* user;
    4545        char* pass;
    4646        struct oauth_info *oauth_info;
    47         guint64 home_timeline_id;
     47
     48        gboolean timeline_doing;
     49        gpointer home_timeline_obj;
     50        gboolean home_timeline_done;
     51        gpointer mentions_obj;
     52        gboolean mentions_done;
     53
     54        guint64 timeline_id;
    4855        guint64 last_status_id; /* For undo */
    4956        gint main_loop_id;
    50         struct groupchat *home_timeline_gc;
     57        struct groupchat *timeline_gc;
    5158        gint http_fails;
    5259        twitter_flags_t flags;
    5360       
  • twitter_lib.c

    diff -uNr twitter/twitter_lib.c twitter-meh/twitter_lib.c
    old new  
    7878{
    7979        if (txu == NULL)
    8080                return;
     81
    8182        g_free(txu->name);
    8283        g_free(txu->screen_name);
    8384        g_free(txu);
    8485}
    8586
    86 
    8787/**
    8888 * Frees a twitter_xml_status struct.
    8989 */
    9090static void txs_free(struct twitter_xml_status *txs)
    9191{
     92        if (txs == NULL)
     93                return;
     94
    9295        g_free(txs->text);
    9396        txu_free(txs->user);
    9497        g_free(txs);
     
    103106        GSList *l;
    104107        if (txl == NULL)
    105108                return;
    106         for (l = txl->list; l; l = g_slist_next(l))
    107                 if (txl->type == TXL_STATUS)
     109
     110        for (l = txl->list; l; l = g_slist_next(l)) {
     111                if (txl->type == TXL_STATUS) {
    108112                        txs_free((struct twitter_xml_status *) l->data);
    109                 else if (txl->type == TXL_ID)
     113                } else if (txl->type == TXL_ID) {
    110114                        g_free(l->data);
     115                }
     116        }
     117
    111118        g_slist_free(txl->list);
    112119        g_free(txl);
    113120}
    114121
    115122/**
    116  * Add a buddy if it is not allready added, set the status to logged in.
     123 * Compare status elements
     124 */
     125static gint twitter_compare_elements(gconstpointer a, gconstpointer b)
     126{
     127        struct twitter_xml_status *a_status = (struct twitter_xml_status *) a;
     128        struct twitter_xml_status *b_status = (struct twitter_xml_status *) b;
     129
     130        if (a_status->created_at < b_status->created_at) {
     131                return -1;
     132        } else if (a_status->created_at > b_status->created_at) {
     133                return 1;
     134        } else {
     135                return 0;
     136        }
     137}
     138
     139/**
     140 * Add a buddy if it is not already added, set the status to logged in.
    117141 */
    118142static void twitter_add_buddy(struct im_connection *ic, char *name, const char *fullname)
    119143{
    120144        struct twitter_data *td = ic->proto_data;
    121145
    122         // Check if the buddy is allready in the buddy list.
     146        // Check if the buddy is already in the buddy list.
    123147        if (!bee_user_by_handle(ic->bee, ic, name)) {
    124148                char *mode = set_getstr(&ic->acc->set, "mode");
    125149
     
    130154                        /* Necessary so that nicks always get translated to the
    131155                           exact Twitter username. */
    132156                        imcb_buddy_nick_hint(ic, name, name);
    133                         imcb_chat_add_buddy(td->home_timeline_gc, name);
     157                        imcb_chat_add_buddy(td->timeline_gc, name);
    134158                } else if (g_strcasecmp(mode, "many") == 0)
    135159                        imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL);
    136160        }
     
    438462        return XT_HANDLED;
    439463}
    440464
    441 static void twitter_http_get_home_timeline(struct http_request *req);
    442 
    443 /**
    444  * Get the timeline.
    445  */
    446 void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
    447 {
    448         struct twitter_data *td = ic->proto_data;
    449 
    450         char *args[4];
    451         args[0] = "cursor";
    452         args[1] = g_strdup_printf("%lld", (long long) next_cursor);
    453         if (td->home_timeline_id) {
    454                 args[2] = "since_id";
    455                 args[3] = g_strdup_printf("%llu", (long long unsigned int) td->home_timeline_id);
    456         }
    457 
    458         twitter_http(ic, TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, args,
    459                      td->home_timeline_id ? 4 : 2);
    460 
    461         g_free(args[1]);
    462         if (td->home_timeline_id) {
    463                 g_free(args[3]);
    464         }
    465 }
    466 
    467465static char *twitter_msg_add_id(struct im_connection *ic,
    468466                                struct twitter_xml_status *txs, const char *prefix)
    469467{
     
    502500        struct twitter_data *td = ic->proto_data;
    503501        GSList *l;
    504502
    505         td->home_timeline_gc = gc = imcb_chat_new(ic, "home/timeline");
     503        td->timeline_gc = gc = imcb_chat_new(ic, "twitter/timeline");
    506504
    507505        name_hint = g_strdup_printf("%s_%s", td->prefix, ic->acc->user);
    508506        imcb_chat_name_hint(gc, name_hint);
     
    511509        for (l = ic->bee->users; l; l = l->next) {
    512510                bee_user_t *bu = l->data;
    513511                if (bu->ic == ic)
    514                         imcb_chat_add_buddy(td->home_timeline_gc, bu->handle);
     512                        imcb_chat_add_buddy(td->timeline_gc, bu->handle);
    515513        }
    516514}
    517515
     
    524522        GSList *l = NULL;
    525523        struct twitter_xml_status *status;
    526524        struct groupchat *gc;
     525        char *text;
     526        guint64 last_id = 0;
     527        size_t length;
    527528
    528529        // Create a new groupchat if it does not exsist.
    529         if (!td->home_timeline_gc)
     530        if (!td->timeline_gc)
    530531                twitter_groupchat_init(ic);
    531532
    532         gc = td->home_timeline_gc;
     533        gc = td->timeline_gc;
    533534        if (!gc->joined)
    534535                imcb_chat_add_buddy(gc, ic->acc->user);
    535536
     
    537538                char *msg;
    538539
    539540                status = l->data;
    540                 if (status->user == NULL || status->text == NULL)
     541                if (status->user == NULL || status->text == NULL || last_id == status->id)
    541542                        continue;
    542543
    543                 twitter_add_buddy(ic, status->user->screen_name, status->user->name);
     544                last_id = status->id;
    544545
    545546                strip_html(status->text);
     547
    546548                msg = twitter_msg_add_id(ic, status, "");
    547549
    548550                // Say it!
    549                 if (g_strcasecmp(td->user, status->user->screen_name) == 0)
     551                if (g_strcasecmp(td->user, status->user->screen_name) == 0) {
    550552                        imcb_chat_log(gc, "You: %s", msg ? msg : status->text);
    551                 else
     553                } else {
     554                        twitter_add_buddy(ic, status->user->screen_name, status->user->name);
     555
    552556                        imcb_chat_msg(gc, status->user->screen_name,
    553557                                      msg ? msg : status->text, 0, status->created_at);
     558                }
    554559
    555560                g_free(msg);
    556561
    557                 // Update the home_timeline_id to hold the highest id, so that by the next request
     562                // Update the timeline_id to hold the highest id, so that by the next request
    558563                // we won't pick up the updates already in the list.
    559                 td->home_timeline_id = MAX(td->home_timeline_id, status->id);
     564                td->timeline_id = MAX(td->timeline_id, status->id);
    560565        }
    561566}
    562567
     
    570575        struct twitter_xml_status *status;
    571576        char from[MAX_STRING];
    572577        gboolean mode_one;
     578        guint64 last_id = 0;
    573579
    574580        mode_one = g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "one") == 0;
    575581
     
    582588                char *prefix = NULL, *text = NULL;
    583589
    584590                status = l->data;
     591                if (status->user == NULL || status->text == NULL || last_id == status->id)
     592                        continue;
     593
     594                last_id = status->id;
    585595
    586596                strip_html(status->text);
    587597                if (mode_one)
     
    596606                               mode_one ? from : status->user->screen_name,
    597607                               text ? text : status->text, 0, status->created_at);
    598608
    599                 // Update the home_timeline_id to hold the highest id, so that by the next request
     609                // Update the timeline_id to hold the highest id, so that by the next request
    600610                // we won't pick up the updates already in the list.
    601                 td->home_timeline_id = MAX(td->home_timeline_id, status->id);
     611                td->timeline_id = MAX(td->timeline_id, status->id);
    602612
    603613                g_free(text);
    604614                g_free(prefix);
    605615        }
    606616}
    607617
     618static void twitter_http_get_home_timeline(struct http_request *req);
     619static void twitter_http_get_mentions(struct http_request *req);
     620
     621/**
     622 * Get the timeline with optionally mentions
     623 */
     624void twitter_get_timeline(struct im_connection *ic, gint64 next_cursor)
     625{
     626        struct twitter_data *td = ic->proto_data;
     627        gboolean include_mentions = set_getbool(&ic->acc->set, "include_mentions");
     628
     629        if (td->timeline_doing) {
     630                return;
     631        }
     632
     633        td->timeline_doing = TRUE;
     634
     635        twitter_get_home_timeline(ic, next_cursor);
     636
     637        if (include_mentions) {
     638                twitter_get_mentions(ic, next_cursor);
     639        }
     640}
     641
     642void twitter_flush_timeline(struct im_connection *ic)
     643{
     644        struct twitter_data *td = ic->proto_data;
     645        gboolean include_mentions = set_getbool(&ic->acc->set, "include_mentions");
     646        struct twitter_xml_list *home_timeline = td->home_timeline_obj;
     647        struct twitter_xml_list *mentions = td->mentions_obj;
     648        GSList *output = NULL;
     649        GSList *l;
     650
     651        if (!td->home_timeline_done) {
     652                return;
     653        }
     654
     655        if (include_mentions && !td->mentions_done) {
     656                return;
     657        }
     658
     659        if (home_timeline && home_timeline->list) {
     660                for (l = home_timeline->list; l; l = g_slist_next(l)) {
     661                        output = g_slist_insert_sorted(output, l->data, twitter_compare_elements);
     662                }
     663        }
     664
     665        if (include_mentions && mentions && mentions->list) {
     666                for (l = mentions->list; l; l = g_slist_next(l)) {
     667                        if (output && twitter_compare_elements(l->data, output->data) < 0) {
     668                                continue;
     669                        }
     670
     671                        output = g_slist_insert_sorted(output, l->data, twitter_compare_elements);
     672                }
     673        }
     674        // See if the user wants to see the messages in a groupchat window or as private messages.
     675        if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0)
     676                twitter_groupchat(ic, output);
     677        else
     678                twitter_private_message_chat(ic, output);
     679
     680        g_slist_free(output);
     681
     682        if (home_timeline && home_timeline->list) {
     683                txl_free(home_timeline);
     684        }
     685
     686        if (mentions && mentions->list) {
     687                txl_free(mentions);
     688        }
     689
     690        td->home_timeline_done = FALSE;
     691        td->mentions_done = FALSE;
     692        td->timeline_doing = FALSE;
     693}
     694
     695/**
     696 * Get the timeline.
     697 */
     698void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
     699{
     700        struct twitter_data *td = ic->proto_data;
     701
     702        td->home_timeline_obj = NULL;
     703        td->home_timeline_done = FALSE;
     704
     705        char *args[4];
     706        args[0] = "cursor";
     707        args[1] = g_strdup_printf("%lld", (long long) next_cursor);
     708        if (td->timeline_id) {
     709                args[2] = "since_id";
     710                args[3] = g_strdup_printf("%llu", (long long unsigned int) td->timeline_id);
     711        }
     712
     713        twitter_http(ic, TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, args,
     714                     td->timeline_id ? 4 : 2);
     715
     716        g_free(args[1]);
     717        if (td->timeline_id) {
     718                g_free(args[3]);
     719        }
     720}
     721
     722/**
     723 * Get mentions.
     724 */
     725void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)
     726{
     727        struct twitter_data *td = ic->proto_data;
     728
     729        td->mentions_obj = NULL;
     730        td->mentions_done = FALSE;
     731
     732        char *args[4];
     733        args[0] = "cursor";
     734        args[1] = g_strdup_printf("%lld", (long long) next_cursor);
     735        if (td->timeline_id) {
     736                args[2] = "since_id";
     737                args[3] = g_strdup_printf("%llu", (long long unsigned int) td->timeline_id);
     738        }
     739
     740        twitter_http(ic, TWITTER_MENTIONS_URL, twitter_http_get_mentions, ic, 0, args,
     741                     td->timeline_id ? 4 : 2);
     742
     743        g_free(args[1]);
     744        if (td->timeline_id) {
     745                g_free(args[3]);
     746        }
     747}
     748
    608749/**
    609750 * Callback for getting the home timeline.
    610751 */
     
    629770        } else if (req->status_code == 401) {
    630771                imcb_error(ic, "Authentication failure");
    631772                imc_logout(ic, FALSE);
    632                 return;
     773                goto end;
    633774        } else {
    634775                // It didn't go well, output the error and return.
    635776                if (++td->http_fails >= 5)
    636777                        imcb_error(ic, "Could not retrieve " TWITTER_HOME_TIMELINE_URL ": %s",
    637778                                   twitter_parse_error(req));
    638779
     780                goto end;
     781        }
     782
     783        txl = g_new0(struct twitter_xml_list, 1);
     784        txl->list = NULL;
     785
     786        // Parse the data.
     787        parser = xt_new(NULL, txl);
     788        xt_feed(parser, req->reply_body, req->body_size);
     789        // The root <statuses> node should hold the list of statuses <status>
     790        twitter_xt_get_status_list(ic, parser->root, txl);
     791        xt_free(parser);
     792
     793        td->home_timeline_obj = txl;
     794
     795      end:
     796        td->home_timeline_done = TRUE;
     797
     798        twitter_flush_timeline(ic);
     799}
     800
     801/**
     802 * Callback for getting mentions.
     803 */
     804static void twitter_http_get_mentions(struct http_request *req)
     805{
     806        struct im_connection *ic = req->data;
     807        struct twitter_data *td;
     808        struct xt_parser *parser;
     809        struct twitter_xml_list *txl;
     810
     811        // Check if the connection is still active.
     812        if (!g_slist_find(twitter_connections, ic))
    639813                return;
     814
     815        td = ic->proto_data;
     816
     817        // Check if the HTTP request went well.
     818        if (req->status_code == 200) {
     819                td->http_fails = 0;
     820                if (!(ic->flags & OPT_LOGGED_IN))
     821                        imcb_connected(ic);
     822        } else if (req->status_code == 401) {
     823                imcb_error(ic, "Authentication failure");
     824                imc_logout(ic, FALSE);
     825                goto end;
     826        } else {
     827                // It didn't go well, output the error and return.
     828                if (++td->http_fails >= 5)
     829                        imcb_error(ic, "Could not retrieve " TWITTER_MENTIONS_URL ": %s",
     830                                   twitter_parse_error(req));
     831
     832                goto end;
    640833        }
    641834
    642835        txl = g_new0(struct twitter_xml_list, 1);
     
    649842        twitter_xt_get_status_list(ic, parser->root, txl);
    650843        xt_free(parser);
    651844
    652         // See if the user wants to see the messages in a groupchat window or as private messages.
    653         if (txl->list == NULL);
    654         else if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0)
    655                 twitter_groupchat(ic, txl->list);
    656         else
    657                 twitter_private_message_chat(ic, txl->list);
     845        td->mentions_obj = txl;
    658846
    659         // Free the structure. 
    660         txl_free(txl);
     847      end:
     848        td->mentions_done = TRUE;
     849
     850        twitter_flush_timeline(ic);
    661851}
    662852
    663853/**
     
    697887                td->http_fails = 0;
    698888        }
    699889
    700         if (!td->home_timeline_gc && g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0)
     890        if (!td->timeline_gc && g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0)
    701891                twitter_groupchat_init(ic);
    702892
    703893        txl = g_new0(struct twitter_xml_list, 1);
  • twitter_lib.h

    diff -uNr twitter/twitter_lib.h twitter-meh/twitter_lib.h
    old new  
    7777#define TWITTER_BLOCKS_CREATE_URL "/blocks/create/"
    7878#define TWITTER_BLOCKS_DESTROY_URL "/blocks/destroy/"
    7979
     80void twitter_get_timeline(struct im_connection *ic, gint64 next_cursor);
    8081void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor);
    8182void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor);
     83void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor);
    8284void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor);
    8385
    8486void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to);