source: protocols/twitter/twitter_lib.c @ 2abceca

Last change on this file since 2abceca was 2abceca, checked in by Geert Mulders <g.c.w.m.mulders@…>, at 2010-04-06T17:25:51Z

Updates made as a result to the comments on the review.

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