diff options
| -rw-r--r-- | doc/user-guide/commands.xml | 3 | ||||
| -rw-r--r-- | protocols/twitter/twitter.c | 29 | ||||
| -rw-r--r-- | protocols/twitter/twitter.h | 12 | ||||
| -rw-r--r-- | protocols/twitter/twitter_lib.c | 56 | 
4 files changed, 77 insertions, 23 deletions
| diff --git a/doc/user-guide/commands.xml b/doc/user-guide/commands.xml index 6ce19774..4d6e7659 100644 --- a/doc/user-guide/commands.xml +++ b/doc/user-guide/commands.xml @@ -120,7 +120,7 @@  			<description>  				<para> -					This commands deletes an account from your account list. You should signoff the account before deleting it. +					This command deletes an account from your account list. You should signoff the account before deleting it.  				</para> @@ -852,6 +852,7 @@  				<varlistentry><term>unfollow <screenname></term><listitem><para>Stop following a person</para></listitem></varlistentry>  				<varlistentry><term>favourite <screenname|#id></term><listitem><para>Favo<emphasis>u</emphasis>rite the given user's most recent tweet, or the given tweet ID.</para></listitem></varlistentry>  				<varlistentry><term>post <message></term><listitem><para>Post a tweet</para></listitem></varlistentry> +				<varlistentry><term>url <screenname|#id></term><listitem><para>Show URL for a tweet to open it in a browser (and see context)</para></listitem></varlistentry>  			</variablelist>  			<para> diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c index 95384573..421a0552 100644 --- a/protocols/twitter/twitter.c +++ b/protocols/twitter/twitter.c @@ -866,6 +866,8 @@ static gboolean twitter_parse_id(char *string, int base, guint64 *id)  	return TRUE;  } +bee_user_t twitter_log_local_user; +  /** Convert the given bitlbee tweet ID, bitlbee username, or twitter tweet ID   *  into a twitter tweet ID.   * @@ -896,10 +898,6 @@ static guint64 twitter_message_id_from_command_arg(struct im_connection *ic, cha  		if (twitter_parse_id(arg, 16, &id) && 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 (twitter_parse_id(arg, 10, &id)) {  			/* Allow normal tweet IDs as well; not a very useful  			   feature but it's always been there. Just ignore @@ -910,6 +908,16 @@ static guint64 twitter_message_id_from_command_arg(struct im_connection *ic, cha  		}  	}  	if (bu_) { +		if (bu == &twitter_log_local_user) { +			/* HACK alert. There's no bee_user object for the local +			 * user so just fake one for the few cmds that need it. */ +			twitter_log_local_user.handle = td->user; +		} else { +			/* Beware of dangling pointers! */ +			if (!g_slist_find(ic->bee->users, bu)) { +				bu = NULL; +			} +		}  		*bu_ = bu;  	}  	return id; @@ -1002,6 +1010,19 @@ static void twitter_handle_command(struct im_connection *ic, char *message)  		message = cmd[2];  		in_reply_to = id;  		allow_post = TRUE; +	} else if (g_strcasecmp(cmd[0], "url") == 0) { +		id = twitter_message_id_from_command_arg(ic, cmd[1], &bu); +		if (!id) { +			twitter_log(ic, "Tweet `%s' does not exist", cmd[1]); +		} else { +			/* More common link is twitter.com/$UID/status/$ID (and that's +			 * what this will 302 to) but can't generate that since for RTs, +			 * bu here points at the retweeter while id contains the id of +			 * the original message. */ +			twitter_log(ic, "https://twitter.com/statuses/%lld", id); +		} +		goto eof; +  	} else if (g_strcasecmp(cmd[0], "post") == 0) {  		message += 5;  		allow_post = TRUE; diff --git a/protocols/twitter/twitter.h b/protocols/twitter/twitter.h index 7cfd9148..5a1a4f63 100644 --- a/protocols/twitter/twitter.h +++ b/protocols/twitter/twitter.h @@ -100,7 +100,9 @@ struct twitter_user_data {  #define TWITTER_LOG_LENGTH 256  struct twitter_log_data {  	guint64 id; -	struct bee_user *bu; /* DANGER: can be a dead pointer. Check it first. */ +	/* DANGER: bu can be a dead pointer. Check it first. +	 * twitter_message_id_from_command_arg() will do this. */ +	struct bee_user *bu;  };  /** @@ -110,6 +112,14 @@ struct twitter_log_data {   */  extern GSList *twitter_connections; +/** + * Evil hack: Fake bee_user which will always point at the local user. + * Sometimes used as a return value by twitter_message_id_from_command_arg. + * NOT thread safe but don't you dare to even think of ever making BitlBee + * threaded. :-) + */ +extern bee_user_t twitter_log_local_user; +  void twitter_login_finish(struct im_connection *ic);  struct http_request; diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index c20f8295..17bceb9f 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -459,7 +459,7 @@ static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *  #define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S +0000 %Y"  #endif -static char* expand_entities(char* text, const json_value *entities); +static void expand_entities(char **text, const json_value *node);  /**   * Function to fill a twitter_xml_status struct. @@ -472,7 +472,7 @@ static char* expand_entities(char* text, const json_value *entities);  static struct twitter_xml_status *twitter_xt_get_status(const json_value *node)  {  	struct twitter_xml_status *txs; -	const json_value *rt = NULL, *entities = NULL; +	const json_value *rt = NULL;  	if (node->type != json_object) {  		return FALSE; @@ -500,8 +500,6 @@ static struct twitter_xml_status *twitter_xt_get_status(const json_value *node)  			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;  		}  	} @@ -515,8 +513,8 @@ static struct twitter_xml_status *twitter_xt_get_status(const json_value *node)  			txs->id = rtxs->id;  			txs_free(rtxs);  		} -	} else if (entities) { -		txs->text = expand_entities(txs->text, entities); +	} else { +		expand_entities(&txs->text, node);  	}  	if (txs->text && txs->user && txs->id) { @@ -533,7 +531,6 @@ static struct twitter_xml_status *twitter_xt_get_status(const json_value *node)  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; @@ -560,9 +557,7 @@ static struct twitter_xml_status *twitter_xt_get_dm(const json_value *node)  		}  	} -	if (entities) { -		txs->text = expand_entities(txs->text, entities); -	} +	expand_entities(&txs->text, node);  	if (txs->text && txs->user && txs->id) {  		return txs; @@ -572,8 +567,26 @@ static struct twitter_xml_status *twitter_xt_get_dm(const json_value *node)  	return NULL;  } -static char* expand_entities(char* text, const json_value *entities) +static void expand_entities(char **text, const json_value *node)  { +	json_value *entities, *quoted; +	char *quote_url = NULL, *quote_text = NULL; + +	if (!((entities = json_o_get(node, "entities")) && entities->type == json_object)) +		return; +	if ((quoted = json_o_get(node, "quoted_status")) && quoted->type == json_object) { +		/* New "retweets with comments" feature. Note that this info +		 * seems to be included in the streaming API only! Grab the +		 * full message and try to insert it when we run into the +		 * Tweet entity. */ +		struct twitter_xml_status *txs = twitter_xt_get_status(quoted); +		quote_text = g_strdup_printf("@%s: %s", txs->user->screen_name, txs->text); +		quote_url = g_strdup_printf("%s/status/%" G_GUINT64_FORMAT, txs->user->screen_name, txs->id); +		txs_free(txs); +	} else { +		quoted = NULL; +	} +  	JSON_O_FOREACH(entities, k, v) {  		int i; @@ -585,28 +598,35 @@ static char* expand_entities(char* text, const json_value *entities)  		}  		for (i = 0; i < v->u.array.length; i++) { +			const char *format = "%s%s <%s>%s"; +  			if (v->u.array.values[i]->type != json_object) {  				continue;  			}  			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"); +			const char *full = json_o_str(v->u.array.values[i], "expanded_url");  			char *pos, *new; -			if (!kort || !disp || !(pos = strstr(text, kort))) { +			if (!kort || !disp || !(pos = strstr(*text, kort))) {  				continue;  			} +			if (quote_url && strstr(full, quote_url)) { +				format = "%s<%s> [%s]%s"; +				disp = quote_text; +			}  			*pos = '\0'; -			new = g_strdup_printf("%s%s <%s>%s", text, kort, +			new = g_strdup_printf(format, *text, kort,  			                      disp, pos + strlen(kort)); -			g_free(text); -			text = new; +			g_free(*text); +			*text = new;  		}  	} - -	return text; +	g_free(quote_text); +	g_free(quote_url);  }  /** @@ -680,6 +700,8 @@ static char *twitter_msg_add_id(struct im_connection *ic,  	   original tweet's id should be remembered for deduplicating. */  	if (g_strcasecmp(txs->user->screen_name, td->user) == 0) {  		td->log[td->log_id].id = txs->rt_id; +		/* More useful than NULL. */ +		td->log[td->log_id].bu = &twitter_log_local_user;  	}  	if (set_getbool(&ic->acc->set, "show_ids")) { | 
