source: protocols/twitter/twitter_lib.c @ 429a9b1

Last change on this file since 429a9b1 was 429a9b1, checked in by Wilmer van der Gaast <wilmer@…>, at 2011-08-26T20:16:17Z

Since t.co is all over Twitter now, start parsing and showing entity
information. I'm going for this way of rendering even though full URLs are
also available to me because A) it puts me on the safe side wrt Twitter ToS
and B) some full URLS may really be very long.

Noticed some problem with truncated RT statuses not getting fixed but this
seems to be unrelated. (This is a truncated RT status without the
"truncated" property set.)

Bug #821.

  • Property mode set to 100644
File size: 28.4 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
[08579a1]24/* For strptime(): */
[daae10f]25#if(__sun)
26#else
[08579a1]27#define _XOPEN_SOURCE
[daae10f]28#endif
[08579a1]29
[1b221e0]30#include "twitter_http.h"
31#include "twitter.h"
32#include "bitlbee.h"
33#include "url.h"
34#include "misc.h"
35#include "base64.h"
36#include "xmltree.h"
37#include "twitter_lib.h"
38#include <ctype.h>
39#include <errno.h>
40
[e4e0b37]41/* GLib < 2.12.0 doesn't have g_ascii_strtoll(), work around using system strtoll(). */
42/* GLib < 2.12.4 can be buggy: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=488013 */
43#if !GLIB_CHECK_VERSION(2,12,5)
44#include <stdlib.h>
45#include <limits.h>
46#define g_ascii_strtoll strtoll
47#endif
48
[1b221e0]49#define TXL_STATUS 1
[62d2cfb]50#define TXL_USER 2
51#define TXL_ID 3
52
[1b221e0]53struct twitter_xml_list {
[62d2cfb]54        int type;
[8203da9]55        gint64 next_cursor;
[1b221e0]56        GSList *list;
57};
58
59struct twitter_xml_user {
60        char *name;
61        char *screen_name;
62};
63
64struct twitter_xml_status {
[08579a1]65        time_t created_at;
[1b221e0]66        char *text;
67        struct twitter_xml_user *user;
[ce81acd]68        guint64 id, reply_to;
[1b221e0]69};
70
[d6aa6dd]71static void twitter_groupchat_init(struct im_connection *ic);
72
[62d2cfb]73/**
74 * Frees a twitter_xml_user struct.
75 */
76static void txu_free(struct twitter_xml_user *txu)
77{
[a26af5c]78        if (txu == NULL)
79                return;
[2322a9f]80
[62d2cfb]81        g_free(txu->name);
82        g_free(txu->screen_name);
[2abceca]83        g_free(txu);
[62d2cfb]84}
85
86/**
87 * Frees a twitter_xml_status struct.
88 */
89static void txs_free(struct twitter_xml_status *txs)
90{
[2322a9f]91        if (txs == NULL)
92                return;
93
[62d2cfb]94        g_free(txs->text);
95        txu_free(txs->user);
[2abceca]96        g_free(txs);
[62d2cfb]97}
98
99/**
100 * Free a twitter_xml_list struct.
101 * type is the type of list the struct holds.
102 */
103static void txl_free(struct twitter_xml_list *txl)
104{
105        GSList *l;
[a26af5c]106        if (txl == NULL)
107                return;
[2322a9f]108
109        for (l = txl->list; l; l = g_slist_next(l)) {
110                if (txl->type == TXL_STATUS) {
[5983eca]111                        txs_free((struct twitter_xml_status *) l->data);
[2322a9f]112                } else if (txl->type == TXL_ID) {
[62d2cfb]113                        g_free(l->data);
[2322a9f]114                } else if (txl->type == TXL_USER) {
[de923d5]115                        txu_free(l->data);
[2322a9f]116                }
117        }
118
[62d2cfb]119        g_slist_free(txl->list);
[fd65edb]120        g_free(txl);
[62d2cfb]121}
122
123/**
[2322a9f]124 * Compare status elements
125 */
126static gint twitter_compare_elements(gconstpointer a, gconstpointer b)
127{
128        struct twitter_xml_status *a_status = (struct twitter_xml_status *) a;
129        struct twitter_xml_status *b_status = (struct twitter_xml_status *) b;
130
131        if (a_status->created_at < b_status->created_at) {
132                return -1;
133        } else if (a_status->created_at > b_status->created_at) {
134                return 1;
135        } else {
136                return 0;
137        }
138}
139
140/**
141 * Add a buddy if it is not already added, set the status to logged in.
[62d2cfb]142 */
[3e69802]143static void twitter_add_buddy(struct im_connection *ic, char *name, const char *fullname)
[62d2cfb]144{
[1014cab]145        struct twitter_data *td = ic->proto_data;
146
[de923d5]147        // Check if the buddy is already in the buddy list.
[5983eca]148        if (!bee_user_by_handle(ic->bee, ic, name)) {
[e88fbe27]149                char *mode = set_getstr(&ic->acc->set, "mode");
[5983eca]150
[62d2cfb]151                // The buddy is not in the list, add the buddy and set the status to logged in.
[5983eca]152                imcb_add_buddy(ic, name, NULL);
153                imcb_rename_buddy(ic, name, fullname);
154                if (g_strcasecmp(mode, "chat") == 0) {
[5c18a76]155                        /* Necessary so that nicks always get translated to the
156                           exact Twitter username. */
[5983eca]157                        imcb_buddy_nick_hint(ic, name, name);
[2322a9f]158                        imcb_chat_add_buddy(td->timeline_gc, name);
[5983eca]159                } else if (g_strcasecmp(mode, "many") == 0)
160                        imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL);
[62d2cfb]161        }
162}
[1b221e0]163
[a7b9ec7]164/* Warning: May return a malloc()ed value, which will be free()d on the next
[de923d5]165   call. Only for short-term use. NOT THREADSAFE!  */
[6eca2eb]166char *twitter_parse_error(struct http_request *req)
[a7b9ec7]167{
168        static char *ret = NULL;
[3d93aed]169        struct xt_parser *xp = NULL;
[c0f33f1]170        struct xt_node *node, *err;
[5983eca]171
[a7b9ec7]172        g_free(ret);
173        ret = NULL;
[5983eca]174
175        if (req->body_size > 0) {
[3d93aed]176                xp = xt_new(NULL, NULL);
177                xt_feed(xp, req->reply_body, req->body_size);
[de923d5]178               
179                for (node = xp->root; node; node = node->next)
[c0f33f1]180                        if ((err = xt_find_node(node->children, "error")) && err->text_len > 0) {
181                                ret = g_strdup_printf("%s (%s)", req->status_string, err->text);
[de923d5]182                                break;
183                        }
[5983eca]184
[a7b9ec7]185                xt_free(xp);
186        }
[5983eca]187
[6eca2eb]188        return ret ? ret : req->status_string;
[a7b9ec7]189}
190
[1b221e0]191static void twitter_http_get_friends_ids(struct http_request *req);
192
193/**
194 * Get the friends ids.
195 */
[8203da9]196void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor)
[1b221e0]197{
[5983eca]198        // Primitive, but hey! It works...     
199        char *args[2];
[1b221e0]200        args[0] = "cursor";
[5983eca]201        args[1] = g_strdup_printf("%lld", (long long) next_cursor);
[bb5ce4d1]202        twitter_http(ic, TWITTER_FRIENDS_IDS_URL, twitter_http_get_friends_ids, ic, 0, args, 2);
[1b221e0]203
204        g_free(args[1]);
205}
206
207/**
208 * Function to help fill a list.
209 */
[5983eca]210static xt_status twitter_xt_next_cursor(struct xt_node *node, struct twitter_xml_list *txl)
[1b221e0]211{
[bd64716]212        char *end = NULL;
[5983eca]213
214        if (node->text)
215                txl->next_cursor = g_ascii_strtoll(node->text, &end, 10);
216        if (end == NULL)
[bd64716]217                txl->next_cursor = -1;
[1b221e0]218
219        return XT_HANDLED;
220}
221
222/**
223 * Fill a list of ids.
224 */
[5983eca]225static xt_status twitter_xt_get_friends_id_list(struct xt_node *node, struct twitter_xml_list *txl)
[1b221e0]226{
227        struct xt_node *child;
[5983eca]228
[62d2cfb]229        // Set the list type.
230        txl->type = TXL_ID;
[1b221e0]231
232        // The root <statuses> node should hold the list of statuses <status>
233        // Walk over the nodes children.
[5983eca]234        for (child = node->children; child; child = child->next) {
[de923d5]235                if (g_strcasecmp("ids", child->name) == 0) {
236                        struct xt_node *idc;
237                        for (idc = child->children; idc; idc = idc->next)
238                                if (g_strcasecmp(idc->name, "id") == 0)
239                                        txl->list = g_slist_prepend(txl->list,
240                                                g_memdup(idc->text, idc->text_len + 1));
[5983eca]241                } else if (g_strcasecmp("next_cursor", child->name) == 0) {
[1b221e0]242                        twitter_xt_next_cursor(child, txl);
243                }
244        }
245
246        return XT_HANDLED;
247}
248
[de923d5]249static void twitter_get_users_lookup(struct im_connection *ic);
250
[1b221e0]251/**
252 * Callback for getting the friends ids.
253 */
254static void twitter_http_get_friends_ids(struct http_request *req)
255{
256        struct im_connection *ic;
257        struct xt_parser *parser;
258        struct twitter_xml_list *txl;
[3bd4a93]259        struct twitter_data *td;
[1b221e0]260
261        ic = req->data;
262
[62d2cfb]263        // Check if the connection is still active.
[5983eca]264        if (!g_slist_find(twitter_connections, ic))
[62d2cfb]265                return;
[5983eca]266
[37aa317]267        td = ic->proto_data;
[62d2cfb]268
[de923d5]269        // Check if the HTTP request went well. More strict checks as this is
270        // the first request we do in a session.
271        if (req->status_code == 401) {
272                imcb_error(ic, "Authentication failure");
273                imc_logout(ic, FALSE);
274                return;
275        } else if (req->status_code != 200) {
[1b221e0]276                // It didn't go well, output the error and return.
[de923d5]277                imcb_error(ic, "Could not retrieve %s: %s",
278                           TWITTER_FRIENDS_IDS_URL, twitter_parse_error(req));
279                imc_logout(ic, TRUE);
[1b221e0]280                return;
[3bd4a93]281        } else {
282                td->http_fails = 0;
[1b221e0]283        }
284
[de923d5]285        /* Create the room now that we "logged in". */
[2322a9f]286        if (!td->timeline_gc && g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0)
[de923d5]287                twitter_groupchat_init(ic);
288
[1b221e0]289        txl = g_new0(struct twitter_xml_list, 1);
[de923d5]290        txl->list = td->follow_ids;
[1b221e0]291
292        // Parse the data.
[5983eca]293        parser = xt_new(NULL, txl);
294        xt_feed(parser, req->reply_body, req->body_size);
[1b221e0]295        twitter_xt_get_friends_id_list(parser->root, txl);
[5983eca]296        xt_free(parser);
[1b221e0]297
[de923d5]298        td->follow_ids = txl->list;
[1b221e0]299        if (txl->next_cursor)
[de923d5]300                /* These were just numbers. Up to 4000 in a response AFAIK so if we get here
301                   we may be using a spammer account. \o/ */
[1b221e0]302                twitter_get_friends_ids(ic, txl->next_cursor);
[de923d5]303        else
304                /* Now to convert all those numbers into names.. */
305                twitter_get_users_lookup(ic);
306
307        txl->list = NULL;
308        txl_free(txl);
309}
310
311static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl);
312static void twitter_http_get_users_lookup(struct http_request *req);
313
314static void twitter_get_users_lookup(struct im_connection *ic)
315{
316        struct twitter_data *td = ic->proto_data;
317        char *args[2] = {
318                "user_id",
319                NULL,
320        };
321        GString *ids = g_string_new("");
322        int i;
323       
324        /* We can request up to 100 users at a time. */
325        for (i = 0; i < 100 && td->follow_ids; i ++) {
326                g_string_append_printf(ids, ",%s", (char*) td->follow_ids->data);
327                g_free(td->follow_ids->data);
328                td->follow_ids = g_slist_remove(td->follow_ids, td->follow_ids->data);
329        }
330        if (ids->len > 0) {
331                args[1] = ids->str + 1;
332                /* POST, because I think ids can be up to 1KB long. */
333                twitter_http(ic, TWITTER_USERS_LOOKUP_URL, twitter_http_get_users_lookup, ic, 1, args, 2);
334        } else {
335                /* We have all users. Continue with login. (Get statuses.) */
336                td->flags |= TWITTER_HAVE_FRIENDS;
337                twitter_login_finish(ic);
338        }
339        g_string_free(ids, TRUE);
340}
341
342/**
343 * Callback for getting (twitter)friends...
344 *
345 * Be afraid, be very afraid! This function will potentially add hundreds of "friends". "Who has
346 * hundreds of friends?" you wonder? You probably not, since you are reading the source of
347 * BitlBee... Get a life and meet new people!
348 */
349static void twitter_http_get_users_lookup(struct http_request *req)
350{
351        struct im_connection *ic = req->data;
352        struct twitter_data *td;
353        struct xt_parser *parser;
354        struct twitter_xml_list *txl;
355        GSList *l = NULL;
356        struct twitter_xml_user *user;
357
358        // Check if the connection is still active.
359        if (!g_slist_find(twitter_connections, ic))
360                return;
361
362        td = ic->proto_data;
363
364        if (req->status_code != 200) {
365                // It didn't go well, output the error and return.
366                imcb_error(ic, "Could not retrieve %s: %s",
367                           TWITTER_USERS_LOOKUP_URL, twitter_parse_error(req));
368                imc_logout(ic, TRUE);
369                return;
370        } else {
371                td->http_fails = 0;
372        }
373
374        txl = g_new0(struct twitter_xml_list, 1);
375        txl->list = NULL;
[1b221e0]376
[de923d5]377        // Parse the data.
378        parser = xt_new(NULL, txl);
379        xt_feed(parser, req->reply_body, req->body_size);
380
381        // Get the user list from the parsed xml feed.
382        twitter_xt_get_users(parser->root, txl);
383        xt_free(parser);
384
385        // Add the users as buddies.
386        for (l = txl->list; l; l = g_slist_next(l)) {
387                user = l->data;
388                twitter_add_buddy(ic, user->screen_name, user->name);
389        }
390
391        // Free the structure.
[62d2cfb]392        txl_free(txl);
[de923d5]393
394        twitter_get_users_lookup(ic);
[1b221e0]395}
396
397/**
398 * Function to fill a twitter_xml_user struct.
399 * It sets:
400 *  - the name and
401 *  - the screen_name.
402 */
[5983eca]403static xt_status twitter_xt_get_user(struct xt_node *node, struct twitter_xml_user *txu)
[1b221e0]404{
405        struct xt_node *child;
406
407        // Walk over the nodes children.
[5983eca]408        for (child = node->children; child; child = child->next) {
409                if (g_strcasecmp("name", child->name) == 0) {
410                        txu->name = g_memdup(child->text, child->text_len + 1);
411                } else if (g_strcasecmp("screen_name", child->name) == 0) {
412                        txu->screen_name = g_memdup(child->text, child->text_len + 1);
[1b221e0]413                }
414        }
415        return XT_HANDLED;
416}
417
[62d2cfb]418/**
419 * Function to fill a twitter_xml_list struct.
420 * It sets:
421 *  - all <user>s from the <users> element.
422 */
[5983eca]423static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl)
[62d2cfb]424{
425        struct twitter_xml_user *txu;
426        struct xt_node *child;
427
428        // Set the type of the list.
429        txl->type = TXL_USER;
430
431        // The root <users> node should hold the list of users <user>
432        // Walk over the nodes children.
[5983eca]433        for (child = node->children; child; child = child->next) {
434                if (g_strcasecmp("user", child->name) == 0) {
[62d2cfb]435                        txu = g_new0(struct twitter_xml_user, 1);
436                        twitter_xt_get_user(child, txu);
437                        // Put the item in the front of the list.
[5983eca]438                        txl->list = g_slist_prepend(txl->list, txu);
[62d2cfb]439                }
440        }
441
442        return XT_HANDLED;
443}
444
[2b02617]445#ifdef __GLIBC__
446#define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S %z %Y"
447#else
448#define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S +0000 %Y"
449#endif
[62d2cfb]450
[1b221e0]451/**
452 * Function to fill a twitter_xml_status struct.
453 * It sets:
454 *  - the status text and
455 *  - the created_at timestamp and
456 *  - the status id and
457 *  - the user in a twitter_xml_user struct.
458 */
[5983eca]459static xt_status twitter_xt_get_status(struct xt_node *node, struct twitter_xml_status *txs)
[1b221e0]460{
[bd599b9]461        struct xt_node *child, *rt = NULL;
[e193aeb]462        gboolean truncated = FALSE;
[1b221e0]463
464        // Walk over the nodes children.
[5983eca]465        for (child = node->children; child; child = child->next) {
466                if (g_strcasecmp("text", child->name) == 0) {
467                        txs->text = g_memdup(child->text, child->text_len + 1);
468                } else if (g_strcasecmp("truncated", child->name) == 0 && child->text) {
[e193aeb]469                        truncated = bool2int(child->text);
[5983eca]470                } else if (g_strcasecmp("retweeted_status", child->name) == 0) {
[e193aeb]471                        rt = child;
[5983eca]472                } else if (g_strcasecmp("created_at", child->name) == 0) {
[08579a1]473                        struct tm parsed;
[5983eca]474
[08579a1]475                        /* Very sensitive to changes to the formatting of
476                           this field. :-( Also assumes the timezone used
477                           is UTC since C time handling functions suck. */
[5983eca]478                        if (strptime(child->text, TWITTER_TIME_FORMAT, &parsed) != NULL)
479                                txs->created_at = mktime_utc(&parsed);
480                } else if (g_strcasecmp("user", child->name) == 0) {
[1b221e0]481                        txs->user = g_new0(struct twitter_xml_user, 1);
[5983eca]482                        twitter_xt_get_user(child, txs->user);
483                } else if (g_strcasecmp("id", child->name) == 0) {
484                        txs->id = g_ascii_strtoull(child->text, NULL, 10);
485                } else if (g_strcasecmp("in_reply_to_status_id", child->name) == 0) {
486                        txs->reply_to = g_ascii_strtoull(child->text, NULL, 10);
[ce81acd]487                }
[1b221e0]488        }
[5983eca]489
[e193aeb]490        /* If it's a truncated retweet, get the original because dots suck. */
[5983eca]491        if (truncated && rt) {
[e193aeb]492                struct twitter_xml_status *rtxs = g_new0(struct twitter_xml_status, 1);
[5983eca]493                if (twitter_xt_get_status(rt, rtxs) != XT_HANDLED) {
[e193aeb]494                        txs_free(rtxs);
495                        return XT_HANDLED;
496                }
[5983eca]497
[e193aeb]498                g_free(txs->text);
499                txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text);
500                txs_free(rtxs);
[429a9b1]501        } else {
502                struct xt_node *urls, *url;
503               
504                urls = xt_find_path(node, "entities/urls");
505                for (url = urls ? urls->children : NULL; url; url = url->next) {
506                        /* "short" is a reserved word. :-P */
507                        struct xt_node *kort = xt_find_node(url->children, "url");
508                        struct xt_node *disp = xt_find_node(url->children, "display_url");
509                        char *pos, *new;
510                       
511                        if (!kort || !kort->text || !disp || !disp->text ||
512                            !(pos = strstr(txs->text, kort->text)))
513                                continue;
514                       
515                        *pos = '\0';
516                        new = g_strdup_printf("%s%s &lt;%s&gt;%s", txs->text, kort->text,
517                                              disp->text, pos + strlen(kort->text));
518                       
519                        g_free(txs->text);
520                        txs->text = new;
521                }
[e193aeb]522        }
[5983eca]523
[1b221e0]524        return XT_HANDLED;
525}
526
527/**
528 * Function to fill a twitter_xml_list struct.
529 * It sets:
530 *  - all <status>es within the <status> element and
531 *  - the next_cursor.
532 */
[5983eca]533static xt_status twitter_xt_get_status_list(struct im_connection *ic, struct xt_node *node,
534                                            struct twitter_xml_list *txl)
[1b221e0]535{
536        struct twitter_xml_status *txs;
537        struct xt_node *child;
[203a2d2]538        bee_user_t *bu;
[1b221e0]539
[62d2cfb]540        // Set the type of the list.
541        txl->type = TXL_STATUS;
542
[1b221e0]543        // The root <statuses> node should hold the list of statuses <status>
544        // Walk over the nodes children.
[5983eca]545        for (child = node->children; child; child = child->next) {
546                if (g_strcasecmp("status", child->name) == 0) {
[1b221e0]547                        txs = g_new0(struct twitter_xml_status, 1);
548                        twitter_xt_get_status(child, txs);
549                        // Put the item in the front of the list.
[5983eca]550                        txl->list = g_slist_prepend(txl->list, txs);
551
[203a2d2]552                        if (txs->user && txs->user->screen_name &&
[5983eca]553                            (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) {
[203a2d2]554                                struct twitter_user_data *tud = bu->data;
[5983eca]555
556                                if (txs->id > tud->last_id) {
[203a2d2]557                                        tud->last_id = txs->id;
558                                        tud->last_time = txs->created_at;
559                                }
560                        }
[5983eca]561                } else if (g_strcasecmp("next_cursor", child->name) == 0) {
[1b221e0]562                        twitter_xt_next_cursor(child, txl);
563                }
564        }
565
566        return XT_HANDLED;
567}
568
[ce81acd]569static char *twitter_msg_add_id(struct im_connection *ic,
[5983eca]570                                struct twitter_xml_status *txs, const char *prefix)
[ce81acd]571{
572        struct twitter_data *td = ic->proto_data;
573        char *ret = NULL;
[5983eca]574
575        if (!set_getbool(&ic->acc->set, "show_ids")) {
[ce81acd]576                if (*prefix)
577                        return g_strconcat(prefix, txs->text, NULL);
578                else
579                        return NULL;
580        }
[5983eca]581
[ce81acd]582        td->log[td->log_id].id = txs->id;
583        td->log[td->log_id].bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name);
[5983eca]584        if (txs->reply_to) {
[ce81acd]585                int i;
[5983eca]586                for (i = 0; i < TWITTER_LOG_LENGTH; i++)
587                        if (td->log[i].id == txs->reply_to) {
588                                ret = g_strdup_printf("\002[\002%02d->%02d\002]\002 %s%s",
589                                                      td->log_id, i, prefix, txs->text);
[ce81acd]590                                break;
591                        }
592        }
593        if (ret == NULL)
[5983eca]594                ret = g_strdup_printf("\002[\002%02d\002]\002 %s%s", td->log_id, prefix, txs->text);
[ce81acd]595        td->log_id = (td->log_id + 1) % TWITTER_LOG_LENGTH;
[5983eca]596
[ce81acd]597        return ret;
598}
599
[d6aa6dd]600static void twitter_groupchat_init(struct im_connection *ic)
601{
602        char *name_hint;
603        struct groupchat *gc;
604        struct twitter_data *td = ic->proto_data;
[fd65edb]605        GSList *l;
[5983eca]606
[2322a9f]607        td->timeline_gc = gc = imcb_chat_new(ic, "twitter/timeline");
[5983eca]608
609        name_hint = g_strdup_printf("%s_%s", td->prefix, ic->acc->user);
610        imcb_chat_name_hint(gc, name_hint);
611        g_free(name_hint);
612
613        for (l = ic->bee->users; l; l = l->next) {
[fd65edb]614                bee_user_t *bu = l->data;
[5983eca]615                if (bu->ic == ic)
[2322a9f]616                        imcb_chat_add_buddy(td->timeline_gc, bu->handle);
[fd65edb]617        }
[d6aa6dd]618}
619
[62d2cfb]620/**
621 * Function that is called to see the statuses in a groupchat window.
622 */
[5983eca]623static void twitter_groupchat(struct im_connection *ic, GSList * list)
[62d2cfb]624{
625        struct twitter_data *td = ic->proto_data;
626        GSList *l = NULL;
627        struct twitter_xml_status *status;
628        struct groupchat *gc;
[2322a9f]629        guint64 last_id = 0;
[62d2cfb]630
631        // Create a new groupchat if it does not exsist.
[2322a9f]632        if (!td->timeline_gc)
[d6aa6dd]633                twitter_groupchat_init(ic);
[5983eca]634
[2322a9f]635        gc = td->timeline_gc;
[d6aa6dd]636        if (!gc->joined)
[5983eca]637                imcb_chat_add_buddy(gc, ic->acc->user);
[62d2cfb]638
[5983eca]639        for (l = list; l; l = g_slist_next(l)) {
[ce81acd]640                char *msg;
[5983eca]641
[62d2cfb]642                status = l->data;
[2322a9f]643                if (status->user == NULL || status->text == NULL || last_id == status->id)
[a26af5c]644                        continue;
645
[2322a9f]646                last_id = status->id;
[5983eca]647
[0b3ffb1]648                strip_html(status->text);
[2322a9f]649
[ce81acd]650                msg = twitter_msg_add_id(ic, status, "");
[5983eca]651
[62d2cfb]652                // Say it!
[2322a9f]653                if (g_strcasecmp(td->user, status->user->screen_name) == 0) {
[ce81acd]654                        imcb_chat_log(gc, "You: %s", msg ? msg : status->text);
[2322a9f]655                } else {
656                        twitter_add_buddy(ic, status->user->screen_name, status->user->name);
657
[ce81acd]658                        imcb_chat_msg(gc, status->user->screen_name,
[5983eca]659                                      msg ? msg : status->text, 0, status->created_at);
[2322a9f]660                }
[5983eca]661
[ce81acd]662                g_free(msg);
[5983eca]663
[2322a9f]664                // Update the timeline_id to hold the highest id, so that by the next request
[ce81acd]665                // we won't pick up the updates already in the list.
[2322a9f]666                td->timeline_id = MAX(td->timeline_id, status->id);
[62d2cfb]667        }
668}
669
670/**
671 * Function that is called to see statuses as private messages.
672 */
[5983eca]673static void twitter_private_message_chat(struct im_connection *ic, GSList * list)
[62d2cfb]674{
675        struct twitter_data *td = ic->proto_data;
676        GSList *l = NULL;
677        struct twitter_xml_status *status;
[e88fbe27]678        char from[MAX_STRING];
679        gboolean mode_one;
[2322a9f]680        guint64 last_id = 0;
[62d2cfb]681
[5983eca]682        mode_one = g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "one") == 0;
683
684        if (mode_one) {
685                g_snprintf(from, sizeof(from) - 1, "%s_%s", td->prefix, ic->acc->user);
686                from[MAX_STRING - 1] = '\0';
[e88fbe27]687        }
[5983eca]688
689        for (l = list; l; l = g_slist_next(l)) {
[ce81acd]690                char *prefix = NULL, *text = NULL;
[5983eca]691
[62d2cfb]692                status = l->data;
[2322a9f]693                if (status->user == NULL || status->text == NULL || last_id == status->id)
694                        continue;
695
696                last_id = status->id;
[5983eca]697
698                strip_html(status->text);
699                if (mode_one)
[ce81acd]700                        prefix = g_strdup_printf("\002<\002%s\002>\002 ",
[5983eca]701                                                 status->user->screen_name);
[55b1e69]702                else
703                        twitter_add_buddy(ic, status->user->screen_name, status->user->name);
[5983eca]704
[ce81acd]705                text = twitter_msg_add_id(ic, status, prefix ? prefix : "");
[5983eca]706
707                imcb_buddy_msg(ic,
708                               mode_one ? from : status->user->screen_name,
709                               text ? text : status->text, 0, status->created_at);
710
[2322a9f]711                // Update the timeline_id to hold the highest id, so that by the next request
[ce81acd]712                // we won't pick up the updates already in the list.
[2322a9f]713                td->timeline_id = MAX(td->timeline_id, status->id);
[5983eca]714
715                g_free(text);
716                g_free(prefix);
[62d2cfb]717        }
718}
719
[2322a9f]720static void twitter_http_get_home_timeline(struct http_request *req);
721static void twitter_http_get_mentions(struct http_request *req);
722
723/**
724 * Get the timeline with optionally mentions
725 */
726void twitter_get_timeline(struct im_connection *ic, gint64 next_cursor)
727{
728        struct twitter_data *td = ic->proto_data;
729        gboolean include_mentions = set_getbool(&ic->acc->set, "fetch_mentions");
730
731        if (td->flags & TWITTER_DOING_TIMELINE) {
732                return;
733        }
734
735        td->flags |= TWITTER_DOING_TIMELINE;
736
737        twitter_get_home_timeline(ic, next_cursor);
738
739        if (include_mentions) {
740                twitter_get_mentions(ic, next_cursor);
741        }
742}
743
744/**
745 * Call this one after receiving timeline/mentions. Show to user once we have
746 * both.
747 */
748void twitter_flush_timeline(struct im_connection *ic)
749{
750        struct twitter_data *td = ic->proto_data;
751        gboolean include_mentions = set_getbool(&ic->acc->set, "fetch_mentions");
752        gboolean show_old_mentions = set_getbool(&ic->acc->set, "show_old_mentions");
753        struct twitter_xml_list *home_timeline = td->home_timeline_obj;
754        struct twitter_xml_list *mentions = td->mentions_obj;
755        GSList *output = NULL;
756        GSList *l;
757
758        if (!(td->flags & TWITTER_GOT_TIMELINE)) {
759                return;
760        }
761
762        if (include_mentions && !(td->flags & TWITTER_GOT_MENTIONS)) {
763                return;
764        }
765
766        if (home_timeline && home_timeline->list) {
767                for (l = home_timeline->list; l; l = g_slist_next(l)) {
768                        output = g_slist_insert_sorted(output, l->data, twitter_compare_elements);
769                }
770        }
771
772        if (include_mentions && mentions && mentions->list) {
773                for (l = mentions->list; l; l = g_slist_next(l)) {
774                        if (!show_old_mentions && output && twitter_compare_elements(l->data, output->data) < 0) {
775                                continue;
776                        }
777
778                        output = g_slist_insert_sorted(output, l->data, twitter_compare_elements);
779                }
780        }
781
782        // See if the user wants to see the messages in a groupchat window or as private messages.
783        if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0)
784                twitter_groupchat(ic, output);
785        else
786                twitter_private_message_chat(ic, output);
787
788        g_slist_free(output);
789
790        if (home_timeline && home_timeline->list) {
791                txl_free(home_timeline);
792        }
793
794        if (mentions && mentions->list) {
795                txl_free(mentions);
796        }
797
798        td->flags &= ~(TWITTER_DOING_TIMELINE | TWITTER_GOT_TIMELINE | TWITTER_GOT_MENTIONS);
799}
800
801/**
802 * Get the timeline.
803 */
804void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
805{
806        struct twitter_data *td = ic->proto_data;
807
808        td->home_timeline_obj = NULL;
809        td->flags &= ~TWITTER_GOT_TIMELINE;
810
[429a9b1]811        char *args[6];
[2322a9f]812        args[0] = "cursor";
813        args[1] = g_strdup_printf("%lld", (long long) next_cursor);
[429a9b1]814        args[2] = "include_entities";
815        args[3] = "true";
[2322a9f]816        if (td->timeline_id) {
[429a9b1]817                args[4] = "since_id";
818                args[5] = g_strdup_printf("%llu", (long long unsigned int) td->timeline_id);
[2322a9f]819        }
820
821        twitter_http(ic, TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, args,
[429a9b1]822                     td->timeline_id ? 6 : 4);
[2322a9f]823
824        g_free(args[1]);
825        if (td->timeline_id) {
[429a9b1]826                g_free(args[5]);
[2322a9f]827        }
828}
829
830/**
831 * Get mentions.
832 */
833void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)
834{
835        struct twitter_data *td = ic->proto_data;
836
837        td->mentions_obj = NULL;
838        td->flags &= ~TWITTER_GOT_MENTIONS;
839
[429a9b1]840        char *args[6];
[2322a9f]841        args[0] = "cursor";
842        args[1] = g_strdup_printf("%lld", (long long) next_cursor);
[429a9b1]843        args[2] = "include_entities";
844        args[3] = "true";
[2322a9f]845        if (td->timeline_id) {
[429a9b1]846                args[4] = "since_id";
847                args[5] = g_strdup_printf("%llu", (long long unsigned int) td->timeline_id);
[2322a9f]848        }
849
850        twitter_http(ic, TWITTER_MENTIONS_URL, twitter_http_get_mentions, ic, 0, args,
[429a9b1]851                     td->timeline_id ? 6 : 4);
[2322a9f]852
853        g_free(args[1]);
854        if (td->timeline_id) {
[429a9b1]855                g_free(args[5]);
[2322a9f]856        }
857}
858
[1b221e0]859/**
860 * Callback for getting the home timeline.
861 */
862static void twitter_http_get_home_timeline(struct http_request *req)
863{
[62d2cfb]864        struct im_connection *ic = req->data;
[37aa317]865        struct twitter_data *td;
[1b221e0]866        struct xt_parser *parser;
867        struct twitter_xml_list *txl;
[62d2cfb]868
869        // Check if the connection is still active.
[5983eca]870        if (!g_slist_find(twitter_connections, ic))
[62d2cfb]871                return;
[5983eca]872
[37aa317]873        td = ic->proto_data;
[1b221e0]874
875        // Check if the HTTP request went well.
[5983eca]876        if (req->status_code == 200) {
[3bd4a93]877                td->http_fails = 0;
[c2ecadc]878                if (!(ic->flags & OPT_LOGGED_IN))
[3bd4a93]879                        imcb_connected(ic);
[5983eca]880        } else if (req->status_code == 401) {
881                imcb_error(ic, "Authentication failure");
882                imc_logout(ic, FALSE);
[2322a9f]883                goto end;
[5983eca]884        } else {
[1b221e0]885                // It didn't go well, output the error and return.
[3bd4a93]886                if (++td->http_fails >= 5)
[de923d5]887                        imcb_error(ic, "Could not retrieve %s: %s",
888                                   TWITTER_HOME_TIMELINE_URL, twitter_parse_error(req));
[5983eca]889
[2322a9f]890                goto end;
891        }
892
893        txl = g_new0(struct twitter_xml_list, 1);
894        txl->list = NULL;
895
896        // Parse the data.
897        parser = xt_new(NULL, txl);
898        xt_feed(parser, req->reply_body, req->body_size);
899        // The root <statuses> node should hold the list of statuses <status>
900        twitter_xt_get_status_list(ic, parser->root, txl);
901        xt_free(parser);
902
903        td->home_timeline_obj = txl;
904
905      end:
906        td->flags |= TWITTER_GOT_TIMELINE;
907
908        twitter_flush_timeline(ic);
909}
910
911/**
912 * Callback for getting mentions.
913 */
914static void twitter_http_get_mentions(struct http_request *req)
915{
916        struct im_connection *ic = req->data;
917        struct twitter_data *td;
918        struct xt_parser *parser;
919        struct twitter_xml_list *txl;
920
921        // Check if the connection is still active.
922        if (!g_slist_find(twitter_connections, ic))
[1b221e0]923                return;
[2322a9f]924
925        td = ic->proto_data;
926
927        // Check if the HTTP request went well.
928        if (req->status_code == 200) {
929                td->http_fails = 0;
930                if (!(ic->flags & OPT_LOGGED_IN))
931                        imcb_connected(ic);
932        } else if (req->status_code == 401) {
933                imcb_error(ic, "Authentication failure");
934                imc_logout(ic, FALSE);
935                goto end;
936        } else {
937                // It didn't go well, output the error and return.
938                if (++td->http_fails >= 5)
939                        imcb_error(ic, "Could not retrieve " TWITTER_MENTIONS_URL ": %s",
940                                   twitter_parse_error(req));
941
942                goto end;
[1b221e0]943        }
944
945        txl = g_new0(struct twitter_xml_list, 1);
946        txl->list = NULL;
[62d2cfb]947
[1b221e0]948        // Parse the data.
[5983eca]949        parser = xt_new(NULL, txl);
950        xt_feed(parser, req->reply_body, req->body_size);
[1b221e0]951        // The root <statuses> node should hold the list of statuses <status>
[203a2d2]952        twitter_xt_get_status_list(ic, parser->root, txl);
[5983eca]953        xt_free(parser);
[1b221e0]954
[2322a9f]955        td->mentions_obj = txl;
[1b221e0]956
[2322a9f]957      end:
958        td->flags |= TWITTER_GOT_MENTIONS;
959
960        twitter_flush_timeline(ic);
[1b221e0]961}
962
963/**
[de923d5]964 * Callback to use after sending a POST request to twitter.
965 * (Generic, used for a few kinds of queries.)
[1b221e0]966 */
[7d53efb]967static void twitter_http_post(struct http_request *req)
[1b221e0]968{
969        struct im_connection *ic = req->data;
[7b87539]970        struct twitter_data *td;
[1b221e0]971
[62d2cfb]972        // Check if the connection is still active.
[5983eca]973        if (!g_slist_find(twitter_connections, ic))
[62d2cfb]974                return;
975
[7b87539]976        td = ic->proto_data;
977        td->last_status_id = 0;
[5983eca]978
[1b221e0]979        // Check if the HTTP request went well.
980        if (req->status_code != 200) {
981                // It didn't go well, output the error and return.
[ba3233c]982                imcb_error(ic, "HTTP error: %s", twitter_parse_error(req));
[1b221e0]983                return;
984        }
[5983eca]985
986        if (req->body_size > 0) {
[7b87539]987                struct xt_parser *xp = NULL;
988                struct xt_node *node;
[5983eca]989
[7b87539]990                xp = xt_new(NULL, NULL);
991                xt_feed(xp, req->reply_body, req->body_size);
[5983eca]992
[7b87539]993                if ((node = xt_find_node(xp->root, "status")) &&
994                    (node = xt_find_node(node->children, "id")) && node->text)
[5983eca]995                        td->last_status_id = g_ascii_strtoull(node->text, NULL, 10);
996
[7b87539]997                xt_free(xp);
998        }
[1b221e0]999}
1000
1001/**
1002 * Function to POST a new status to twitter.
[5983eca]1003 */
[b890626]1004void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to)
[1b221e0]1005{
[5983eca]1006        char *args[4] = {
[b890626]1007                "status", msg,
1008                "in_reply_to_status_id",
1009                g_strdup_printf("%llu", (unsigned long long) in_reply_to)
1010        };
1011        twitter_http(ic, TWITTER_STATUS_UPDATE_URL, twitter_http_post, ic, 1,
[5983eca]1012                     args, in_reply_to ? 4 : 2);
[b890626]1013        g_free(args[3]);
[1b221e0]1014}
1015
1016
[62d2cfb]1017/**
1018 * Function to POST a new message to twitter.
1019 */
1020void twitter_direct_messages_new(struct im_connection *ic, char *who, char *msg)
1021{
[5983eca]1022        char *args[4];
[62d2cfb]1023        args[0] = "screen_name";
1024        args[1] = who;
1025        args[2] = "text";
1026        args[3] = msg;
1027        // Use the same callback as for twitter_post_status, since it does basically the same.
[ba3233c]1028        twitter_http(ic, TWITTER_DIRECT_MESSAGES_NEW_URL, twitter_http_post, ic, 1, args, 4);
[62d2cfb]1029}
[7d53efb]1030
1031void twitter_friendships_create_destroy(struct im_connection *ic, char *who, int create)
1032{
[5983eca]1033        char *args[2];
[7d53efb]1034        args[0] = "screen_name";
1035        args[1] = who;
[5983eca]1036        twitter_http(ic, create ? TWITTER_FRIENDSHIPS_CREATE_URL : TWITTER_FRIENDSHIPS_DESTROY_URL,
1037                     twitter_http_post, ic, 1, args, 2);
[a26af5c]1038}
[7b87539]1039
1040void twitter_status_destroy(struct im_connection *ic, guint64 id)
1041{
1042        char *url;
[de923d5]1043        url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_DESTROY_URL,
1044                              (unsigned long long) id, ".xml");
[7b87539]1045        twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);
1046        g_free(url);
1047}
[b890626]1048
1049void twitter_status_retweet(struct im_connection *ic, guint64 id)
1050{
1051        char *url;
[de923d5]1052        url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_RETWEET_URL,
1053                              (unsigned long long) id, ".xml");
[b890626]1054        twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);
1055        g_free(url);
1056}
Note: See TracBrowser for help on using the repository browser.