source: protocols/twitter/twitter_lib.c @ 7a90d02

Last change on this file since 7a90d02 was 08579a1, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-04-08T00:42:11Z

Parse timestamps in tweets.

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