Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • protocols/twitter/twitter_lib.c

    r2a6da96 r573e274  
    3535#include "misc.h"
    3636#include "base64.h"
    37 #include "xmltree.h"
    3837#include "twitter_lib.h"
     38#include "json_util.h"
    3939#include <ctype.h>
    4040#include <errno.h>
     
    6767        char *text;
    6868        struct twitter_xml_user *user;
    69         guint64 id, reply_to;
     69        guint64 id, rt_id; /* Usually equal, with RTs id == *original* id */
     70        guint64 reply_to;
    7071};
    71 
    72 static void twitter_groupchat_init(struct im_connection *ic);
    7372
    7473/**
     
    148147        // Check if the buddy is already in the buddy list.
    149148        if (!bee_user_by_handle(ic->bee, ic, name)) {
    150                 char *mode = set_getstr(&ic->acc->set, "mode");
    151 
    152149                // The buddy is not in the list, add the buddy and set the status to logged in.
    153150                imcb_add_buddy(ic, name, NULL);
    154151                imcb_rename_buddy(ic, name, fullname);
    155                 if (g_strcasecmp(mode, "chat") == 0) {
     152                if (td->flags & TWITTER_MODE_CHAT) {
    156153                        /* Necessary so that nicks always get translated to the
    157154                           exact Twitter username. */
    158155                        imcb_buddy_nick_hint(ic, name, name);
    159                         imcb_chat_add_buddy(td->timeline_gc, name);
    160                 } else if (g_strcasecmp(mode, "many") == 0)
     156                        if (td->timeline_gc)
     157                                imcb_chat_add_buddy(td->timeline_gc, name);
     158                } else if (td->flags & TWITTER_MODE_MANY)
    161159                        imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL);
    162160        }
     
    168166{
    169167        static char *ret = NULL;
    170         struct xt_node *root, *node, *err;
     168        json_value *root, *err;
    171169
    172170        g_free(ret);
     
    174172
    175173        if (req->body_size > 0) {
    176                 root = xt_from_string(req->reply_body, req->body_size);
    177                
    178                 for (node = root; node; node = node->next)
    179                         if ((err = xt_find_node(node->children, "error")) && err->text_len > 0) {
    180                                 ret = g_strdup_printf("%s (%s)", req->status_string, err->text);
    181                                 break;
    182                         }
    183 
    184                 xt_free_node(root);
     174                root = json_parse(req->reply_body);
     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                json_value_free(root);
    185183        }
    186184
     
    188186}
    189187
    190 static struct xt_node *twitter_parse_response(struct im_connection *ic, struct http_request *req)
     188/* WATCH OUT: This function might or might not destroy your connection.
     189   Sub-optimal indeed, but just be careful when this returns NULL! */
     190static json_value *twitter_parse_response(struct im_connection *ic, struct http_request *req)
    191191{
    192192        gboolean logging_in = !(ic->flags & OPT_LOGGED_IN);
    193193        gboolean periodic;
    194194        struct twitter_data *td = ic->proto_data;
    195         struct xt_node *ret;
     195        json_value *ret;
    196196        char path[64] = "", *s;
    197197       
     
    211211                   throwing 401s so I'll keep treating this one as fatal
    212212                   only during login. */
    213                 imcb_error(ic, "Authentication failure");
     213                imcb_error(ic, "Authentication failure (%s)",
     214                               twitter_parse_error(req));
    214215                imc_logout(ic, FALSE);
    215216                return NULL;
     
    217218                // It didn't go well, output the error and return.
    218219                if (!periodic || logging_in || ++td->http_fails >= 5)
    219                         imcb_error(ic, "Could not retrieve %s: %s",
    220                                    path, twitter_parse_error(req));
     220                        twitter_log(ic, "Error: Could not retrieve %s: %s",
     221                                    path, twitter_parse_error(req));
    221222               
    222223                if (logging_in)
     
    227228        }
    228229
    229         if ((ret = xt_from_string(req->reply_body, req->body_size)) == NULL) {
     230        if ((ret = json_parse(req->reply_body)) == NULL) {
    230231                imcb_error(ic, "Could not retrieve %s: %s",
    231232                           path, "XML parse error");
     
    251252
    252253/**
    253  * Function to help fill a list.
    254  */
    255 static xt_status twitter_xt_next_cursor(struct xt_node *node, struct twitter_xml_list *txl)
    256 {
    257         char *end = NULL;
    258 
    259         if (node->text)
    260                 txl->next_cursor = g_ascii_strtoll(node->text, &end, 10);
    261         if (end == NULL)
    262                 txl->next_cursor = -1;
    263 
    264         return XT_HANDLED;
    265 }
    266 
    267 /**
    268254 * Fill a list of ids.
    269255 */
    270 static xt_status twitter_xt_get_friends_id_list(struct xt_node *node, struct twitter_xml_list *txl)
    271 {
    272         struct xt_node *child;
     256static gboolean twitter_xt_get_friends_id_list(json_value *node, struct twitter_xml_list *txl)
     257{
     258        json_value *c;
     259        int i;
    273260
    274261        // Set the list type.
    275262        txl->type = TXL_ID;
    276263
    277         // The root <statuses> node should hold the list of statuses <status>
    278         // Walk over the nodes children.
    279         for (child = node->children; child; child = child->next) {
    280                 if (g_strcasecmp("ids", child->name) == 0) {
    281                         struct xt_node *idc;
    282                         for (idc = child->children; idc; idc = idc->next)
    283                                 if (g_strcasecmp(idc->name, "id") == 0)
    284                                         txl->list = g_slist_prepend(txl->list,
    285                                                 g_memdup(idc->text, idc->text_len + 1));
    286                 } else if (g_strcasecmp("next_cursor", child->name) == 0) {
    287                         twitter_xt_next_cursor(child, txl);
    288                 }
    289         }
    290 
    291         return XT_HANDLED;
     264        c = json_o_get(node, "ids");
     265        if (!c || c->type != json_array)
     266                return FALSE;
     267
     268        for (i = 0; i < c->u.array.length; i ++) {
     269                if (c->u.array.values[i]->type != json_integer)
     270                        continue;
     271               
     272                txl->list = g_slist_prepend(txl->list,
     273                        g_strdup_printf("%lld", c->u.array.values[i]->u.integer));
     274        }
     275       
     276        c = json_o_get(node, "next_cursor");
     277        if (c && c->type == json_integer)
     278                txl->next_cursor = c->u.integer;
     279        else
     280                txl->next_cursor = -1;
     281       
     282        return TRUE;
    292283}
    293284
     
    300291{
    301292        struct im_connection *ic;
    302         struct xt_node *parsed;
     293        json_value *parsed;
    303294        struct twitter_xml_list *txl;
    304295        struct twitter_data *td;
     
    312303        td = ic->proto_data;
    313304
    314         /* Create the room now that we "logged in". */
    315         if (!td->timeline_gc && g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0)
    316                 twitter_groupchat_init(ic);
    317 
    318305        txl = g_new0(struct twitter_xml_list, 1);
    319306        txl->list = td->follow_ids;
     
    322309        if (!(parsed = twitter_parse_response(ic, req)))
    323310                return;
     311       
    324312        twitter_xt_get_friends_id_list(parsed, txl);
    325         xt_free_node(parsed);
     313        json_value_free(parsed);
    326314
    327315        td->follow_ids = txl->list;
     
    338326}
    339327
    340 static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl);
     328static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl);
    341329static void twitter_http_get_users_lookup(struct http_request *req);
    342330
     
    379367{
    380368        struct im_connection *ic = req->data;
    381         struct xt_node *parsed;
     369        json_value *parsed;
    382370        struct twitter_xml_list *txl;
    383371        GSList *l = NULL;
     
    395383                return;
    396384        twitter_xt_get_users(parsed, txl);
    397         xt_free_node(parsed);
     385        json_value_free(parsed);
    398386
    399387        // Add the users as buddies.
     
    409397}
    410398
    411 /**
    412  * Function to fill a twitter_xml_user struct.
    413  * It sets:
    414  *  - the name and
    415  *  - the screen_name.
    416  */
    417 static xt_status twitter_xt_get_user(struct xt_node *node, struct twitter_xml_user *txu)
    418 {
    419         struct xt_node *child;
    420 
    421         // Walk over the nodes children.
    422         for (child = node->children; child; child = child->next) {
    423                 if (g_strcasecmp("name", child->name) == 0) {
    424                         txu->name = g_memdup(child->text, child->text_len + 1);
    425                 } else if (g_strcasecmp("screen_name", child->name) == 0) {
    426                         txu->screen_name = g_memdup(child->text, child->text_len + 1);
    427                 }
    428         }
    429         return XT_HANDLED;
     399struct twitter_xml_user *twitter_xt_get_user(const json_value *node)
     400{
     401        struct twitter_xml_user *txu;
     402       
     403        txu = g_new0(struct twitter_xml_user, 1);
     404        txu->name = g_strdup(json_o_str(node, "name"));
     405        txu->screen_name = g_strdup(json_o_str(node, "screen_name"));
     406       
     407        return txu;
    430408}
    431409
     
    435413 *  - all <user>s from the <users> element.
    436414 */
    437 static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl)
     415static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl)
    438416{
    439417        struct twitter_xml_user *txu;
    440         struct xt_node *child;
     418        int i;
    441419
    442420        // Set the type of the list.
    443421        txl->type = TXL_USER;
    444422
     423        if (!node || node->type != json_array)
     424                return FALSE;
     425
    445426        // The root <users> node should hold the list of users <user>
    446427        // Walk over the nodes children.
    447         for (child = node->children; child; child = child->next) {
    448                 if (g_strcasecmp("user", child->name) == 0) {
    449                         txu = g_new0(struct twitter_xml_user, 1);
    450                         twitter_xt_get_user(child, txu);
    451                         // Put the item in the front of the list.
     428        for (i = 0; i < node->u.array.length; i ++) {
     429                txu = twitter_xt_get_user(node->u.array.values[i]);
     430                if (txu)
    452431                        txl->list = g_slist_prepend(txl->list, txu);
    453                 }
    454         }
    455 
    456         return XT_HANDLED;
     432        }
     433
     434        return TRUE;
    457435}
    458436
     
    462440#define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S +0000 %Y"
    463441#endif
     442
     443static char* expand_entities(char* text, const json_value *entities);
    464444
    465445/**
     
    471451 *  - the user in a twitter_xml_user struct.
    472452 */
    473 static xt_status twitter_xt_get_status(struct xt_node *node, struct twitter_xml_status *txs)
    474 {
    475         struct xt_node *child, *rt = NULL;
    476 
    477         // Walk over the nodes children.
    478         for (child = node->children; child; child = child->next) {
    479                 if (g_strcasecmp("text", child->name) == 0) {
    480                         txs->text = g_memdup(child->text, child->text_len + 1);
    481                 } else if (g_strcasecmp("retweeted_status", child->name) == 0) {
    482                         rt = child;
    483                 } else if (g_strcasecmp("created_at", child->name) == 0) {
     453static struct twitter_xml_status *twitter_xt_get_status(const json_value *node)
     454{
     455        struct twitter_xml_status *txs;
     456        const json_value *rt = NULL, *entities = NULL;
     457       
     458        if (node->type != json_object)
     459                return FALSE;
     460        txs = g_new0(struct twitter_xml_status, 1);
     461
     462        JSON_O_FOREACH (node, k, v) {
     463                if (strcmp("text", k) == 0 && v->type == json_string) {
     464                        txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1);
     465                        strip_html(txs->text);
     466                } else if (strcmp("retweeted_status", k) == 0 && v->type == json_object) {
     467                        rt = v;
     468                } else if (strcmp("created_at", k) == 0 && v->type == json_string) {
    484469                        struct tm parsed;
    485470
     
    487472                           this field. :-( Also assumes the timezone used
    488473                           is UTC since C time handling functions suck. */
    489                         if (strptime(child->text, TWITTER_TIME_FORMAT, &parsed) != NULL)
     474                        if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL)
    490475                                txs->created_at = mktime_utc(&parsed);
    491                 } else if (g_strcasecmp("user", child->name) == 0) {
    492                         txs->user = g_new0(struct twitter_xml_user, 1);
    493                         twitter_xt_get_user(child, txs->user);
    494                 } else if (g_strcasecmp("id", child->name) == 0) {
    495                         txs->id = g_ascii_strtoull(child->text, NULL, 10);
    496                 } else if (g_strcasecmp("in_reply_to_status_id", child->name) == 0) {
    497                         txs->reply_to = g_ascii_strtoull(child->text, NULL, 10);
     476                } else if (strcmp("user", k) == 0 && v->type == json_object) {
     477                        txs->user = twitter_xt_get_user(v);
     478                } else if (strcmp("id", k) == 0 && v->type == json_integer) {
     479                        txs->rt_id = txs->id = v->u.integer;
     480                } else if (strcmp("in_reply_to_status_id", k) == 0 && v->type == json_integer) {
     481                        txs->reply_to = v->u.integer;
     482                } else if (strcmp("entities", k) == 0 && v->type == json_object) {
     483                        entities = v;
    498484                }
    499485        }
     
    502488           wasn't truncated because it may be lying. */
    503489        if (rt) {
    504                 struct twitter_xml_status *rtxs = g_new0(struct twitter_xml_status, 1);
    505                 if (twitter_xt_get_status(rt, rtxs) != XT_HANDLED) {
     490                struct twitter_xml_status *rtxs = twitter_xt_get_status(rt);
     491                if (rtxs) {
     492                        g_free(txs->text);
     493                        txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text);
     494                        txs->id = rtxs->id;
    506495                        txs_free(rtxs);
    507                         return XT_HANDLED;
    508                 }
    509 
    510                 g_free(txs->text);
    511                 txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text);
    512                 txs_free(rtxs);
    513         } else {
    514                 struct xt_node *urls, *url;
     496                }
     497        } else if (entities) {
     498                txs->text = expand_entities(txs->text, entities);
     499        }
     500
     501        if (txs->text && txs->user && txs->id)
     502                return txs;
     503       
     504        txs_free(txs);
     505        return NULL;
     506}
     507
     508/**
     509 * Function to fill a twitter_xml_status struct (DM variant).
     510 */
     511static struct twitter_xml_status *twitter_xt_get_dm(const json_value *node)
     512{
     513        struct twitter_xml_status *txs;
     514        const json_value *entities = NULL;
     515       
     516        if (node->type != json_object)
     517                return FALSE;
     518        txs = g_new0(struct twitter_xml_status, 1);
     519
     520        JSON_O_FOREACH (node, k, v) {
     521                if (strcmp("text", k) == 0 && v->type == json_string) {
     522                        txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1);
     523                        strip_html(txs->text);
     524                } else if (strcmp("created_at", k) == 0 && v->type == json_string) {
     525                        struct tm parsed;
     526
     527                        /* Very sensitive to changes to the formatting of
     528                           this field. :-( Also assumes the timezone used
     529                           is UTC since C time handling functions suck. */
     530                        if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL)
     531                                txs->created_at = mktime_utc(&parsed);
     532                } else if (strcmp("sender", k) == 0 && v->type == json_object) {
     533                        txs->user = twitter_xt_get_user(v);
     534                } else if (strcmp("id", k) == 0 && v->type == json_integer) {
     535                        txs->id = v->u.integer;
     536                }
     537        }
     538
     539        if (entities) {
     540                txs->text = expand_entities(txs->text, entities);
     541        }
     542
     543        if (txs->text && txs->user && txs->id)
     544                return txs;
     545       
     546        txs_free(txs);
     547        return NULL;
     548}
     549
     550static char* expand_entities(char* text, const json_value *entities) {
     551        JSON_O_FOREACH (entities, k, v) {
     552                int i;
    515553               
    516                 urls = xt_find_path(node, "entities");
    517                 if (urls != NULL)
    518                         urls = urls->children;
    519                 for (; urls; urls = urls->next) {
    520                         if (strcmp(urls->name, "urls") != 0 && strcmp(urls->name, "media") != 0)
     554                if (v->type != json_array)
     555                        continue;
     556                if (strcmp(k, "urls") != 0 && strcmp(k, "media") != 0)
     557                        continue;
     558               
     559                for (i = 0; i < v->u.array.length; i ++) {
     560                        if (v->u.array.values[i]->type != json_object)
    521561                                continue;
    522562                       
    523                         for (url = urls ? urls->children : NULL; url; url = url->next) {
    524                                 /* "short" is a reserved word. :-P */
    525                                 struct xt_node *kort = xt_find_node(url->children, "url");
    526                                 struct xt_node *disp = xt_find_node(url->children, "display_url");
    527                                 char *pos, *new;
    528                                
    529                                 if (!kort || !kort->text || !disp || !disp->text ||
    530                                     !(pos = strstr(txs->text, kort->text)))
    531                                         continue;
    532                                
    533                                 *pos = '\0';
    534                                 new = g_strdup_printf("%s%s &lt;%s&gt;%s", txs->text, kort->text,
    535                                                       disp->text, pos + strlen(kort->text));
    536                                
    537                                 g_free(txs->text);
    538                                 txs->text = new;
    539                         }
    540                 }
    541         }
    542 
    543         return XT_HANDLED;
     563                        const char *kort = json_o_str(v->u.array.values[i], "url");
     564                        const char *disp = json_o_str(v->u.array.values[i], "display_url");
     565                        char *pos, *new;
     566                       
     567                        if (!kort || !disp || !(pos = strstr(text, kort)))
     568                                continue;
     569                       
     570                        *pos = '\0';
     571                        new = g_strdup_printf("%s%s <%s>%s", text, kort,
     572                                              disp, pos + strlen(kort));
     573                       
     574                        g_free(text);
     575                        text = new;
     576                }
     577        }
     578       
     579        return text;
    544580}
    545581
     
    550586 *  - the next_cursor.
    551587 */
    552 static xt_status twitter_xt_get_status_list(struct im_connection *ic, struct xt_node *node,
    553                                             struct twitter_xml_list *txl)
     588static gboolean twitter_xt_get_status_list(struct im_connection *ic, const json_value *node,
     589                                           struct twitter_xml_list *txl)
    554590{
    555591        struct twitter_xml_status *txs;
    556         struct xt_node *child;
    557         bee_user_t *bu;
     592        int i;
    558593
    559594        // Set the type of the list.
    560595        txl->type = TXL_STATUS;
     596       
     597        if (node->type != json_array)
     598                return FALSE;
    561599
    562600        // The root <statuses> node should hold the list of statuses <status>
    563601        // Walk over the nodes children.
    564         for (child = node->children; child; child = child->next) {
    565                 if (g_strcasecmp("status", child->name) == 0) {
    566                         txs = g_new0(struct twitter_xml_status, 1);
    567                         twitter_xt_get_status(child, txs);
    568                         // Put the item in the front of the list.
    569                         txl->list = g_slist_prepend(txl->list, txs);
    570 
    571                         if (txs->user && txs->user->screen_name &&
    572                             (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) {
    573                                 struct twitter_user_data *tud = bu->data;
    574 
    575                                 if (txs->id > tud->last_id) {
    576                                         tud->last_id = txs->id;
    577                                         tud->last_time = txs->created_at;
    578                                 }
    579                         }
    580                 } else if (g_strcasecmp("next_cursor", child->name) == 0) {
    581                         twitter_xt_next_cursor(child, txl);
    582                 }
    583         }
    584 
    585         return XT_HANDLED;
    586 }
    587 
     602        for (i = 0; i < node->u.array.length; i ++) {
     603                txs = twitter_xt_get_status(node->u.array.values[i]);
     604                if (!txs)
     605                        continue;
     606               
     607                txl->list = g_slist_prepend(txl->list, txs);
     608        }
     609
     610        return TRUE;
     611}
     612
     613/* Will log messages either way. Need to keep track of IDs for stream deduping.
     614   Plus, show_ids is on by default and I don't see why anyone would disable it. */
    588615static char *twitter_msg_add_id(struct im_connection *ic,
    589616                                struct twitter_xml_status *txs, const char *prefix)
    590617{
    591618        struct twitter_data *td = ic->proto_data;
    592         char *ret = NULL;
    593 
    594         if (!set_getbool(&ic->acc->set, "show_ids")) {
     619        int reply_to = -1;
     620        bee_user_t *bu;
     621
     622        if (txs->reply_to) {
     623                int i;
     624                for (i = 0; i < TWITTER_LOG_LENGTH; i++)
     625                        if (td->log[i].id == txs->reply_to) {
     626                                reply_to = i;
     627                                break;
     628                        }
     629        }
     630
     631        if (txs->user && txs->user->screen_name &&
     632            (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) {
     633                struct twitter_user_data *tud = bu->data;
     634
     635                if (txs->id > tud->last_id) {
     636                        tud->last_id = txs->id;
     637                        tud->last_time = txs->created_at;
     638                }
     639        }
     640       
     641        td->log_id = (td->log_id + 1) % TWITTER_LOG_LENGTH;
     642        td->log[td->log_id].id = txs->id;
     643        td->log[td->log_id].bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name);
     644       
     645        /* This is all getting hairy. :-( If we RT'ed something ourselves,
     646           remember OUR id instead so undo will work. In other cases, the
     647           original tweet's id should be remembered for deduplicating. */
     648        if (strcmp(txs->user->screen_name, td->user) == 0)
     649                td->log[td->log_id].id = txs->rt_id;
     650       
     651        if (set_getbool(&ic->acc->set, "show_ids")) {
     652                if (reply_to != -1)
     653                        return g_strdup_printf("\002[\002%02x->%02x\002]\002 %s%s",
     654                                               td->log_id, reply_to, prefix, txs->text);
     655                else
     656                        return g_strdup_printf("\002[\002%02x\002]\002 %s%s",
     657                                               td->log_id, prefix, txs->text);
     658        } else {
    595659                if (*prefix)
    596660                        return g_strconcat(prefix, txs->text, NULL);
     
    598662                        return NULL;
    599663        }
    600 
    601         td->log[td->log_id].id = txs->id;
    602         td->log[td->log_id].bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name);
    603         if (txs->reply_to) {
    604                 int i;
    605                 for (i = 0; i < TWITTER_LOG_LENGTH; i++)
    606                         if (td->log[i].id == txs->reply_to) {
    607                                 ret = g_strdup_printf("\002[\002%02d->%02d\002]\002 %s%s",
    608                                                       td->log_id, i, prefix, txs->text);
    609                                 break;
    610                         }
    611         }
    612         if (ret == NULL)
    613                 ret = g_strdup_printf("\002[\002%02d\002]\002 %s%s", td->log_id, prefix, txs->text);
    614         td->log_id = (td->log_id + 1) % TWITTER_LOG_LENGTH;
    615 
    616         return ret;
    617 }
    618 
    619 static void twitter_groupchat_init(struct im_connection *ic)
    620 {
    621         char *name_hint;
     664}
     665
     666/**
     667 * Function that is called to see the statuses in a groupchat window.
     668 */
     669static void twitter_status_show_chat(struct im_connection *ic, struct twitter_xml_status *status)
     670{
     671        struct twitter_data *td = ic->proto_data;
    622672        struct groupchat *gc;
    623         struct twitter_data *td = ic->proto_data;
    624         GSList *l;
    625 
    626         td->timeline_gc = gc = imcb_chat_new(ic, "twitter/timeline");
    627 
    628         name_hint = g_strdup_printf("%s_%s", td->prefix, ic->acc->user);
    629         imcb_chat_name_hint(gc, name_hint);
    630         g_free(name_hint);
    631 
    632         for (l = ic->bee->users; l; l = l->next) {
    633                 bee_user_t *bu = l->data;
    634                 if (bu->ic == ic)
    635                         imcb_chat_add_buddy(td->timeline_gc, bu->handle);
    636         }
    637 }
    638 
    639 /**
    640  * Function that is called to see the statuses in a groupchat window.
    641  */
    642 static void twitter_groupchat(struct im_connection *ic, GSList * list)
    643 {
    644         struct twitter_data *td = ic->proto_data;
    645         GSList *l = NULL;
    646         struct twitter_xml_status *status;
    647         struct groupchat *gc;
    648         guint64 last_id = 0;
     673        gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0;
     674        char *msg;
    649675
    650676        // Create a new groupchat if it does not exsist.
    651         if (!td->timeline_gc)
    652                 twitter_groupchat_init(ic);
    653 
    654         gc = td->timeline_gc;
    655         if (!gc->joined)
    656                 imcb_chat_add_buddy(gc, ic->acc->user);
    657 
    658         for (l = list; l; l = g_slist_next(l)) {
    659                 char *msg;
    660 
    661                 status = l->data;
    662                 if (status->user == NULL || status->text == NULL || last_id == status->id)
    663                         continue;
    664 
    665                 last_id = status->id;
    666 
    667                 strip_html(status->text);
    668 
    669                 if (set_getbool(&ic->acc->set, "strip_newlines"))
    670                         strip_newlines(status->text);
    671 
    672                 msg = twitter_msg_add_id(ic, status, "");
    673 
    674                 // Say it!
    675                 if (g_strcasecmp(td->user, status->user->screen_name) == 0) {
    676                         imcb_chat_log(gc, "You: %s", msg ? msg : status->text);
    677                 } else {
    678                         twitter_add_buddy(ic, status->user->screen_name, status->user->name);
    679 
    680                         imcb_chat_msg(gc, status->user->screen_name,
    681                                       msg ? msg : status->text, 0, status->created_at);
    682                 }
    683 
    684                 g_free(msg);
    685 
    686                 // Update the timeline_id to hold the highest id, so that by the next request
    687                 // we won't pick up the updates already in the list.
    688                 td->timeline_id = MAX(td->timeline_id, status->id);
    689         }
     677        gc = twitter_groupchat_init(ic);
     678
     679        if (!me)
     680                /* MUST be done before twitter_msg_add_id() to avoid #872. */
     681                twitter_add_buddy(ic, status->user->screen_name, status->user->name);
     682        msg = twitter_msg_add_id(ic, status, "");
     683       
     684        // Say it!
     685        if (me) {
     686                imcb_chat_log(gc, "You: %s", msg ? msg : status->text);
     687        } else {
     688                imcb_chat_msg(gc, status->user->screen_name,
     689                              msg ? msg : status->text, 0, status->created_at);
     690        }
     691
     692        g_free(msg);
    690693}
    691694
     
    693696 * Function that is called to see statuses as private messages.
    694697 */
    695 static void twitter_private_message_chat(struct im_connection *ic, GSList * list)
    696 {
    697         struct twitter_data *td = ic->proto_data;
    698         GSList *l = NULL;
    699         struct twitter_xml_status *status;
    700         char from[MAX_STRING];
    701         gboolean mode_one;
    702         guint64 last_id = 0;
    703 
    704         mode_one = g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "one") == 0;
    705 
    706         if (mode_one) {
     698static void twitter_status_show_msg(struct im_connection *ic, struct twitter_xml_status *status)
     699{
     700        struct twitter_data *td = ic->proto_data;
     701        char from[MAX_STRING] = "";
     702        char *prefix = NULL, *text = NULL;
     703        gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0;
     704
     705        if (td->flags & TWITTER_MODE_ONE) {
    707706                g_snprintf(from, sizeof(from) - 1, "%s_%s", td->prefix, ic->acc->user);
    708707                from[MAX_STRING - 1] = '\0';
    709708        }
    710709
    711         for (l = list; l; l = g_slist_next(l)) {
    712                 char *prefix = NULL, *text = NULL;
    713 
    714                 status = l->data;
    715                 if (status->user == NULL || status->text == NULL || last_id == status->id)
    716                         continue;
    717 
    718                 last_id = status->id;
    719 
    720                 strip_html(status->text);
    721                 if (mode_one)
    722                         prefix = g_strdup_printf("\002<\002%s\002>\002 ",
    723                                                  status->user->screen_name);
    724                 else
    725                         twitter_add_buddy(ic, status->user->screen_name, status->user->name);
    726 
    727                 text = twitter_msg_add_id(ic, status, prefix ? prefix : "");
    728 
    729                 imcb_buddy_msg(ic,
    730                                mode_one ? from : status->user->screen_name,
    731                                text ? text : status->text, 0, status->created_at);
    732 
    733                 // Update the timeline_id to hold the highest id, so that by the next request
    734                 // we won't pick up the updates already in the list.
    735                 td->timeline_id = MAX(td->timeline_id, status->id);
    736 
    737                 g_free(text);
    738                 g_free(prefix);
    739         }
    740 }
    741 
    742 static void twitter_http_get_home_timeline(struct http_request *req);
    743 static void twitter_http_get_mentions(struct http_request *req);
     710        if (td->flags & TWITTER_MODE_ONE)
     711                prefix = g_strdup_printf("\002<\002%s\002>\002 ",
     712                                         status->user->screen_name);
     713        else if (!me)
     714                twitter_add_buddy(ic, status->user->screen_name, status->user->name);
     715        else
     716                prefix = g_strdup("You: ");
     717
     718        text = twitter_msg_add_id(ic, status, prefix ? prefix : "");
     719
     720        imcb_buddy_msg(ic,
     721                       *from ? from : status->user->screen_name,
     722                       text ? text : status->text, 0, status->created_at);
     723
     724        g_free(text);
     725        g_free(prefix);
     726}
     727
     728static void twitter_status_show(struct im_connection *ic, struct twitter_xml_status *status)
     729{
     730        struct twitter_data *td = ic->proto_data;
     731       
     732        if (status->user == NULL || status->text == NULL)
     733                return;
     734       
     735        /* Grrrr. Would like to do this during parsing, but can't access
     736           settings from there. */
     737        if (set_getbool(&ic->acc->set, "strip_newlines"))
     738                strip_newlines(status->text);
     739       
     740        if (td->flags & TWITTER_MODE_CHAT)
     741                twitter_status_show_chat(ic, status);
     742        else
     743                twitter_status_show_msg(ic, status);
     744
     745        // Update the timeline_id to hold the highest id, so that by the next request
     746        // we won't pick up the updates already in the list.
     747        td->timeline_id = MAX(td->timeline_id, status->rt_id);
     748}
     749
     750static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o);
     751
     752static void twitter_http_stream(struct http_request *req)
     753{
     754        struct im_connection *ic = req->data;
     755        struct twitter_data *td;
     756        json_value *parsed;
     757        int len = 0;
     758        char c, *nl;
     759       
     760        if (!g_slist_find(twitter_connections, ic))
     761                return;
     762       
     763        ic->flags |= OPT_PONGED;
     764        td = ic->proto_data;
     765       
     766        if ((req->flags & HTTPC_EOF) || !req->reply_body) {
     767                td->stream = NULL;
     768                imcb_error(ic, "Stream closed (%s)", req->status_string);
     769                imc_logout(ic, TRUE);
     770                return;
     771        }
     772       
     773        printf( "%d bytes in stream\n", req->body_size );
     774       
     775        /* MUST search for CRLF, not just LF:
     776           https://dev.twitter.com/docs/streaming-apis/processing#Parsing_responses */
     777        nl = strstr(req->reply_body, "\r\n");
     778       
     779        if (!nl) {
     780                printf("Incomplete data\n");
     781                return;
     782        }
     783       
     784        len = nl - req->reply_body;
     785        if (len > 0) {
     786                c = req->reply_body[len];
     787                req->reply_body[len] = '\0';
     788               
     789                printf("JSON: %s\n", req->reply_body);
     790                printf("parsed: %p\n", (parsed = json_parse(req->reply_body)));
     791                if (parsed) {
     792                        twitter_stream_handle_object(ic, parsed);
     793                }
     794                json_value_free(parsed);
     795                req->reply_body[len] = c;
     796        }
     797       
     798        http_flush_bytes(req, len + 2);
     799       
     800        /* One notification might bring multiple events! */
     801        if (req->body_size > 0)
     802                twitter_http_stream(req);
     803}
     804
     805static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o);
     806static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs);
     807
     808static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o)
     809{
     810        struct twitter_data *td = ic->proto_data;
     811        struct twitter_xml_status *txs;
     812        json_value *c;
     813       
     814        if ((txs = twitter_xt_get_status(o))) {
     815                gboolean ret = twitter_stream_handle_status(ic, txs);
     816                txs_free(txs);
     817                return ret;
     818        } else if ((c = json_o_get(o, "direct_message")) &&
     819                   (txs = twitter_xt_get_dm(c))) {
     820                if (strcmp(txs->user->screen_name, td->user) != 0)
     821                        imcb_buddy_msg(ic, txs->user->screen_name,
     822                                       txs->text, 0, txs->created_at);
     823                txs_free(txs);
     824                return TRUE;
     825        } else if ((c = json_o_get(o, "event")) && c->type == json_string) {
     826                twitter_stream_handle_event(ic, o);
     827                return TRUE;
     828        } else if ((c = json_o_get(o, "disconnect")) && c->type == json_object) {
     829                /* HACK: Because we're inside an event handler, we can't just
     830                   disconnect here. Instead, just change the HTTP status string
     831                   into a Twitter status string. */
     832                char *reason = json_o_strdup(c, "reason");
     833                if (reason) {
     834                        g_free(td->stream->status_string);
     835                        td->stream->status_string = reason;
     836                }
     837                return TRUE;
     838        }
     839        return FALSE;
     840}
     841
     842static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs)
     843{
     844        struct twitter_data *td = ic->proto_data;
     845        int i;
     846       
     847        for (i = 0; i < TWITTER_LOG_LENGTH; i++) {
     848                if (td->log[i].id == txs->id) {
     849                        /* Got a duplicate (RT, probably). Drop it. */
     850                        return TRUE;
     851                }
     852        }
     853       
     854        if (!(strcmp(txs->user->screen_name, td->user) == 0 ||
     855              set_getbool(&ic->acc->set, "fetch_mentions") ||
     856              bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) {
     857                /* Tweet is from an unknown person and the user does not want
     858                   to see @mentions, so drop it. twitter_stream_handle_event()
     859                   picks up new follows so this simple filter should be safe. */
     860                /* TODO: The streaming API seems to do poor @mention matching.
     861                   I.e. I'm getting mentions for @WilmerSomething, not just for
     862                   @Wilmer. But meh. You want spam, you get spam. */
     863                return TRUE;
     864        }
     865       
     866        twitter_status_show(ic, txs);
     867       
     868        return TRUE;
     869}
     870
     871static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o)
     872{
     873        struct twitter_data *td = ic->proto_data;
     874        json_value *source = json_o_get(o, "source");
     875        json_value *target = json_o_get(o, "target");
     876        const char *type = json_o_str(o, "event");
     877       
     878        if (!type || !source || source->type != json_object
     879                  || !target || target->type != json_object) {
     880                return FALSE;
     881        }
     882       
     883        if (strcmp(type, "follow") == 0) {
     884                struct twitter_xml_user *us = twitter_xt_get_user(source);
     885                struct twitter_xml_user *ut = twitter_xt_get_user(target);
     886                if (strcmp(us->screen_name, td->user) == 0) {
     887                        twitter_add_buddy(ic, ut->screen_name, ut->name);
     888                }
     889                txu_free(us);
     890                txu_free(ut);
     891        }
     892       
     893        return TRUE;
     894}
     895
     896gboolean twitter_open_stream(struct im_connection *ic)
     897{
     898        struct twitter_data *td = ic->proto_data;
     899        char *args[2] = {"with", "followings"};
     900       
     901        if ((td->stream = twitter_http(ic, TWITTER_USER_STREAM_URL,
     902                                       twitter_http_stream, ic, 0, args, 2))) {
     903                /* This flag must be enabled or we'll get no data until EOF
     904                   (which err, kind of, defeats the purpose of a streaming API). */
     905                td->stream->flags |= HTTPC_STREAMING;
     906                return TRUE;
     907        }
     908       
     909        return FALSE;
     910}
     911
     912static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor);
     913static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor);
    744914
    745915/**
     
    778948        struct twitter_xml_list *home_timeline = td->home_timeline_obj;
    779949        struct twitter_xml_list *mentions = td->mentions_obj;
     950        guint64 last_id = 0;
    780951        GSList *output = NULL;
    781952        GSList *l;
    782953
     954        imcb_connected(ic);
     955       
    783956        if (!(td->flags & TWITTER_GOT_TIMELINE)) {
    784957                return;
     
    804977                }
    805978        }
    806        
    807         if (!(ic->flags & OPT_LOGGED_IN))
    808                 imcb_connected(ic);
    809979
    810980        // See if the user wants to see the messages in a groupchat window or as private messages.
    811         if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0)
    812                 twitter_groupchat(ic, output);
    813         else
    814                 twitter_private_message_chat(ic, output);
    815 
    816         g_slist_free(output);
     981        while (output) {
     982                struct twitter_xml_status *txs = output->data;
     983                if (txs->id != last_id)
     984                        twitter_status_show(ic, txs);
     985                last_id = txs->id;
     986                output = g_slist_remove(output, txs);
     987        }
    817988
    818989        txl_free(home_timeline);
     
    823994}
    824995
     996static void twitter_http_get_home_timeline(struct http_request *req);
     997static void twitter_http_get_mentions(struct http_request *req);
     998
    825999/**
    8261000 * Get the timeline.
    8271001 */
    828 void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
     1002static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
    8291003{
    8301004        struct twitter_data *td = ic->proto_data;
     
    8621036 * Get mentions.
    8631037 */
    864 void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)
     1038static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)
    8651039{
    8661040        struct twitter_data *td = ic->proto_data;
     
    8931067
    8941068        g_free(args[1]);
    895         if (td->timeline_id) {
    896                 g_free(args[5]);
    897         }
     1069        g_free(args[5]);
    8981070}
    8991071
     
    9051077        struct im_connection *ic = req->data;
    9061078        struct twitter_data *td;
    907         struct xt_node *parsed;
     1079        json_value *parsed;
    9081080        struct twitter_xml_list *txl;
    9091081
     
    9211093                goto end;
    9221094        twitter_xt_get_status_list(ic, parsed, txl);
    923         xt_free_node(parsed);
     1095        json_value_free(parsed);
    9241096
    9251097        td->home_timeline_obj = txl;
    9261098
    9271099      end:
     1100        if (!g_slist_find(twitter_connections, ic))
     1101                return;
     1102
    9281103        td->flags |= TWITTER_GOT_TIMELINE;
    9291104
     
    9381113        struct im_connection *ic = req->data;
    9391114        struct twitter_data *td;
    940         struct xt_node *parsed;
     1115        json_value *parsed;
    9411116        struct twitter_xml_list *txl;
    9421117
     
    9541129                goto end;
    9551130        twitter_xt_get_status_list(ic, parsed, txl);
    956         xt_free_node(parsed);
     1131        json_value_free(parsed);
    9571132
    9581133        td->mentions_obj = txl;
    9591134
    9601135      end:
     1136        if (!g_slist_find(twitter_connections, ic))
     1137                return;
     1138
    9611139        td->flags |= TWITTER_GOT_MENTIONS;
    9621140
     
    9721150        struct im_connection *ic = req->data;
    9731151        struct twitter_data *td;
    974         struct xt_node *parsed, *node;
     1152        json_value *parsed, *id;
    9751153
    9761154        // Check if the connection is still active.
     
    9841162                return;
    9851163       
    986         if ((node = xt_find_node(parsed, "status")) &&
    987             (node = xt_find_node(node->children, "id")) && node->text)
    988                 td->last_status_id = g_ascii_strtoull(node->text, NULL, 10);
     1164        if ((id = json_o_get(parsed, "id")) && id->type == json_integer) {
     1165                td->last_status_id = id->u.integer;
     1166        }
     1167       
     1168        json_value_free(parsed);
     1169       
     1170        if (req->flags & TWITTER_HTTP_USER_ACK)
     1171                twitter_log(ic, "Command processed successfully");
    9891172}
    9901173
     
    10321215        char *url;
    10331216        url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_DESTROY_URL,
    1034                               (unsigned long long) id, ".xml");
    1035         twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);
     1217                              (unsigned long long) id, ".json");
     1218        twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0,
     1219                       TWITTER_HTTP_USER_ACK);
    10361220        g_free(url);
    10371221}
     
    10411225        char *url;
    10421226        url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_RETWEET_URL,
    1043                               (unsigned long long) id, ".xml");
    1044         twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);
     1227                              (unsigned long long) id, ".json");
     1228        twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0,
     1229                       TWITTER_HTTP_USER_ACK);
    10451230        g_free(url);
    10461231}
     
    10561241        };
    10571242        args[1] = screen_name;
    1058         twitter_http(ic, TWITTER_REPORT_SPAM_URL, twitter_http_post,
    1059                      ic, 1, args, 2);
     1243        twitter_http_f(ic, TWITTER_REPORT_SPAM_URL, twitter_http_post,
     1244                       ic, 1, args, 2, TWITTER_HTTP_USER_ACK);
    10601245}
    10611246
     
    10671252        char *url;
    10681253        url = g_strdup_printf("%s%llu%s", TWITTER_FAVORITE_CREATE_URL,
    1069                               (unsigned long long) id, ".xml");
    1070         twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);
     1254                              (unsigned long long) id, ".json");
     1255        twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0,
     1256                       TWITTER_HTTP_USER_ACK);
    10711257        g_free(url);
    10721258}
Note: See TracChangeset for help on using the changeset viewer.