source: protocols/twitter/twitter_lib.c @ 985d66d

Last change on this file since 985d66d was 985d66d, checked in by dequis <dx@…>, at 2016-09-25T02:21:59Z

twitter: don't count filter stream reply as valid pongs

Twitter streams send newlines to indicate that they are alive. The
twitter_http_stream() function processes those and sets the ponged
flag so that the whole connection doesn't timeout.

That function is used to handle both user stream and filter stream.
If the user stream is dead (not sending whitespace) but the filter
stream isn't, the latter keeps the connection alive while the main
twitter channel is completely dead.

This commit only sets the ponged flag for the user stream. This has
the side effect of not detecting if the filter stream dies - but that
didn't work before, anyway. In the future the whole stream connection
management should be revamped - for example stream disconnections
shouldn't take the whole account down, especially not filter streams.

  • Property mode set to 100644
File size: 46.5 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);
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);
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
605        if (node->type != json_object) {
606                return FALSE;
607        }
608        txs = g_new0(struct twitter_xml_status, 1);
609
610        JSON_O_FOREACH(node, k, v) {
611                if (strcmp("text", k) == 0 && v->type == json_string && text_value == NULL) {
612                        text_value = v;
613                } else if (strcmp("full_text", k) == 0 && v->type == json_string) {
614                        text_value = v;
615                } else if (strcmp("extended_tweet", k) == 0 && v->type == json_object) {
616                        text_value = json_o_get(v, "full_text");
617                } else if (strcmp("retweeted_status", k) == 0 && v->type == json_object) {
618                        rt = v;
619                } else if (strcmp("created_at", k) == 0 && v->type == json_string) {
620                        struct tm parsed;
621
622                        /* Very sensitive to changes to the formatting of
623                           this field. :-( Also assumes the timezone used
624                           is UTC since C time handling functions suck. */
625                        if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL) {
626                                txs->created_at = mktime_utc(&parsed);
627                        }
628                } else if (strcmp("user", k) == 0 && v->type == json_object) {
629                        txs->user = twitter_xt_get_user(v);
630                } else if (strcmp("id", k) == 0 && v->type == json_integer) {
631                        txs->rt_id = txs->id = v->u.integer;
632                } else if (strcmp("in_reply_to_status_id", k) == 0 && v->type == json_integer) {
633                        txs->reply_to = v->u.integer;
634                }
635        }
636
637        /* If it's a (truncated) retweet, get the original. Even if the API claims it
638           wasn't truncated because it may be lying. */
639        if (rt) {
640                struct twitter_xml_status *rtxs = twitter_xt_get_status(rt);
641                if (rtxs) {
642                        txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text);
643                        txs->id = rtxs->id;
644                        txs_free(rtxs);
645                }
646        } else if (text_value && text_value->type == json_string) {
647                txs->text = g_memdup(text_value->u.string.ptr, text_value->u.string.length + 1);
648                strip_html(txs->text);
649                expand_entities(&txs->text, node);
650        }
651
652        if (txs->text && txs->user && txs->id) {
653                return txs;
654        }
655
656        txs_free(txs);
657        return NULL;
658}
659
660/**
661 * Function to fill a twitter_xml_status struct (DM variant).
662 */
663static struct twitter_xml_status *twitter_xt_get_dm(const json_value *node)
664{
665        struct twitter_xml_status *txs;
666
667        if (node->type != json_object) {
668                return FALSE;
669        }
670        txs = g_new0(struct twitter_xml_status, 1);
671
672        JSON_O_FOREACH(node, k, v) {
673                if (strcmp("text", k) == 0 && v->type == json_string) {
674                        txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1);
675                        strip_html(txs->text);
676                } else if (strcmp("created_at", k) == 0 && v->type == json_string) {
677                        struct tm parsed;
678
679                        /* Very sensitive to changes to the formatting of
680                           this field. :-( Also assumes the timezone used
681                           is UTC since C time handling functions suck. */
682                        if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL) {
683                                txs->created_at = mktime_utc(&parsed);
684                        }
685                } else if (strcmp("sender", k) == 0 && v->type == json_object) {
686                        txs->user = twitter_xt_get_user(v);
687                } else if (strcmp("id", k) == 0 && v->type == json_integer) {
688                        txs->id = v->u.integer;
689                }
690        }
691
692        expand_entities(&txs->text, node);
693
694        if (txs->text && txs->user && txs->id) {
695                return txs;
696        }
697
698        txs_free(txs);
699        return NULL;
700}
701
702static void expand_entities(char **text, const json_value *node)
703{
704        json_value *entities, *quoted;
705        char *quote_url = NULL, *quote_text = NULL;
706
707        if (!((entities = json_o_get(node, "entities")) && entities->type == json_object))
708                return;
709        if ((quoted = json_o_get(node, "quoted_status")) && quoted->type == json_object) {
710                /* New "retweets with comments" feature. Note that this info
711                 * seems to be included in the streaming API only! Grab the
712                 * full message and try to insert it when we run into the
713                 * Tweet entity. */
714                struct twitter_xml_status *txs = twitter_xt_get_status(quoted);
715                quote_text = g_strdup_printf("@%s: %s", txs->user->screen_name, txs->text);
716                quote_url = g_strdup_printf("%s/status/%" G_GUINT64_FORMAT, txs->user->screen_name, txs->id);
717                txs_free(txs);
718        } else {
719                quoted = NULL;
720        }
721
722        JSON_O_FOREACH(entities, k, v) {
723                int i;
724
725                if (v->type != json_array) {
726                        continue;
727                }
728                if (strcmp(k, "urls") != 0 && strcmp(k, "media") != 0) {
729                        continue;
730                }
731
732                for (i = 0; i < v->u.array.length; i++) {
733                        const char *format = "%s%s <%s>%s";
734
735                        if (v->u.array.values[i]->type != json_object) {
736                                continue;
737                        }
738
739                        const char *kort = json_o_str(v->u.array.values[i], "url");
740                        const char *disp = json_o_str(v->u.array.values[i], "display_url");
741                        const char *full = json_o_str(v->u.array.values[i], "expanded_url");
742                        char *pos, *new;
743
744                        if (!kort || !disp || !(pos = strstr(*text, kort))) {
745                                continue;
746                        }
747                        if (quote_url && strstr(full, quote_url)) {
748                                format = "%s<%s> [%s]%s";
749                                disp = quote_text;
750                        }
751
752                        *pos = '\0';
753                        new = g_strdup_printf(format, *text, kort,
754                                              disp, pos + strlen(kort));
755
756                        g_free(*text);
757                        *text = new;
758                }
759        }
760        g_free(quote_text);
761        g_free(quote_url);
762}
763
764/**
765 * Function to fill a twitter_xml_list struct.
766 * It sets:
767 *  - all <status>es within the <status> element and
768 *  - the next_cursor.
769 */
770static gboolean twitter_xt_get_status_list(struct im_connection *ic, const json_value *node,
771                                           struct twitter_xml_list *txl)
772{
773        struct twitter_xml_status *txs;
774        int i;
775
776        // Set the type of the list.
777        txl->type = TXL_STATUS;
778
779        if (node->type != json_array) {
780                return FALSE;
781        }
782
783        // The root <statuses> node should hold the list of statuses <status>
784        // Walk over the nodes children.
785        for (i = 0; i < node->u.array.length; i++) {
786                txs = twitter_xt_get_status(node->u.array.values[i]);
787                if (!txs) {
788                        continue;
789                }
790
791                txl->list = g_slist_prepend(txl->list, txs);
792        }
793
794        return TRUE;
795}
796
797/* Will log messages either way. Need to keep track of IDs for stream deduping.
798   Plus, show_ids is on by default and I don't see why anyone would disable it. */
799static char *twitter_msg_add_id(struct im_connection *ic,
800                                struct twitter_xml_status *txs, const char *prefix)
801{
802        struct twitter_data *td = ic->proto_data;
803        int reply_to = -1;
804        bee_user_t *bu;
805
806        if (txs->reply_to) {
807                int i;
808                for (i = 0; i < TWITTER_LOG_LENGTH; i++) {
809                        if (td->log[i].id == txs->reply_to) {
810                                reply_to = i;
811                                break;
812                        }
813                }
814        }
815
816        if (txs->user && txs->user->screen_name &&
817            (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) {
818                struct twitter_user_data *tud = bu->data;
819
820                if (txs->id > tud->last_id) {
821                        tud->last_id = txs->id;
822                        tud->last_time = txs->created_at;
823                }
824        }
825
826        td->log_id = (td->log_id + 1) % TWITTER_LOG_LENGTH;
827        td->log[td->log_id].id = txs->id;
828        td->log[td->log_id].bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name);
829
830        /* This is all getting hairy. :-( If we RT'ed something ourselves,
831           remember OUR id instead so undo will work. In other cases, the
832           original tweet's id should be remembered for deduplicating. */
833        if (g_strcasecmp(txs->user->screen_name, td->user) == 0) {
834                td->log[td->log_id].id = txs->rt_id;
835                /* More useful than NULL. */
836                td->log[td->log_id].bu = &twitter_log_local_user;
837        }
838
839        if (set_getbool(&ic->acc->set, "show_ids")) {
840                if (reply_to != -1) {
841                        return g_strdup_printf("\002[\002%02x->%02x\002]\002 %s%s",
842                                               td->log_id, reply_to, prefix, txs->text);
843                } else {
844                        return g_strdup_printf("\002[\002%02x\002]\002 %s%s",
845                                               td->log_id, prefix, txs->text);
846                }
847        } else {
848                if (*prefix) {
849                        return g_strconcat(prefix, txs->text, NULL);
850                } else {
851                        return NULL;
852                }
853        }
854}
855
856/**
857 * Function that is called to see the filter statuses in groupchat windows.
858 */
859static void twitter_status_show_filter(struct im_connection *ic, struct twitter_xml_status *status)
860{
861        struct twitter_data *td = ic->proto_data;
862        char *msg = twitter_msg_add_id(ic, status, "");
863        struct twitter_filter *tf;
864        GSList *f;
865        GSList *l;
866
867        for (f = td->filters; f; f = g_slist_next(f)) {
868                tf = f->data;
869
870                switch (tf->type) {
871                case TWITTER_FILTER_TYPE_FOLLOW:
872                        if (status->user->uid != tf->uid) {
873                                continue;
874                        }
875                        break;
876
877                case TWITTER_FILTER_TYPE_TRACK:
878                        if (strcasestr(status->text, tf->text) == NULL) {
879                                continue;
880                        }
881                        break;
882
883                default:
884                        continue;
885                }
886
887                for (l = tf->groupchats; l; l = g_slist_next(l)) {
888                        imcb_chat_msg(l->data, status->user->screen_name,
889                                      msg ? msg : status->text, 0, 0);
890                }
891        }
892
893        g_free(msg);
894}
895
896/**
897 * Function that is called to see the statuses in a groupchat window.
898 */
899static void twitter_status_show_chat(struct im_connection *ic, struct twitter_xml_status *status)
900{
901        struct twitter_data *td = ic->proto_data;
902        struct groupchat *gc;
903        gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0;
904        char *msg;
905
906        // Create a new groupchat if it does not exsist.
907        gc = twitter_groupchat_init(ic);
908
909        if (!me) {
910                /* MUST be done before twitter_msg_add_id() to avoid #872. */
911                twitter_add_buddy(ic, status->user->screen_name, status->user->name);
912        }
913        msg = twitter_msg_add_id(ic, status, "");
914
915        // Say it!
916        if (me) {
917                imcb_chat_log(gc, "You: %s", msg ? msg : status->text);
918        } else {
919                imcb_chat_msg(gc, status->user->screen_name,
920                              msg ? msg : status->text, 0, status->created_at);
921        }
922
923        g_free(msg);
924}
925
926/**
927 * Function that is called to see statuses as private messages.
928 */
929static void twitter_status_show_msg(struct im_connection *ic, struct twitter_xml_status *status)
930{
931        struct twitter_data *td = ic->proto_data;
932        char from[MAX_STRING] = "";
933        char *prefix = NULL, *text = NULL;
934        gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0;
935
936        if (td->flags & TWITTER_MODE_ONE) {
937                g_snprintf(from, sizeof(from) - 1, "%s_%s", td->prefix, ic->acc->user);
938                from[MAX_STRING - 1] = '\0';
939        }
940
941        if (td->flags & TWITTER_MODE_ONE) {
942                prefix = g_strdup_printf("\002<\002%s\002>\002 ",
943                                         status->user->screen_name);
944        } else if (!me) {
945                twitter_add_buddy(ic, status->user->screen_name, status->user->name);
946        } else {
947                prefix = g_strdup("You: ");
948        }
949
950        text = twitter_msg_add_id(ic, status, prefix ? prefix : "");
951
952        imcb_buddy_msg(ic,
953                       *from ? from : status->user->screen_name,
954                       text ? text : status->text, 0, status->created_at);
955
956        g_free(text);
957        g_free(prefix);
958}
959
960static void twitter_status_show(struct im_connection *ic, struct twitter_xml_status *status)
961{
962        struct twitter_data *td = ic->proto_data;
963        char *last_id_str;
964        char *uid_str;
965
966        if (status->user == NULL || status->text == NULL) {
967                return;
968        }
969       
970        /* Check this is not a tweet that should be muted */
971        uid_str = g_strdup_printf("%" PRIu64, status->user->uid);
972
973        if (g_slist_find_custom(td->mutes_ids, uid_str, (GCompareFunc)strcmp)) {
974                g_free(uid_str);
975                return;
976        }
977        if (status->id != status->rt_id && g_slist_find_custom(td->noretweets_ids, uid_str, (GCompareFunc)strcmp)) {
978                g_free(uid_str);
979                return;
980        }
981
982        /* Grrrr. Would like to do this during parsing, but can't access
983           settings from there. */
984        if (set_getbool(&ic->acc->set, "strip_newlines")) {
985                strip_newlines(status->text);
986        }
987
988        if (status->from_filter) {
989                twitter_status_show_filter(ic, status);
990        } else if (td->flags & TWITTER_MODE_CHAT) {
991                twitter_status_show_chat(ic, status);
992        } else {
993                twitter_status_show_msg(ic, status);
994        }
995
996        // Update the timeline_id to hold the highest id, so that by the next request
997        // we won't pick up the updates already in the list.
998        td->timeline_id = MAX(td->timeline_id, status->rt_id);
999
1000        last_id_str = g_strdup_printf("%" G_GUINT64_FORMAT, td->timeline_id);
1001        set_setstr(&ic->acc->set, "_last_tweet", last_id_str);
1002        g_free(last_id_str);
1003        g_free(uid_str);
1004}
1005
1006static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o, gboolean from_filter);
1007
1008static void twitter_http_stream(struct http_request *req)
1009{
1010        struct im_connection *ic = req->data;
1011        struct twitter_data *td;
1012        json_value *parsed;
1013        int len = 0;
1014        char c, *nl;
1015        gboolean from_filter;
1016
1017        if (!g_slist_find(twitter_connections, ic)) {
1018                return;
1019        }
1020
1021        td = ic->proto_data;
1022
1023        if ((req->flags & HTTPC_EOF) || !req->reply_body) {
1024                if (req == td->stream) {
1025                        td->stream = NULL;
1026                } else if (req == td->filter_stream) {
1027                        td->filter_stream = NULL;
1028                }
1029
1030                imcb_error(ic, "Stream closed (%s)", req->status_string);
1031                if (req->status_code == 401) {
1032                        imcb_error(ic, "Check your system clock.");
1033                }
1034                imc_logout(ic, TRUE);
1035                return;
1036        }
1037
1038        if (req == td->stream) {
1039                ic->flags |= OPT_PONGED;
1040        }
1041
1042        /* MUST search for CRLF, not just LF:
1043           https://dev.twitter.com/docs/streaming-apis/processing#Parsing_responses */
1044        if (!(nl = strstr(req->reply_body, "\r\n"))) {
1045                return;
1046        }
1047
1048        len = nl - req->reply_body;
1049        if (len > 0) {
1050                c = req->reply_body[len];
1051                req->reply_body[len] = '\0';
1052
1053                if ((parsed = json_parse(req->reply_body, req->body_size))) {
1054                        from_filter = (req == td->filter_stream);
1055                        twitter_stream_handle_object(ic, parsed, from_filter);
1056                }
1057                json_value_free(parsed);
1058                req->reply_body[len] = c;
1059        }
1060
1061        http_flush_bytes(req, len + 2);
1062
1063        /* One notification might bring multiple events! */
1064        if (req->body_size > 0) {
1065                twitter_http_stream(req);
1066        }
1067}
1068
1069static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o);
1070static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs);
1071
1072static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o, gboolean from_filter)
1073{
1074        struct twitter_data *td = ic->proto_data;
1075        struct twitter_xml_status *txs;
1076        json_value *c;
1077
1078        if ((txs = twitter_xt_get_status(o))) {
1079                txs->from_filter = from_filter;
1080                gboolean ret = twitter_stream_handle_status(ic, txs);
1081                txs_free(txs);
1082                return ret;
1083        } else if ((c = json_o_get(o, "direct_message")) &&
1084                   (txs = twitter_xt_get_dm(c))) {
1085                if (g_strcasecmp(txs->user->screen_name, td->user) != 0) {
1086                        imcb_buddy_msg(ic, txs->user->screen_name,
1087                                       txs->text, 0, txs->created_at);
1088                }
1089                txs_free(txs);
1090                return TRUE;
1091        } else if ((c = json_o_get(o, "event")) && c->type == json_string) {
1092                twitter_stream_handle_event(ic, o);
1093                return TRUE;
1094        } else if ((c = json_o_get(o, "disconnect")) && c->type == json_object) {
1095                /* HACK: Because we're inside an event handler, we can't just
1096                   disconnect here. Instead, just change the HTTP status string
1097                   into a Twitter status string. */
1098                char *reason = json_o_strdup(c, "reason");
1099                if (reason) {
1100                        g_free(td->stream->status_string);
1101                        td->stream->status_string = reason;
1102                }
1103                return TRUE;
1104        }
1105        return FALSE;
1106}
1107
1108static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs)
1109{
1110        struct twitter_data *td = ic->proto_data;
1111        int i;
1112
1113        for (i = 0; i < TWITTER_LOG_LENGTH; i++) {
1114                if (td->log[i].id == txs->id) {
1115                        /* Got a duplicate (RT, probably). Drop it. */
1116                        return TRUE;
1117                }
1118        }
1119
1120        if (!(g_strcasecmp(txs->user->screen_name, td->user) == 0 ||
1121              set_getbool(&ic->acc->set, "fetch_mentions") ||
1122              bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) {
1123                /* Tweet is from an unknown person and the user does not want
1124                   to see @mentions, so drop it. twitter_stream_handle_event()
1125                   picks up new follows so this simple filter should be safe. */
1126                /* TODO: The streaming API seems to do poor @mention matching.
1127                   I.e. I'm getting mentions for @WilmerSomething, not just for
1128                   @Wilmer. But meh. You want spam, you get spam. */
1129                return TRUE;
1130        }
1131
1132        twitter_status_show(ic, txs);
1133
1134        return TRUE;
1135}
1136
1137static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o)
1138{
1139        struct twitter_data *td = ic->proto_data;
1140        json_value *source = json_o_get(o, "source");
1141        json_value *target = json_o_get(o, "target");
1142        const char *type = json_o_str(o, "event");
1143        struct twitter_xml_user *us = NULL;
1144        struct twitter_xml_user *ut = NULL;
1145
1146        if (!type || !source || source->type != json_object
1147            || !target || target->type != json_object) {
1148                return FALSE;
1149        }
1150
1151        if (strcmp(type, "follow") == 0) {
1152                us = twitter_xt_get_user(source);
1153                ut = twitter_xt_get_user(target);
1154                if (g_strcasecmp(us->screen_name, td->user) == 0) {
1155                        twitter_add_buddy(ic, ut->screen_name, ut->name);
1156                }
1157        } else if (strcmp(type, "mute") == 0) {
1158                GSList *found;
1159                char *uid_str;
1160                ut = twitter_xt_get_user(target);
1161                uid_str = g_strdup_printf("%" PRIu64, ut->uid);
1162                if (!(found = g_slist_find_custom(td->mutes_ids, uid_str,
1163                                                  (GCompareFunc)strcmp))) {
1164                        td->mutes_ids = g_slist_prepend(td->mutes_ids, uid_str);
1165                }
1166                twitter_log(ic, "Muted user %s", ut->screen_name);
1167                if (getenv("BITLBEE_DEBUG")) {
1168                        fprintf(stderr, "New mute: %s %"PRIu64"\n",
1169                                ut->screen_name, ut->uid);
1170                }
1171        } else if (strcmp(type, "unmute") == 0) {
1172                GSList *found;
1173                char *uid_str;
1174                ut = twitter_xt_get_user(target);
1175                uid_str = g_strdup_printf("%" PRIu64, ut->uid);
1176                if ((found = g_slist_find_custom(td->mutes_ids, uid_str,
1177                                                (GCompareFunc)strcmp))) {
1178                        char *found_str = found->data;
1179                        td->mutes_ids = g_slist_delete_link(td->mutes_ids, found);
1180                        g_free(found_str);
1181                }
1182                g_free(uid_str);
1183                twitter_log(ic, "Unmuted user %s", ut->screen_name);
1184                if (getenv("BITLBEE_DEBUG")) {
1185                        fprintf(stderr, "New unmute: %s %"PRIu64"\n",
1186                                ut->screen_name, ut->uid);
1187                }
1188        }
1189
1190        txu_free(us);
1191        txu_free(ut);
1192
1193        return TRUE;
1194}
1195
1196gboolean twitter_open_stream(struct im_connection *ic)
1197{
1198        struct twitter_data *td = ic->proto_data;
1199        char *args[2] = { "with", "followings" };
1200
1201        if ((td->stream = twitter_http(ic, TWITTER_USER_STREAM_URL,
1202                                       twitter_http_stream, ic, 0, args, 2))) {
1203                /* This flag must be enabled or we'll get no data until EOF
1204                   (which err, kind of, defeats the purpose of a streaming API). */
1205                td->stream->flags |= HTTPC_STREAMING;
1206                return TRUE;
1207        }
1208
1209        return FALSE;
1210}
1211
1212static gboolean twitter_filter_stream(struct im_connection *ic)
1213{
1214        struct twitter_data *td = ic->proto_data;
1215        char *args[4] = { "follow", NULL, "track", NULL };
1216        GString *followstr = g_string_new("");
1217        GString *trackstr = g_string_new("");
1218        gboolean ret = FALSE;
1219        struct twitter_filter *tf;
1220        GSList *l;
1221
1222        for (l = td->filters; l; l = g_slist_next(l)) {
1223                tf = l->data;
1224
1225                switch (tf->type) {
1226                case TWITTER_FILTER_TYPE_FOLLOW:
1227                        if (followstr->len > 0) {
1228                                g_string_append_c(followstr, ',');
1229                        }
1230
1231                        g_string_append_printf(followstr, "%" G_GUINT64_FORMAT,
1232                                               tf->uid);
1233                        break;
1234
1235                case TWITTER_FILTER_TYPE_TRACK:
1236                        if (trackstr->len > 0) {
1237                                g_string_append_c(trackstr, ',');
1238                        }
1239
1240                        g_string_append(trackstr, tf->text);
1241                        break;
1242
1243                default:
1244                        continue;
1245                }
1246        }
1247
1248        args[1] = followstr->str;
1249        args[3] = trackstr->str;
1250
1251        if (td->filter_stream) {
1252                http_close(td->filter_stream);
1253        }
1254
1255        if ((td->filter_stream = twitter_http(ic, TWITTER_FILTER_STREAM_URL,
1256                                              twitter_http_stream, ic, 0,
1257                                              args, 4))) {
1258                /* This flag must be enabled or we'll get no data until EOF
1259                   (which err, kind of, defeats the purpose of a streaming API). */
1260                td->filter_stream->flags |= HTTPC_STREAMING;
1261                ret = TRUE;
1262        }
1263
1264        g_string_free(followstr, TRUE);
1265        g_string_free(trackstr, TRUE);
1266
1267        return ret;
1268}
1269
1270static void twitter_filter_users_post(struct http_request *req)
1271{
1272        struct im_connection *ic = req->data;
1273        struct twitter_data *td;
1274        struct twitter_filter *tf;
1275        GList *users = NULL;
1276        json_value *parsed;
1277        json_value *id;
1278        const char *name;
1279        GString *fstr;
1280        GSList *l;
1281        GList *u;
1282        int i;
1283
1284        // Check if the connection is still active.
1285        if (!g_slist_find(twitter_connections, ic)) {
1286                return;
1287        }
1288
1289        td = ic->proto_data;
1290
1291        if (!(parsed = twitter_parse_response(ic, req))) {
1292                return;
1293        }
1294
1295        for (l = td->filters; l; l = g_slist_next(l)) {
1296                tf = l->data;
1297
1298                if (tf->type == TWITTER_FILTER_TYPE_FOLLOW) {
1299                        users = g_list_prepend(users, tf);
1300                }
1301        }
1302
1303        if (parsed->type != json_array) {
1304                goto finish;
1305        }
1306
1307        for (i = 0; i < parsed->u.array.length; i++) {
1308                id = json_o_get(parsed->u.array.values[i], "id");
1309                name = json_o_str(parsed->u.array.values[i], "screen_name");
1310
1311                if (!name || !id || id->type != json_integer) {
1312                        continue;
1313                }
1314
1315                for (u = users; u; u = g_list_next(u)) {
1316                        tf = u->data;
1317
1318                        if (g_strcasecmp(tf->text, name) == 0) {
1319                                tf->uid = id->u.integer;
1320                                users = g_list_delete_link(users, u);
1321                                break;
1322                        }
1323                }
1324        }
1325
1326finish:
1327        json_value_free(parsed);
1328        twitter_filter_stream(ic);
1329
1330        if (!users) {
1331                return;
1332        }
1333
1334        fstr = g_string_new("");
1335
1336        for (u = users; u; u = g_list_next(u)) {
1337                if (fstr->len > 0) {
1338                        g_string_append(fstr, ", ");
1339                }
1340
1341                g_string_append(fstr, tf->text);
1342        }
1343
1344        imcb_error(ic, "Failed UID acquisitions: %s", fstr->str);
1345
1346        g_string_free(fstr, TRUE);
1347        g_list_free(users);
1348}
1349
1350gboolean twitter_open_filter_stream(struct im_connection *ic)
1351{
1352        struct twitter_data *td = ic->proto_data;
1353        char *args[2] = { "screen_name", NULL };
1354        GString *ustr = g_string_new("");
1355        struct twitter_filter *tf;
1356        struct http_request *req;
1357        GSList *l;
1358
1359        for (l = td->filters; l; l = g_slist_next(l)) {
1360                tf = l->data;
1361
1362                if (tf->type != TWITTER_FILTER_TYPE_FOLLOW || tf->uid != 0) {
1363                        continue;
1364                }
1365
1366                if (ustr->len > 0) {
1367                        g_string_append_c(ustr, ',');
1368                }
1369
1370                g_string_append(ustr, tf->text);
1371        }
1372
1373        if (ustr->len == 0) {
1374                g_string_free(ustr, TRUE);
1375                return twitter_filter_stream(ic);
1376        }
1377
1378        args[1] = ustr->str;
1379        req = twitter_http(ic, TWITTER_USERS_LOOKUP_URL,
1380                           twitter_filter_users_post,
1381                           ic, 0, args, 2);
1382
1383        g_string_free(ustr, TRUE);
1384        return req != NULL;
1385}
1386
1387static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor);
1388static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor);
1389
1390/**
1391 * Get the timeline with optionally mentions
1392 */
1393gboolean twitter_get_timeline(struct im_connection *ic, gint64 next_cursor)
1394{
1395        struct twitter_data *td = ic->proto_data;
1396        gboolean include_mentions = set_getbool(&ic->acc->set, "fetch_mentions");
1397
1398        if (td->flags & TWITTER_DOING_TIMELINE) {
1399                if (++td->http_fails >= 5) {
1400                        imcb_error(ic, "Fetch timeout (%d)", td->flags);
1401                        imc_logout(ic, TRUE);
1402                        return FALSE;
1403                }
1404        }
1405
1406        td->flags |= TWITTER_DOING_TIMELINE;
1407
1408        twitter_get_home_timeline(ic, next_cursor);
1409
1410        if (include_mentions) {
1411                twitter_get_mentions(ic, next_cursor);
1412        }
1413
1414        return TRUE;
1415}
1416
1417/**
1418 * Call this one after receiving timeline/mentions. Show to user once we have
1419 * both.
1420 */
1421void twitter_flush_timeline(struct im_connection *ic)
1422{
1423        struct twitter_data *td = ic->proto_data;
1424        gboolean include_mentions = set_getbool(&ic->acc->set, "fetch_mentions");
1425        int show_old_mentions = set_getint(&ic->acc->set, "show_old_mentions");
1426        struct twitter_xml_list *home_timeline = td->home_timeline_obj;
1427        struct twitter_xml_list *mentions = td->mentions_obj;
1428        guint64 last_id = 0;
1429        GSList *output = NULL;
1430        GSList *l;
1431
1432        imcb_connected(ic);
1433
1434        if (!(td->flags & TWITTER_GOT_TIMELINE)) {
1435                return;
1436        }
1437
1438        if (include_mentions && !(td->flags & TWITTER_GOT_MENTIONS)) {
1439                return;
1440        }
1441
1442        if (home_timeline && home_timeline->list) {
1443                for (l = home_timeline->list; l; l = g_slist_next(l)) {
1444                        output = g_slist_insert_sorted(output, l->data, twitter_compare_elements);
1445                }
1446        }
1447
1448        if (include_mentions && mentions && mentions->list) {
1449                for (l = mentions->list; l; l = g_slist_next(l)) {
1450                        if (show_old_mentions < 1 && output && twitter_compare_elements(l->data, output->data) < 0) {
1451                                continue;
1452                        }
1453
1454                        output = g_slist_insert_sorted(output, l->data, twitter_compare_elements);
1455                }
1456        }
1457
1458        // See if the user wants to see the messages in a groupchat window or as private messages.
1459        while (output) {
1460                struct twitter_xml_status *txs = output->data;
1461                if (txs->id != last_id) {
1462                        twitter_status_show(ic, txs);
1463                }
1464                last_id = txs->id;
1465                output = g_slist_remove(output, txs);
1466        }
1467
1468        txl_free(home_timeline);
1469        txl_free(mentions);
1470
1471        td->flags &= ~(TWITTER_DOING_TIMELINE | TWITTER_GOT_TIMELINE | TWITTER_GOT_MENTIONS);
1472        td->home_timeline_obj = td->mentions_obj = NULL;
1473}
1474
1475static void twitter_http_get_home_timeline(struct http_request *req);
1476static void twitter_http_get_mentions(struct http_request *req);
1477
1478/**
1479 * Get the timeline.
1480 */
1481static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
1482{
1483        struct twitter_data *td = ic->proto_data;
1484
1485        txl_free(td->home_timeline_obj);
1486        td->home_timeline_obj = NULL;
1487        td->flags &= ~TWITTER_GOT_TIMELINE;
1488
1489        char *args[8];
1490        args[0] = "cursor";
1491        args[1] = g_strdup_printf("%" G_GINT64_FORMAT, next_cursor);
1492        args[2] = "include_entities";
1493        args[3] = "true";
1494        args[4] = "tweet_mode";
1495        args[5] = "extended";
1496        if (td->timeline_id) {
1497                args[6] = "since_id";
1498                args[7] = g_strdup_printf("%" G_GUINT64_FORMAT, td->timeline_id);
1499        }
1500
1501        if (twitter_http(ic, TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, args,
1502                         td->timeline_id ? 8 : 6) == NULL) {
1503                if (++td->http_fails >= 5) {
1504                        imcb_error(ic, "Could not retrieve %s: %s",
1505                                   TWITTER_HOME_TIMELINE_URL, "connection failed");
1506                }
1507                td->flags |= TWITTER_GOT_TIMELINE;
1508                twitter_flush_timeline(ic);
1509        }
1510
1511        g_free(args[1]);
1512        if (td->timeline_id) {
1513                g_free(args[7]);
1514        }
1515}
1516
1517/**
1518 * Get mentions.
1519 */
1520static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)
1521{
1522        struct twitter_data *td = ic->proto_data;
1523
1524        txl_free(td->mentions_obj);
1525        td->mentions_obj = NULL;
1526        td->flags &= ~TWITTER_GOT_MENTIONS;
1527
1528        char *args[8];
1529        args[0] = "cursor";
1530        args[1] = g_strdup_printf("%" G_GINT64_FORMAT, next_cursor);
1531        args[2] = "include_entities";
1532        args[3] = "true";
1533        if (td->timeline_id) {
1534                args[4] = "since_id";
1535                args[5] = g_strdup_printf("%" G_GUINT64_FORMAT, td->timeline_id);
1536        } else {
1537                args[4] = "count";
1538                args[5] = g_strdup_printf("%d", set_getint(&ic->acc->set, "show_old_mentions"));
1539        }
1540        args[6] = "tweet_mode";
1541        args[7] = "extended";
1542
1543        if (twitter_http(ic, TWITTER_MENTIONS_URL, twitter_http_get_mentions,
1544                         ic, 0, args, 8) == NULL) {
1545                if (++td->http_fails >= 5) {
1546                        imcb_error(ic, "Could not retrieve %s: %s",
1547                                   TWITTER_MENTIONS_URL, "connection failed");
1548                }
1549                td->flags |= TWITTER_GOT_MENTIONS;
1550                twitter_flush_timeline(ic);
1551        }
1552
1553        g_free(args[1]);
1554        g_free(args[5]);
1555}
1556
1557/**
1558 * Callback for getting the home timeline.
1559 */
1560static void twitter_http_get_home_timeline(struct http_request *req)
1561{
1562        struct im_connection *ic = req->data;
1563        struct twitter_data *td;
1564        json_value *parsed;
1565        struct twitter_xml_list *txl;
1566
1567        // Check if the connection is still active.
1568        if (!g_slist_find(twitter_connections, ic)) {
1569                return;
1570        }
1571
1572        td = ic->proto_data;
1573
1574        // The root <statuses> node should hold the list of statuses <status>
1575        if (!(parsed = twitter_parse_response(ic, req))) {
1576                goto end;
1577        }
1578
1579        txl = g_new0(struct twitter_xml_list, 1);
1580        txl->list = NULL;
1581
1582        twitter_xt_get_status_list(ic, parsed, txl);
1583        json_value_free(parsed);
1584
1585        td->home_timeline_obj = txl;
1586
1587end:
1588        if (!g_slist_find(twitter_connections, ic)) {
1589                return;
1590        }
1591
1592        td->flags |= TWITTER_GOT_TIMELINE;
1593
1594        twitter_flush_timeline(ic);
1595}
1596
1597/**
1598 * Callback for getting mentions.
1599 */
1600static void twitter_http_get_mentions(struct http_request *req)
1601{
1602        struct im_connection *ic = req->data;
1603        struct twitter_data *td;
1604        json_value *parsed;
1605        struct twitter_xml_list *txl;
1606
1607        // Check if the connection is still active.
1608        if (!g_slist_find(twitter_connections, ic)) {
1609                return;
1610        }
1611
1612        td = ic->proto_data;
1613
1614        // The root <statuses> node should hold the list of statuses <status>
1615        if (!(parsed = twitter_parse_response(ic, req))) {
1616                goto end;
1617        }
1618
1619        txl = g_new0(struct twitter_xml_list, 1);
1620        txl->list = NULL;
1621
1622        twitter_xt_get_status_list(ic, parsed, txl);
1623        json_value_free(parsed);
1624
1625        td->mentions_obj = txl;
1626
1627end:
1628        if (!g_slist_find(twitter_connections, ic)) {
1629                return;
1630        }
1631
1632        td->flags |= TWITTER_GOT_MENTIONS;
1633
1634        twitter_flush_timeline(ic);
1635}
1636
1637/**
1638 * Callback to use after sending a POST request to twitter.
1639 * (Generic, used for a few kinds of queries.)
1640 */
1641static void twitter_http_post(struct http_request *req)
1642{
1643        struct im_connection *ic = req->data;
1644        struct twitter_data *td;
1645        json_value *parsed, *id;
1646
1647        // Check if the connection is still active.
1648        if (!g_slist_find(twitter_connections, ic)) {
1649                return;
1650        }
1651
1652        td = ic->proto_data;
1653        td->last_status_id = 0;
1654
1655        if (!(parsed = twitter_parse_response(ic, req))) {
1656                return;
1657        }
1658
1659        if ((id = json_o_get(parsed, "id")) && id->type == json_integer) {
1660                td->last_status_id = id->u.integer;
1661        }
1662
1663        json_value_free(parsed);
1664
1665        if (req->flags & TWITTER_HTTP_USER_ACK) {
1666                twitter_log(ic, "Command processed successfully");
1667        }
1668}
1669
1670/**
1671 * Function to POST a new status to twitter.
1672 */
1673void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to)
1674{
1675        char *args[4] = {
1676                "status", msg,
1677                "in_reply_to_status_id",
1678                g_strdup_printf("%" G_GUINT64_FORMAT, in_reply_to)
1679        };
1680
1681        twitter_http(ic, TWITTER_STATUS_UPDATE_URL, twitter_http_post, ic, 1,
1682                     args, in_reply_to ? 4 : 2);
1683        g_free(args[3]);
1684}
1685
1686
1687/**
1688 * Function to POST a new message to twitter.
1689 */
1690void twitter_direct_messages_new(struct im_connection *ic, char *who, char *msg)
1691{
1692        char *args[4];
1693
1694        args[0] = "screen_name";
1695        args[1] = who;
1696        args[2] = "text";
1697        args[3] = msg;
1698        // Use the same callback as for twitter_post_status, since it does basically the same.
1699        twitter_http(ic, TWITTER_DIRECT_MESSAGES_NEW_URL, twitter_http_post, ic, 1, args, 4);
1700}
1701
1702void twitter_friendships_create_destroy(struct im_connection *ic, char *who, int create)
1703{
1704        char *args[2];
1705
1706        args[0] = "screen_name";
1707        args[1] = who;
1708        twitter_http(ic, create ? TWITTER_FRIENDSHIPS_CREATE_URL : TWITTER_FRIENDSHIPS_DESTROY_URL,
1709                     twitter_http_post, ic, 1, args, 2);
1710}
1711
1712/**
1713 * Mute or unmute a user
1714 */
1715void twitter_mute_create_destroy(struct im_connection *ic, char *who, int create)
1716{
1717        char *args[2];
1718
1719        args[0] = "screen_name";
1720        args[1] = who;
1721        twitter_http(ic, create ? TWITTER_MUTES_CREATE_URL : TWITTER_MUTES_DESTROY_URL,
1722                     twitter_http_post, ic, 1, args, 2);
1723}
1724
1725void twitter_status_destroy(struct im_connection *ic, guint64 id)
1726{
1727        char *url;
1728
1729        url = g_strdup_printf("%s%" G_GUINT64_FORMAT "%s",
1730                              TWITTER_STATUS_DESTROY_URL, id, ".json");
1731        twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0,
1732                       TWITTER_HTTP_USER_ACK);
1733        g_free(url);
1734}
1735
1736void twitter_status_retweet(struct im_connection *ic, guint64 id)
1737{
1738        char *url;
1739
1740        url = g_strdup_printf("%s%" G_GUINT64_FORMAT "%s",
1741                              TWITTER_STATUS_RETWEET_URL, id, ".json");
1742        twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0,
1743                       TWITTER_HTTP_USER_ACK);
1744        g_free(url);
1745}
1746
1747/**
1748 * Report a user for sending spam.
1749 */
1750void twitter_report_spam(struct im_connection *ic, char *screen_name)
1751{
1752        char *args[2] = {
1753                "screen_name",
1754                NULL,
1755        };
1756
1757        args[1] = screen_name;
1758        twitter_http_f(ic, TWITTER_REPORT_SPAM_URL, twitter_http_post,
1759                       ic, 1, args, 2, TWITTER_HTTP_USER_ACK);
1760}
1761
1762/**
1763 * Favourite a tweet.
1764 */
1765void twitter_favourite_tweet(struct im_connection *ic, guint64 id)
1766{
1767        char *args[2] = {
1768                "id",
1769                NULL,
1770        };
1771
1772        args[1] = g_strdup_printf("%" G_GUINT64_FORMAT, id);
1773        twitter_http_f(ic, TWITTER_FAVORITE_CREATE_URL, twitter_http_post,
1774                       ic, 1, args, 2, TWITTER_HTTP_USER_ACK);
1775        g_free(args[1]);
1776}
1777
1778static void twitter_http_status_show_url(struct http_request *req)
1779{
1780        struct im_connection *ic = req->data;
1781        json_value *parsed, *id;
1782        const char *name;
1783
1784        // Check if the connection is still active.
1785        if (!g_slist_find(twitter_connections, ic)) {
1786                return;
1787        }
1788
1789        if (!(parsed = twitter_parse_response(ic, req))) {
1790                return;
1791        }
1792
1793        /* for the parson branch:
1794        name = json_object_dotget_string(json_object(parsed), "user.screen_name");
1795        id = json_object_get_integer(json_object(parsed), "id");
1796        */
1797
1798        name = json_o_str(json_o_get(parsed, "user"), "screen_name");
1799        id = json_o_get(parsed, "id");
1800
1801        if (name && id && id->type == json_integer) {
1802                twitter_log(ic, "https://twitter.com/%s/status/%" G_GUINT64_FORMAT, name, id->u.integer);
1803        } else {
1804                twitter_log(ic, "Error: could not fetch tweet url.");
1805        }
1806
1807        json_value_free(parsed);
1808}
1809
1810void twitter_status_show_url(struct im_connection *ic, guint64 id)
1811{
1812        char *url = g_strdup_printf("%s%" G_GUINT64_FORMAT "%s", TWITTER_STATUS_SHOW_URL, id, ".json");
1813        twitter_http(ic, url, twitter_http_status_show_url, ic, 0, NULL, 0);
1814        g_free(url);
1815}
Note: See TracBrowser for help on using the repository browser.