source: protocols/twitter/twitter_lib.c @ 62d2cfb

Last change on this file since 62d2cfb was 62d2cfb, checked in by Geert Mulders <g.c.w.m.mulders@…>, at 2010-03-25T21:31:27Z

Added option to get tweeds either through groupchat or privmes.

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