source: protocols/twitter/twitter_lib.c @ 80c2f3c

Last change on this file since 80c2f3c was bbff22d, checked in by dequis <dx@…>, at 2015-10-09T02:41:01Z

twitter: Fix some nitpicky issues reported by coverity

Mostly minor rare leaks that happen in error conditions, and one
dereference before null check in twitter_logout (the null check is
probably the wrong one there, but it doesn't hurt to keep it)

  • Property mode set to 100644
File size: 41.4 KB
RevLine 
[1b221e0]1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Simple module to facilitate twitter functionality.                       *
5*                                                                           *
[199fea6]6*  Copyright 2009-2010 Geert Mulders <g.c.w.m.mulders@gmail.com>            *
[0e788f5]7*  Copyright 2010-2013 Wilmer van der Gaast <wilmer@gaast.net>              *
[1b221e0]8*                                                                           *
9*  This library is free software; you can redistribute it and/or            *
10*  modify it under the terms of the GNU Lesser General Public               *
11*  License as published by the Free Software Foundation, version            *
12*  2.1.                                                                     *
13*                                                                           *
14*  This library is distributed in the hope that it will be useful,          *
15*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
16*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU        *
17*  Lesser General Public License for more details.                          *
18*                                                                           *
19*  You should have received a copy of the GNU Lesser General Public License *
20*  along with this library; if not, write to the Free Software Foundation,  *
21*  Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA           *
22*                                                                           *
23****************************************************************************/
24
[08579a1]25/* For strptime(): */
[5ebff60]26#if (__sun)
[daae10f]27#else
[08579a1]28#define _XOPEN_SOURCE
[daae10f]29#endif
[08579a1]30
[1b221e0]31#include "twitter_http.h"
32#include "twitter.h"
33#include "bitlbee.h"
34#include "url.h"
35#include "misc.h"
36#include "base64.h"
37#include "twitter_lib.h"
[e08ae0c]38#include "json_util.h"
[1b221e0]39#include <ctype.h>
40#include <errno.h>
41
42#define TXL_STATUS 1
[62d2cfb]43#define TXL_USER 2
44#define TXL_ID 3
45
[1b221e0]46struct twitter_xml_list {
[62d2cfb]47        int type;
[8203da9]48        gint64 next_cursor;
[1b221e0]49        GSList *list;
50};
51
52struct twitter_xml_user {
[73ee390]53        guint64 uid;
[1b221e0]54        char *name;
55        char *screen_name;
56};
57
58struct twitter_xml_status {
[08579a1]59        time_t created_at;
[1b221e0]60        char *text;
61        struct twitter_xml_user *user;
[67f6828]62        guint64 id, rt_id; /* Usually equal, with RTs id == *original* id */
63        guint64 reply_to;
[73ee390]64        gboolean from_filter;
[1b221e0]65};
66
[62d2cfb]67/**
68 * Frees a twitter_xml_user struct.
69 */
70static void txu_free(struct twitter_xml_user *txu)
71{
[5ebff60]72        if (txu == NULL) {
[a26af5c]73                return;
[5ebff60]74        }
[2322a9f]75
[62d2cfb]76        g_free(txu->name);
77        g_free(txu->screen_name);
[2abceca]78        g_free(txu);
[62d2cfb]79}
80
81/**
82 * Frees a twitter_xml_status struct.
83 */
84static void txs_free(struct twitter_xml_status *txs)
85{
[5ebff60]86        if (txs == NULL) {
[2322a9f]87                return;
[5ebff60]88        }
[2322a9f]89
[62d2cfb]90        g_free(txs->text);
91        txu_free(txs->user);
[2abceca]92        g_free(txs);
[62d2cfb]93}
94
95/**
96 * Free a twitter_xml_list struct.
97 * type is the type of list the struct holds.
98 */
99static void txl_free(struct twitter_xml_list *txl)
100{
101        GSList *l;
[5ebff60]102
103        if (txl == NULL) {
[a26af5c]104                return;
[5ebff60]105        }
[2322a9f]106
107        for (l = txl->list; l; l = g_slist_next(l)) {
108                if (txl->type == TXL_STATUS) {
[5983eca]109                        txs_free((struct twitter_xml_status *) l->data);
[2322a9f]110                } else if (txl->type == TXL_ID) {
[62d2cfb]111                        g_free(l->data);
[2322a9f]112                } else if (txl->type == TXL_USER) {
[de923d5]113                        txu_free(l->data);
[2322a9f]114                }
115        }
116
[62d2cfb]117        g_slist_free(txl->list);
[fd65edb]118        g_free(txl);
[62d2cfb]119}
120
121/**
[2322a9f]122 * Compare status elements
123 */
124static gint twitter_compare_elements(gconstpointer a, gconstpointer b)
125{
126        struct twitter_xml_status *a_status = (struct twitter_xml_status *) a;
127        struct twitter_xml_status *b_status = (struct twitter_xml_status *) b;
128
129        if (a_status->created_at < b_status->created_at) {
130                return -1;
131        } else if (a_status->created_at > b_status->created_at) {
132                return 1;
133        } else {
134                return 0;
135        }
136}
137
138/**
139 * Add a buddy if it is not already added, set the status to logged in.
[62d2cfb]140 */
[3e69802]141static void twitter_add_buddy(struct im_connection *ic, char *name, const char *fullname)
[62d2cfb]142{
[1014cab]143        struct twitter_data *td = ic->proto_data;
144
[de923d5]145        // Check if the buddy is already in the buddy list.
[5983eca]146        if (!bee_user_by_handle(ic->bee, ic, name)) {
[62d2cfb]147                // The buddy is not in the list, add the buddy and set the status to logged in.
[5983eca]148                imcb_add_buddy(ic, name, NULL);
149                imcb_rename_buddy(ic, name, fullname);
[631ec80]150                if (td->flags & TWITTER_MODE_CHAT) {
[5c18a76]151                        /* Necessary so that nicks always get translated to the
152                           exact Twitter username. */
[5983eca]153                        imcb_buddy_nick_hint(ic, name, name);
[5ebff60]154                        if (td->timeline_gc) {
[7f557d5]155                                imcb_chat_add_buddy(td->timeline_gc, name);
[5ebff60]156                        }
157                } else if (td->flags & TWITTER_MODE_MANY) {
[5983eca]158                        imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL);
[5ebff60]159                }
[62d2cfb]160        }
161}
[1b221e0]162
[a7b9ec7]163/* Warning: May return a malloc()ed value, which will be free()d on the next
[de923d5]164   call. Only for short-term use. NOT THREADSAFE!  */
[6eca2eb]165char *twitter_parse_error(struct http_request *req)
[a7b9ec7]166{
167        static char *ret = NULL;
[5246133]168        json_value *root, *err;
[5983eca]169
[a7b9ec7]170        g_free(ret);
171        ret = NULL;
[5983eca]172
173        if (req->body_size > 0) {
[7a80925]174                root = json_parse(req->reply_body, req->body_size);
[5246133]175                err = json_o_get(root, "errors");
[dff0e0b]176                if (err && err->type == json_array && (err = err->u.array.values[0]) &&
[5246133]177                    err->type == json_object) {
178                        const char *msg = json_o_str(err, "message");
[5ebff60]179                        if (msg) {
[5246133]180                                ret = g_strdup_printf("%s (%s)", req->status_string, msg);
[5ebff60]181                        }
[5246133]182                }
183                json_value_free(root);
[a7b9ec7]184        }
[5983eca]185
[6eca2eb]186        return ret ? ret : req->status_string;
[a7b9ec7]187}
188
[fb351ce]189/* WATCH OUT: This function might or might not destroy your connection.
190   Sub-optimal indeed, but just be careful when this returns NULL! */
[e08ae0c]191static json_value *twitter_parse_response(struct im_connection *ic, struct http_request *req)
[2a6da96]192{
193        gboolean logging_in = !(ic->flags & OPT_LOGGED_IN);
194        gboolean periodic;
195        struct twitter_data *td = ic->proto_data;
[e08ae0c]196        json_value *ret;
[2a6da96]197        char path[64] = "", *s;
[5ebff60]198
[2a6da96]199        if ((s = strchr(req->request, ' '))) {
[5ebff60]200                path[sizeof(path) - 1] = '\0';
[2a6da96]201                strncpy(path, s + 1, sizeof(path) - 1);
[5ebff60]202                if ((s = strchr(path, '?')) || (s = strchr(path, ' '))) {
[2a6da96]203                        *s = '\0';
[5ebff60]204                }
[2a6da96]205        }
[5ebff60]206
[2a6da96]207        /* Kinda nasty. :-( Trying to suppress error messages, but only
208           for periodic (i.e. mentions/timeline) queries. */
209        periodic = strstr(path, "timeline") || strstr(path, "mentions");
[5ebff60]210
[2a6da96]211        if (req->status_code == 401 && logging_in) {
212                /* IIRC Twitter once had an outage where they were randomly
213                   throwing 401s so I'll keep treating this one as fatal
214                   only during login. */
[5246133]215                imcb_error(ic, "Authentication failure (%s)",
[5ebff60]216                           twitter_parse_error(req));
[2a6da96]217                imc_logout(ic, FALSE);
218                return NULL;
219        } else if (req->status_code != 200) {
220                // It didn't go well, output the error and return.
[5ebff60]221                if (!periodic || logging_in || ++td->http_fails >= 5) {
[96dd574]222                        twitter_log(ic, "Error: Could not retrieve %s: %s",
[5ebff60]223                                    path, twitter_parse_error(req));
224                }
225
226                if (logging_in) {
[2a6da96]227                        imc_logout(ic, TRUE);
[5ebff60]228                }
[2a6da96]229                return NULL;
230        } else {
231                td->http_fails = 0;
232        }
233
[7a80925]234        if ((ret = json_parse(req->reply_body, req->body_size)) == NULL) {
[2a6da96]235                imcb_error(ic, "Could not retrieve %s: %s",
[5ebff60]236                           path, "XML parse error");
[2a6da96]237        }
238        return ret;
239}
240
[1b221e0]241static void twitter_http_get_friends_ids(struct http_request *req);
242
243/**
244 * Get the friends ids.
245 */
[8203da9]246void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor)
[1b221e0]247{
[85c3004]248        // Primitive, but hey! It works...
[5983eca]249        char *args[2];
[5ebff60]250
[1b221e0]251        args[0] = "cursor";
[85c3004]252        args[1] = g_strdup_printf("%" G_GINT64_FORMAT, next_cursor);
[bb5ce4d1]253        twitter_http(ic, TWITTER_FRIENDS_IDS_URL, twitter_http_get_friends_ids, ic, 0, args, 2);
[1b221e0]254
255        g_free(args[1]);
256}
257
258/**
259 * Fill a list of ids.
260 */
[9e8c945]261static gboolean twitter_xt_get_friends_id_list(json_value *node, struct twitter_xml_list *txl)
[1b221e0]262{
[e08ae0c]263        json_value *c;
264        int i;
[5983eca]265
[62d2cfb]266        // Set the list type.
267        txl->type = TXL_ID;
[1b221e0]268
[e08ae0c]269        c = json_o_get(node, "ids");
[5ebff60]270        if (!c || c->type != json_array) {
[9e8c945]271                return FALSE;
[5ebff60]272        }
[1b221e0]273
[5ebff60]274        for (i = 0; i < c->u.array.length; i++) {
275                if (c->u.array.values[i]->type != json_integer) {
[0688e99]276                        continue;
[5ebff60]277                }
278
[e08ae0c]279                txl->list = g_slist_prepend(txl->list,
[5ebff60]280                                            g_strdup_printf("%" PRIu64, c->u.array.values[i]->u.integer));
[e08ae0c]281        }
[5ebff60]282
[e08ae0c]283        c = json_o_get(node, "next_cursor");
[5ebff60]284        if (c && c->type == json_integer) {
[e08ae0c]285                txl->next_cursor = c->u.integer;
[5ebff60]286        } else {
[e08ae0c]287                txl->next_cursor = -1;
[5ebff60]288        }
289
[9e8c945]290        return TRUE;
[1b221e0]291}
292
[de923d5]293static void twitter_get_users_lookup(struct im_connection *ic);
294
[1b221e0]295/**
296 * Callback for getting the friends ids.
297 */
298static void twitter_http_get_friends_ids(struct http_request *req)
299{
300        struct im_connection *ic;
[e08ae0c]301        json_value *parsed;
[1b221e0]302        struct twitter_xml_list *txl;
[3bd4a93]303        struct twitter_data *td;
[1b221e0]304
305        ic = req->data;
306
[62d2cfb]307        // Check if the connection is still active.
[5ebff60]308        if (!g_slist_find(twitter_connections, ic)) {
[62d2cfb]309                return;
[5ebff60]310        }
[5983eca]311
[37aa317]312        td = ic->proto_data;
[62d2cfb]313
[1b221e0]314        // Parse the data.
[5ebff60]315        if (!(parsed = twitter_parse_response(ic, req))) {
[2a6da96]316                return;
[5ebff60]317        }
318
[bbff22d]319        txl = g_new0(struct twitter_xml_list, 1);
320        txl->list = td->follow_ids;
321
[d0752e8]322        twitter_xt_get_friends_id_list(parsed, txl);
[e08ae0c]323        json_value_free(parsed);
[1b221e0]324
[de923d5]325        td->follow_ids = txl->list;
[5ebff60]326        if (txl->next_cursor) {
[de923d5]327                /* These were just numbers. Up to 4000 in a response AFAIK so if we get here
328                   we may be using a spammer account. \o/ */
[1b221e0]329                twitter_get_friends_ids(ic, txl->next_cursor);
[5ebff60]330        } else {
[de923d5]331                /* Now to convert all those numbers into names.. */
332                twitter_get_users_lookup(ic);
[5ebff60]333        }
[de923d5]334
335        txl->list = NULL;
336        txl_free(txl);
337}
338
[9e8c945]339static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl);
[de923d5]340static void twitter_http_get_users_lookup(struct http_request *req);
341
342static void twitter_get_users_lookup(struct im_connection *ic)
343{
344        struct twitter_data *td = ic->proto_data;
345        char *args[2] = {
346                "user_id",
347                NULL,
348        };
349        GString *ids = g_string_new("");
350        int i;
[5ebff60]351
[de923d5]352        /* We can request up to 100 users at a time. */
[5ebff60]353        for (i = 0; i < 100 && td->follow_ids; i++) {
354                g_string_append_printf(ids, ",%s", (char *) td->follow_ids->data);
[de923d5]355                g_free(td->follow_ids->data);
356                td->follow_ids = g_slist_remove(td->follow_ids, td->follow_ids->data);
357        }
358        if (ids->len > 0) {
359                args[1] = ids->str + 1;
360                /* POST, because I think ids can be up to 1KB long. */
361                twitter_http(ic, TWITTER_USERS_LOOKUP_URL, twitter_http_get_users_lookup, ic, 1, args, 2);
362        } else {
363                /* We have all users. Continue with login. (Get statuses.) */
364                td->flags |= TWITTER_HAVE_FRIENDS;
365                twitter_login_finish(ic);
366        }
367        g_string_free(ids, TRUE);
368}
369
370/**
371 * Callback for getting (twitter)friends...
372 *
[5ebff60]373 * Be afraid, be very afraid! This function will potentially add hundreds of "friends". "Who has
374 * hundreds of friends?" you wonder? You probably not, since you are reading the source of
[de923d5]375 * BitlBee... Get a life and meet new people!
376 */
377static void twitter_http_get_users_lookup(struct http_request *req)
378{
379        struct im_connection *ic = req->data;
[e08ae0c]380        json_value *parsed;
[de923d5]381        struct twitter_xml_list *txl;
382        GSList *l = NULL;
383        struct twitter_xml_user *user;
384
385        // Check if the connection is still active.
[5ebff60]386        if (!g_slist_find(twitter_connections, ic)) {
[de923d5]387                return;
[5ebff60]388        }
[de923d5]389
390        // Get the user list from the parsed xml feed.
[5ebff60]391        if (!(parsed = twitter_parse_response(ic, req))) {
[2a6da96]392                return;
[5ebff60]393        }
[bbff22d]394
395        txl = g_new0(struct twitter_xml_list, 1);
396        txl->list = NULL;
397
[d0752e8]398        twitter_xt_get_users(parsed, txl);
[e08ae0c]399        json_value_free(parsed);
[de923d5]400
401        // Add the users as buddies.
402        for (l = txl->list; l; l = g_slist_next(l)) {
403                user = l->data;
404                twitter_add_buddy(ic, user->screen_name, user->name);
405        }
406
407        // Free the structure.
[62d2cfb]408        txl_free(txl);
[de923d5]409
410        twitter_get_users_lookup(ic);
[1b221e0]411}
412
[8e3b7ac]413struct twitter_xml_user *twitter_xt_get_user(const json_value *node)
414{
415        struct twitter_xml_user *txu;
[73ee390]416        json_value *jv;
[5ebff60]417
[8e3b7ac]418        txu = g_new0(struct twitter_xml_user, 1);
419        txu->name = g_strdup(json_o_str(node, "name"));
420        txu->screen_name = g_strdup(json_o_str(node, "screen_name"));
[5ebff60]421
[73ee390]422        jv = json_o_get(node, "id");
423        txu->uid = jv->u.integer;
[5ebff60]424
[8e3b7ac]425        return txu;
426}
427
[62d2cfb]428/**
429 * Function to fill a twitter_xml_list struct.
430 * It sets:
431 *  - all <user>s from the <users> element.
432 */
[9e8c945]433static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl)
[62d2cfb]434{
435        struct twitter_xml_user *txu;
[e08ae0c]436        int i;
[62d2cfb]437
438        // Set the type of the list.
439        txl->type = TXL_USER;
440
[5ebff60]441        if (!node || node->type != json_array) {
[9e8c945]442                return FALSE;
[5ebff60]443        }
[e08ae0c]444
[62d2cfb]445        // The root <users> node should hold the list of users <user>
446        // Walk over the nodes children.
[5ebff60]447        for (i = 0; i < node->u.array.length; i++) {
[8e3b7ac]448                txu = twitter_xt_get_user(node->u.array.values[i]);
[5ebff60]449                if (txu) {
[8e3b7ac]450                        txl->list = g_slist_prepend(txl->list, txu);
[5ebff60]451                }
[62d2cfb]452        }
453
[9e8c945]454        return TRUE;
[62d2cfb]455}
456
[2b02617]457#ifdef __GLIBC__
458#define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S %z %Y"
459#else
460#define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S +0000 %Y"
461#endif
[62d2cfb]462
[cd60710]463static void expand_entities(char **text, const json_value *node);
[d1356cb]464
[1b221e0]465/**
466 * Function to fill a twitter_xml_status struct.
467 * It sets:
468 *  - the status text and
469 *  - the created_at timestamp and
470 *  - the status id and
471 *  - the user in a twitter_xml_user struct.
472 */
[c9b5817]473static struct twitter_xml_status *twitter_xt_get_status(const json_value *node)
[1b221e0]474{
[c9b5817]475        struct twitter_xml_status *txs;
[cd60710]476        const json_value *rt = NULL;
[5ebff60]477
478        if (node->type != json_object) {
[9e8c945]479                return FALSE;
[5ebff60]480        }
[c9b5817]481        txs = g_new0(struct twitter_xml_status, 1);
[1b221e0]482
[5ebff60]483        JSON_O_FOREACH(node, k, v) {
[8e3b7ac]484                if (strcmp("text", k) == 0 && v->type == json_string) {
[fb351ce]485                        txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1);
[29f72b7]486                        strip_html(txs->text);
[8e3b7ac]487                } else if (strcmp("retweeted_status", k) == 0 && v->type == json_object) {
488                        rt = v;
489                } else if (strcmp("created_at", k) == 0 && v->type == json_string) {
[08579a1]490                        struct tm parsed;
[5983eca]491
[08579a1]492                        /* Very sensitive to changes to the formatting of
493                           this field. :-( Also assumes the timezone used
494                           is UTC since C time handling functions suck. */
[5ebff60]495                        if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL) {
[5983eca]496                                txs->created_at = mktime_utc(&parsed);
[5ebff60]497                        }
[8e3b7ac]498                } else if (strcmp("user", k) == 0 && v->type == json_object) {
499                        txs->user = twitter_xt_get_user(v);
500                } else if (strcmp("id", k) == 0 && v->type == json_integer) {
[573e274]501                        txs->rt_id = txs->id = v->u.integer;
[8e3b7ac]502                } else if (strcmp("in_reply_to_status_id", k) == 0 && v->type == json_integer) {
503                        txs->reply_to = v->u.integer;
[ce81acd]504                }
[1b221e0]505        }
[5983eca]506
[0fff0b2]507        /* If it's a (truncated) retweet, get the original. Even if the API claims it
508           wasn't truncated because it may be lying. */
509        if (rt) {
[c9b5817]510                struct twitter_xml_status *rtxs = twitter_xt_get_status(rt);
511                if (rtxs) {
512                        g_free(txs->text);
[0ca1d79]513                        txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text);
[c9b5817]514                        txs->id = rtxs->id;
[0ca1d79]515                        txs_free(rtxs);
[e193aeb]516                }
[cd60710]517        } else {
518                expand_entities(&txs->text, node);
[d1356cb]519        }
520
[5ebff60]521        if (txs->text && txs->user && txs->id) {
[c9b5817]522                return txs;
[5ebff60]523        }
524
[c9b5817]525        txs_free(txs);
526        return NULL;
[d1356cb]527}
528
529/**
530 * Function to fill a twitter_xml_status struct (DM variant).
531 */
[2cd8540]532static struct twitter_xml_status *twitter_xt_get_dm(const json_value *node)
[d1356cb]533{
[2cd8540]534        struct twitter_xml_status *txs;
[5ebff60]535
536        if (node->type != json_object) {
[d1356cb]537                return FALSE;
[5ebff60]538        }
[2cd8540]539        txs = g_new0(struct twitter_xml_status, 1);
[d1356cb]540
[5ebff60]541        JSON_O_FOREACH(node, k, v) {
[d1356cb]542                if (strcmp("text", k) == 0 && v->type == json_string) {
543                        txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1);
[29f72b7]544                        strip_html(txs->text);
[d1356cb]545                } else if (strcmp("created_at", k) == 0 && v->type == json_string) {
546                        struct tm parsed;
547
548                        /* Very sensitive to changes to the formatting of
549                           this field. :-( Also assumes the timezone used
550                           is UTC since C time handling functions suck. */
[5ebff60]551                        if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL) {
[d1356cb]552                                txs->created_at = mktime_utc(&parsed);
[5ebff60]553                        }
[d1356cb]554                } else if (strcmp("sender", k) == 0 && v->type == json_object) {
555                        txs->user = twitter_xt_get_user(v);
556                } else if (strcmp("id", k) == 0 && v->type == json_integer) {
557                        txs->id = v->u.integer;
558                }
559        }
560
[cd60710]561        expand_entities(&txs->text, node);
[d1356cb]562
[5ebff60]563        if (txs->text && txs->user && txs->id) {
[2cd8540]564                return txs;
[5ebff60]565        }
566
[2cd8540]567        txs_free(txs);
568        return NULL;
[d1356cb]569}
570
[cd60710]571static void expand_entities(char **text, const json_value *node)
[5ebff60]572{
[cd60710]573        json_value *entities, *quoted;
574        char *quote_url = NULL, *quote_text = NULL;
575
576        if (!((entities = json_o_get(node, "entities")) && entities->type == json_object))
577                return;
578        if ((quoted = json_o_get(node, "quoted_status")) && quoted->type == json_object) {
579                /* New "retweets with comments" feature. Note that this info
580                 * seems to be included in the streaming API only! Grab the
581                 * full message and try to insert it when we run into the
582                 * Tweet entity. */
583                struct twitter_xml_status *txs = twitter_xt_get_status(quoted);
584                quote_text = g_strdup_printf("@%s: %s", txs->user->screen_name, txs->text);
585                quote_url = g_strdup_printf("%s/status/%" G_GUINT64_FORMAT, txs->user->screen_name, txs->id);
586                txs_free(txs);
587        } else {
588                quoted = NULL;
589        }
590
[5ebff60]591        JSON_O_FOREACH(entities, k, v) {
[d1356cb]592                int i;
[5ebff60]593
594                if (v->type != json_array) {
[d1356cb]595                        continue;
[5ebff60]596                }
597                if (strcmp(k, "urls") != 0 && strcmp(k, "media") != 0) {
[d1356cb]598                        continue;
[5ebff60]599                }
600
601                for (i = 0; i < v->u.array.length; i++) {
[cd60710]602                        const char *format = "%s%s <%s>%s";
603
[5ebff60]604                        if (v->u.array.values[i]->type != json_object) {
[8e3b7ac]605                                continue;
[5ebff60]606                        }
607
[d1356cb]608                        const char *kort = json_o_str(v->u.array.values[i], "url");
609                        const char *disp = json_o_str(v->u.array.values[i], "display_url");
[cd60710]610                        const char *full = json_o_str(v->u.array.values[i], "expanded_url");
[d1356cb]611                        char *pos, *new;
[5ebff60]612
[cd60710]613                        if (!kort || !disp || !(pos = strstr(*text, kort))) {
[429a9b1]614                                continue;
[5ebff60]615                        }
[cd60710]616                        if (quote_url && strstr(full, quote_url)) {
617                                format = "%s<%s> [%s]%s";
618                                disp = quote_text;
619                        }
[5ebff60]620
[d1356cb]621                        *pos = '\0';
[cd60710]622                        new = g_strdup_printf(format, *text, kort,
[d1356cb]623                                              disp, pos + strlen(kort));
[5ebff60]624
[cd60710]625                        g_free(*text);
626                        *text = new;
[429a9b1]627                }
[e193aeb]628        }
[cd60710]629        g_free(quote_text);
630        g_free(quote_url);
[1b221e0]631}
632
633/**
634 * Function to fill a twitter_xml_list struct.
635 * It sets:
636 *  - all <status>es within the <status> element and
637 *  - the next_cursor.
638 */
[9e8c945]639static gboolean twitter_xt_get_status_list(struct im_connection *ic, const json_value *node,
640                                           struct twitter_xml_list *txl)
[1b221e0]641{
642        struct twitter_xml_status *txs;
[8e3b7ac]643        int i;
[1b221e0]644
[62d2cfb]645        // Set the type of the list.
646        txl->type = TXL_STATUS;
[5ebff60]647
648        if (node->type != json_array) {
[9e8c945]649                return FALSE;
[5ebff60]650        }
[62d2cfb]651
[1b221e0]652        // The root <statuses> node should hold the list of statuses <status>
653        // Walk over the nodes children.
[5ebff60]654        for (i = 0; i < node->u.array.length; i++) {
[c9b5817]655                txs = twitter_xt_get_status(node->u.array.values[i]);
[5ebff60]656                if (!txs) {
[c9b5817]657                        continue;
[5ebff60]658                }
659
[8e3b7ac]660                txl->list = g_slist_prepend(txl->list, txs);
[1b221e0]661        }
662
[9e8c945]663        return TRUE;
[1b221e0]664}
665
[c9b5817]666/* Will log messages either way. Need to keep track of IDs for stream deduping.
667   Plus, show_ids is on by default and I don't see why anyone would disable it. */
[ce81acd]668static char *twitter_msg_add_id(struct im_connection *ic,
[5ebff60]669                                struct twitter_xml_status *txs, const char *prefix)
[ce81acd]670{
671        struct twitter_data *td = ic->proto_data;
[c9b5817]672        int reply_to = -1;
673        bee_user_t *bu;
[5983eca]674
675        if (txs->reply_to) {
[ce81acd]676                int i;
[5ebff60]677                for (i = 0; i < TWITTER_LOG_LENGTH; i++) {
[5983eca]678                        if (td->log[i].id == txs->reply_to) {
[c9b5817]679                                reply_to = i;
[ce81acd]680                                break;
681                        }
[5ebff60]682                }
[ce81acd]683        }
[5983eca]684
[c9b5817]685        if (txs->user && txs->user->screen_name &&
686            (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) {
687                struct twitter_user_data *tud = bu->data;
688
689                if (txs->id > tud->last_id) {
690                        tud->last_id = txs->id;
691                        tud->last_time = txs->created_at;
692                }
693        }
[5ebff60]694
[c9b5817]695        td->log_id = (td->log_id + 1) % TWITTER_LOG_LENGTH;
696        td->log[td->log_id].id = txs->id;
697        td->log[td->log_id].bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name);
[5ebff60]698
[67f6828]699        /* This is all getting hairy. :-( If we RT'ed something ourselves,
700           remember OUR id instead so undo will work. In other cases, the
701           original tweet's id should be remembered for deduplicating. */
[5ebff60]702        if (g_strcasecmp(txs->user->screen_name, td->user) == 0) {
[67f6828]703                td->log[td->log_id].id = txs->rt_id;
[c43146d]704                /* More useful than NULL. */
705                td->log[td->log_id].bu = &twitter_log_local_user;
[5ebff60]706        }
707
[0ca1d79]708        if (set_getbool(&ic->acc->set, "show_ids")) {
[5ebff60]709                if (reply_to != -1) {
[0ca1d79]710                        return g_strdup_printf("\002[\002%02x->%02x\002]\002 %s%s",
711                                               td->log_id, reply_to, prefix, txs->text);
[5ebff60]712                } else {
[0ca1d79]713                        return g_strdup_printf("\002[\002%02x\002]\002 %s%s",
714                                               td->log_id, prefix, txs->text);
[5ebff60]715                }
[0ca1d79]716        } else {
[5ebff60]717                if (*prefix) {
[0ca1d79]718                        return g_strconcat(prefix, txs->text, NULL);
[5ebff60]719                } else {
[0ca1d79]720                        return NULL;
[5ebff60]721                }
[0ca1d79]722        }
[ce81acd]723}
724
[73ee390]725/**
726 * Function that is called to see the filter statuses in groupchat windows.
727 */
728static void twitter_status_show_filter(struct im_connection *ic, struct twitter_xml_status *status)
729{
730        struct twitter_data *td = ic->proto_data;
731        char *msg = twitter_msg_add_id(ic, status, "");
732        struct twitter_filter *tf;
733        GSList *f;
734        GSList *l;
735
736        for (f = td->filters; f; f = g_slist_next(f)) {
737                tf = f->data;
738
739                switch (tf->type) {
740                case TWITTER_FILTER_TYPE_FOLLOW:
[5ebff60]741                        if (status->user->uid != tf->uid) {
[73ee390]742                                continue;
[5ebff60]743                        }
[73ee390]744                        break;
745
746                case TWITTER_FILTER_TYPE_TRACK:
[5ebff60]747                        if (strcasestr(status->text, tf->text) == NULL) {
[73ee390]748                                continue;
[5ebff60]749                        }
[73ee390]750                        break;
751
752                default:
753                        continue;
754                }
755
756                for (l = tf->groupchats; l; l = g_slist_next(l)) {
757                        imcb_chat_msg(l->data, status->user->screen_name,
[5ebff60]758                                      msg ? msg : status->text, 0, 0);
[73ee390]759                }
760        }
761
762        g_free(msg);
763}
764
[62d2cfb]765/**
766 * Function that is called to see the statuses in a groupchat window.
767 */
[29f72b7]768static void twitter_status_show_chat(struct im_connection *ic, struct twitter_xml_status *status)
[62d2cfb]769{
770        struct twitter_data *td = ic->proto_data;
771        struct groupchat *gc;
[29f72b7]772        gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0;
773        char *msg;
[62d2cfb]774
775        // Create a new groupchat if it does not exsist.
[631ec80]776        gc = twitter_groupchat_init(ic);
[62d2cfb]777
[5ebff60]778        if (!me) {
[29f72b7]779                /* MUST be done before twitter_msg_add_id() to avoid #872. */
780                twitter_add_buddy(ic, status->user->screen_name, status->user->name);
[5ebff60]781        }
[29f72b7]782        msg = twitter_msg_add_id(ic, status, "");
[5ebff60]783
[29f72b7]784        // Say it!
785        if (me) {
786                imcb_chat_log(gc, "You: %s", msg ? msg : status->text);
787        } else {
788                imcb_chat_msg(gc, status->user->screen_name,
[5ebff60]789                              msg ? msg : status->text, 0, status->created_at);
[62d2cfb]790        }
[29f72b7]791
792        g_free(msg);
[62d2cfb]793}
794
795/**
796 * Function that is called to see statuses as private messages.
797 */
[29f72b7]798static void twitter_status_show_msg(struct im_connection *ic, struct twitter_xml_status *status)
[62d2cfb]799{
800        struct twitter_data *td = ic->proto_data;
[631ec80]801        char from[MAX_STRING] = "";
[29f72b7]802        char *prefix = NULL, *text = NULL;
803        gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0;
[62d2cfb]804
[631ec80]805        if (td->flags & TWITTER_MODE_ONE) {
[5983eca]806                g_snprintf(from, sizeof(from) - 1, "%s_%s", td->prefix, ic->acc->user);
807                from[MAX_STRING - 1] = '\0';
[e88fbe27]808        }
[5983eca]809
[5ebff60]810        if (td->flags & TWITTER_MODE_ONE) {
[29f72b7]811                prefix = g_strdup_printf("\002<\002%s\002>\002 ",
812                                         status->user->screen_name);
[5ebff60]813        } else if (!me) {
[29f72b7]814                twitter_add_buddy(ic, status->user->screen_name, status->user->name);
[5ebff60]815        } else {
[29f72b7]816                prefix = g_strdup("You: ");
[5ebff60]817        }
[5983eca]818
[29f72b7]819        text = twitter_msg_add_id(ic, status, prefix ? prefix : "");
[5983eca]820
[29f72b7]821        imcb_buddy_msg(ic,
822                       *from ? from : status->user->screen_name,
823                       text ? text : status->text, 0, status->created_at);
[5983eca]824
[29f72b7]825        g_free(text);
826        g_free(prefix);
827}
[5983eca]828
[29f72b7]829static void twitter_status_show(struct im_connection *ic, struct twitter_xml_status *status)
830{
831        struct twitter_data *td = ic->proto_data;
[7549d00]832        char *last_id_str;
[5ebff60]833
834        if (status->user == NULL || status->text == NULL) {
[29f72b7]835                return;
[5ebff60]836        }
837
[29f72b7]838        /* Grrrr. Would like to do this during parsing, but can't access
839           settings from there. */
[5ebff60]840        if (set_getbool(&ic->acc->set, "strip_newlines")) {
[29f72b7]841                strip_newlines(status->text);
[5ebff60]842        }
843
844        if (status->from_filter) {
[73ee390]845                twitter_status_show_filter(ic, status);
[5ebff60]846        } else if (td->flags & TWITTER_MODE_CHAT) {
[29f72b7]847                twitter_status_show_chat(ic, status);
[5ebff60]848        } else {
[29f72b7]849                twitter_status_show_msg(ic, status);
[5ebff60]850        }
[5983eca]851
[29f72b7]852        // Update the timeline_id to hold the highest id, so that by the next request
853        // we won't pick up the updates already in the list.
[573e274]854        td->timeline_id = MAX(td->timeline_id, status->rt_id);
[7549d00]855
856        last_id_str = g_strdup_printf("%" G_GUINT64_FORMAT, td->timeline_id);
[fedc8f1]857        set_setstr(&ic->acc->set, "_last_tweet", last_id_str);
[7549d00]858        g_free(last_id_str);
[62d2cfb]859}
860
[73ee390]861static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o, gboolean from_filter);
[ddc2de5]862
863static void twitter_http_stream(struct http_request *req)
864{
[dff0e0b]865        struct im_connection *ic = req->data;
[dd672e2]866        struct twitter_data *td;
[dff0e0b]867        json_value *parsed;
[62f6b45]868        int len = 0;
869        char c, *nl;
[73ee390]870        gboolean from_filter;
[5ebff60]871
872        if (!g_slist_find(twitter_connections, ic)) {
[dff0e0b]873                return;
[5ebff60]874        }
875
[e132b60]876        ic->flags |= OPT_PONGED;
[dd672e2]877        td = ic->proto_data;
[5ebff60]878
[dd672e2]879        if ((req->flags & HTTPC_EOF) || !req->reply_body) {
[5ebff60]880                if (req == td->stream) {
[73ee390]881                        td->stream = NULL;
[5ebff60]882                } else if (req == td->filter_stream) {
[73ee390]883                        td->filter_stream = NULL;
[5ebff60]884                }
[73ee390]885
[dd672e2]886                imcb_error(ic, "Stream closed (%s)", req->status_string);
[7320610]887                if (req->status_code == 401) {
888                        imcb_error(ic, "Check your system clock.");
889                }
[dd672e2]890                imc_logout(ic, TRUE);
891                return;
892        }
[5ebff60]893
[62f6b45]894        /* MUST search for CRLF, not just LF:
895           https://dev.twitter.com/docs/streaming-apis/processing#Parsing_responses */
[5ebff60]896        if (!(nl = strstr(req->reply_body, "\r\n"))) {
[ddc2de5]897                return;
[5ebff60]898        }
899
[62f6b45]900        len = nl - req->reply_body;
901        if (len > 0) {
902                c = req->reply_body[len];
903                req->reply_body[len] = '\0';
[5ebff60]904
[7a80925]905                if ((parsed = json_parse(req->reply_body, req->body_size))) {
[73ee390]906                        from_filter = (req == td->filter_stream);
907                        twitter_stream_handle_object(ic, parsed, from_filter);
[62f6b45]908                }
909                json_value_free(parsed);
910                req->reply_body[len] = c;
[dff0e0b]911        }
[5ebff60]912
[62f6b45]913        http_flush_bytes(req, len + 2);
[5ebff60]914
[dff0e0b]915        /* One notification might bring multiple events! */
[5ebff60]916        if (req->body_size > 0) {
[dff0e0b]917                twitter_http_stream(req);
[5ebff60]918        }
[ddc2de5]919}
[2322a9f]920
[5aa96fc8]921static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o);
[c9b5817]922static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs);
[5aa96fc8]923
[73ee390]924static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o, gboolean from_filter)
[dff0e0b]925{
[5aa96fc8]926        struct twitter_data *td = ic->proto_data;
[c9b5817]927        struct twitter_xml_status *txs;
[d1356cb]928        json_value *c;
[5ebff60]929
[c9b5817]930        if ((txs = twitter_xt_get_status(o))) {
[73ee390]931                txs->from_filter = from_filter;
[2dcde94]932                gboolean ret = twitter_stream_handle_status(ic, txs);
933                txs_free(txs);
934                return ret;
[d1356cb]935        } else if ((c = json_o_get(o, "direct_message")) &&
[2cd8540]936                   (txs = twitter_xt_get_dm(c))) {
[5ebff60]937                if (g_strcasecmp(txs->user->screen_name, td->user) != 0) {
[2cd8540]938                        imcb_buddy_msg(ic, txs->user->screen_name,
[5ebff60]939                                       txs->text, 0, txs->created_at);
940                }
[d1356cb]941                txs_free(txs);
942                return TRUE;
[5aa96fc8]943        } else if ((c = json_o_get(o, "event")) && c->type == json_string) {
944                twitter_stream_handle_event(ic, o);
945                return TRUE;
946        } else if ((c = json_o_get(o, "disconnect")) && c->type == json_object) {
947                /* HACK: Because we're inside an event handler, we can't just
948                   disconnect here. Instead, just change the HTTP status string
949                   into a Twitter status string. */
950                char *reason = json_o_strdup(c, "reason");
951                if (reason) {
952                        g_free(td->stream->status_string);
953                        td->stream->status_string = reason;
954                }
955                return TRUE;
[dff0e0b]956        }
957        return FALSE;
958}
959
[c9b5817]960static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs)
961{
962        struct twitter_data *td = ic->proto_data;
963        int i;
[5ebff60]964
[c9b5817]965        for (i = 0; i < TWITTER_LOG_LENGTH; i++) {
966                if (td->log[i].id == txs->id) {
[29f72b7]967                        /* Got a duplicate (RT, probably). Drop it. */
[c9b5817]968                        return TRUE;
969                }
970        }
[5ebff60]971
[9f8bb17]972        if (!(g_strcasecmp(txs->user->screen_name, td->user) == 0 ||
[2dcde94]973              set_getbool(&ic->acc->set, "fetch_mentions") ||
[c9b5817]974              bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) {
975                /* Tweet is from an unknown person and the user does not want
976                   to see @mentions, so drop it. twitter_stream_handle_event()
977                   picks up new follows so this simple filter should be safe. */
978                /* TODO: The streaming API seems to do poor @mention matching.
979                   I.e. I'm getting mentions for @WilmerSomething, not just for
980                   @Wilmer. But meh. You want spam, you get spam. */
981                return TRUE;
982        }
[5ebff60]983
[29f72b7]984        twitter_status_show(ic, txs);
[5ebff60]985
[c9b5817]986        return TRUE;
987}
988
[5aa96fc8]989static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o)
990{
991        struct twitter_data *td = ic->proto_data;
992        json_value *source = json_o_get(o, "source");
993        json_value *target = json_o_get(o, "target");
994        const char *type = json_o_str(o, "event");
[5ebff60]995
[5aa96fc8]996        if (!type || !source || source->type != json_object
[5ebff60]997            || !target || target->type != json_object) {
[5aa96fc8]998                return FALSE;
999        }
[5ebff60]1000
[5aa96fc8]1001        if (strcmp(type, "follow") == 0) {
1002                struct twitter_xml_user *us = twitter_xt_get_user(source);
1003                struct twitter_xml_user *ut = twitter_xt_get_user(target);
[9f8bb17]1004                if (g_strcasecmp(us->screen_name, td->user) == 0) {
[5aa96fc8]1005                        twitter_add_buddy(ic, ut->screen_name, ut->name);
1006                }
1007                txu_free(us);
1008                txu_free(ut);
1009        }
[5ebff60]1010
[5aa96fc8]1011        return TRUE;
1012}
1013
[dff0e0b]1014gboolean twitter_open_stream(struct im_connection *ic)
1015{
1016        struct twitter_data *td = ic->proto_data;
[5ebff60]1017        char *args[2] = { "with", "followings" };
1018
[dff0e0b]1019        if ((td->stream = twitter_http(ic, TWITTER_USER_STREAM_URL,
[62f6b45]1020                                       twitter_http_stream, ic, 0, args, 2))) {
[dff0e0b]1021                /* This flag must be enabled or we'll get no data until EOF
1022                   (which err, kind of, defeats the purpose of a streaming API). */
1023                td->stream->flags |= HTTPC_STREAMING;
1024                return TRUE;
1025        }
[5ebff60]1026
[dff0e0b]1027        return FALSE;
1028}
1029
[73ee390]1030static gboolean twitter_filter_stream(struct im_connection *ic)
1031{
1032        struct twitter_data *td = ic->proto_data;
[5ebff60]1033        char *args[4] = { "follow", NULL, "track", NULL };
[73ee390]1034        GString *followstr = g_string_new("");
1035        GString *trackstr = g_string_new("");
1036        gboolean ret = FALSE;
1037        struct twitter_filter *tf;
1038        GSList *l;
1039
1040        for (l = td->filters; l; l = g_slist_next(l)) {
1041                tf = l->data;
1042
1043                switch (tf->type) {
1044                case TWITTER_FILTER_TYPE_FOLLOW:
[5ebff60]1045                        if (followstr->len > 0) {
[73ee390]1046                                g_string_append_c(followstr, ',');
[5ebff60]1047                        }
[73ee390]1048
1049                        g_string_append_printf(followstr, "%" G_GUINT64_FORMAT,
[5ebff60]1050                                               tf->uid);
[73ee390]1051                        break;
1052
1053                case TWITTER_FILTER_TYPE_TRACK:
[5ebff60]1054                        if (trackstr->len > 0) {
[73ee390]1055                                g_string_append_c(trackstr, ',');
[5ebff60]1056                        }
[73ee390]1057
1058                        g_string_append(trackstr, tf->text);
1059                        break;
1060
1061                default:
1062                        continue;
1063                }
1064        }
1065
1066        args[1] = followstr->str;
1067        args[3] = trackstr->str;
1068
[5ebff60]1069        if (td->filter_stream) {
[73ee390]1070                http_close(td->filter_stream);
[5ebff60]1071        }
[73ee390]1072
1073        if ((td->filter_stream = twitter_http(ic, TWITTER_FILTER_STREAM_URL,
1074                                              twitter_http_stream, ic, 0,
[5ebff60]1075                                              args, 4))) {
[73ee390]1076                /* This flag must be enabled or we'll get no data until EOF
1077                   (which err, kind of, defeats the purpose of a streaming API). */
1078                td->filter_stream->flags |= HTTPC_STREAMING;
1079                ret = TRUE;
1080        }
1081
1082        g_string_free(followstr, TRUE);
1083        g_string_free(trackstr, TRUE);
1084
1085        return ret;
1086}
1087
1088static void twitter_filter_users_post(struct http_request *req)
1089{
1090        struct im_connection *ic = req->data;
1091        struct twitter_data *td;
1092        struct twitter_filter *tf;
1093        GList *users = NULL;
1094        json_value *parsed;
1095        json_value *id;
1096        const char *name;
1097        GString *fstr;
1098        GSList *l;
1099        GList *u;
1100        int i;
1101
1102        // Check if the connection is still active.
[5ebff60]1103        if (!g_slist_find(twitter_connections, ic)) {
[73ee390]1104                return;
[5ebff60]1105        }
[73ee390]1106
1107        td = ic->proto_data;
1108
[5ebff60]1109        if (!(parsed = twitter_parse_response(ic, req))) {
[73ee390]1110                return;
[5ebff60]1111        }
[73ee390]1112
1113        for (l = td->filters; l; l = g_slist_next(l)) {
1114                tf = l->data;
1115
[5ebff60]1116                if (tf->type == TWITTER_FILTER_TYPE_FOLLOW) {
[73ee390]1117                        users = g_list_prepend(users, tf);
[5ebff60]1118                }
[73ee390]1119        }
1120
[5ebff60]1121        if (parsed->type != json_array) {
[73ee390]1122                goto finish;
[5ebff60]1123        }
[73ee390]1124
1125        for (i = 0; i < parsed->u.array.length; i++) {
1126                id = json_o_get(parsed->u.array.values[i], "id");
1127                name = json_o_str(parsed->u.array.values[i], "screen_name");
1128
[5ebff60]1129                if (!name || !id || id->type != json_integer) {
[73ee390]1130                        continue;
[5ebff60]1131                }
[73ee390]1132
1133                for (u = users; u; u = g_list_next(u)) {
1134                        tf = u->data;
1135
1136                        if (g_strcasecmp(tf->text, name) == 0) {
1137                                tf->uid = id->u.integer;
1138                                users = g_list_delete_link(users, u);
1139                                break;
1140                        }
1141                }
1142        }
1143
1144finish:
1145        json_value_free(parsed);
1146        twitter_filter_stream(ic);
1147
[5ebff60]1148        if (!users) {
[73ee390]1149                return;
[5ebff60]1150        }
[73ee390]1151
1152        fstr = g_string_new("");
1153
1154        for (u = users; u; u = g_list_next(u)) {
[5ebff60]1155                if (fstr->len > 0) {
[73ee390]1156                        g_string_append(fstr, ", ");
[5ebff60]1157                }
[73ee390]1158
1159                g_string_append(fstr, tf->text);
1160        }
1161
1162        imcb_error(ic, "Failed UID acquisitions: %s", fstr->str);
1163
1164        g_string_free(fstr, TRUE);
1165        g_list_free(users);
1166}
1167
1168gboolean twitter_open_filter_stream(struct im_connection *ic)
1169{
1170        struct twitter_data *td = ic->proto_data;
[5ebff60]1171        char *args[2] = { "screen_name", NULL };
[73ee390]1172        GString *ustr = g_string_new("");
1173        struct twitter_filter *tf;
1174        struct http_request *req;
1175        GSList *l;
1176
1177        for (l = td->filters; l; l = g_slist_next(l)) {
1178                tf = l->data;
1179
[5ebff60]1180                if (tf->type != TWITTER_FILTER_TYPE_FOLLOW || tf->uid != 0) {
[73ee390]1181                        continue;
[5ebff60]1182                }
[73ee390]1183
[5ebff60]1184                if (ustr->len > 0) {
[73ee390]1185                        g_string_append_c(ustr, ',');
[5ebff60]1186                }
[73ee390]1187
1188                g_string_append(ustr, tf->text);
1189        }
1190
1191        if (ustr->len == 0) {
1192                g_string_free(ustr, TRUE);
1193                return twitter_filter_stream(ic);
1194        }
1195
1196        args[1] = ustr->str;
1197        req = twitter_http(ic, TWITTER_USERS_LOOKUP_URL,
[5ebff60]1198                           twitter_filter_users_post,
1199                           ic, 0, args, 2);
[73ee390]1200
1201        g_string_free(ustr, TRUE);
1202        return req != NULL;
1203}
1204
[dff0e0b]1205static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor);
1206static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor);
1207
[2322a9f]1208/**
1209 * Get the timeline with optionally mentions
1210 */
[2f9027c]1211gboolean twitter_get_timeline(struct im_connection *ic, gint64 next_cursor)
[2322a9f]1212{
1213        struct twitter_data *td = ic->proto_data;
1214        gboolean include_mentions = set_getbool(&ic->acc->set, "fetch_mentions");
1215
1216        if (td->flags & TWITTER_DOING_TIMELINE) {
[11ec078]1217                if (++td->http_fails >= 5) {
1218                        imcb_error(ic, "Fetch timeout (%d)", td->flags);
1219                        imc_logout(ic, TRUE);
[2f9027c]1220                        return FALSE;
[11ec078]1221                }
[2322a9f]1222        }
1223
1224        td->flags |= TWITTER_DOING_TIMELINE;
1225
1226        twitter_get_home_timeline(ic, next_cursor);
1227
1228        if (include_mentions) {
1229                twitter_get_mentions(ic, next_cursor);
1230        }
[5ebff60]1231
[2f9027c]1232        return TRUE;
[2322a9f]1233}
1234
1235/**
1236 * Call this one after receiving timeline/mentions. Show to user once we have
1237 * both.
1238 */
1239void twitter_flush_timeline(struct im_connection *ic)
1240{
1241        struct twitter_data *td = ic->proto_data;
1242        gboolean include_mentions = set_getbool(&ic->acc->set, "fetch_mentions");
[b5fe39b]1243        int show_old_mentions = set_getint(&ic->acc->set, "show_old_mentions");
[2322a9f]1244        struct twitter_xml_list *home_timeline = td->home_timeline_obj;
1245        struct twitter_xml_list *mentions = td->mentions_obj;
[29f72b7]1246        guint64 last_id = 0;
[2322a9f]1247        GSList *output = NULL;
1248        GSList *l;
1249
[29f72b7]1250        imcb_connected(ic);
[5ebff60]1251
[2322a9f]1252        if (!(td->flags & TWITTER_GOT_TIMELINE)) {
1253                return;
1254        }
1255
1256        if (include_mentions && !(td->flags & TWITTER_GOT_MENTIONS)) {
1257                return;
1258        }
1259
1260        if (home_timeline && home_timeline->list) {
1261                for (l = home_timeline->list; l; l = g_slist_next(l)) {
1262                        output = g_slist_insert_sorted(output, l->data, twitter_compare_elements);
1263                }
1264        }
1265
1266        if (include_mentions && mentions && mentions->list) {
1267                for (l = mentions->list; l; l = g_slist_next(l)) {
[b5fe39b]1268                        if (show_old_mentions < 1 && output && twitter_compare_elements(l->data, output->data) < 0) {
[2322a9f]1269                                continue;
1270                        }
1271
1272                        output = g_slist_insert_sorted(output, l->data, twitter_compare_elements);
1273                }
1274        }
1275
1276        // See if the user wants to see the messages in a groupchat window or as private messages.
[29f72b7]1277        while (output) {
1278                struct twitter_xml_status *txs = output->data;
[5ebff60]1279                if (txs->id != last_id) {
[29f72b7]1280                        twitter_status_show(ic, txs);
[5ebff60]1281                }
[29f72b7]1282                last_id = txs->id;
1283                output = g_slist_remove(output, txs);
1284        }
[2322a9f]1285
[199fea6]1286        txl_free(home_timeline);
1287        txl_free(mentions);
[2322a9f]1288
1289        td->flags &= ~(TWITTER_DOING_TIMELINE | TWITTER_GOT_TIMELINE | TWITTER_GOT_MENTIONS);
[199fea6]1290        td->home_timeline_obj = td->mentions_obj = NULL;
[2322a9f]1291}
1292
[ddc2de5]1293static void twitter_http_get_home_timeline(struct http_request *req);
1294static void twitter_http_get_mentions(struct http_request *req);
1295
[2322a9f]1296/**
1297 * Get the timeline.
1298 */
[ddc2de5]1299static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
[2322a9f]1300{
1301        struct twitter_data *td = ic->proto_data;
1302
[199fea6]1303        txl_free(td->home_timeline_obj);
[2322a9f]1304        td->home_timeline_obj = NULL;
1305        td->flags &= ~TWITTER_GOT_TIMELINE;
1306
[429a9b1]1307        char *args[6];
[2322a9f]1308        args[0] = "cursor";
[85c3004]1309        args[1] = g_strdup_printf("%" G_GINT64_FORMAT, next_cursor);
[429a9b1]1310        args[2] = "include_entities";
1311        args[3] = "true";
[2322a9f]1312        if (td->timeline_id) {
[429a9b1]1313                args[4] = "since_id";
[7549d00]1314                args[5] = g_strdup_printf("%" G_GUINT64_FORMAT, td->timeline_id);
[2322a9f]1315        }
1316
[90fc864]1317        if (twitter_http(ic, TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, args,
[5ebff60]1318                         td->timeline_id ? 6 : 4) == NULL) {
1319                if (++td->http_fails >= 5) {
[90fc864]1320                        imcb_error(ic, "Could not retrieve %s: %s",
1321                                   TWITTER_HOME_TIMELINE_URL, "connection failed");
[5ebff60]1322                }
[90fc864]1323                td->flags |= TWITTER_GOT_TIMELINE;
1324                twitter_flush_timeline(ic);
1325        }
[2322a9f]1326
1327        g_free(args[1]);
1328        if (td->timeline_id) {
[429a9b1]1329                g_free(args[5]);
[2322a9f]1330        }
1331}
1332
1333/**
1334 * Get mentions.
1335 */
[ddc2de5]1336static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)
[2322a9f]1337{
1338        struct twitter_data *td = ic->proto_data;
1339
[199fea6]1340        txl_free(td->mentions_obj);
[2322a9f]1341        td->mentions_obj = NULL;
1342        td->flags &= ~TWITTER_GOT_MENTIONS;
1343
[429a9b1]1344        char *args[6];
[2322a9f]1345        args[0] = "cursor";
[85c3004]1346        args[1] = g_strdup_printf("%" G_GINT64_FORMAT, next_cursor);
[429a9b1]1347        args[2] = "include_entities";
1348        args[3] = "true";
[2322a9f]1349        if (td->timeline_id) {
[429a9b1]1350                args[4] = "since_id";
[85c3004]1351                args[5] = g_strdup_printf("%" G_GUINT64_FORMAT, td->timeline_id);
[b5fe39b]1352        } else {
1353                args[4] = "count";
1354                args[5] = g_strdup_printf("%d", set_getint(&ic->acc->set, "show_old_mentions"));
[2322a9f]1355        }
1356
[b5fe39b]1357        if (twitter_http(ic, TWITTER_MENTIONS_URL, twitter_http_get_mentions,
1358                         ic, 0, args, 6) == NULL) {
[5ebff60]1359                if (++td->http_fails >= 5) {
[90fc864]1360                        imcb_error(ic, "Could not retrieve %s: %s",
1361                                   TWITTER_MENTIONS_URL, "connection failed");
[5ebff60]1362                }
[90fc864]1363                td->flags |= TWITTER_GOT_MENTIONS;
1364                twitter_flush_timeline(ic);
1365        }
[2322a9f]1366
1367        g_free(args[1]);
[2fb1262]1368        g_free(args[5]);
[2322a9f]1369}
1370
[1b221e0]1371/**
1372 * Callback for getting the home timeline.
1373 */
1374static void twitter_http_get_home_timeline(struct http_request *req)
1375{
[62d2cfb]1376        struct im_connection *ic = req->data;
[37aa317]1377        struct twitter_data *td;
[8e3b7ac]1378        json_value *parsed;
[1b221e0]1379        struct twitter_xml_list *txl;
[62d2cfb]1380
1381        // Check if the connection is still active.
[5ebff60]1382        if (!g_slist_find(twitter_connections, ic)) {
[62d2cfb]1383                return;
[5ebff60]1384        }
[5983eca]1385
[37aa317]1386        td = ic->proto_data;
[1b221e0]1387
[2322a9f]1388        // The root <statuses> node should hold the list of statuses <status>
[5ebff60]1389        if (!(parsed = twitter_parse_response(ic, req))) {
[2a6da96]1390                goto end;
[5ebff60]1391        }
[bbff22d]1392
1393        txl = g_new0(struct twitter_xml_list, 1);
1394        txl->list = NULL;
1395
[d0752e8]1396        twitter_xt_get_status_list(ic, parsed, txl);
[2fb1262]1397        json_value_free(parsed);
[2322a9f]1398
1399        td->home_timeline_obj = txl;
1400
[5ebff60]1401end:
1402        if (!g_slist_find(twitter_connections, ic)) {
[fb351ce]1403                return;
[5ebff60]1404        }
[fb351ce]1405
[2322a9f]1406        td->flags |= TWITTER_GOT_TIMELINE;
1407
1408        twitter_flush_timeline(ic);
1409}
1410
1411/**
1412 * Callback for getting mentions.
1413 */
1414static void twitter_http_get_mentions(struct http_request *req)
1415{
1416        struct im_connection *ic = req->data;
1417        struct twitter_data *td;
[8e3b7ac]1418        json_value *parsed;
[2322a9f]1419        struct twitter_xml_list *txl;
1420
1421        // Check if the connection is still active.
[5ebff60]1422        if (!g_slist_find(twitter_connections, ic)) {
[1b221e0]1423                return;
[5ebff60]1424        }
[2322a9f]1425
1426        td = ic->proto_data;
1427
[1b221e0]1428        // The root <statuses> node should hold the list of statuses <status>
[5ebff60]1429        if (!(parsed = twitter_parse_response(ic, req))) {
[2a6da96]1430                goto end;
[5ebff60]1431        }
[bbff22d]1432
1433        txl = g_new0(struct twitter_xml_list, 1);
1434        txl->list = NULL;
1435
[d0752e8]1436        twitter_xt_get_status_list(ic, parsed, txl);
[2fb1262]1437        json_value_free(parsed);
[1b221e0]1438
[2322a9f]1439        td->mentions_obj = txl;
[1b221e0]1440
[5ebff60]1441end:
1442        if (!g_slist_find(twitter_connections, ic)) {
[fb351ce]1443                return;
[5ebff60]1444        }
[fb351ce]1445
[2322a9f]1446        td->flags |= TWITTER_GOT_MENTIONS;
1447
1448        twitter_flush_timeline(ic);
[1b221e0]1449}
1450
1451/**
[de923d5]1452 * Callback to use after sending a POST request to twitter.
1453 * (Generic, used for a few kinds of queries.)
[1b221e0]1454 */
[7d53efb]1455static void twitter_http_post(struct http_request *req)
[1b221e0]1456{
1457        struct im_connection *ic = req->data;
[7b87539]1458        struct twitter_data *td;
[24132ec]1459        json_value *parsed, *id;
[1b221e0]1460
[62d2cfb]1461        // Check if the connection is still active.
[5ebff60]1462        if (!g_slist_find(twitter_connections, ic)) {
[62d2cfb]1463                return;
[5ebff60]1464        }
[62d2cfb]1465
[7b87539]1466        td = ic->proto_data;
1467        td->last_status_id = 0;
[5983eca]1468
[5ebff60]1469        if (!(parsed = twitter_parse_response(ic, req))) {
[1b221e0]1470                return;
[5ebff60]1471        }
1472
[67f6828]1473        if ((id = json_o_get(parsed, "id")) && id->type == json_integer) {
[24132ec]1474                td->last_status_id = id->u.integer;
[67f6828]1475        }
[5ebff60]1476
[c751e51]1477        json_value_free(parsed);
[5ebff60]1478
1479        if (req->flags & TWITTER_HTTP_USER_ACK) {
[b235228]1480                twitter_log(ic, "Command processed successfully");
[5ebff60]1481        }
[1b221e0]1482}
1483
1484/**
1485 * Function to POST a new status to twitter.
[5983eca]1486 */
[b890626]1487void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to)
[1b221e0]1488{
[5983eca]1489        char *args[4] = {
[b890626]1490                "status", msg,
1491                "in_reply_to_status_id",
[85c3004]1492                g_strdup_printf("%" G_GUINT64_FORMAT, in_reply_to)
[b890626]1493        };
[5ebff60]1494
[b890626]1495        twitter_http(ic, TWITTER_STATUS_UPDATE_URL, twitter_http_post, ic, 1,
[5ebff60]1496                     args, in_reply_to ? 4 : 2);
[b890626]1497        g_free(args[3]);
[1b221e0]1498}
1499
1500
[62d2cfb]1501/**
1502 * Function to POST a new message to twitter.
1503 */
1504void twitter_direct_messages_new(struct im_connection *ic, char *who, char *msg)
1505{
[5983eca]1506        char *args[4];
[5ebff60]1507
[62d2cfb]1508        args[0] = "screen_name";
1509        args[1] = who;
1510        args[2] = "text";
1511        args[3] = msg;
1512        // Use the same callback as for twitter_post_status, since it does basically the same.
[ba3233c]1513        twitter_http(ic, TWITTER_DIRECT_MESSAGES_NEW_URL, twitter_http_post, ic, 1, args, 4);
[62d2cfb]1514}
[7d53efb]1515
1516void twitter_friendships_create_destroy(struct im_connection *ic, char *who, int create)
1517{
[5983eca]1518        char *args[2];
[5ebff60]1519
[7d53efb]1520        args[0] = "screen_name";
1521        args[1] = who;
[5983eca]1522        twitter_http(ic, create ? TWITTER_FRIENDSHIPS_CREATE_URL : TWITTER_FRIENDSHIPS_DESTROY_URL,
[5ebff60]1523                     twitter_http_post, ic, 1, args, 2);
[a26af5c]1524}
[7b87539]1525
1526void twitter_status_destroy(struct im_connection *ic, guint64 id)
1527{
1528        char *url;
[5ebff60]1529
[85c3004]1530        url = g_strdup_printf("%s%" G_GUINT64_FORMAT "%s",
1531                              TWITTER_STATUS_DESTROY_URL, id, ".json");
[b235228]1532        twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0,
1533                       TWITTER_HTTP_USER_ACK);
[7b87539]1534        g_free(url);
1535}
[b890626]1536
1537void twitter_status_retweet(struct im_connection *ic, guint64 id)
1538{
1539        char *url;
[5ebff60]1540
[85c3004]1541        url = g_strdup_printf("%s%" G_GUINT64_FORMAT "%s",
1542                              TWITTER_STATUS_RETWEET_URL, id, ".json");
[b235228]1543        twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0,
1544                       TWITTER_HTTP_USER_ACK);
[b890626]1545        g_free(url);
1546}
[d18dee42]1547
1548/**
1549 * Report a user for sending spam.
1550 */
1551void twitter_report_spam(struct im_connection *ic, char *screen_name)
1552{
1553        char *args[2] = {
1554                "screen_name",
1555                NULL,
1556        };
[5ebff60]1557
[d18dee42]1558        args[1] = screen_name;
[b235228]1559        twitter_http_f(ic, TWITTER_REPORT_SPAM_URL, twitter_http_post,
1560                       ic, 1, args, 2, TWITTER_HTTP_USER_ACK);
[d18dee42]1561}
[b61c74c]1562
1563/**
1564 * Favourite a tweet.
1565 */
1566void twitter_favourite_tweet(struct im_connection *ic, guint64 id)
1567{
[75bda8b]1568        char *args[2] = {
1569                "id",
1570                NULL,
1571        };
[5ebff60]1572
[85c3004]1573        args[1] = g_strdup_printf("%" G_GUINT64_FORMAT, id);
[75bda8b]1574        twitter_http_f(ic, TWITTER_FAVORITE_CREATE_URL, twitter_http_post,
1575                       ic, 1, args, 2, TWITTER_HTTP_USER_ACK);
1576        g_free(args[1]);
[b61c74c]1577}
[1201fcb]1578
1579static void twitter_http_status_show_url(struct http_request *req)
1580{
1581        struct im_connection *ic = req->data;
1582        json_value *parsed, *id;
1583        const char *name;
1584
1585        // Check if the connection is still active.
1586        if (!g_slist_find(twitter_connections, ic)) {
1587                return;
1588        }
1589
1590        if (!(parsed = twitter_parse_response(ic, req))) {
1591                return;
1592        }
1593
1594        /* for the parson branch:
1595        name = json_object_dotget_string(json_object(parsed), "user.screen_name");
1596        id = json_object_get_integer(json_object(parsed), "id");
1597        */
1598
1599        name = json_o_str(json_o_get(parsed, "user"), "screen_name");
1600        id = json_o_get(parsed, "id");
1601
1602        if (name && id && id->type == json_integer) {
1603                twitter_log(ic, "https://twitter.com/%s/status/%" G_GUINT64_FORMAT, name, id->u.integer);
1604        } else {
1605                twitter_log(ic, "Error: could not fetch tweet url.");
1606        }
1607
1608        json_value_free(parsed);
1609}
1610
1611void twitter_status_show_url(struct im_connection *ic, guint64 id)
1612{
1613        char *url = g_strdup_printf("%s%" G_GUINT64_FORMAT "%s", TWITTER_STATUS_SHOW_URL, id, ".json");
1614        twitter_http(ic, url, twitter_http_status_show_url, ic, 0, NULL, 0);
1615        g_free(url);
1616}
Note: See TracBrowser for help on using the repository browser.