source: protocols/twitter/twitter_lib.c @ ec2ebcc

Last change on this file since ec2ebcc was cca0692, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-04-09T00:40:38Z

Added imcb_chat_nick_hint() and use it in the Twitter module to get saner
channel names.

This also closes bug #577, making the Skype module a bit nicer.

  • Property mode set to 100644
File size: 16.6 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                char *name_hint = g_strdup_printf( "Twitter_%s", ic->acc->user );
410                td->home_timeline_gc = gc = imcb_chat_new( ic, "home/timeline" );
411                imcb_chat_name_hint( gc, name_hint );
412                g_free( name_hint );
413                // Add the current user to the chat...
414                imcb_chat_add_buddy( gc, ic->acc->user );
415        }
416        else
417        {   
418                gc = td->home_timeline_gc;
419        }
420
421        for ( l = list; l ; l = g_slist_next(l) )
422        {
423                status = l->data;
424                twitter_add_buddy(ic, status->user->screen_name);
425               
426                // Say it!
427                if (g_strcasecmp(td->user, status->user->screen_name) == 0)
428                        imcb_chat_log (gc, "Your Tweet: %s", status->text);
429                else
430                        imcb_chat_msg (gc, status->user->screen_name, status->text, 0, status->created_at );
431               
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 * Function that is called to see statuses as private messages.
440 */
441static void twitter_private_message_chat(struct im_connection *ic, GSList *list)
442{
443        struct twitter_data *td = ic->proto_data;
444        GSList *l = NULL;
445        struct twitter_xml_status *status;
446
447        for ( l = list; l ; l = g_slist_next(l) )
448        {
449                status = l->data;
450                imcb_buddy_msg( ic, status->user->screen_name, status->text, 0, status->created_at );
451                // Update the home_timeline_id to hold the highest id, so that by the next request
452                // we won't pick up the updates allready in the list.
453                td->home_timeline_id = td->home_timeline_id < status->id ? status->id : td->home_timeline_id;
454        }
455}
456
457/**
458 * Callback for getting the home timeline.
459 */
460static void twitter_http_get_home_timeline(struct http_request *req)
461{
462        struct im_connection *ic = req->data;
463        struct xt_parser *parser;
464        struct twitter_xml_list *txl;
465
466        // Check if the connection is still active.
467        if( !g_slist_find( twitter_connections, ic ) )
468                return;
469
470        // Check if the HTTP request went well.
471        if (req->status_code != 200) {
472                // It didn't go well, output the error and return.
473                imcb_error(ic, "Could not retrieve " TWITTER_HOME_TIMELINE_URL ". HTTP STATUS: %d", req->status_code);
474                return;
475        }
476
477        txl = g_new0(struct twitter_xml_list, 1);
478        txl->list = NULL;
479
480        // Parse the data.
481        parser = xt_new( NULL, txl );
482        xt_feed( parser, req->reply_body, req->body_size );
483        // The root <statuses> node should hold the list of statuses <status>
484        twitter_xt_get_status_list(parser->root, txl);
485        xt_free( parser );
486
487        // See if the user wants to see the messages in a groupchat window or as private messages.
488        if (set_getbool( &ic->acc->set, "use_groupchat" ))
489                twitter_groupchat(ic, txl->list);
490        else
491                twitter_private_message_chat(ic, txl->list);
492
493        // Free the structure. 
494        txl_free(txl);
495        g_free(txl);
496}
497
498/**
499 * Callback for getting (twitter)friends...
500 *
501 * Be afraid, be very afraid! This function will potentially add hundreds of "friends". "Who has
502 * hundreds of friends?" you wonder? You probably not, since you are reading the source of
503 * BitlBee... Get a life and meet new people!
504 */
505static void twitter_http_get_statuses_friends(struct http_request *req)
506{
507        struct im_connection *ic = req->data;
508        struct xt_parser *parser;
509        struct twitter_xml_list *txl;
510        GSList *l = NULL;
511        struct twitter_xml_user *user;
512
513        // Check if the connection is still active.
514        if( !g_slist_find( twitter_connections, ic ) )
515                return;
516
517        // Check if the HTTP request went well.
518        if (req->status_code != 200) {
519                // It didn't go well, output the error and return.
520                imcb_error(ic, "Could not retrieve " TWITTER_SHOW_FRIENDS_URL " HTTP STATUS: %d", req->status_code);
521                return;
522        }
523
524        txl = g_new0(struct twitter_xml_list, 1);
525        txl->list = NULL;
526
527        // Parse the data.
528        parser = xt_new( NULL, txl );
529        xt_feed( parser, req->reply_body, req->body_size );
530
531        // Get the user list from the parsed xml feed.
532        twitter_xt_get_user_list(parser->root, txl);
533        xt_free( parser );
534
535        // Add the users as buddies.
536        for ( l = txl->list; l ; l = g_slist_next(l) )
537        {
538                user = l->data;
539                twitter_add_buddy(ic, user->screen_name);
540        }
541
542        // if the next_cursor is set to something bigger then 0 there are more friends to gather.
543        if (txl->next_cursor > 0)
544                twitter_get_statuses_friends(ic, txl->next_cursor);
545
546        // Free the structure.
547        txl_free(txl);
548        g_free(txl);
549}
550
551/**
552 * Get the friends.
553 */
554void twitter_get_statuses_friends(struct im_connection *ic, int next_cursor)
555{
556        struct twitter_data *td = ic->proto_data;
557
558        char* args[2];
559        args[0] = "cursor";
560        args[1] = g_strdup_printf ("%d", next_cursor);
561
562        twitter_http(TWITTER_SHOW_FRIENDS_URL, twitter_http_get_statuses_friends, ic, 0, td->user, td->pass, args, 2);
563
564        g_free(args[1]);
565}
566
567/**
568 * Callback after sending a new update to twitter.
569 */
570static void twitter_http_post_status(struct http_request *req)
571{
572        struct im_connection *ic = req->data;
573
574        // Check if the connection is still active.
575        if( !g_slist_find( twitter_connections, ic ) )
576                return;
577
578        // Check if the HTTP request went well.
579        if (req->status_code != 200) {
580                // It didn't go well, output the error and return.
581                imcb_error(ic, "Could not post tweet... HTTP STATUS: %d", req->status_code);
582                return;
583        }
584}
585
586/**
587 * Function to POST a new status to twitter.
588 */ 
589void twitter_post_status(struct im_connection *ic, char* msg)
590{
591        struct twitter_data *td = ic->proto_data;
592
593        char* args[2];
594        args[0] = "status";
595        args[1] = msg;
596        twitter_http(TWITTER_STATUS_UPDATE_URL, twitter_http_post_status, ic, 1, td->user, td->pass, args, 2);
597//      g_free(args[1]);
598}
599
600
601/**
602 * Function to POST a new message to twitter.
603 */
604void twitter_direct_messages_new(struct im_connection *ic, char *who, char *msg)
605{
606        struct twitter_data *td = ic->proto_data;
607
608        char* args[4];
609        args[0] = "screen_name";
610        args[1] = who;
611        args[2] = "text";
612        args[3] = msg;
613        // Use the same callback as for twitter_post_status, since it does basically the same.
614        twitter_http(TWITTER_DIRECT_MESSAGES_NEW_URL, twitter_http_post_status, ic, 1, td->user, td->pass, args, 4);
615//      g_free(args[1]);
616//      g_free(args[3]);
617}
Note: See TracBrowser for help on using the repository browser.