source: protocols/twitter/twitter_lib.c @ d569019

Last change on this file since d569019 was d569019, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-04-07T00:27:51Z

A little more cleanup.

  • Property mode set to 100644
File size: 16.1 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
[1b221e0]39struct twitter_xml_list {
[62d2cfb]40        int type;
[1b221e0]41        int next_cursor;
42        GSList *list;
43        gpointer data;
44};
45
46struct twitter_xml_user {
47        char *name;
48        char *screen_name;
49};
50
51struct twitter_xml_status {
52        char *created_at;
53        char *text;
54        struct twitter_xml_user *user;
55        guint64 id;
56};
57
[62d2cfb]58/**
59 * Frees a twitter_xml_user struct.
60 */
61static void txu_free(struct twitter_xml_user *txu)
62{
63        g_free(txu->name);
64        g_free(txu->screen_name);
[2abceca]65        g_free(txu);
[62d2cfb]66}
67
68
69/**
70 * Frees a twitter_xml_status struct.
71 */
72static void txs_free(struct twitter_xml_status *txs)
73{
74        g_free(txs->created_at);
75        g_free(txs->text);
76        txu_free(txs->user);
[2abceca]77        g_free(txs);
[62d2cfb]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.
[d569019]101        if (!imcb_find_buddy( ic, name ))
[62d2cfb]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.
[2abceca]133        txl->next_cursor = node->text != NULL ? atoi(node->text) : -1;
[1b221e0]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.
[2abceca]155                        txl->list = g_slist_append (txl->list, g_memdup( child->text, child->text_len + 1 ));
[1b221e0]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
190        // Parse the data.
191        parser = xt_new( NULL, txl );
192        xt_feed( parser, req->reply_body, req->body_size );
193        twitter_xt_get_friends_id_list(parser->root, txl);
194        xt_free( parser );
195
196        if (txl->next_cursor)
197                twitter_get_friends_ids(ic, txl->next_cursor);
198
[62d2cfb]199        txl_free(txl);
[1b221e0]200        g_free(txl);
201}
202
203/**
204 * Function to fill a twitter_xml_user struct.
205 * It sets:
206 *  - the name and
207 *  - the screen_name.
208 */
209static xt_status twitter_xt_get_user( struct xt_node *node, struct twitter_xml_user *txu )
210{
211        struct xt_node *child;
212
213        // Walk over the nodes children.
214        for( child = node->children ; child ; child = child->next )
215        {
216                if ( g_strcasecmp( "name", child->name ) == 0)
217                {
218                        txu->name = g_memdup( child->text, child->text_len + 1 );
219                }
220                else if (g_strcasecmp( "screen_name", child->name ) == 0)
221                {
222                        txu->screen_name = g_memdup( child->text, child->text_len + 1 );
223                }
224        }
225        return XT_HANDLED;
226}
227
[62d2cfb]228/**
229 * Function to fill a twitter_xml_list struct.
230 * It sets:
231 *  - all <user>s from the <users> element.
232 */
233static xt_status twitter_xt_get_users( struct xt_node *node, struct twitter_xml_list *txl )
234{
235        struct twitter_xml_user *txu;
236        struct xt_node *child;
237
238        // Set the type of the list.
239        txl->type = TXL_USER;
240
241        // The root <users> node should hold the list of users <user>
242        // Walk over the nodes children.
243        for( child = node->children ; child ; child = child->next )
244        {
245                if ( g_strcasecmp( "user", child->name ) == 0)
246                {
247                        txu = g_new0(struct twitter_xml_user, 1);
248                        twitter_xt_get_user(child, txu);
249                        // Put the item in the front of the list.
250                        txl->list = g_slist_prepend (txl->list, txu);
251                }
252        }
253
254        return XT_HANDLED;
255}
256
257/**
258 * Function to fill a twitter_xml_list struct.
259 * It calls twitter_xt_get_users to get the <user>s from a <users> element.
260 * It sets:
261 *  - the next_cursor.
262 */
263static xt_status twitter_xt_get_user_list( struct xt_node *node, struct twitter_xml_list *txl )
264{
265        struct xt_node *child;
266
267        // Set the type of the list.
268        txl->type = TXL_USER;
269
270        // The root <user_list> node should hold a users <users> element
271        // Walk over the nodes children.
272        for( child = node->children ; child ; child = child->next )
273        {
274                if ( g_strcasecmp( "users", child->name ) == 0)
275                {
276                        twitter_xt_get_users(child, txl);
277                }
278                else if ( g_strcasecmp( "next_cursor", child->name ) == 0)
279                {
280                        twitter_xt_next_cursor(child, txl);
281                }
282        }
283
284        return XT_HANDLED;
285}
286
287
[1b221e0]288/**
289 * Function to fill a twitter_xml_status struct.
290 * It sets:
291 *  - the status text and
292 *  - the created_at timestamp and
293 *  - the status id and
294 *  - the user in a twitter_xml_user struct.
295 */
296static xt_status twitter_xt_get_status( struct xt_node *node, struct twitter_xml_status *txs )
297{
298        struct xt_node *child;
299
300        // Walk over the nodes children.
301        for( child = node->children ; child ; child = child->next )
302        {
303                if ( g_strcasecmp( "text", child->name ) == 0)
304                {
305                        txs->text = g_memdup( child->text, child->text_len + 1 );
306                }
307                else if (g_strcasecmp( "created_at", child->name ) == 0)
308                {
309                        txs->created_at = g_memdup( child->text, child->text_len + 1 );
310                }
311                else if (g_strcasecmp( "user", child->name ) == 0)
312                {
313                        txs->user = g_new0(struct twitter_xml_user, 1);
314                        twitter_xt_get_user( child, txs->user );
315                }
316                else if (g_strcasecmp( "id", child->name ) == 0)
317                {
318                        txs->id = g_ascii_strtoull (child->text, NULL, 10);
319                }
320        }
321        return XT_HANDLED;
322}
323
324/**
325 * Function to fill a twitter_xml_list struct.
326 * It sets:
327 *  - all <status>es within the <status> element and
328 *  - the next_cursor.
329 */
330static xt_status twitter_xt_get_status_list( struct xt_node *node, struct twitter_xml_list *txl )
331{
332        struct twitter_xml_status *txs;
333        struct xt_node *child;
334
[62d2cfb]335        // Set the type of the list.
336        txl->type = TXL_STATUS;
337
[1b221e0]338        // The root <statuses> node should hold the list of statuses <status>
339        // Walk over the nodes children.
340        for( child = node->children ; child ; child = child->next )
341        {
342                if ( g_strcasecmp( "status", child->name ) == 0)
343                {
344                        txs = g_new0(struct twitter_xml_status, 1);
345                        twitter_xt_get_status(child, txs);
346                        // Put the item in the front of the list.
347                        txl->list = g_slist_prepend (txl->list, txs);
348                }
349                else if ( g_strcasecmp( "next_cursor", child->name ) == 0)
350                {
351                        twitter_xt_next_cursor(child, txl);
352                }
353        }
354
355        return XT_HANDLED;
356}
357
358static void twitter_http_get_home_timeline(struct http_request *req);
359
360/**
361 * Get the timeline.
362 */
363void twitter_get_home_timeline(struct im_connection *ic, int next_cursor)
364{
365        struct twitter_data *td = ic->proto_data;
366
367        char* args[4];
368        args[0] = "cursor";
369        args[1] = g_strdup_printf ("%d", next_cursor);
370        if (td->home_timeline_id) {
371                args[2] = "since_id";
[0519b0a]372                args[3] = g_strdup_printf ("%llu", (long long unsigned int) td->home_timeline_id);
[1b221e0]373        }
374
375        twitter_http(TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, td->user, td->pass, args, td->home_timeline_id ? 4 : 2);
376
377        g_free(args[1]);
378        if (td->home_timeline_id) {
379                g_free(args[3]);
380        }
381}
382
[62d2cfb]383/**
384 * Function that is called to see the statuses in a groupchat window.
385 */
386static void twitter_groupchat(struct im_connection *ic, GSList *list)
387{
388        struct twitter_data *td = ic->proto_data;
389        GSList *l = NULL;
390        struct twitter_xml_status *status;
391        struct groupchat *gc;
392
393        // Create a new groupchat if it does not exsist.
394        if (!td->home_timeline_gc)
395        {   
396                td->home_timeline_gc = gc = imcb_chat_new( ic, "home/timeline" );
397                // Add the current user to the chat...
398                imcb_chat_add_buddy( gc, ic->acc->user );
399        }
400        else
401        {   
402                gc = td->home_timeline_gc;
403        }
404
405        for ( l = list; l ; l = g_slist_next(l) )
406        {
407                status = l->data;
408                twitter_add_buddy(ic, status->user->screen_name);
[d569019]409               
[62d2cfb]410                // Say it!
[d569019]411                if (g_strcasecmp(td->user, status->user->screen_name) == 0)
412                        imcb_chat_log (gc, "Your Tweet: %s", status->text);
413                else
414                        imcb_chat_msg (gc, status->user->screen_name, status->text, 0, 0 );
415               
[62d2cfb]416                // Update the home_timeline_id to hold the highest id, so that by the next request
417                // we won't pick up the updates allready in the list.
418                td->home_timeline_id = td->home_timeline_id < status->id ? status->id : td->home_timeline_id;
419        }
420}
421
422/**
423 * Function that is called to see statuses as private messages.
424 */
425static void twitter_private_message_chat(struct im_connection *ic, GSList *list)
426{
427        struct twitter_data *td = ic->proto_data;
428        GSList *l = NULL;
429        struct twitter_xml_status *status;
430
431        for ( l = list; l ; l = g_slist_next(l) )
432        {
433                status = l->data;
434                imcb_buddy_msg( ic, status->user->screen_name, status->text, 0, 0 );
435                // Update the home_timeline_id to hold the highest id, so that by the next request
436                // we won't pick up the updates allready in the list.
437                td->home_timeline_id = td->home_timeline_id < status->id ? status->id : td->home_timeline_id;
438        }
439}
440
[1b221e0]441/**
442 * Callback for getting the home timeline.
443 */
444static void twitter_http_get_home_timeline(struct http_request *req)
445{
[62d2cfb]446        struct im_connection *ic = req->data;
[1b221e0]447        struct xt_parser *parser;
448        struct twitter_xml_list *txl;
[62d2cfb]449
450        // Check if the connection is still active.
451        if( !g_slist_find( twitter_connections, ic ) )
452                return;
[1b221e0]453
454        // Check if the HTTP request went well.
455        if (req->status_code != 200) {
456                // It didn't go well, output the error and return.
[2abceca]457                imcb_error(ic, "Could not retrieve " TWITTER_HOME_TIMELINE_URL ". HTTP STATUS: %d", req->status_code);
[1b221e0]458                return;
459        }
460
461        txl = g_new0(struct twitter_xml_list, 1);
462        txl->list = NULL;
[62d2cfb]463
[1b221e0]464        // Parse the data.
465        parser = xt_new( NULL, txl );
466        xt_feed( parser, req->reply_body, req->body_size );
467        // The root <statuses> node should hold the list of statuses <status>
468        twitter_xt_get_status_list(parser->root, txl);
469        xt_free( parser );
470
[62d2cfb]471        // See if the user wants to see the messages in a groupchat window or as private messages.
472        if (set_getbool( &ic->acc->set, "use_groupchat" ))
473                twitter_groupchat(ic, txl->list);
[b4dd253]474        else
[62d2cfb]475                twitter_private_message_chat(ic, txl->list);
[1b221e0]476
477        // Free the structure. 
[62d2cfb]478        txl_free(txl);
[1b221e0]479        g_free(txl);
480}
481
482/**
[62d2cfb]483 * Callback for getting (twitter)friends...
484 *
485 * Be afraid, be very afraid! This function will potentially add hundreds of "friends". "Who has
486 * hundreds of friends?" you wonder? You probably not, since you are reading the source of
487 * BitlBee... Get a life and meet new people!
[1b221e0]488 */
[62d2cfb]489static void twitter_http_get_statuses_friends(struct http_request *req)
[1b221e0]490{
[62d2cfb]491        struct im_connection *ic = req->data;
492        struct xt_parser *parser;
493        struct twitter_xml_list *txl;
[2abceca]494        GSList *l = NULL;
495        struct twitter_xml_user *user;
[62d2cfb]496
497        // Check if the connection is still active.
498        if( !g_slist_find( twitter_connections, ic ) )
499                return;
500
501        // Check if the HTTP request went well.
502        if (req->status_code != 200) {
503                // It didn't go well, output the error and return.
[2abceca]504                imcb_error(ic, "Could not retrieve " TWITTER_SHOW_FRIENDS_URL " HTTP STATUS: %d", req->status_code);
[62d2cfb]505                return;
506        }
507
508        txl = g_new0(struct twitter_xml_list, 1);
509        txl->list = NULL;
510
511        // Parse the data.
512        parser = xt_new( NULL, txl );
513        xt_feed( parser, req->reply_body, req->body_size );
514
515        // Get the user list from the parsed xml feed.
516        twitter_xt_get_user_list(parser->root, txl);
517        xt_free( parser );
518
519        // Add the users as buddies.
[1b221e0]520        for ( l = txl->list; l ; l = g_slist_next(l) )
[62d2cfb]521        {
522                user = l->data;
523                twitter_add_buddy(ic, user->screen_name);
524        }
[1b221e0]525
[62d2cfb]526        // if the next_cursor is set to something bigger then 0 there are more friends to gather.
527        if (txl->next_cursor > 0)
528                twitter_get_statuses_friends(ic, txl->next_cursor);
529
530        // Free the structure.
531        txl_free(txl);
532        g_free(txl);
[1b221e0]533}
534
535/**
[62d2cfb]536 * Get the friends.
[1b221e0]537 */
[62d2cfb]538void twitter_get_statuses_friends(struct im_connection *ic, int next_cursor)
[1b221e0]539{
[62d2cfb]540        struct twitter_data *td = ic->proto_data;
541
542        char* args[2];
543        args[0] = "cursor";
544        args[1] = g_strdup_printf ("%d", next_cursor);
545
546        twitter_http(TWITTER_SHOW_FRIENDS_URL, twitter_http_get_statuses_friends, ic, 0, td->user, td->pass, args, 2);
547
548        g_free(args[1]);
[1b221e0]549}
550
551/**
552 * Callback after sending a new update to twitter.
553 */
554static void twitter_http_post_status(struct http_request *req)
555{
556        struct im_connection *ic = req->data;
557
[62d2cfb]558        // Check if the connection is still active.
559        if( !g_slist_find( twitter_connections, ic ) )
560                return;
561
[1b221e0]562        // Check if the HTTP request went well.
563        if (req->status_code != 200) {
564                // It didn't go well, output the error and return.
[2abceca]565                imcb_error(ic, "Could not post tweet... HTTP STATUS: %d", req->status_code);
[1b221e0]566                return;
567        }
568}
569
570/**
571 * Function to POST a new status to twitter.
572 */ 
573void twitter_post_status(struct im_connection *ic, char* msg)
574{
575        struct twitter_data *td = ic->proto_data;
576
577        char* args[2];
578        args[0] = "status";
579        args[1] = msg;
580        twitter_http(TWITTER_STATUS_UPDATE_URL, twitter_http_post_status, ic, 1, td->user, td->pass, args, 2);
[62d2cfb]581//      g_free(args[1]);
[1b221e0]582}
583
584
[62d2cfb]585/**
586 * Function to POST a new message to twitter.
587 */
588void twitter_direct_messages_new(struct im_connection *ic, char *who, char *msg)
589{
590        struct twitter_data *td = ic->proto_data;
591
592        char* args[4];
593        args[0] = "screen_name";
594        args[1] = who;
595        args[2] = "text";
596        args[3] = msg;
597        // Use the same callback as for twitter_post_status, since it does basically the same.
598        twitter_http(TWITTER_DIRECT_MESSAGES_NEW_URL, twitter_http_post_status, ic, 1, td->user, td->pass, args, 4);
599//      g_free(args[1]);
600//      g_free(args[3]);
601}
Note: See TracBrowser for help on using the repository browser.