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 | |
| parent | 4804b2f2e7270148ab303bf31351a5e2aa829761 (diff) | |
Merging Twitter-mentions patch from meh. Bug #663.
Diffstat (limited to 'protocols')
| -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); | 
