source: protocols/twitter/twitter_lib.c @ bbff22d

Last change on this file since bbff22d 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
Line 
1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Simple module to facilitate twitter functionality.                       *
5*                                                                           *
6*  Copyright 2009-2010 Geert Mulders <g.c.w.m.mulders@gmail.com>            *
7*  Copyright 2010-2013 Wilmer van der Gaast <wilmer@gaast.net>              *
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
25/* For strptime(): */
26#if (__sun)
27#else
28#define _XOPEN_SOURCE
29#endif
30
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"
38#include "json_util.h"
39#include <ctype.h>
40#include <errno.h>
41
42#define TXL_STATUS 1
43#define TXL_USER 2
44#define TXL_ID 3
45
46struct twitter_xml_list {
47        int type;
48        gint64 next_cursor;
49        GSList *list;
50};
51
52struct twitter_xml_user {
53        guint64 uid;
54        char *name;
55        char *screen_name;
56};
57
58struct twitter_xml_status {
59        time_t created_at;
60        char *text;
61        struct twitter_xml_user *user;
62        guint64 id, rt_id; /* Usually equal, with RTs id == *original* id */
63        guint64 reply_to;
64        gboolean from_filter;
65};
66
67/**
68 * Frees a twitter_xml_user struct.
69 */
70static void txu_free(struct twitter_xml_user *txu)
71{
72        if (txu == NULL) {
73                return;
74        }
75
76        g_free(txu->name);
77        g_free(txu->screen_name);
78        g_free(txu);
79}
80
81/**
82 * Frees a twitter_xml_status struct.
83 */
84static void txs_free(struct twitter_xml_status *txs)
85{
86        if (txs == NULL) {
87                return;
88        }
89
90        g_free(txs->text);
91        txu_free(txs->user);
92        g_free(txs);
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;
102
103        if (txl == NULL) {
104                return;
105        }
106
107        for (l = txl->list; l; l = g_slist_next(l)) {
108                if (txl->type == TXL_STATUS) {
109                        txs_free((struct twitter_xml_status *) l->data);
110                } else if (txl->type == TXL_ID) {
111                        g_free(l->data);
112                } else if (txl->type == TXL_USER) {
113                        txu_free(l->data);
114                }
115        }
116
117        g_slist_free(txl->list);
118        g_free(txl);
119}
120
121/**
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.
140 */
141static void twitter_add_buddy(struct im_connection *ic, char *name, const char *fullname)
142{
143        struct twitter_data *td = ic->proto_data;
144
145        // Check if the buddy is already in the buddy list.
146        if (!bee_user_by_handle(ic->bee, ic, name)) {
147                // The buddy is not in the list, add the buddy and set the status to logged in.
148                imcb_add_buddy(ic, name, NULL);
149                imcb_rename_buddy(ic, name, fullname);
150                if (td->flags & TWITTER_MODE_CHAT) {
151                        /* Necessary so that nicks always get translated to the
152                           exact Twitter username. */
153                        imcb_buddy_nick_hint(ic, name, name);
154                        if (td->timeline_gc) {
155                                imcb_chat_add_buddy(td->timeline_gc, name);
156                        }
157                } else if (td->flags & TWITTER_MODE_MANY) {
158                        imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL);
159                }
160        }
161}
162
163/* Warning: May return a malloc()ed value, which will be free()d on the next
164   call. Only for short-term use. NOT THREADSAFE!  */
165char *twitter_parse_error(struct http_request *req)
166{
167        static char *ret = NULL;
168        json_value *root, *err;
169
170        g_free(ret);
171        ret = NULL;
172
173        if (req->body_size > 0) {
174                root = json_parse(req->reply_body, req->body_size);
175                err = json_o_get(root, "errors");
176                if (err && err->type == json_array && (err = err->u.array.values[0]) &&
177                    err->type == json_object) {
178                        const char *msg = json_o_str(err, "message");
179                        if (msg) {
180                                ret = g_strdup_printf("%s (%s)", req->status_string, msg);
181                        }
182                }
183                json_value_free(root);
184        }
185
186        return ret ? ret : req->status_string;
187}
188
189/* WATCH OUT: This function might or might not destroy your connection.
190   Sub-optimal indeed, but just be careful when this returns NULL! */
191static json_value *twitter_parse_response(struct im_connection *ic, struct http_request *req)
192{
193        gboolean logging_in = !(ic->flags & OPT_LOGGED_IN);
194        gboolean periodic;
195        struct twitter_data *td = ic->proto_data;
196        json_value *ret;
197        char path[64] = "", *s;
198
199        if ((s = strchr(req->request, ' '))) {
200                path[sizeof(path) - 1] = '\0';
201                strncpy(path, s + 1, sizeof(path) - 1);
202                if ((s = strchr(path, '?')) || (s = strchr(path, ' '))) {
203                        *s = '\0';
204                }
205        }
206
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");
210
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. */
215                imcb_error(ic, "Authentication failure (%s)",
216                           twitter_parse_error(req));
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.
221                if (!periodic || logging_in || ++td->http_fails >= 5) {
222                        twitter_log(ic, "Error: Could not retrieve %s: %s",
223                                    path, twitter_parse_error(req));
224                }
225
226                if (logging_in) {
227                        imc_logout(ic, TRUE);
228                }
229                return NULL;
230        } else {
231                td->http_fails = 0;
232        }
233
234        if ((ret = json_parse(req->reply_body, req->body_size)) == NULL) {
235                imcb_error(ic, "Could not retrieve %s: %s",
236                           path, "XML parse error");
237        }
238        return ret;
239}
240
241static void twitter_http_get_friends_ids(struct http_request *req);
242
243/**
244 * Get the friends ids.
245 */
246void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor)
247{
248        // Primitive, but hey! It works...
249        char *args[2];
250
251        args[0] = "cursor";
252        args[1] = g_strdup_printf("%" G_GINT64_FORMAT, next_cursor);
253        twitter_http(ic, TWITTER_FRIENDS_IDS_URL, twitter_http_get_friends_ids, ic, 0, args, 2);
254
255        g_free(args[1]);
256}
257
258/**
259 * Fill a list of ids.
260 */
261static gboolean twitter_xt_get_friends_id_list(json_value *node, struct twitter_xml_list *txl)
262{
263        json_value *c;
264        int i;
265
266        // Set the list type.
267        txl->type = TXL_ID;
268
269        c = json_o_get(node, "ids");
270        if (!c || c->type != json_array) {
271                return FALSE;
272        }
273
274        for (i = 0; i < c->u.array.length; i++) {
275                if (c->u.array.values[i]->type != json_integer) {
276                        continue;
277                }
278
279                txl->list = g_slist_prepend(txl->list,
280                                            g_strdup_printf("%" PRIu64, c->u.array.values[i]->u.integer));
281        }
282
283        c = json_o_get(node, "next_cursor");
284        if (c && c->type == json_integer) {
285                txl->next_cursor = c->u.integer;
286        } else {
287                txl->next_cursor = -1;
288        }
289
290        return TRUE;
291}
292
293static void twitter_get_users_lookup(struct im_connection *ic);
294
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;
301        json_value *parsed;
302        struct twitter_xml_list *txl;
303        struct twitter_data *td;
304
305        ic = req->data;
306
307        // Check if the connection is still active.
308        if (!g_slist_find(twitter_connections, ic)) {
309                return;
310        }
311
312        td = ic->proto_data;
313
314        // Parse the data.
315        if (!(parsed = twitter_parse_response(ic, req))) {
316                return;
317        }
318
319        txl = g_new0(struct twitter_xml_list, 1);
320        txl->list = td->follow_ids;
321
322        twitter_xt_get_friends_id_list(parsed, txl);
323        json_value_free(parsed);
324
325        td->follow_ids = txl->list;
326        if (txl->next_cursor) {
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/ */
329                twitter_get_friends_ids(ic, txl->next_cursor);
330        } else {
331                /* Now to convert all those numbers into names.. */
332                twitter_get_users_lookup(ic);
333        }
334
335        txl->list = NULL;
336        txl_free(txl);
337}
338
339static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl);
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;
351
352        /* We can request up to 100 users at a time. */
353        for (i = 0; i < 100 && td->follow_ids; i++) {
354                g_string_append_printf(ids, ",%s", (char *) td->follow_ids->data);
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 *
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
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;
380        json_value *parsed;
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.
386        if (!g_slist_find(twitter_connections, ic)) {
387                return;
388        }
389
390        // Get the user list from the parsed xml feed.
391        if (!(parsed = twitter_parse_response(ic, req))) {
392                return;
393        }
394
395        txl = g_new0(struct twitter_xml_list, 1);
396        txl->list = NULL;
397
398        twitter_xt_get_users(parsed, txl);
399        json_value_free(parsed);
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.
408        txl_free(txl);
409
410        twitter_get_users_lookup(ic);
411}
412
413struct twitter_xml_user *twitter_xt_get_user(const json_value *node)
414{
415        struct twitter_xml_user *txu;
416        json_value *jv;
417
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"));
421
422        jv = json_o_get(node, "id");
423        txu->uid = jv->u.integer;
424
425        return txu;
426}
427
428/**
429 * Function to fill a twitter_xml_list struct.
430 * It sets:
431 *  - all <user>s from the <users> element.
432 */
433static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl)
434{
435        struct twitter_xml_user *txu;
436        int i;
437
438        // Set the type of the list.
439        txl->type = TXL_USER;
440
441        if (!node || node->type != json_array) {
442                return FALSE;
443        }
444
445        // The root <users> node should hold the list of users <user>
446        // Walk over the nodes children.
447        for (i = 0; i < node->u.array.length; i++) {
448                txu = twitter_xt_get_user(node->u.array.values[i]);
449                if (txu) {
450                        txl->list = g_slist_prepend(txl->list, txu);
451                }
452        }
453
454        return TRUE;
455}
456
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
462
463static void expand_entities(char **text, const json_value *node);
464
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 */
473static struct twitter_xml_status *twitter_xt_get_status(const json_value *node)
474{
475        struct twitter_xml_status *txs;
476        const json_value *rt = NULL;
477
478        if (node->type != json_object) {
479                return FALSE;
480        }
481        txs = g_new0(struct twitter_xml_status, 1);
482
483        JSON_O_FOREACH(node, k, v) {
484                if (strcmp("text", k) == 0 && v->type == json_string) {
485                        txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1);
486                        strip_html(txs->text);
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) {
490                        struct tm parsed;
491
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. */
495                        if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL) {
496                                txs->created_at = mktime_utc(&parsed);
497                        }
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) {
501                        txs->rt_id = txs->id = v->u.integer;
502                } else if (strcmp("in_reply_to_status_id", k) == 0 && v->type == json_integer) {
503                        txs->reply_to = v->u.integer;
504                }
505        }
506
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) {
510                struct twitter_xml_status *rtxs = twitter_xt_get_status(rt);
511                if (rtxs) {
512                        g_free(txs->text);
513                        txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text);
514                        txs->id = rtxs->id;
515                        txs_free(rtxs);
516                }
517        } else {
518                expand_entities(&txs->text, node);
519        }
520
521        if (txs->text && txs->user && txs->id) {
522                return txs;
523        }
524
525        txs_free(txs);
526        return NULL;
527}
528
529/**
530 * Function to fill a twitter_xml_status struct (DM variant).
531 */
532static struct twitter_xml_status *twitter_xt_get_dm(const json_value *node)
533{
534        struct twitter_xml_status *txs;
535
536        if (node->type != json_object) {
537                return FALSE;
538        }
539        txs = g_new0(struct twitter_xml_status, 1);
540
541        JSON_O_FOREACH(node, k, v) {
542                if (strcmp("text", k) == 0 && v->type == json_string) {
543                        txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1);
544                        strip_html(txs->text);
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. */
551                        if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL) {
552                                txs->created_at = mktime_utc(&parsed);
553                        }
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
561        expand_entities(&txs->text, node);
562
563        if (txs->text && txs->user && txs->id) {
564                return txs;
565        }
566
567        txs_free(txs);
568        return NULL;
569}
570
571static void expand_entities(char **text, const json_value *node)
572{
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
591        JSON_O_FOREACH(entities, k, v) {
592                int i;
593
594                if (v->type != json_array) {
595                        continue;
596                }
597                if (strcmp(k, "urls") != 0 && strcmp(k, "media") != 0) {
598                        continue;
599                }
600
601                for (i = 0; i < v->u.array.length; i++) {
602                        const char *format = "%s%s <%s>%s";
603
604                        if (v->u.array.values[i]->type != json_object) {
605                                continue;
606                        }
607
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");
610                        const char *full = json_o_str(v->u.array.values[i], "expanded_url");
611                        char *pos, *new;
612
613                        if (!kort || !disp || !(pos = strstr(*text, kort))) {
614                                continue;
615                        }
616                        if (quote_url && strstr(full, quote_url)) {
617                                format = "%s<%s> [%s]%s";
618                                disp = quote_text;
619                        }
620
621                        *pos = '\0';
622                        new = g_strdup_printf(format, *text, kort,
623                                              disp, pos + strlen(kort));
624
625                        g_free(*text);
626                        *text = new;
627                }
628        }
629        g_free(quote_text);
630        g_free(quote_url);
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 */
639static gboolean twitter_xt_get_status_list(struct im_connection *ic, const json_value *node,
640                                           struct twitter_xml_list *txl)
641{
642        struct twitter_xml_status *txs;
643        int i;
644
645        // Set the type of the list.
646        txl->type = TXL_STATUS;
647
648        if (node->type != json_array) {
649                return FALSE;
650        }
651
652        // The root <statuses> node should hold the list of statuses <status>
653        // Walk over the nodes children.
654        for (i = 0; i < node->u.array.length; i++) {
655                txs = twitter_xt_get_status(node->u.array.values[i]);
656                if (!txs) {
657                        continue;
658                }
659
660                txl->list = g_slist_prepend(txl->list, txs);
661        }
662
663        return TRUE;
664}
665
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. */
668static char *twitter_msg_add_id(struct im_connection *ic,
669                                struct twitter_xml_status *txs, const char *prefix)
670{
671        struct twitter_data *td = ic->proto_data;
672        int reply_to = -1;
673        bee_user_t *bu;
674
675        if (txs->reply_to) {
676                int i;
677                for (i = 0; i < TWITTER_LOG_LENGTH; i++) {
678                        if (td->log[i].id == txs->reply_to) {
679                                reply_to = i;
680                                break;
681                        }
682                }
683        }
684
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        }
694
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);
698
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. */
702        if (g_strcasecmp(txs->user->screen_name, td->user) == 0) {
703                td->log[td->log_id].id = txs->rt_id;
704                /* More useful than NULL. */
705                td->log[td->log_id].bu = &twitter_log_local_user;
706        }
707
708        if (set_getbool(&ic->acc->set, "show_ids")) {
709                if (reply_to != -1) {
710                        return g_strdup_printf("\002[\002%02x->%02x\002]\002 %s%s",
711                                               td->log_id, reply_to, prefix, txs->text);
712                } else {
713                        return g_strdup_printf("\002[\002%02x\002]\002 %s%s",
714                                               td->log_id, prefix, txs->text);
715                }
716        } else {
717                if (*prefix) {
718                        return g_strconcat(prefix, txs->text, NULL);
719                } else {
720                        return NULL;
721                }
722        }
723}
724
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:
741                        if (status->user->uid != tf->uid) {
742                                continue;
743                        }
744                        break;
745
746                case TWITTER_FILTER_TYPE_TRACK:
747                        if (strcasestr(status->text, tf->text) == NULL) {
748                                continue;
749                        }
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,
758                                      msg ? msg : status->text, 0, 0);
759                }
760        }
761
762        g_free(msg);
763}
764
765/**
766 * Function that is called to see the statuses in a groupchat window.
767 */
768static void twitter_status_show_chat(struct im_connection *ic, struct twitter_xml_status *status)
769{
770        struct twitter_data *td = ic->proto_data;
771        struct groupchat *gc;
772        gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0;
773        char *msg;
774
775        // Create a new groupchat if it does not exsist.
776        gc = twitter_groupchat_init(ic);
777
778        if (!me) {
779                /* MUST be done before twitter_msg_add_id() to avoid #872. */
780                twitter_add_buddy(ic, status->user->screen_name, status->user->name);
781        }
782        msg = twitter_msg_add_id(ic, status, "");
783
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,
789                              msg ? msg : status->text, 0, status->created_at);
790        }
791
792        g_free(msg);
793}
794
795/**
796 * Function that is called to see statuses as private messages.
797 */
798static void twitter_status_show_msg(struct im_connection *ic, struct twitter_xml_status *status)
799{
800        struct twitter_data *td = ic->proto_data;
801        char from[MAX_STRING] = "";
802        char *prefix = NULL, *text = NULL;
803        gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0;
804
805        if (td->flags & TWITTER_MODE_ONE) {
806                g_snprintf(from, sizeof(from) - 1, "%s_%s", td->prefix, ic->acc->user);
807                from[MAX_STRING - 1] = '\0';
808        }
809
810        if (td->flags & TWITTER_MODE_ONE) {
811                prefix = g_strdup_printf("\002<\002%s\002>\002 ",
812                                         status->user->screen_name);
813        } else if (!me) {
814                twitter_add_buddy(ic, status->user->screen_name, status->user->name);
815        } else {
816                prefix = g_strdup("You: ");
817        }
818
819        text = twitter_msg_add_id(ic, status, prefix ? prefix : "");
820
821        imcb_buddy_msg(ic,
822                       *from ? from : status->user->screen_name,
823                       text ? text : status->text, 0, status->created_at);
824
825        g_free(text);
826        g_free(prefix);
827}
828
829static void twitter_status_show(struct im_connection *ic, struct twitter_xml_status *status)
830{
831        struct twitter_data *td = ic->proto_data;
832        char *last_id_str;
833
834        if (status->user == NULL || status->text == NULL) {
835                return;
836        }
837
838        /* Grrrr. Would like to do this during parsing, but can't access
839           settings from there. */
840        if (set_getbool(&ic->acc->set, "strip_newlines")) {
841                strip_newlines(status->text);
842        }
843
844        if (status->from_filter) {
845                twitter_status_show_filter(ic, status);
846        } else if (td->flags & TWITTER_MODE_CHAT) {
847                twitter_status_show_chat(ic, status);
848        } else {
849                twitter_status_show_msg(ic, status);
850        }
851
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.
854        td->timeline_id = MAX(td->timeline_id, status->rt_id);
855
856        last_id_str = g_strdup_printf("%" G_GUINT64_FORMAT, td->timeline_id);
857        set_setstr(&ic->acc->set, "_last_tweet", last_id_str);
858        g_free(last_id_str);
859}
860
861static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o, gboolean from_filter);
862
863static void twitter_http_stream(struct http_request *req)
864{
865        struct im_connection *ic = req->data;
866        struct twitter_data *td;
867        json_value *parsed;
868        int len = 0;
869        char c, *nl;
870        gboolean from_filter;
871
872        if (!g_slist_find(twitter_connections, ic)) {
873                return;
874        }
875
876        ic->flags |= OPT_PONGED;
877        td = ic->proto_data;
878
879        if ((req->flags & HTTPC_EOF) || !req->reply_body) {
880                if (req == td->stream) {
881                        td->stream = NULL;
882                } else if (req == td->filter_stream) {
883                        td->filter_stream = NULL;
884                }
885
886                imcb_error(ic, "Stream closed (%s)", req->status_string);
887                if (req->status_code == 401) {
888                        imcb_error(ic, "Check your system clock.");
889                }
890                imc_logout(ic, TRUE);
891                return;
892        }
893
894        /* MUST search for CRLF, not just LF:
895           https://dev.twitter.com/docs/streaming-apis/processing#Parsing_responses */
896        if (!(nl = strstr(req->reply_body, "\r\n"))) {
897                return;
898        }
899
900        len = nl - req->reply_body;
901        if (len > 0) {
902                c = req->reply_body[len];
903                req->reply_body[len] = '\0';
904
905                if ((parsed = json_parse(req->reply_body, req->body_size))) {
906                        from_filter = (req == td->filter_stream);
907                        twitter_stream_handle_object(ic, parsed, from_filter);
908                }
909                json_value_free(parsed);
910                req->reply_body[len] = c;
911        }
912
913        http_flush_bytes(req, len + 2);
914
915        /* One notification might bring multiple events! */
916        if (req->body_size > 0) {
917                twitter_http_stream(req);
918        }
919}
920
921static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o);
922static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs);
923
924static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o, gboolean from_filter)
925{
926        struct twitter_data *td = ic->proto_data;
927        struct twitter_xml_status *txs;
928        json_value *c;
929
930        if ((txs = twitter_xt_get_status(o))) {
931                txs->from_filter = from_filter;
932                gboolean ret = twitter_stream_handle_status(ic, txs);
933                txs_free(txs);
934                return ret;
935        } else if ((c = json_o_get(o, "direct_message")) &&
936                   (txs = twitter_xt_get_dm(c))) {
937                if (g_strcasecmp(txs->user->screen_name, td->user) != 0) {
938                        imcb_buddy_msg(ic, txs->user->screen_name,
939                                       txs->text, 0, txs->created_at);
940                }
941                txs_free(txs);
942                return TRUE;
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;
956        }
957        return FALSE;
958}
959
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;
964
965        for (i = 0; i < TWITTER_LOG_LENGTH; i++) {
966                if (td->log[i].id == txs->id) {
967                        /* Got a duplicate (RT, probably). Drop it. */
968                        return TRUE;
969                }
970        }
971
972        if (!(g_strcasecmp(txs->user->screen_name, td->user) == 0 ||
973              set_getbool(&ic->acc->set, "fetch_mentions") ||
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        }
983
984        twitter_status_show(ic, txs);
985
986        return TRUE;
987}
988
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");
995
996        if (!type || !source || source->type != json_object
997            || !target || target->type != json_object) {
998                return FALSE;
999        }
1000
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);
1004                if (g_strcasecmp(us->screen_name, td->user) == 0) {
1005                        twitter_add_buddy(ic, ut->screen_name, ut->name);
1006                }
1007                txu_free(us);
1008                txu_free(ut);
1009        }
1010
1011        return TRUE;
1012}
1013
1014gboolean twitter_open_stream(struct im_connection *ic)
1015{
1016        struct twitter_data *td = ic->proto_data;
1017        char *args[2] = { "with", "followings" };
1018
1019        if ((td->stream = twitter_http(ic, TWITTER_USER_STREAM_URL,
1020                                       twitter_http_stream, ic, 0, args, 2))) {
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        }
1026
1027        return FALSE;
1028}
1029
1030static gboolean twitter_filter_stream(struct im_connection *ic)
1031{
1032        struct twitter_data *td = ic->proto_data;
1033        char *args[4] = { "follow", NULL, "track", NULL };
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:
1045                        if (followstr->len > 0) {
1046                                g_string_append_c(followstr, ',');
1047                        }
1048
1049                        g_string_append_printf(followstr, "%" G_GUINT64_FORMAT,
1050                                               tf->uid);
1051                        break;
1052
1053                case TWITTER_FILTER_TYPE_TRACK:
1054                        if (trackstr->len > 0) {
1055                                g_string_append_c(trackstr, ',');
1056                        }
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
1069        if (td->filter_stream) {
1070                http_close(td->filter_stream);
1071        }
1072
1073        if ((td->filter_stream = twitter_http(ic, TWITTER_FILTER_STREAM_URL,
1074                                              twitter_http_stream, ic, 0,
1075                                              args, 4))) {
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.
1103        if (!g_slist_find(twitter_connections, ic)) {
1104                return;
1105        }
1106
1107        td = ic->proto_data;
1108
1109        if (!(parsed = twitter_parse_response(ic, req))) {
1110                return;
1111        }
1112
1113        for (l = td->filters; l; l = g_slist_next(l)) {
1114                tf = l->data;
1115
1116                if (tf->type == TWITTER_FILTER_TYPE_FOLLOW) {
1117                        users = g_list_prepend(users, tf);
1118                }
1119        }
1120
1121        if (parsed->type != json_array) {
1122                goto finish;
1123        }
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
1129                if (!name || !id || id->type != json_integer) {
1130                        continue;
1131                }
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
1148        if (!users) {
1149                return;
1150        }
1151
1152        fstr = g_string_new("");
1153
1154        for (u = users; u; u = g_list_next(u)) {
1155                if (fstr->len > 0) {
1156                        g_string_append(fstr, ", ");
1157                }
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;
1171        char *args[2] = { "screen_name", NULL };
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
1180                if (tf->type != TWITTER_FILTER_TYPE_FOLLOW || tf->uid != 0) {
1181                        continue;
1182                }
1183
1184                if (ustr->len > 0) {
1185                        g_string_append_c(ustr, ',');
1186                }
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,
1198                           twitter_filter_users_post,
1199                           ic, 0, args, 2);
1200
1201        g_string_free(ustr, TRUE);
1202        return req != NULL;
1203}
1204
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
1208/**
1209 * Get the timeline with optionally mentions
1210 */
1211gboolean twitter_get_timeline(struct im_connection *ic, gint64 next_cursor)
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) {
1217                if (++td->http_fails >= 5) {
1218                        imcb_error(ic, "Fetch timeout (%d)", td->flags);
1219                        imc_logout(ic, TRUE);
1220                        return FALSE;
1221                }
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        }
1231
1232        return TRUE;
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");
1243        int show_old_mentions = set_getint(&ic->acc->set, "show_old_mentions");
1244        struct twitter_xml_list *home_timeline = td->home_timeline_obj;
1245        struct twitter_xml_list *mentions = td->mentions_obj;
1246        guint64 last_id = 0;
1247        GSList *output = NULL;
1248        GSList *l;
1249
1250        imcb_connected(ic);
1251
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)) {
1268                        if (show_old_mentions < 1 && output && twitter_compare_elements(l->data, output->data) < 0) {
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.
1277        while (output) {
1278                struct twitter_xml_status *txs = output->data;
1279                if (txs->id != last_id) {
1280                        twitter_status_show(ic, txs);
1281                }
1282                last_id = txs->id;
1283                output = g_slist_remove(output, txs);
1284        }
1285
1286        txl_free(home_timeline);
1287        txl_free(mentions);
1288
1289        td->flags &= ~(TWITTER_DOING_TIMELINE | TWITTER_GOT_TIMELINE | TWITTER_GOT_MENTIONS);
1290        td->home_timeline_obj = td->mentions_obj = NULL;
1291}
1292
1293static void twitter_http_get_home_timeline(struct http_request *req);
1294static void twitter_http_get_mentions(struct http_request *req);
1295
1296/**
1297 * Get the timeline.
1298 */
1299static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
1300{
1301        struct twitter_data *td = ic->proto_data;
1302
1303        txl_free(td->home_timeline_obj);
1304        td->home_timeline_obj = NULL;
1305        td->flags &= ~TWITTER_GOT_TIMELINE;
1306
1307        char *args[6];
1308        args[0] = "cursor";
1309        args[1] = g_strdup_printf("%" G_GINT64_FORMAT, next_cursor);
1310        args[2] = "include_entities";
1311        args[3] = "true";
1312        if (td->timeline_id) {
1313                args[4] = "since_id";
1314                args[5] = g_strdup_printf("%" G_GUINT64_FORMAT, td->timeline_id);
1315        }
1316
1317        if (twitter_http(ic, TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, args,
1318                         td->timeline_id ? 6 : 4) == NULL) {
1319                if (++td->http_fails >= 5) {
1320                        imcb_error(ic, "Could not retrieve %s: %s",
1321                                   TWITTER_HOME_TIMELINE_URL, "connection failed");
1322                }
1323                td->flags |= TWITTER_GOT_TIMELINE;
1324                twitter_flush_timeline(ic);
1325        }
1326
1327        g_free(args[1]);
1328        if (td->timeline_id) {
1329                g_free(args[5]);
1330        }
1331}
1332
1333/**
1334 * Get mentions.
1335 */
1336static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)
1337{
1338        struct twitter_data *td = ic->proto_data;
1339
1340        txl_free(td->mentions_obj);
1341        td->mentions_obj = NULL;
1342        td->flags &= ~TWITTER_GOT_MENTIONS;
1343
1344        char *args[6];
1345        args[0] = "cursor";
1346        args[1] = g_strdup_printf("%" G_GINT64_FORMAT, next_cursor);
1347        args[2] = "include_entities";
1348        args[3] = "true";
1349        if (td->timeline_id) {
1350                args[4] = "since_id";
1351                args[5] = g_strdup_printf("%" G_GUINT64_FORMAT, td->timeline_id);
1352        } else {
1353                args[4] = "count";
1354                args[5] = g_strdup_printf("%d", set_getint(&ic->acc->set, "show_old_mentions"));
1355        }
1356
1357        if (twitter_http(ic, TWITTER_MENTIONS_URL, twitter_http_get_mentions,
1358                         ic, 0, args, 6) == NULL) {
1359                if (++td->http_fails >= 5) {
1360                        imcb_error(ic, "Could not retrieve %s: %s",
1361                                   TWITTER_MENTIONS_URL, "connection failed");
1362                }
1363                td->flags |= TWITTER_GOT_MENTIONS;
1364                twitter_flush_timeline(ic);
1365        }
1366
1367        g_free(args[1]);
1368        g_free(args[5]);
1369}
1370
1371/**
1372 * Callback for getting the home timeline.
1373 */
1374static void twitter_http_get_home_timeline(struct http_request *req)
1375{
1376        struct im_connection *ic = req->data;
1377        struct twitter_data *td;
1378        json_value *parsed;
1379        struct twitter_xml_list *txl;
1380
1381        // Check if the connection is still active.
1382        if (!g_slist_find(twitter_connections, ic)) {
1383                return;
1384        }
1385
1386        td = ic->proto_data;
1387
1388        // The root <statuses> node should hold the list of statuses <status>
1389        if (!(parsed = twitter_parse_response(ic, req))) {
1390                goto end;
1391        }
1392
1393        txl = g_new0(struct twitter_xml_list, 1);
1394        txl->list = NULL;
1395
1396        twitter_xt_get_status_list(ic, parsed, txl);
1397        json_value_free(parsed);
1398
1399        td->home_timeline_obj = txl;
1400
1401end:
1402        if (!g_slist_find(twitter_connections, ic)) {
1403                return;
1404        }
1405
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;
1418        json_value *parsed;
1419        struct twitter_xml_list *txl;
1420
1421        // Check if the connection is still active.
1422        if (!g_slist_find(twitter_connections, ic)) {
1423                return;
1424        }
1425
1426        td = ic->proto_data;
1427
1428        // The root <statuses> node should hold the list of statuses <status>
1429        if (!(parsed = twitter_parse_response(ic, req))) {
1430                goto end;
1431        }
1432
1433        txl = g_new0(struct twitter_xml_list, 1);
1434        txl->list = NULL;
1435
1436        twitter_xt_get_status_list(ic, parsed, txl);
1437        json_value_free(parsed);
1438
1439        td->mentions_obj = txl;
1440
1441end:
1442        if (!g_slist_find(twitter_connections, ic)) {
1443                return;
1444        }
1445
1446        td->flags |= TWITTER_GOT_MENTIONS;
1447
1448        twitter_flush_timeline(ic);
1449}
1450
1451/**
1452 * Callback to use after sending a POST request to twitter.
1453 * (Generic, used for a few kinds of queries.)
1454 */
1455static void twitter_http_post(struct http_request *req)
1456{
1457        struct im_connection *ic = req->data;
1458        struct twitter_data *td;
1459        json_value *parsed, *id;
1460
1461        // Check if the connection is still active.
1462        if (!g_slist_find(twitter_connections, ic)) {
1463                return;
1464        }
1465
1466        td = ic->proto_data;
1467        td->last_status_id = 0;
1468
1469        if (!(parsed = twitter_parse_response(ic, req))) {
1470                return;
1471        }
1472
1473        if ((id = json_o_get(parsed, "id")) && id->type == json_integer) {
1474                td->last_status_id = id->u.integer;
1475        }
1476
1477        json_value_free(parsed);
1478
1479        if (req->flags & TWITTER_HTTP_USER_ACK) {
1480                twitter_log(ic, "Command processed successfully");
1481        }
1482}
1483
1484/**
1485 * Function to POST a new status to twitter.
1486 */
1487void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to)
1488{
1489        char *args[4] = {
1490                "status", msg,
1491                "in_reply_to_status_id",
1492                g_strdup_printf("%" G_GUINT64_FORMAT, in_reply_to)
1493        };
1494
1495        twitter_http(ic, TWITTER_STATUS_UPDATE_URL, twitter_http_post, ic, 1,
1496                     args, in_reply_to ? 4 : 2);
1497        g_free(args[3]);
1498}
1499
1500
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{
1506        char *args[4];
1507
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.
1513        twitter_http(ic, TWITTER_DIRECT_MESSAGES_NEW_URL, twitter_http_post, ic, 1, args, 4);
1514}
1515
1516void twitter_friendships_create_destroy(struct im_connection *ic, char *who, int create)
1517{
1518        char *args[2];
1519
1520        args[0] = "screen_name";
1521        args[1] = who;
1522        twitter_http(ic, create ? TWITTER_FRIENDSHIPS_CREATE_URL : TWITTER_FRIENDSHIPS_DESTROY_URL,
1523                     twitter_http_post, ic, 1, args, 2);
1524}
1525
1526void twitter_status_destroy(struct im_connection *ic, guint64 id)
1527{
1528        char *url;
1529
1530        url = g_strdup_printf("%s%" G_GUINT64_FORMAT "%s",
1531                              TWITTER_STATUS_DESTROY_URL, id, ".json");
1532        twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0,
1533                       TWITTER_HTTP_USER_ACK);
1534        g_free(url);
1535}
1536
1537void twitter_status_retweet(struct im_connection *ic, guint64 id)
1538{
1539        char *url;
1540
1541        url = g_strdup_printf("%s%" G_GUINT64_FORMAT "%s",
1542                              TWITTER_STATUS_RETWEET_URL, id, ".json");
1543        twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0,
1544                       TWITTER_HTTP_USER_ACK);
1545        g_free(url);
1546}
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        };
1557
1558        args[1] = screen_name;
1559        twitter_http_f(ic, TWITTER_REPORT_SPAM_URL, twitter_http_post,
1560                       ic, 1, args, 2, TWITTER_HTTP_USER_ACK);
1561}
1562
1563/**
1564 * Favourite a tweet.
1565 */
1566void twitter_favourite_tweet(struct im_connection *ic, guint64 id)
1567{
1568        char *args[2] = {
1569                "id",
1570                NULL,
1571        };
1572
1573        args[1] = g_strdup_printf("%" G_GUINT64_FORMAT, id);
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]);
1577}
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.