Index: protocols/twitter/twitter.c
===================================================================
--- protocols/twitter/twitter.c	(revision 7549d0074aa4917e62106ac285b05baa1e76c1e9)
+++ protocols/twitter/twitter.c	(revision ce402b20d82ec323e6bd5e306de934773590742d)
@@ -31,4 +31,213 @@
 
 GSList *twitter_connections = NULL;
+
+static int twitter_filter_cmp(struct twitter_filter *tf1,
+                              struct twitter_filter *tf2)
+{
+	int i1 = 0;
+	int i2 = 0;
+	int i;
+
+	static const twitter_filter_type_t types[] = {
+		/* Order of the types */
+		TWITTER_FILTER_TYPE_FOLLOW,
+		TWITTER_FILTER_TYPE_TRACK
+	};
+
+	for (i = 0; i < G_N_ELEMENTS(types); i++) {
+		if (types[i] == tf1->type) {
+			i1 = i + 1;
+			break;
+		}
+	}
+
+	for (i = 0; i < G_N_ELEMENTS(types); i++) {
+		if (types[i] == tf2->type) {
+			i2 = i + 1;
+			break;
+		}
+	}
+
+	if (i1 != i2) {
+		/* With different types, return their difference */
+		return i1 - i2;
+	}
+
+	/* With the same type, return the text comparison */
+	return g_strcasecmp(tf1->text, tf2->text);
+}
+
+static gboolean twitter_filter_update(gpointer data, gint fd,
+                                      b_input_condition cond)
+{
+	struct im_connection *ic = data;
+	struct twitter_data *td = ic->proto_data;
+
+	if (td->filters) {
+		twitter_open_filter_stream(ic);
+	} else if (td->filter_stream) {
+		http_close(td->filter_stream);
+		td->filter_stream = NULL;
+	}
+
+	td->filter_update_id = 0;
+	return FALSE;
+}
+
+static struct twitter_filter *twitter_filter_get(struct groupchat *c,
+                                                 twitter_filter_type_t type,
+                                                 const char *text)
+{
+	struct twitter_data *td = c->ic->proto_data;
+	struct twitter_filter *tf = NULL;
+	struct twitter_filter tfc = {type, (char*) text};
+	GSList *l;
+
+	for (l = td->filters; l; l = g_slist_next(l)) {
+		tf = l->data;
+
+		if (twitter_filter_cmp(tf, &tfc) == 0)
+			break;
+
+		tf = NULL;
+	}
+
+	if (!tf) {
+		tf = g_new0(struct twitter_filter, 1);
+		tf->type = type;
+		tf->text = g_strdup(text);
+		td->filters = g_slist_prepend(td->filters, tf);
+	}
+
+	if (!g_slist_find(tf->groupchats, c))
+		tf->groupchats = g_slist_prepend(tf->groupchats, c);
+
+	if (td->filter_update_id > 0)
+		b_event_remove(td->filter_update_id);
+
+	/* Wait for other possible filter changes to avoid request spam */
+	td->filter_update_id = b_timeout_add(TWITTER_FILTER_UPDATE_WAIT,
+					     twitter_filter_update, c->ic);
+	return tf;
+}
+
+static void twitter_filter_free(struct twitter_filter *tf)
+{
+	g_slist_free(tf->groupchats);
+	g_free(tf->text);
+	g_free(tf);
+}
+
+static void twitter_filter_remove(struct groupchat *c)
+{
+	struct twitter_data *td = c->ic->proto_data;
+	struct twitter_filter *tf;
+	GSList *l = td->filters;
+	GSList *p;
+
+	while (l != NULL) {
+		tf = l->data;
+		tf->groupchats = g_slist_remove(tf->groupchats, c);
+
+		p = l;
+		l = g_slist_next(l);
+
+		if (!tf->groupchats) {
+			twitter_filter_free(tf);
+			td->filters = g_slist_delete_link(td->filters, p);
+		}
+	}
+
+	if (td->filter_update_id > 0)
+		b_event_remove(td->filter_update_id);
+
+	/* Wait for other possible filter changes to avoid request spam */
+	td->filter_update_id = b_timeout_add(TWITTER_FILTER_UPDATE_WAIT,
+					     twitter_filter_update, c->ic);}
+
+static void twitter_filter_remove_all(struct im_connection *ic)
+{
+	struct twitter_data *td = ic->proto_data;
+	GSList *chats = NULL;
+	struct twitter_filter *tf;
+	GSList *l = td->filters;
+	GSList *p;
+
+	while (l != NULL) {
+		tf = l->data;
+
+		/* Build up a list of groupchats to be freed */
+		for (p = tf->groupchats; p; p = g_slist_next(p)) {
+			if (!g_slist_find(chats, p->data))
+				chats = g_slist_prepend(chats, p->data);
+		}
+
+		p = l;
+		l = g_slist_next(l);
+		twitter_filter_free(p->data);
+		td->filters = g_slist_delete_link(td->filters, p);
+	}
+
+	l = chats;
+
+	while (l != NULL) {
+		p = l;
+		l = g_slist_next(l);
+
+		/* Freed each remaining groupchat */
+		imcb_chat_free(p->data);
+		chats = g_slist_delete_link(chats, p);
+	}
+
+	if (td->filter_stream) {
+		http_close(td->filter_stream);
+		td->filter_stream = NULL;
+	}
+}
+
+static GSList *twitter_filter_parse(struct groupchat *c, const char *text)
+{
+	char **fs = g_strsplit(text, ";", 0);
+	GSList *ret = NULL;
+	struct twitter_filter *tf;
+	char **f;
+	char *v;
+	int i;
+	int t;
+
+	static const twitter_filter_type_t types[] = {
+		TWITTER_FILTER_TYPE_FOLLOW,
+		TWITTER_FILTER_TYPE_TRACK
+	};
+
+	static const char *typestrs[] = {
+		"follow",
+		"track"
+	};
+
+	for (f = fs; *f; f++) {
+		if ((v = strchr(*f, ':')) == NULL)
+			continue;
+
+		*(v++) = 0;
+
+		for (t = -1, i = 0; i < G_N_ELEMENTS(types); i++) {
+			if (g_strcasecmp(typestrs[i], *f) == 0) {
+				t = i;
+				break;
+			}
+		}
+
+		if (t < 0 || strlen(v) == 0)
+			continue;
+
+		tf = twitter_filter_get(c, types[t], v);
+		ret = g_slist_prepend(ret, tf);
+	}
+
+	g_strfreev(fs);
+	return ret;
+}
+
 /**
  * Main loop function
@@ -51,5 +260,5 @@
 	struct twitter_data *td = ic->proto_data;
 
-	char *last_tweet = set_getstr(&ic->acc->set, "last_tweet");
+	char *last_tweet = set_getstr(&ic->acc->set, "_last_tweet");
 	if (last_tweet)
 		td->timeline_id = g_ascii_strtoull(last_tweet, NULL, 0);
@@ -330,7 +539,11 @@
 
 	s = set_add(&acc->set, "strip_newlines", "false", set_eval_bool, acc);
-	
-	s = set_add(&acc->set, "last_tweet", "0", NULL, acc);
-	s->flags |= SET_HIDDEN;
+
+	s = set_add(&acc->set, "format_string", "\002[\002%i\002]\002 %c", NULL, acc);
+	s = set_add(&acc->set, "retweet_format_string", "\002[\002%i\002]\002 RT @%a: %c", NULL, acc);
+	s = set_add(&acc->set, "reply_format_string", "\002[\002%i->%r\002]\002 %c", NULL, acc);
+	
+	s = set_add(&acc->set, "_last_tweet", "0", NULL, acc);
+	s->flags |= SET_HIDDEN | SET_NOSAVE;
 
 	if (strcmp(acc->prpl->name, "twitter") == 0) {
@@ -436,5 +649,9 @@
 
 	if (td) {
+		if (td->filter_update_id > 0)
+			b_event_remove(td->filter_update_id);
+
 		http_close(td->stream);
+		twitter_filter_remove_all(ic);
 		oauth_info_free(td->oauth_info);
 		g_free(td->user);
@@ -509,10 +726,55 @@
 }
 
+static struct groupchat *twitter_chat_join(struct im_connection *ic,
+                                           const char *room, const char *nick,
+                                           const char *password, set_t **sets)
+{
+	struct groupchat *c = imcb_chat_new(ic, room);
+	GSList *fs = twitter_filter_parse(c, room);
+	GString *topic = g_string_new("");
+	struct twitter_filter *tf;
+	GSList *l;
+
+	fs = g_slist_sort(fs, (GCompareFunc) twitter_filter_cmp);
+
+	for (l = fs; l; l = g_slist_next(l)) {
+		tf = l->data;
+
+		if (topic->len > 0)
+			g_string_append(topic, ", ");
+
+		if (tf->type == TWITTER_FILTER_TYPE_FOLLOW)
+			g_string_append_c(topic, '@');
+
+		g_string_append(topic, tf->text);
+	}
+
+	if (topic->len > 0)
+		g_string_prepend(topic, "Twitter Filter: ");
+
+	imcb_chat_topic(c, NULL, topic->str, 0);
+	imcb_chat_add_buddy(c, ic->acc->user);
+
+	if (topic->len == 0) {
+		imcb_error(ic, "Failed to handle any filters");
+		imcb_chat_free(c);
+		c = NULL;
+	}
+
+	g_string_free(topic, TRUE);
+	g_slist_free(fs);
+
+	return c;
+}
+
 static void twitter_chat_leave(struct groupchat *c)
 {
 	struct twitter_data *td = c->ic->proto_data;
 
-	if (c != td->timeline_gc)
-		return;		/* WTF? */
+	if (c != td->timeline_gc) {
+		twitter_filter_remove(c);
+		imcb_chat_free(c);
+		return;
+	}
 
 	/* If the user leaves the channel: Fine. Rejoin him/her once new
@@ -674,4 +936,13 @@
 		in_reply_to = id;
 		allow_post = TRUE;
+	} else if (g_strcasecmp(cmd[0], "rawreply") == 0 && cmd[1] && cmd[2]) {
+		id = twitter_message_id_from_command_arg(ic, cmd[1], NULL);
+		if (!id) {
+			twitter_log(ic, "Tweet `%s' does not exist", cmd[1]);
+			goto eof;
+		}
+		message = cmd[2];
+		in_reply_to = id;
+		allow_post = TRUE;
 	} else if (g_strcasecmp(cmd[0], "post") == 0) {
 		message += 5;
@@ -748,4 +1019,5 @@
 	ret->chat_msg = twitter_chat_msg;
 	ret->chat_invite = twitter_chat_invite;
+	ret->chat_join = twitter_chat_join;
 	ret->chat_leave = twitter_chat_leave;
 	ret->keepalive = twitter_keepalive;
