source: protocols/twitter/twitter_lib.c @ 166a571

Last change on this file since 166a571 was 166a571, checked in by Flexo <nick@…>, at 2016-04-01T18:06:15Z

Avoid adding an id twice to the mutes list.

Twitter doesn't error if you mute the same user multiple times.

Also, correct signedness of the stringified user ids. bitlbee keeps them as
unsigned even if the json library uses signed for integers...

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