source: protocols/twitter/twitter_lib.c @ f8c9347

Last change on this file since f8c9347 was f8c9347, checked in by Wilmer van der Gaast <wilmer@…>, at 2016-11-23T23:41:22Z

Heh, oops, some error in the Twitter module still mentioned XML. Fixed.

Some structs still have xml in their name but meh, at least that's not
visible to the user.

  • Property mode set to 100644
File size: 46.8 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, "JSON parse error");
237        }
238        return ret;
239}
240
241static void twitter_http_get_friends_ids(struct http_request *req);
242static void twitter_http_get_mutes_ids(struct http_request *req);
243static void twitter_http_get_noretweets_ids(struct http_request *req);
244
245/**
246 * Get the friends ids.
247 */
248void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor)
249{
250        // Primitive, but hey! It works...
251        char *args[2];
252
253        args[0] = "cursor";
254        args[1] = g_strdup_printf("%" G_GINT64_FORMAT, next_cursor);
255        twitter_http(ic, TWITTER_FRIENDS_IDS_URL, twitter_http_get_friends_ids, ic, 0, args, 2);
256
257        g_free(args[1]);
258}
259
260/**
261 * Get the muted users ids.
262 */
263void twitter_get_mutes_ids(struct im_connection *ic, gint64 next_cursor)
264{
265        char *args[2];
266
267        args[0] = "cursor";
268        args[1] = g_strdup_printf("%" G_GINT64_FORMAT, next_cursor);
269        twitter_http(ic, TWITTER_MUTES_IDS_URL, twitter_http_get_mutes_ids, ic, 0, args, 2);
270
271        g_free(args[1]);
272}
273
274/**
275 * Get the ids for users from whom we should ignore retweets.
276 */
277void twitter_get_noretweets_ids(struct im_connection *ic, gint64 next_cursor)
278{
279        char *args[2];
280
281        args[0] = "cursor";
282        args[1] = g_strdup_printf("%" G_GINT64_FORMAT, next_cursor);
283        twitter_http(ic, TWITTER_NORETWEETS_IDS_URL, twitter_http_get_noretweets_ids, ic, 0, args, 2);
284
285        g_free(args[1]);
286}
287
288/**
289 * Fill a list of ids.
290 */
291static gboolean twitter_xt_get_friends_id_list(json_value *node, struct twitter_xml_list *txl)
292{
293        json_value *c;
294        int i;
295
296        // Set the list type.
297        txl->type = TXL_ID;
298
299        c = json_o_get(node, "ids");
300        if (!c || c->type != json_array) {
301                return FALSE;
302        }
303
304        for (i = 0; i < c->u.array.length; i++) {
305                if (c->u.array.values[i]->type != json_integer) {
306                        continue;
307                }
308
309                txl->list = g_slist_prepend(txl->list,
310                                            g_strdup_printf("%" PRIu64, c->u.array.values[i]->u.integer));
311        }
312
313        c = json_o_get(node, "next_cursor");
314        if (c && c->type == json_integer) {
315                txl->next_cursor = c->u.integer;
316        } else {
317                txl->next_cursor = -1;
318        }
319
320        return TRUE;
321}
322
323static void twitter_get_users_lookup(struct im_connection *ic);
324
325/**
326 * Callback for getting the friends ids.
327 */
328static void twitter_http_get_friends_ids(struct http_request *req)
329{
330        struct im_connection *ic;
331        json_value *parsed;
332        struct twitter_xml_list *txl;
333        struct twitter_data *td;
334
335        ic = req->data;
336
337        // Check if the connection is still active.
338        if (!g_slist_find(twitter_connections, ic)) {
339                return;
340        }
341
342        td = ic->proto_data;
343
344        // Parse the data.
345        if (!(parsed = twitter_parse_response(ic, req))) {
346                return;
347        }
348
349        txl = g_new0(struct twitter_xml_list, 1);
350        txl->list = td->follow_ids;
351
352        twitter_xt_get_friends_id_list(parsed, txl);
353        json_value_free(parsed);
354
355        td->follow_ids = txl->list;
356        if (txl->next_cursor) {
357                /* These were just numbers. Up to 4000 in a response AFAIK so if we get here
358                   we may be using a spammer account. \o/ */
359                twitter_get_friends_ids(ic, txl->next_cursor);
360        } else {
361                /* Now to convert all those numbers into names.. */
362                twitter_get_users_lookup(ic);
363        }
364
365        txl->list = NULL;
366        txl_free(txl);
367}
368
369/**
370 * Callback for getting the mutes ids.
371 */
372static void twitter_http_get_mutes_ids(struct http_request *req)
373{
374        struct im_connection *ic = req->data;
375        json_value *parsed;
376        struct twitter_xml_list *txl;
377        struct twitter_data *td;
378
379        // Check if the connection is stil active
380        if (!g_slist_find(twitter_connections, ic)) {
381                return;
382        }
383
384        td = ic->proto_data;
385
386        if (req->status_code != 200) {
387                /* Fail silently */
388                return;
389        }
390
391        // Parse the data.
392        if (!(parsed = twitter_parse_response(ic, req))) {
393                return;
394        }
395
396        txl = g_new0(struct twitter_xml_list, 1);
397        txl->list = td->mutes_ids;
398
399        /* mute ids API response is similar enough to friends response
400           to reuse this method */
401        twitter_xt_get_friends_id_list(parsed, txl);
402        json_value_free(parsed);
403
404        td->mutes_ids = txl->list;
405        if (txl->next_cursor) {
406                /* Recurse while there are still more pages */
407                twitter_get_mutes_ids(ic, txl->next_cursor);
408        }
409
410        txl->list = NULL;
411        txl_free(txl);
412}
413
414/**
415 * Callback for getting the no-retweets ids.
416 */
417static void twitter_http_get_noretweets_ids(struct http_request *req)
418{
419        struct im_connection *ic = req->data;
420        json_value *parsed;
421        struct twitter_xml_list *txl;
422        struct twitter_data *td;
423
424        // Check if the connection is stil active
425        if (!g_slist_find(twitter_connections, ic)) {
426                return;
427        }
428
429        if (req->status_code != 200) {
430                /* Fail silently */
431                return;
432        }
433
434        td = ic->proto_data;
435
436        // Parse the data.
437        if (!(parsed = twitter_parse_response(ic, req))) {
438                return;
439        }
440
441        txl = g_new0(struct twitter_xml_list, 1);
442        txl->list = td->noretweets_ids;
443       
444        // Process the retweet ids
445        txl->type = TXL_ID;
446        if (parsed->type == json_array) {
447                unsigned int i;
448                for (i = 0; i < parsed->u.array.length; i++) {
449                        json_value *c = parsed->u.array.values[i];
450                        if (c->type != json_integer) {
451                                continue;
452                        }
453                        txl->list = g_slist_prepend(txl->list,
454                                                    g_strdup_printf("%"PRIu64, c->u.integer));
455                }
456        }
457
458        json_value_free(parsed);
459        td->noretweets_ids = txl->list;
460
461        txl->list = NULL;
462        txl_free(txl);
463}
464
465static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl);
466static void twitter_http_get_users_lookup(struct http_request *req);
467
468static void twitter_get_users_lookup(struct im_connection *ic)
469{
470        struct twitter_data *td = ic->proto_data;
471        char *args[2] = {
472                "user_id",
473                NULL,
474        };
475        GString *ids = g_string_new("");
476        int i;
477
478        /* We can request up to 100 users at a time. */
479        for (i = 0; i < 100 && td->follow_ids; i++) {
480                g_string_append_printf(ids, ",%s", (char *) td->follow_ids->data);
481                g_free(td->follow_ids->data);
482                td->follow_ids = g_slist_remove(td->follow_ids, td->follow_ids->data);
483        }
484        if (ids->len > 0) {
485                args[1] = ids->str + 1;
486                /* POST, because I think ids can be up to 1KB long. */
487                twitter_http(ic, TWITTER_USERS_LOOKUP_URL, twitter_http_get_users_lookup, ic, 1, args, 2);
488        } else {
489                /* We have all users. Continue with login. (Get statuses.) */
490                td->flags |= TWITTER_HAVE_FRIENDS;
491                twitter_login_finish(ic);
492        }
493        g_string_free(ids, TRUE);
494}
495
496/**
497 * Callback for getting (twitter)friends...
498 *
499 * Be afraid, be very afraid! This function will potentially add hundreds of "friends". "Who has
500 * hundreds of friends?" you wonder? You probably not, since you are reading the source of
501 * BitlBee... Get a life and meet new people!
502 */
503static void twitter_http_get_users_lookup(struct http_request *req)
504{
505        struct im_connection *ic = req->data;
506        json_value *parsed;
507        struct twitter_xml_list *txl;
508        GSList *l = NULL;
509        struct twitter_xml_user *user;
510
511        // Check if the connection is still active.
512        if (!g_slist_find(twitter_connections, ic)) {
513                return;
514        }
515
516        // Get the user list from the parsed xml feed.
517        if (!(parsed = twitter_parse_response(ic, req))) {
518                return;
519        }
520
521        txl = g_new0(struct twitter_xml_list, 1);
522        txl->list = NULL;
523
524        twitter_xt_get_users(parsed, txl);
525        json_value_free(parsed);
526
527        // Add the users as buddies.
528        for (l = txl->list; l; l = g_slist_next(l)) {
529                user = l->data;
530                twitter_add_buddy(ic, user->screen_name, user->name);
531        }
532
533        // Free the structure.
534        txl_free(txl);
535
536        twitter_get_users_lookup(ic);
537}
538
539struct twitter_xml_user *twitter_xt_get_user(const json_value *node)
540{
541        struct twitter_xml_user *txu;
542        json_value *jv;
543
544        txu = g_new0(struct twitter_xml_user, 1);
545        txu->name = g_strdup(json_o_str(node, "name"));
546        txu->screen_name = g_strdup(json_o_str(node, "screen_name"));
547
548        jv = json_o_get(node, "id");
549        txu->uid = jv->u.integer;
550
551        return txu;
552}
553
554/**
555 * Function to fill a twitter_xml_list struct.
556 * It sets:
557 *  - all <user>s from the <users> element.
558 */
559static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl)
560{
561        struct twitter_xml_user *txu;
562        int i;
563
564        // Set the type of the list.
565        txl->type = TXL_USER;
566
567        if (!node || node->type != json_array) {
568                return FALSE;
569        }
570
571        // The root <users> node should hold the list of users <user>
572        // Walk over the nodes children.
573        for (i = 0; i < node->u.array.length; i++) {
574                txu = twitter_xt_get_user(node->u.array.values[i]);
575                if (txu) {
576                        txl->list = g_slist_prepend(txl->list, txu);
577                }
578        }
579
580        return TRUE;
581}
582
583#ifdef __GLIBC__
584#define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S %z %Y"
585#else
586#define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S +0000 %Y"
587#endif
588
589static void expand_entities(char **text, const json_value *node, const json_value *extended_node);
590
591/**
592 * Function to fill a twitter_xml_status struct.
593 * It sets:
594 *  - the status text and
595 *  - the created_at timestamp and
596 *  - the status id and
597 *  - the user in a twitter_xml_user struct.
598 */
599static struct twitter_xml_status *twitter_xt_get_status(const json_value *node)
600{
601        struct twitter_xml_status *txs = {0};
602        const json_value *rt = NULL;
603        const json_value *text_value = NULL;
604        const json_value *extended_node = NULL;
605
606        if (node->type != json_object) {
607                return FALSE;
608        }
609        txs = g_new0(struct twitter_xml_status, 1);
610
611        JSON_O_FOREACH(node, k, v) {
612                if (strcmp("text", k) == 0 && v->type == json_string && text_value == NULL) {
613                        text_value = v;
614                } else if (strcmp("full_text", k) == 0 && v->type == json_string) {
615                        text_value = v;
616                } else if (strcmp("extended_tweet", k) == 0 && v->type == json_object) {
617                        text_value = json_o_get(v, "full_text");
618                        extended_node = v;
619                } else if (strcmp("retweeted_status", k) == 0 && v->type == json_object) {
620                        rt = v;
621                } else if (strcmp("created_at", k) == 0 && v->type == json_string) {
622                        struct tm parsed;
623
624                        /* Very sensitive to changes to the formatting of
625                           this field. :-( Also assumes the timezone used
626                           is UTC since C time handling functions suck. */
627                        if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL) {
628                                txs->created_at = mktime_utc(&parsed);
629                        }
630                } else if (strcmp("user", k) == 0 && v->type == json_object) {
631                        txs->user = twitter_xt_get_user(v);
632                } else if (strcmp("id", k) == 0 && v->type == json_integer) {
633                        txs->rt_id = txs->id = v->u.integer;
634                } else if (strcmp("in_reply_to_status_id", k) == 0 && v->type == json_integer) {
635                        txs->reply_to = v->u.integer;
636                }
637        }
638
639        /* If it's a (truncated) retweet, get the original. Even if the API claims it
640           wasn't truncated because it may be lying. */
641        if (rt) {
642                struct twitter_xml_status *rtxs = twitter_xt_get_status(rt);
643                if (rtxs) {
644                        txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text);
645                        txs->id = rtxs->id;
646                        txs_free(rtxs);
647                }
648        } else if (text_value && text_value->type == json_string) {
649                txs->text = g_memdup(text_value->u.string.ptr, text_value->u.string.length + 1);
650                strip_html(txs->text);
651                expand_entities(&txs->text, node, extended_node);
652        }
653
654        if (txs->text && txs->user && txs->id) {
655                return txs;
656        }
657
658        txs_free(txs);
659        return NULL;
660}
661
662/**
663 * Function to fill a twitter_xml_status struct (DM variant).
664 */
665static struct twitter_xml_status *twitter_xt_get_dm(const json_value *node)
666{
667        struct twitter_xml_status *txs;
668
669        if (node->type != json_object) {
670                return FALSE;
671        }
672        txs = g_new0(struct twitter_xml_status, 1);
673
674        JSON_O_FOREACH(node, k, v) {
675                if (strcmp("text", k) == 0 && v->type == json_string) {
676                        txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1);
677                        strip_html(txs->text);
678                } else if (strcmp("created_at", k) == 0 && v->type == json_string) {
679                        struct tm parsed;
680
681                        /* Very sensitive to changes to the formatting of
682                           this field. :-( Also assumes the timezone used
683                           is UTC since C time handling functions suck. */
684                        if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL) {
685                                txs->created_at = mktime_utc(&parsed);
686                        }
687                } else if (strcmp("sender", k) == 0 && v->type == json_object) {
688                        txs->user = twitter_xt_get_user(v);
689                } else if (strcmp("id", k) == 0 && v->type == json_integer) {
690                        txs->id = v->u.integer;
691                }
692        }
693
694        expand_entities(&txs->text, node, NULL);
695
696        if (txs->text && txs->user && txs->id) {
697                return txs;
698        }
699
700        txs_free(txs);
701        return NULL;
702}
703
704static void expand_entities(char **text, const json_value *node, const json_value *extended_node)
705{
706        json_value *entities, *extended_entities, *quoted;
707        char *quote_url = NULL, *quote_text = NULL;
708
709        if (!((entities = json_o_get(node, "entities")) && entities->type == json_object))
710                return;
711        if ((quoted = json_o_get(node, "quoted_status")) && quoted->type == json_object) {
712                /* New "retweets with comments" feature. Note that this info
713                 * seems to be included in the streaming API only! Grab the
714                 * full message and try to insert it when we run into the
715                 * Tweet entity. */
716                struct twitter_xml_status *txs = twitter_xt_get_status(quoted);
717                quote_text = g_strdup_printf("@%s: %s", txs->user->screen_name, txs->text);
718                quote_url = g_strdup_printf("%s/status/%" G_GUINT64_FORMAT, txs->user->screen_name, txs->id);
719                txs_free(txs);
720        } else {
721                quoted = NULL;
722        }
723
724        if (extended_node) {
725                extended_entities = json_o_get(extended_node, "entities");
726                if (extended_entities && extended_entities->type == json_object) {
727                        entities = extended_entities;
728                }
729        }
730
731        JSON_O_FOREACH(entities, k, v) {
732                int i;
733
734                if (v->type != json_array) {
735                        continue;
736                }
737                if (strcmp(k, "urls") != 0 && strcmp(k, "media") != 0) {
738                        continue;
739                }
740
741                for (i = 0; i < v->u.array.length; i++) {
742                        const char *format = "%s%s <%s>%s";
743
744                        if (v->u.array.values[i]->type != json_object) {
745                                continue;
746                        }
747
748                        const char *kort = json_o_str(v->u.array.values[i], "url");
749                        const char *disp = json_o_str(v->u.array.values[i], "display_url");
750                        const char *full = json_o_str(v->u.array.values[i], "expanded_url");
751                        char *pos, *new;
752
753                        if (!kort || !disp || !(pos = strstr(*text, kort))) {
754                                continue;
755                        }
756                        if (quote_url && strstr(full, quote_url)) {
757                                format = "%s<%s> [%s]%s";
758                                disp = quote_text;
759                        }
760
761                        *pos = '\0';
762                        new = g_strdup_printf(format, *text, kort,
763                                              disp, pos + strlen(kort));
764
765                        g_free(*text);
766                        *text = new;
767                }
768        }
769        g_free(quote_text);
770        g_free(quote_url);
771}
772
773/**
774 * Function to fill a twitter_xml_list struct.
775 * It sets:
776 *  - all <status>es within the <status> element and
777 *  - the next_cursor.
778 */
779static gboolean twitter_xt_get_status_list(struct im_connection *ic, const json_value *node,
780                                           struct twitter_xml_list *txl)
781{
782        struct twitter_xml_status *txs;
783        int i;
784
785        // Set the type of the list.
786        txl->type = TXL_STATUS;
787
788        if (node->type != json_array) {
789                return FALSE;
790        }
791
792        // The root <statuses> node should hold the list of statuses <status>
793        // Walk over the nodes children.
794        for (i = 0; i < node->u.array.length; i++) {
795                txs = twitter_xt_get_status(node->u.array.values[i]);
796                if (!txs) {
797                        continue;
798                }
799
800                txl->list = g_slist_prepend(txl->list, txs);
801        }
802
803        return TRUE;
804}
805
806/* Will log messages either way. Need to keep track of IDs for stream deduping.
807   Plus, show_ids is on by default and I don't see why anyone would disable it. */
808static char *twitter_msg_add_id(struct im_connection *ic,
809                                struct twitter_xml_status *txs, const char *prefix)
810{
811        struct twitter_data *td = ic->proto_data;
812        int reply_to = -1;
813        bee_user_t *bu;
814
815        if (txs->reply_to) {
816                int i;
817                for (i = 0; i < TWITTER_LOG_LENGTH; i++) {
818                        if (td->log[i].id == txs->reply_to) {
819                                reply_to = i;
820                                break;
821                        }
822                }
823        }
824
825        if (txs->user && txs->user->screen_name &&
826            (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) {
827                struct twitter_user_data *tud = bu->data;
828
829                if (txs->id > tud->last_id) {
830                        tud->last_id = txs->id;
831                        tud->last_time = txs->created_at;
832                }
833        }
834
835        td->log_id = (td->log_id + 1) % TWITTER_LOG_LENGTH;
836        td->log[td->log_id].id = txs->id;
837        td->log[td->log_id].bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name);
838
839        /* This is all getting hairy. :-( If we RT'ed something ourselves,
840           remember OUR id instead so undo will work. In other cases, the
841           original tweet's id should be remembered for deduplicating. */
842        if (g_strcasecmp(txs->user->screen_name, td->user) == 0) {
843                td->log[td->log_id].id = txs->rt_id;
844                /* More useful than NULL. */
845                td->log[td->log_id].bu = &twitter_log_local_user;
846        }
847
848        if (set_getbool(&ic->acc->set, "show_ids")) {
849                if (reply_to != -1) {
850                        return g_strdup_printf("\002[\002%02x->%02x\002]\002 %s%s",
851                                               td->log_id, reply_to, prefix, txs->text);
852                } else {
853                        return g_strdup_printf("\002[\002%02x\002]\002 %s%s",
854                                               td->log_id, prefix, txs->text);
855                }
856        } else {
857                if (*prefix) {
858                        return g_strconcat(prefix, txs->text, NULL);
859                } else {
860                        return NULL;
861                }
862        }
863}
864
865/**
866 * Function that is called to see the filter statuses in groupchat windows.
867 */
868static void twitter_status_show_filter(struct im_connection *ic, struct twitter_xml_status *status)
869{
870        struct twitter_data *td = ic->proto_data;
871        char *msg = twitter_msg_add_id(ic, status, "");
872        struct twitter_filter *tf;
873        GSList *f;
874        GSList *l;
875
876        for (f = td->filters; f; f = g_slist_next(f)) {
877                tf = f->data;
878
879                switch (tf->type) {
880                case TWITTER_FILTER_TYPE_FOLLOW:
881                        if (status->user->uid != tf->uid) {
882                                continue;
883                        }
884                        break;
885
886                case TWITTER_FILTER_TYPE_TRACK:
887                        if (strcasestr(status->text, tf->text) == NULL) {
888                                continue;
889                        }
890                        break;
891
892                default:
893                        continue;
894                }
895
896                for (l = tf->groupchats; l; l = g_slist_next(l)) {
897                        imcb_chat_msg(l->data, status->user->screen_name,
898                                      msg ? msg : status->text, 0, 0);
899                }
900        }
901
902        g_free(msg);
903}
904
905/**
906 * Function that is called to see the statuses in a groupchat window.
907 */
908static void twitter_status_show_chat(struct im_connection *ic, struct twitter_xml_status *status)
909{
910        struct twitter_data *td = ic->proto_data;
911        struct groupchat *gc;
912        gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0;
913        char *msg;
914
915        // Create a new groupchat if it does not exsist.
916        gc = twitter_groupchat_init(ic);
917
918        if (!me) {
919                /* MUST be done before twitter_msg_add_id() to avoid #872. */
920                twitter_add_buddy(ic, status->user->screen_name, status->user->name);
921        }
922        msg = twitter_msg_add_id(ic, status, "");
923
924        // Say it!
925        if (me) {
926                imcb_chat_log(gc, "You: %s", msg ? msg : status->text);
927        } else {
928                imcb_chat_msg(gc, status->user->screen_name,
929                              msg ? msg : status->text, 0, status->created_at);
930        }
931
932        g_free(msg);
933}
934
935/**
936 * Function that is called to see statuses as private messages.
937 */
938static void twitter_status_show_msg(struct im_connection *ic, struct twitter_xml_status *status)
939{
940        struct twitter_data *td = ic->proto_data;
941        char from[MAX_STRING] = "";
942        char *prefix = NULL, *text = NULL;
943        gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0;
944
945        if (td->flags & TWITTER_MODE_ONE) {
946                g_snprintf(from, sizeof(from) - 1, "%s_%s", td->prefix, ic->acc->user);
947                from[MAX_STRING - 1] = '\0';
948        }
949
950        if (td->flags & TWITTER_MODE_ONE) {
951                prefix = g_strdup_printf("\002<\002%s\002>\002 ",
952                                         status->user->screen_name);
953        } else if (!me) {
954                twitter_add_buddy(ic, status->user->screen_name, status->user->name);
955        } else {
956                prefix = g_strdup("You: ");
957        }
958
959        text = twitter_msg_add_id(ic, status, prefix ? prefix : "");
960
961        imcb_buddy_msg(ic,
962                       *from ? from : status->user->screen_name,
963                       text ? text : status->text, 0, status->created_at);
964
965        g_free(text);
966        g_free(prefix);
967}
968
969static void twitter_status_show(struct im_connection *ic, struct twitter_xml_status *status)
970{
971        struct twitter_data *td = ic->proto_data;
972        char *last_id_str;
973        char *uid_str;
974
975        if (status->user == NULL || status->text == NULL) {
976                return;
977        }
978       
979        /* Check this is not a tweet that should be muted */
980        uid_str = g_strdup_printf("%" PRIu64, status->user->uid);
981
982        if (g_slist_find_custom(td->mutes_ids, uid_str, (GCompareFunc)strcmp)) {
983                g_free(uid_str);
984                return;
985        }
986        if (status->id != status->rt_id && g_slist_find_custom(td->noretweets_ids, uid_str, (GCompareFunc)strcmp)) {
987                g_free(uid_str);
988                return;
989        }
990
991        /* Grrrr. Would like to do this during parsing, but can't access
992           settings from there. */
993        if (set_getbool(&ic->acc->set, "strip_newlines")) {
994                strip_newlines(status->text);
995        }
996
997        if (status->from_filter) {
998                twitter_status_show_filter(ic, status);
999        } else if (td->flags & TWITTER_MODE_CHAT) {
1000                twitter_status_show_chat(ic, status);
1001        } else {
1002                twitter_status_show_msg(ic, status);
1003        }
1004
1005        // Update the timeline_id to hold the highest id, so that by the next request
1006        // we won't pick up the updates already in the list.
1007        td->timeline_id = MAX(td->timeline_id, status->rt_id);
1008
1009        last_id_str = g_strdup_printf("%" G_GUINT64_FORMAT, td->timeline_id);
1010        set_setstr(&ic->acc->set, "_last_tweet", last_id_str);
1011        g_free(last_id_str);
1012        g_free(uid_str);
1013}
1014
1015static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o, gboolean from_filter);
1016
1017static void twitter_http_stream(struct http_request *req)
1018{
1019        struct im_connection *ic = req->data;
1020        struct twitter_data *td;
1021        json_value *parsed;
1022        int len = 0;
1023        char c, *nl;
1024        gboolean from_filter;
1025
1026        if (!g_slist_find(twitter_connections, ic)) {
1027                return;
1028        }
1029
1030        td = ic->proto_data;
1031
1032        if ((req->flags & HTTPC_EOF) || !req->reply_body) {
1033                if (req == td->stream) {
1034                        td->stream = NULL;
1035                } else if (req == td->filter_stream) {
1036                        td->filter_stream = NULL;
1037                }
1038
1039                imcb_error(ic, "Stream closed (%s)", req->status_string);
1040                if (req->status_code == 401) {
1041                        imcb_error(ic, "Check your system clock.");
1042                }
1043                imc_logout(ic, TRUE);
1044                return;
1045        }
1046
1047        if (req == td->stream) {
1048                ic->flags |= OPT_PONGED;
1049        }
1050
1051        /* MUST search for CRLF, not just LF:
1052           https://dev.twitter.com/docs/streaming-apis/processing#Parsing_responses */
1053        if (!(nl = strstr(req->reply_body, "\r\n"))) {
1054                return;
1055        }
1056
1057        len = nl - req->reply_body;
1058        if (len > 0) {
1059                c = req->reply_body[len];
1060                req->reply_body[len] = '\0';
1061
1062                if ((parsed = json_parse(req->reply_body, req->body_size))) {
1063                        from_filter = (req == td->filter_stream);
1064                        twitter_stream_handle_object(ic, parsed, from_filter);
1065                }
1066                json_value_free(parsed);
1067                req->reply_body[len] = c;
1068        }
1069
1070        http_flush_bytes(req, len + 2);
1071
1072        /* One notification might bring multiple events! */
1073        if (req->body_size > 0) {
1074                twitter_http_stream(req);
1075        }
1076}
1077
1078static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o);
1079static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs);
1080
1081static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o, gboolean from_filter)
1082{
1083        struct twitter_data *td = ic->proto_data;
1084        struct twitter_xml_status *txs;
1085        json_value *c;
1086
1087        if ((txs = twitter_xt_get_status(o))) {
1088                txs->from_filter = from_filter;
1089                gboolean ret = twitter_stream_handle_status(ic, txs);
1090                txs_free(txs);
1091                return ret;
1092        } else if ((c = json_o_get(o, "direct_message")) &&
1093                   (txs = twitter_xt_get_dm(c))) {
1094                if (g_strcasecmp(txs->user->screen_name, td->user) != 0) {
1095                        imcb_buddy_msg(ic, txs->user->screen_name,
1096                                       txs->text, 0, txs->created_at);
1097                }
1098                txs_free(txs);
1099                return TRUE;
1100        } else if ((c = json_o_get(o, "event")) && c->type == json_string) {
1101                twitter_stream_handle_event(ic, o);
1102                return TRUE;
1103        } else if ((c = json_o_get(o, "disconnect")) && c->type == json_object) {
1104                /* HACK: Because we're inside an event handler, we can't just
1105                   disconnect here. Instead, just change the HTTP status string
1106                   into a Twitter status string. */
1107                char *reason = json_o_strdup(c, "reason");
1108                if (reason) {
1109                        g_free(td->stream->status_string);
1110                        td->stream->status_string = reason;
1111                }
1112                return TRUE;
1113        }
1114        return FALSE;
1115}
1116
1117static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs)
1118{
1119        struct twitter_data *td = ic->proto_data;
1120        int i;
1121
1122        for (i = 0; i < TWITTER_LOG_LENGTH; i++) {
1123                if (td->log[i].id == txs->id) {
1124                        /* Got a duplicate (RT, probably). Drop it. */
1125                        return TRUE;
1126                }
1127        }
1128
1129        if (!(g_strcasecmp(txs->user->screen_name, td->user) == 0 ||
1130              set_getbool(&ic->acc->set, "fetch_mentions") ||
1131              bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) {
1132                /* Tweet is from an unknown person and the user does not want
1133                   to see @mentions, so drop it. twitter_stream_handle_event()
1134                   picks up new follows so this simple filter should be safe. */
1135                /* TODO: The streaming API seems to do poor @mention matching.
1136                   I.e. I'm getting mentions for @WilmerSomething, not just for
1137                   @Wilmer. But meh. You want spam, you get spam. */
1138                return TRUE;
1139        }
1140
1141        twitter_status_show(ic, txs);
1142
1143        return TRUE;
1144}
1145
1146static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o)
1147{
1148        struct twitter_data *td = ic->proto_data;
1149        json_value *source = json_o_get(o, "source");
1150        json_value *target = json_o_get(o, "target");
1151        const char *type = json_o_str(o, "event");
1152        struct twitter_xml_user *us = NULL;
1153        struct twitter_xml_user *ut = NULL;
1154
1155        if (!type || !source || source->type != json_object
1156            || !target || target->type != json_object) {
1157                return FALSE;
1158        }
1159
1160        if (strcmp(type, "follow") == 0) {
1161                us = twitter_xt_get_user(source);
1162                ut = twitter_xt_get_user(target);
1163                if (g_strcasecmp(us->screen_name, td->user) == 0) {
1164                        twitter_add_buddy(ic, ut->screen_name, ut->name);
1165                }
1166        } else if (strcmp(type, "mute") == 0) {
1167                GSList *found;
1168                char *uid_str;
1169                ut = twitter_xt_get_user(target);
1170                uid_str = g_strdup_printf("%" PRIu64, ut->uid);
1171                if (!(found = g_slist_find_custom(td->mutes_ids, uid_str,
1172                                                  (GCompareFunc)strcmp))) {
1173                        td->mutes_ids = g_slist_prepend(td->mutes_ids, uid_str);
1174                }
1175                twitter_log(ic, "Muted user %s", ut->screen_name);
1176                if (getenv("BITLBEE_DEBUG")) {
1177                        fprintf(stderr, "New mute: %s %"PRIu64"\n",
1178                                ut->screen_name, ut->uid);
1179                }
1180        } else if (strcmp(type, "unmute") == 0) {
1181                GSList *found;
1182                char *uid_str;
1183                ut = twitter_xt_get_user(target);
1184                uid_str = g_strdup_printf("%" PRIu64, ut->uid);
1185                if ((found = g_slist_find_custom(td->mutes_ids, uid_str,
1186                                                (GCompareFunc)strcmp))) {
1187                        char *found_str = found->data;
1188                        td->mutes_ids = g_slist_delete_link(td->mutes_ids, found);
1189                        g_free(found_str);
1190                }
1191                g_free(uid_str);
1192                twitter_log(ic, "Unmuted user %s", ut->screen_name);
1193                if (getenv("BITLBEE_DEBUG")) {
1194                        fprintf(stderr, "New unmute: %s %"PRIu64"\n",
1195                                ut->screen_name, ut->uid);
1196                }
1197        }
1198
1199        txu_free(us);
1200        txu_free(ut);
1201
1202        return TRUE;
1203}
1204
1205gboolean twitter_open_stream(struct im_connection *ic)
1206{
1207        struct twitter_data *td = ic->proto_data;
1208        char *args[2] = { "with", "followings" };
1209
1210        if ((td->stream = twitter_http(ic, TWITTER_USER_STREAM_URL,
1211                                       twitter_http_stream, ic, 0, args, 2))) {
1212                /* This flag must be enabled or we'll get no data until EOF
1213                   (which err, kind of, defeats the purpose of a streaming API). */
1214                td->stream->flags |= HTTPC_STREAMING;
1215                return TRUE;
1216        }
1217
1218        return FALSE;
1219}
1220
1221static gboolean twitter_filter_stream(struct im_connection *ic)
1222{
1223        struct twitter_data *td = ic->proto_data;
1224        char *args[4] = { "follow", NULL, "track", NULL };
1225        GString *followstr = g_string_new("");
1226        GString *trackstr = g_string_new("");
1227        gboolean ret = FALSE;
1228        struct twitter_filter *tf;
1229        GSList *l;
1230
1231        for (l = td->filters; l; l = g_slist_next(l)) {
1232                tf = l->data;
1233
1234                switch (tf->type) {
1235                case TWITTER_FILTER_TYPE_FOLLOW:
1236                        if (followstr->len > 0) {
1237                                g_string_append_c(followstr, ',');
1238                        }
1239
1240                        g_string_append_printf(followstr, "%" G_GUINT64_FORMAT,
1241                                               tf->uid);
1242                        break;
1243
1244                case TWITTER_FILTER_TYPE_TRACK:
1245                        if (trackstr->len > 0) {
1246                                g_string_append_c(trackstr, ',');
1247                        }
1248
1249                        g_string_append(trackstr, tf->text);
1250                        break;
1251
1252                default:
1253                        continue;
1254                }
1255        }
1256
1257        args[1] = followstr->str;
1258        args[3] = trackstr->str;
1259
1260        if (td->filter_stream) {
1261                http_close(td->filter_stream);
1262        }
1263
1264        if ((td->filter_stream = twitter_http(ic, TWITTER_FILTER_STREAM_URL,
1265                                              twitter_http_stream, ic, 0,
1266                                              args, 4))) {
1267                /* This flag must be enabled or we'll get no data until EOF
1268                   (which err, kind of, defeats the purpose of a streaming API). */
1269                td->filter_stream->flags |= HTTPC_STREAMING;
1270                ret = TRUE;
1271        }
1272
1273        g_string_free(followstr, TRUE);
1274        g_string_free(trackstr, TRUE);
1275
1276        return ret;
1277}
1278
1279static void twitter_filter_users_post(struct http_request *req)
1280{
1281        struct im_connection *ic = req->data;
1282        struct twitter_data *td;
1283        struct twitter_filter *tf;
1284        GList *users = NULL;
1285        json_value *parsed;
1286        json_value *id;
1287        const char *name;
1288        GString *fstr;
1289        GSList *l;
1290        GList *u;
1291        int i;
1292
1293        // Check if the connection is still active.
1294        if (!g_slist_find(twitter_connections, ic)) {
1295                return;
1296        }
1297
1298        td = ic->proto_data;
1299
1300        if (!(parsed = twitter_parse_response(ic, req))) {
1301                return;
1302        }
1303
1304        for (l = td->filters; l; l = g_slist_next(l)) {
1305                tf = l->data;
1306
1307                if (tf->type == TWITTER_FILTER_TYPE_FOLLOW) {
1308                        users = g_list_prepend(users, tf);
1309                }
1310        }
1311
1312        if (parsed->type != json_array) {
1313                goto finish;
1314        }
1315
1316        for (i = 0; i < parsed->u.array.length; i++) {
1317                id = json_o_get(parsed->u.array.values[i], "id");
1318                name = json_o_str(parsed->u.array.values[i], "screen_name");
1319
1320                if (!name || !id || id->type != json_integer) {
1321                        continue;
1322                }
1323
1324                for (u = users; u; u = g_list_next(u)) {
1325                        tf = u->data;
1326
1327                        if (g_strcasecmp(tf->text, name) == 0) {
1328                                tf->uid = id->u.integer;
1329                                users = g_list_delete_link(users, u);
1330                                break;
1331                        }
1332                }
1333        }
1334
1335finish:
1336        json_value_free(parsed);
1337        twitter_filter_stream(ic);
1338
1339        if (!users) {
1340                return;
1341        }
1342
1343        fstr = g_string_new("");
1344
1345        for (u = users; u; u = g_list_next(u)) {
1346                if (fstr->len > 0) {
1347                        g_string_append(fstr, ", ");
1348                }
1349
1350                g_string_append(fstr, tf->text);
1351        }
1352
1353        imcb_error(ic, "Failed UID acquisitions: %s", fstr->str);
1354
1355        g_string_free(fstr, TRUE);
1356        g_list_free(users);
1357}
1358
1359gboolean twitter_open_filter_stream(struct im_connection *ic)
1360{
1361        struct twitter_data *td = ic->proto_data;
1362        char *args[2] = { "screen_name", NULL };
1363        GString *ustr = g_string_new("");
1364        struct twitter_filter *tf;
1365        struct http_request *req;
1366        GSList *l;
1367
1368        for (l = td->filters; l; l = g_slist_next(l)) {
1369                tf = l->data;
1370
1371                if (tf->type != TWITTER_FILTER_TYPE_FOLLOW || tf->uid != 0) {
1372                        continue;
1373                }
1374
1375                if (ustr->len > 0) {
1376                        g_string_append_c(ustr, ',');
1377                }
1378
1379                g_string_append(ustr, tf->text);
1380        }
1381
1382        if (ustr->len == 0) {
1383                g_string_free(ustr, TRUE);
1384                return twitter_filter_stream(ic);
1385        }
1386
1387        args[1] = ustr->str;
1388        req = twitter_http(ic, TWITTER_USERS_LOOKUP_URL,
1389                           twitter_filter_users_post,
1390                           ic, 0, args, 2);
1391
1392        g_string_free(ustr, TRUE);
1393        return req != NULL;
1394}
1395
1396static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor);
1397static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor);
1398
1399/**
1400 * Get the timeline with optionally mentions
1401 */
1402gboolean twitter_get_timeline(struct im_connection *ic, gint64 next_cursor)
1403{
1404        struct twitter_data *td = ic->proto_data;
1405        gboolean include_mentions = set_getbool(&ic->acc->set, "fetch_mentions");
1406
1407        if (td->flags & TWITTER_DOING_TIMELINE) {
1408                if (++td->http_fails >= 5) {
1409                        imcb_error(ic, "Fetch timeout (%d)", td->flags);
1410                        imc_logout(ic, TRUE);
1411                        return FALSE;
1412                }
1413        }
1414
1415        td->flags |= TWITTER_DOING_TIMELINE;
1416
1417        twitter_get_home_timeline(ic, next_cursor);
1418
1419        if (include_mentions) {
1420                twitter_get_mentions(ic, next_cursor);
1421        }
1422
1423        return TRUE;
1424}
1425
1426/**
1427 * Call this one after receiving timeline/mentions. Show to user once we have
1428 * both.
1429 */
1430void twitter_flush_timeline(struct im_connection *ic)
1431{
1432        struct twitter_data *td = ic->proto_data;
1433        gboolean include_mentions = set_getbool(&ic->acc->set, "fetch_mentions");
1434        int show_old_mentions = set_getint(&ic->acc->set, "show_old_mentions");
1435        struct twitter_xml_list *home_timeline = td->home_timeline_obj;
1436        struct twitter_xml_list *mentions = td->mentions_obj;
1437        guint64 last_id = 0;
1438        GSList *output = NULL;
1439        GSList *l;
1440
1441        imcb_connected(ic);
1442
1443        if (!(td->flags & TWITTER_GOT_TIMELINE)) {
1444                return;
1445        }
1446
1447        if (include_mentions && !(td->flags & TWITTER_GOT_MENTIONS)) {
1448                return;
1449        }
1450
1451        if (home_timeline && home_timeline->list) {
1452                for (l = home_timeline->list; l; l = g_slist_next(l)) {
1453                        output = g_slist_insert_sorted(output, l->data, twitter_compare_elements);
1454                }
1455        }
1456
1457        if (include_mentions && mentions && mentions->list) {
1458                for (l = mentions->list; l; l = g_slist_next(l)) {
1459                        if (show_old_mentions < 1 && output && twitter_compare_elements(l->data, output->data) < 0) {
1460                                continue;
1461                        }
1462
1463                        output = g_slist_insert_sorted(output, l->data, twitter_compare_elements);
1464                }
1465        }
1466
1467        // See if the user wants to see the messages in a groupchat window or as private messages.
1468        while (output) {
1469                struct twitter_xml_status *txs = output->data;
1470                if (txs->id != last_id) {
1471                        twitter_status_show(ic, txs);
1472                }
1473                last_id = txs->id;
1474                output = g_slist_remove(output, txs);
1475        }
1476
1477        txl_free(home_timeline);
1478        txl_free(mentions);
1479
1480        td->flags &= ~(TWITTER_DOING_TIMELINE | TWITTER_GOT_TIMELINE | TWITTER_GOT_MENTIONS);
1481        td->home_timeline_obj = td->mentions_obj = NULL;
1482}
1483
1484static void twitter_http_get_home_timeline(struct http_request *req);
1485static void twitter_http_get_mentions(struct http_request *req);
1486
1487/**
1488 * Get the timeline.
1489 */
1490static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
1491{
1492        struct twitter_data *td = ic->proto_data;
1493
1494        txl_free(td->home_timeline_obj);
1495        td->home_timeline_obj = NULL;
1496        td->flags &= ~TWITTER_GOT_TIMELINE;
1497
1498        char *args[8];
1499        args[0] = "cursor";
1500        args[1] = g_strdup_printf("%" G_GINT64_FORMAT, next_cursor);
1501        args[2] = "include_entities";
1502        args[3] = "true";
1503        args[4] = "tweet_mode";
1504        args[5] = "extended";
1505        if (td->timeline_id) {
1506                args[6] = "since_id";
1507                args[7] = g_strdup_printf("%" G_GUINT64_FORMAT, td->timeline_id);
1508        }
1509
1510        if (twitter_http(ic, TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, args,
1511                         td->timeline_id ? 8 : 6) == NULL) {
1512                if (++td->http_fails >= 5) {
1513                        imcb_error(ic, "Could not retrieve %s: %s",
1514                                   TWITTER_HOME_TIMELINE_URL, "connection failed");
1515                }
1516                td->flags |= TWITTER_GOT_TIMELINE;
1517                twitter_flush_timeline(ic);
1518        }
1519
1520        g_free(args[1]);
1521        if (td->timeline_id) {
1522                g_free(args[7]);
1523        }
1524}
1525
1526/**
1527 * Get mentions.
1528 */
1529static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)
1530{
1531        struct twitter_data *td = ic->proto_data;
1532
1533        txl_free(td->mentions_obj);
1534        td->mentions_obj = NULL;
1535        td->flags &= ~TWITTER_GOT_MENTIONS;
1536
1537        char *args[8];
1538        args[0] = "cursor";
1539        args[1] = g_strdup_printf("%" G_GINT64_FORMAT, next_cursor);
1540        args[2] = "include_entities";
1541        args[3] = "true";
1542        if (td->timeline_id) {
1543                args[4] = "since_id";
1544                args[5] = g_strdup_printf("%" G_GUINT64_FORMAT, td->timeline_id);
1545        } else {
1546                args[4] = "count";
1547                args[5] = g_strdup_printf("%d", set_getint(&ic->acc->set, "show_old_mentions"));
1548        }
1549        args[6] = "tweet_mode";
1550        args[7] = "extended";
1551
1552        if (twitter_http(ic, TWITTER_MENTIONS_URL, twitter_http_get_mentions,
1553                         ic, 0, args, 8) == NULL) {
1554                if (++td->http_fails >= 5) {
1555                        imcb_error(ic, "Could not retrieve %s: %s",
1556                                   TWITTER_MENTIONS_URL, "connection failed");
1557                }
1558                td->flags |= TWITTER_GOT_MENTIONS;
1559                twitter_flush_timeline(ic);
1560        }
1561
1562        g_free(args[1]);
1563        g_free(args[5]);
1564}
1565
1566/**
1567 * Callback for getting the home timeline.
1568 */
1569static void twitter_http_get_home_timeline(struct http_request *req)
1570{
1571        struct im_connection *ic = req->data;
1572        struct twitter_data *td;
1573        json_value *parsed;
1574        struct twitter_xml_list *txl;
1575
1576        // Check if the connection is still active.
1577        if (!g_slist_find(twitter_connections, ic)) {
1578                return;
1579        }
1580
1581        td = ic->proto_data;
1582
1583        // The root <statuses> node should hold the list of statuses <status>
1584        if (!(parsed = twitter_parse_response(ic, req))) {
1585                goto end;
1586        }
1587
1588        txl = g_new0(struct twitter_xml_list, 1);
1589        txl->list = NULL;
1590
1591        twitter_xt_get_status_list(ic, parsed, txl);
1592        json_value_free(parsed);
1593
1594        td->home_timeline_obj = txl;
1595
1596end:
1597        if (!g_slist_find(twitter_connections, ic)) {
1598                return;
1599        }
1600
1601        td->flags |= TWITTER_GOT_TIMELINE;
1602
1603        twitter_flush_timeline(ic);
1604}
1605
1606/**
1607 * Callback for getting mentions.
1608 */
1609static void twitter_http_get_mentions(struct http_request *req)
1610{
1611        struct im_connection *ic = req->data;
1612        struct twitter_data *td;
1613        json_value *parsed;
1614        struct twitter_xml_list *txl;
1615
1616        // Check if the connection is still active.
1617        if (!g_slist_find(twitter_connections, ic)) {
1618                return;
1619        }
1620
1621        td = ic->proto_data;
1622
1623        // The root <statuses> node should hold the list of statuses <status>
1624        if (!(parsed = twitter_parse_response(ic, req))) {
1625                goto end;
1626        }
1627
1628        txl = g_new0(struct twitter_xml_list, 1);
1629        txl->list = NULL;
1630
1631        twitter_xt_get_status_list(ic, parsed, txl);
1632        json_value_free(parsed);
1633
1634        td->mentions_obj = txl;
1635
1636end:
1637        if (!g_slist_find(twitter_connections, ic)) {
1638                return;
1639        }
1640
1641        td->flags |= TWITTER_GOT_MENTIONS;
1642
1643        twitter_flush_timeline(ic);
1644}
1645
1646/**
1647 * Callback to use after sending a POST request to twitter.
1648 * (Generic, used for a few kinds of queries.)
1649 */
1650static void twitter_http_post(struct http_request *req)
1651{
1652        struct im_connection *ic = req->data;
1653        struct twitter_data *td;
1654        json_value *parsed, *id;
1655
1656        // Check if the connection is still active.
1657        if (!g_slist_find(twitter_connections, ic)) {
1658                return;
1659        }
1660
1661        td = ic->proto_data;
1662        td->last_status_id = 0;
1663
1664        if (!(parsed = twitter_parse_response(ic, req))) {
1665                return;
1666        }
1667
1668        if ((id = json_o_get(parsed, "id")) && id->type == json_integer) {
1669                td->last_status_id = id->u.integer;
1670        }
1671
1672        json_value_free(parsed);
1673
1674        if (req->flags & TWITTER_HTTP_USER_ACK) {
1675                twitter_log(ic, "Command processed successfully");
1676        }
1677}
1678
1679/**
1680 * Function to POST a new status to twitter.
1681 */
1682void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to)
1683{
1684        char *args[4] = {
1685                "status", msg,
1686                "in_reply_to_status_id",
1687                g_strdup_printf("%" G_GUINT64_FORMAT, in_reply_to)
1688        };
1689
1690        twitter_http(ic, TWITTER_STATUS_UPDATE_URL, twitter_http_post, ic, 1,
1691                     args, in_reply_to ? 4 : 2);
1692        g_free(args[3]);
1693}
1694
1695
1696/**
1697 * Function to POST a new message to twitter.
1698 */
1699void twitter_direct_messages_new(struct im_connection *ic, char *who, char *msg)
1700{
1701        char *args[4];
1702
1703        args[0] = "screen_name";
1704        args[1] = who;
1705        args[2] = "text";
1706        args[3] = msg;
1707        // Use the same callback as for twitter_post_status, since it does basically the same.
1708        twitter_http(ic, TWITTER_DIRECT_MESSAGES_NEW_URL, twitter_http_post, ic, 1, args, 4);
1709}
1710
1711void twitter_friendships_create_destroy(struct im_connection *ic, char *who, int create)
1712{
1713        char *args[2];
1714
1715        args[0] = "screen_name";
1716        args[1] = who;
1717        twitter_http(ic, create ? TWITTER_FRIENDSHIPS_CREATE_URL : TWITTER_FRIENDSHIPS_DESTROY_URL,
1718                     twitter_http_post, ic, 1, args, 2);
1719}
1720
1721/**
1722 * Mute or unmute a user
1723 */
1724void twitter_mute_create_destroy(struct im_connection *ic, char *who, int create)
1725{
1726        char *args[2];
1727
1728        args[0] = "screen_name";
1729        args[1] = who;
1730        twitter_http(ic, create ? TWITTER_MUTES_CREATE_URL : TWITTER_MUTES_DESTROY_URL,
1731                     twitter_http_post, ic, 1, args, 2);
1732}
1733
1734void twitter_status_destroy(struct im_connection *ic, guint64 id)
1735{
1736        char *url;
1737
1738        url = g_strdup_printf("%s%" G_GUINT64_FORMAT "%s",
1739                              TWITTER_STATUS_DESTROY_URL, id, ".json");
1740        twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0,
1741                       TWITTER_HTTP_USER_ACK);
1742        g_free(url);
1743}
1744
1745void twitter_status_retweet(struct im_connection *ic, guint64 id)
1746{
1747        char *url;
1748
1749        url = g_strdup_printf("%s%" G_GUINT64_FORMAT "%s",
1750                              TWITTER_STATUS_RETWEET_URL, id, ".json");
1751        twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0,
1752                       TWITTER_HTTP_USER_ACK);
1753        g_free(url);
1754}
1755
1756/**
1757 * Report a user for sending spam.
1758 */
1759void twitter_report_spam(struct im_connection *ic, char *screen_name)
1760{
1761        char *args[2] = {
1762                "screen_name",
1763                NULL,
1764        };
1765
1766        args[1] = screen_name;
1767        twitter_http_f(ic, TWITTER_REPORT_SPAM_URL, twitter_http_post,
1768                       ic, 1, args, 2, TWITTER_HTTP_USER_ACK);
1769}
1770
1771/**
1772 * Favourite a tweet.
1773 */
1774void twitter_favourite_tweet(struct im_connection *ic, guint64 id)
1775{
1776        char *args[2] = {
1777                "id",
1778                NULL,
1779        };
1780
1781        args[1] = g_strdup_printf("%" G_GUINT64_FORMAT, id);
1782        twitter_http_f(ic, TWITTER_FAVORITE_CREATE_URL, twitter_http_post,
1783                       ic, 1, args, 2, TWITTER_HTTP_USER_ACK);
1784        g_free(args[1]);
1785}
1786
1787static void twitter_http_status_show_url(struct http_request *req)
1788{
1789        struct im_connection *ic = req->data;
1790        json_value *parsed, *id;
1791        const char *name;
1792
1793        // Check if the connection is still active.
1794        if (!g_slist_find(twitter_connections, ic)) {
1795                return;
1796        }
1797
1798        if (!(parsed = twitter_parse_response(ic, req))) {
1799                return;
1800        }
1801
1802        /* for the parson branch:
1803        name = json_object_dotget_string(json_object(parsed), "user.screen_name");
1804        id = json_object_get_integer(json_object(parsed), "id");
1805        */
1806
1807        name = json_o_str(json_o_get(parsed, "user"), "screen_name");
1808        id = json_o_get(parsed, "id");
1809
1810        if (name && id && id->type == json_integer) {
1811                twitter_log(ic, "https://twitter.com/%s/status/%" G_GUINT64_FORMAT, name, id->u.integer);
1812        } else {
1813                twitter_log(ic, "Error: could not fetch tweet url.");
1814        }
1815
1816        json_value_free(parsed);
1817}
1818
1819void twitter_status_show_url(struct im_connection *ic, guint64 id)
1820{
1821        char *url = g_strdup_printf("%s%" G_GUINT64_FORMAT "%s", TWITTER_STATUS_SHOW_URL, id, ".json");
1822        twitter_http(ic, url, twitter_http_status_show_url, ic, 0, NULL, 0);
1823        g_free(url);
1824}
Note: See TracBrowser for help on using the repository browser.