Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • protocols/twitter/twitter_lib.c

    r573e274 r2a6da96  
    3535#include "misc.h"
    3636#include "base64.h"
     37#include "xmltree.h"
    3738#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, rt_id; /* Usually equal, with RTs id == *original* id */
    70         guint64 reply_to;
     69        guint64 id, reply_to;
    7170};
     71
     72static void twitter_groupchat_init(struct im_connection *ic);
    7273
    7374/**
     
    147148        // Check if the buddy is already in the buddy list.
    148149        if (!bee_user_by_handle(ic->bee, ic, name)) {
     150                char *mode = set_getstr(&ic->acc->set, "mode");
     151
    149152                // The buddy is not in the list, add the buddy and set the status to logged in.
    150153                imcb_add_buddy(ic, name, NULL);
    151154                imcb_rename_buddy(ic, name, fullname);
    152                 if (td->flags & TWITTER_MODE_CHAT) {
     155                if (g_strcasecmp(mode, "chat") == 0) {
    153156                        /* Necessary so that nicks always get translated to the
    154157                           exact Twitter username. */
    155158                        imcb_buddy_nick_hint(ic, name, name);
    156                         if (td->timeline_gc)
    157                                 imcb_chat_add_buddy(td->timeline_gc, name);
    158                 } else if (td->flags & TWITTER_MODE_MANY)
     159                        imcb_chat_add_buddy(td->timeline_gc, name);
     160                } else if (g_strcasecmp(mode, "many") == 0)
    159161                        imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL);
    160162        }
     
    166168{
    167169        static char *ret = NULL;
    168         json_value *root, *err;
     170        struct xt_node *root, *node, *err;
    169171
    170172        g_free(ret);
     
    172174
    173175        if (req->body_size > 0) {
    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);
     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);
    183185        }
    184186
     
    186188}
    187189
    188 /* WATCH OUT: This function might or might not destroy your connection.
    189    Sub-optimal indeed, but just be careful when this returns NULL! */
    190 static json_value *twitter_parse_response(struct im_connection *ic, struct http_request *req)
     190static struct xt_node *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         json_value *ret;
     195        struct xt_node *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 (%s)",
    214                                twitter_parse_error(req));
     213                imcb_error(ic, "Authentication failure");
    215214                imc_logout(ic, FALSE);
    216215                return NULL;
     
    218217                // It didn't go well, output the error and return.
    219218                if (!periodic || logging_in || ++td->http_fails >= 5)
    220                         twitter_log(ic, "Error: Could not retrieve %s: %s",
    221                                     path, twitter_parse_error(req));
     219                        imcb_error(ic, "Could not retrieve %s: %s",
     220                                   path, twitter_parse_error(req));
    222221               
    223222                if (logging_in)
     
    228227        }
    229228
    230         if ((ret = json_parse(req->reply_body)) == NULL) {
     229        if ((ret = xt_from_string(req->reply_body, req->body_size)) == NULL) {
    231230                imcb_error(ic, "Could not retrieve %s: %s",
    232231                           path, "XML parse error");
     
    252251
    253252/**
     253 * Function to help fill a list.
     254 */
     255static 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/**
    254268 * Fill a list of ids.
    255269 */
    256 static gboolean twitter_xt_get_friends_id_list(json_value *node, struct twitter_xml_list *txl)
    257 {
    258         json_value *c;
    259         int i;
     270static xt_status twitter_xt_get_friends_id_list(struct xt_node *node, struct twitter_xml_list *txl)
     271{
     272        struct xt_node *child;
    260273
    261274        // Set the list type.
    262275        txl->type = TXL_ID;
    263276
    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;
     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;
    283292}
    284293
     
    291300{
    292301        struct im_connection *ic;
    293         json_value *parsed;
     302        struct xt_node *parsed;
    294303        struct twitter_xml_list *txl;
    295304        struct twitter_data *td;
     
    303312        td = ic->proto_data;
    304313
     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
    305318        txl = g_new0(struct twitter_xml_list, 1);
    306319        txl->list = td->follow_ids;
     
    309322        if (!(parsed = twitter_parse_response(ic, req)))
    310323                return;
    311        
    312324        twitter_xt_get_friends_id_list(parsed, txl);
    313         json_value_free(parsed);
     325        xt_free_node(parsed);
    314326
    315327        td->follow_ids = txl->list;
     
    326338}
    327339
    328 static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl);
     340static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl);
    329341static void twitter_http_get_users_lookup(struct http_request *req);
    330342
     
    367379{
    368380        struct im_connection *ic = req->data;
    369         json_value *parsed;
     381        struct xt_node *parsed;
    370382        struct twitter_xml_list *txl;
    371383        GSList *l = NULL;
     
    383395                return;
    384396        twitter_xt_get_users(parsed, txl);
    385         json_value_free(parsed);
     397        xt_free_node(parsed);
    386398
    387399        // Add the users as buddies.
     
    397409}
    398410
    399 struct 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;
     411/**
     412 * Function to fill a twitter_xml_user struct.
     413 * It sets:
     414 *  - the name and
     415 *  - the screen_name.
     416 */
     417static 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;
    408430}
    409431
     
    413435 *  - all <user>s from the <users> element.
    414436 */
    415 static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl)
     437static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl)
    416438{
    417439        struct twitter_xml_user *txu;
    418         int i;
     440        struct xt_node *child;
    419441
    420442        // Set the type of the list.
    421443        txl->type = TXL_USER;
    422444
    423         if (!node || node->type != json_array)
    424                 return FALSE;
    425 
    426445        // The root <users> node should hold the list of users <user>
    427446        // Walk over the nodes children.
    428         for (i = 0; i < node->u.array.length; i ++) {
    429                 txu = twitter_xt_get_user(node->u.array.values[i]);
    430                 if (txu)
     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.
    431452                        txl->list = g_slist_prepend(txl->list, txu);
    432         }
    433 
    434         return TRUE;
     453                }
     454        }
     455
     456        return XT_HANDLED;
    435457}
    436458
     
    440462#define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S +0000 %Y"
    441463#endif
    442 
    443 static char* expand_entities(char* text, const json_value *entities);
    444464
    445465/**
     
    451471 *  - the user in a twitter_xml_user struct.
    452472 */
    453 static 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) {
     473static 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) {
    469484                        struct tm parsed;
    470485
     
    472487                           this field. :-( Also assumes the timezone used
    473488                           is UTC since C time handling functions suck. */
    474                         if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL)
     489                        if (strptime(child->text, TWITTER_TIME_FORMAT, &parsed) != NULL)
    475490                                txs->created_at = mktime_utc(&parsed);
    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;
     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);
    484498                }
    485499        }
     
    488502           wasn't truncated because it may be lying. */
    489503        if (rt) {
    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;
     504                struct twitter_xml_status *rtxs = g_new0(struct twitter_xml_status, 1);
     505                if (twitter_xt_get_status(rt, rtxs) != XT_HANDLED) {
    495506                        txs_free(rtxs);
    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  */
    511 static 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 
    550 static char* expand_entities(char* text, const json_value *entities) {
    551         JSON_O_FOREACH (entities, k, v) {
    552                 int i;
     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;
    553515               
    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)
     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)
    561521                                continue;
    562522                       
    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;
     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;
    580544}
    581545
     
    586550 *  - the next_cursor.
    587551 */
    588 static gboolean twitter_xt_get_status_list(struct im_connection *ic, const json_value *node,
    589                                            struct twitter_xml_list *txl)
     552static xt_status twitter_xt_get_status_list(struct im_connection *ic, struct xt_node *node,
     553                                            struct twitter_xml_list *txl)
    590554{
    591555        struct twitter_xml_status *txs;
    592         int i;
     556        struct xt_node *child;
     557        bee_user_t *bu;
    593558
    594559        // Set the type of the list.
    595560        txl->type = TXL_STATUS;
    596        
    597         if (node->type != json_array)
    598                 return FALSE;
    599561
    600562        // The root <statuses> node should hold the list of statuses <status>
    601563        // Walk over the nodes children.
    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. */
     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
    615588static char *twitter_msg_add_id(struct im_connection *ic,
    616589                                struct twitter_xml_status *txs, const char *prefix)
    617590{
    618591        struct twitter_data *td = ic->proto_data;
    619         int reply_to = -1;
    620         bee_user_t *bu;
    621 
     592        char *ret = NULL;
     593
     594        if (!set_getbool(&ic->acc->set, "show_ids")) {
     595                if (*prefix)
     596                        return g_strconcat(prefix, txs->text, NULL);
     597                else
     598                        return NULL;
     599        }
     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);
    622603        if (txs->reply_to) {
    623604                int i;
    624605                for (i = 0; i < TWITTER_LOG_LENGTH; i++)
    625606                        if (td->log[i].id == txs->reply_to) {
    626                                 reply_to = i;
     607                                ret = g_strdup_printf("\002[\002%02d->%02d\002]\002 %s%s",
     608                                                      td->log_id, i, prefix, txs->text);
    627609                                break;
    628610                        }
    629611        }
    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        
     612        if (ret == NULL)
     613                ret = g_strdup_printf("\002[\002%02d\002]\002 %s%s", td->log_id, prefix, txs->text);
    641614        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 {
    659                 if (*prefix)
    660                         return g_strconcat(prefix, txs->text, NULL);
    661                 else
    662                         return NULL;
     615
     616        return ret;
     617}
     618
     619static void twitter_groupchat_init(struct im_connection *ic)
     620{
     621        char *name_hint;
     622        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);
    663636        }
    664637}
     
    667640 * Function that is called to see the statuses in a groupchat window.
    668641 */
    669 static void twitter_status_show_chat(struct im_connection *ic, struct twitter_xml_status *status)
     642static void twitter_groupchat(struct im_connection *ic, GSList * list)
    670643{
    671644        struct twitter_data *td = ic->proto_data;
     645        GSList *l = NULL;
     646        struct twitter_xml_status *status;
    672647        struct groupchat *gc;
    673         gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0;
    674         char *msg;
     648        guint64 last_id = 0;
    675649
    676650        // Create a new groupchat if it does not exsist.
    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);
     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        }
    693690}
    694691
     
    696693 * Function that is called to see statuses as private messages.
    697694 */
    698 static void twitter_status_show_msg(struct im_connection *ic, struct twitter_xml_status *status)
     695static void twitter_private_message_chat(struct im_connection *ic, GSList * list)
    699696{
    700697        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) {
     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) {
    706707                g_snprintf(from, sizeof(from) - 1, "%s_%s", td->prefix, ic->acc->user);
    707708                from[MAX_STRING - 1] = '\0';
    708709        }
    709710
    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 
    728 static 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 
    750 static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o);
    751 
    752 static 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 
    805 static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o);
    806 static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs);
    807 
    808 static 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 
    842 static 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 
    871 static 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 
    896 gboolean 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 
    912 static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor);
    913 static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor);
     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
     742static void twitter_http_get_home_timeline(struct http_request *req);
     743static void twitter_http_get_mentions(struct http_request *req);
    914744
    915745/**
     
    948778        struct twitter_xml_list *home_timeline = td->home_timeline_obj;
    949779        struct twitter_xml_list *mentions = td->mentions_obj;
    950         guint64 last_id = 0;
    951780        GSList *output = NULL;
    952781        GSList *l;
    953782
    954         imcb_connected(ic);
    955        
    956783        if (!(td->flags & TWITTER_GOT_TIMELINE)) {
    957784                return;
     
    977804                }
    978805        }
     806       
     807        if (!(ic->flags & OPT_LOGGED_IN))
     808                imcb_connected(ic);
    979809
    980810        // See if the user wants to see the messages in a groupchat window or as private messages.
    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         }
     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);
    988817
    989818        txl_free(home_timeline);
     
    994823}
    995824
    996 static void twitter_http_get_home_timeline(struct http_request *req);
    997 static void twitter_http_get_mentions(struct http_request *req);
    998 
    999825/**
    1000826 * Get the timeline.
    1001827 */
    1002 static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
     828void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
    1003829{
    1004830        struct twitter_data *td = ic->proto_data;
     
    1036862 * Get mentions.
    1037863 */
    1038 static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)
     864void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)
    1039865{
    1040866        struct twitter_data *td = ic->proto_data;
     
    1067893
    1068894        g_free(args[1]);
    1069         g_free(args[5]);
     895        if (td->timeline_id) {
     896                g_free(args[5]);
     897        }
    1070898}
    1071899
     
    1077905        struct im_connection *ic = req->data;
    1078906        struct twitter_data *td;
    1079         json_value *parsed;
     907        struct xt_node *parsed;
    1080908        struct twitter_xml_list *txl;
    1081909
     
    1093921                goto end;
    1094922        twitter_xt_get_status_list(ic, parsed, txl);
    1095         json_value_free(parsed);
     923        xt_free_node(parsed);
    1096924
    1097925        td->home_timeline_obj = txl;
    1098926
    1099927      end:
    1100         if (!g_slist_find(twitter_connections, ic))
    1101                 return;
    1102 
    1103928        td->flags |= TWITTER_GOT_TIMELINE;
    1104929
     
    1113938        struct im_connection *ic = req->data;
    1114939        struct twitter_data *td;
    1115         json_value *parsed;
     940        struct xt_node *parsed;
    1116941        struct twitter_xml_list *txl;
    1117942
     
    1129954                goto end;
    1130955        twitter_xt_get_status_list(ic, parsed, txl);
    1131         json_value_free(parsed);
     956        xt_free_node(parsed);
    1132957
    1133958        td->mentions_obj = txl;
    1134959
    1135960      end:
    1136         if (!g_slist_find(twitter_connections, ic))
    1137                 return;
    1138 
    1139961        td->flags |= TWITTER_GOT_MENTIONS;
    1140962
     
    1150972        struct im_connection *ic = req->data;
    1151973        struct twitter_data *td;
    1152         json_value *parsed, *id;
     974        struct xt_node *parsed, *node;
    1153975
    1154976        // Check if the connection is still active.
     
    1162984                return;
    1163985       
    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");
     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);
    1172989}
    1173990
     
    12151032        char *url;
    12161033        url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_DESTROY_URL,
    1217                               (unsigned long long) id, ".json");
    1218         twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0,
    1219                        TWITTER_HTTP_USER_ACK);
     1034                              (unsigned long long) id, ".xml");
     1035        twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);
    12201036        g_free(url);
    12211037}
     
    12251041        char *url;
    12261042        url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_RETWEET_URL,
    1227                               (unsigned long long) id, ".json");
    1228         twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0,
    1229                        TWITTER_HTTP_USER_ACK);
     1043                              (unsigned long long) id, ".xml");
     1044        twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);
    12301045        g_free(url);
    12311046}
     
    12411056        };
    12421057        args[1] = screen_name;
    1243         twitter_http_f(ic, TWITTER_REPORT_SPAM_URL, twitter_http_post,
    1244                        ic, 1, args, 2, TWITTER_HTTP_USER_ACK);
     1058        twitter_http(ic, TWITTER_REPORT_SPAM_URL, twitter_http_post,
     1059                     ic, 1, args, 2);
    12451060}
    12461061
     
    12521067        char *url;
    12531068        url = g_strdup_printf("%s%llu%s", TWITTER_FAVORITE_CREATE_URL,
    1254                               (unsigned long long) id, ".json");
    1255         twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0,
    1256                        TWITTER_HTTP_USER_ACK);
     1069                              (unsigned long long) id, ".xml");
     1070        twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);
    12571071        g_free(url);
    12581072}
Note: See TracChangeset for help on using the changeset viewer.