Index: protocols/twitter/twitter_lib.c
===================================================================
--- protocols/twitter/twitter_lib.c	(revision 85c30041680c8147b8f320ba3eab912027cb0b3e)
+++ protocols/twitter/twitter_lib.c	(revision ce402b20d82ec323e6bd5e306de934773590742d)
@@ -51,4 +51,5 @@
 
 struct twitter_xml_user {
+	guint64 uid;
 	char *name;
 	char *screen_name;
@@ -61,4 +62,6 @@
 	guint64 id, rt_id; /* Usually equal, with RTs id == *original* id */
 	guint64 reply_to;
+	gboolean from_filter;
+	struct twitter_xml_status *rt;
 };
 
@@ -86,4 +89,5 @@
 	g_free(txs->text);
 	txu_free(txs->user);
+	txs_free(txs->rt);
 	g_free(txs);
 }
@@ -392,8 +396,12 @@
 {
 	struct twitter_xml_user *txu;
+	json_value *jv;
 	
 	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"));
+	
+	jv = json_o_get(node, "id");
+	txu->uid = jv->u.integer;
 	
 	return txu;
@@ -483,7 +491,7 @@
 		if (rtxs) {
 			g_free(txs->text);
-			txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text);
+			txs->text = g_strdup(rtxs->text);
 			txs->id = rtxs->id;
-			txs_free(rtxs);
+			txs->rt = rtxs;
 		}
 	} else if (entities) {
@@ -603,4 +611,47 @@
 }
 
+/**
+ * Function to properly format a tweet as per the users configuration.
+ */
+static char *twitter_msg_get_text(struct im_connection *ic, int log_id, int reply_to,
+				struct twitter_xml_status *txs, const char *prefix) {
+	gchar * format = set_getstr(&ic->acc->set, "format_string");
+	GString * text = g_string_new(NULL);
+
+	gchar *c;
+	if (reply_to != -1)
+		format = set_getstr(&ic->acc->set, "reply_format_string");
+	if (txs->rt)
+		format = set_getstr(&ic->acc->set, "retweet_format_string");
+
+	for (c = format; *c ; c++) {
+		if (!(*c == '%' && *(c+1))) {
+			text = g_string_append_c(text, *c);
+			continue;
+		}
+		c++; // Move past the %
+		switch (*c) {
+			case 'i':
+				g_string_append_printf(text, "%02x", log_id);
+				break;
+			case 'r':
+				if (reply_to != -1) // In case someone does put %r in the wrong format_string
+				g_string_append_printf(text, "%02x", reply_to);
+				break;
+			case 'a':
+				if (txs->rt) // In case someone does put %a in the wrong format_string
+					text = g_string_append(text, txs->rt->user->screen_name);
+				break;
+			case 'c':
+				text = g_string_append(text, txs->text);
+				break;
+			default:
+				text = g_string_append_c(text, *c);
+		}
+	}
+	text = g_string_prepend(text, prefix);
+	return g_string_free(text, FALSE);
+}
+
 /* 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. */
@@ -641,17 +692,43 @@
 		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;
-	}
+	return twitter_msg_get_text(ic, td->log_id, reply_to, txs, prefix);
+}
+
+/**
+ * Function that is called to see the filter statuses in groupchat windows.
+ */
+static void twitter_status_show_filter(struct im_connection *ic, struct twitter_xml_status *status)
+{
+	struct twitter_data *td = ic->proto_data;
+	char *msg = twitter_msg_add_id(ic, status, "");
+	struct twitter_filter *tf;
+	GSList *f;
+	GSList *l;
+
+	for (f = td->filters; f; f = g_slist_next(f)) {
+		tf = f->data;
+
+		switch (tf->type) {
+		case TWITTER_FILTER_TYPE_FOLLOW:
+			if (status->user->uid != tf->uid)
+				continue;
+			break;
+
+		case TWITTER_FILTER_TYPE_TRACK:
+			if (strcasestr(status->text, tf->text) == NULL)
+				continue;
+			break;
+
+		default:
+			continue;
+		}
+
+		for (l = tf->groupchats; l; l = g_slist_next(l)) {
+			imcb_chat_msg(l->data, status->user->screen_name,
+				      msg ? msg : status->text, 0, 0);
+		}
+	}
+
+	g_free(msg);
 }
 
@@ -731,5 +808,7 @@
 		strip_newlines(status->text);
 	
-	if (td->flags & TWITTER_MODE_CHAT)
+	if (status->from_filter)
+		twitter_status_show_filter(ic, status);
+	else if (td->flags & TWITTER_MODE_CHAT)
 		twitter_status_show_chat(ic, status);
 	else
@@ -745,5 +824,5 @@
 }
 
-static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o);
+static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o, gboolean from_filter);
 
 static void twitter_http_stream(struct http_request *req)
@@ -754,4 +833,5 @@
 	int len = 0;
 	char c, *nl;
+	gboolean from_filter;
 	
 	if (!g_slist_find(twitter_connections, ic))
@@ -762,5 +842,9 @@
 	
 	if ((req->flags & HTTPC_EOF) || !req->reply_body) {
-		td->stream = NULL;
+		if (req == td->stream)
+			td->stream = NULL;
+		else if (req == td->filter_stream)
+			td->filter_stream = NULL;
+
 		imcb_error(ic, "Stream closed (%s)", req->status_string);
 		imc_logout(ic, TRUE);
@@ -779,5 +863,6 @@
 		
 		if ((parsed = json_parse(req->reply_body, req->body_size))) {
-			twitter_stream_handle_object(ic, parsed);
+			from_filter = (req == td->filter_stream);
+			twitter_stream_handle_object(ic, parsed, from_filter);
 		}
 		json_value_free(parsed);
@@ -795,5 +880,5 @@
 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)
