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