Ticket #663: twitter-mentions.patch

File twitter-mentions.patch, 18.6 KB (added by meh. <meh@…>, at 2011-05-07T23:21:03Z)

First patch

  • protocols/twitter/twitter.c

    diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c
    index dbe9b98..293cfa1 100644
    a b  
    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;
    gboolean twitter_main_loop(gpointer data, gint fd, b_input_condition cond) 
    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;
    static void twitter_main_loop_start( struct im_connection *ic ) 
    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 = b_timeout_add(set_getint(&ic->acc->set, "fetch_every") * 1000, twitter_main_loop, ic);
    7272}
    7373
    7474static void twitter_oauth_start( struct im_connection *ic );
    static void twitter_oauth_start( struct im_connection *ic ); 
    7676void twitter_login_finish( struct im_connection *ic )
    7777{
    7878        struct twitter_data *td = ic->proto_data;
    79        
     79
     80    td->timeline_doing = FALSE;
     81
    8082        if( set_getbool( &ic->acc->set, "oauth" ) && !td->oauth_info )
    8183                twitter_oauth_start( ic );
    8284        else if( g_strcasecmp( set_getstr( &ic->acc->set, "mode" ), "one" ) != 0 &&
    static void twitter_init( account_t *acc ) 
    211213        }
    212214       
    213215        s = set_add( &acc->set, "auto_reply_timeout", "10800", set_eval_int, acc );
     216
     217    s = set_add( &acc->set, "fetch_every", "60", set_eval_int, acc );
     218        s->flags |= ACC_SET_OFFLINE_ONLY;
    214219       
    215220        s = set_add( &acc->set, "base_url", def_url, NULL, acc );
    216221        s->flags |= ACC_SET_OFFLINE_ONLY;
    static void twitter_init( account_t *acc ) 
    218223        s = set_add( &acc->set, "commands", "true", set_eval_bool, acc );
    219224       
    220225        s = set_add( &acc->set, "message_length", "140", set_eval_int, acc );
     226
     227    s = set_add( &acc->set, "include_mentions", "true", set_eval_bool, acc );
    221228       
    222229        s = set_add( &acc->set, "mode", "chat", set_eval_mode, acc );
    223230        s->flags |= ACC_SET_OFFLINE_ONLY;
    static void twitter_logout( struct im_connection *ic ) 
    286293        // Remove the main_loop function from the function queue.
    287294        b_event_remove(td->main_loop_id);
    288295
    289         if(td->home_timeline_gc)
    290                 imcb_chat_free(td->home_timeline_gc);
     296        if(td->timeline_gc)
     297                imcb_chat_free(td->timeline_gc);
    291298
    292299        if( td )
    293300        {
    static void twitter_chat_leave( struct groupchat *c ) 
    377384{
    378385        struct twitter_data *td = c->ic->proto_data;
    379386       
    380         if( c != td->home_timeline_gc )
     387        if( c != td->timeline_gc )
    381388                return; /* WTF? */
    382389       
    383390        /* If the user leaves the channel: Fine. Rejoin him/her once new
    384391           tweets come in. */
    385         imcb_chat_free(td->home_timeline_gc);
    386         td->home_timeline_gc = NULL;
     392        imcb_chat_free(td->timeline_gc);
     393        td->timeline_gc = NULL;
    387394}
    388395
    389396static void twitter_keepalive( struct im_connection *ic )
  • protocols/twitter/twitter.h

    diff --git a/protocols/twitter/twitter.h b/protocols/twitter/twitter.h
    index 98f1683..0ebf996 100644
    a b struct twitter_data 
    4242        char* user;
    4343        char* pass;
    4444        struct oauth_info *oauth_info;
    45         guint64 home_timeline_id;
     45
     46    gboolean timeline_doing;
     47    gpointer home_timeline_obj;
     48    gboolean home_timeline_done;
     49    gpointer mentions_obj;
     50    gboolean mentions_done;
     51
     52        guint64 timeline_id;
    4653        guint64 last_status_id; /* For undo */
    4754        gint main_loop_id;
    48         struct groupchat *home_timeline_gc;
     55        struct groupchat *timeline_gc;
    4956        gint http_fails;
    5057        twitter_flags_t flags;
    5158       
  • protocols/twitter/twitter_lib.c

    diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c
    index c8e4e76..7e1e21a 100644
    a b static void txu_free(struct twitter_xml_user *txu) 
    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);
    static void txl_free(struct twitter_xml_list *txl) 
    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        }
     114                else if (txl->type == TXL_ID) {
    110115                        g_free(l->data);
     116        }
     117    }
     118
    111119        g_slist_free(txl->list);
    112120        g_free(txl);
    113121}
    114122
    115123/**
    116  * Add a buddy if it is not allready added, set the status to logged in.
     124 * Compare status elements
     125 */
     126static gint twitter_compare_elements (gconstpointer a, gconstpointer b)
     127{
     128    struct twitter_xml_status *a_status = (struct twitter_xml_status *) a;
     129    struct twitter_xml_status *b_status = (struct twitter_xml_status *) b;
     130
     131    if (a_status->created_at < b_status->created_at) {
     132        return -1;
     133    }
     134    else if (a_status->created_at > b_status->created_at) {
     135        return 1;
     136    }
     137    else {
     138        return 0;
     139    }
     140}
     141
     142/**
     143 * Add a buddy if it is not already added, set the status to logged in.
    117144 */
    118145static void twitter_add_buddy(struct im_connection *ic, char *name, const char *fullname)
    119146{
    120147        struct twitter_data *td = ic->proto_data;
    121148
    122         // Check if the buddy is allready in the buddy list.
     149        // Check if the buddy is already in the buddy list.
    123150        if (!bee_user_by_handle( ic->bee, ic, name ))
    124151        {
    125152                char *mode = set_getstr(&ic->acc->set, "mode");
    static void twitter_add_buddy(struct im_connection *ic, char *name, const char * 
    132159                        /* Necessary so that nicks always get translated to the
    133160                           exact Twitter username. */
    134161                        imcb_buddy_nick_hint( ic, name, name );
    135                         imcb_chat_add_buddy( td->home_timeline_gc, name );
     162                        imcb_chat_add_buddy( td->timeline_gc, name );
    136163                }
    137164                else if (g_strcasecmp(mode, "many") == 0)
    138165                        imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL );
    static xt_status twitter_xt_get_status_list( struct im_connection *ic, struct xt 
    476503        return XT_HANDLED;
    477504}
    478505
    479 static void twitter_http_get_home_timeline(struct http_request *req);
    480 
    481 /**
    482  * Get the timeline.
    483  */
    484 void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
    485 {
    486         struct twitter_data *td = ic->proto_data;
    487 
    488         char* args[4];
    489         args[0] = "cursor";
    490         args[1] = g_strdup_printf ("%lld", (long long) next_cursor);
    491         if (td->home_timeline_id) {
    492                 args[2] = "since_id";
    493                 args[3] = g_strdup_printf ("%llu", (long long unsigned int) td->home_timeline_id);
    494         }
    495 
    496         twitter_http(ic, TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, args, td->home_timeline_id ? 4 : 2);
    497 
    498         g_free(args[1]);
    499         if (td->home_timeline_id) {
    500                 g_free(args[3]);
    501         }
    502 }
    503 
    504506static void twitter_groupchat_init(struct im_connection *ic)
    505507{
    506508        char *name_hint;
    static void twitter_groupchat_init(struct im_connection *ic) 
    508510        struct twitter_data *td = ic->proto_data;
    509511        GSList *l;
    510512       
    511         td->home_timeline_gc = gc = imcb_chat_new( ic, "home/timeline" );
     513        td->timeline_gc = gc = imcb_chat_new( ic, "twitter/timeline" );
    512514       
    513515        name_hint = g_strdup_printf( "%s_%s", td->prefix, ic->acc->user );
    514516        imcb_chat_name_hint( gc, name_hint );
    static void twitter_groupchat_init(struct im_connection *ic) 
    518520        {
    519521                bee_user_t *bu = l->data;
    520522                if( bu->ic == ic )
    521                         imcb_chat_add_buddy( td->home_timeline_gc, bu->handle );
     523                        imcb_chat_add_buddy( td->timeline_gc, bu->handle );
    522524        }
    523525}
    524526
    static void twitter_groupchat(struct im_connection *ic, GSList *list) 
    531533        GSList *l = NULL;
    532534        struct twitter_xml_status *status;
    533535        struct groupchat *gc;
     536    char* text;
     537    guint64 last_id = 0;
     538    size_t length;
    534539
    535540        // Create a new groupchat if it does not exsist.
    536         if (!td->home_timeline_gc)
     541        if (!td->timeline_gc)
    537542                twitter_groupchat_init(ic);
    538543       
    539         gc = td->home_timeline_gc;
     544        gc = td->timeline_gc;
    540545        if (!gc->joined)
    541546                imcb_chat_add_buddy( gc, ic->acc->user );
    542547
    543548        for ( l = list; l ; l = g_slist_next(l) )
    544549        {
    545550                status = l->data;
    546                 if (status->user == NULL || status->text == NULL)
     551                if (status->user == NULL || status->text == NULL || last_id == status->id)
    547552                        continue;
    548553
    549                 twitter_add_buddy(ic, status->user->screen_name, status->user->name);
    550                
     554        last_id = status->id;
     555
    551556                strip_html(status->text);
    552557               
    553558                // Say it!
    554                 if (g_strcasecmp(td->user, status->user->screen_name) == 0)
     559                if (g_strcasecmp(td->user, status->user->screen_name) == 0) {
    555560                        imcb_chat_log (gc, "You: %s", status->text);
    556                 else
    557                         imcb_chat_msg (gc, status->user->screen_name, status->text, 0, status->created_at );
     561        }
     562                else {
     563                twitter_add_buddy(ic, status->user->screen_name, status->user->name);
     564
     565            text = g_strdup_printf("@%s", td->user);
     566
     567            if (g_str_has_prefix(status->text, text)) {
     568                length = strlen(text); g_free(text);
     569
     570                text = g_strdup_printf("%s%s", ic->acc->user, &status->text[length]);
     571            }
     572            else {
     573                text = g_strdup(status->text);
     574            }
     575
     576                        imcb_chat_msg (gc, status->user->screen_name, text, 0, status->created_at );
     577
     578            g_free(text);
     579        }
    558580               
    559                 // Update the home_timeline_id to hold the highest id, so that by the next request
    560                 // we won't pick up the updates allready in the list.
    561                 td->home_timeline_id = td->home_timeline_id < status->id ? status->id : td->home_timeline_id;
     581                // Update the timeline_id to hold the highest id, so that by the next request
     582                // we won't pick up the updates already in the list.
     583                td->timeline_id = td->timeline_id < status->id ? status->id : td->timeline_id;
    562584        }
    563585}
    564586
    static void twitter_private_message_chat(struct im_connection *ic, GSList *list) 
    572594        struct twitter_xml_status *status;
    573595        char from[MAX_STRING];
    574596        gboolean mode_one;
     597    guint64 last_id = 0;
    575598       
    576599        mode_one = g_strcasecmp( set_getstr( &ic->acc->set, "mode" ), "one" ) == 0;
    577600
    static void twitter_private_message_chat(struct im_connection *ic, GSList *list) 
    586609                char *text = NULL;
    587610               
    588611                status = l->data;
     612                if (status->user == NULL || status->text == NULL || last_id == status->id)
     613                        continue;
     614
     615        last_id = status->id;
    589616               
    590617                strip_html( status->text );
    591618                if( mode_one )
    static void twitter_private_message_chat(struct im_connection *ic, GSList *list) 
    599626                                mode_one ? text : status->text,
    600627                                0, status->created_at );
    601628               
    602                 // Update the home_timeline_id to hold the highest id, so that by the next request
    603                 // we won't pick up the updates allready in the list.
    604                 td->home_timeline_id = td->home_timeline_id < status->id ? status->id : td->home_timeline_id;
     629                // Update the timeline_id to hold the highest id, so that by the next request
     630                // we won't pick up the updates already in the list.
     631                td->timeline_id = td->timeline_id < status->id ? status->id : td->timeline_id;
    605632               
    606633                g_free( text );
    607634        }
    608635}
    609636
     637static void twitter_http_get_home_timeline(struct http_request *req);
     638static void twitter_http_get_mentions(struct http_request *req);
     639
     640/**
     641 * Get the timeline with optionally mentions
     642 */
     643void twitter_get_timeline(struct im_connection *ic, gint64 next_cursor)
     644{
     645    struct twitter_data *td = ic->proto_data;
     646    gboolean include_mentions = set_getbool(&ic->acc->set, "include_mentions");
     647
     648    if (td->timeline_doing) {
     649        return;
     650    }
     651
     652    td->timeline_doing = TRUE;
     653
     654    twitter_get_home_timeline(ic, next_cursor);
     655
     656    if (include_mentions) {
     657        twitter_get_mentions(ic, next_cursor);
     658    }
     659}
     660
     661void twitter_flush_timeline(struct im_connection *ic)
     662{
     663    struct twitter_data *td = ic->proto_data;
     664    gboolean include_mentions = set_getbool(&ic->acc->set, "include_mentions");
     665    struct twitter_xml_list *home_timeline = td->home_timeline_obj;
     666    struct twitter_xml_list *mentions = td->mentions_obj;
     667    GSList *output = NULL;
     668    GSList *l;
     669
     670    if (!td->home_timeline_done) {
     671        return;
     672    }
     673
     674    if (include_mentions && !td->mentions_done) {
     675        return;
     676    }
     677
     678    if (home_timeline && home_timeline->list) {
     679        for (l = home_timeline->list; l ; l = g_slist_next(l)) {
     680            output = g_slist_insert_sorted(output, l->data, twitter_compare_elements);
     681        }
     682    }
     683
     684    if (include_mentions && mentions && mentions->list) {
     685        for (l = mentions->list; l ; l = g_slist_next(l)) {
     686            if (output && twitter_compare_elements(l->data, output->data) < 0) {
     687                continue;
     688            }
     689           
     690            output = g_slist_insert_sorted(output, l->data, twitter_compare_elements);
     691        }
     692    }
     693
     694        // See if the user wants to see the messages in a groupchat window or as private messages.
     695        if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0)
     696                twitter_groupchat(ic, output);
     697        else
     698                twitter_private_message_chat(ic, output);
     699
     700    g_slist_free(output);
     701
     702        if (home_timeline && home_timeline->list) {
     703        txl_free(home_timeline);
     704    }
     705
     706    if (mentions && mentions->list) {
     707        txl_free(mentions);
     708    }
     709
     710    td->home_timeline_done = FALSE;
     711    td->mentions_done = FALSE;
     712    td->timeline_doing = FALSE;
     713}
     714
     715/**
     716 * Get the timeline.
     717 */
     718void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
     719{
     720        struct twitter_data *td = ic->proto_data;
     721
     722    td->home_timeline_obj = NULL;
     723    td->home_timeline_done = FALSE;
     724
     725        char* args[4];
     726        args[0] = "cursor";
     727        args[1] = g_strdup_printf ("%lld", (long long) next_cursor);
     728        if (td->timeline_id) {
     729                args[2] = "since_id";
     730                args[3] = g_strdup_printf ("%llu", (long long unsigned int) td->timeline_id);
     731        }
     732
     733        twitter_http(ic, TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, args, td->timeline_id ? 4 : 2);
     734
     735        g_free(args[1]);
     736        if (td->timeline_id) {
     737                g_free(args[3]);
     738        }
     739}
     740
     741/**
     742 * Get mentions.
     743 */
     744void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)
     745{
     746        struct twitter_data *td = ic->proto_data;
     747
     748    td->mentions_obj = NULL;
     749    td->mentions_done = FALSE;
     750
     751        char* args[4];
     752        args[0] = "cursor";
     753        args[1] = g_strdup_printf ("%lld", (long long) next_cursor);
     754        if (td->timeline_id) {
     755                args[2] = "since_id";
     756                args[3] = g_strdup_printf ("%llu", (long long unsigned int) td->timeline_id);
     757        }
     758
     759        twitter_http(ic, TWITTER_MENTIONS_URL, twitter_http_get_mentions, ic, 0, args, td->timeline_id ? 4 : 2);
     760
     761        g_free(args[1]);
     762        if (td->timeline_id) {
     763                g_free(args[3]);
     764        }
     765}
     766
    610767/**
    611768 * Callback for getting the home timeline.
    612769 */
    static void twitter_http_get_home_timeline(struct http_request *req) 
    634791        {
    635792                imcb_error( ic, "Authentication failure" );
    636793                imc_logout( ic, FALSE );
    637                 return;
     794                goto end;
    638795        }
    639796        else
    640797        {
    static void twitter_http_get_home_timeline(struct http_request *req) 
    642799                if (++td->http_fails >= 5)
    643800                        imcb_error(ic, "Could not retrieve " TWITTER_HOME_TIMELINE_URL ": %s", twitter_parse_error(req));
    644801               
    645                 return;
     802                goto end;
    646803        }
    647804
    648805        txl = g_new0(struct twitter_xml_list, 1);
    static void twitter_http_get_home_timeline(struct http_request *req) 
    655812        twitter_xt_get_status_list(ic, parser->root, txl);
    656813        xt_free( parser );
    657814
    658         // See if the user wants to see the messages in a groupchat window or as private messages.
    659         if (txl->list == NULL)
    660                 ;
    661         else if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0)
    662                 twitter_groupchat(ic, txl->list);
     815    td->home_timeline_obj = txl;
     816
     817end:
     818    td->home_timeline_done = TRUE;
     819
     820    twitter_flush_timeline(ic);
     821}
     822
     823/**
     824 * Callback for getting mentions.
     825 */
     826static void twitter_http_get_mentions(struct http_request *req)
     827{
     828        struct im_connection *ic = req->data;
     829        struct twitter_data *td;
     830        struct xt_parser *parser;
     831        struct twitter_xml_list *txl;
     832
     833        // Check if the connection is still active.
     834        if( !g_slist_find( twitter_connections, ic ) )
     835                return;
     836       
     837        td = ic->proto_data;
     838
     839        // Check if the HTTP request went well.
     840        if (req->status_code == 200)
     841        {
     842                td->http_fails = 0;
     843                if (!(ic->flags & OPT_LOGGED_IN))
     844                        imcb_connected(ic);
     845        }
     846        else if (req->status_code == 401)
     847        {
     848                imcb_error( ic, "Authentication failure" );
     849                imc_logout( ic, FALSE );
     850        goto end;
     851        }
    663852        else
    664                 twitter_private_message_chat(ic, txl->list);
     853        {
     854                // It didn't go well, output the error and return.
     855                if (++td->http_fails >= 5)
     856                        imcb_error(ic, "Could not retrieve " TWITTER_MENTIONS_URL ": %s", twitter_parse_error(req));
     857               
     858        goto end;
     859        }
    665860
    666         // Free the structure. 
    667         txl_free(txl);
     861        txl = g_new0(struct twitter_xml_list, 1);
     862        txl->list = NULL;
     863
     864        // Parse the data.
     865        parser = xt_new( NULL, txl );
     866        xt_feed( parser, req->reply_body, req->body_size );
     867        // The root <statuses> node should hold the list of statuses <status>
     868        twitter_xt_get_status_list(ic, parser->root, txl);
     869        xt_free( parser );
     870
     871    td->mentions_obj = txl;
     872
     873end:
     874    td->mentions_done = TRUE;
     875
     876    twitter_flush_timeline(ic);
    668877}
    669878
    670879/**
    static void twitter_http_get_statuses_friends(struct http_request *req) 
    704913                td->http_fails = 0;
    705914        }
    706915       
    707         if( !td->home_timeline_gc &&
     916        if( !td->timeline_gc &&
    708917            g_strcasecmp( set_getstr( &ic->acc->set, "mode" ), "chat" ) == 0 )
    709918                twitter_groupchat_init( ic );
    710919
  • protocols/twitter/twitter_lib.h

    diff --git a/protocols/twitter/twitter_lib.h b/protocols/twitter/twitter_lib.h
    index 24b4a08..6ebbdfe 100644
    a b  
    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);