+static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o, gboolean from_filter)
 {
 	struct twitter_data *td = ic->proto_data;
@@ -802,4 +887,5 @@
 	
 	if ((txs = twitter_xt_get_status(o))) {
+		txs->from_filter = from_filter;
 		gboolean ret = twitter_stream_handle_status(ic, txs);
 		txs_free(txs);
@@ -899,4 +985,167 @@
 }
 
+static gboolean twitter_filter_stream(struct im_connection *ic)
+{
+	struct twitter_data *td = ic->proto_data;
+	char *args[4] = {"follow", NULL, "track", NULL};
+	GString *followstr = g_string_new("");
+	GString *trackstr = g_string_new("");
+	gboolean ret = FALSE;
+	struct twitter_filter *tf;
+	GSList *l;
+
+	for (l = td->filters; l; l = g_slist_next(l)) {
+		tf = l->data;
+
+		switch (tf->type) {
+		case TWITTER_FILTER_TYPE_FOLLOW:
+			if (followstr->len > 0)
+				g_string_append_c(followstr, ',');
+
+			g_string_append_printf(followstr, "%" G_GUINT64_FORMAT,
+					       tf->uid);
+			break;
+
+		case TWITTER_FILTER_TYPE_TRACK:
+			if (trackstr->len > 0)
+				g_string_append_c(trackstr, ',');
+
+			g_string_append(trackstr, tf->text);
+			break;
+
+		default:
+			continue;
+		}
+	}
+
+	args[1] = followstr->str;
+	args[3] = trackstr->str;
+
+	if (td->filter_stream)
+		http_close(td->filter_stream);
+
+	if ((td->filter_stream = twitter_http(ic, TWITTER_FILTER_STREAM_URL,
+	                                      twitter_http_stream, ic, 0,
+					      args, 4))) {
+		/* 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->filter_stream->flags |= HTTPC_STREAMING;
+		ret = TRUE;
+	}
+
+	g_string_free(followstr, TRUE);
+	g_string_free(trackstr, TRUE);
+
+	return ret;
+}
+
+static void twitter_filter_users_post(struct http_request *req)
+{
+	struct im_connection *ic = req->data;
+	struct twitter_data *td;
+	struct twitter_filter *tf;
+	GList *users = NULL;
+	json_value *parsed;
+	json_value *id;
+	const char *name;
+	GString *fstr;
+	GSList *l;
+	GList *u;
+	int i;
+
+	// Check if the connection is still active.
+	if (!g_slist_find(twitter_connections, ic))
+		return;
+
+	td = ic->proto_data;
+
+	if (!(parsed = twitter_parse_response(ic, req)))
+		return;
+
+	for (l = td->filters; l; l = g_slist_next(l)) {
+		tf = l->data;
+
+		if (tf->type == TWITTER_FILTER_TYPE_FOLLOW)
+			users = g_list_prepend(users, tf);
+	}
+
+	if (parsed->type != json_array)
+		goto finish;
+
+	for (i = 0; i < parsed->u.array.length; i++) {
+		id = json_o_get(parsed->u.array.values[i], "id");
+		name = json_o_str(parsed->u.array.values[i], "screen_name");
+
+		if (!name || !id || id->type != json_integer)
+			continue;
+
+		for (u = users; u; u = g_list_next(u)) {
+			tf = u->data;
+
+			if (g_strcasecmp(tf->text, name) == 0) {
+				tf->uid = id->u.integer;
+				users = g_list_delete_link(users, u);
+				break;
+			}
+		}
+	}
+
+finish:
+	json_value_free(parsed);
+	twitter_filter_stream(ic);
+
+	if (!users)
+		return;
+
+	fstr = g_string_new("");
+
+	for (u = users; u; u = g_list_next(u)) {
+		if (fstr->len > 0)
+			g_string_append(fstr, ", ");
+
+		g_string_append(fstr, tf->text);
+	}
+
+	imcb_error(ic, "Failed UID acquisitions: %s", fstr->str);
+
+	g_string_free(fstr, TRUE);
+	g_list_free(users);
+}
+
+gboolean twitter_open_filter_stream(struct im_connection *ic)
+{
+	struct twitter_data *td = ic->proto_data;
+	char *args[2] = {"screen_name", NULL};
+	GString *ustr = g_string_new("");
+	struct twitter_filter *tf;
+	struct http_request *req;
+	GSList *l;
+
+	for (l = td->filters; l; l = g_slist_next(l)) {
+		tf = l->data;
+
+		if (tf->type != TWITTER_FILTER_TYPE_FOLLOW || tf->uid != 0)
+			continue;
+
+		if (ustr->len > 0)
+			g_string_append_c(ustr, ',');
+
+		g_string_append(ustr, tf->text);
+	}
+
+	if (ustr->len == 0) {
+		g_string_free(ustr, TRUE);
+		return twitter_filter_stream(ic);
+	}
+
+	args[1] = ustr->str;
+	req = twitter_http(ic, TWITTER_USERS_LOOKUP_URL,
+			   twitter_filter_users_post,
+			   ic, 0, args, 2);
+
+	g_string_free(ustr, TRUE);
+	return req != NULL;
+}
+
 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);
