source: protocols/twitter/twitter_lib.c @ eb6df6a

Last change on this file since eb6df6a was 8203da9, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-06-30T22:52:27Z

D'oh. Of course the getter functions should also treat next_cursor as a
64-bit integer. This code now successfully fetches lists with up to ~900
items. (Since this takes quite long, maybe there should be an upper limit.)

  • Property mode set to 100644
File size: 19.3 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/* For strptime(): */
25#define _XOPEN_SOURCE
26
27#include "twitter_http.h"
28#include "twitter.h"
29#include "bitlbee.h"
30#include "url.h"
31#include "misc.h"
32#include "base64.h"
33#include "xmltree.h"
34#include "twitter_lib.h"
35#include <ctype.h>
36#include <errno.h>
37
38#define TXL_STATUS 1
39#define TXL_USER 2
40#define TXL_ID 3
41
42struct twitter_xml_list {
43        int type;
44        gint64 next_cursor;
45        GSList *list;
46        gpointer data;
47};
48
49struct twitter_xml_user {
50        char *name;
51        char *screen_name;
52};
53
54struct twitter_xml_status {
55        time_t created_at;
56        char *text;
57        struct twitter_xml_user *user;
58        guint64 id;
59};
60
61static void twitter_groupchat_init(struct im_connection *ic);
62
63/**
64 * Frees a twitter_xml_user struct.
65 */
66static void txu_free(struct twitter_xml_user *txu)
67{
68        g_free(txu->name);
69        g_free(txu->screen_name);
70        g_free(txu);
71}
72
73
74/**
75 * Frees a twitter_xml_status struct.
76 */
77static void txs_free(struct twitter_xml_status *txs)
78{
79        g_free(txs->text);
80        txu_free(txs->user);
81        g_free(txs);
82}
83
84/**
85 * Free a twitter_xml_list struct.
86 * type is the type of list the struct holds.
87 */
88static void txl_free(struct twitter_xml_list *txl)
89{
90        GSList *l;
91        for ( l = txl->list; l ; l = g_slist_next(l) )
92                if (txl->type == TXL_STATUS)
93                        txs_free((struct twitter_xml_status *)l->data);
94                else if (txl->type == TXL_ID)
95                        g_free(l->data);
96        g_slist_free(txl->list);
97}
98
99/**
100 * Add a buddy if it is not allready added, set the status to logged in.
101 */
102static void twitter_add_buddy(struct im_connection *ic, char *name, const char *fullname)
103{
104        struct twitter_data *td = ic->proto_data;
105
106        // Check if the buddy is allready in the buddy list.
107        if (!imcb_find_buddy( ic, name ))
108        {
109                char *mode = set_getstr(&ic->acc->set, "mode");
110               
111                // The buddy is not in the list, add the buddy and set the status to logged in.
112                imcb_add_buddy( ic, name, NULL );
113                imcb_rename_buddy( ic, name, fullname );
114                if (g_strcasecmp(mode, "chat") == 0)
115                        imcb_chat_add_buddy( td->home_timeline_gc, name );
116                else if (g_strcasecmp(mode, "many") == 0)
117                        imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL );
118        }
119}
120
121/* Warning: May return a malloc()ed value, which will be free()d on the next
122   call. Only for short-term use. */
123static char *twitter_parse_error(struct http_request *req)
124{
125        static char *ret = NULL;
126        struct xt_parser *xp = NULL;
127        struct xt_node *node;
128       
129        g_free(ret);
130        ret = NULL;
131       
132        if (req->body_size > 0)
133        {
134                xp = xt_new(NULL, NULL);
135                xt_feed(xp, req->reply_body, req->body_size);
136               
137                if ((node = xt_find_node(xp->root, "hash")) &&
138                    (node = xt_find_node(node->children, "error")) &&
139                    node->text_len > 0)
140                {
141                        ret = g_strdup_printf("%s (%s)", req->status_string, node->text);
142                        xt_free(xp);
143                        return ret;
144                }
145               
146                xt_free(xp);
147        }
148       
149        return req->status_string;
150}
151
152static void twitter_http_get_friends_ids(struct http_request *req);
153
154/**
155 * Get the friends ids.
156 */
157void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor)
158{
159        // Primitive, but hey! It works...     
160        char* args[2];
161        args[0] = "cursor";
162        args[1] = g_strdup_printf ("%lld", (long long) next_cursor);
163        twitter_http(ic, TWITTER_FRIENDS_IDS_URL, twitter_http_get_friends_ids, ic, 0, args, 2);
164
165        g_free(args[1]);
166}
167
168/**
169 * Function to help fill a list.
170 */
171static xt_status twitter_xt_next_cursor( struct xt_node *node, struct twitter_xml_list *txl )
172{
173        char *end = NULL;
174       
175        if( node->text )
176                txl->next_cursor = g_ascii_strtoll( node->text, &end, 10 );
177        if( end == NULL )
178                txl->next_cursor = -1;
179
180        return XT_HANDLED;
181}
182
183/**
184 * Fill a list of ids.
185 */
186static xt_status twitter_xt_get_friends_id_list( struct xt_node *node, struct twitter_xml_list *txl )
187{
188        struct xt_node *child;
189       
190        // Set the list type.
191        txl->type = TXL_ID;
192
193        // The root <statuses> node should hold the list of statuses <status>
194        // Walk over the nodes children.
195        for( child = node->children ; child ; child = child->next )
196        {
197                if ( g_strcasecmp( "id", child->name ) == 0)
198                {
199                        // Add the item to the list.
200                        txl->list = g_slist_append (txl->list, g_memdup( child->text, child->text_len + 1 ));
201                }
202                else if ( g_strcasecmp( "next_cursor", child->name ) == 0)
203                {
204                        twitter_xt_next_cursor(child, txl);
205                }
206        }
207
208        return XT_HANDLED;
209}
210
211/**
212 * Callback for getting the friends ids.
213 */
214static void twitter_http_get_friends_ids(struct http_request *req)
215{
216        struct im_connection *ic;
217        struct xt_parser *parser;
218        struct twitter_xml_list *txl;
219        struct twitter_data *td;
220
221        ic = req->data;
222
223        // Check if the connection is still active.
224        if( !g_slist_find( twitter_connections, ic ) )
225                return;
226       
227        td = ic->proto_data;
228
229        // Check if the HTTP request went well.
230        if (req->status_code != 200) {
231                // It didn't go well, output the error and return.
232                if (++td->http_fails >= 5)
233                        imcb_error(ic, "Could not retrieve friends: %s", twitter_parse_error(req));
234               
235                return;
236        } else {
237                td->http_fails = 0;
238        }
239
240        txl = g_new0(struct twitter_xml_list, 1);
241
242        // Parse the data.
243        parser = xt_new( NULL, txl );
244        xt_feed( parser, req->reply_body, req->body_size );
245        twitter_xt_get_friends_id_list(parser->root, txl);
246        xt_free( parser );
247
248        if (txl->next_cursor)
249                twitter_get_friends_ids(ic, txl->next_cursor);
250
251        txl_free(txl);
252        g_free(txl);
253}
254
255/**
256 * Function to fill a twitter_xml_user struct.
257 * It sets:
258 *  - the name and
259 *  - the screen_name.
260 */
261static xt_status twitter_xt_get_user( struct xt_node *node, struct twitter_xml_user *txu )
262{
263        struct xt_node *child;
264
265        // Walk over the nodes children.
266        for( child = node->children ; child ; child = child->next )
267        {
268                if ( g_strcasecmp( "name", child->name ) == 0)
269                {
270                        txu->name = g_memdup( child->text, child->text_len + 1 );
271                }
272                else if (g_strcasecmp( "screen_name", child->name ) == 0)
273                {
274                        txu->screen_name = g_memdup( child->text, child->text_len + 1 );
275                }
276        }
277        return XT_HANDLED;
278}
279
280/**
281 * Function to fill a twitter_xml_list struct.
282 * It sets:
283 *  - all <user>s from the <users> element.
284 */
285static xt_status twitter_xt_get_users( struct xt_node *node, struct twitter_xml_list *txl )
286{
287        struct twitter_xml_user *txu;
288        struct xt_node *child;
289
290        // Set the type of the list.
291        txl->type = TXL_USER;
292
293        // The root <users> node should hold the list of users <user>
294        // Walk over the nodes children.
295        for( child = node->children ; child ; child = child->next )
296        {
297                if ( g_strcasecmp( "user", child->name ) == 0)
298                {
299                        txu = g_new0(struct twitter_xml_user, 1);
300                        twitter_xt_get_user(child, txu);
301                        // Put the item in the front of the list.
302                        txl->list = g_slist_prepend (txl->list, txu);
303                }
304        }
305
306        return XT_HANDLED;
307}
308
309/**
310 * Function to fill a twitter_xml_list struct.
311 * It calls twitter_xt_get_users to get the <user>s from a <users> element.
312 * It sets:
313 *  - the next_cursor.
314 */
315static xt_status twitter_xt_get_user_list( struct xt_node *node, struct twitter_xml_list *txl )
316{
317        struct xt_node *child;
318
319        // Set the type of the list.
320        txl->type = TXL_USER;
321
322        // The root <user_list> node should hold a users <users> element
323        // Walk over the nodes children.
324        for( child = node->children ; child ; child = child->next )
325        {
326                if ( g_strcasecmp( "users", child->name ) == 0)
327                {
328                        twitter_xt_get_users(child, txl);
329                }
330                else if ( g_strcasecmp( "next_cursor", child->name ) == 0)
331                {
332                        twitter_xt_next_cursor(child, txl);
333                }
334        }
335
336        return XT_HANDLED;
337}
338
339
340/**
341 * Function to fill a twitter_xml_status struct.
342 * It sets:
343 *  - the status text and
344 *  - the created_at timestamp and
345 *  - the status id and
346 *  - the user in a twitter_xml_user struct.
347 */
348static xt_status twitter_xt_get_status( struct xt_node *node, struct twitter_xml_status *txs )
349{
350        struct xt_node *child;
351
352        // Walk over the nodes children.
353        for( child = node->children ; child ; child = child->next )
354        {
355                if ( g_strcasecmp( "text", child->name ) == 0)
356                {
357                        txs->text = g_memdup( child->text, child->text_len + 1 );
358                }
359                else if (g_strcasecmp( "created_at", child->name ) == 0)
360                {
361                        struct tm parsed;
362                       
363                        /* Very sensitive to changes to the formatting of
364                           this field. :-( Also assumes the timezone used
365                           is UTC since C time handling functions suck. */
366                        if( strptime( child->text, "%a %b %d %H:%M:%S %z %Y", &parsed ) != NULL )
367                                txs->created_at = mktime_utc( &parsed );
368                }
369                else if (g_strcasecmp( "user", child->name ) == 0)
370                {
371                        txs->user = g_new0(struct twitter_xml_user, 1);
372                        twitter_xt_get_user( child, txs->user );
373                }
374                else if (g_strcasecmp( "id", child->name ) == 0)
375                {
376                        txs->id = g_ascii_strtoull (child->text, NULL, 10);
377                }
378        }
379        return XT_HANDLED;
380}
381
382/**
383 * Function to fill a twitter_xml_list struct.
384 * It sets:
385 *  - all <status>es within the <status> element and
386 *  - the next_cursor.
387 */
388static xt_status twitter_xt_get_status_list( struct xt_node *node, struct twitter_xml_list *txl )
389{
390        struct twitter_xml_status *txs;
391        struct xt_node *child;
392
393        // Set the type of the list.
394        txl->type = TXL_STATUS;
395
396        // The root <statuses> node should hold the list of statuses <status>
397        // Walk over the nodes children.
398        for( child = node->children ; child ; child = child->next )
399        {
400                if ( g_strcasecmp( "status", child->name ) == 0)
401                {
402                        txs = g_new0(struct twitter_xml_status, 1);
403                        twitter_xt_get_status(child, txs);
404                        // Put the item in the front of the list.
405                        txl->list = g_slist_prepend (txl->list, txs);
406                }
407                else if ( g_strcasecmp( "next_cursor", child->name ) == 0)
408                {
409                        twitter_xt_next_cursor(child, txl);
410                }
411        }
412
413        return XT_HANDLED;
414}
415
416static void twitter_http_get_home_timeline(struct http_request *req);
417
418/**
419 * Get the timeline.
420 */
421void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
422{
423        struct twitter_data *td = ic->proto_data;
424
425        char* args[4];
426        args[0] = "cursor";
427        args[1] = g_strdup_printf ("%lld", (long long) next_cursor);
428        if (td->home_timeline_id) {
429                args[2] = "since_id";
430                args[3] = g_strdup_printf ("%llu", (long long unsigned int) td->home_timeline_id);
431        }
432
433        twitter_http(ic, TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, args, td->home_timeline_id ? 4 : 2);
434
435        g_free(args[1]);
436        if (td->home_timeline_id) {
437                g_free(args[3]);
438        }
439}
440
441static void twitter_groupchat_init(struct im_connection *ic)
442{
443        char *name_hint;
444        struct groupchat *gc;
445        struct twitter_data *td = ic->proto_data;
446       
447        td->home_timeline_gc = gc = imcb_chat_new( ic, "home/timeline" );
448       
449        name_hint = g_strdup_printf( "Twitter_%s", ic->acc->user );
450        imcb_chat_name_hint( gc, name_hint );
451        g_free( name_hint );
452}
453
454/**
455 * Function that is called to see the statuses in a groupchat window.
456 */
457static void twitter_groupchat(struct im_connection *ic, GSList *list)
458{
459        struct twitter_data *td = ic->proto_data;
460        GSList *l = NULL;
461        struct twitter_xml_status *status;
462        struct groupchat *gc;
463
464        // Create a new groupchat if it does not exsist.
465        if (!td->home_timeline_gc)
466                twitter_groupchat_init(ic);
467       
468        gc = td->home_timeline_gc;
469        if (!gc->joined)
470                imcb_chat_add_buddy( gc, ic->acc->user );
471
472        for ( l = list; l ; l = g_slist_next(l) )
473        {
474                status = l->data;
475                twitter_add_buddy(ic, status->user->screen_name, status->user->name);
476               
477                strip_html(status->text);
478               
479                // Say it!
480                if (g_strcasecmp(td->user, status->user->screen_name) == 0)
481                        imcb_chat_log (gc, "Your Tweet: %s", status->text);
482                else
483                        imcb_chat_msg (gc, status->user->screen_name, status->text, 0, status->created_at );
484               
485                // Update the home_timeline_id to hold the highest id, so that by the next request
486                // we won't pick up the updates allready in the list.
487                td->home_timeline_id = td->home_timeline_id < status->id ? status->id : td->home_timeline_id;
488        }
489}
490
491/**
492 * Function that is called to see statuses as private messages.
493 */
494static void twitter_private_message_chat(struct im_connection *ic, GSList *list)
495{
496        struct twitter_data *td = ic->proto_data;
497        GSList *l = NULL;
498        struct twitter_xml_status *status;
499        char from[MAX_STRING];
500        gboolean mode_one;
501       
502        mode_one = g_strcasecmp( set_getstr( &ic->acc->set, "mode" ), "one" ) == 0;
503
504        if( mode_one )
505        {
506                g_snprintf( from, sizeof( from ) - 1, "twitter_%s", ic->acc->user );
507                from[MAX_STRING-1] = '\0';
508        }
509       
510        for ( l = list; l ; l = g_slist_next(l) )
511        {
512                char *text = NULL;
513               
514                status = l->data;
515               
516                strip_html( status->text );
517                if( mode_one )
518                        text = g_strdup_printf( "\002<\002%s\002>\002 %s",
519                                                status->user->screen_name, status->text );
520                else
521                        twitter_add_buddy(ic, status->user->screen_name, status->user->name);
522               
523                imcb_buddy_msg( ic,
524                                mode_one ? from : status->user->screen_name,
525                                mode_one ? text : status->text,
526                                0, status->created_at );
527               
528                // Update the home_timeline_id to hold the highest id, so that by the next request
529                // we won't pick up the updates allready in the list.
530                td->home_timeline_id = td->home_timeline_id < status->id ? status->id : td->home_timeline_id;
531               
532                g_free( text );
533        }
534}
535
536/**
537 * Callback for getting the home timeline.
538 */
539static void twitter_http_get_home_timeline(struct http_request *req)
540{
541        struct im_connection *ic = req->data;
542        struct twitter_data *td;
543        struct xt_parser *parser;
544        struct twitter_xml_list *txl;
545
546        // Check if the connection is still active.
547        if( !g_slist_find( twitter_connections, ic ) )
548                return;
549       
550        td = ic->proto_data;
551
552        // Check if the HTTP request went well.
553        if (req->status_code == 200)
554        {
555                td->http_fails = 0;
556                if (!(ic->flags & OPT_LOGGED_IN))
557                        imcb_connected(ic);
558        }
559        else if (req->status_code == 401)
560        {
561                imcb_error( ic, "Authentication failure" );
562                imc_logout( ic, FALSE );
563                return;
564        }
565        else
566        {
567                // It didn't go well, output the error and return.
568                if (++td->http_fails >= 5)
569                        imcb_error(ic, "Could not retrieve " TWITTER_HOME_TIMELINE_URL ": %s", twitter_parse_error(req));
570               
571                return;
572        }
573
574        txl = g_new0(struct twitter_xml_list, 1);
575        txl->list = NULL;
576
577        // Parse the data.
578        parser = xt_new( NULL, txl );
579        xt_feed( parser, req->reply_body, req->body_size );
580        // The root <statuses> node should hold the list of statuses <status>
581        twitter_xt_get_status_list(parser->root, txl);
582        xt_free( parser );
583
584        // See if the user wants to see the messages in a groupchat window or as private messages.
585        if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0)
586                twitter_groupchat(ic, txl->list);
587        else
588                twitter_private_message_chat(ic, txl->list);
589
590        // Free the structure. 
591        txl_free(txl);
592        g_free(txl);
593}
594
595/**
596 * Callback for getting (twitter)friends...
597 *
598 * Be afraid, be very afraid! This function will potentially add hundreds of "friends". "Who has
599 * hundreds of friends?" you wonder? You probably not, since you are reading the source of
600 * BitlBee... Get a life and meet new people!
601 */
602static void twitter_http_get_statuses_friends(struct http_request *req)
603{
604        struct im_connection *ic = req->data;
605        struct twitter_data *td;
606        struct xt_parser *parser;
607        struct twitter_xml_list *txl;
608        GSList *l = NULL;
609        struct twitter_xml_user *user;
610
611        // Check if the connection is still active.
612        if( !g_slist_find( twitter_connections, ic ) )
613                return;
614       
615        td = ic->proto_data;
616       
617        // Check if the HTTP request went well.
618        if (req->status_code == 401)
619        {
620                imcb_error( ic, "Authentication failure" );
621                imc_logout( ic, FALSE );
622                return;
623        } else if (req->status_code != 200) {
624                // It didn't go well, output the error and return.
625                imcb_error(ic, "Could not retrieve " TWITTER_SHOW_FRIENDS_URL ": %s", twitter_parse_error(req));
626                imc_logout( ic, TRUE );
627                return;
628        } else {
629                td->http_fails = 0;
630        }
631       
632        if( !td->home_timeline_gc &&
633            g_strcasecmp( set_getstr( &ic->acc->set, "mode" ), "chat" ) == 0 )
634                twitter_groupchat_init( ic );
635
636        txl = g_new0(struct twitter_xml_list, 1);
637        txl->list = NULL;
638
639        // Parse the data.
640        parser = xt_new( NULL, txl );
641        xt_feed( parser, req->reply_body, req->body_size );
642
643        // Get the user list from the parsed xml feed.
644        twitter_xt_get_user_list(parser->root, txl);
645        xt_free( parser );
646
647        // Add the users as buddies.
648        for ( l = txl->list; l ; l = g_slist_next(l) )
649        {
650                user = l->data;
651                twitter_add_buddy(ic, user->screen_name, user->name);
652        }
653
654        // if the next_cursor is set to something bigger then 0 there are more friends to gather.
655        if (txl->next_cursor > 0)
656        {
657                twitter_get_statuses_friends(ic, txl->next_cursor);
658        }
659        else
660        {
661                td->flags |= TWITTER_HAVE_FRIENDS;
662                twitter_login_finish(ic);
663        }
664       
665        // Free the structure.
666        txl_free(txl);
667        g_free(txl);
668}
669
670/**
671 * Get the friends.
672 */
673void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor)
674{
675        char* args[2];
676        args[0] = "cursor";
677        args[1] = g_strdup_printf ("%lld", (long long) next_cursor);
678
679        twitter_http(ic, TWITTER_SHOW_FRIENDS_URL, twitter_http_get_statuses_friends, ic, 0, args, 2);
680
681        g_free(args[1]);
682}
683
684/**
685 * Callback to use after sending a post request to twitter.
686 */
687static void twitter_http_post(struct http_request *req)
688{
689        struct im_connection *ic = req->data;
690
691        // Check if the connection is still active.
692        if( !g_slist_find( twitter_connections, ic ) )
693                return;
694
695        // Check if the HTTP request went well.
696        if (req->status_code != 200) {
697                // It didn't go well, output the error and return.
698                imcb_error(ic, "HTTP error: %s", twitter_parse_error(req));
699                return;
700        }
701}
702
703/**
704 * Function to POST a new status to twitter.
705 */ 
706void twitter_post_status(struct im_connection *ic, char* msg)
707{
708        char* args[2];
709        args[0] = "status";
710        args[1] = msg;
711        twitter_http(ic, TWITTER_STATUS_UPDATE_URL, twitter_http_post, ic, 1, args, 2);
712//      g_free(args[1]);
713}
714
715
716/**
717 * Function to POST a new message to twitter.
718 */
719void twitter_direct_messages_new(struct im_connection *ic, char *who, char *msg)
720{
721        char* args[4];
722        args[0] = "screen_name";
723        args[1] = who;
724        args[2] = "text";
725        args[3] = msg;
726        // Use the same callback as for twitter_post_status, since it does basically the same.
727        twitter_http(ic, TWITTER_DIRECT_MESSAGES_NEW_URL, twitter_http_post, ic, 1, args, 4);
728//      g_free(args[1]);
729//      g_free(args[3]);
730}
731
732void twitter_friendships_create_destroy(struct im_connection *ic, char *who, int create)
733{
734        char* args[2];
735        args[0] = "screen_name";
736        args[1] = who;
737        twitter_http(ic, create ? TWITTER_FRIENDSHIPS_CREATE_URL : TWITTER_FRIENDSHIPS_DESTROY_URL, twitter_http_post, ic, 1, args, 2);
738}
Note: See TracBrowser for help on using the repository browser.