diff options
Diffstat (limited to 'protocols')
| -rw-r--r-- | protocols/msn/msn.c | 1 | ||||
| -rw-r--r-- | protocols/msn/ns.c | 4 | ||||
| -rw-r--r-- | protocols/nogaim.c | 23 | ||||
| -rw-r--r-- | protocols/nogaim.h | 2 | ||||
| -rw-r--r-- | protocols/twitter/twitter.c | 304 | ||||
| -rw-r--r-- | protocols/twitter/twitter.h | 18 | ||||
| -rw-r--r-- | protocols/twitter/twitter_http.c | 40 | ||||
| -rw-r--r-- | protocols/twitter/twitter_http.h | 12 | ||||
| -rw-r--r-- | protocols/twitter/twitter_lib.c | 792 | ||||
| -rw-r--r-- | protocols/twitter/twitter_lib.h | 45 | 
10 files changed, 779 insertions, 462 deletions
| diff --git a/protocols/msn/msn.c b/protocols/msn/msn.c index 71570ce0..845f9cf8 100644 --- a/protocols/msn/msn.c +++ b/protocols/msn/msn.c @@ -52,6 +52,7 @@ static void msn_login( account_t *acc )  	struct msn_data *md = g_new0( struct msn_data, 1 );  	ic->proto_data = md; +	ic->flags |= OPT_PONGS | OPT_PONGED;  	if( strchr( acc->user, '@' ) == NULL )  	{ diff --git a/protocols/msn/ns.c b/protocols/msn/ns.c index d9a558f9..7acf4654 100644 --- a/protocols/msn/ns.c +++ b/protocols/msn/ns.c @@ -576,6 +576,10 @@ static int msn_ns_command( struct msn_handler_data *handler, char **cmd, int num  		if( num_parts >= 7 )  			handler->msglen = atoi( cmd[6] );  	} +	else if( strcmp( cmd[0], "QNG" ) == 0 ) +	{ +		ic->flags |= OPT_PONGED; +	}  	else if( isdigit( cmd[0][0] ) )  	{  		int num = atoi( cmd[0] ); diff --git a/protocols/nogaim.c b/protocols/nogaim.c index 773e3877..2f85a3eb 100644 --- a/protocols/nogaim.c +++ b/protocols/nogaim.c @@ -260,12 +260,32 @@ static gboolean send_keepalive( gpointer d, gint fd, b_input_condition cond )  {  	struct im_connection *ic = d; +	if( ( ic->flags & OPT_PONGS ) && !( ic->flags & OPT_PONGED ) ) +	{ +		/* This protocol is expected to ack keepalives and hasn't +		   since the last time we were here. */ +		imcb_error( ic, "Connection timeout" ); +		imc_logout( ic, TRUE ); +		return FALSE; +	} +	ic->flags &= ~OPT_PONGED; +	  	if( ic->acc->prpl->keepalive )  		ic->acc->prpl->keepalive( ic );  	return TRUE;  } +void start_keepalives( struct im_connection *ic, int interval ) +{ +	b_event_remove( ic->keepalive ); +	ic->keepalive = b_timeout_add( interval, send_keepalive, ic ); +	 +	/* Connecting successfully counts as a first successful pong. */ +	if( ic->flags & OPT_PONGS ) +		ic->flags |= OPT_PONGED; +} +  void imcb_connected( struct im_connection *ic )  {  	/* MSN servers sometimes redirect you to a different server and do @@ -276,9 +296,8 @@ void imcb_connected( struct im_connection *ic )  	imcb_log( ic, "Logged in" ); -	b_event_remove( ic->keepalive ); -	ic->keepalive = b_timeout_add( 60000, send_keepalive, ic );  	ic->flags |= OPT_LOGGED_IN; +	start_keepalives( ic, 60000 );  	/* Necessary to send initial presence status, even if we're not away. */  	imc_away_send_update( ic ); diff --git a/protocols/nogaim.h b/protocols/nogaim.h index 0536ce69..aa6ba7c0 100644 --- a/protocols/nogaim.h +++ b/protocols/nogaim.h @@ -67,6 +67,8 @@  #define OPT_TYPING      0x00000100 /* Some pieces of code make assumptions */  #define OPT_THINKING    0x00000200 /* about these values... Stupid me! */  #define OPT_NOOTR       0x00001000 /* protocol not suitable for OTR */ +#define OPT_PONGS       0x00010000 /* Service sends us keep-alives */ +#define OPT_PONGED      0x00020000 /* Received a keep-alive during last interval */  /* ok. now the fun begins. first we create a connection structure */  struct im_connection diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c index 93ef4ae2..651bf345 100644 --- a/protocols/twitter/twitter.c +++ b/protocols/twitter/twitter.c @@ -3,7 +3,8 @@  *  BitlBee - An IRC to IM gateway                                           *  *  Simple module to facilitate twitter functionality.                       *  *                                                                           * -*  Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com>                 * +*  Copyright 2009-2010 Geert Mulders <g.c.w.m.mulders@gmail.com>            * +*  Copyright 2010-2012 Wilmer van der Gaast <wilmer@gaast.net>              *  *                                                                           *  *  This library is free software; you can redistribute it and/or            *  *  modify it under the terms of the GNU Lesser General Public               * @@ -28,17 +29,7 @@  #include "twitter_lib.h"  #include "url.h" -#define twitter_msg( 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; -  /**   * Main loop function   */ @@ -61,15 +52,57 @@ static void twitter_main_loop_start(struct im_connection *ic)  {  	struct twitter_data *td = ic->proto_data; +	/* Create the room now that we "logged in". */ +	if (td->flags & TWITTER_MODE_CHAT) +		twitter_groupchat_init(ic); +  	imcb_log(ic, "Getting initial statuses"); -	// Run this once. After this queue the main loop function. +	// Run this once. After this queue the main loop function (or open the +	// stream if available).  	twitter_main_loop(ic, -1, 0); +	 +	if (set_getbool(&ic->acc->set, "stream")) { +		/* That fetch was just to get backlog, the stream will give +		   us the rest. \o/ */ +		twitter_open_stream(ic); +		 +		/* Stream sends keepalives (empty lines) or actual data at +		   least twice a minute. Disconnect if this stops. */ +		ic->flags |= OPT_PONGS; +	} else { +		/* Not using the streaming API, so keep polling the old- +		   fashioned way. :-( */ +		td->main_loop_id = +		    b_timeout_add(set_getint(&ic->acc->set, "fetch_interval") * 1000, +		                  twitter_main_loop, ic); +	} +} + +struct groupchat *twitter_groupchat_init(struct im_connection *ic) +{ +	char *name_hint; +	struct groupchat *gc; +	struct twitter_data *td = ic->proto_data; +	GSList *l; + +	if (td->timeline_gc) +		return td->timeline_gc; -	// Queue the main_loop -	// Save the return value, so we can remove the timeout on logout. -	td->main_loop_id = -	    b_timeout_add(set_getint(&ic->acc->set, "fetch_interval") * 1000, twitter_main_loop, ic); +	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); +	g_free(name_hint); + +	for (l = ic->bee->users; l; l = l->next) { +		bee_user_t *bu = l->data; +		if (bu->ic == ic) +			imcb_chat_add_buddy(gc, bu->handle); +	} +	imcb_chat_add_buddy(gc, ic->acc->user); +	 +	return gc;  }  static void twitter_oauth_start(struct im_connection *ic); @@ -82,11 +115,10 @@ void twitter_login_finish(struct im_connection *ic)  	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 && -		 !(td->flags & TWITTER_HAVE_FRIENDS)) { +	else if (!(td->flags & TWITTER_MODE_ONE) && +	         !(td->flags & TWITTER_HAVE_FRIENDS)) {  		imcb_log(ic, "Getting contact list");  		twitter_get_friends_ids(ic, -1); -		//twitter_get_statuses_friends(ic, -1);  	} else  		twitter_main_loop_start(ic);  } @@ -186,16 +218,6 @@ static gboolean twitter_oauth_callback(struct oauth_info *info)  	return TRUE;  } - -static char *set_eval_mode(set_t * set, char *value) -{ -	if (g_strcasecmp(value, "one") == 0 || -	    g_strcasecmp(value, "many") == 0 || g_strcasecmp(value, "chat") == 0) -		return value; -	else -		return NULL; -} -  int twitter_url_len_diff(gchar *msg, unsigned int target_len)  {  	int url_len_diff = 0; @@ -232,11 +254,28 @@ static gboolean twitter_length_check(struct im_connection *ic, gchar * msg)  	if (max == 0 || (len = g_utf8_strlen(msg, -1) + url_len_diff) <= max)  		return TRUE; -	imcb_error(ic, "Maximum message length exceeded: %d > %d", len, max); +	twitter_log(ic, "Maximum message length exceeded: %d > %d", len, max);  	return FALSE;  } +static char *set_eval_commands(set_t * set, char *value) +{ +	if (g_strcasecmp(value, "strict") == 0 ) +		return value; +	else +		return set_eval_bool(set, value); +} + +static char *set_eval_mode(set_t * set, char *value) +{ +	if (g_strcasecmp(value, "one") == 0 || +	    g_strcasecmp(value, "many") == 0 || g_strcasecmp(value, "chat") == 0) +		return value; +	else +		return NULL; +} +  static void twitter_init(account_t * acc)  {  	set_t *s; @@ -256,7 +295,7 @@ static void twitter_init(account_t * acc)  	s = set_add(&acc->set, "base_url", def_url, NULL, acc);  	s->flags |= ACC_SET_OFFLINE_ONLY; -	s = set_add(&acc->set, "commands", "true", set_eval_bool, acc); +	s = set_add(&acc->set, "commands", "true", set_eval_commands, acc);  	s = set_add(&acc->set, "fetch_interval", "60", set_eval_int, acc);  	s->flags |= ACC_SET_OFFLINE_ONLY; @@ -273,15 +312,19 @@ static void twitter_init(account_t * acc)  	s = set_add(&acc->set, "oauth", "true", set_eval_oauth, acc);  	s = set_add(&acc->set, "show_ids", "true", set_eval_bool, acc); -	s->flags |= ACC_SET_OFFLINE_ONLY;  	s = set_add(&acc->set, "show_old_mentions", "20", set_eval_int, acc);  	s = set_add(&acc->set, "strip_newlines", "false", set_eval_bool, acc); +	 +	if (strcmp(acc->prpl->name, "twitter") == 0) { +		s = set_add(&acc->set, "stream", "true", set_eval_bool, acc); +		s->flags |= ACC_SET_OFFLINE_ONLY; +	}  }  /** - * Login method. Since the twitter API works with seperate HTTP request we  + * Login method. Since the twitter API works with separate HTTP request we   * only save the user and pass to the twitter_data object.   */  static void twitter_login(account_t * acc) @@ -299,6 +342,12 @@ static void twitter_login(account_t * acc)  		return;  	} +	if (!strstr(url.host, "twitter.com") && +	    set_getbool(&ic->acc->set, "stream")) { +		imcb_error(ic, "Warning: The streaming API is only supported by Twitter, " +		               "and you seem to be connecting to a different service."); +	} +  	imcb_log(ic, "Connecting");  	twitter_connections = g_slist_append(twitter_connections, ic); @@ -339,8 +388,16 @@ static void twitter_login(account_t * acc)  	imcb_add_buddy(ic, name, NULL);  	imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL); -	if (set_getbool(&acc->set, "show_ids")) -		td->log = g_new0(struct twitter_log_data, TWITTER_LOG_LENGTH); +	td->log = g_new0(struct twitter_log_data, TWITTER_LOG_LENGTH); +	td->log_id = -1; +	 +	s = set_getstr(&ic->acc->set, "mode"); +	if (g_strcasecmp(s, "one") == 0) +		td->flags |= TWITTER_MODE_ONE; +	else if (g_strcasecmp(s, "many") == 0) +		td->flags |= TWITTER_MODE_MANY; +	else +		td->flags |= TWITTER_MODE_CHAT;  	twitter_login_finish(ic);  } @@ -362,6 +419,7 @@ static void twitter_logout(struct im_connection *ic)  		imcb_chat_free(td->timeline_gc);  	if (td) { +		http_close(td->stream);  		oauth_info_free(td->oauth_info);  		g_free(td->user);  		g_free(td->prefix); @@ -494,21 +552,40 @@ static void twitter_buddy_data_free(struct bee_user *bu)   *   *  Returns 0 if the user provides garbage.   */ -static guint64 twitter_message_id_from_command_arg(struct im_connection *ic, struct twitter_data *td, char *arg) { +static guint64 twitter_message_id_from_command_arg(struct im_connection *ic, char *arg, bee_user_t **bu_) { +	struct twitter_data *td = ic->proto_data;  	struct twitter_user_data *tud; -	bee_user_t *bu; +	bee_user_t *bu = NULL;  	guint64 id = 0; -	if (g_str_has_prefix(arg, "#") && -		sscanf(arg + 1, "%" G_GUINT64_FORMAT, &id) == 1) { -		if (id < TWITTER_LOG_LENGTH && td->log) -			id = td->log[id].id; -	} else if ((bu = bee_user_by_handle(ic->bee, ic, arg)) && -		(tud = bu->data) && tud->last_id) -		id = tud->last_id; -	else if (sscanf(arg, "%" G_GUINT64_FORMAT, &id) == 1){ -		if (id < TWITTER_LOG_LENGTH && td->log) +	 +	if (bu_) +		*bu_ = NULL; +	if (!arg || !arg[0]) +		return 0; +	 +	if (arg[0] != '#' && (bu = bee_user_by_handle(ic->bee, ic, arg))) { +		if ((tud = bu->data)) +			id = tud->last_id; +	} else { +		if (arg[0] == '#') +			arg++; +		if (sscanf(arg, "%" G_GINT64_MODIFIER "x", &id) == 1 && +		    id < TWITTER_LOG_LENGTH) { +			bu = td->log[id].bu;  			id = td->log[id].id; +			/* Beware of dangling pointers! */ +			if (!g_slist_find(ic->bee->users, bu)) +				bu = NULL; +		} else if (sscanf(arg, "%" G_GINT64_MODIFIER "d", &id) == 1) { +			/* Allow normal tweet IDs as well; not a very useful +			   feature but it's always been there. Just ignore +			   very low IDs to avoid accidents. */ +			if (id < 1000000) +				id = 0; +		}  	} +	if (bu_) +		*bu_ = bu;  	return id;  } @@ -516,124 +593,85 @@ static void twitter_handle_command(struct im_connection *ic, char *message)  {  	struct twitter_data *td = ic->proto_data;  	char *cmds, **cmd, *new = NULL; -	guint64 in_reply_to = 0; +	guint64 in_reply_to = 0, id; +	gboolean allow_post = +		g_strcasecmp(set_getstr(&ic->acc->set, "commands"), "strict") != 0; +	bee_user_t *bu = NULL;  	cmds = g_strdup(message);  	cmd = split_command_parts(cmds);  	if (cmd[0] == NULL) { -		g_free(cmds); -		return; -	} else if (!set_getbool(&ic->acc->set, "commands")) { -		/* Not supporting commands. */ +		goto eof; +	} else if (!set_getbool(&ic->acc->set, "commands") && allow_post) { +		/* Not supporting commands if "commands" is set to true/strict. */  	} else if (g_strcasecmp(cmd[0], "undo") == 0) { -		guint64 id; -  		if (cmd[1] == NULL)  			twitter_status_destroy(ic, td->last_status_id); -		else if (sscanf(cmd[1], "%" G_GUINT64_FORMAT, &id) == 1) { -			if (id < TWITTER_LOG_LENGTH && td->log) -				id = td->log[id].id; -			 +		else if ((id = twitter_message_id_from_command_arg(ic, cmd[1], NULL)))  			twitter_status_destroy(ic, id); -		} else -			twitter_msg(ic, "Could not undo last action"); +		else +			twitter_log(ic, "Could not undo last action"); -		g_free(cmds); -		return; +		goto eof;  	} else if (g_strcasecmp(cmd[0], "favourite") == 0 && cmd[1]) { -		guint64 id; -		if ((id = twitter_message_id_from_command_arg(ic, td, cmd[1]))) { +		if ((id = twitter_message_id_from_command_arg(ic, cmd[1], NULL))) {  			twitter_favourite_tweet(ic, id);  		} else { -			twitter_msg(ic, "Please provide a message ID or username."); +			twitter_log(ic, "Please provide a message ID or username.");  		} -		g_free(cmds); -		return; +		goto eof;  	} else if (g_strcasecmp(cmd[0], "follow") == 0 && cmd[1]) {  		twitter_add_buddy(ic, cmd[1], NULL); -		g_free(cmds); -		return; +		goto eof;  	} else if (g_strcasecmp(cmd[0], "unfollow") == 0 && cmd[1]) {  		twitter_remove_buddy(ic, cmd[1], NULL); -		g_free(cmds); -		return; +		goto eof;  	} else if ((g_strcasecmp(cmd[0], "report") == 0 ||  	            g_strcasecmp(cmd[0], "spam") == 0) && cmd[1]) { -		char * screen_name; -		guint64 id; -		screen_name = cmd[1]; +		char *screen_name; +		  		/* Report nominally works on users but look up the user who  		   posted the given ID if the user wants to do it that way */ -		if (g_str_has_prefix(cmd[1], "#") && -		    sscanf(cmd[1] + 1, "%" G_GUINT64_FORMAT, &id) == 1) { -			if (id < TWITTER_LOG_LENGTH && td->log) { -				if (g_slist_find(ic->bee->users, td->log[id].bu)) { -					screen_name = td->log[id].bu->handle; -				} -			} -		} +		twitter_message_id_from_command_arg(ic, cmd[1], &bu); +		if (bu) +			screen_name = bu->handle; +		else +			screen_name = cmd[1]; +		  		twitter_report_spam(ic, screen_name); -		g_free(cmds); -		return; +		goto eof;  	} else if (g_strcasecmp(cmd[0], "rt") == 0 && cmd[1]) { -		guint64 id = twitter_message_id_from_command_arg(ic, td, cmd[1]); +		id = twitter_message_id_from_command_arg(ic, cmd[1], NULL);  		td->last_status_id = 0;  		if (id)  			twitter_status_retweet(ic, id);  		else -			twitter_msg(ic, "User `%s' does not exist or didn't " +			twitter_log(ic, "User `%s' does not exist or didn't "  				    "post any statuses recently", cmd[1]); -		g_free(cmds); -		return; +		goto eof;  	} else if (g_strcasecmp(cmd[0], "reply") == 0 && cmd[1] && cmd[2]) { -		struct twitter_user_data *tud; -		bee_user_t *bu = NULL; -		guint64 id = 0; - -		if (g_str_has_prefix(cmd[1], "#") && -		    sscanf(cmd[1] + 1, "%" G_GUINT64_FORMAT, &id) == 1 && -		    (id < TWITTER_LOG_LENGTH) && td->log) { -			bu = td->log[id].bu; -			if (g_slist_find(ic->bee->users, bu)) -				id = td->log[id].id; -			else -				bu = NULL; -		} else if ((bu = bee_user_by_handle(ic->bee, ic, cmd[1])) && -		    (tud = bu->data) && tud->last_id) { -			id = tud->last_id; -		} else if (sscanf(cmd[1], "%" G_GUINT64_FORMAT, &id) == 1 && -		           (id < TWITTER_LOG_LENGTH) && td->log) { -			bu = td->log[id].bu; -			if (g_slist_find(ic->bee->users, bu)) -				id = td->log[id].id; -			else -				bu = NULL; -		} - +		id = twitter_message_id_from_command_arg(ic, cmd[1], &bu);  		if (!id || !bu) { -			twitter_msg(ic, "User `%s' does not exist or didn't " +			twitter_log(ic, "User `%s' does not exist or didn't "  				    "post any statuses recently", cmd[1]); -			g_free(cmds); -			return; +			goto eof;  		}  		message = new = g_strdup_printf("@%s %s", bu->handle, message + (cmd[2] - cmd[0]));  		in_reply_to = id; +		allow_post = TRUE;  	} else if (g_strcasecmp(cmd[0], "post") == 0) {  		message += 5; +		allow_post = TRUE;  	} -	{ +	if (allow_post) {  		char *s; -		bee_user_t *bu; -		if (!twitter_length_check(ic, message)) { -			g_free(new); -			g_free(cmds); -			return; -		} +		if (!twitter_length_check(ic, message)) +			goto eof;  		s = cmd[0] + strlen(cmd[0]) - 1;  		if (!new && s > cmd[0] && (*s == ':' || *s == ',')) { @@ -656,11 +694,33 @@ static void twitter_handle_command(struct im_connection *ic, char *message)  		   this would delete the second-last Tweet. Prevent that. */  		td->last_status_id = 0;  		twitter_post_status(ic, message, in_reply_to); -		g_free(new); +	} else { +		twitter_log(ic, "Unknown command: %s", cmd[0]);  	} +eof: +	g_free(new);  	g_free(cmds);  } +void twitter_log(struct im_connection *ic, char *format, ... ) +{ +	struct twitter_data *td = ic->proto_data; +	va_list params; +	char *text; +	 +	va_start(params, format); +	text = g_strdup_vprintf(format, params); +	va_end(params); +	 +	if (td->timeline_gc) +		imcb_chat_log(td->timeline_gc, "%s", text); +	else +		imcb_log(ic, "%s", text); +	 +	g_free(text); +} + +  void twitter_initmodule()  {  	struct prpl *ret = g_new0(struct prpl, 1); diff --git a/protocols/twitter/twitter.h b/protocols/twitter/twitter.h index 14e43824..8792b7c9 100644 --- a/protocols/twitter/twitter.h +++ b/protocols/twitter/twitter.h @@ -3,7 +3,8 @@  *  BitlBee - An IRC to IM gateway                                           *  *  Simple module to facilitate twitter functionality.                       *  *                                                                           * -*  Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com>                 * +*  Copyright 2009-2010 Geert Mulders <g.c.w.m.mulders@gmail.com>            * +*  Copyright 2010-2012 Wilmer van der Gaast <wilmer@gaast.net>              *  *                                                                           *  *  This library is free software; you can redistribute it and/or            *  *  modify it under the terms of the GNU Lesser General Public               * @@ -34,10 +35,13 @@  typedef enum  { -	TWITTER_HAVE_FRIENDS = 1, +	TWITTER_HAVE_FRIENDS   = 0x00001, +	TWITTER_MODE_ONE       = 0x00002, +	TWITTER_MODE_MANY      = 0x00004, +	TWITTER_MODE_CHAT      = 0x00008,  	TWITTER_DOING_TIMELINE = 0x10000, -	TWITTER_GOT_TIMELINE = 0x20000, -	TWITTER_GOT_MENTIONS = 0x40000, +	TWITTER_GOT_TIMELINE   = 0x20000, +	TWITTER_GOT_MENTIONS   = 0x40000,  } twitter_flags_t;  struct twitter_log_data; @@ -56,6 +60,7 @@ struct twitter_data  	guint64 last_status_id; /* For undo */  	gint main_loop_id; +	struct http_request *stream;  	struct groupchat *timeline_gc;  	gint http_fails;  	twitter_flags_t flags; @@ -79,7 +84,7 @@ struct twitter_user_data  	time_t last_time;  }; -#define TWITTER_LOG_LENGTH 100 +#define TWITTER_LOG_LENGTH 256  struct twitter_log_data  {  	guint64 id; @@ -98,4 +103,7 @@ void twitter_login_finish( struct im_connection *ic );  struct http_request;  char *twitter_parse_error( struct http_request *req ); +void twitter_log(struct im_connection *ic, char *format, ... ); +struct groupchat *twitter_groupchat_init(struct im_connection *ic); +  #endif //_TWITTER_H diff --git a/protocols/twitter/twitter_http.c b/protocols/twitter/twitter_http.c index dbac5461..0f1ab518 100644 --- a/protocols/twitter/twitter_http.c +++ b/protocols/twitter/twitter_http.c @@ -46,14 +46,15 @@ static char *twitter_url_append(char *url, char *key, char *value);   * Do a request.   * This is actually pretty generic function... Perhaps it should move to the lib/http_client.c   */ -void *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, -		   gpointer data, int is_post, char **arguments, int arguments_len) +struct http_request *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, +		                  gpointer data, int is_post, char **arguments, int arguments_len)  {  	struct twitter_data *td = ic->proto_data;  	char *tmp;  	GString *request = g_string_new("");  	void *ret;  	char *url_arguments; +	url_t *base_url = NULL;  	url_arguments = g_strdup(""); @@ -66,20 +67,34 @@ void *twitter_http(struct im_connection *ic, char *url_string, http_input_functi  			url_arguments = tmp;  		}  	} +	 +	if (strstr(url_string, "://")) { +		base_url = g_new0(url_t, 1); +		if (!url_set(base_url, url_string)) { +			g_free(base_url); +			return NULL; +		} +	} +	  	// Make the request.  	g_string_printf(request, "%s %s%s%s%s HTTP/1.0\r\n"  			"Host: %s\r\n"  			"User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n",  			is_post ? "POST" : "GET", -			td->url_path, url_string, -			is_post ? "" : "?", is_post ? "" : url_arguments, td->url_host); +			base_url ? base_url->file : td->url_path, +			base_url ? "" : url_string, +			is_post ? "" : "?", is_post ? "" : url_arguments, +			base_url ? base_url->host : td->url_host);  	// If a pass and user are given we append them to the request.  	if (td->oauth_info) {  		char *full_header;  		char *full_url; -		full_url = g_strconcat(set_getstr(&ic->acc->set, "base_url"), url_string, NULL); +		if (base_url) +			full_url = g_strdup(url_string); +		else +			full_url = g_strconcat(set_getstr(&ic->acc->set, "base_url"), url_string, NULL);  		full_header = oauth_http_header(td->oauth_info, is_post ? "POST" : "GET",  						full_url, url_arguments); @@ -108,10 +123,23 @@ void *twitter_http(struct im_connection *ic, char *url_string, http_input_functi  		g_string_append(request, "\r\n");  	} -	ret = http_dorequest(td->url_host, td->url_port, td->url_ssl, request->str, func, data); +	if (base_url) +		ret = http_dorequest(base_url->host, base_url->port, base_url->proto == PROTO_HTTPS, request->str, func, data); +	else +		ret = http_dorequest(td->url_host, td->url_port, td->url_ssl, request->str, func, data);  	g_free(url_arguments);  	g_string_free(request, TRUE); +	g_free(base_url); +	return ret; +} + +struct http_request *twitter_http_f(struct im_connection *ic, char *url_string, http_input_function func, +		                    gpointer data, int is_post, char **arguments, int arguments_len, twitter_http_flags_t flags) +{ +	struct http_request *ret = twitter_http(ic, url_string, func, data, is_post, arguments, arguments_len); +	if (ret) +		ret->flags |= flags;  	return ret;  } diff --git a/protocols/twitter/twitter_http.h b/protocols/twitter/twitter_http.h index 393a1c26..09ef350c 100644 --- a/protocols/twitter/twitter_http.h +++ b/protocols/twitter/twitter_http.h @@ -27,10 +27,18 @@  #include "nogaim.h"  #include "http_client.h" +typedef enum { +	/* With this set, twitter_http_post() will post a generic confirmation +	   message to the user. */ +	TWITTER_HTTP_USER_ACK = 0x1000000, +} twitter_http_flags_t; +  struct oauth_info; -void *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, -                   gpointer data, int is_post, char** arguments, int arguments_len); +struct http_request *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, +                                  gpointer data, int is_post, char** arguments, int arguments_len); +struct http_request *twitter_http_f(struct im_connection *ic, char *url_string, http_input_function func, +                                    gpointer data, int is_post, char** arguments, int arguments_len, twitter_http_flags_t flags);  #endif //_TWITTER_HTTP_H diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index 4a09cbb1..3d368d2e 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -34,8 +34,8 @@  #include "url.h"  #include "misc.h"  #include "base64.h" -#include "xmltree.h"  #include "twitter_lib.h" +#include "json_util.h"  #include <ctype.h>  #include <errno.h> @@ -66,11 +66,10 @@ struct twitter_xml_status {  	time_t created_at;  	char *text;  	struct twitter_xml_user *user; -	guint64 id, reply_to; +	guint64 id, rt_id; /* Usually equal, with RTs id == *original* id */ +	guint64 reply_to;  }; -static void twitter_groupchat_init(struct im_connection *ic); -  /**   * Frees a twitter_xml_user struct.   */ @@ -147,17 +146,16 @@ static void twitter_add_buddy(struct im_connection *ic, char *name, const char *  	// Check if the buddy is already in the buddy list.  	if (!bee_user_by_handle(ic->bee, ic, name)) { -		char *mode = set_getstr(&ic->acc->set, "mode"); -  		// The buddy is not in the list, add the buddy and set the status to logged in.  		imcb_add_buddy(ic, name, NULL);  		imcb_rename_buddy(ic, name, fullname); -		if (g_strcasecmp(mode, "chat") == 0) { +		if (td->flags & TWITTER_MODE_CHAT) {  			/* Necessary so that nicks always get translated to the  			   exact Twitter username. */  			imcb_buddy_nick_hint(ic, name, name); -			imcb_chat_add_buddy(td->timeline_gc, name); -		} else if (g_strcasecmp(mode, "many") == 0) +			if (td->timeline_gc) +				imcb_chat_add_buddy(td->timeline_gc, name); +		} else if (td->flags & TWITTER_MODE_MANY)  			imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL);  	}  } @@ -167,32 +165,34 @@ static void twitter_add_buddy(struct im_connection *ic, char *name, const char *  char *twitter_parse_error(struct http_request *req)  {  	static char *ret = NULL; -	struct xt_node *root, *node, *err; +	json_value *root, *err;  	g_free(ret);  	ret = NULL;  	if (req->body_size > 0) { -		root = xt_from_string(req->reply_body, req->body_size); -		 -		for (node = root; node; node = node->next) -			if ((err = xt_find_node(node->children, "error")) && err->text_len > 0) { -				ret = g_strdup_printf("%s (%s)", req->status_string, err->text); -				break; -			} - -		xt_free_node(root); +		root = json_parse(req->reply_body); +		err = json_o_get(root, "errors"); +		if (err && err->type == json_array && (err = err->u.array.values[0]) && +		    err->type == json_object) { +			const char *msg = json_o_str(err, "message"); +			if (msg) +				ret = g_strdup_printf("%s (%s)", req->status_string, msg); +		} +		json_value_free(root);  	}  	return ret ? ret : req->status_string;  } -static struct xt_node *twitter_parse_response(struct im_connection *ic, struct http_request *req) +/* WATCH OUT: This function might or might not destroy your connection. +   Sub-optimal indeed, but just be careful when this returns NULL! */ +static json_value *twitter_parse_response(struct im_connection *ic, struct http_request *req)  {  	gboolean logging_in = !(ic->flags & OPT_LOGGED_IN);  	gboolean periodic;  	struct twitter_data *td = ic->proto_data; -	struct xt_node *ret; +	json_value *ret;  	char path[64] = "", *s;  	if ((s = strchr(req->request, ' '))) { @@ -210,14 +210,15 @@ static struct xt_node *twitter_parse_response(struct im_connection *ic, struct h  		/* IIRC Twitter once had an outage where they were randomly  		   throwing 401s so I'll keep treating this one as fatal  		   only during login. */ -		imcb_error(ic, "Authentication failure"); +		imcb_error(ic, "Authentication failure (%s)", +		               twitter_parse_error(req));  		imc_logout(ic, FALSE);  		return NULL;  	} else if (req->status_code != 200) {  		// It didn't go well, output the error and return.  		if (!periodic || logging_in || ++td->http_fails >= 5) -			imcb_error(ic, "Could not retrieve %s: %s", -				   path, twitter_parse_error(req)); +			twitter_log(ic, "Error: Could not retrieve %s: %s", +				    path, twitter_parse_error(req));  		if (logging_in)  			imc_logout(ic, TRUE); @@ -226,7 +227,7 @@ static struct xt_node *twitter_parse_response(struct im_connection *ic, struct h  		td->http_fails = 0;  	} -	if ((ret = xt_from_string(req->reply_body, req->body_size)) == NULL) { +	if ((ret = json_parse(req->reply_body)) == NULL) {  		imcb_error(ic, "Could not retrieve %s: %s",  			   path, "XML parse error");  	} @@ -250,45 +251,35 @@ void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor)  }  /** - * Function to help fill a list. - */ -static xt_status twitter_xt_next_cursor(struct xt_node *node, struct twitter_xml_list *txl) -{ -	char *end = NULL; - -	if (node->text) -		txl->next_cursor = g_ascii_strtoll(node->text, &end, 10); -	if (end == NULL) -		txl->next_cursor = -1; - -	return XT_HANDLED; -} - -/**   * Fill a list of ids.   */ -static xt_status twitter_xt_get_friends_id_list(struct xt_node *node, struct twitter_xml_list *txl) +static gboolean twitter_xt_get_friends_id_list(json_value *node, struct twitter_xml_list *txl)  { -	struct xt_node *child; +	json_value *c; +	int i;  	// Set the list type.  	txl->type = TXL_ID; -	// The root <statuses> node should hold the list of statuses <status> -	// Walk over the nodes children. -	for (child = node->children; child; child = child->next) { -		if (g_strcasecmp("ids", child->name) == 0) { -			struct xt_node *idc; -			for (idc = child->children; idc; idc = idc->next) -				if (g_strcasecmp(idc->name, "id") == 0) -					txl->list = g_slist_prepend(txl->list, -						g_memdup(idc->text, idc->text_len + 1)); -		} else if (g_strcasecmp("next_cursor", child->name) == 0) { -			twitter_xt_next_cursor(child, txl); -		} -	} +	c = json_o_get(node, "ids"); +	if (!c || c->type != json_array) +		return FALSE; -	return XT_HANDLED; +	for (i = 0; i < c->u.array.length; i ++) { +		if (c->u.array.values[i]->type != json_integer) +			continue; +		 +		txl->list = g_slist_prepend(txl->list, +			g_strdup_printf("%lld", c->u.array.values[i]->u.integer)); +	} +	 +	c = json_o_get(node, "next_cursor"); +	if (c && c->type == json_integer) +		txl->next_cursor = c->u.integer; +	else +		txl->next_cursor = -1; +	 +	return TRUE;  }  static void twitter_get_users_lookup(struct im_connection *ic); @@ -299,7 +290,7 @@ static void twitter_get_users_lookup(struct im_connection *ic);  static void twitter_http_get_friends_ids(struct http_request *req)  {  	struct im_connection *ic; -	struct xt_node *parsed; +	json_value *parsed;  	struct twitter_xml_list *txl;  	struct twitter_data *td; @@ -311,18 +302,15 @@ static void twitter_http_get_friends_ids(struct http_request *req)  	td = ic->proto_data; -	/* Create the room now that we "logged in". */ -	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);  	txl->list = td->follow_ids;  	// Parse the data.  	if (!(parsed = twitter_parse_response(ic, req)))  		return; +	  	twitter_xt_get_friends_id_list(parsed, txl); -	xt_free_node(parsed); +	json_value_free(parsed);  	td->follow_ids = txl->list;  	if (txl->next_cursor) @@ -337,7 +325,7 @@ static void twitter_http_get_friends_ids(struct http_request *req)  	txl_free(txl);  } -static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl); +static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl);  static void twitter_http_get_users_lookup(struct http_request *req);  static void twitter_get_users_lookup(struct im_connection *ic) @@ -378,7 +366,7 @@ static void twitter_get_users_lookup(struct im_connection *ic)  static void twitter_http_get_users_lookup(struct http_request *req)  {  	struct im_connection *ic = req->data; -	struct xt_node *parsed; +	json_value *parsed;  	struct twitter_xml_list *txl;  	GSList *l = NULL;  	struct twitter_xml_user *user; @@ -394,7 +382,7 @@ static void twitter_http_get_users_lookup(struct http_request *req)  	if (!(parsed = twitter_parse_response(ic, req)))  		return;  	twitter_xt_get_users(parsed, txl); -	xt_free_node(parsed); +	json_value_free(parsed);  	// Add the users as buddies.  	for (l = txl->list; l; l = g_slist_next(l)) { @@ -408,25 +396,15 @@ static void twitter_http_get_users_lookup(struct http_request *req)  	twitter_get_users_lookup(ic);  } -/** - * Function to fill a twitter_xml_user struct. - * It sets: - *  - the name and - *  - the screen_name. - */ -static xt_status twitter_xt_get_user(struct xt_node *node, struct twitter_xml_user *txu) +struct twitter_xml_user *twitter_xt_get_user(const json_value *node)  { -	struct xt_node *child; - -	// Walk over the nodes children. -	for (child = node->children; child; child = child->next) { -		if (g_strcasecmp("name", child->name) == 0) { -			txu->name = g_memdup(child->text, child->text_len + 1); -		} else if (g_strcasecmp("screen_name", child->name) == 0) { -			txu->screen_name = g_memdup(child->text, child->text_len + 1); -		} -	} -	return XT_HANDLED; +	struct twitter_xml_user *txu; +	 +	txu = g_new0(struct twitter_xml_user, 1); +	txu->name = g_strdup(json_o_str(node, "name")); +	txu->screen_name = g_strdup(json_o_str(node, "screen_name")); +	 +	return txu;  }  /** @@ -434,26 +412,26 @@ static xt_status twitter_xt_get_user(struct xt_node *node, struct twitter_xml_us   * It sets:   *  - all <user>s from the <users> element.   */ -static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl) +static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl)  {  	struct twitter_xml_user *txu; -	struct xt_node *child; +	int i;  	// Set the type of the list.  	txl->type = TXL_USER; +	if (!node || node->type != json_array) +		return FALSE; +  	// The root <users> node should hold the list of users <user>  	// Walk over the nodes children. -	for (child = node->children; child; child = child->next) { -		if (g_strcasecmp("user", child->name) == 0) { -			txu = g_new0(struct twitter_xml_user, 1); -			twitter_xt_get_user(child, txu); -			// Put the item in the front of the list. +	for (i = 0; i < node->u.array.length; i ++) { +		txu = twitter_xt_get_user(node->u.array.values[i]); +		if (txu)  			txl->list = g_slist_prepend(txl->list, txu); -		}  	} -	return XT_HANDLED; +	return TRUE;  }  #ifdef __GLIBC__ @@ -462,6 +440,8 @@ static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_l  #define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S +0000 %Y"  #endif +static char* expand_entities(char* text, const json_value *entities); +  /**   * Function to fill a twitter_xml_status struct.   * It sets: @@ -470,77 +450,133 @@ static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_l   *  - the status id and   *  - the user in a twitter_xml_user struct.   */ -static xt_status twitter_xt_get_status(struct xt_node *node, struct twitter_xml_status *txs) +static struct twitter_xml_status *twitter_xt_get_status(const json_value *node)  { -	struct xt_node *child, *rt = NULL; - -	// Walk over the nodes children. -	for (child = node->children; child; child = child->next) { -		if (g_strcasecmp("text", child->name) == 0) { -			txs->text = g_memdup(child->text, child->text_len + 1); -		} else if (g_strcasecmp("retweeted_status", child->name) == 0) { -			rt = child; -		} else if (g_strcasecmp("created_at", child->name) == 0) { +	struct twitter_xml_status *txs; +	const json_value *rt = NULL, *entities = NULL; +	 +	if (node->type != json_object) +		return FALSE; +	txs = g_new0(struct twitter_xml_status, 1); + +	JSON_O_FOREACH (node, k, v) { +		if (strcmp("text", k) == 0 && v->type == json_string) { +			txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1); +			strip_html(txs->text); +		} else if (strcmp("retweeted_status", k) == 0 && v->type == json_object) { +			rt = v; +		} else if (strcmp("created_at", k) == 0 && v->type == json_string) {  			struct tm parsed;  			/* Very sensitive to changes to the formatting of  			   this field. :-( Also assumes the timezone used  			   is UTC since C time handling functions suck. */ -			if (strptime(child->text, TWITTER_TIME_FORMAT, &parsed) != NULL) +			if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL)  				txs->created_at = mktime_utc(&parsed); -		} else if (g_strcasecmp("user", child->name) == 0) { -			txs->user = g_new0(struct twitter_xml_user, 1); -			twitter_xt_get_user(child, txs->user); -		} else if (g_strcasecmp("id", child->name) == 0) { -			txs->id = g_ascii_strtoull(child->text, NULL, 10); -		} else if (g_strcasecmp("in_reply_to_status_id", child->name) == 0) { -			txs->reply_to = g_ascii_strtoull(child->text, NULL, 10); +		} else if (strcmp("user", k) == 0 && v->type == json_object) { +			txs->user = twitter_xt_get_user(v); +		} else if (strcmp("id", k) == 0 && v->type == json_integer) { +			txs->rt_id = txs->id = v->u.integer; +		} else if (strcmp("in_reply_to_status_id", k) == 0 && v->type == json_integer) { +			txs->reply_to = v->u.integer; +		} else if (strcmp("entities", k) == 0 && v->type == json_object) { +			entities = v;  		}  	}  	/* If it's a (truncated) retweet, get the original. Even if the API claims it  	   wasn't truncated because it may be lying. */  	if (rt) { -		struct twitter_xml_status *rtxs = g_new0(struct twitter_xml_status, 1); -		if (twitter_xt_get_status(rt, rtxs) != XT_HANDLED) { +		struct twitter_xml_status *rtxs = twitter_xt_get_status(rt); +		if (rtxs) { +			g_free(txs->text); +			txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text); +			txs->id = rtxs->id;  			txs_free(rtxs); -			return XT_HANDLED;  		} +	} else if (entities) { +		txs->text = expand_entities(txs->text, entities); +	} -		g_free(txs->text); -		txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text); -		txs_free(rtxs); -	} else { -		struct xt_node *urls, *url; +	if (txs->text && txs->user && txs->id) +		return txs; +	 +	txs_free(txs); +	return NULL; +} + +/** + * Function to fill a twitter_xml_status struct (DM variant). + */ +static struct twitter_xml_status *twitter_xt_get_dm(const json_value *node) +{ +	struct twitter_xml_status *txs; +	const json_value *entities = NULL; +	 +	if (node->type != json_object) +		return FALSE; +	txs = g_new0(struct twitter_xml_status, 1); + +	JSON_O_FOREACH (node, k, v) { +		if (strcmp("text", k) == 0 && v->type == json_string) { +			txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1); +			strip_html(txs->text); +		} else if (strcmp("created_at", k) == 0 && v->type == json_string) { +			struct tm parsed; + +			/* Very sensitive to changes to the formatting of +			   this field. :-( Also assumes the timezone used +			   is UTC since C time handling functions suck. */ +			if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL) +				txs->created_at = mktime_utc(&parsed); +		} else if (strcmp("sender", k) == 0 && v->type == json_object) { +			txs->user = twitter_xt_get_user(v); +		} else if (strcmp("id", k) == 0 && v->type == json_integer) { +			txs->id = v->u.integer; +		} +	} + +	if (entities) { +		txs->text = expand_entities(txs->text, entities); +	} + +	if (txs->text && txs->user && txs->id) +		return txs; +	 +	txs_free(txs); +	return NULL; +} + +static char* expand_entities(char* text, const json_value *entities) { +	JSON_O_FOREACH (entities, k, v) { +		int i; +		 +		if (v->type != json_array) +			continue; +		if (strcmp(k, "urls") != 0 && strcmp(k, "media") != 0) +			continue; -		urls = xt_find_path(node, "entities"); -		if (urls != NULL) -			urls = urls->children; -		for (; urls; urls = urls->next) { -			if (strcmp(urls->name, "urls") != 0 && strcmp(urls->name, "media") != 0) +		for (i = 0; i < v->u.array.length; i ++) { +			if (v->u.array.values[i]->type != json_object)  				continue; -			for (url = urls ? urls->children : NULL; url; url = url->next) { -				/* "short" is a reserved word. :-P */ -				struct xt_node *kort = xt_find_node(url->children, "url"); -				struct xt_node *disp = xt_find_node(url->children, "display_url"); -				char *pos, *new; -				 -				if (!kort || !kort->text || !disp || !disp->text || -				    !(pos = strstr(txs->text, kort->text))) -					continue; -				 -				*pos = '\0'; -				new = g_strdup_printf("%s%s <%s>%s", txs->text, kort->text, -				                      disp->text, pos + strlen(kort->text)); -				 -				g_free(txs->text); -				txs->text = new; -			} +			const char *kort = json_o_str(v->u.array.values[i], "url"); +			const char *disp = json_o_str(v->u.array.values[i], "display_url"); +			char *pos, *new; +			 +			if (!kort || !disp || !(pos = strstr(text, kort))) +				continue; +			 +			*pos = '\0'; +			new = g_strdup_printf("%s%s <%s>%s", text, kort, +			                      disp, pos + strlen(kort)); +			 +			g_free(text); +			text = new;  		}  	} - -	return XT_HANDLED; +	 +	return text;  }  /** @@ -549,198 +585,332 @@ static xt_status twitter_xt_get_status(struct xt_node *node, struct twitter_xml_   *  - all <status>es within the <status> element and   *  - the next_cursor.   */ -static xt_status twitter_xt_get_status_list(struct im_connection *ic, struct xt_node *node, -					    struct twitter_xml_list *txl) +static gboolean twitter_xt_get_status_list(struct im_connection *ic, const json_value *node, +                                           struct twitter_xml_list *txl)  {  	struct twitter_xml_status *txs; -	struct xt_node *child; -	bee_user_t *bu; +	int i;  	// Set the type of the list.  	txl->type = TXL_STATUS; +	 +	if (node->type != json_array) +		return FALSE;  	// The root <statuses> node should hold the list of statuses <status>  	// Walk over the nodes children. -	for (child = node->children; child; child = child->next) { -		if (g_strcasecmp("status", child->name) == 0) { -			txs = g_new0(struct twitter_xml_status, 1); -			twitter_xt_get_status(child, txs); -			// Put the item in the front of the list. -			txl->list = g_slist_prepend(txl->list, txs); - -			if (txs->user && txs->user->screen_name && -			    (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) { -				struct twitter_user_data *tud = bu->data; - -				if (txs->id > tud->last_id) { -					tud->last_id = txs->id; -					tud->last_time = txs->created_at; -				} -			} -		} else if (g_strcasecmp("next_cursor", child->name) == 0) { -			twitter_xt_next_cursor(child, txl); -		} +	for (i = 0; i < node->u.array.length; i ++) { +		txs = twitter_xt_get_status(node->u.array.values[i]); +		if (!txs) +			continue; +		 +		txl->list = g_slist_prepend(txl->list, txs);  	} -	return XT_HANDLED; +	return TRUE;  } +/* Will log messages either way. Need to keep track of IDs for stream deduping. +   Plus, show_ids is on by default and I don't see why anyone would disable it. */  static char *twitter_msg_add_id(struct im_connection *ic,  				struct twitter_xml_status *txs, const char *prefix)  {  	struct twitter_data *td = ic->proto_data; -	char *ret = NULL; - -	if (!set_getbool(&ic->acc->set, "show_ids")) { -		if (*prefix) -			return g_strconcat(prefix, txs->text, NULL); -		else -			return NULL; -	} +	int reply_to = -1; +	bee_user_t *bu; -	td->log[td->log_id].id = txs->id; -	td->log[td->log_id].bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name);  	if (txs->reply_to) {  		int i;  		for (i = 0; i < TWITTER_LOG_LENGTH; i++)  			if (td->log[i].id == txs->reply_to) { -				ret = g_strdup_printf("\002[\002%02d->%02d\002]\002 %s%s", -						      td->log_id, i, prefix, txs->text); +				reply_to = i;  				break;  			}  	} -	if (ret == NULL) -		ret = g_strdup_printf("\002[\002%02d\002]\002 %s%s", td->log_id, prefix, txs->text); -	td->log_id = (td->log_id + 1) % TWITTER_LOG_LENGTH; -	return ret; -} +	if (txs->user && txs->user->screen_name && +	    (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) { +		struct twitter_user_data *tud = bu->data; -static void twitter_groupchat_init(struct im_connection *ic) -{ -	char *name_hint; -	struct groupchat *gc; -	struct twitter_data *td = ic->proto_data; -	GSList *l; - -	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); -	g_free(name_hint); - -	for (l = ic->bee->users; l; l = l->next) { -		bee_user_t *bu = l->data; -		if (bu->ic == ic) -			imcb_chat_add_buddy(td->timeline_gc, bu->handle); +		if (txs->id > tud->last_id) { +			tud->last_id = txs->id; +			tud->last_time = txs->created_at; +		} +	} +	 +	td->log_id = (td->log_id + 1) % TWITTER_LOG_LENGTH; +	td->log[td->log_id].id = txs->id; +	td->log[td->log_id].bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name); +	 +	/* This is all getting hairy. :-( If we RT'ed something ourselves, +	   remember OUR id instead so undo will work. In other cases, the +	   original tweet's id should be remembered for deduplicating. */ +	if (strcmp(txs->user->screen_name, td->user) == 0) +		td->log[td->log_id].id = txs->rt_id; +	 +	if (set_getbool(&ic->acc->set, "show_ids")) { +		if (reply_to != -1) +			return g_strdup_printf("\002[\002%02x->%02x\002]\002 %s%s", +			                       td->log_id, reply_to, prefix, txs->text); +		else +			return g_strdup_printf("\002[\002%02x\002]\002 %s%s", +			                       td->log_id, prefix, txs->text); +	} else { +		if (*prefix) +			return g_strconcat(prefix, txs->text, NULL); +		else +			return NULL;  	}  }  /**   * Function that is called to see the statuses in a groupchat window.   */ -static void twitter_groupchat(struct im_connection *ic, GSList * list) +static void twitter_status_show_chat(struct im_connection *ic, struct twitter_xml_status *status)  {  	struct twitter_data *td = ic->proto_data; -	GSList *l = NULL; -	struct twitter_xml_status *status;  	struct groupchat *gc; -	guint64 last_id = 0; +	gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0; +	char *msg;  	// Create a new groupchat if it does not exsist. -	if (!td->timeline_gc) -		twitter_groupchat_init(ic); - -	gc = td->timeline_gc; -	if (!gc->joined) -		imcb_chat_add_buddy(gc, ic->acc->user); - -	for (l = list; l; l = g_slist_next(l)) { -		char *msg; - -		status = l->data; -		if (status->user == NULL || status->text == NULL || last_id == status->id) -			continue; - -		last_id = status->id; - -		strip_html(status->text); - -		if (set_getbool(&ic->acc->set, "strip_newlines")) -			strip_newlines(status->text); - -		msg = twitter_msg_add_id(ic, status, ""); - -		// Say it! -		if (g_strcasecmp(td->user, status->user->screen_name) == 0) { -			imcb_chat_log(gc, "You: %s", msg ? msg : status->text); -		} else { -			twitter_add_buddy(ic, status->user->screen_name, status->user->name); +	gc = twitter_groupchat_init(ic); -			imcb_chat_msg(gc, status->user->screen_name, -				      msg ? msg : status->text, 0, status->created_at); -		} - -		g_free(msg); - -		// 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->timeline_id = MAX(td->timeline_id, status->id); +	if (!me) +		/* MUST be done before twitter_msg_add_id() to avoid #872. */ +		twitter_add_buddy(ic, status->user->screen_name, status->user->name); +	msg = twitter_msg_add_id(ic, status, ""); +	 +	// Say it! +	if (me) { +		imcb_chat_log(gc, "You: %s", msg ? msg : status->text); +	} else { +		imcb_chat_msg(gc, status->user->screen_name, +			      msg ? msg : status->text, 0, status->created_at);  	} + +	g_free(msg);  }  /**   * Function that is called to see statuses as private messages.   */ -static void twitter_private_message_chat(struct im_connection *ic, GSList * list) +static void twitter_status_show_msg(struct im_connection *ic, struct twitter_xml_status *status)  {  	struct twitter_data *td = ic->proto_data; -	GSList *l = NULL; -	struct twitter_xml_status *status; -	char from[MAX_STRING]; -	gboolean mode_one; -	guint64 last_id = 0; +	char from[MAX_STRING] = ""; +	char *prefix = NULL, *text = NULL; +	gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0; -	mode_one = g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "one") == 0; - -	if (mode_one) { +	if (td->flags & TWITTER_MODE_ONE) {  		g_snprintf(from, sizeof(from) - 1, "%s_%s", td->prefix, ic->acc->user);  		from[MAX_STRING - 1] = '\0';  	} -	for (l = list; l; l = g_slist_next(l)) { -		char *prefix = NULL, *text = NULL; +	if (td->flags & TWITTER_MODE_ONE) +		prefix = g_strdup_printf("\002<\002%s\002>\002 ", +		                         status->user->screen_name); +	else if (!me) +		twitter_add_buddy(ic, status->user->screen_name, status->user->name); +	else +		prefix = g_strdup("You: "); -		status = l->data; -		if (status->user == NULL || status->text == NULL || last_id == status->id) -			continue; +	text = twitter_msg_add_id(ic, status, prefix ? prefix : ""); -		last_id = status->id; +	imcb_buddy_msg(ic, +	               *from ? from : status->user->screen_name, +	               text ? text : status->text, 0, status->created_at); -		strip_html(status->text); -		if (mode_one) -			prefix = g_strdup_printf("\002<\002%s\002>\002 ", -						 status->user->screen_name); -		else -			twitter_add_buddy(ic, status->user->screen_name, status->user->name); +	g_free(text); +	g_free(prefix); +} -		text = twitter_msg_add_id(ic, status, prefix ? prefix : ""); +static void twitter_status_show(struct im_connection *ic, struct twitter_xml_status *status) +{ +	struct twitter_data *td = ic->proto_data; +	 +	if (status->user == NULL || status->text == NULL) +		return; +	 +	/* Grrrr. Would like to do this during parsing, but can't access +	   settings from there. */ +	if (set_getbool(&ic->acc->set, "strip_newlines")) +		strip_newlines(status->text); +	 +	if (td->flags & TWITTER_MODE_CHAT) +		twitter_status_show_chat(ic, status); +	else +		twitter_status_show_msg(ic, status); -		imcb_buddy_msg(ic, -			       mode_one ? from : status->user->screen_name, -			       text ? text : status->text, 0, status->created_at); +	// 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->timeline_id = MAX(td->timeline_id, status->rt_id); +} -		// 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->timeline_id = MAX(td->timeline_id, status->id); +static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o); -		g_free(text); -		g_free(prefix); +static void twitter_http_stream(struct http_request *req) +{ +	struct im_connection *ic = req->data; +	struct twitter_data *td; +	json_value *parsed; +	int len = 0; +	char c, *nl; +	 +	if (!g_slist_find(twitter_connections, ic)) +		return; +	 +	ic->flags |= OPT_PONGED; +	td = ic->proto_data; +	 +	if ((req->flags & HTTPC_EOF) || !req->reply_body) { +		td->stream = NULL; +		imcb_error(ic, "Stream closed (%s)", req->status_string); +		imc_logout(ic, TRUE); +		return; +	} +	 +	printf( "%d bytes in stream\n", req->body_size ); +	 +	/* MUST search for CRLF, not just LF: +	   https://dev.twitter.com/docs/streaming-apis/processing#Parsing_responses */ +	nl = strstr(req->reply_body, "\r\n"); +	 +	if (!nl) { +		printf("Incomplete data\n"); +		return; +	} +	 +	len = nl - req->reply_body; +	if (len > 0) { +		c = req->reply_body[len]; +		req->reply_body[len] = '\0'; +		 +		printf("JSON: %s\n", req->reply_body); +		printf("parsed: %p\n", (parsed = json_parse(req->reply_body))); +		if (parsed) { +			twitter_stream_handle_object(ic, parsed); +		} +		json_value_free(parsed); +		req->reply_body[len] = c;  	} +	 +	http_flush_bytes(req, len + 2); +	 +	/* One notification might bring multiple events! */ +	if (req->body_size > 0) +		twitter_http_stream(req);  } -static void twitter_http_get_home_timeline(struct http_request *req); -static void twitter_http_get_mentions(struct http_request *req); +static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o); +static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs); + +static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o) +{ +	struct twitter_data *td = ic->proto_data; +	struct twitter_xml_status *txs; +	json_value *c; +	 +	if ((txs = twitter_xt_get_status(o))) { +		gboolean ret = twitter_stream_handle_status(ic, txs); +		txs_free(txs); +		return ret; +	} else if ((c = json_o_get(o, "direct_message")) && +	           (txs = twitter_xt_get_dm(c))) { +		if (strcmp(txs->user->screen_name, td->user) != 0) +			imcb_buddy_msg(ic, txs->user->screen_name, +				       txs->text, 0, txs->created_at); +		txs_free(txs); +		return TRUE; +	} else if ((c = json_o_get(o, "event")) && c->type == json_string) { +		twitter_stream_handle_event(ic, o); +		return TRUE; +	} else if ((c = json_o_get(o, "disconnect")) && c->type == json_object) { +		/* HACK: Because we're inside an event handler, we can't just +		   disconnect here. Instead, just change the HTTP status string +		   into a Twitter status string. */ +		char *reason = json_o_strdup(c, "reason"); +		if (reason) { +			g_free(td->stream->status_string); +			td->stream->status_string = reason; +		} +		return TRUE; +	} +	return FALSE; +} + +static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs) +{ +	struct twitter_data *td = ic->proto_data; +	int i; +	 +	for (i = 0; i < TWITTER_LOG_LENGTH; i++) { +		if (td->log[i].id == txs->id) { +			/* Got a duplicate (RT, probably). Drop it. */ +			return TRUE; +		} +	} +	 +	if (!(strcmp(txs->user->screen_name, td->user) == 0 || +	      set_getbool(&ic->acc->set, "fetch_mentions") || +	      bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) { +		/* Tweet is from an unknown person and the user does not want +		   to see @mentions, so drop it. twitter_stream_handle_event() +		   picks up new follows so this simple filter should be safe. */ +		/* TODO: The streaming API seems to do poor @mention matching. +		   I.e. I'm getting mentions for @WilmerSomething, not just for +		   @Wilmer. But meh. You want spam, you get spam. */ +		return TRUE; +	} +	 +	twitter_status_show(ic, txs); +	 +	return TRUE; +} + +static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o) +{ +	struct twitter_data *td = ic->proto_data; +	json_value *source = json_o_get(o, "source"); +	json_value *target = json_o_get(o, "target"); +	const char *type = json_o_str(o, "event"); +	 +	if (!type || !source || source->type != json_object +	          || !target || target->type != json_object) { +		return FALSE; +	} +	 +	if (strcmp(type, "follow") == 0) { +		struct twitter_xml_user *us = twitter_xt_get_user(source); +		struct twitter_xml_user *ut = twitter_xt_get_user(target); +		if (strcmp(us->screen_name, td->user) == 0) { +			twitter_add_buddy(ic, ut->screen_name, ut->name); +		} +		txu_free(us); +		txu_free(ut); +	} +	 +	return TRUE; +} + +gboolean twitter_open_stream(struct im_connection *ic) +{ +	struct twitter_data *td = ic->proto_data; +	char *args[2] = {"with", "followings"}; +	 +	if ((td->stream = twitter_http(ic, TWITTER_USER_STREAM_URL, +	                               twitter_http_stream, ic, 0, args, 2))) { +		/* This flag must be enabled or we'll get no data until EOF +		   (which err, kind of, defeats the purpose of a streaming API). */ +		td->stream->flags |= HTTPC_STREAMING; +		return TRUE; +	} +	 +	return FALSE; +} + +static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor); +static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor);  /**   * Get the timeline with optionally mentions @@ -777,9 +947,12 @@ void twitter_flush_timeline(struct im_connection *ic)  	int show_old_mentions = set_getint(&ic->acc->set, "show_old_mentions");  	struct twitter_xml_list *home_timeline = td->home_timeline_obj;  	struct twitter_xml_list *mentions = td->mentions_obj; +	guint64 last_id = 0;  	GSList *output = NULL;  	GSList *l; +	imcb_connected(ic); +	  	if (!(td->flags & TWITTER_GOT_TIMELINE)) {  		return;  	} @@ -803,17 +976,15 @@ void twitter_flush_timeline(struct im_connection *ic)  			output = g_slist_insert_sorted(output, l->data, twitter_compare_elements);  		}  	} -	 -	if (!(ic->flags & OPT_LOGGED_IN)) -		imcb_connected(ic);  	// 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); +	while (output) { +		struct twitter_xml_status *txs = output->data; +		if (txs->id != last_id) +			twitter_status_show(ic, txs); +		last_id = txs->id; +		output = g_slist_remove(output, txs); +	}  	txl_free(home_timeline);  	txl_free(mentions); @@ -822,10 +993,13 @@ void twitter_flush_timeline(struct im_connection *ic)  	td->home_timeline_obj = td->mentions_obj = NULL;  } +static void twitter_http_get_home_timeline(struct http_request *req); +static void twitter_http_get_mentions(struct http_request *req); +  /**   * Get the timeline.   */ -void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor) +static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)  {  	struct twitter_data *td = ic->proto_data; @@ -861,7 +1035,7 @@ void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)  /**   * Get mentions.   */ -void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor) +static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)  {  	struct twitter_data *td = ic->proto_data; @@ -892,9 +1066,7 @@ void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)  	}  	g_free(args[1]); -	if (td->timeline_id) { -		g_free(args[5]); -	} +	g_free(args[5]);  }  /** @@ -904,7 +1076,7 @@ static void twitter_http_get_home_timeline(struct http_request *req)  {  	struct im_connection *ic = req->data;  	struct twitter_data *td; -	struct xt_node *parsed; +	json_value *parsed;  	struct twitter_xml_list *txl;  	// Check if the connection is still active. @@ -920,11 +1092,14 @@ static void twitter_http_get_home_timeline(struct http_request *req)  	if (!(parsed = twitter_parse_response(ic, req)))  		goto end;  	twitter_xt_get_status_list(ic, parsed, txl); -	xt_free_node(parsed); +	json_value_free(parsed);  	td->home_timeline_obj = txl;        end: +	if (!g_slist_find(twitter_connections, ic)) +		return; +  	td->flags |= TWITTER_GOT_TIMELINE;  	twitter_flush_timeline(ic); @@ -937,7 +1112,7 @@ static void twitter_http_get_mentions(struct http_request *req)  {  	struct im_connection *ic = req->data;  	struct twitter_data *td; -	struct xt_node *parsed; +	json_value *parsed;  	struct twitter_xml_list *txl;  	// Check if the connection is still active. @@ -953,11 +1128,14 @@ static void twitter_http_get_mentions(struct http_request *req)  	if (!(parsed = twitter_parse_response(ic, req)))  		goto end;  	twitter_xt_get_status_list(ic, parsed, txl); -	xt_free_node(parsed); +	json_value_free(parsed);  	td->mentions_obj = txl;        end: +	if (!g_slist_find(twitter_connections, ic)) +		return; +  	td->flags |= TWITTER_GOT_MENTIONS;  	twitter_flush_timeline(ic); @@ -971,7 +1149,7 @@ static void twitter_http_post(struct http_request *req)  {  	struct im_connection *ic = req->data;  	struct twitter_data *td; -	struct xt_node *parsed, *node; +	json_value *parsed, *id;  	// Check if the connection is still active.  	if (!g_slist_find(twitter_connections, ic)) @@ -983,9 +1161,14 @@ static void twitter_http_post(struct http_request *req)  	if (!(parsed = twitter_parse_response(ic, req)))  		return; -	if ((node = xt_find_node(parsed, "status")) && -	    (node = xt_find_node(node->children, "id")) && node->text) -		td->last_status_id = g_ascii_strtoull(node->text, NULL, 10); +	if ((id = json_o_get(parsed, "id")) && id->type == json_integer) { +		td->last_status_id = id->u.integer; +	} +	 +	json_value_free(parsed); +	 +	if (req->flags & TWITTER_HTTP_USER_ACK) +		twitter_log(ic, "Command processed successfully");  }  /** @@ -1031,8 +1214,9 @@ void twitter_status_destroy(struct im_connection *ic, guint64 id)  {  	char *url;  	url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_DESTROY_URL, -	                      (unsigned long long) id, ".xml"); -	twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0); +	                      (unsigned long long) id, ".json"); +	twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0, +	               TWITTER_HTTP_USER_ACK);  	g_free(url);  } @@ -1040,8 +1224,9 @@ void twitter_status_retweet(struct im_connection *ic, guint64 id)  {  	char *url;  	url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_RETWEET_URL, -	                      (unsigned long long) id, ".xml"); -	twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0); +	                      (unsigned long long) id, ".json"); +	twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0, +	               TWITTER_HTTP_USER_ACK);  	g_free(url);  } @@ -1055,8 +1240,8 @@ void twitter_report_spam(struct im_connection *ic, char *screen_name)  		NULL,  	};  	args[1] = screen_name; -	twitter_http(ic, TWITTER_REPORT_SPAM_URL, twitter_http_post, -	             ic, 1, args, 2); +	twitter_http_f(ic, TWITTER_REPORT_SPAM_URL, twitter_http_post, +	               ic, 1, args, 2, TWITTER_HTTP_USER_ACK);  }  /** @@ -1066,7 +1251,8 @@ void twitter_favourite_tweet(struct im_connection *ic, guint64 id)  {  	char *url;  	url = g_strdup_printf("%s%llu%s", TWITTER_FAVORITE_CREATE_URL, -	                      (unsigned long long) id, ".xml"); -	twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0); +	                      (unsigned long long) id, ".json"); +	twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0, +	               TWITTER_HTTP_USER_ACK);  	g_free(url);  } diff --git a/protocols/twitter/twitter_lib.h b/protocols/twitter/twitter_lib.h index 2404e4eb..d5f5b16a 100644 --- a/protocols/twitter/twitter_lib.h +++ b/protocols/twitter/twitter_lib.h @@ -28,46 +28,46 @@  #include "nogaim.h"  #include "twitter_http.h" -#define TWITTER_API_URL "http://api.twitter.com/1" +#define TWITTER_API_URL "http://api.twitter.com/1.1"  #define IDENTICA_API_URL "https://identi.ca/api"  /* Status URLs */ -#define TWITTER_STATUS_UPDATE_URL "/statuses/update.xml" +#define TWITTER_STATUS_UPDATE_URL "/statuses/update.json"  #define TWITTER_STATUS_SHOW_URL "/statuses/show/"  #define TWITTER_STATUS_DESTROY_URL "/statuses/destroy/"  #define TWITTER_STATUS_RETWEET_URL "/statuses/retweet/"  /* Timeline URLs */ -#define TWITTER_PUBLIC_TIMELINE_URL "/statuses/public_timeline.xml" -#define TWITTER_FEATURED_USERS_URL "/statuses/featured.xml" -#define TWITTER_FRIENDS_TIMELINE_URL "/statuses/friends_timeline.xml" -#define TWITTER_HOME_TIMELINE_URL "/statuses/home_timeline.xml" -#define TWITTER_MENTIONS_URL "/statuses/mentions.xml" -#define TWITTER_USER_TIMELINE_URL "/statuses/user_timeline.xml" +#define TWITTER_PUBLIC_TIMELINE_URL "/statuses/public_timeline.json" +#define TWITTER_FEATURED_USERS_URL "/statuses/featured.json" +#define TWITTER_FRIENDS_TIMELINE_URL "/statuses/friends_timeline.json" +#define TWITTER_HOME_TIMELINE_URL "/statuses/home_timeline.json" +#define TWITTER_MENTIONS_URL "/statuses/mentions_timeline.json" +#define TWITTER_USER_TIMELINE_URL "/statuses/user_timeline.json"  /* Users URLs */ -#define TWITTER_USERS_LOOKUP_URL "/users/lookup.xml" +#define TWITTER_USERS_LOOKUP_URL "/users/lookup.json"  /* Direct messages URLs */ -#define TWITTER_DIRECT_MESSAGES_URL "/direct_messages.xml" -#define TWITTER_DIRECT_MESSAGES_NEW_URL "/direct_messages/new.xml" -#define TWITTER_DIRECT_MESSAGES_SENT_URL "/direct_messages/sent.xml" +#define TWITTER_DIRECT_MESSAGES_URL "/direct_messages.json" +#define TWITTER_DIRECT_MESSAGES_NEW_URL "/direct_messages/new.json" +#define TWITTER_DIRECT_MESSAGES_SENT_URL "/direct_messages/sent.json"  #define TWITTER_DIRECT_MESSAGES_DESTROY_URL "/direct_messages/destroy/"  /* Friendships URLs */ -#define TWITTER_FRIENDSHIPS_CREATE_URL "/friendships/create.xml" -#define TWITTER_FRIENDSHIPS_DESTROY_URL "/friendships/destroy.xml" -#define TWITTER_FRIENDSHIPS_SHOW_URL "/friendships/show.xml" +#define TWITTER_FRIENDSHIPS_CREATE_URL "/friendships/create.json" +#define TWITTER_FRIENDSHIPS_DESTROY_URL "/friendships/destroy.json" +#define TWITTER_FRIENDSHIPS_SHOW_URL "/friendships/show.json"  /* Social graphs URLs */ -#define TWITTER_FRIENDS_IDS_URL "/friends/ids.xml" -#define TWITTER_FOLLOWERS_IDS_URL "/followers/ids.xml" +#define TWITTER_FRIENDS_IDS_URL "/friends/ids.json" +#define TWITTER_FOLLOWERS_IDS_URL "/followers/ids.json"  /* Account URLs */ -#define TWITTER_ACCOUNT_RATE_LIMIT_URL "/account/rate_limit_status.xml" +#define TWITTER_ACCOUNT_RATE_LIMIT_URL "/account/rate_limit_status.json"  /* Favorites URLs */ -#define TWITTER_FAVORITES_GET_URL "/favorites.xml" +#define TWITTER_FAVORITES_GET_URL "/favorites.json"  #define TWITTER_FAVORITE_CREATE_URL "/favorites/create/"  #define TWITTER_FAVORITE_DESTROY_URL "/favorites/destroy/" @@ -76,12 +76,13 @@  #define TWITTER_BLOCKS_DESTROY_URL "/blocks/destroy/"  /* Report spam */ -#define TWITTER_REPORT_SPAM_URL "/report_spam.xml" +#define TWITTER_REPORT_SPAM_URL "/report_spam.json" +#define TWITTER_USER_STREAM_URL "https://userstream.twitter.com/1.1/user.json" + +gboolean twitter_open_stream(struct im_connection *ic);  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); | 
