Changes in protocols/twitter/twitter_lib.c [2a6da96:573e274]
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
protocols/twitter/twitter_lib.c
r2a6da96 r573e274 35 35 #include "misc.h" 36 36 #include "base64.h" 37 #include "xmltree.h"38 37 #include "twitter_lib.h" 38 #include "json_util.h" 39 39 #include <ctype.h> 40 40 #include <errno.h> … … 67 67 char *text; 68 68 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; 70 71 }; 71 72 static void twitter_groupchat_init(struct im_connection *ic);73 72 74 73 /** … … 148 147 // Check if the buddy is already in the buddy list. 149 148 if (!bee_user_by_handle(ic->bee, ic, name)) { 150 char *mode = set_getstr(&ic->acc->set, "mode");151 152 149 // The buddy is not in the list, add the buddy and set the status to logged in. 153 150 imcb_add_buddy(ic, name, NULL); 154 151 imcb_rename_buddy(ic, name, fullname); 155 if ( g_strcasecmp(mode, "chat") == 0) {152 if (td->flags & TWITTER_MODE_CHAT) { 156 153 /* Necessary so that nicks always get translated to the 157 154 exact Twitter username. */ 158 155 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) 161 159 imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL); 162 160 } … … 168 166 { 169 167 static char *ret = NULL; 170 struct xt_node *root, *node, *err;168 json_value *root, *err; 171 169 172 170 g_free(ret); … … 174 172 175 173 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); 185 183 } 186 184 … … 188 186 } 189 187 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! */ 190 static json_value *twitter_parse_response(struct im_connection *ic, struct http_request *req) 191 191 { 192 192 gboolean logging_in = !(ic->flags & OPT_LOGGED_IN); 193 193 gboolean periodic; 194 194 struct twitter_data *td = ic->proto_data; 195 struct xt_node *ret;195 json_value *ret; 196 196 char path[64] = "", *s; 197 197 … … 211 211 throwing 401s so I'll keep treating this one as fatal 212 212 only during login. */ 213 imcb_error(ic, "Authentication failure"); 213 imcb_error(ic, "Authentication failure (%s)", 214 twitter_parse_error(req)); 214 215 imc_logout(ic, FALSE); 215 216 return NULL; … … 217 218 // It didn't go well, output the error and return. 218 219 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)); 221 222 222 223 if (logging_in) … … 227 228 } 228 229 229 if ((ret = xt_from_string(req->reply_body, req->body_size)) == NULL) {230 if ((ret = json_parse(req->reply_body)) == NULL) { 230 231 imcb_error(ic, "Could not retrieve %s: %s", 231 232 path, "XML parse error"); … … 251 252 252 253 /** 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 /**268 254 * Fill a list of ids. 269 255 */ 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; 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; 273 260 274 261 // Set the list type. 275 262 txl->type = TXL_ID; 276 263 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; 292 283 } 293 284 … … 300 291 { 301 292 struct im_connection *ic; 302 struct xt_node *parsed;293 json_value *parsed; 303 294 struct twitter_xml_list *txl; 304 295 struct twitter_data *td; … … 312 303 td = ic->proto_data; 313 304 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 318 305 txl = g_new0(struct twitter_xml_list, 1); 319 306 txl->list = td->follow_ids; … … 322 309 if (!(parsed = twitter_parse_response(ic, req))) 323 310 return; 311 324 312 twitter_xt_get_friends_id_list(parsed, txl); 325 xt_free_node(parsed);313 json_value_free(parsed); 326 314 327 315 td->follow_ids = txl->list; … … 338 326 } 339 327 340 static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl);328 static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl); 341 329 static void twitter_http_get_users_lookup(struct http_request *req); 342 330 … … 379 367 { 380 368 struct im_connection *ic = req->data; 381 struct xt_node *parsed;369 json_value *parsed; 382 370 struct twitter_xml_list *txl; 383 371 GSList *l = NULL; … … 395 383 return; 396 384 twitter_xt_get_users(parsed, txl); 397 xt_free_node(parsed);385 json_value_free(parsed); 398 386 399 387 // Add the users as buddies. … … 409 397 } 410 398 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; 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; 430 408 } 431 409 … … 435 413 * - all <user>s from the <users> element. 436 414 */ 437 static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl)415 static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl) 438 416 { 439 417 struct twitter_xml_user *txu; 440 struct xt_node *child;418 int i; 441 419 442 420 // Set the type of the list. 443 421 txl->type = TXL_USER; 444 422 423 if (!node || node->type != json_array) 424 return FALSE; 425 445 426 // The root <users> node should hold the list of users <user> 446 427 // 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) 452 431 txl->list = g_slist_prepend(txl->list, txu); 453 } 454 } 455 456 return XT_HANDLED; 432 } 433 434 return TRUE; 457 435 } 458 436 … … 462 440 #define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S +0000 %Y" 463 441 #endif 442 443 static char* expand_entities(char* text, const json_value *entities); 464 444 465 445 /** … … 471 451 * - the user in a twitter_xml_user struct. 472 452 */ 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) { 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) { 484 469 struct tm parsed; 485 470 … … 487 472 this field. :-( Also assumes the timezone used 488 473 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) 490 475 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; 498 484 } 499 485 } … … 502 488 wasn't truncated because it may be lying. */ 503 489 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; 506 495 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 */ 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; 515 553 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) 521 561 continue; 522 562 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 <%s>%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; 544 580 } 545 581 … … 550 586 * - the next_cursor. 551 587 */ 552 static xt_status twitter_xt_get_status_list(struct im_connection *ic, struct xt_node *node,553 588 static gboolean twitter_xt_get_status_list(struct im_connection *ic, const json_value *node, 589 struct twitter_xml_list *txl) 554 590 { 555 591 struct twitter_xml_status *txs; 556 struct xt_node *child; 557 bee_user_t *bu; 592 int i; 558 593 559 594 // Set the type of the list. 560 595 txl->type = TXL_STATUS; 596 597 if (node->type != json_array) 598 return FALSE; 561 599 562 600 // The root <statuses> node should hold the list of statuses <status> 563 601 // 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. */ 588 615 static char *twitter_msg_add_id(struct im_connection *ic, 589 616 struct twitter_xml_status *txs, const char *prefix) 590 617 { 591 618 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 { 595 659 if (*prefix) 596 660 return g_strconcat(prefix, txs->text, NULL); … … 598 662 return NULL; 599 663 } 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 */ 669 static void twitter_status_show_chat(struct im_connection *ic, struct twitter_xml_status *status) 670 { 671 struct twitter_data *td = ic->proto_data; 622 672 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; 649 675 650 676 // 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); 690 693 } 691 694 … … 693 696 * Function that is called to see statuses as private messages. 694 697 */ 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) { 698 static 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) { 707 706 g_snprintf(from, sizeof(from) - 1, "%s_%s", td->prefix, ic->acc->user); 708 707 from[MAX_STRING - 1] = '\0'; 709 708 } 710 709 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 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); 744 914 745 915 /** … … 778 948 struct twitter_xml_list *home_timeline = td->home_timeline_obj; 779 949 struct twitter_xml_list *mentions = td->mentions_obj; 950 guint64 last_id = 0; 780 951 GSList *output = NULL; 781 952 GSList *l; 782 953 954 imcb_connected(ic); 955 783 956 if (!(td->flags & TWITTER_GOT_TIMELINE)) { 784 957 return; … … 804 977 } 805 978 } 806 807 if (!(ic->flags & OPT_LOGGED_IN))808 imcb_connected(ic);809 979 810 980 // 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 } 817 988 818 989 txl_free(home_timeline); … … 823 994 } 824 995 996 static void twitter_http_get_home_timeline(struct http_request *req); 997 static void twitter_http_get_mentions(struct http_request *req); 998 825 999 /** 826 1000 * Get the timeline. 827 1001 */ 828 void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)1002 static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor) 829 1003 { 830 1004 struct twitter_data *td = ic->proto_data; … … 862 1036 * Get mentions. 863 1037 */ 864 void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)1038 static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor) 865 1039 { 866 1040 struct twitter_data *td = ic->proto_data; … … 893 1067 894 1068 g_free(args[1]); 895 if (td->timeline_id) { 896 g_free(args[5]); 897 } 1069 g_free(args[5]); 898 1070 } 899 1071 … … 905 1077 struct im_connection *ic = req->data; 906 1078 struct twitter_data *td; 907 struct xt_node *parsed;1079 json_value *parsed; 908 1080 struct twitter_xml_list *txl; 909 1081 … … 921 1093 goto end; 922 1094 twitter_xt_get_status_list(ic, parsed, txl); 923 xt_free_node(parsed);1095 json_value_free(parsed); 924 1096 925 1097 td->home_timeline_obj = txl; 926 1098 927 1099 end: 1100 if (!g_slist_find(twitter_connections, ic)) 1101 return; 1102 928 1103 td->flags |= TWITTER_GOT_TIMELINE; 929 1104 … … 938 1113 struct im_connection *ic = req->data; 939 1114 struct twitter_data *td; 940 struct xt_node *parsed;1115 json_value *parsed; 941 1116 struct twitter_xml_list *txl; 942 1117 … … 954 1129 goto end; 955 1130 twitter_xt_get_status_list(ic, parsed, txl); 956 xt_free_node(parsed);1131 json_value_free(parsed); 957 1132 958 1133 td->mentions_obj = txl; 959 1134 960 1135 end: 1136 if (!g_slist_find(twitter_connections, ic)) 1137 return; 1138 961 1139 td->flags |= TWITTER_GOT_MENTIONS; 962 1140 … … 972 1150 struct im_connection *ic = req->data; 973 1151 struct twitter_data *td; 974 struct xt_node *parsed, *node;1152 json_value *parsed, *id; 975 1153 976 1154 // Check if the connection is still active. … … 984 1162 return; 985 1163 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"); 989 1172 } 990 1173 … … 1032 1215 char *url; 1033 1216 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); 1036 1220 g_free(url); 1037 1221 } … … 1041 1225 char *url; 1042 1226 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); 1045 1230 g_free(url); 1046 1231 } … … 1056 1241 }; 1057 1242 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); 1060 1245 } 1061 1246 … … 1067 1252 char *url; 1068 1253 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); 1071 1257 g_free(url); 1072 1258 }
Note: See TracChangeset
for help on using the changeset viewer.