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

Last change on this file since 2e3a857 was 1014cab, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-04-07T00:46:38Z

In groupchat mode, make contacts show up in the room instead of in &bitlbee.
And clean up the room when disabling the Twitter account.

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