source: protocols/twitter/twitter_lib.c @ 3bd4a93

Last change on this file since 3bd4a93 was 3bd4a93, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-04-13T17:36:16Z

Suppress HTTP error messages unless we get five or more in a row.

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