source: protocols/twitter/twitter_lib.c @ 31e2b09

Last change on this file since 31e2b09 was ed83279, checked in by Wilmer van der Gaast <wilmer@…>, at 2015-06-17T23:58:02Z

Update Twitter "url" command change to work with Parson.

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