diff options
Diffstat (limited to 'protocols')
-rw-r--r-- | protocols/twitter/twitter.c | 263 | ||||
-rw-r--r-- | protocols/twitter/twitter.h | 18 | ||||
-rw-r--r-- | protocols/twitter/twitter_lib.c | 226 | ||||
-rw-r--r-- | protocols/twitter/twitter_lib.h | 3 |
4 files changed, 503 insertions, 7 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; diff --git a/protocols/twitter/twitter.h b/protocols/twitter/twitter.h index 8792b7c9..00230cc0 100644 --- a/protocols/twitter/twitter.h +++ b/protocols/twitter/twitter.h @@ -44,6 +44,12 @@ typedef enum TWITTER_GOT_MENTIONS = 0x40000, } twitter_flags_t; +typedef enum +{ + TWITTER_FILTER_TYPE_FOLLOW = 0, + TWITTER_FILTER_TYPE_TRACK +} twitter_filter_type_t; + struct twitter_log_data; struct twitter_data @@ -57,10 +63,13 @@ struct twitter_data guint64 timeline_id; GSList *follow_ids; + GSList *filters; guint64 last_status_id; /* For undo */ gint main_loop_id; + gint filter_update_id; struct http_request *stream; + struct http_request *filter_stream; struct groupchat *timeline_gc; gint http_fails; twitter_flags_t flags; @@ -78,6 +87,15 @@ struct twitter_data int log_id; }; +#define TWITTER_FILTER_UPDATE_WAIT 3000 +struct twitter_filter +{ + twitter_filter_type_t type; + char *text; + guint64 uid; + GSList *groupchats; +}; + struct twitter_user_data { guint64 last_id; diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index 718867a7..c8956606 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -50,6 +50,7 @@ struct twitter_xml_list { }; struct twitter_xml_user { + guint64 uid; char *name; char *screen_name; }; @@ -60,6 +61,7 @@ struct twitter_xml_status { struct twitter_xml_user *user; guint64 id, rt_id; /* Usually equal, with RTs id == *original* id */ guint64 reply_to; + gboolean from_filter; }; /** @@ -391,11 +393,15 @@ static void twitter_http_get_users_lookup(struct http_request *req) struct twitter_xml_user *twitter_xt_get_user(const json_value *node) { 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; } @@ -656,6 +662,44 @@ static char *twitter_msg_add_id(struct im_connection *ic, } /** + * 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); +} + +/** * Function that is called to see the statuses in a groupchat window. */ static void twitter_status_show_chat(struct im_connection *ic, struct twitter_xml_status *status) @@ -730,7 +774,9 @@ static void twitter_status_show(struct im_connection *ic, struct twitter_xml_sta if (set_getbool(&ic->acc->set, "strip_newlines")) 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 twitter_status_show_msg(ic, status); @@ -744,7 +790,7 @@ static void twitter_status_show(struct im_connection *ic, struct twitter_xml_sta g_free(last_id_str); } -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) { @@ -753,6 +799,7 @@ static void twitter_http_stream(struct http_request *req) json_value *parsed; int len = 0; char c, *nl; + gboolean from_filter; if (!g_slist_find(twitter_connections, ic)) return; @@ -761,7 +808,11 @@ static void twitter_http_stream(struct http_request *req) td = ic->proto_data; 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); return; @@ -778,7 +829,8 @@ static void twitter_http_stream(struct http_request *req) req->reply_body[len] = '\0'; 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); req->reply_body[len] = c; @@ -794,13 +846,14 @@ static void twitter_http_stream(struct http_request *req) static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o); 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; struct twitter_xml_status *txs; json_value *c; if ((txs = twitter_xt_get_status(o))) { + txs->from_filter = from_filter; gboolean ret = twitter_stream_handle_status(ic, txs); txs_free(txs); return ret; @@ -898,6 +951,169 @@ gboolean twitter_open_stream(struct im_connection *ic) return FALSE; } +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); diff --git a/protocols/twitter/twitter_lib.h b/protocols/twitter/twitter_lib.h index 7fd3b808..ee103100 100644 --- a/protocols/twitter/twitter_lib.h +++ b/protocols/twitter/twitter_lib.h @@ -78,9 +78,12 @@ /* Report spam */ #define TWITTER_REPORT_SPAM_URL "/users/report_spam.json" +/* Stream URLs */ #define TWITTER_USER_STREAM_URL "https://userstream.twitter.com/1.1/user.json" +#define TWITTER_FILTER_STREAM_URL "https://stream.twitter.com/1.1/statuses/filter.json" gboolean twitter_open_stream(struct im_connection *ic); +gboolean twitter_open_filter_stream(struct im_connection *ic); gboolean twitter_get_timeline(struct im_connection *ic, gint64 next_cursor); void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor); void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor); |