diff options
author | Wilmer van der Gaast <wilmer@gaast.net> | 2011-08-25 20:08:07 +0200 |
---|---|---|
committer | Wilmer van der Gaast <wilmer@gaast.net> | 2011-08-25 20:08:07 +0200 |
commit | 2322a9f38a2f6c9bf86eb62c2cd68fd3848b694f (patch) | |
tree | 78fd08f7cc9d3790ad2c5d8e9b1f1da06c2dcaba /protocols/twitter | |
parent | 4804b2f2e7270148ab303bf31351a5e2aa829761 (diff) |
Merging Twitter-mentions patch from meh. Bug #663.
Diffstat (limited to 'protocols/twitter')
-rw-r--r-- | protocols/twitter/twitter.c | 37 | ||||
-rw-r--r-- | protocols/twitter/twitter.h | 12 | ||||
-rw-r--r-- | protocols/twitter/twitter_lib.c | 302 | ||||
-rw-r--r-- | protocols/twitter/twitter_lib.h | 2 |
4 files changed, 282 insertions, 71 deletions
diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c index 175b1d0d..ac180250 100644 --- a/protocols/twitter/twitter.c +++ b/protocols/twitter/twitter.c @@ -29,12 +29,12 @@ #include "url.h" #define twitter_msg( ic, fmt... ) \ - do { \ - struct twitter_data *td = ic->proto_data; \ - if( td->home_timeline_gc ) \ - imcb_chat_log( td->home_timeline_gc, fmt ); \ - else \ - imcb_log( ic, fmt ); \ + do { \ + struct twitter_data *td = ic->proto_data; \ + if( td->timeline_gc ) \ + imcb_chat_log( td->timeline_gc, fmt ); \ + else \ + imcb_log( ic, fmt ); \ } while( 0 ); GSList *twitter_connections = NULL; @@ -51,7 +51,7 @@ gboolean twitter_main_loop(gpointer data, gint fd, b_input_condition cond) return 0; // Do stuff.. - twitter_get_home_timeline(ic, -1); + twitter_get_timeline(ic, -1); // If we are still logged in run this function again after timeout. return (ic->flags & OPT_LOGGED_IN) == OPT_LOGGED_IN; @@ -68,7 +68,8 @@ static void twitter_main_loop_start(struct im_connection *ic) // Queue the main_loop // Save the return value, so we can remove the timeout on logout. - td->main_loop_id = b_timeout_add(60000, twitter_main_loop, ic); + td->main_loop_id = + b_timeout_add(set_getint(&ic->acc->set, "fetch_interval") * 1000, twitter_main_loop, ic); } static void twitter_oauth_start(struct im_connection *ic); @@ -77,6 +78,8 @@ void twitter_login_finish(struct im_connection *ic) { struct twitter_data *td = ic->proto_data; + td->flags &= ~TWITTER_DOING_TIMELINE; + if (set_getbool(&ic->acc->set, "oauth") && !td->oauth_info) twitter_oauth_start(ic); else if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "one") != 0 && @@ -215,7 +218,6 @@ static void twitter_init(account_t * acc) def_url = TWITTER_API_URL; def_oauth = "true"; } else { /* if( strcmp( acc->prpl->name, "identica" ) == 0 ) */ - def_url = IDENTICA_API_URL; def_oauth = "false"; } @@ -227,6 +229,11 @@ static void twitter_init(account_t * acc) s = set_add(&acc->set, "commands", "true", set_eval_bool, acc); + s = set_add(&acc->set, "fetch_interval", "60", set_eval_int, acc); + s->flags |= ACC_SET_OFFLINE_ONLY; + + s = set_add(&acc->set, "fetch_mentions", "true", set_eval_bool, acc); + s = set_add(&acc->set, "message_length", "140", set_eval_int, acc); s = set_add(&acc->set, "mode", "chat", set_eval_mode, acc); @@ -235,6 +242,8 @@ static void twitter_init(account_t * acc) s = set_add(&acc->set, "show_ids", "false", set_eval_bool, acc); s->flags |= ACC_SET_OFFLINE_ONLY; + s = set_add(&acc->set, "show_old_mentions", "true", set_eval_bool, acc); + s = set_add(&acc->set, "oauth", def_oauth, set_eval_bool, acc); } @@ -316,8 +325,8 @@ static void twitter_logout(struct im_connection *ic) // Remove the main_loop function from the function queue. b_event_remove(td->main_loop_id); - if (td->home_timeline_gc) - imcb_chat_free(td->home_timeline_gc); + if (td->timeline_gc) + imcb_chat_free(td->timeline_gc); if (td) { oauth_info_free(td->oauth_info); @@ -403,13 +412,13 @@ static void twitter_chat_leave(struct groupchat *c) { struct twitter_data *td = c->ic->proto_data; - if (c != td->home_timeline_gc) + if (c != td->timeline_gc) return; /* WTF? */ /* If the user leaves the channel: Fine. Rejoin him/her once new tweets come in. */ - imcb_chat_free(td->home_timeline_gc); - td->home_timeline_gc = NULL; + imcb_chat_free(td->timeline_gc); + td->timeline_gc = NULL; } static void twitter_keepalive(struct im_connection *ic) diff --git a/protocols/twitter/twitter.h b/protocols/twitter/twitter.h index c38d9b86..14e43824 100644 --- a/protocols/twitter/twitter.h +++ b/protocols/twitter/twitter.h @@ -35,6 +35,9 @@ typedef enum { TWITTER_HAVE_FRIENDS = 1, + TWITTER_DOING_TIMELINE = 0x10000, + TWITTER_GOT_TIMELINE = 0x20000, + TWITTER_GOT_MENTIONS = 0x40000, } twitter_flags_t; struct twitter_log_data; @@ -43,12 +46,17 @@ struct twitter_data { char* user; struct oauth_info *oauth_info; + + gpointer home_timeline_obj; + gpointer mentions_obj; + + guint64 timeline_id; + GSList *follow_ids; - guint64 home_timeline_id; guint64 last_status_id; /* For undo */ gint main_loop_id; - struct groupchat *home_timeline_gc; + struct groupchat *timeline_gc; gint http_fails; twitter_flags_t flags; diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index 14e98c53..805ff5aa 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -77,17 +77,20 @@ static void txu_free(struct twitter_xml_user *txu) { if (txu == NULL) return; + g_free(txu->name); g_free(txu->screen_name); g_free(txu); } - /** * Frees a twitter_xml_status struct. */ static void txs_free(struct twitter_xml_status *txs) { + if (txs == NULL) + return; + g_free(txs->text); txu_free(txs->user); g_free(txs); @@ -102,19 +105,40 @@ static void txl_free(struct twitter_xml_list *txl) GSList *l; if (txl == NULL) return; - for (l = txl->list; l; l = g_slist_next(l)) - if (txl->type == TXL_STATUS) + + for (l = txl->list; l; l = g_slist_next(l)) { + if (txl->type == TXL_STATUS) { txs_free((struct twitter_xml_status *) l->data); - else if (txl->type == TXL_ID) + } else if (txl->type == TXL_ID) { g_free(l->data); - else if (txl->type == TXL_USER) + } else if (txl->type == TXL_USER) { txu_free(l->data); + } + } + g_slist_free(txl->list); g_free(txl); } /** - * Add a buddy if it is not allready added, set the status to logged in. + * Compare status elements + */ +static gint twitter_compare_elements(gconstpointer a, gconstpointer b) +{ + struct twitter_xml_status *a_status = (struct twitter_xml_status *) a; + struct twitter_xml_status *b_status = (struct twitter_xml_status *) b; + + if (a_status->created_at < b_status->created_at) { + return -1; + } else if (a_status->created_at > b_status->created_at) { + return 1; + } else { + return 0; + } +} + +/** + * Add a buddy if it is not already added, set the status to logged in. */ static void twitter_add_buddy(struct im_connection *ic, char *name, const char *fullname) { @@ -131,7 +155,7 @@ static void twitter_add_buddy(struct im_connection *ic, char *name, const char * /* Necessary so that nicks always get translated to the exact Twitter username. */ imcb_buddy_nick_hint(ic, name, name); - imcb_chat_add_buddy(td->home_timeline_gc, name); + imcb_chat_add_buddy(td->timeline_gc, name); } else if (g_strcasecmp(mode, "many") == 0) imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL); } @@ -259,7 +283,7 @@ static void twitter_http_get_friends_ids(struct http_request *req) } /* Create the room now that we "logged in". */ - if (!td->home_timeline_gc && g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0) + if (!td->timeline_gc && g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0) twitter_groupchat_init(ic); txl = g_new0(struct twitter_xml_list, 1); @@ -521,32 +545,6 @@ static xt_status twitter_xt_get_status_list(struct im_connection *ic, struct xt_ return XT_HANDLED; } -static void twitter_http_get_home_timeline(struct http_request *req); - -/** - * Get the timeline. - */ -void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor) -{ - struct twitter_data *td = ic->proto_data; - - char *args[4]; - args[0] = "cursor"; - args[1] = g_strdup_printf("%lld", (long long) next_cursor); - if (td->home_timeline_id) { - args[2] = "since_id"; - args[3] = g_strdup_printf("%llu", (long long unsigned int) td->home_timeline_id); - } - - twitter_http(ic, TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, args, - td->home_timeline_id ? 4 : 2); - - g_free(args[1]); - if (td->home_timeline_id) { - g_free(args[3]); - } -} - static char *twitter_msg_add_id(struct im_connection *ic, struct twitter_xml_status *txs, const char *prefix) { @@ -585,7 +583,7 @@ static void twitter_groupchat_init(struct im_connection *ic) struct twitter_data *td = ic->proto_data; GSList *l; - td->home_timeline_gc = gc = imcb_chat_new(ic, "home/timeline"); + td->timeline_gc = gc = imcb_chat_new(ic, "twitter/timeline"); name_hint = g_strdup_printf("%s_%s", td->prefix, ic->acc->user); imcb_chat_name_hint(gc, name_hint); @@ -594,7 +592,7 @@ static void twitter_groupchat_init(struct im_connection *ic) for (l = ic->bee->users; l; l = l->next) { bee_user_t *bu = l->data; if (bu->ic == ic) - imcb_chat_add_buddy(td->home_timeline_gc, bu->handle); + imcb_chat_add_buddy(td->timeline_gc, bu->handle); } } @@ -607,12 +605,13 @@ static void twitter_groupchat(struct im_connection *ic, GSList * list) GSList *l = NULL; struct twitter_xml_status *status; struct groupchat *gc; + guint64 last_id = 0; // Create a new groupchat if it does not exsist. - if (!td->home_timeline_gc) + if (!td->timeline_gc) twitter_groupchat_init(ic); - gc = td->home_timeline_gc; + gc = td->timeline_gc; if (!gc->joined) imcb_chat_add_buddy(gc, ic->acc->user); @@ -620,26 +619,30 @@ static void twitter_groupchat(struct im_connection *ic, GSList * list) char *msg; status = l->data; - if (status->user == NULL || status->text == NULL) + if (status->user == NULL || status->text == NULL || last_id == status->id) continue; - twitter_add_buddy(ic, status->user->screen_name, status->user->name); + last_id = status->id; strip_html(status->text); + msg = twitter_msg_add_id(ic, status, ""); // Say it! - if (g_strcasecmp(td->user, status->user->screen_name) == 0) + if (g_strcasecmp(td->user, status->user->screen_name) == 0) { imcb_chat_log(gc, "You: %s", msg ? msg : status->text); - else + } else { + twitter_add_buddy(ic, status->user->screen_name, status->user->name); + imcb_chat_msg(gc, status->user->screen_name, msg ? msg : status->text, 0, status->created_at); + } g_free(msg); - // Update the home_timeline_id to hold the highest id, so that by the next request + // Update the timeline_id to hold the highest id, so that by the next request // we won't pick up the updates already in the list. - td->home_timeline_id = MAX(td->home_timeline_id, status->id); + td->timeline_id = MAX(td->timeline_id, status->id); } } @@ -653,6 +656,7 @@ static void twitter_private_message_chat(struct im_connection *ic, GSList * list struct twitter_xml_status *status; char from[MAX_STRING]; gboolean mode_one; + guint64 last_id = 0; mode_one = g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "one") == 0; @@ -665,6 +669,10 @@ static void twitter_private_message_chat(struct im_connection *ic, GSList * list char *prefix = NULL, *text = NULL; status = l->data; + if (status->user == NULL || status->text == NULL || last_id == status->id) + continue; + + last_id = status->id; strip_html(status->text); if (mode_one) @@ -679,15 +687,150 @@ static void twitter_private_message_chat(struct im_connection *ic, GSList * list mode_one ? from : status->user->screen_name, text ? text : status->text, 0, status->created_at); - // Update the home_timeline_id to hold the highest id, so that by the next request + // Update the timeline_id to hold the highest id, so that by the next request // we won't pick up the updates already in the list. - td->home_timeline_id = MAX(td->home_timeline_id, status->id); + td->timeline_id = MAX(td->timeline_id, status->id); g_free(text); g_free(prefix); } } +static void twitter_http_get_home_timeline(struct http_request *req); +static void twitter_http_get_mentions(struct http_request *req); + +/** + * Get the timeline with optionally mentions + */ +void twitter_get_timeline(struct im_connection *ic, gint64 next_cursor) +{ + struct twitter_data *td = ic->proto_data; + gboolean include_mentions = set_getbool(&ic->acc->set, "fetch_mentions"); + + if (td->flags & TWITTER_DOING_TIMELINE) { + return; + } + + td->flags |= TWITTER_DOING_TIMELINE; + + twitter_get_home_timeline(ic, next_cursor); + + if (include_mentions) { + twitter_get_mentions(ic, next_cursor); + } +} + +/** + * Call this one after receiving timeline/mentions. Show to user once we have + * both. + */ +void twitter_flush_timeline(struct im_connection *ic) +{ + struct twitter_data *td = ic->proto_data; + gboolean include_mentions = set_getbool(&ic->acc->set, "fetch_mentions"); + gboolean show_old_mentions = set_getbool(&ic->acc->set, "show_old_mentions"); + struct twitter_xml_list *home_timeline = td->home_timeline_obj; + struct twitter_xml_list *mentions = td->mentions_obj; + GSList *output = NULL; + GSList *l; + + if (!(td->flags & TWITTER_GOT_TIMELINE)) { + return; + } + + if (include_mentions && !(td->flags & TWITTER_GOT_MENTIONS)) { + return; + } + + if (home_timeline && home_timeline->list) { + for (l = home_timeline->list; l; l = g_slist_next(l)) { + output = g_slist_insert_sorted(output, l->data, twitter_compare_elements); + } + } + + if (include_mentions && mentions && mentions->list) { + for (l = mentions->list; l; l = g_slist_next(l)) { + if (!show_old_mentions && output && twitter_compare_elements(l->data, output->data) < 0) { + continue; + } + + output = g_slist_insert_sorted(output, l->data, twitter_compare_elements); + } + } + + // See if the user wants to see the messages in a groupchat window or as private messages. + if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0) + twitter_groupchat(ic, output); + else + twitter_private_message_chat(ic, output); + + g_slist_free(output); + + if (home_timeline && home_timeline->list) { + txl_free(home_timeline); + } + + if (mentions && mentions->list) { + txl_free(mentions); + } + + td->flags &= ~(TWITTER_DOING_TIMELINE | TWITTER_GOT_TIMELINE | TWITTER_GOT_MENTIONS); +} + +/** + * Get the timeline. + */ +void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor) +{ + struct twitter_data *td = ic->proto_data; + + td->home_timeline_obj = NULL; + td->flags &= ~TWITTER_GOT_TIMELINE; + + char *args[4]; + args[0] = "cursor"; + args[1] = g_strdup_printf("%lld", (long long) next_cursor); + if (td->timeline_id) { + args[2] = "since_id"; + args[3] = g_strdup_printf("%llu", (long long unsigned int) td->timeline_id); + } + + twitter_http(ic, TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, args, + td->timeline_id ? 4 : 2); + + g_free(args[1]); + if (td->timeline_id) { + g_free(args[3]); + } +} + +/** + * Get mentions. + */ +void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor) +{ + struct twitter_data *td = ic->proto_data; + + td->mentions_obj = NULL; + td->flags &= ~TWITTER_GOT_MENTIONS; + + char *args[4]; + args[0] = "cursor"; + args[1] = g_strdup_printf("%lld", (long long) next_cursor); + if (td->timeline_id) { + args[2] = "since_id"; + args[3] = g_strdup_printf("%llu", (long long unsigned int) td->timeline_id); + } + + twitter_http(ic, TWITTER_MENTIONS_URL, twitter_http_get_mentions, ic, 0, args, + td->timeline_id ? 4 : 2); + + g_free(args[1]); + if (td->timeline_id) { + g_free(args[3]); + } +} + /** * Callback for getting the home timeline. */ @@ -712,14 +855,66 @@ static void twitter_http_get_home_timeline(struct http_request *req) } else if (req->status_code == 401) { imcb_error(ic, "Authentication failure"); imc_logout(ic, FALSE); - return; + goto end; } else { // It didn't go well, output the error and return. if (++td->http_fails >= 5) imcb_error(ic, "Could not retrieve %s: %s", TWITTER_HOME_TIMELINE_URL, twitter_parse_error(req)); + goto end; + } + + txl = g_new0(struct twitter_xml_list, 1); + txl->list = NULL; + + // Parse the data. + parser = xt_new(NULL, txl); + xt_feed(parser, req->reply_body, req->body_size); + // The root <statuses> node should hold the list of statuses <status> + twitter_xt_get_status_list(ic, parser->root, txl); + xt_free(parser); + + td->home_timeline_obj = txl; + + end: + td->flags |= TWITTER_GOT_TIMELINE; + + twitter_flush_timeline(ic); +} + +/** + * Callback for getting mentions. + */ +static void twitter_http_get_mentions(struct http_request *req) +{ + struct im_connection *ic = req->data; + struct twitter_data *td; + struct xt_parser *parser; + struct twitter_xml_list *txl; + + // Check if the connection is still active. + if (!g_slist_find(twitter_connections, ic)) return; + + td = ic->proto_data; + + // Check if the HTTP request went well. + if (req->status_code == 200) { + td->http_fails = 0; + if (!(ic->flags & OPT_LOGGED_IN)) + imcb_connected(ic); + } else if (req->status_code == 401) { + imcb_error(ic, "Authentication failure"); + imc_logout(ic, FALSE); + goto end; + } else { + // It didn't go well, output the error and return. + if (++td->http_fails >= 5) + imcb_error(ic, "Could not retrieve " TWITTER_MENTIONS_URL ": %s", + twitter_parse_error(req)); + + goto end; } txl = g_new0(struct twitter_xml_list, 1); @@ -732,15 +927,12 @@ static void twitter_http_get_home_timeline(struct http_request *req) twitter_xt_get_status_list(ic, parser->root, txl); xt_free(parser); - // See if the user wants to see the messages in a groupchat window or as private messages. - if (txl->list == NULL); - else if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0) - twitter_groupchat(ic, txl->list); - else - twitter_private_message_chat(ic, txl->list); + td->mentions_obj = txl; - // Free the structure. - txl_free(txl); + end: + td->flags |= TWITTER_GOT_MENTIONS; + + twitter_flush_timeline(ic); } /** diff --git a/protocols/twitter/twitter_lib.h b/protocols/twitter/twitter_lib.h index c33b2dfc..b06f5055 100644 --- a/protocols/twitter/twitter_lib.h +++ b/protocols/twitter/twitter_lib.h @@ -75,8 +75,10 @@ #define TWITTER_BLOCKS_CREATE_URL "/blocks/create/" #define TWITTER_BLOCKS_DESTROY_URL "/blocks/destroy/" +void twitter_get_timeline(struct im_connection *ic, gint64 next_cursor); void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor); void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor); +void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor); void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor); void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to); |