source: protocols/twitter/twitter_lib.c @ cd60710

Last change on this file since cd60710 was cd60710, checked in by Wilmer van der Gaast <wilmer@…>, at 2015-05-02T22:26:29Z

Parsing of "retweets with comment". Not documented by Twitter so good luck.

Also, fix bug in parsing of entities in DMs.

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