diff options
Diffstat (limited to 'protocols/twitter')
| -rw-r--r-- | protocols/twitter/Makefile | 6 | ||||
| -rw-r--r-- | protocols/twitter/twitter.c | 173 | ||||
| -rw-r--r-- | protocols/twitter/twitter.h | 9 | ||||
| -rw-r--r-- | protocols/twitter/twitter_lib.c | 132 | ||||
| -rw-r--r-- | protocols/twitter/twitter_lib.h | 6 | 
5 files changed, 297 insertions, 29 deletions
| diff --git a/protocols/twitter/Makefile b/protocols/twitter/Makefile index ca1e4695..3fa9b61e 100644 --- a/protocols/twitter/Makefile +++ b/protocols/twitter/Makefile @@ -7,11 +7,13 @@  ### DEFINITIONS  -include ../../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)protocols/twitter/ +endif  # [SH] Program variables  objects = twitter.o twitter_http.o twitter_lib.o -CFLAGS += -Wall  LFLAGS += -r  # [SH] Phony targets @@ -32,7 +34,7 @@ distclean: clean  $(objects): ../../Makefile.settings Makefile -$(objects): %.o: %.c +$(objects): %.o: $(SRCDIR)%.c  	@echo '*' Compiling $<  	@$(CC) -c $(CFLAGS) $< -o $@ diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c index fca619c3..16b069ee 100644 --- a/protocols/twitter/twitter.c +++ b/protocols/twitter/twitter.c @@ -118,7 +118,7 @@ static gboolean twitter_oauth_callback( struct oauth_info *info )  			return FALSE;  		} -		sprintf( name, "twitter_%s", ic->acc->user ); +		sprintf( name, "%s_%s", td->prefix, ic->acc->user );  		msg = g_strdup_printf( "To finish OAuth authentication, please visit "  		                       "%s and respond with the resulting PIN code.",  		                       info->auth_url ); @@ -171,16 +171,33 @@ static gboolean twitter_length_check( struct im_connection *ic, gchar *msg )  static void twitter_init( account_t *acc )  {  	set_t *s; +	char *def_url; +	char *def_oauth; -	s = set_add( &acc->set, "base_url", TWITTER_API_URL, NULL, acc ); +	if( strcmp( acc->prpl->name, "twitter" ) == 0 ) +	{ +		def_url = TWITTER_API_URL; +		def_oauth = "true"; +	} +	else /* if( strcmp( acc->prpl->name, "identica" ) == 0 ) */ +	{ +		def_url = IDENTICA_API_URL; +		def_oauth = "false"; +	} +	 +	s = set_add( &acc->set, "auto_reply_timeout", "10800", set_eval_int, 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, "message_length", "140", set_eval_int, acc );  	s = set_add( &acc->set, "mode", "one", set_eval_mode, acc );  	s->flags |= ACC_SET_OFFLINE_ONLY; -	s = set_add( &acc->set, "oauth", "true", set_eval_bool, acc ); +	s = set_add( &acc->set, "oauth", def_oauth, set_eval_bool, acc );  }  /** @@ -213,12 +230,16 @@ static void twitter_login( account_t *acc )  		td->url_path = g_strdup( url.file );  	else  		td->url_path = g_strdup( "" ); +	if( g_str_has_suffix( url.host, ".com" ) ) +		td->prefix = g_strndup( url.host, strlen( url.host ) - 4 ); +	else +		td->prefix = g_strdup( url.host );  	td->user = acc->user;  	if( strstr( acc->pass, "oauth_token=" ) )  		td->oauth_info = oauth_from_string( acc->pass, &twitter_oauth ); -	sprintf( name, "twitter_%s", acc->user ); +	sprintf( name, "%s_%s", td->prefix, acc->user );  	imcb_add_buddy( ic, name, NULL );  	imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL ); @@ -235,7 +256,7 @@ static void twitter_logout( struct im_connection *ic )  	struct twitter_data *td = ic->proto_data;  	// Set the status to logged out. -	ic->flags = 0; +	ic->flags &= ~ OPT_LOGGED_IN;  	// Remove the main_loop function from the function queue.  	b_event_remove(td->main_loop_id); @@ -246,6 +267,7 @@ static void twitter_logout( struct im_connection *ic )  	if( td )  	{  		oauth_info_free( td->oauth_info ); +		g_free( td->prefix );  		g_free( td->url_host );  		g_free( td->url_path );  		g_free( td->pass ); @@ -255,15 +277,18 @@ static void twitter_logout( struct im_connection *ic )  	twitter_connections = g_slist_remove( twitter_connections, ic );  } +static void twitter_handle_command( struct im_connection *ic, char *message ); +  /**   *   */  static int twitter_buddy_msg( struct im_connection *ic, char *who, char *message, int away )  {  	struct twitter_data *td = ic->proto_data; +	int plen = strlen( td->prefix ); -	if (g_strncasecmp(who, "twitter_", 8) == 0 && -	    g_strcasecmp(who + 8, ic->acc->user) == 0) +	if (g_strncasecmp(who, td->prefix, plen) == 0 && who[plen] == '_' && +	    g_strcasecmp(who + plen + 1, ic->acc->user) == 0)  	{  		if( set_getbool( &ic->acc->set, "oauth" ) &&  		    td->oauth_info && td->oauth_info->token == NULL ) @@ -282,8 +307,8 @@ static int twitter_buddy_msg( struct im_connection *ic, char *who, char *message  				return FALSE;  			}  		} -		else if( twitter_length_check(ic, message) ) -			twitter_post_status(ic, message); +		else +			twitter_handle_command(ic, message);  	}  	else  	{ @@ -315,8 +340,8 @@ static void twitter_remove_buddy( struct im_connection *ic, char *who, char *gro  static void twitter_chat_msg( struct groupchat *c, char *message, int flags )  { -	if( c && message && twitter_length_check(c->ic, message)) -		twitter_post_status(c->ic, message); +	if( c && message ) +		twitter_handle_command( c->ic, message );  }  static void twitter_chat_invite( struct groupchat *c, char *who, char *message ) @@ -371,6 +396,124 @@ static int twitter_send_typing( struct im_connection *ic, char *who, int typing  //	return value;  //} +static void twitter_buddy_data_add( struct bee_user *bu ) +{ +	bu->data = g_new0( struct twitter_user_data, 1 ); +} + +static void twitter_buddy_data_free( struct bee_user *bu ) +{ +	g_free( bu->data ); +} + +static void twitter_handle_command( struct im_connection *ic, char *message ) +{ +	struct twitter_data *td = ic->proto_data; +	char *cmds, **cmd; +	 +	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. */ +	} +	else if( g_strcasecmp( cmd[0], "undo" ) == 0 ) +	{ +		guint64 id; +		 +		if( cmd[1] ) +			id = g_ascii_strtoull( cmd[1], NULL, 10 ); +		else +			id = td->last_status_id; +		 +		/* TODO: User feedback. */ +		if( id ) +			twitter_status_destroy( ic, id ); +		 +		g_free( cmds ); +		return; +	} +	else if( g_strcasecmp( cmd[0], "follow" ) == 0 && cmd[1] ) +	{ +		twitter_add_buddy( ic, cmd[1], NULL ); +		g_free( cmds ); +		return; +	} +	else if( g_strcasecmp( cmd[0], "unfollow" ) == 0 && cmd[1] ) +	{ +		twitter_remove_buddy( ic, cmd[1], NULL ); +		g_free( cmds ); +		return; +	} +	else if( g_strcasecmp( cmd[0], "rt" ) == 0 && cmd[1] ) +	{ +		struct twitter_user_data *tud; +		bee_user_t *bu; +		guint64 id; +		 +		if( ( bu = bee_user_by_handle( ic->bee, ic, cmd[1] ) ) && +		    ( tud = bu->data ) && tud->last_id ) +			id = tud->last_id; +		else +			id = g_ascii_strtoull( cmd[1], NULL, 10 ); +		 +		td->last_status_id = 0; +		if( id ) +			twitter_status_retweet( ic, id ); +		 +		g_free( cmds ); +		return; +	} +	else if( g_strcasecmp( cmd[0], "post" ) == 0 ) +	{ +		message += 5; +	} +	 +	{ +		guint64 in_reply_to = 0; +		char *s, *new = NULL; +		bee_user_t *bu; +		 +		if( !twitter_length_check( ic, message ) ) +		{ +			g_free( cmds ); +		  	return; +		} +		 +		s = cmd[0] + strlen( cmd[0] ) - 1; +		if( s > cmd[0] && ( *s == ':' || *s == ',' ) ) +		{ +			*s = '\0'; +			 +			if( ( bu = bee_user_by_handle( ic->bee, ic, cmd[0] ) ) ) +			{ +				struct twitter_user_data *tud = bu->data; +				 +				new = g_strdup_printf( "@%s %s", bu->handle, +				                       message + ( s - cmd[0] ) + 2 ); +				message = new; +				 +				if( time( NULL ) < tud->last_time + +				    set_getint( &ic->acc->set, "auto_reply_timeout" ) ) +					in_reply_to = tud->last_id; +			} +		} +		 +		/* If the user runs undo between this request and its response +		   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 ); +	} +	g_free( cmds ); +} +  void twitter_initmodule()  {  	struct prpl *ret = g_new0(struct prpl, 1); @@ -395,11 +538,17 @@ void twitter_initmodule()  	ret->add_deny = twitter_add_deny;  	ret->rem_deny = twitter_rem_deny;  	ret->send_typing = twitter_send_typing; +	ret->buddy_data_add = twitter_buddy_data_add; +	ret->buddy_data_free = twitter_buddy_data_free;  	ret->handle_cmp = g_strcasecmp; +	 +	register_protocol(ret); +	/* And an identi.ca variant: */ +	ret = g_memdup(ret, sizeof(struct prpl)); +	ret->name = "identica";  	register_protocol(ret);  	// Initialise the twitter_connections GSList.  	twitter_connections = NULL;  } - diff --git a/protocols/twitter/twitter.h b/protocols/twitter/twitter.h index e61d32be..0acb3b4d 100644 --- a/protocols/twitter/twitter.h +++ b/protocols/twitter/twitter.h @@ -43,6 +43,7 @@ struct twitter_data  	char* pass;  	struct oauth_info *oauth_info;  	guint64 home_timeline_id; +	guint64 last_status_id; /* For undo */  	gint main_loop_id;  	struct groupchat *home_timeline_gc;  	gint http_fails; @@ -52,6 +53,14 @@ struct twitter_data  	int url_port;  	char *url_host;  	char *url_path; + +	char *prefix; /* Used to generate contact + channel name. */ +}; + +struct twitter_user_data +{ +	guint64 last_id; +	time_t last_time;  };  /** diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index d1b65c26..f86e1f15 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -22,7 +22,10 @@  ****************************************************************************/  /* For strptime(): */ +#if(__sun) +#else  #define _XOPEN_SOURCE +#endif  #include "twitter_http.h"  #include "twitter.h" @@ -35,6 +38,14 @@  #include <ctype.h>  #include <errno.h> +/* GLib < 2.12.0 doesn't have g_ascii_strtoll(), work around using system strtoll(). */ +/* GLib < 2.12.4 can be buggy: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=488013 */ +#if !GLIB_CHECK_VERSION(2,12,5) +#include <stdlib.h> +#include <limits.h> +#define g_ascii_strtoll strtoll +#endif +  #define TXL_STATUS 1  #define TXL_USER 2  #define TXL_ID 3 @@ -65,6 +76,8 @@ static void twitter_groupchat_init(struct im_connection *ic);   */  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); @@ -88,6 +101,8 @@ static void txs_free(struct twitter_xml_status *txs)  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)  			txs_free((struct twitter_xml_status *)l->data); @@ -104,7 +119,7 @@ static void twitter_add_buddy(struct im_connection *ic, char *name, const char *  	struct twitter_data *td = ic->proto_data;  	// Check if the buddy is allready in the buddy list. -	if (!imcb_find_buddy( ic, name )) +	if (!bee_user_by_handle( ic->bee, ic, name ))  	{  		char *mode = set_getstr(&ic->acc->set, "mode"); @@ -112,7 +127,12 @@ static void twitter_add_buddy(struct im_connection *ic, char *name, const char *  		imcb_add_buddy( ic, name, NULL );  		imcb_rename_buddy( ic, name, fullname );  		if (g_strcasecmp(mode, "chat") == 0) +		{ +			/* 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 ); +		}  		else if (g_strcasecmp(mode, "many") == 0)  			imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL );  	} @@ -336,6 +356,11 @@ static xt_status twitter_xt_get_user_list( struct xt_node *node, struct twitter_  	return XT_HANDLED;  } +#ifdef __GLIBC__ +#define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S %z %Y" +#else +#define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S +0000 %Y" +#endif  /**   * Function to fill a twitter_xml_status struct. @@ -347,7 +372,8 @@ static xt_status twitter_xt_get_user_list( struct xt_node *node, struct twitter_   */  static xt_status twitter_xt_get_status( struct xt_node *node, struct twitter_xml_status *txs )  { -	struct xt_node *child; +	struct xt_node *child, *rt = NULL; +	gboolean truncated = FALSE;  	// Walk over the nodes children.  	for( child = node->children ; child ; child = child->next ) @@ -356,6 +382,14 @@ static xt_status twitter_xt_get_status( struct xt_node *node, struct twitter_xml  		{  			txs->text = g_memdup( child->text, child->text_len + 1 );  		} +		else if (g_strcasecmp( "truncated", child->name ) == 0 && child->text) +		{ +			truncated = bool2int(child->text); +		} +		else if (g_strcasecmp( "retweeted_status", child->name ) == 0) +		{ +			rt = child; +		}  		else if (g_strcasecmp( "created_at", child->name ) == 0)  		{  			struct tm parsed; @@ -363,7 +397,7 @@ static xt_status twitter_xt_get_status( struct xt_node *node, struct twitter_xml  			/* 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, "%a %b %d %H:%M:%S %z %Y", &parsed ) != NULL ) +			if( strptime( child->text, TWITTER_TIME_FORMAT, &parsed ) != NULL )  				txs->created_at = mktime_utc( &parsed );  		}  		else if (g_strcasecmp( "user", child->name ) == 0) @@ -376,6 +410,22 @@ static xt_status twitter_xt_get_status( struct xt_node *node, struct twitter_xml  			txs->id = g_ascii_strtoull (child->text, NULL, 10);  		}  	} +	 +	/* If it's a truncated retweet, get the original because dots suck. */ +	if (truncated && rt) +	{ +		struct twitter_xml_status *rtxs = g_new0(struct twitter_xml_status, 1); +		if (twitter_xt_get_status(rt, rtxs) != XT_HANDLED) +		{ +			txs_free(rtxs); +			return XT_HANDLED; +		} +		 +		g_free(txs->text); +		txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text); +		txs_free(rtxs); +	} +	  	return XT_HANDLED;  } @@ -385,10 +435,11 @@ 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 xt_node *node, struct twitter_xml_list *txl ) +static xt_status twitter_xt_get_status_list( struct im_connection *ic, struct xt_node *node, struct twitter_xml_list *txl )  {  	struct twitter_xml_status *txs;  	struct xt_node *child; +	bee_user_t *bu;  	// Set the type of the list.  	txl->type = TXL_STATUS; @@ -403,6 +454,18 @@ static xt_status twitter_xt_get_status_list( struct xt_node *node, struct twitte  			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)  		{ @@ -446,7 +509,7 @@ static void twitter_groupchat_init(struct im_connection *ic)  	td->home_timeline_gc = gc = imcb_chat_new( ic, "home/timeline" ); -	name_hint = g_strdup_printf( "Twitter_%s", ic->acc->user ); +	name_hint = g_strdup_printf( "%s_%s", td->prefix, ic->acc->user );  	imcb_chat_name_hint( gc, name_hint );  	g_free( name_hint );  } @@ -472,6 +535,9 @@ static void twitter_groupchat(struct im_connection *ic, GSList *list)  	for ( l = list; l ; l = g_slist_next(l) )  	{  		status = l->data; +		if (status->user == NULL || status->text == NULL) +			continue; +  		twitter_add_buddy(ic, status->user->screen_name, status->user->name);  		strip_html(status->text); @@ -503,7 +569,7 @@ static void twitter_private_message_chat(struct im_connection *ic, GSList *list)  	if( mode_one )  	{ -		g_snprintf( from, sizeof( from ) - 1, "twitter_%s", ic->acc->user ); +		g_snprintf( from, sizeof( from ) - 1, "%s_%s", td->prefix, ic->acc->user );  		from[MAX_STRING-1] = '\0';  	} @@ -578,7 +644,7 @@ static void twitter_http_get_home_timeline(struct http_request *req)  	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(parser->root, txl); +	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. @@ -687,29 +753,51 @@ void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor)  static void twitter_http_post(struct http_request *req)  {  	struct im_connection *ic = req->data; +	struct twitter_data *td;  	// Check if the connection is still active.  	if( !g_slist_find( twitter_connections, ic ) )  		return; +	td = ic->proto_data; +	td->last_status_id = 0; +	  	// Check if the HTTP request went well.  	if (req->status_code != 200) {  		// It didn't go well, output the error and return.  		imcb_error(ic, "HTTP error: %s", twitter_parse_error(req));  		return;  	} +	 +	if (req->body_size > 0) +	{ +		struct xt_parser *xp = NULL; +		struct xt_node *node; +		 +		xp = xt_new(NULL, NULL); +		xt_feed(xp, req->reply_body, req->body_size); +		 +		if ((node = xt_find_node(xp->root, "status")) && +		    (node = xt_find_node(node->children, "id")) && node->text) +			td->last_status_id = g_ascii_strtoull( node->text, NULL, 10 ); +		 +		xt_free(xp); +	}  }  /**   * Function to POST a new status to twitter.   */  -void twitter_post_status(struct im_connection *ic, char* msg) +void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to)  { -	char* args[2]; -	args[0] = "status"; -	args[1] = msg; -	twitter_http(ic, TWITTER_STATUS_UPDATE_URL, twitter_http_post, ic, 1, args, 2); -//	g_free(args[1]); +	char* args[4] = { +		"status", msg, +		"in_reply_to_status_id", +		g_strdup_printf("%llu", (unsigned long long) in_reply_to) +	}; +	twitter_http(ic, TWITTER_STATUS_UPDATE_URL, twitter_http_post, ic, 1, +	             args, in_reply_to ? 4 : 2); +	g_free(args[3]);  } @@ -735,4 +823,20 @@ void twitter_friendships_create_destroy(struct im_connection *ic, char *who, int  	args[0] = "screen_name";  	args[1] = who;  	twitter_http(ic, create ? TWITTER_FRIENDSHIPS_CREATE_URL : TWITTER_FRIENDSHIPS_DESTROY_URL, twitter_http_post, ic, 1, args, 2); -}
\ No newline at end of file +} + +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); +	g_free(url); +} + +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); +	g_free(url); +} diff --git a/protocols/twitter/twitter_lib.h b/protocols/twitter/twitter_lib.h index 6b90f9bb..24b4a089 100644 --- a/protocols/twitter/twitter_lib.h +++ b/protocols/twitter/twitter_lib.h @@ -29,11 +29,13 @@  #include "twitter_http.h"  #define TWITTER_API_URL "http://twitter.com" +#define IDENTICA_API_URL "http://identi.ca/api"  /* Status URLs */  #define TWITTER_STATUS_UPDATE_URL "/statuses/update.xml"  #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" @@ -79,9 +81,11 @@ 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_statuses_friends(struct im_connection *ic, gint64 next_cursor); -void twitter_post_status(struct im_connection *ic, char *msg); +void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to);  void twitter_direct_messages_new(struct im_connection *ic, char *who, char *message);  void twitter_friendships_create_destroy(struct im_connection *ic, char *who, int create); +void twitter_status_destroy(struct im_connection *ic, guint64 id); +void twitter_status_retweet(struct im_connection *ic, guint64 id);  #endif //_TWITTER_LIB_H | 
