diff options
Diffstat (limited to 'protocols/twitter/twitter_lib.c')
-rw-r--r-- | protocols/twitter/twitter_lib.c | 287 |
1 files changed, 268 insertions, 19 deletions
diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index 718867a7..b827a139 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,8 @@ 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; + struct twitter_xml_status *rt; }; /** @@ -85,6 +88,7 @@ static void txs_free(struct twitter_xml_status *txs) g_free(txs->text); txu_free(txs->user); + txs_free(txs->rt); g_free(txs); } @@ -391,11 +395,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; } @@ -482,9 +490,9 @@ static struct twitter_xml_status *twitter_xt_get_status(const json_value *node) struct twitter_xml_status *rtxs = twitter_xt_get_status(rt); 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) { txs->text = expand_entities(txs->text, entities); @@ -602,6 +610,49 @@ static gboolean twitter_xt_get_status_list(struct im_connection *ic, const json_ return TRUE; } +/** + * 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. */ static char *twitter_msg_add_id(struct im_connection *ic, @@ -640,19 +691,45 @@ static char *twitter_msg_add_id(struct im_connection *ic, if (g_strcasecmp(txs->user->screen_name, td->user) == 0) 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); } /** @@ -730,7 +807,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 +823,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 +832,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 +841,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 +862,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 +879,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 +984,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); |