aboutsummaryrefslogtreecommitdiffstats
path: root/protocols/twitter/twitter.c
diff options
context:
space:
mode:
authorjgeboski <jgeboski@gmail.com>2014-12-15 07:17:12 -0500
committerjgeboski <jgeboski@gmail.com>2015-01-25 22:46:03 -0500
commit73ee390abec21c2b724175d4a5c9cfc27aa85bcf (patch)
tree626202c9c9521816762540bc4c3f3f7bab17dee5 /protocols/twitter/twitter.c
parent5eab298f82c97d9181f2fb07deea51db567750b2 (diff)
twitter: implemented filter based group chats
Filter group chats allow for the ability to read the tweets of select users without actually following the users, and/or track keywords or hashtags. A filter group chat can have multiple users, keywords, or hashtags. These users, keywords, or hashtags can span multiple group chats. This allows for rather robust filter organization. The underlying structure for the filters is based on linked list, as using the glib hash tables requires >= glib-2.16 for sanity. Since the glib requirement of bitlbee is only 2.14, linked list are used in order to prevent an overly complex implementation. The idea for this patch was inspired by Artem Savkov's "Twitter search channels" patch. In order to use the filter group chats, a group chat must be added to the twitter account. The channel room name is either follow:username, track:keyword, and/or track:#hashtag. Multiple elements can be used by separating each element by a semicolon.
Diffstat (limited to 'protocols/twitter/twitter.c')
-rw-r--r--protocols/twitter/twitter.c263
1 files changed, 261 insertions, 2 deletions
diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c
index d2aafcb4..edc81427 100644
--- a/protocols/twitter/twitter.c
+++ b/protocols/twitter/twitter.c
@@ -30,6 +30,215 @@
#include "url.h"
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
*/
@@ -435,7 +644,11 @@ static void twitter_logout(struct im_connection *ic)
imcb_chat_free(td->timeline_gc);
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);
g_free(td->prefix);
@@ -508,12 +721,57 @@ static void twitter_chat_invite(struct groupchat *c, char *who, char *message)
{
}
+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
tweets come in. */
@@ -747,6 +1005,7 @@ void twitter_initmodule()
ret->remove_buddy = twitter_remove_buddy;
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;
ret->add_permit = twitter_add_permit;