Changeset cc6fdf8 for protocols/twitter
- Timestamp:
- 2012-12-22T00:14:26Z (12 years ago)
- Branches:
- master
- Children:
- 7d5afa6
- Parents:
- 92d3044 (diff), 573e274 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the(diff)
links above to see all the changes relative to each parent. - Location:
- protocols/twitter
- Files:
-
- 6 edited
Legend:
- Unmodified
- Added
- Removed
-
protocols/twitter/twitter.c
r92d3044 rcc6fdf8 4 4 * Simple module to facilitate twitter functionality. * 5 5 * * 6 * Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> * 6 * Copyright 2009-2010 Geert Mulders <g.c.w.m.mulders@gmail.com> * 7 * Copyright 2010-2012 Wilmer van der Gaast <wilmer@gaast.net> * 7 8 * * 8 9 * This library is free software; you can redistribute it and/or * … … 29 30 #include "url.h" 30 31 31 #define twitter_msg( ic, fmt... ) \32 do { \33 struct twitter_data *td = ic->proto_data; \34 if( td->timeline_gc ) \35 imcb_chat_log( td->timeline_gc, fmt ); \36 else \37 imcb_log( ic, fmt ); \38 } while( 0 );39 40 32 GSList *twitter_connections = NULL; 41 42 33 /** 43 34 * Main loop function … … 62 53 struct twitter_data *td = ic->proto_data; 63 54 55 /* Create the room now that we "logged in". */ 56 if (td->flags & TWITTER_MODE_CHAT) 57 twitter_groupchat_init(ic); 58 64 59 imcb_log(ic, "Getting initial statuses"); 65 60 66 // Run this once. After this queue the main loop function. 61 // Run this once. After this queue the main loop function (or open the 62 // stream if available). 67 63 twitter_main_loop(ic, -1, 0); 68 69 // Queue the main_loop 70 // Save the return value, so we can remove the timeout on logout. 71 td->main_loop_id = 72 b_timeout_add(set_getint(&ic->acc->set, "fetch_interval") * 1000, twitter_main_loop, ic); 64 65 if (set_getbool(&ic->acc->set, "stream")) { 66 /* That fetch was just to get backlog, the stream will give 67 us the rest. \o/ */ 68 twitter_open_stream(ic); 69 70 /* Stream sends keepalives (empty lines) or actual data at 71 least twice a minute. Disconnect if this stops. */ 72 ic->flags |= OPT_PONGS; 73 } else { 74 /* Not using the streaming API, so keep polling the old- 75 fashioned way. :-( */ 76 td->main_loop_id = 77 b_timeout_add(set_getint(&ic->acc->set, "fetch_interval") * 1000, 78 twitter_main_loop, ic); 79 } 80 } 81 82 struct groupchat *twitter_groupchat_init(struct im_connection *ic) 83 { 84 char *name_hint; 85 struct groupchat *gc; 86 struct twitter_data *td = ic->proto_data; 87 GSList *l; 88 89 if (td->timeline_gc) 90 return td->timeline_gc; 91 92 td->timeline_gc = gc = imcb_chat_new(ic, "twitter/timeline"); 93 94 name_hint = g_strdup_printf("%s_%s", td->prefix, ic->acc->user); 95 imcb_chat_name_hint(gc, name_hint); 96 g_free(name_hint); 97 98 for (l = ic->bee->users; l; l = l->next) { 99 bee_user_t *bu = l->data; 100 if (bu->ic == ic) 101 imcb_chat_add_buddy(gc, bu->handle); 102 } 103 imcb_chat_add_buddy(gc, ic->acc->user); 104 105 return gc; 73 106 } 74 107 … … 83 116 if (set_getbool(&ic->acc->set, "oauth") && !td->oauth_info) 84 117 twitter_oauth_start(ic); 85 else if ( g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "one") != 0&&86 118 else if (!(td->flags & TWITTER_MODE_ONE) && 119 !(td->flags & TWITTER_HAVE_FRIENDS)) { 87 120 imcb_log(ic, "Getting contact list"); 88 121 twitter_get_friends_ids(ic, -1); 89 //twitter_get_statuses_friends(ic, -1);90 122 } else 91 123 twitter_main_loop_start(ic); … … 187 219 } 188 220 189 190 static char *set_eval_mode(set_t * set, char *value)191 {192 if (g_strcasecmp(value, "one") == 0 ||193 g_strcasecmp(value, "many") == 0 || g_strcasecmp(value, "chat") == 0)194 return value;195 else196 return NULL;197 }198 199 221 int twitter_url_len_diff(gchar *msg, unsigned int target_len) 200 222 { … … 233 255 return TRUE; 234 256 235 imcb_error(ic, "Maximum message length exceeded: %d > %d", len, max);257 twitter_log(ic, "Maximum message length exceeded: %d > %d", len, max); 236 258 237 259 return FALSE; 260 } 261 262 static char *set_eval_commands(set_t * set, char *value) 263 { 264 if (g_strcasecmp(value, "strict") == 0 ) 265 return value; 266 else 267 return set_eval_bool(set, value); 268 } 269 270 static char *set_eval_mode(set_t * set, char *value) 271 { 272 if (g_strcasecmp(value, "one") == 0 || 273 g_strcasecmp(value, "many") == 0 || g_strcasecmp(value, "chat") == 0) 274 return value; 275 else 276 return NULL; 238 277 } 239 278 … … 257 296 s->flags |= ACC_SET_OFFLINE_ONLY; 258 297 259 s = set_add(&acc->set, "commands", "true", set_eval_ bool, acc);298 s = set_add(&acc->set, "commands", "true", set_eval_commands, acc); 260 299 261 300 s = set_add(&acc->set, "fetch_interval", "60", set_eval_int, acc); … … 274 313 275 314 s = set_add(&acc->set, "show_ids", "true", set_eval_bool, acc); 276 s->flags |= ACC_SET_OFFLINE_ONLY;277 315 278 316 s = set_add(&acc->set, "show_old_mentions", "20", set_eval_int, acc); 279 317 280 318 s = set_add(&acc->set, "strip_newlines", "false", set_eval_bool, acc); 319 320 if (strcmp(acc->prpl->name, "twitter") == 0) { 321 s = set_add(&acc->set, "stream", "true", set_eval_bool, acc); 322 s->flags |= ACC_SET_OFFLINE_ONLY; 323 } 281 324 } 282 325 283 326 /** 284 * Login method. Since the twitter API works with sep erate HTTP request we327 * Login method. Since the twitter API works with separate HTTP request we 285 328 * only save the user and pass to the twitter_data object. 286 329 */ … … 298 341 imc_logout(ic, FALSE); 299 342 return; 343 } 344 345 if (!strstr(url.host, "twitter.com") && 346 set_getbool(&ic->acc->set, "stream")) { 347 imcb_error(ic, "Warning: The streaming API is only supported by Twitter, " 348 "and you seem to be connecting to a different service."); 300 349 } 301 350 … … 340 389 imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL); 341 390 342 if (set_getbool(&acc->set, "show_ids")) 343 td->log = g_new0(struct twitter_log_data, TWITTER_LOG_LENGTH); 391 td->log = g_new0(struct twitter_log_data, TWITTER_LOG_LENGTH); 392 td->log_id = -1; 393 394 s = set_getstr(&ic->acc->set, "mode"); 395 if (g_strcasecmp(s, "one") == 0) 396 td->flags |= TWITTER_MODE_ONE; 397 else if (g_strcasecmp(s, "many") == 0) 398 td->flags |= TWITTER_MODE_MANY; 399 else 400 td->flags |= TWITTER_MODE_CHAT; 344 401 345 402 twitter_login_finish(ic); … … 363 420 364 421 if (td) { 422 http_close(td->stream); 365 423 oauth_info_free(td->oauth_info); 366 424 g_free(td->user); … … 495 553 * Returns 0 if the user provides garbage. 496 554 */ 497 static guint64 twitter_message_id_from_command_arg(struct im_connection *ic, struct twitter_data *td, char *arg) { 555 static guint64 twitter_message_id_from_command_arg(struct im_connection *ic, char *arg, bee_user_t **bu_) { 556 struct twitter_data *td = ic->proto_data; 498 557 struct twitter_user_data *tud; 499 bee_user_t *bu ;558 bee_user_t *bu = NULL; 500 559 guint64 id = 0; 501 if (g_str_has_prefix(arg, "#") && 502 sscanf(arg + 1, "%" G_GUINT64_FORMAT, &id) == 1) { 503 if (id < TWITTER_LOG_LENGTH && td->log) 560 561 if (bu_) 562 *bu_ = NULL; 563 if (!arg || !arg[0]) 564 return 0; 565 566 if (arg[0] != '#' && (bu = bee_user_by_handle(ic->bee, ic, arg))) { 567 if ((tud = bu->data)) 568 id = tud->last_id; 569 } else { 570 if (arg[0] == '#') 571 arg++; 572 if (sscanf(arg, "%" G_GINT64_MODIFIER "x", &id) == 1 && 573 id < TWITTER_LOG_LENGTH) { 574 bu = td->log[id].bu; 504 575 id = td->log[id].id; 505 } else if ((bu = bee_user_by_handle(ic->bee, ic, arg)) && 506 (tud = bu->data) && tud->last_id) 507 id = tud->last_id; 508 else if (sscanf(arg, "%" G_GUINT64_FORMAT, &id) == 1){ 509 if (id < TWITTER_LOG_LENGTH && td->log) 510 id = td->log[id].id; 511 } 576 /* Beware of dangling pointers! */ 577 if (!g_slist_find(ic->bee->users, bu)) 578 bu = NULL; 579 } else if (sscanf(arg, "%" G_GINT64_MODIFIER "d", &id) == 1) { 580 /* Allow normal tweet IDs as well; not a very useful 581 feature but it's always been there. Just ignore 582 very low IDs to avoid accidents. */ 583 if (id < 1000000) 584 id = 0; 585 } 586 } 587 if (bu_) 588 *bu_ = bu; 512 589 return id; 513 590 } … … 517 594 struct twitter_data *td = ic->proto_data; 518 595 char *cmds, **cmd, *new = NULL; 519 guint64 in_reply_to = 0; 596 guint64 in_reply_to = 0, id; 597 gboolean allow_post = 598 g_strcasecmp(set_getstr(&ic->acc->set, "commands"), "strict") != 0; 599 bee_user_t *bu = NULL; 520 600 521 601 cmds = g_strdup(message); … … 523 603 524 604 if (cmd[0] == NULL) { 525 g_free(cmds); 526 return; 527 } else if (!set_getbool(&ic->acc->set, "commands")) { 528 /* Not supporting commands. */ 605 goto eof; 606 } else if (!set_getbool(&ic->acc->set, "commands") && allow_post) { 607 /* Not supporting commands if "commands" is set to true/strict. */ 529 608 } else if (g_strcasecmp(cmd[0], "undo") == 0) { 530 guint64 id;531 532 609 if (cmd[1] == NULL) 533 610 twitter_status_destroy(ic, td->last_status_id); 534 else if (sscanf(cmd[1], "%" G_GUINT64_FORMAT, &id) == 1) { 535 if (id < TWITTER_LOG_LENGTH && td->log) 536 id = td->log[id].id; 537 611 else if ((id = twitter_message_id_from_command_arg(ic, cmd[1], NULL))) 538 612 twitter_status_destroy(ic, id); 539 } else 540 twitter_msg(ic, "Could not undo last action"); 541 542 g_free(cmds); 543 return; 613 else 614 twitter_log(ic, "Could not undo last action"); 615 616 goto eof; 544 617 } else if (g_strcasecmp(cmd[0], "favourite") == 0 && cmd[1]) { 545 guint64 id; 546 if ((id = twitter_message_id_from_command_arg(ic, td, cmd[1]))) { 618 if ((id = twitter_message_id_from_command_arg(ic, cmd[1], NULL))) { 547 619 twitter_favourite_tweet(ic, id); 548 620 } else { 549 twitter_ msg(ic, "Please provide a message ID or username.");621 twitter_log(ic, "Please provide a message ID or username."); 550 622 } 551 g_free(cmds); 552 return; 623 goto eof; 553 624 } else if (g_strcasecmp(cmd[0], "follow") == 0 && cmd[1]) { 554 625 twitter_add_buddy(ic, cmd[1], NULL); 555 g_free(cmds); 556 return; 626 goto eof; 557 627 } else if (g_strcasecmp(cmd[0], "unfollow") == 0 && cmd[1]) { 558 628 twitter_remove_buddy(ic, cmd[1], NULL); 559 g_free(cmds); 560 return; 629 goto eof; 561 630 } else if ((g_strcasecmp(cmd[0], "report") == 0 || 562 631 g_strcasecmp(cmd[0], "spam") == 0) && cmd[1]) { 563 char * screen_name; 564 guint64 id; 565 screen_name = cmd[1]; 632 char *screen_name; 633 566 634 /* Report nominally works on users but look up the user who 567 635 posted the given ID if the user wants to do it that way */ 568 if (g_str_has_prefix(cmd[1], "#") && 569 sscanf(cmd[1] + 1, "%" G_GUINT64_FORMAT, &id) == 1) { 570 if (id < TWITTER_LOG_LENGTH && td->log) { 571 if (g_slist_find(ic->bee->users, td->log[id].bu)) { 572 screen_name = td->log[id].bu->handle; 573 } 574 } 575 } 636 twitter_message_id_from_command_arg(ic, cmd[1], &bu); 637 if (bu) 638 screen_name = bu->handle; 639 else 640 screen_name = cmd[1]; 641 576 642 twitter_report_spam(ic, screen_name); 577 g_free(cmds); 578 return; 643 goto eof; 579 644 } else if (g_strcasecmp(cmd[0], "rt") == 0 && cmd[1]) { 580 guint64 id = twitter_message_id_from_command_arg(ic, td, cmd[1]);645 id = twitter_message_id_from_command_arg(ic, cmd[1], NULL); 581 646 582 647 td->last_status_id = 0; … … 584 649 twitter_status_retweet(ic, id); 585 650 else 586 twitter_ msg(ic, "User `%s' does not exist or didn't "651 twitter_log(ic, "User `%s' does not exist or didn't " 587 652 "post any statuses recently", cmd[1]); 588 653 589 g_free(cmds); 590 return; 654 goto eof; 591 655 } else if (g_strcasecmp(cmd[0], "reply") == 0 && cmd[1] && cmd[2]) { 592 struct twitter_user_data *tud; 593 bee_user_t *bu = NULL; 594 guint64 id = 0; 595 596 if (g_str_has_prefix(cmd[1], "#") && 597 sscanf(cmd[1] + 1, "%" G_GUINT64_FORMAT, &id) == 1 && 598 (id < TWITTER_LOG_LENGTH) && td->log) { 599 bu = td->log[id].bu; 600 if (g_slist_find(ic->bee->users, bu)) 601 id = td->log[id].id; 602 else 603 bu = NULL; 604 } else if ((bu = bee_user_by_handle(ic->bee, ic, cmd[1])) && 605 (tud = bu->data) && tud->last_id) { 606 id = tud->last_id; 607 } else if (sscanf(cmd[1], "%" G_GUINT64_FORMAT, &id) == 1 && 608 (id < TWITTER_LOG_LENGTH) && td->log) { 609 bu = td->log[id].bu; 610 if (g_slist_find(ic->bee->users, bu)) 611 id = td->log[id].id; 612 else 613 bu = NULL; 614 } 615 656 id = twitter_message_id_from_command_arg(ic, cmd[1], &bu); 616 657 if (!id || !bu) { 617 twitter_ msg(ic, "User `%s' does not exist or didn't "658 twitter_log(ic, "User `%s' does not exist or didn't " 618 659 "post any statuses recently", cmd[1]); 619 g_free(cmds); 620 return; 660 goto eof; 621 661 } 622 662 message = new = g_strdup_printf("@%s %s", bu->handle, message + (cmd[2] - cmd[0])); 623 663 in_reply_to = id; 664 allow_post = TRUE; 624 665 } else if (g_strcasecmp(cmd[0], "post") == 0) { 625 666 message += 5; 626 } 627 628 { 667 allow_post = TRUE; 668 } 669 670 if (allow_post) { 629 671 char *s; 630 bee_user_t *bu; 631 632 if (!twitter_length_check(ic, message)) { 633 g_free(new); 634 g_free(cmds); 635 return; 636 } 672 673 if (!twitter_length_check(ic, message)) 674 goto eof; 637 675 638 676 s = cmd[0] + strlen(cmd[0]) - 1; … … 657 695 td->last_status_id = 0; 658 696 twitter_post_status(ic, message, in_reply_to); 659 g_free(new); 660 } 697 } else { 698 twitter_log(ic, "Unknown command: %s", cmd[0]); 699 } 700 eof: 701 g_free(new); 661 702 g_free(cmds); 662 703 } 704 705 void twitter_log(struct im_connection *ic, char *format, ... ) 706 { 707 struct twitter_data *td = ic->proto_data; 708 va_list params; 709 char *text; 710 711 va_start(params, format); 712 text = g_strdup_vprintf(format, params); 713 va_end(params); 714 715 if (td->timeline_gc) 716 imcb_chat_log(td->timeline_gc, "%s", text); 717 else 718 imcb_log(ic, "%s", text); 719 720 g_free(text); 721 } 722 663 723 664 724 void twitter_initmodule() -
protocols/twitter/twitter.h
r92d3044 rcc6fdf8 4 4 * Simple module to facilitate twitter functionality. * 5 5 * * 6 * Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> * 6 * Copyright 2009-2010 Geert Mulders <g.c.w.m.mulders@gmail.com> * 7 * Copyright 2010-2012 Wilmer van der Gaast <wilmer@gaast.net> * 7 8 * * 8 9 * This library is free software; you can redistribute it and/or * … … 35 36 typedef enum 36 37 { 37 TWITTER_HAVE_FRIENDS = 1, 38 TWITTER_HAVE_FRIENDS = 0x00001, 39 TWITTER_MODE_ONE = 0x00002, 40 TWITTER_MODE_MANY = 0x00004, 41 TWITTER_MODE_CHAT = 0x00008, 38 42 TWITTER_DOING_TIMELINE = 0x10000, 39 TWITTER_GOT_TIMELINE = 0x20000,40 TWITTER_GOT_MENTIONS = 0x40000,43 TWITTER_GOT_TIMELINE = 0x20000, 44 TWITTER_GOT_MENTIONS = 0x40000, 41 45 } twitter_flags_t; 42 46 … … 57 61 guint64 last_status_id; /* For undo */ 58 62 gint main_loop_id; 63 struct http_request *stream; 59 64 struct groupchat *timeline_gc; 60 65 gint http_fails; … … 80 85 }; 81 86 82 #define TWITTER_LOG_LENGTH 10087 #define TWITTER_LOG_LENGTH 256 83 88 struct twitter_log_data 84 89 { … … 99 104 char *twitter_parse_error( struct http_request *req ); 100 105 106 void twitter_log(struct im_connection *ic, char *format, ... ); 107 struct groupchat *twitter_groupchat_init(struct im_connection *ic); 108 101 109 #endif //_TWITTER_H -
protocols/twitter/twitter_http.c
r92d3044 rcc6fdf8 47 47 * This is actually pretty generic function... Perhaps it should move to the lib/http_client.c 48 48 */ 49 void*twitter_http(struct im_connection *ic, char *url_string, http_input_function func,50 gpointer data, int is_post, char **arguments, int arguments_len)49 struct http_request *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, 50 gpointer data, int is_post, char **arguments, int arguments_len) 51 51 { 52 52 struct twitter_data *td = ic->proto_data; … … 55 55 void *ret; 56 56 char *url_arguments; 57 url_t *base_url = NULL; 57 58 58 59 url_arguments = g_strdup(""); … … 67 68 } 68 69 } 70 71 if (strstr(url_string, "://")) { 72 base_url = g_new0(url_t, 1); 73 if (!url_set(base_url, url_string)) { 74 g_free(base_url); 75 return NULL; 76 } 77 } 78 69 79 // Make the request. 70 80 g_string_printf(request, "%s %s%s%s%s HTTP/1.0\r\n" … … 72 82 "User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n", 73 83 is_post ? "POST" : "GET", 74 td->url_path, url_string, 75 is_post ? "" : "?", is_post ? "" : url_arguments, td->url_host); 84 base_url ? base_url->file : td->url_path, 85 base_url ? "" : url_string, 86 is_post ? "" : "?", is_post ? "" : url_arguments, 87 base_url ? base_url->host : td->url_host); 76 88 77 89 // If a pass and user are given we append them to the request. … … 80 92 char *full_url; 81 93 82 full_url = g_strconcat(set_getstr(&ic->acc->set, "base_url"), url_string, NULL); 94 if (base_url) 95 full_url = g_strdup(url_string); 96 else 97 full_url = g_strconcat(set_getstr(&ic->acc->set, "base_url"), url_string, NULL); 83 98 full_header = oauth_http_header(td->oauth_info, is_post ? "POST" : "GET", 84 99 full_url, url_arguments); … … 109 124 } 110 125 111 ret = http_dorequest(td->url_host, td->url_port, td->url_ssl, request->str, func, data); 126 if (base_url) 127 ret = http_dorequest(base_url->host, base_url->port, base_url->proto == PROTO_HTTPS, request->str, func, data); 128 else 129 ret = http_dorequest(td->url_host, td->url_port, td->url_ssl, request->str, func, data); 112 130 113 131 g_free(url_arguments); 114 132 g_string_free(request, TRUE); 133 g_free(base_url); 134 return ret; 135 } 136 137 struct http_request *twitter_http_f(struct im_connection *ic, char *url_string, http_input_function func, 138 gpointer data, int is_post, char **arguments, int arguments_len, twitter_http_flags_t flags) 139 { 140 struct http_request *ret = twitter_http(ic, url_string, func, data, is_post, arguments, arguments_len); 141 if (ret) 142 ret->flags |= flags; 115 143 return ret; 116 144 } -
protocols/twitter/twitter_http.h
r92d3044 rcc6fdf8 28 28 #include "http_client.h" 29 29 30 typedef enum { 31 /* With this set, twitter_http_post() will post a generic confirmation 32 message to the user. */ 33 TWITTER_HTTP_USER_ACK = 0x1000000, 34 } twitter_http_flags_t; 35 30 36 struct oauth_info; 31 37 32 void *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, 33 gpointer data, int is_post, char** arguments, int arguments_len); 38 struct http_request *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, 39 gpointer data, int is_post, char** arguments, int arguments_len); 40 struct http_request *twitter_http_f(struct im_connection *ic, char *url_string, http_input_function func, 41 gpointer data, int is_post, char** arguments, int arguments_len, twitter_http_flags_t flags); 34 42 35 43 #endif //_TWITTER_HTTP_H -
protocols/twitter/twitter_lib.c
r92d3044 rcc6fdf8 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 } -
protocols/twitter/twitter_lib.h
r92d3044 rcc6fdf8 29 29 #include "twitter_http.h" 30 30 31 #define TWITTER_API_URL "http://api.twitter.com/1 "31 #define TWITTER_API_URL "http://api.twitter.com/1.1" 32 32 #define IDENTICA_API_URL "https://identi.ca/api" 33 33 34 34 /* Status URLs */ 35 #define TWITTER_STATUS_UPDATE_URL "/statuses/update. xml"35 #define TWITTER_STATUS_UPDATE_URL "/statuses/update.json" 36 36 #define TWITTER_STATUS_SHOW_URL "/statuses/show/" 37 37 #define TWITTER_STATUS_DESTROY_URL "/statuses/destroy/" … … 39 39 40 40 /* Timeline URLs */ 41 #define TWITTER_PUBLIC_TIMELINE_URL "/statuses/public_timeline. xml"42 #define TWITTER_FEATURED_USERS_URL "/statuses/featured. xml"43 #define TWITTER_FRIENDS_TIMELINE_URL "/statuses/friends_timeline. xml"44 #define TWITTER_HOME_TIMELINE_URL "/statuses/home_timeline. xml"45 #define TWITTER_MENTIONS_URL "/statuses/mentions .xml"46 #define TWITTER_USER_TIMELINE_URL "/statuses/user_timeline. xml"41 #define TWITTER_PUBLIC_TIMELINE_URL "/statuses/public_timeline.json" 42 #define TWITTER_FEATURED_USERS_URL "/statuses/featured.json" 43 #define TWITTER_FRIENDS_TIMELINE_URL "/statuses/friends_timeline.json" 44 #define TWITTER_HOME_TIMELINE_URL "/statuses/home_timeline.json" 45 #define TWITTER_MENTIONS_URL "/statuses/mentions_timeline.json" 46 #define TWITTER_USER_TIMELINE_URL "/statuses/user_timeline.json" 47 47 48 48 /* Users URLs */ 49 #define TWITTER_USERS_LOOKUP_URL "/users/lookup. xml"49 #define TWITTER_USERS_LOOKUP_URL "/users/lookup.json" 50 50 51 51 /* Direct messages URLs */ 52 #define TWITTER_DIRECT_MESSAGES_URL "/direct_messages. xml"53 #define TWITTER_DIRECT_MESSAGES_NEW_URL "/direct_messages/new. xml"54 #define TWITTER_DIRECT_MESSAGES_SENT_URL "/direct_messages/sent. xml"52 #define TWITTER_DIRECT_MESSAGES_URL "/direct_messages.json" 53 #define TWITTER_DIRECT_MESSAGES_NEW_URL "/direct_messages/new.json" 54 #define TWITTER_DIRECT_MESSAGES_SENT_URL "/direct_messages/sent.json" 55 55 #define TWITTER_DIRECT_MESSAGES_DESTROY_URL "/direct_messages/destroy/" 56 56 57 57 /* Friendships URLs */ 58 #define TWITTER_FRIENDSHIPS_CREATE_URL "/friendships/create. xml"59 #define TWITTER_FRIENDSHIPS_DESTROY_URL "/friendships/destroy. xml"60 #define TWITTER_FRIENDSHIPS_SHOW_URL "/friendships/show. xml"58 #define TWITTER_FRIENDSHIPS_CREATE_URL "/friendships/create.json" 59 #define TWITTER_FRIENDSHIPS_DESTROY_URL "/friendships/destroy.json" 60 #define TWITTER_FRIENDSHIPS_SHOW_URL "/friendships/show.json" 61 61 62 62 /* Social graphs URLs */ 63 #define TWITTER_FRIENDS_IDS_URL "/friends/ids. xml"64 #define TWITTER_FOLLOWERS_IDS_URL "/followers/ids. xml"63 #define TWITTER_FRIENDS_IDS_URL "/friends/ids.json" 64 #define TWITTER_FOLLOWERS_IDS_URL "/followers/ids.json" 65 65 66 66 /* Account URLs */ 67 #define TWITTER_ACCOUNT_RATE_LIMIT_URL "/account/rate_limit_status. xml"67 #define TWITTER_ACCOUNT_RATE_LIMIT_URL "/account/rate_limit_status.json" 68 68 69 69 /* Favorites URLs */ 70 #define TWITTER_FAVORITES_GET_URL "/favorites. xml"70 #define TWITTER_FAVORITES_GET_URL "/favorites.json" 71 71 #define TWITTER_FAVORITE_CREATE_URL "/favorites/create/" 72 72 #define TWITTER_FAVORITE_DESTROY_URL "/favorites/destroy/" … … 77 77 78 78 /* Report spam */ 79 #define TWITTER_REPORT_SPAM_URL "/report_spam. xml"79 #define TWITTER_REPORT_SPAM_URL "/report_spam.json" 80 80 81 #define TWITTER_USER_STREAM_URL "https://userstream.twitter.com/1.1/user.json" 82 83 gboolean twitter_open_stream(struct im_connection *ic); 81 84 void twitter_get_timeline(struct im_connection *ic, gint64 next_cursor); 82 85 void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor); 83 void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor);84 void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor);85 86 void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor); 86 87
Note: See TracChangeset
for help on using the changeset viewer.