From 3b4a22a8ca8a88122fa5fbf51218a5e6e65a90c8 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 4 Nov 2012 23:23:07 +0100 Subject: s/.xml/.json/. Good luck getting that working again. --- protocols/twitter/twitter_lib.c | 6 +++--- protocols/twitter/twitter_lib.h | 40 ++++++++++++++++++++-------------------- 2 files changed, 23 insertions(+), 23 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index 4a09cbb1..f0a6bbb1 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -1031,7 +1031,7 @@ void twitter_status_destroy(struct im_connection *ic, guint64 id) { char *url; url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_DESTROY_URL, - (unsigned long long) id, ".xml"); + (unsigned long long) id, ".json"); twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0); g_free(url); } @@ -1040,7 +1040,7 @@ void twitter_status_retweet(struct im_connection *ic, guint64 id) { char *url; url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_RETWEET_URL, - (unsigned long long) id, ".xml"); + (unsigned long long) id, ".json"); twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0); g_free(url); } @@ -1066,7 +1066,7 @@ void twitter_favourite_tweet(struct im_connection *ic, guint64 id) { char *url; url = g_strdup_printf("%s%llu%s", TWITTER_FAVORITE_CREATE_URL, - (unsigned long long) id, ".xml"); + (unsigned long long) id, ".json"); twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0); g_free(url); } diff --git a/protocols/twitter/twitter_lib.h b/protocols/twitter/twitter_lib.h index 2404e4eb..104a168b 100644 --- a/protocols/twitter/twitter_lib.h +++ b/protocols/twitter/twitter_lib.h @@ -28,46 +28,46 @@ #include "nogaim.h" #include "twitter_http.h" -#define TWITTER_API_URL "http://api.twitter.com/1" +#define TWITTER_API_URL "http://api.twitter.com/1.1" #define IDENTICA_API_URL "https://identi.ca/api" /* Status URLs */ -#define TWITTER_STATUS_UPDATE_URL "/statuses/update.xml" +#define TWITTER_STATUS_UPDATE_URL "/statuses/update.json" #define TWITTER_STATUS_SHOW_URL "/statuses/show/" #define TWITTER_STATUS_DESTROY_URL "/statuses/destroy/" #define TWITTER_STATUS_RETWEET_URL "/statuses/retweet/" /* Timeline URLs */ -#define TWITTER_PUBLIC_TIMELINE_URL "/statuses/public_timeline.xml" -#define TWITTER_FEATURED_USERS_URL "/statuses/featured.xml" -#define TWITTER_FRIENDS_TIMELINE_URL "/statuses/friends_timeline.xml" -#define TWITTER_HOME_TIMELINE_URL "/statuses/home_timeline.xml" -#define TWITTER_MENTIONS_URL "/statuses/mentions.xml" -#define TWITTER_USER_TIMELINE_URL "/statuses/user_timeline.xml" +#define TWITTER_PUBLIC_TIMELINE_URL "/statuses/public_timeline.json" +#define TWITTER_FEATURED_USERS_URL "/statuses/featured.json" +#define TWITTER_FRIENDS_TIMELINE_URL "/statuses/friends_timeline.json" +#define TWITTER_HOME_TIMELINE_URL "/statuses/home_timeline.json" +#define TWITTER_MENTIONS_URL "/statuses/mentions.json" +#define TWITTER_USER_TIMELINE_URL "/statuses/user_timeline.json" /* Users URLs */ -#define TWITTER_USERS_LOOKUP_URL "/users/lookup.xml" +#define TWITTER_USERS_LOOKUP_URL "/users/lookup.json" /* Direct messages URLs */ -#define TWITTER_DIRECT_MESSAGES_URL "/direct_messages.xml" -#define TWITTER_DIRECT_MESSAGES_NEW_URL "/direct_messages/new.xml" -#define TWITTER_DIRECT_MESSAGES_SENT_URL "/direct_messages/sent.xml" +#define TWITTER_DIRECT_MESSAGES_URL "/direct_messages.json" +#define TWITTER_DIRECT_MESSAGES_NEW_URL "/direct_messages/new.json" +#define TWITTER_DIRECT_MESSAGES_SENT_URL "/direct_messages/sent.json" #define TWITTER_DIRECT_MESSAGES_DESTROY_URL "/direct_messages/destroy/" /* Friendships URLs */ -#define TWITTER_FRIENDSHIPS_CREATE_URL "/friendships/create.xml" -#define TWITTER_FRIENDSHIPS_DESTROY_URL "/friendships/destroy.xml" -#define TWITTER_FRIENDSHIPS_SHOW_URL "/friendships/show.xml" +#define TWITTER_FRIENDSHIPS_CREATE_URL "/friendships/create.json" +#define TWITTER_FRIENDSHIPS_DESTROY_URL "/friendships/destroy.json" +#define TWITTER_FRIENDSHIPS_SHOW_URL "/friendships/show.json" /* Social graphs URLs */ -#define TWITTER_FRIENDS_IDS_URL "/friends/ids.xml" -#define TWITTER_FOLLOWERS_IDS_URL "/followers/ids.xml" +#define TWITTER_FRIENDS_IDS_URL "/friends/ids.json" +#define TWITTER_FOLLOWERS_IDS_URL "/followers/ids.json" /* Account URLs */ -#define TWITTER_ACCOUNT_RATE_LIMIT_URL "/account/rate_limit_status.xml" +#define TWITTER_ACCOUNT_RATE_LIMIT_URL "/account/rate_limit_status.json" /* Favorites URLs */ -#define TWITTER_FAVORITES_GET_URL "/favorites.xml" +#define TWITTER_FAVORITES_GET_URL "/favorites.json" #define TWITTER_FAVORITE_CREATE_URL "/favorites/create/" #define TWITTER_FAVORITE_DESTROY_URL "/favorites/destroy/" @@ -76,7 +76,7 @@ #define TWITTER_BLOCKS_DESTROY_URL "/blocks/destroy/" /* Report spam */ -#define TWITTER_REPORT_SPAM_URL "/report_spam.xml" +#define TWITTER_REPORT_SPAM_URL "/report_spam.json" void twitter_get_timeline(struct im_connection *ic, gint64 next_cursor); void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor); -- cgit v1.2.3 From e08ae0cff03ff880344cb3dc75cc90576cc314b3 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Mon, 5 Nov 2012 00:39:48 +0100 Subject: Can log in now. Fails to fetch timelines though, which is going to be the more annoying part anyway. Plus, I want streaming API stuff instead. --- protocols/twitter/twitter_lib.c | 114 +++++++++++++++------------------------- 1 file changed, 42 insertions(+), 72 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index f0a6bbb1..dad97495 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -36,6 +36,7 @@ #include "base64.h" #include "xmltree.h" #include "twitter_lib.h" +#include "json_util.h" #include #include @@ -187,12 +188,12 @@ char *twitter_parse_error(struct http_request *req) return ret ? ret : req->status_string; } -static struct xt_node *twitter_parse_response(struct im_connection *ic, struct http_request *req) +static json_value *twitter_parse_response(struct im_connection *ic, struct http_request *req) { gboolean logging_in = !(ic->flags & OPT_LOGGED_IN); gboolean periodic; struct twitter_data *td = ic->proto_data; - struct xt_node *ret; + json_value *ret; char path[64] = "", *s; if ((s = strchr(req->request, ' '))) { @@ -226,7 +227,7 @@ static struct xt_node *twitter_parse_response(struct im_connection *ic, struct h td->http_fails = 0; } - if ((ret = xt_from_string(req->reply_body, req->body_size)) == NULL) { + if ((ret = json_parse(req->reply_body)) == NULL) { imcb_error(ic, "Could not retrieve %s: %s", path, "XML parse error"); } @@ -249,45 +250,33 @@ void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor) g_free(args[1]); } -/** - * Function to help fill a list. - */ -static xt_status twitter_xt_next_cursor(struct xt_node *node, struct twitter_xml_list *txl) -{ - char *end = NULL; - - if (node->text) - txl->next_cursor = g_ascii_strtoll(node->text, &end, 10); - if (end == NULL) - txl->next_cursor = -1; - - return XT_HANDLED; -} - /** * Fill a list of ids. */ -static xt_status twitter_xt_get_friends_id_list(struct xt_node *node, struct twitter_xml_list *txl) +static xt_status twitter_xt_get_friends_id_list(json_value *node, struct twitter_xml_list *txl) { - struct xt_node *child; + json_value *c; + int i; // Set the list type. txl->type = TXL_ID; - // The root node should hold the list of statuses - // Walk over the nodes children. - for (child = node->children; child; child = child->next) { - if (g_strcasecmp("ids", child->name) == 0) { - struct xt_node *idc; - for (idc = child->children; idc; idc = idc->next) - if (g_strcasecmp(idc->name, "id") == 0) - txl->list = g_slist_prepend(txl->list, - g_memdup(idc->text, idc->text_len + 1)); - } else if (g_strcasecmp("next_cursor", child->name) == 0) { - twitter_xt_next_cursor(child, txl); - } - } + c = json_o_get(node, "ids"); + if (!c || c->type != json_array) + return XT_ABORT; + for (i = 0; i < c->u.array.length; i ++) { + txl->list = g_slist_prepend(txl->list, + g_strdup_printf("%ld", c->u.array.values[i]->u.integer)); + + } + + c = json_o_get(node, "next_cursor"); + if (c && c->type == json_integer) + txl->next_cursor = c->u.integer; + else + txl->next_cursor = -1; + return XT_HANDLED; } @@ -299,7 +288,7 @@ static void twitter_get_users_lookup(struct im_connection *ic); static void twitter_http_get_friends_ids(struct http_request *req) { struct im_connection *ic; - struct xt_node *parsed; + json_value *parsed; struct twitter_xml_list *txl; struct twitter_data *td; @@ -321,8 +310,9 @@ static void twitter_http_get_friends_ids(struct http_request *req) // Parse the data. if (!(parsed = twitter_parse_response(ic, req))) return; + twitter_xt_get_friends_id_list(parsed, txl); - xt_free_node(parsed); + json_value_free(parsed); td->follow_ids = txl->list; if (txl->next_cursor) @@ -337,7 +327,7 @@ static void twitter_http_get_friends_ids(struct http_request *req) txl_free(txl); } -static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl); +static xt_status twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl); static void twitter_http_get_users_lookup(struct http_request *req); static void twitter_get_users_lookup(struct im_connection *ic) @@ -378,7 +368,7 @@ static void twitter_get_users_lookup(struct im_connection *ic) static void twitter_http_get_users_lookup(struct http_request *req) { struct im_connection *ic = req->data; - struct xt_node *parsed; + json_value *parsed; struct twitter_xml_list *txl; GSList *l = NULL; struct twitter_xml_user *user; @@ -394,7 +384,7 @@ static void twitter_http_get_users_lookup(struct http_request *req) if (!(parsed = twitter_parse_response(ic, req))) return; twitter_xt_get_users(parsed, txl); - xt_free_node(parsed); + json_value_free(parsed); // Add the users as buddies. for (l = txl->list; l; l = g_slist_next(l)) { @@ -408,49 +398,29 @@ static void twitter_http_get_users_lookup(struct http_request *req) twitter_get_users_lookup(ic); } -/** - * Function to fill a twitter_xml_user struct. - * It sets: - * - the name and - * - the screen_name. - */ -static xt_status twitter_xt_get_user(struct xt_node *node, struct twitter_xml_user *txu) -{ - struct xt_node *child; - - // Walk over the nodes children. - for (child = node->children; child; child = child->next) { - if (g_strcasecmp("name", child->name) == 0) { - txu->name = g_memdup(child->text, child->text_len + 1); - } else if (g_strcasecmp("screen_name", child->name) == 0) { - txu->screen_name = g_memdup(child->text, child->text_len + 1); - } - } - return XT_HANDLED; -} - /** * Function to fill a twitter_xml_list struct. * It sets: * - all s from the element. */ -static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl) +static xt_status twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl) { struct twitter_xml_user *txu; - struct xt_node *child; + int i; // Set the type of the list. txl->type = TXL_USER; + if (!node || node->type != json_array) + return XT_ABORT; + // The root node should hold the list of users // Walk over the nodes children. - for (child = node->children; child; child = child->next) { - if (g_strcasecmp("user", child->name) == 0) { - txu = g_new0(struct twitter_xml_user, 1); - twitter_xt_get_user(child, txu); - // Put the item in the front of the list. - txl->list = g_slist_prepend(txl->list, txu); - } + for (i = 0; i < node->u.array.length; i ++) { + txu = g_new0(struct twitter_xml_user, 1); + txu->name = g_strdup(json_o_str(node->u.array.values[i], "name")); + txu->screen_name = g_strdup(json_o_str(node->u.array.values[i], "screen_name")); + txl->list = g_slist_prepend(txl->list, txu); } return XT_HANDLED; @@ -490,7 +460,7 @@ static xt_status twitter_xt_get_status(struct xt_node *node, struct twitter_xml_ txs->created_at = mktime_utc(&parsed); } else if (g_strcasecmp("user", child->name) == 0) { txs->user = g_new0(struct twitter_xml_user, 1); - twitter_xt_get_user(child, txs->user); +// twitter_xt_get_user(child, txs->user); } else if (g_strcasecmp("id", child->name) == 0) { txs->id = g_ascii_strtoull(child->text, NULL, 10); } else if (g_strcasecmp("in_reply_to_status_id", child->name) == 0) { @@ -578,7 +548,7 @@ static xt_status twitter_xt_get_status_list(struct im_connection *ic, struct xt_ } } } else if (g_strcasecmp("next_cursor", child->name) == 0) { - twitter_xt_next_cursor(child, txl); +// twitter_xt_next_cursor(child, txl); } } @@ -920,7 +890,7 @@ static void twitter_http_get_home_timeline(struct http_request *req) if (!(parsed = twitter_parse_response(ic, req))) goto end; twitter_xt_get_status_list(ic, parsed, txl); - xt_free_node(parsed); +// json_value_free(parsed); td->home_timeline_obj = txl; @@ -953,7 +923,7 @@ static void twitter_http_get_mentions(struct http_request *req) if (!(parsed = twitter_parse_response(ic, req))) goto end; twitter_xt_get_status_list(ic, parsed, txl); - xt_free_node(parsed); +// json_value_free(parsed); td->mentions_obj = txl; -- cgit v1.2.3 From 0688e99d12297b6dda980803d1f1a54bf8590299 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Mon, 5 Nov 2012 09:46:06 +0100 Subject: Type safety check. --- protocols/twitter/twitter_lib.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'protocols') diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index dad97495..a4ce28e7 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -188,6 +188,7 @@ char *twitter_parse_error(struct http_request *req) return ret ? ret : req->status_string; } +/* TODO: NULL means failure, but not always that the connection is dead. This sucks for callers. */ static json_value *twitter_parse_response(struct im_connection *ic, struct http_request *req) { gboolean logging_in = !(ic->flags & OPT_LOGGED_IN); @@ -266,6 +267,9 @@ static xt_status twitter_xt_get_friends_id_list(json_value *node, struct twitter return XT_ABORT; for (i = 0; i < c->u.array.length; i ++) { + if (c->u.array.values[i]->type != json_integer) + continue; + txl->list = g_slist_prepend(txl->list, g_strdup_printf("%ld", c->u.array.values[i]->u.integer)); -- cgit v1.2.3 From 8e3b7acdfc96e086f1cd1c606e46d9a91d46a842 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Thu, 8 Nov 2012 22:38:20 +0000 Subject: It logs in and fetches statuses! \o/ But, some corruption.. --- protocols/twitter/twitter_lib.c | 135 ++++++++++++++++++++++------------------ 1 file changed, 76 insertions(+), 59 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index a4ce28e7..47264db0 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -271,7 +271,7 @@ static xt_status twitter_xt_get_friends_id_list(json_value *node, struct twitter continue; txl->list = g_slist_prepend(txl->list, - g_strdup_printf("%ld", c->u.array.values[i]->u.integer)); + g_strdup_printf("%lld", c->u.array.values[i]->u.integer)); } @@ -402,6 +402,17 @@ static void twitter_http_get_users_lookup(struct http_request *req) twitter_get_users_lookup(ic); } +struct twitter_xml_user *twitter_xt_get_user(const json_value *node) +{ + struct twitter_xml_user *txu; + + 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")); + + return txu; +} + /** * Function to fill a twitter_xml_list struct. * It sets: @@ -421,10 +432,9 @@ static xt_status twitter_xt_get_users(json_value *node, struct twitter_xml_list // The root node should hold the list of users // Walk over the nodes children. for (i = 0; i < node->u.array.length; i ++) { - txu = g_new0(struct twitter_xml_user, 1); - txu->name = g_strdup(json_o_str(node->u.array.values[i], "name")); - txu->screen_name = g_strdup(json_o_str(node->u.array.values[i], "screen_name")); - txl->list = g_slist_prepend(txl->list, txu); + txu = twitter_xt_get_user(node->u.array.values[i]); + if (txu) + txl->list = g_slist_prepend(txl->list, txu); } return XT_HANDLED; @@ -444,31 +454,38 @@ static xt_status twitter_xt_get_users(json_value *node, struct twitter_xml_list * - the status id and * - the user in a twitter_xml_user struct. */ -static xt_status twitter_xt_get_status(struct xt_node *node, struct twitter_xml_status *txs) +static xt_status twitter_xt_get_status(const json_value *node, struct twitter_xml_status *txs) { - struct xt_node *child, *rt = NULL; + const json_value *c, *rt = NULL, *entities = NULL; + int i; + + if (node->type != json_object) + return XT_ABORT; - // Walk over the nodes children. - for (child = node->children; child; child = child->next) { - if (g_strcasecmp("text", child->name) == 0) { - txs->text = g_memdup(child->text, child->text_len + 1); - } else if (g_strcasecmp("retweeted_status", child->name) == 0) { - rt = child; - } else if (g_strcasecmp("created_at", child->name) == 0) { + for (i = 0; i < node->u.object.length; i ++) { + const char *k = node->u.object.values[i].name; + const json_value *v = node->u.object.values[i].value; + + if (strcmp("text", k) == 0 && v->type == json_string) { + txs->text = g_memdup(v->u.string.ptr, v->u.string.length); + } else if (strcmp("retweeted_status", k) == 0 && v->type == json_object) { + rt = v; + } else if (strcmp("created_at", k) == 0 && v->type == json_string) { struct tm parsed; /* Very sensitive to changes to the formatting of this field. :-( Also assumes the timezone used is UTC since C time handling functions suck. */ - if (strptime(child->text, TWITTER_TIME_FORMAT, &parsed) != NULL) + if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL) txs->created_at = mktime_utc(&parsed); - } else if (g_strcasecmp("user", child->name) == 0) { - txs->user = g_new0(struct twitter_xml_user, 1); -// twitter_xt_get_user(child, txs->user); - } else if (g_strcasecmp("id", child->name) == 0) { - txs->id = g_ascii_strtoull(child->text, NULL, 10); - } else if (g_strcasecmp("in_reply_to_status_id", child->name) == 0) { - txs->reply_to = g_ascii_strtoull(child->text, NULL, 10); + } else if (strcmp("user", k) == 0 && v->type == json_object) { + txs->user = twitter_xt_get_user(v); + } else if (strcmp("id", k) == 0 && v->type == json_integer) { + txs->id = v->u.integer; + } else if (strcmp("in_reply_to_status_id", k) == 0 && v->type == json_integer) { + txs->reply_to = v->u.integer; + } else if (strcmp("entities", k) == 0 && v->type == json_object) { + txs->reply_to = v->u.integer; } } @@ -484,29 +501,29 @@ static xt_status twitter_xt_get_status(struct xt_node *node, struct twitter_xml_ g_free(txs->text); txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text); txs_free(rtxs); - } else { - struct xt_node *urls, *url; - - urls = xt_find_path(node, "entities"); - if (urls != NULL) - urls = urls->children; - for (; urls; urls = urls->next) { - if (strcmp(urls->name, "urls") != 0 && strcmp(urls->name, "media") != 0) + } else if (entities && NULL) { + JSON_O_FOREACH (entities, k, v) { + int i; + + if (v->type != json_array) + continue; + if (strcmp(k, "urls") != 0 && strcmp(k, "media") != 0) continue; - for (url = urls ? urls->children : NULL; url; url = url->next) { - /* "short" is a reserved word. :-P */ - struct xt_node *kort = xt_find_node(url->children, "url"); - struct xt_node *disp = xt_find_node(url->children, "display_url"); + for (i = 0; i < v->u.array.length; i ++) { + if (v->u.array.values[i]->type != json_object) + continue; + + const char *kort = json_o_str(v->u.array.values[i], "url"); + const char *disp = json_o_str(v->u.array.values[i], "display_url"); char *pos, *new; - if (!kort || !kort->text || !disp || !disp->text || - !(pos = strstr(txs->text, kort->text))) + if (!kort || !disp || !(pos = strstr(txs->text, kort))) continue; *pos = '\0'; - new = g_strdup_printf("%s%s <%s>%s", txs->text, kort->text, - disp->text, pos + strlen(kort->text)); + new = g_strdup_printf("%s%s <%s>%s", txs->text, kort, + disp, pos + strlen(kort)); g_free(txs->text); txs->text = new; @@ -523,36 +540,36 @@ static xt_status twitter_xt_get_status(struct xt_node *node, struct twitter_xml_ * - all es within the element and * - the next_cursor. */ -static xt_status twitter_xt_get_status_list(struct im_connection *ic, struct xt_node *node, +static xt_status twitter_xt_get_status_list(struct im_connection *ic, const json_value *node, struct twitter_xml_list *txl) { struct twitter_xml_status *txs; - struct xt_node *child; + json_value *c; bee_user_t *bu; + int i; // Set the type of the list. txl->type = TXL_STATUS; + + if (node->type != json_array) + return XT_ABORT; // The root node should hold the list of statuses // Walk over the nodes children. - for (child = node->children; child; child = child->next) { - if (g_strcasecmp("status", child->name) == 0) { - txs = g_new0(struct twitter_xml_status, 1); - twitter_xt_get_status(child, txs); - // Put the item in the front of the list. - txl->list = g_slist_prepend(txl->list, txs); - - if (txs->user && txs->user->screen_name && - (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) { - struct twitter_user_data *tud = bu->data; - - if (txs->id > tud->last_id) { - tud->last_id = txs->id; - tud->last_time = txs->created_at; - } + for (i = 0; i < node->u.array.length; i ++) { + txs = g_new0(struct twitter_xml_status, 1); + twitter_xt_get_status(node->u.array.values[i], txs); + // Put the item in the front of the list. + txl->list = g_slist_prepend(txl->list, txs); + + if (txs->user && txs->user->screen_name && + (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) { + struct twitter_user_data *tud = bu->data; + + if (txs->id > tud->last_id) { + tud->last_id = txs->id; + tud->last_time = txs->created_at; } - } else if (g_strcasecmp("next_cursor", child->name) == 0) { -// twitter_xt_next_cursor(child, txl); } } @@ -878,7 +895,7 @@ static void twitter_http_get_home_timeline(struct http_request *req) { struct im_connection *ic = req->data; struct twitter_data *td; - struct xt_node *parsed; + json_value *parsed; struct twitter_xml_list *txl; // Check if the connection is still active. @@ -911,7 +928,7 @@ static void twitter_http_get_mentions(struct http_request *req) { struct im_connection *ic = req->data; struct twitter_data *td; - struct xt_node *parsed; + json_value *parsed; struct twitter_xml_list *txl; // Check if the connection is still active. -- cgit v1.2.3 From fb351ce6b6cdc714019f49f693182e32055fd78b Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Fri, 9 Nov 2012 00:07:23 +0000 Subject: Getting better. Corruption fixed, fetching of mentions fixed, error handling "fixed". --- protocols/twitter/twitter_lib.c | 20 ++++++++++++++------ protocols/twitter/twitter_lib.h | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index 47264db0..6231cf69 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -173,6 +173,8 @@ char *twitter_parse_error(struct http_request *req) g_free(ret); ret = NULL; + /* EX: + {"errors":[{"message":"Rate limit exceeded","code":88}]} */ if (req->body_size > 0) { root = xt_from_string(req->reply_body, req->body_size); @@ -188,7 +190,8 @@ char *twitter_parse_error(struct http_request *req) return ret ? ret : req->status_string; } -/* TODO: NULL means failure, but not always that the connection is dead. This sucks for callers. */ +/* WATCH OUT: This function might or might not destroy your connection. + Sub-optimal indeed, but just be careful when this returns NULL! */ static json_value *twitter_parse_response(struct im_connection *ic, struct http_request *req) { gboolean logging_in = !(ic->flags & OPT_LOGGED_IN); @@ -456,7 +459,7 @@ static xt_status twitter_xt_get_users(json_value *node, struct twitter_xml_list */ static xt_status twitter_xt_get_status(const json_value *node, struct twitter_xml_status *txs) { - const json_value *c, *rt = NULL, *entities = NULL; + const json_value *rt = NULL, *entities = NULL; int i; if (node->type != json_object) @@ -467,7 +470,7 @@ static xt_status twitter_xt_get_status(const json_value *node, struct twitter_xm const json_value *v = node->u.object.values[i].value; if (strcmp("text", k) == 0 && v->type == json_string) { - txs->text = g_memdup(v->u.string.ptr, v->u.string.length); + txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1); } else if (strcmp("retweeted_status", k) == 0 && v->type == json_object) { rt = v; } else if (strcmp("created_at", k) == 0 && v->type == json_string) { @@ -485,7 +488,7 @@ static xt_status twitter_xt_get_status(const json_value *node, struct twitter_xm } else if (strcmp("in_reply_to_status_id", k) == 0 && v->type == json_integer) { txs->reply_to = v->u.integer; } else if (strcmp("entities", k) == 0 && v->type == json_object) { - txs->reply_to = v->u.integer; + entities = v; } } @@ -501,7 +504,7 @@ static xt_status twitter_xt_get_status(const json_value *node, struct twitter_xm g_free(txs->text); txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text); txs_free(rtxs); - } else if (entities && NULL) { + } else if (entities) { JSON_O_FOREACH (entities, k, v) { int i; @@ -544,7 +547,6 @@ static xt_status twitter_xt_get_status_list(struct im_connection *ic, const json struct twitter_xml_list *txl) { struct twitter_xml_status *txs; - json_value *c; bee_user_t *bu; int i; @@ -916,6 +918,9 @@ static void twitter_http_get_home_timeline(struct http_request *req) td->home_timeline_obj = txl; end: + if (!g_slist_find(twitter_connections, ic)) + return; + td->flags |= TWITTER_GOT_TIMELINE; twitter_flush_timeline(ic); @@ -949,6 +954,9 @@ static void twitter_http_get_mentions(struct http_request *req) td->mentions_obj = txl; end: + if (!g_slist_find(twitter_connections, ic)) + return; + td->flags |= TWITTER_GOT_MENTIONS; twitter_flush_timeline(ic); diff --git a/protocols/twitter/twitter_lib.h b/protocols/twitter/twitter_lib.h index 104a168b..7b640b00 100644 --- a/protocols/twitter/twitter_lib.h +++ b/protocols/twitter/twitter_lib.h @@ -42,7 +42,7 @@ #define TWITTER_FEATURED_USERS_URL "/statuses/featured.json" #define TWITTER_FRIENDS_TIMELINE_URL "/statuses/friends_timeline.json" #define TWITTER_HOME_TIMELINE_URL "/statuses/home_timeline.json" -#define TWITTER_MENTIONS_URL "/statuses/mentions.json" +#define TWITTER_MENTIONS_URL "/statuses/mentions_timeline.json" #define TWITTER_USER_TIMELINE_URL "/statuses/user_timeline.json" /* Users URLs */ -- cgit v1.2.3 From 5246133a607561abe8096c471de02bddeb6be67c Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Fri, 9 Nov 2012 00:23:44 +0000 Subject: Updated error response parsing. Also, use this for 401 responses so for example the auth errors caused by NTP desync are clearer: twitter - Login error: Authentication failure (401 Unauthorized (Timestamp out of bounds)) --- protocols/twitter/twitter_lib.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index 6231cf69..c72c317b 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -168,23 +168,21 @@ static void twitter_add_buddy(struct im_connection *ic, char *name, const char * char *twitter_parse_error(struct http_request *req) { static char *ret = NULL; - struct xt_node *root, *node, *err; + json_value *root, *err; g_free(ret); ret = NULL; - /* EX: - {"errors":[{"message":"Rate limit exceeded","code":88}]} */ if (req->body_size > 0) { - root = xt_from_string(req->reply_body, req->body_size); - - for (node = root; node; node = node->next) - if ((err = xt_find_node(node->children, "error")) && err->text_len > 0) { - ret = g_strdup_printf("%s (%s)", req->status_string, err->text); - break; - } - - xt_free_node(root); + root = json_parse(req->reply_body); + err = json_o_get(root, "errors"); + if (err->type == json_array && (err = err->u.array.values[0]) && + err->type == json_object) { + const char *msg = json_o_str(err, "message"); + if (msg) + ret = g_strdup_printf("%s (%s)", req->status_string, msg); + } + json_value_free(root); } return ret ? ret : req->status_string; @@ -215,7 +213,8 @@ static json_value *twitter_parse_response(struct im_connection *ic, struct http_ /* IIRC Twitter once had an outage where they were randomly throwing 401s so I'll keep treating this one as fatal only during login. */ - imcb_error(ic, "Authentication failure"); + imcb_error(ic, "Authentication failure (%s)", + twitter_parse_error(req)); imc_logout(ic, FALSE); return NULL; } else if (req->status_code != 200) { -- cgit v1.2.3 From 24132ec9af8b275fb4b94e347c6ccce0017c27d0 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Fri, 9 Nov 2012 23:53:45 +0000 Subject: Fixed the last parser (generic handler which just remembers status IDs). xmltree.h include can almost be removed, it's just used for return values now. --- protocols/twitter/twitter_lib.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index c72c317b..cfa2cf06 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -969,7 +969,7 @@ static void twitter_http_post(struct http_request *req) { struct im_connection *ic = req->data; struct twitter_data *td; - struct xt_node *parsed, *node; + json_value *parsed, *id; // Check if the connection is still active. if (!g_slist_find(twitter_connections, ic)) @@ -981,9 +981,8 @@ static void twitter_http_post(struct http_request *req) if (!(parsed = twitter_parse_response(ic, req))) return; - if ((node = xt_find_node(parsed, "status")) && - (node = xt_find_node(node->children, "id")) && node->text) - td->last_status_id = g_ascii_strtoull(node->text, NULL, 10); + if ((id = json_o_get(parsed, "id")) && id->type == json_integer) + td->last_status_id = id->u.integer; } /** -- cgit v1.2.3 From 9e8c945b0c0e794fdc33413ad87feb4606a07981 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sat, 10 Nov 2012 12:31:33 +0000 Subject: Removed xmltree dependency entirely. --- protocols/twitter/twitter_lib.c | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index cfa2cf06..654cf467 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -34,7 +34,6 @@ #include "url.h" #include "misc.h" #include "base64.h" -#include "xmltree.h" #include "twitter_lib.h" #include "json_util.h" #include @@ -256,7 +255,7 @@ void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor) /** * Fill a list of ids. */ -static xt_status twitter_xt_get_friends_id_list(json_value *node, struct twitter_xml_list *txl) +static gboolean twitter_xt_get_friends_id_list(json_value *node, struct twitter_xml_list *txl) { json_value *c; int i; @@ -266,7 +265,7 @@ static xt_status twitter_xt_get_friends_id_list(json_value *node, struct twitter c = json_o_get(node, "ids"); if (!c || c->type != json_array) - return XT_ABORT; + return FALSE; for (i = 0; i < c->u.array.length; i ++) { if (c->u.array.values[i]->type != json_integer) @@ -283,7 +282,7 @@ static xt_status twitter_xt_get_friends_id_list(json_value *node, struct twitter else txl->next_cursor = -1; - return XT_HANDLED; + return TRUE; } static void twitter_get_users_lookup(struct im_connection *ic); @@ -333,7 +332,7 @@ static void twitter_http_get_friends_ids(struct http_request *req) txl_free(txl); } -static xt_status twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl); +static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl); static void twitter_http_get_users_lookup(struct http_request *req); static void twitter_get_users_lookup(struct im_connection *ic) @@ -420,7 +419,7 @@ struct twitter_xml_user *twitter_xt_get_user(const json_value *node) * It sets: * - all s from the element. */ -static xt_status twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl) +static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl) { struct twitter_xml_user *txu; int i; @@ -429,7 +428,7 @@ static xt_status twitter_xt_get_users(json_value *node, struct twitter_xml_list txl->type = TXL_USER; if (!node || node->type != json_array) - return XT_ABORT; + return FALSE; // The root node should hold the list of users // Walk over the nodes children. @@ -439,7 +438,7 @@ static xt_status twitter_xt_get_users(json_value *node, struct twitter_xml_list txl->list = g_slist_prepend(txl->list, txu); } - return XT_HANDLED; + return TRUE; } #ifdef __GLIBC__ @@ -456,18 +455,14 @@ static xt_status twitter_xt_get_users(json_value *node, struct twitter_xml_list * - the status id and * - the user in a twitter_xml_user struct. */ -static xt_status twitter_xt_get_status(const json_value *node, struct twitter_xml_status *txs) +static gboolean twitter_xt_get_status(const json_value *node, struct twitter_xml_status *txs) { const json_value *rt = NULL, *entities = NULL; - int i; if (node->type != json_object) - return XT_ABORT; + return FALSE; - for (i = 0; i < node->u.object.length; i ++) { - const char *k = node->u.object.values[i].name; - const json_value *v = node->u.object.values[i].value; - + JSON_O_FOREACH (node, k, v) { if (strcmp("text", k) == 0 && v->type == json_string) { txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1); } else if (strcmp("retweeted_status", k) == 0 && v->type == json_object) { @@ -495,9 +490,9 @@ static xt_status twitter_xt_get_status(const json_value *node, struct twitter_xm wasn't truncated because it may be lying. */ if (rt) { struct twitter_xml_status *rtxs = g_new0(struct twitter_xml_status, 1); - if (twitter_xt_get_status(rt, rtxs) != XT_HANDLED) { + if (!twitter_xt_get_status(rt, rtxs)) { txs_free(rtxs); - return XT_HANDLED; + return TRUE; } g_free(txs->text); @@ -533,7 +528,7 @@ static xt_status twitter_xt_get_status(const json_value *node, struct twitter_xm } } - return XT_HANDLED; + return TRUE; } /** @@ -542,8 +537,8 @@ static xt_status twitter_xt_get_status(const json_value *node, struct twitter_xm * - all es within the element and * - the next_cursor. */ -static xt_status twitter_xt_get_status_list(struct im_connection *ic, const json_value *node, - struct twitter_xml_list *txl) +static gboolean twitter_xt_get_status_list(struct im_connection *ic, const json_value *node, + struct twitter_xml_list *txl) { struct twitter_xml_status *txs; bee_user_t *bu; @@ -553,7 +548,7 @@ static xt_status twitter_xt_get_status_list(struct im_connection *ic, const json txl->type = TXL_STATUS; if (node->type != json_array) - return XT_ABORT; + return FALSE; // The root node should hold the list of statuses // Walk over the nodes children. @@ -574,7 +569,7 @@ static xt_status twitter_xt_get_status_list(struct im_connection *ic, const json } } - return XT_HANDLED; + return TRUE; } static char *twitter_msg_add_id(struct im_connection *ic, -- cgit v1.2.3 From ddc2de54664ec25b95bbce997fbbb6a7104f1203 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sat, 10 Nov 2012 23:52:21 +0000 Subject: Very immature code for reading from the streaming API. It reads from a fixed URL and tried to parse individual JSON objects. Not doing anything useful with it. --- protocols/twitter/twitter.h | 1 + protocols/twitter/twitter_http.c | 31 ++++++++++++++---- protocols/twitter/twitter_http.h | 4 +-- protocols/twitter/twitter_lib.c | 68 +++++++++++++++++++++++++++++++++++++--- protocols/twitter/twitter_lib.h | 2 -- 5 files changed, 92 insertions(+), 14 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter.h b/protocols/twitter/twitter.h index 14e43824..f2afb754 100644 --- a/protocols/twitter/twitter.h +++ b/protocols/twitter/twitter.h @@ -56,6 +56,7 @@ struct twitter_data guint64 last_status_id; /* For undo */ gint main_loop_id; + struct http_request *stream; struct groupchat *timeline_gc; gint http_fails; twitter_flags_t flags; diff --git a/protocols/twitter/twitter_http.c b/protocols/twitter/twitter_http.c index dbac5461..216dad9e 100644 --- a/protocols/twitter/twitter_http.c +++ b/protocols/twitter/twitter_http.c @@ -46,14 +46,15 @@ static char *twitter_url_append(char *url, char *key, char *value); * Do a request. * This is actually pretty generic function... Perhaps it should move to the lib/http_client.c */ -void *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, - gpointer data, int is_post, char **arguments, int arguments_len) +struct http_request *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, + gpointer data, int is_post, char **arguments, int arguments_len) { struct twitter_data *td = ic->proto_data; char *tmp; GString *request = g_string_new(""); void *ret; char *url_arguments; + url_t *base_url = NULL; url_arguments = g_strdup(""); @@ -66,20 +67,34 @@ void *twitter_http(struct im_connection *ic, char *url_string, http_input_functi url_arguments = tmp; } } + + if (strstr(url_string, "://")) { + base_url = g_new0(url_t, 1); + if (!url_set(base_url, url_string)) { + g_free(base_url); + return NULL; + } + } + // Make the request. g_string_printf(request, "%s %s%s%s%s HTTP/1.0\r\n" "Host: %s\r\n" "User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n", is_post ? "POST" : "GET", - td->url_path, url_string, - is_post ? "" : "?", is_post ? "" : url_arguments, td->url_host); + base_url ? base_url->file : td->url_path, + base_url ? "" : url_string, + is_post ? "" : "?", is_post ? "" : url_arguments, + base_url ? base_url->host : td->url_host); // If a pass and user are given we append them to the request. if (td->oauth_info) { char *full_header; char *full_url; - full_url = g_strconcat(set_getstr(&ic->acc->set, "base_url"), url_string, NULL); + if (base_url) + full_url = g_strdup(url_string); + else + full_url = g_strconcat(set_getstr(&ic->acc->set, "base_url"), url_string, NULL); full_header = oauth_http_header(td->oauth_info, is_post ? "POST" : "GET", full_url, url_arguments); @@ -108,10 +123,14 @@ void *twitter_http(struct im_connection *ic, char *url_string, http_input_functi g_string_append(request, "\r\n"); } - ret = http_dorequest(td->url_host, td->url_port, td->url_ssl, request->str, func, data); + if (base_url) + ret = http_dorequest(base_url->host, base_url->port, base_url->proto == PROTO_HTTPS, request->str, func, data); + else + ret = http_dorequest(td->url_host, td->url_port, td->url_ssl, request->str, func, data); g_free(url_arguments); g_string_free(request, TRUE); + g_free(base_url); return ret; } diff --git a/protocols/twitter/twitter_http.h b/protocols/twitter/twitter_http.h index 393a1c26..02a7ea43 100644 --- a/protocols/twitter/twitter_http.h +++ b/protocols/twitter/twitter_http.h @@ -29,8 +29,8 @@ struct oauth_info; -void *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, - gpointer data, int is_post, char** arguments, int arguments_len); +struct http_request *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, + gpointer data, int is_post, char** arguments, int arguments_len); #endif //_TWITTER_HTTP_H diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index 654cf467..8d10a406 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -726,8 +726,48 @@ static void twitter_private_message_chat(struct im_connection *ic, GSList * list } } -static void twitter_http_get_home_timeline(struct http_request *req); -static void twitter_http_get_mentions(struct http_request *req); +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); + +static void twitter_http_stream(struct http_request *req) +{ + int len; + int i; + char c; + json_value *j; + + printf( "%d bytes in stream\n", req->body_size ); + + /* m/^[\d\s]*/ /* why is there a second commend here? :-) */ + for (i = 0; i < req->body_size; i ++) { + if (!isspace(req->reply_body[i]) && !isdigit(req->reply_body[i])) + break; + } + + /* Nothing but numbers and whitespace in there. Try again later. */ + if (i == req->body_size) + return; + + /* Get length. */ + if (sscanf(req->reply_body, "%d", &len) != 1) + return; + + if (req->body_size < i + len) { + printf("Not enough bytes in buffer yet\n"); + return; + } + + http_flush_bytes(req, i); + c = req->reply_body[len]; + req->reply_body[len] = '\0'; + + printf("JSON: %s\n", req->reply_body); + printf("parsed: %p\n", (j = json_parse(req->reply_body))); + json_value_free(j); + req->reply_body[len] = c; + + http_flush_bytes(req, len); +} /** * Get the timeline with optionally mentions @@ -751,6 +791,23 @@ void twitter_get_timeline(struct im_connection *ic, gint64 next_cursor) if (include_mentions) { twitter_get_mentions(ic, next_cursor); } + + static int bla = 0; + + if (bla) + return; + bla = 1; + + char *args[4]; + args[0] = "with"; + args[1] = "followings"; + args[2] = "delimited"; + args[3] = "length"; + + if ((td->stream = twitter_http(ic, "https://userstream.twitter.com/1.1/user.json", + twitter_http_stream, ic, 0, args, 4))) { + td->stream->flags |= HTTPC_STREAMING; + } } /** @@ -809,10 +866,13 @@ void twitter_flush_timeline(struct im_connection *ic) td->home_timeline_obj = td->mentions_obj = NULL; } +static void twitter_http_get_home_timeline(struct http_request *req); +static void twitter_http_get_mentions(struct http_request *req); + /** * Get the timeline. */ -void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor) +static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor) { struct twitter_data *td = ic->proto_data; @@ -848,7 +908,7 @@ void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor) /** * Get mentions. */ -void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor) +static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor) { struct twitter_data *td = ic->proto_data; diff --git a/protocols/twitter/twitter_lib.h b/protocols/twitter/twitter_lib.h index 7b640b00..624fc80a 100644 --- a/protocols/twitter/twitter_lib.h +++ b/protocols/twitter/twitter_lib.h @@ -80,8 +80,6 @@ void 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_home_timeline(struct im_connection *ic, gint64 next_cursor); -void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor); void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor); void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to); -- cgit v1.2.3 From dff0e0bdf3acea7d301242421b4fa906ff46278d Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 11 Nov 2012 14:42:20 +0000 Subject: Showing tweets now, and leaking less memory. Still lots of cleanup left to do. --- protocols/twitter/twitter.c | 21 ++++++++--- protocols/twitter/twitter_lib.c | 80 +++++++++++++++++++++++++++-------------- protocols/twitter/twitter_lib.h | 3 ++ 3 files changed, 73 insertions(+), 31 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c index 93ef4ae2..ea440a15 100644 --- a/protocols/twitter/twitter.c +++ b/protocols/twitter/twitter.c @@ -4,6 +4,7 @@ * Simple module to facilitate twitter functionality. * * * * Copyright 2009 Geert Mulders * +* Copyright 2010-2012 Wilmer van der Gaast * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * @@ -65,11 +66,18 @@ static void twitter_main_loop_start(struct im_connection *ic) // Run this once. After this queue the main loop function. twitter_main_loop(ic, -1, 0); - - // Queue the main_loop - // Save the return value, so we can remove the timeout on logout. - td->main_loop_id = - b_timeout_add(set_getint(&ic->acc->set, "fetch_interval") * 1000, twitter_main_loop, ic); + + if (set_getbool(&ic->acc->set, "stream")) { + /* That fetch was just to get backlog, the stream will give + us the rest. \o/ */ + twitter_open_stream(ic); + } else { + /* Not using the streaming API, so keep polling the old- + fashioned way. :-( */ + td->main_loop_id = + b_timeout_add(set_getint(&ic->acc->set, "fetch_interval") * 1000, + twitter_main_loop, ic); + } } static void twitter_oauth_start(struct im_connection *ic); @@ -278,6 +286,9 @@ static void twitter_init(account_t * acc) s = set_add(&acc->set, "show_old_mentions", "20", set_eval_int, acc); s = set_add(&acc->set, "strip_newlines", "false", set_eval_bool, acc); + + s = set_add(&acc->set, "stream", "true", set_eval_bool, acc); + s->flags |= ACC_SET_OFFLINE_ONLY; } /** diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index 8d10a406..031d824e 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -175,7 +175,7 @@ char *twitter_parse_error(struct http_request *req) if (req->body_size > 0) { root = json_parse(req->reply_body); err = json_o_get(root, "errors"); - if (err->type == json_array && (err = err->u.array.values[0]) && + if (err && err->type == json_array && (err = err->u.array.values[0]) && err->type == json_object) { const char *msg = json_o_str(err, "message"); if (msg) @@ -528,7 +528,7 @@ static gboolean twitter_xt_get_status(const json_value *node, struct twitter_xml } } - return TRUE; + return txs->text && txs->user && txs->id; } /** @@ -726,15 +726,19 @@ static void twitter_private_message_chat(struct im_connection *ic, GSList * list } } -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); +static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o); static void twitter_http_stream(struct http_request *req) { - int len; - int i; + struct im_connection *ic = req->data; + //struct twitter_data *td = ic->proto_data; + json_value *parsed; + int len, i; char c; - json_value *j; + + if (!g_slist_find(twitter_connections, ic)) + return; + //td = ic->proto_data;; printf( "%d bytes in stream\n", req->body_size ); @@ -762,13 +766,54 @@ static void twitter_http_stream(struct http_request *req) req->reply_body[len] = '\0'; printf("JSON: %s\n", req->reply_body); - printf("parsed: %p\n", (j = json_parse(req->reply_body))); - json_value_free(j); + printf("parsed: %p\n", (parsed = json_parse(req->reply_body))); + if (parsed) { + twitter_stream_handle_object(ic, parsed); + } + json_value_free(parsed); req->reply_body[len] = c; http_flush_bytes(req, len); + + /* One notification might bring multiple events! */ + if (req->body_size > 0) + twitter_http_stream(req); +} + +static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o) +{ + struct twitter_xml_status *txs = g_new0(struct twitter_xml_status, 1); + + if (twitter_xt_get_status(o, txs)) { + GSList *output = g_slist_append(NULL, txs); + twitter_groupchat(ic, output); + txs_free(txs); + g_slist_free(output); + return TRUE; + } + txs_free(txs); + return FALSE; } +gboolean twitter_open_stream(struct im_connection *ic) +{ + struct twitter_data *td = ic->proto_data; + char *args[4] = {"with", "followings", "delimited", "length"}; + + if ((td->stream = twitter_http(ic, TWITTER_USER_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->stream->flags |= HTTPC_STREAMING; + return TRUE; + } + + return FALSE; +} + +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); + /** * Get the timeline with optionally mentions */ @@ -791,23 +836,6 @@ void twitter_get_timeline(struct im_connection *ic, gint64 next_cursor) if (include_mentions) { twitter_get_mentions(ic, next_cursor); } - - static int bla = 0; - - if (bla) - return; - bla = 1; - - char *args[4]; - args[0] = "with"; - args[1] = "followings"; - args[2] = "delimited"; - args[3] = "length"; - - if ((td->stream = twitter_http(ic, "https://userstream.twitter.com/1.1/user.json", - twitter_http_stream, ic, 0, args, 4))) { - td->stream->flags |= HTTPC_STREAMING; - } } /** diff --git a/protocols/twitter/twitter_lib.h b/protocols/twitter/twitter_lib.h index 624fc80a..d5f5b16a 100644 --- a/protocols/twitter/twitter_lib.h +++ b/protocols/twitter/twitter_lib.h @@ -78,6 +78,9 @@ /* Report spam */ #define TWITTER_REPORT_SPAM_URL "/report_spam.json" +#define TWITTER_USER_STREAM_URL "https://userstream.twitter.com/1.1/user.json" + +gboolean twitter_open_stream(struct im_connection *ic); void 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); -- cgit v1.2.3 From 62f6b45d2056b90f57b908cd9bc9b6a995365c43 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 11 Nov 2012 16:53:56 +0000 Subject: Realised I don't need delimited=length at all, objects are guaranteed to be CRLF-terminated. --- protocols/twitter/twitter_lib.c | 54 +++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 32 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index 031d824e..3e5da854 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -731,49 +731,39 @@ static gboolean twitter_stream_handle_object(struct im_connection *ic, json_valu static void twitter_http_stream(struct http_request *req) { struct im_connection *ic = req->data; - //struct twitter_data *td = ic->proto_data; json_value *parsed; - int len, i; - char c; + int len = 0; + char c, *nl; if (!g_slist_find(twitter_connections, ic)) return; - //td = ic->proto_data;; printf( "%d bytes in stream\n", req->body_size ); - /* m/^[\d\s]*/ /* why is there a second commend here? :-) */ - for (i = 0; i < req->body_size; i ++) { - if (!isspace(req->reply_body[i]) && !isdigit(req->reply_body[i])) - break; - } - - /* Nothing but numbers and whitespace in there. Try again later. */ - if (i == req->body_size) - return; - - /* Get length. */ - if (sscanf(req->reply_body, "%d", &len) != 1) - return; + /* MUST search for CRLF, not just LF: + https://dev.twitter.com/docs/streaming-apis/processing#Parsing_responses */ + nl = strstr(req->reply_body, "\r\n"); - if (req->body_size < i + len) { - printf("Not enough bytes in buffer yet\n"); + if (!nl) { + printf("Incomplete data\n"); return; } - http_flush_bytes(req, i); - c = req->reply_body[len]; - req->reply_body[len] = '\0'; - - printf("JSON: %s\n", req->reply_body); - printf("parsed: %p\n", (parsed = json_parse(req->reply_body))); - if (parsed) { - twitter_stream_handle_object(ic, parsed); + len = nl - req->reply_body; + if (len > 0) { + c = req->reply_body[len]; + req->reply_body[len] = '\0'; + + printf("JSON: %s\n", req->reply_body); + printf("parsed: %p\n", (parsed = json_parse(req->reply_body))); + if (parsed) { + twitter_stream_handle_object(ic, parsed); + } + json_value_free(parsed); + req->reply_body[len] = c; } - json_value_free(parsed); - req->reply_body[len] = c; - http_flush_bytes(req, len); + http_flush_bytes(req, len + 2); /* One notification might bring multiple events! */ if (req->body_size > 0) @@ -798,10 +788,10 @@ static gboolean twitter_stream_handle_object(struct im_connection *ic, json_valu gboolean twitter_open_stream(struct im_connection *ic) { struct twitter_data *td = ic->proto_data; - char *args[4] = {"with", "followings", "delimited", "length"}; + char *args[2] = {"with", "followings"}; if ((td->stream = twitter_http(ic, TWITTER_USER_STREAM_URL, - twitter_http_stream, ic, 0, args, 4))) { + twitter_http_stream, ic, 0, args, 2))) { /* 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->stream->flags |= HTTPC_STREAMING; -- cgit v1.2.3 From 1388d303ba0d2097ff745d4a17192195cebbd349 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 11 Nov 2012 17:57:20 +0000 Subject: Mostly finished HTTP streaming support: Shrink the buffer and add a http_close(). --- protocols/twitter/twitter.c | 1 + 1 file changed, 1 insertion(+) (limited to 'protocols') diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c index ea440a15..f538f885 100644 --- a/protocols/twitter/twitter.c +++ b/protocols/twitter/twitter.c @@ -373,6 +373,7 @@ static void twitter_logout(struct im_connection *ic) imcb_chat_free(td->timeline_gc); if (td) { + http_close(td->stream); oauth_info_free(td->oauth_info); g_free(td->user); g_free(td->prefix); -- cgit v1.2.3 From 2fb1262a8200ec05d1b3334103fb7182dc2b2fa7 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 11 Nov 2012 18:22:39 +0000 Subject: Tiny cleanup. Fixing some memory leaks (why did I not notice so far that those free()s were commented out?). --- protocols/twitter/twitter_lib.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index 3e5da854..ee19786a 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -957,9 +957,7 @@ static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor) } g_free(args[1]); - if (td->timeline_id) { - g_free(args[5]); - } + g_free(args[5]); } /** @@ -985,7 +983,7 @@ static void twitter_http_get_home_timeline(struct http_request *req) if (!(parsed = twitter_parse_response(ic, req))) goto end; twitter_xt_get_status_list(ic, parsed, txl); -// json_value_free(parsed); + json_value_free(parsed); td->home_timeline_obj = txl; @@ -1021,7 +1019,7 @@ static void twitter_http_get_mentions(struct http_request *req) if (!(parsed = twitter_parse_response(ic, req))) goto end; twitter_xt_get_status_list(ic, parsed, txl); -// json_value_free(parsed); + json_value_free(parsed); td->mentions_obj = txl; -- cgit v1.2.3 From dd672e2c4d0dcf73a30be3d8f7fc2ec38cb6450e Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 11 Nov 2012 21:52:26 +0000 Subject: Detect and handle streaming connection loss. --- protocols/twitter/twitter_lib.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'protocols') diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index ee19786a..d7e54392 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -731,6 +731,7 @@ static gboolean twitter_stream_handle_object(struct im_connection *ic, json_valu static void twitter_http_stream(struct http_request *req) { struct im_connection *ic = req->data; + struct twitter_data *td; json_value *parsed; int len = 0; char c, *nl; @@ -738,6 +739,15 @@ static void twitter_http_stream(struct http_request *req) if (!g_slist_find(twitter_connections, ic)) return; + td = ic->proto_data; + + if ((req->flags & HTTPC_EOF) || !req->reply_body) { + td->stream = NULL; + imcb_error(ic, "Stream closed (%s)", req->status_string); + imc_logout(ic, TRUE); + return; + } + printf( "%d bytes in stream\n", req->body_size ); /* MUST search for CRLF, not just LF: -- cgit v1.2.3 From e132b60e77f395463cf95dc4ee09e96e9658ae35 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 11 Nov 2012 23:32:47 +0000 Subject: Extend keepalive code to time out connections when pings don't get acknowledged, using this for Twitter streams and MSN so far. --- protocols/msn/msn.c | 1 + protocols/msn/ns.c | 4 ++++ protocols/nogaim.c | 24 ++++++++++++++++++++++-- protocols/nogaim.h | 2 ++ protocols/twitter/twitter.c | 1 + protocols/twitter/twitter_lib.c | 1 + 6 files changed, 31 insertions(+), 2 deletions(-) (limited to 'protocols') diff --git a/protocols/msn/msn.c b/protocols/msn/msn.c index 71570ce0..845f9cf8 100644 --- a/protocols/msn/msn.c +++ b/protocols/msn/msn.c @@ -52,6 +52,7 @@ static void msn_login( account_t *acc ) struct msn_data *md = g_new0( struct msn_data, 1 ); ic->proto_data = md; + ic->flags |= OPT_PONGS | OPT_PONGED; if( strchr( acc->user, '@' ) == NULL ) { diff --git a/protocols/msn/ns.c b/protocols/msn/ns.c index d9a558f9..7acf4654 100644 --- a/protocols/msn/ns.c +++ b/protocols/msn/ns.c @@ -576,6 +576,10 @@ static int msn_ns_command( struct msn_handler_data *handler, char **cmd, int num if( num_parts >= 7 ) handler->msglen = atoi( cmd[6] ); } + else if( strcmp( cmd[0], "QNG" ) == 0 ) + { + ic->flags |= OPT_PONGED; + } else if( isdigit( cmd[0][0] ) ) { int num = atoi( cmd[0] ); diff --git a/protocols/nogaim.c b/protocols/nogaim.c index 773e3877..744f73ad 100644 --- a/protocols/nogaim.c +++ b/protocols/nogaim.c @@ -260,12 +260,32 @@ static gboolean send_keepalive( gpointer d, gint fd, b_input_condition cond ) { struct im_connection *ic = d; + if( ( ic->flags & OPT_PONGS ) && !( ic->flags & OPT_PONGED ) ) + { + /* This protocol is expected to ack keepalives and hasn't + since the last time we were here. */ + imcb_error( ic, "Connection timeout" ); + imc_logout( ic, TRUE ); + return FALSE; + } + ic->flags &= ~OPT_PONGED; + if( ic->acc->prpl->keepalive ) ic->acc->prpl->keepalive( ic ); return TRUE; } +void start_keepalives( struct im_connection *ic, int interval ) +{ + b_event_remove( ic->keepalive ); + ic->keepalive = b_timeout_add( interval, send_keepalive, ic ); + + /* Connecting successfully counts as a first successful pong. */ + if( ic->flags & OPT_PONGS ) + ic->flags |= OPT_PONGED; +} + void imcb_connected( struct im_connection *ic ) { /* MSN servers sometimes redirect you to a different server and do @@ -276,9 +296,9 @@ void imcb_connected( struct im_connection *ic ) imcb_log( ic, "Logged in" ); - b_event_remove( ic->keepalive ); - ic->keepalive = b_timeout_add( 60000, send_keepalive, ic ); ic->flags |= OPT_LOGGED_IN; + if( !ic->keepalive ) + start_keepalives( ic, 60000 ); /* Necessary to send initial presence status, even if we're not away. */ imc_away_send_update( ic ); diff --git a/protocols/nogaim.h b/protocols/nogaim.h index eccf77da..fb82fc73 100644 --- a/protocols/nogaim.h +++ b/protocols/nogaim.h @@ -67,6 +67,8 @@ #define OPT_TYPING 0x00000100 /* Some pieces of code make assumptions */ #define OPT_THINKING 0x00000200 /* about these values... Stupid me! */ #define OPT_NOOTR 0x00001000 /* protocol not suitable for OTR */ +#define OPT_PONGS 0x00010000 /* Service sends us keep-alives */ +#define OPT_PONGED 0x00020000 /* Received a keep-alive during last interval */ /* ok. now the fun begins. first we create a connection structure */ struct im_connection diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c index f538f885..6bde497a 100644 --- a/protocols/twitter/twitter.c +++ b/protocols/twitter/twitter.c @@ -71,6 +71,7 @@ static void twitter_main_loop_start(struct im_connection *ic) /* That fetch was just to get backlog, the stream will give us the rest. \o/ */ twitter_open_stream(ic); + ic->flags |= OPT_PONGS; } else { /* Not using the streaming API, so keep polling the old- fashioned way. :-( */ diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index d7e54392..bf5d76ab 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -739,6 +739,7 @@ static void twitter_http_stream(struct http_request *req) if (!g_slist_find(twitter_connections, ic)) return; + ic->flags |= OPT_PONGED; td = ic->proto_data; if ((req->flags & HTTPC_EOF) || !req->reply_body) { -- cgit v1.2.3 From aef207752aa19700bf5b68531243c36a6c8e02ca Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Mon, 12 Nov 2012 00:14:29 +0000 Subject: Bugfix: Two commits ago I broke cleanup of the account_on_timeout() handler, taking down every IM connection 2m after setup. --- protocols/nogaim.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'protocols') diff --git a/protocols/nogaim.c b/protocols/nogaim.c index 744f73ad..2f85a3eb 100644 --- a/protocols/nogaim.c +++ b/protocols/nogaim.c @@ -297,8 +297,7 @@ void imcb_connected( struct im_connection *ic ) imcb_log( ic, "Logged in" ); ic->flags |= OPT_LOGGED_IN; - if( !ic->keepalive ) - start_keepalives( ic, 60000 ); + start_keepalives( ic, 60000 ); /* Necessary to send initial presence status, even if we're not away. */ imc_away_send_update( ic ); -- cgit v1.2.3 From d1356cb8b0f964ddf7de50e1ba52eecc271e470a Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Mon, 12 Nov 2012 23:57:43 +0000 Subject: Decode incoming DMs. --- protocols/twitter/twitter_lib.c | 101 ++++++++++++++++++++++++++++++---------- 1 file changed, 76 insertions(+), 25 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index bf5d76ab..57c84f24 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -447,6 +447,8 @@ static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list * #define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S +0000 %Y" #endif +static char* expand_entities(char* text, const json_value *entities); + /** * Function to fill a twitter_xml_status struct. * It sets: @@ -499,36 +501,77 @@ static gboolean twitter_xt_get_status(const json_value *node, struct twitter_xml txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text); txs_free(rtxs); } else if (entities) { - JSON_O_FOREACH (entities, k, v) { - int i; - - if (v->type != json_array) + txs->text = expand_entities(txs->text, entities); + } + + return txs->text && txs->user && txs->id; +} + +/** + * Function to fill a twitter_xml_status struct (DM variant). + */ +static gboolean twitter_xt_get_dm(const json_value *node, struct twitter_xml_status *txs) +{ + const json_value *entities = NULL; + + if (node->type != json_object) + return FALSE; + + JSON_O_FOREACH (node, k, v) { + if (strcmp("text", k) == 0 && v->type == json_string) { + txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1); + } else if (strcmp("created_at", k) == 0 && v->type == json_string) { + struct tm parsed; + + /* Very sensitive to changes to the formatting of + this field. :-( Also assumes the timezone used + is UTC since C time handling functions suck. */ + if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL) + txs->created_at = mktime_utc(&parsed); + } else if (strcmp("sender", k) == 0 && v->type == json_object) { + txs->user = twitter_xt_get_user(v); + } else if (strcmp("id", k) == 0 && v->type == json_integer) { + txs->id = v->u.integer; + } + } + + if (entities) { + txs->text = expand_entities(txs->text, entities); + } + + return txs->text && txs->user && txs->id; +} + +static char* expand_entities(char* text, const json_value *entities) { + JSON_O_FOREACH (entities, k, v) { + int i; + + if (v->type != json_array) + continue; + if (strcmp(k, "urls") != 0 && strcmp(k, "media") != 0) + continue; + + for (i = 0; i < v->u.array.length; i ++) { + if (v->u.array.values[i]->type != json_object) continue; - if (strcmp(k, "urls") != 0 && strcmp(k, "media") != 0) + + const char *kort = json_o_str(v->u.array.values[i], "url"); + const char *disp = json_o_str(v->u.array.values[i], "display_url"); + char *pos, *new; + + if (!kort || !disp || !(pos = strstr(text, kort))) continue; - for (i = 0; i < v->u.array.length; i ++) { - if (v->u.array.values[i]->type != json_object) - continue; - - const char *kort = json_o_str(v->u.array.values[i], "url"); - const char *disp = json_o_str(v->u.array.values[i], "display_url"); - char *pos, *new; - - if (!kort || !disp || !(pos = strstr(txs->text, kort))) - continue; - - *pos = '\0'; - new = g_strdup_printf("%s%s <%s>%s", txs->text, kort, - disp, pos + strlen(kort)); - - g_free(txs->text); - txs->text = new; - } + *pos = '\0'; + new = g_strdup_printf("%s%s <%s>%s", text, kort, + disp, pos + strlen(kort)); + + g_free(text); + text = new; } } - - return txs->text && txs->user && txs->id; + + return text; } /** @@ -784,6 +827,7 @@ static void twitter_http_stream(struct http_request *req) static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o) { struct twitter_xml_status *txs = g_new0(struct twitter_xml_status, 1); + json_value *c; if (twitter_xt_get_status(o, txs)) { GSList *output = g_slist_append(NULL, txs); @@ -791,6 +835,13 @@ static gboolean twitter_stream_handle_object(struct im_connection *ic, json_valu txs_free(txs); g_slist_free(output); return TRUE; + } else if ((c = json_o_get(o, "direct_message")) && + twitter_xt_get_dm(c, txs)) { + GSList *output = g_slist_append(NULL, txs); + twitter_private_message_chat(ic, output); + txs_free(txs); + g_slist_free(output); + return TRUE; } txs_free(txs); return FALSE; -- cgit v1.2.3 From 5aa96fc81a66224d9218e54d70944b1ef3d26e2d Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Tue, 20 Nov 2012 00:20:10 +0000 Subject: Some code for handling disconnect/event messages. --- protocols/twitter/twitter_lib.c | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) (limited to 'protocols') diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index 57c84f24..ff1d9f22 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -824,8 +824,11 @@ static void twitter_http_stream(struct http_request *req) twitter_http_stream(req); } +static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o); + static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o) { + struct twitter_data *td = ic->proto_data; struct twitter_xml_status *txs = g_new0(struct twitter_xml_status, 1); json_value *c; @@ -842,11 +845,49 @@ static gboolean twitter_stream_handle_object(struct im_connection *ic, json_valu txs_free(txs); g_slist_free(output); return TRUE; + } else if ((c = json_o_get(o, "event")) && c->type == json_string) { + twitter_stream_handle_event(ic, o); + return TRUE; + } else if ((c = json_o_get(o, "disconnect")) && c->type == json_object) { + /* HACK: Because we're inside an event handler, we can't just + disconnect here. Instead, just change the HTTP status string + into a Twitter status string. */ + char *reason = json_o_strdup(c, "reason"); + if (reason) { + g_free(td->stream->status_string); + td->stream->status_string = reason; + } + return TRUE; } txs_free(txs); return FALSE; } +static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o) +{ + struct twitter_data *td = ic->proto_data; + json_value *source = json_o_get(o, "source"); + json_value *target = json_o_get(o, "target"); + const char *type = json_o_str(o, "event"); + + if (!type || !source || source->type != json_object + || !target || target->type != json_object) { + return FALSE; + } + + if (strcmp(type, "follow") == 0) { + struct twitter_xml_user *us = twitter_xt_get_user(source); + struct twitter_xml_user *ut = twitter_xt_get_user(target); + if (strcmp(us->screen_name, td->user) == 0) { + twitter_add_buddy(ic, ut->screen_name, ut->name); + } + txu_free(us); + txu_free(ut); + } + + return TRUE; +} + gboolean twitter_open_stream(struct im_connection *ic) { struct twitter_data *td = ic->proto_data; -- cgit v1.2.3 From c751e512afd4dc200c04a70cc6d0edb94e63e8b3 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sat, 24 Nov 2012 00:15:17 +0000 Subject: Fixing one obvious memory leak. --- protocols/twitter/twitter_lib.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'protocols') diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index ff1d9f22..36efc18a 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -1157,6 +1157,8 @@ static void twitter_http_post(struct http_request *req) if ((id = json_o_get(parsed, "id")) && id->type == json_integer) td->last_status_id = id->u.integer; + + json_value_free(parsed); } /** -- cgit v1.2.3 From f26d9a3e54a89b640902d6ffad4f3d32b76dc97e Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sat, 24 Nov 2012 14:10:34 +0000 Subject: Add help info for "stream" setting. Also, disable it for non-Twitter accounts. --- protocols/twitter/twitter.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c index 6bde497a..b7eacc4b 100644 --- a/protocols/twitter/twitter.c +++ b/protocols/twitter/twitter.c @@ -64,13 +64,17 @@ static void twitter_main_loop_start(struct im_connection *ic) imcb_log(ic, "Getting initial statuses"); - // Run this once. After this queue the main loop function. + // Run this once. After this queue the main loop function (or open the + // stream if available). twitter_main_loop(ic, -1, 0); if (set_getbool(&ic->acc->set, "stream")) { /* That fetch was just to get backlog, the stream will give us the rest. \o/ */ twitter_open_stream(ic); + + /* Stream sends keepalives (empty lines) or actual data at + least twice a minute. Disconnect if this stops. */ ic->flags |= OPT_PONGS; } else { /* Not using the streaming API, so keep polling the old- @@ -95,7 +99,6 @@ void twitter_login_finish(struct im_connection *ic) !(td->flags & TWITTER_HAVE_FRIENDS)) { imcb_log(ic, "Getting contact list"); twitter_get_friends_ids(ic, -1); - //twitter_get_statuses_friends(ic, -1); } else twitter_main_loop_start(ic); } @@ -288,12 +291,14 @@ static void twitter_init(account_t * acc) s = set_add(&acc->set, "strip_newlines", "false", set_eval_bool, acc); - s = set_add(&acc->set, "stream", "true", set_eval_bool, acc); - s->flags |= ACC_SET_OFFLINE_ONLY; + if (strcmp(acc->prpl->name, "twitter") == 0) { + s = set_add(&acc->set, "stream", "true", set_eval_bool, acc); + s->flags |= ACC_SET_OFFLINE_ONLY; + } } /** - * Login method. Since the twitter API works with seperate HTTP request we + * Login method. Since the twitter API works with separate HTTP request we * only save the user and pass to the twitter_data object. */ static void twitter_login(account_t * acc) @@ -311,6 +316,12 @@ static void twitter_login(account_t * acc) return; } + if (!strstr(url.host, "twitter.com") && + set_getbool(&ic->acc->set, "stream")) { + imcb_error(ic, "Warning: The streaming API is only supported by Twitter, " + "and you seem to be connecting to a different service."); + } + imcb_log(ic, "Connecting"); twitter_connections = g_slist_append(twitter_connections, ic); -- cgit v1.2.3 From c9b5817211cfcbf66a44c6a347245d5913806f91 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 25 Nov 2012 00:55:47 +0000 Subject: Minor rework: Always fill td->log now and use it not just for show_ids, but also for stream deduplication. Also, drop tweets from unknown people unless fetch_mentions is set. The stream will feed us that spam either way but not everyone wants to see it. Last, fixing a bug where in streaming mode, per-user last tweet times were no longer getting tracked. --- protocols/twitter/twitter.c | 5 +- protocols/twitter/twitter_lib.c | 131 ++++++++++++++++++++++++++-------------- 2 files changed, 86 insertions(+), 50 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c index b7eacc4b..d6cc89f1 100644 --- a/protocols/twitter/twitter.c +++ b/protocols/twitter/twitter.c @@ -285,7 +285,6 @@ static void twitter_init(account_t * acc) s = set_add(&acc->set, "oauth", "true", set_eval_oauth, acc); s = set_add(&acc->set, "show_ids", "true", set_eval_bool, acc); - s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add(&acc->set, "show_old_mentions", "20", set_eval_int, acc); @@ -362,8 +361,8 @@ static void twitter_login(account_t * acc) imcb_add_buddy(ic, name, NULL); imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL); - if (set_getbool(&acc->set, "show_ids")) - td->log = g_new0(struct twitter_log_data, TWITTER_LOG_LENGTH); + td->log = g_new0(struct twitter_log_data, TWITTER_LOG_LENGTH); + td->log_id = -1; twitter_login_finish(ic); } diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index 36efc18a..7048e6ae 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -457,12 +457,14 @@ static char* expand_entities(char* text, const json_value *entities); * - the status id and * - the user in a twitter_xml_user struct. */ -static gboolean twitter_xt_get_status(const json_value *node, struct twitter_xml_status *txs) +static struct twitter_xml_status *twitter_xt_get_status(const json_value *node) { + struct twitter_xml_status *txs; const json_value *rt = NULL, *entities = NULL; if (node->type != json_object) return FALSE; + txs = g_new0(struct twitter_xml_status, 1); JSON_O_FOREACH (node, k, v) { if (strcmp("text", k) == 0 && v->type == json_string) { @@ -491,20 +493,22 @@ static gboolean twitter_xt_get_status(const json_value *node, struct twitter_xml /* If it's a (truncated) retweet, get the original. Even if the API claims it wasn't truncated because it may be lying. */ if (rt) { - struct twitter_xml_status *rtxs = g_new0(struct twitter_xml_status, 1); - if (!twitter_xt_get_status(rt, rtxs)) { + 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->id = rtxs->id; txs_free(rtxs); - return TRUE; } - - g_free(txs->text); - txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text); - txs_free(rtxs); } else if (entities) { txs->text = expand_entities(txs->text, entities); } - return txs->text && txs->user && txs->id; + if (txs->text && txs->user && txs->id) + return txs; + + txs_free(txs); + return NULL; } /** @@ -584,7 +588,6 @@ static gboolean twitter_xt_get_status_list(struct im_connection *ic, const json_ struct twitter_xml_list *txl) { struct twitter_xml_status *txs; - bee_user_t *bu; int i; // Set the type of the list. @@ -596,54 +599,61 @@ static gboolean twitter_xt_get_status_list(struct im_connection *ic, const json_ // The root node should hold the list of statuses // Walk over the nodes children. for (i = 0; i < node->u.array.length; i ++) { - txs = g_new0(struct twitter_xml_status, 1); - twitter_xt_get_status(node->u.array.values[i], txs); - // Put the item in the front of the list. + txs = twitter_xt_get_status(node->u.array.values[i]); + if (!txs) + continue; + txl->list = g_slist_prepend(txl->list, txs); - - if (txs->user && txs->user->screen_name && - (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) { - struct twitter_user_data *tud = bu->data; - - if (txs->id > tud->last_id) { - tud->last_id = txs->id; - tud->last_time = txs->created_at; - } - } } return TRUE; } +/* 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, struct twitter_xml_status *txs, const char *prefix) { struct twitter_data *td = ic->proto_data; - char *ret = NULL; - - if (!set_getbool(&ic->acc->set, "show_ids")) { - if (*prefix) - return g_strconcat(prefix, txs->text, NULL); - else - return NULL; - } + int reply_to = -1; + bee_user_t *bu; - td->log[td->log_id].id = txs->id; - td->log[td->log_id].bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name); if (txs->reply_to) { int i; for (i = 0; i < TWITTER_LOG_LENGTH; i++) if (td->log[i].id == txs->reply_to) { - ret = g_strdup_printf("\002[\002%02d->%02d\002]\002 %s%s", - td->log_id, i, prefix, txs->text); + reply_to = i; break; } } - if (ret == NULL) - ret = g_strdup_printf("\002[\002%02d\002]\002 %s%s", td->log_id, prefix, txs->text); - td->log_id = (td->log_id + 1) % TWITTER_LOG_LENGTH; - return ret; + if (txs->user && txs->user->screen_name && + (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) { + struct twitter_user_data *tud = bu->data; + + if (txs->id > tud->last_id) { + tud->last_id = txs->id; + tud->last_time = txs->created_at; + } + } + + td->log_id = (td->log_id + 1) % TWITTER_LOG_LENGTH; + td->log[td->log_id].id = txs->id; + td->log[td->log_id].bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name); + + if (set_getbool(&ic->acc->set, "show_ids")) { + if (reply_to != -1) + return g_strdup_printf("\002[\002%02d->%02d\002]\002 %s%s", + td->log_id, reply_to, prefix, txs->text); + else + return g_strdup_printf("\002[\002%02d\002]\002 %s%s", + td->log_id, prefix, txs->text); + } else { + if (*prefix) + return g_strconcat(prefix, txs->text, NULL); + else + return NULL; + } } static void twitter_groupchat_init(struct im_connection *ic) @@ -825,19 +835,16 @@ 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) { struct twitter_data *td = ic->proto_data; - struct twitter_xml_status *txs = g_new0(struct twitter_xml_status, 1); + struct twitter_xml_status *txs; json_value *c; - if (twitter_xt_get_status(o, txs)) { - GSList *output = g_slist_append(NULL, txs); - twitter_groupchat(ic, output); - txs_free(txs); - g_slist_free(output); - return TRUE; + if ((txs = twitter_xt_get_status(o))) { + return twitter_stream_handle_status(ic, txs); } else if ((c = json_o_get(o, "direct_message")) && twitter_xt_get_dm(c, txs)) { GSList *output = g_slist_append(NULL, txs); @@ -859,10 +866,40 @@ static gboolean twitter_stream_handle_object(struct im_connection *ic, json_valu } return TRUE; } - txs_free(txs); return FALSE; } +static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs) +{ + struct twitter_data *td = ic->proto_data; + int i; + + for (i = 0; i < TWITTER_LOG_LENGTH; i++) { + if (td->log[i].id == txs->id) { + /* Got a duplicate (RT, surely). Drop it. */ + txs_free(txs); + return TRUE; + } + } + + if (!(set_getbool(&ic->acc->set, "fetch_mentions") || + bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) { + /* Tweet is from an unknown person and the user does not want + to see @mentions, so drop it. twitter_stream_handle_event() + picks up new follows so this simple filter should be safe. */ + /* TODO: The streaming API seems to do poor @mention matching. + I.e. I'm getting mentions for @WilmerSomething, not just for + @Wilmer. But meh. You want spam, you get spam. */ + return TRUE; + } + + GSList *output = g_slist_append(NULL, txs); + twitter_groupchat(ic, output); + txs_free(txs); + g_slist_free(output); + return TRUE; +} + static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o) { struct twitter_data *td = ic->proto_data; -- cgit v1.2.3 From 2cd8540c1076c3d0423abb1927bd47fdcbfbd382 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 25 Nov 2012 12:05:48 +0000 Subject: Show DMs the right way. With mode=many and show_ids=off they'll look just like tweets but that's what you get with bad settings.. Also, don't show DMs from ourselves. --- protocols/twitter/twitter_lib.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index 7048e6ae..676d804d 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -514,12 +514,14 @@ static struct twitter_xml_status *twitter_xt_get_status(const json_value *node) /** * Function to fill a twitter_xml_status struct (DM variant). */ -static gboolean twitter_xt_get_dm(const json_value *node, struct twitter_xml_status *txs) +static struct twitter_xml_status *twitter_xt_get_dm(const json_value *node) { + struct twitter_xml_status *txs; const json_value *entities = NULL; if (node->type != json_object) return FALSE; + txs = g_new0(struct twitter_xml_status, 1); JSON_O_FOREACH (node, k, v) { if (strcmp("text", k) == 0 && v->type == json_string) { @@ -543,7 +545,11 @@ static gboolean twitter_xt_get_dm(const json_value *node, struct twitter_xml_sta txs->text = expand_entities(txs->text, entities); } - return txs->text && txs->user && txs->id; + if (txs->text && txs->user && txs->id) + return txs; + + txs_free(txs); + return NULL; } static char* expand_entities(char* text, const json_value *entities) { @@ -846,11 +852,11 @@ static gboolean twitter_stream_handle_object(struct im_connection *ic, json_valu if ((txs = twitter_xt_get_status(o))) { return twitter_stream_handle_status(ic, txs); } else if ((c = json_o_get(o, "direct_message")) && - twitter_xt_get_dm(c, txs)) { - GSList *output = g_slist_append(NULL, txs); - twitter_private_message_chat(ic, output); + (txs = twitter_xt_get_dm(c))) { + if (strcmp(txs->user->screen_name, td->user) != 0) + imcb_buddy_msg(ic, txs->user->screen_name, + txs->text, 0, txs->created_at); txs_free(txs); - g_slist_free(output); return TRUE; } else if ((c = json_o_get(o, "event")) && c->type == json_string) { twitter_stream_handle_event(ic, o); -- cgit v1.2.3 From 96dd574444f2c99bb0a82b2c354804f03e306f23 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 25 Nov 2012 13:11:19 +0000 Subject: s/twitter_msg/twitter_log/ and use it in a few more places. --- protocols/twitter/twitter.c | 41 +++++++++++++++++++++++++---------------- protocols/twitter/twitter.h | 14 ++++++++++---- protocols/twitter/twitter_lib.c | 4 ++-- 3 files changed, 37 insertions(+), 22 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c index d6cc89f1..ee410abc 100644 --- a/protocols/twitter/twitter.c +++ b/protocols/twitter/twitter.c @@ -3,7 +3,7 @@ * BitlBee - An IRC to IM gateway * * Simple module to facilitate twitter functionality. * * * -* Copyright 2009 Geert Mulders * +* Copyright 2009-2010 Geert Mulders * * Copyright 2010-2012 Wilmer van der Gaast * * * * This library is free software; you can redistribute it and/or * @@ -29,17 +29,7 @@ #include "twitter_lib.h" #include "url.h" -#define twitter_msg( ic, fmt... ) \ - do { \ - struct twitter_data *td = ic->proto_data; \ - if( td->timeline_gc ) \ - imcb_chat_log( td->timeline_gc, fmt ); \ - else \ - imcb_log( ic, fmt ); \ - } while( 0 ); - GSList *twitter_connections = NULL; - /** * Main loop function */ @@ -244,7 +234,7 @@ static gboolean twitter_length_check(struct im_connection *ic, gchar * msg) if (max == 0 || (len = g_utf8_strlen(msg, -1) + url_len_diff) <= max) return TRUE; - imcb_error(ic, "Maximum message length exceeded: %d > %d", len, max); + twitter_log(ic, "Maximum message length exceeded: %d > %d", len, max); return FALSE; } @@ -560,7 +550,7 @@ static void twitter_handle_command(struct im_connection *ic, char *message) twitter_status_destroy(ic, id); } else - twitter_msg(ic, "Could not undo last action"); + twitter_log(ic, "Could not undo last action"); g_free(cmds); return; @@ -569,7 +559,7 @@ static void twitter_handle_command(struct im_connection *ic, char *message) if ((id = twitter_message_id_from_command_arg(ic, td, cmd[1]))) { twitter_favourite_tweet(ic, id); } else { - twitter_msg(ic, "Please provide a message ID or username."); + twitter_log(ic, "Please provide a message ID or username."); } g_free(cmds); return; @@ -606,7 +596,7 @@ static void twitter_handle_command(struct im_connection *ic, char *message) if (id) twitter_status_retweet(ic, id); else - twitter_msg(ic, "User `%s' does not exist or didn't " + twitter_log(ic, "User `%s' does not exist or didn't " "post any statuses recently", cmd[1]); g_free(cmds); @@ -637,7 +627,7 @@ static void twitter_handle_command(struct im_connection *ic, char *message) } if (!id || !bu) { - twitter_msg(ic, "User `%s' does not exist or didn't " + twitter_log(ic, "User `%s' does not exist or didn't " "post any statuses recently", cmd[1]); g_free(cmds); return; @@ -684,6 +674,25 @@ static void twitter_handle_command(struct im_connection *ic, char *message) g_free(cmds); } +void twitter_log(struct im_connection *ic, char *format, ... ) +{ + struct twitter_data *td = ic->proto_data; + va_list params; + char *text; + + va_start(params, format); + text = g_strdup_vprintf(format, params); + va_end(params); + + if (td->timeline_gc) + imcb_chat_log(td->timeline_gc, "%s", text); + else + imcb_log(ic, "%s", text); + + g_free(text); +} + + void twitter_initmodule() { struct prpl *ret = g_new0(struct prpl, 1); diff --git a/protocols/twitter/twitter.h b/protocols/twitter/twitter.h index f2afb754..108d20b3 100644 --- a/protocols/twitter/twitter.h +++ b/protocols/twitter/twitter.h @@ -3,7 +3,8 @@ * BitlBee - An IRC to IM gateway * * Simple module to facilitate twitter functionality. * * * -* Copyright 2009 Geert Mulders * +* Copyright 2009-2010 Geert Mulders * +* Copyright 2010-2012 Wilmer van der Gaast * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * @@ -34,10 +35,13 @@ typedef enum { - TWITTER_HAVE_FRIENDS = 1, + TWITTER_HAVE_FRIENDS = 0x00001, + TWITTER_MODE_ONE = 0x00002, + TWITTER_MODE_MANY = 0x00004, + TWITTER_MODE_CHAT = 0x00008, TWITTER_DOING_TIMELINE = 0x10000, - TWITTER_GOT_TIMELINE = 0x20000, - TWITTER_GOT_MENTIONS = 0x40000, + TWITTER_GOT_TIMELINE = 0x20000, + TWITTER_GOT_MENTIONS = 0x40000, } twitter_flags_t; struct twitter_log_data; @@ -99,4 +103,6 @@ void twitter_login_finish( struct im_connection *ic ); struct http_request; char *twitter_parse_error( struct http_request *req ); +void twitter_log(struct im_connection *ic, char *format, ... ); + #endif //_TWITTER_H diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index 676d804d..f358b307 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -219,8 +219,8 @@ static json_value *twitter_parse_response(struct im_connection *ic, struct http_ } else if (req->status_code != 200) { // It didn't go well, output the error and return. if (!periodic || logging_in || ++td->http_fails >= 5) - imcb_error(ic, "Could not retrieve %s: %s", - path, twitter_parse_error(req)); + twitter_log(ic, "Error: Could not retrieve %s: %s", + path, twitter_parse_error(req)); if (logging_in) imc_logout(ic, TRUE); -- cgit v1.2.3 From 631ec80ccd9d3deeb590ae9537ad4260d804363d Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 25 Nov 2012 14:26:23 +0000 Subject: Changed mode/room management a little bit. --- protocols/twitter/twitter.c | 42 +++++++++++++++++++++++++++++-- protocols/twitter/twitter.h | 1 + protocols/twitter/twitter_lib.c | 56 ++++++++--------------------------------- 3 files changed, 51 insertions(+), 48 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c index ee410abc..25304bbf 100644 --- a/protocols/twitter/twitter.c +++ b/protocols/twitter/twitter.c @@ -52,6 +52,10 @@ static void twitter_main_loop_start(struct im_connection *ic) { struct twitter_data *td = ic->proto_data; + /* Create the room now that we "logged in". */ + if (td->flags & TWITTER_MODE_CHAT) + twitter_groupchat_init(ic); + imcb_log(ic, "Getting initial statuses"); // Run this once. After this queue the main loop function (or open the @@ -75,6 +79,32 @@ static void twitter_main_loop_start(struct im_connection *ic) } } +struct groupchat *twitter_groupchat_init(struct im_connection *ic) +{ + char *name_hint; + struct groupchat *gc; + struct twitter_data *td = ic->proto_data; + GSList *l; + + if (td->timeline_gc) + return td->timeline_gc; + + td->timeline_gc = gc = imcb_chat_new(ic, "twitter/timeline"); + + name_hint = g_strdup_printf("%s_%s", td->prefix, ic->acc->user); + imcb_chat_name_hint(gc, name_hint); + g_free(name_hint); + + for (l = ic->bee->users; l; l = l->next) { + bee_user_t *bu = l->data; + if (bu->ic == ic) + imcb_chat_add_buddy(td->timeline_gc, bu->handle); + } + imcb_chat_add_buddy(gc, ic->acc->user); + + return gc; +} + static void twitter_oauth_start(struct im_connection *ic); void twitter_login_finish(struct im_connection *ic) @@ -85,8 +115,8 @@ void twitter_login_finish(struct im_connection *ic) if (set_getbool(&ic->acc->set, "oauth") && !td->oauth_info) twitter_oauth_start(ic); - else if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "one") != 0 && - !(td->flags & TWITTER_HAVE_FRIENDS)) { + else if ((td->flags & TWITTER_MODE_ONE) && + !(td->flags & TWITTER_HAVE_FRIENDS)) { imcb_log(ic, "Getting contact list"); twitter_get_friends_ids(ic, -1); } else @@ -353,6 +383,14 @@ static void twitter_login(account_t * acc) td->log = g_new0(struct twitter_log_data, TWITTER_LOG_LENGTH); td->log_id = -1; + + s = set_getstr(&ic->acc->set, "mode"); + if (g_strcasecmp(s, "one") == 0) + td->flags |= TWITTER_MODE_ONE; + else if (g_strcasecmp(s, "many") == 0) + td->flags |= TWITTER_MODE_MANY; + else + td->flags |= TWITTER_MODE_CHAT; twitter_login_finish(ic); } diff --git a/protocols/twitter/twitter.h b/protocols/twitter/twitter.h index 108d20b3..ceac4371 100644 --- a/protocols/twitter/twitter.h +++ b/protocols/twitter/twitter.h @@ -104,5 +104,6 @@ struct http_request; char *twitter_parse_error( struct http_request *req ); void twitter_log(struct im_connection *ic, char *format, ... ); +struct groupchat *twitter_groupchat_init(struct im_connection *ic); #endif //_TWITTER_H diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index f358b307..ad349c6f 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -69,8 +69,6 @@ struct twitter_xml_status { guint64 id, reply_to; }; -static void twitter_groupchat_init(struct im_connection *ic); - /** * Frees a twitter_xml_user struct. */ @@ -147,17 +145,15 @@ static void twitter_add_buddy(struct im_connection *ic, char *name, const char * // Check if the buddy is already in the buddy list. if (!bee_user_by_handle(ic->bee, ic, name)) { - char *mode = set_getstr(&ic->acc->set, "mode"); - // The buddy is not in the list, add the buddy and set the status to logged in. imcb_add_buddy(ic, name, NULL); imcb_rename_buddy(ic, name, fullname); - if (g_strcasecmp(mode, "chat") == 0) { + if (td->flags & TWITTER_MODE_CHAT) { /* Necessary so that nicks always get translated to the exact Twitter username. */ imcb_buddy_nick_hint(ic, name, name); imcb_chat_add_buddy(td->timeline_gc, name); - } else if (g_strcasecmp(mode, "many") == 0) + } else if (td->flags & TWITTER_MODE_MANY) imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL); } } @@ -305,10 +301,6 @@ static void twitter_http_get_friends_ids(struct http_request *req) td = ic->proto_data; - /* Create the room now that we "logged in". */ - if (!td->timeline_gc && g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0) - twitter_groupchat_init(ic); - txl = g_new0(struct twitter_xml_list, 1); txl->list = td->follow_ids; @@ -662,26 +654,6 @@ static char *twitter_msg_add_id(struct im_connection *ic, } } -static void twitter_groupchat_init(struct im_connection *ic) -{ - char *name_hint; - struct groupchat *gc; - struct twitter_data *td = ic->proto_data; - GSList *l; - - td->timeline_gc = gc = imcb_chat_new(ic, "twitter/timeline"); - - name_hint = g_strdup_printf("%s_%s", td->prefix, ic->acc->user); - imcb_chat_name_hint(gc, name_hint); - g_free(name_hint); - - for (l = ic->bee->users; l; l = l->next) { - bee_user_t *bu = l->data; - if (bu->ic == ic) - imcb_chat_add_buddy(td->timeline_gc, bu->handle); - } -} - /** * Function that is called to see the statuses in a groupchat window. */ @@ -694,12 +666,7 @@ static void twitter_groupchat(struct im_connection *ic, GSList * list) guint64 last_id = 0; // Create a new groupchat if it does not exsist. - if (!td->timeline_gc) - twitter_groupchat_init(ic); - - gc = td->timeline_gc; - if (!gc->joined) - imcb_chat_add_buddy(gc, ic->acc->user); + gc = twitter_groupchat_init(ic); for (l = list; l; l = g_slist_next(l)) { char *msg; @@ -743,13 +710,10 @@ static void twitter_private_message_chat(struct im_connection *ic, GSList * list struct twitter_data *td = ic->proto_data; GSList *l = NULL; struct twitter_xml_status *status; - char from[MAX_STRING]; - gboolean mode_one; + char from[MAX_STRING] = ""; guint64 last_id = 0; - mode_one = g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "one") == 0; - - if (mode_one) { + if (td->flags & TWITTER_MODE_ONE) { g_snprintf(from, sizeof(from) - 1, "%s_%s", td->prefix, ic->acc->user); from[MAX_STRING - 1] = '\0'; } @@ -764,17 +728,17 @@ static void twitter_private_message_chat(struct im_connection *ic, GSList * list last_id = status->id; strip_html(status->text); - if (mode_one) + if (td->flags & TWITTER_MODE_ONE) prefix = g_strdup_printf("\002<\002%s\002>\002 ", - status->user->screen_name); + status->user->screen_name); else twitter_add_buddy(ic, status->user->screen_name, status->user->name); text = twitter_msg_add_id(ic, status, prefix ? prefix : ""); imcb_buddy_msg(ic, - mode_one ? from : status->user->screen_name, - text ? text : status->text, 0, status->created_at); + *from ? from : status->user->screen_name, + text ? text : status->text, 0, status->created_at); // Update the timeline_id to hold the highest id, so that by the next request // we won't pick up the updates already in the list. @@ -1016,7 +980,7 @@ void twitter_flush_timeline(struct im_connection *ic) imcb_connected(ic); // See if the user wants to see the messages in a groupchat window or as private messages. - if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0) + if (td->flags & TWITTER_MODE_CHAT) twitter_groupchat(ic, output); else twitter_private_message_chat(ic, output); -- cgit v1.2.3 From 7f557d53d986c5f688c5781fea04a46464e5b491 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 25 Nov 2012 14:58:29 +0000 Subject: Fixing two oopses from my last commit. --- protocols/twitter/twitter.c | 2 +- protocols/twitter/twitter_lib.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c index 25304bbf..4f79cd5f 100644 --- a/protocols/twitter/twitter.c +++ b/protocols/twitter/twitter.c @@ -115,7 +115,7 @@ void twitter_login_finish(struct im_connection *ic) if (set_getbool(&ic->acc->set, "oauth") && !td->oauth_info) twitter_oauth_start(ic); - else if ((td->flags & TWITTER_MODE_ONE) && + else if (!(td->flags & TWITTER_MODE_ONE) && !(td->flags & TWITTER_HAVE_FRIENDS)) { imcb_log(ic, "Getting contact list"); twitter_get_friends_ids(ic, -1); diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index ad349c6f..1b7f382a 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -152,7 +152,8 @@ static void twitter_add_buddy(struct im_connection *ic, char *name, const char * /* Necessary so that nicks always get translated to the exact Twitter username. */ imcb_buddy_nick_hint(ic, name, name); - imcb_chat_add_buddy(td->timeline_gc, name); + if (td->timeline_gc) + imcb_chat_add_buddy(td->timeline_gc, name); } else if (td->flags & TWITTER_MODE_MANY) imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL); } -- cgit v1.2.3 From 29f72b7a4b4fa960b6fccb45243fa3f5d87a99bf Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 25 Nov 2012 17:43:13 +0000 Subject: Finally cleaned up the show-tweet functions. --- protocols/twitter/twitter.c | 2 +- protocols/twitter/twitter_lib.c | 145 +++++++++++++++++++--------------------- 2 files changed, 69 insertions(+), 78 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c index 4f79cd5f..2dfb4a26 100644 --- a/protocols/twitter/twitter.c +++ b/protocols/twitter/twitter.c @@ -98,7 +98,7 @@ struct groupchat *twitter_groupchat_init(struct im_connection *ic) for (l = ic->bee->users; l; l = l->next) { bee_user_t *bu = l->data; if (bu->ic == ic) - imcb_chat_add_buddy(td->timeline_gc, bu->handle); + imcb_chat_add_buddy(gc, bu->handle); } imcb_chat_add_buddy(gc, ic->acc->user); diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index 1b7f382a..b888a730 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -462,6 +462,7 @@ static struct twitter_xml_status *twitter_xt_get_status(const json_value *node) JSON_O_FOREACH (node, k, v) { if (strcmp("text", k) == 0 && v->type == json_string) { txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1); + strip_html(txs->text); } else if (strcmp("retweeted_status", k) == 0 && v->type == json_object) { rt = v; } else if (strcmp("created_at", k) == 0 && v->type == json_string) { @@ -519,6 +520,7 @@ static struct twitter_xml_status *twitter_xt_get_dm(const json_value *node) JSON_O_FOREACH (node, k, v) { if (strcmp("text", k) == 0 && v->type == json_string) { txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1); + strip_html(txs->text); } else if (strcmp("created_at", k) == 0 && v->type == json_string) { struct tm parsed; @@ -566,7 +568,7 @@ static char* expand_entities(char* text, const json_value *entities) { continue; *pos = '\0'; - new = g_strdup_printf("%s%s <%s>%s", text, kort, + new = g_strdup_printf("%s%s <%s>%s", text, kort, disp, pos + strlen(kort)); g_free(text); @@ -658,96 +660,85 @@ static char *twitter_msg_add_id(struct im_connection *ic, /** * Function that is called to see the statuses in a groupchat window. */ -static void twitter_groupchat(struct im_connection *ic, GSList * list) +static void twitter_status_show_chat(struct im_connection *ic, struct twitter_xml_status *status) { struct twitter_data *td = ic->proto_data; - GSList *l = NULL; - struct twitter_xml_status *status; struct groupchat *gc; - guint64 last_id = 0; + gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0; + char *msg; // Create a new groupchat if it does not exsist. gc = twitter_groupchat_init(ic); - for (l = list; l; l = g_slist_next(l)) { - char *msg; - - status = l->data; - if (status->user == NULL || status->text == NULL || last_id == status->id) - continue; - - last_id = status->id; - - strip_html(status->text); - - if (set_getbool(&ic->acc->set, "strip_newlines")) - strip_newlines(status->text); - - msg = twitter_msg_add_id(ic, status, ""); - - // Say it! - if (g_strcasecmp(td->user, status->user->screen_name) == 0) { - imcb_chat_log(gc, "You: %s", msg ? msg : status->text); - } else { - twitter_add_buddy(ic, status->user->screen_name, status->user->name); - - imcb_chat_msg(gc, status->user->screen_name, - msg ? msg : status->text, 0, status->created_at); - } - - g_free(msg); - - // Update the timeline_id to hold the highest id, so that by the next request - // we won't pick up the updates already in the list. - td->timeline_id = MAX(td->timeline_id, status->id); + if (!me) + /* MUST be done before twitter_msg_add_id() to avoid #872. */ + twitter_add_buddy(ic, status->user->screen_name, status->user->name); + msg = twitter_msg_add_id(ic, status, ""); + + // Say it! + if (me) { + imcb_chat_log(gc, "You: %s", msg ? msg : status->text); + } else { + imcb_chat_msg(gc, status->user->screen_name, + msg ? msg : status->text, 0, status->created_at); } + + g_free(msg); } /** * Function that is called to see statuses as private messages. */ -static void twitter_private_message_chat(struct im_connection *ic, GSList * list) +static void twitter_status_show_msg(struct im_connection *ic, struct twitter_xml_status *status) { struct twitter_data *td = ic->proto_data; - GSList *l = NULL; - struct twitter_xml_status *status; char from[MAX_STRING] = ""; - guint64 last_id = 0; + char *prefix = NULL, *text = NULL; + gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0; if (td->flags & TWITTER_MODE_ONE) { g_snprintf(from, sizeof(from) - 1, "%s_%s", td->prefix, ic->acc->user); from[MAX_STRING - 1] = '\0'; } - for (l = list; l; l = g_slist_next(l)) { - char *prefix = NULL, *text = NULL; - - status = l->data; - if (status->user == NULL || status->text == NULL || last_id == status->id) - continue; - - last_id = status->id; + if (td->flags & TWITTER_MODE_ONE) + prefix = g_strdup_printf("\002<\002%s\002>\002 ", + status->user->screen_name); + else if (!me) + twitter_add_buddy(ic, status->user->screen_name, status->user->name); + else + prefix = g_strdup("You: "); - strip_html(status->text); - if (td->flags & TWITTER_MODE_ONE) - prefix = g_strdup_printf("\002<\002%s\002>\002 ", - status->user->screen_name); - else - twitter_add_buddy(ic, status->user->screen_name, status->user->name); + text = twitter_msg_add_id(ic, status, prefix ? prefix : ""); - text = twitter_msg_add_id(ic, status, prefix ? prefix : ""); + imcb_buddy_msg(ic, + *from ? from : status->user->screen_name, + text ? text : status->text, 0, status->created_at); - imcb_buddy_msg(ic, - *from ? from : status->user->screen_name, - text ? text : status->text, 0, status->created_at); + g_free(text); + g_free(prefix); +} - // Update the timeline_id to hold the highest id, so that by the next request - // we won't pick up the updates already in the list. - td->timeline_id = MAX(td->timeline_id, status->id); +static void twitter_status_show(struct im_connection *ic, struct twitter_xml_status *status) +{ + struct twitter_data *td = ic->proto_data; + + if (status->user == NULL || status->text == NULL) + return; + + /* Grrrr. Would like to do this during parsing, but can't access + settings from there. */ + if (set_getbool(&ic->acc->set, "strip_newlines")) + strip_newlines(status->text); + + if (td->flags & TWITTER_MODE_CHAT) + twitter_status_show_chat(ic, status); + else + twitter_status_show_msg(ic, status); - g_free(text); - g_free(prefix); - } + // Update the timeline_id to hold the highest id, so that by the next request + // we won't pick up the updates already in the list. + td->timeline_id = MAX(td->timeline_id, status->id); } static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o); @@ -847,7 +838,7 @@ static gboolean twitter_stream_handle_status(struct im_connection *ic, struct tw for (i = 0; i < TWITTER_LOG_LENGTH; i++) { if (td->log[i].id == txs->id) { - /* Got a duplicate (RT, surely). Drop it. */ + /* Got a duplicate (RT, probably). Drop it. */ txs_free(txs); return TRUE; } @@ -864,10 +855,9 @@ static gboolean twitter_stream_handle_status(struct im_connection *ic, struct tw return TRUE; } - GSList *output = g_slist_append(NULL, txs); - twitter_groupchat(ic, output); + twitter_status_show(ic, txs); txs_free(txs); - g_slist_free(output); + return TRUE; } @@ -950,9 +940,12 @@ void twitter_flush_timeline(struct im_connection *ic) int show_old_mentions = set_getint(&ic->acc->set, "show_old_mentions"); struct twitter_xml_list *home_timeline = td->home_timeline_obj; struct twitter_xml_list *mentions = td->mentions_obj; + guint64 last_id = 0; GSList *output = NULL; GSList *l; + imcb_connected(ic); + if (!(td->flags & TWITTER_GOT_TIMELINE)) { return; } @@ -976,17 +969,15 @@ void twitter_flush_timeline(struct im_connection *ic) output = g_slist_insert_sorted(output, l->data, twitter_compare_elements); } } - - if (!(ic->flags & OPT_LOGGED_IN)) - imcb_connected(ic); // See if the user wants to see the messages in a groupchat window or as private messages. - if (td->flags & TWITTER_MODE_CHAT) - twitter_groupchat(ic, output); - else - twitter_private_message_chat(ic, output); - - g_slist_free(output); + while (output) { + struct twitter_xml_status *txs = output->data; + if (txs->id != last_id) + twitter_status_show(ic, txs); + last_id = txs->id; + output = g_slist_remove(output, txs); + } txl_free(home_timeline); txl_free(mentions); -- cgit v1.2.3 From 9ed00818d7351ca5bcb4ba16b5dfe900d45831d5 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 25 Nov 2012 18:39:11 +0000 Subject: Fixed one potential memory leak, and re-remembered that for GLib + valgrind you REALLY need to set G_SLICE=always-malloc. Argh! --- protocols/twitter/twitter_lib.c | 1 - 1 file changed, 1 deletion(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index b888a730..fe276133 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -270,7 +270,6 @@ static gboolean twitter_xt_get_friends_id_list(json_value *node, struct twitter_ txl->list = g_slist_prepend(txl->list, g_strdup_printf("%lld", c->u.array.values[i]->u.integer)); - } c = json_o_get(node, "next_cursor"); -- cgit v1.2.3 From 3592b95ac23e243425e20ff9f69f407cb5ec4a74 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 25 Nov 2012 20:21:24 +0000 Subject: To address #991, adding a strict commands mode, where one has to use "post" to post tweets. --- protocols/twitter/twitter.c | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c index 2dfb4a26..ad75c886 100644 --- a/protocols/twitter/twitter.c +++ b/protocols/twitter/twitter.c @@ -218,16 +218,6 @@ static gboolean twitter_oauth_callback(struct oauth_info *info) return TRUE; } - -static char *set_eval_mode(set_t * set, char *value) -{ - if (g_strcasecmp(value, "one") == 0 || - g_strcasecmp(value, "many") == 0 || g_strcasecmp(value, "chat") == 0) - return value; - else - return NULL; -} - int twitter_url_len_diff(gchar *msg, unsigned int target_len) { int url_len_diff = 0; @@ -269,6 +259,23 @@ static gboolean twitter_length_check(struct im_connection *ic, gchar * msg) return FALSE; } +static char *set_eval_commands(set_t * set, char *value) +{ + if (g_strcasecmp(value, "strict") == 0 ) + return value; + else + return set_eval_bool(set, value); +} + +static char *set_eval_mode(set_t * set, char *value) +{ + if (g_strcasecmp(value, "one") == 0 || + g_strcasecmp(value, "many") == 0 || g_strcasecmp(value, "chat") == 0) + return value; + else + return NULL; +} + static void twitter_init(account_t * acc) { set_t *s; @@ -288,7 +295,7 @@ static void twitter_init(account_t * acc) s = set_add(&acc->set, "base_url", def_url, NULL, acc); s->flags |= ACC_SET_OFFLINE_ONLY; - s = set_add(&acc->set, "commands", "true", set_eval_bool, acc); + s = set_add(&acc->set, "commands", "true", set_eval_commands, acc); s = set_add(&acc->set, "fetch_interval", "60", set_eval_int, acc); s->flags |= ACC_SET_OFFLINE_ONLY; @@ -568,6 +575,8 @@ static void twitter_handle_command(struct im_connection *ic, char *message) struct twitter_data *td = ic->proto_data; char *cmds, **cmd, *new = NULL; guint64 in_reply_to = 0; + gboolean strict_commands = + g_strcasecmp(set_getstr(&ic->acc->set, "commands"), "strict") == 0; cmds = g_strdup(message); cmd = split_command_parts(cmds); @@ -575,7 +584,7 @@ static void twitter_handle_command(struct im_connection *ic, char *message) if (cmd[0] == NULL) { g_free(cmds); return; - } else if (!set_getbool(&ic->acc->set, "commands")) { + } else if (!(strict_commands || set_getbool(&ic->acc->set, "commands"))) { /* Not supporting commands. */ } else if (g_strcasecmp(cmd[0], "undo") == 0) { guint64 id; @@ -674,9 +683,12 @@ static void twitter_handle_command(struct im_connection *ic, char *message) in_reply_to = id; } else if (g_strcasecmp(cmd[0], "post") == 0) { message += 5; + strict_commands = FALSE; } - { + if (strict_commands) { + twitter_log(ic, "Unknown command: %s", cmd[0]); + } else { char *s; bee_user_t *bu; -- cgit v1.2.3 From f97b8e9637acba704e976dff79436a83c0f9c63a Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 25 Nov 2012 20:43:52 +0000 Subject: Use hex for show_ids, but stick to the 2-char maximum. a 256-message backlog really should be enough. --- protocols/twitter/twitter.c | 4 ++-- protocols/twitter/twitter.h | 2 +- protocols/twitter/twitter_lib.c | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c index ad75c886..fdfc83ec 100644 --- a/protocols/twitter/twitter.c +++ b/protocols/twitter/twitter.c @@ -557,13 +557,13 @@ static guint64 twitter_message_id_from_command_arg(struct im_connection *ic, str bee_user_t *bu; guint64 id = 0; if (g_str_has_prefix(arg, "#") && - sscanf(arg + 1, "%" G_GUINT64_FORMAT, &id) == 1) { + sscanf(arg + 1, "%" G_GINT64_MODIFIER "x", &id) == 1) { if (id < TWITTER_LOG_LENGTH && td->log) id = td->log[id].id; } else if ((bu = bee_user_by_handle(ic->bee, ic, arg)) && (tud = bu->data) && tud->last_id) id = tud->last_id; - else if (sscanf(arg, "%" G_GUINT64_FORMAT, &id) == 1){ + else if (sscanf(arg, "%" G_GINT64_MODIFIER "x", &id) == 1){ if (id < TWITTER_LOG_LENGTH && td->log) id = td->log[id].id; } diff --git a/protocols/twitter/twitter.h b/protocols/twitter/twitter.h index ceac4371..8792b7c9 100644 --- a/protocols/twitter/twitter.h +++ b/protocols/twitter/twitter.h @@ -84,7 +84,7 @@ struct twitter_user_data time_t last_time; }; -#define TWITTER_LOG_LENGTH 100 +#define TWITTER_LOG_LENGTH 256 struct twitter_log_data { guint64 id; diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index fe276133..862104f5 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -643,10 +643,10 @@ static char *twitter_msg_add_id(struct im_connection *ic, if (set_getbool(&ic->acc->set, "show_ids")) { if (reply_to != -1) - return g_strdup_printf("\002[\002%02d->%02d\002]\002 %s%s", + 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%02d\002]\002 %s%s", + return g_strdup_printf("\002[\002%02x\002]\002 %s%s", td->log_id, prefix, txs->text); } else { if (*prefix) -- cgit v1.2.3 From 67f68282bb20ad3af6dfa6017b89b89ab0a1767f Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 25 Nov 2012 22:09:41 +0000 Subject: Only a few commands use twitter_message_id_from_command_arg(), others were still using a decimal scanf format string. Messy code duplication. :-( --- protocols/twitter/twitter.c | 8 ++++---- protocols/twitter/twitter_lib.c | 13 +++++++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c index fdfc83ec..91050578 100644 --- a/protocols/twitter/twitter.c +++ b/protocols/twitter/twitter.c @@ -591,7 +591,7 @@ static void twitter_handle_command(struct im_connection *ic, char *message) if (cmd[1] == NULL) twitter_status_destroy(ic, td->last_status_id); - else if (sscanf(cmd[1], "%" G_GUINT64_FORMAT, &id) == 1) { + else if (sscanf(cmd[1], "%" G_GINT64_MODIFIER "x", &id) == 1) { if (id < TWITTER_LOG_LENGTH && td->log) id = td->log[id].id; @@ -626,7 +626,7 @@ static void twitter_handle_command(struct im_connection *ic, char *message) /* Report nominally works on users but look up the user who posted the given ID if the user wants to do it that way */ if (g_str_has_prefix(cmd[1], "#") && - sscanf(cmd[1] + 1, "%" G_GUINT64_FORMAT, &id) == 1) { + sscanf(cmd[1] + 1, "%" G_GINT64_MODIFIER "x", &id) == 1) { if (id < TWITTER_LOG_LENGTH && td->log) { if (g_slist_find(ic->bee->users, td->log[id].bu)) { screen_name = td->log[id].bu->handle; @@ -654,7 +654,7 @@ static void twitter_handle_command(struct im_connection *ic, char *message) guint64 id = 0; if (g_str_has_prefix(cmd[1], "#") && - sscanf(cmd[1] + 1, "%" G_GUINT64_FORMAT, &id) == 1 && + sscanf(cmd[1] + 1, "%" G_GINT64_MODIFIER "x", &id) == 1 && (id < TWITTER_LOG_LENGTH) && td->log) { bu = td->log[id].bu; if (g_slist_find(ic->bee->users, bu)) @@ -664,7 +664,7 @@ static void twitter_handle_command(struct im_connection *ic, char *message) } else if ((bu = bee_user_by_handle(ic->bee, ic, cmd[1])) && (tud = bu->data) && tud->last_id) { id = tud->last_id; - } else if (sscanf(cmd[1], "%" G_GUINT64_FORMAT, &id) == 1 && + } else if (sscanf(cmd[1], "%" G_GINT64_MODIFIER "x", &id) == 1 && (id < TWITTER_LOG_LENGTH) && td->log) { bu = td->log[id].bu; if (g_slist_find(ic->bee->users, bu)) diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index 862104f5..f4c81e0c 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -66,7 +66,8 @@ struct twitter_xml_status { time_t created_at; char *text; struct twitter_xml_user *user; - guint64 id, reply_to; + guint64 id, rt_id; /* Usually equal, with RTs id == *original* id */ + guint64 reply_to; }; /** @@ -490,6 +491,7 @@ static struct twitter_xml_status *twitter_xt_get_status(const json_value *node) if (rtxs) { g_free(txs->text); txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text); + txs->rt_id = txs->id; txs->id = rtxs->id; txs_free(rtxs); } @@ -641,6 +643,12 @@ static char *twitter_msg_add_id(struct im_connection *ic, td->log[td->log_id].id = txs->id; td->log[td->log_id].bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name); + /* This is all getting hairy. :-( If we RT'ed something ourselves, + remember OUR id instead so undo will work. In other cases, the + original tweet's id should be remembered for deduplicating. */ + if (txs->rt_id && strcmp(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", @@ -1153,8 +1161,9 @@ static void twitter_http_post(struct http_request *req) if (!(parsed = twitter_parse_response(ic, req))) return; - if ((id = json_o_get(parsed, "id")) && id->type == json_integer) + if ((id = json_o_get(parsed, "id")) && id->type == json_integer) { td->last_status_id = id->u.integer; + } json_value_free(parsed); } -- cgit v1.2.3 From b235228768645c11d3ef138ddba839c7dd529567 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 25 Nov 2012 22:28:38 +0000 Subject: Have root confirm some commands that so far gave no feedback at all, since "no news is good news" can be a little confusing. --- protocols/twitter/twitter_http.c | 9 +++++++++ protocols/twitter/twitter_http.h | 8 ++++++++ protocols/twitter/twitter_lib.c | 16 +++++++++++----- 3 files changed, 28 insertions(+), 5 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter_http.c b/protocols/twitter/twitter_http.c index 216dad9e..0f1ab518 100644 --- a/protocols/twitter/twitter_http.c +++ b/protocols/twitter/twitter_http.c @@ -134,6 +134,15 @@ struct http_request *twitter_http(struct im_connection *ic, char *url_string, ht return ret; } +struct http_request *twitter_http_f(struct im_connection *ic, char *url_string, http_input_function func, + gpointer data, int is_post, char **arguments, int arguments_len, twitter_http_flags_t flags) +{ + struct http_request *ret = twitter_http(ic, url_string, func, data, is_post, arguments, arguments_len); + if (ret) + ret->flags |= flags; + return ret; +} + static char *twitter_url_append(char *url, char *key, char *value) { char *key_encoded = g_strndup(key, 3 * strlen(key)); diff --git a/protocols/twitter/twitter_http.h b/protocols/twitter/twitter_http.h index 02a7ea43..09ef350c 100644 --- a/protocols/twitter/twitter_http.h +++ b/protocols/twitter/twitter_http.h @@ -27,10 +27,18 @@ #include "nogaim.h" #include "http_client.h" +typedef enum { + /* With this set, twitter_http_post() will post a generic confirmation + message to the user. */ + TWITTER_HTTP_USER_ACK = 0x1000000, +} twitter_http_flags_t; + struct oauth_info; struct http_request *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, gpointer data, int is_post, char** arguments, int arguments_len); +struct http_request *twitter_http_f(struct im_connection *ic, char *url_string, http_input_function func, + gpointer data, int is_post, char** arguments, int arguments_len, twitter_http_flags_t flags); #endif //_TWITTER_HTTP_H diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index f4c81e0c..c7041a68 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -1166,6 +1166,9 @@ static void twitter_http_post(struct http_request *req) } json_value_free(parsed); + + if (req->flags & TWITTER_HTTP_USER_ACK) + twitter_log(ic, "Command processed successfully"); } /** @@ -1212,7 +1215,8 @@ void twitter_status_destroy(struct im_connection *ic, guint64 id) char *url; url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_DESTROY_URL, (unsigned long long) id, ".json"); - twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0); + twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0, + TWITTER_HTTP_USER_ACK); g_free(url); } @@ -1221,7 +1225,8 @@ void twitter_status_retweet(struct im_connection *ic, guint64 id) char *url; url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_RETWEET_URL, (unsigned long long) id, ".json"); - twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0); + twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0, + TWITTER_HTTP_USER_ACK); g_free(url); } @@ -1235,8 +1240,8 @@ void twitter_report_spam(struct im_connection *ic, char *screen_name) NULL, }; args[1] = screen_name; - twitter_http(ic, TWITTER_REPORT_SPAM_URL, twitter_http_post, - ic, 1, args, 2); + twitter_http_f(ic, TWITTER_REPORT_SPAM_URL, twitter_http_post, + ic, 1, args, 2, TWITTER_HTTP_USER_ACK); } /** @@ -1247,6 +1252,7 @@ void twitter_favourite_tweet(struct im_connection *ic, guint64 id) char *url; url = g_strdup_printf("%s%llu%s", TWITTER_FAVORITE_CREATE_URL, (unsigned long long) id, ".json"); - twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0); + twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0, + TWITTER_HTTP_USER_ACK); g_free(url); } -- cgit v1.2.3 From 3ca001bcb054e861ef9ad6e818fbb6615a519804 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Sun, 2 Dec 2012 12:53:12 +0000 Subject: Extend twitter_message_id_from_command_arg() a little bit and use it for all commands. Also fixed reply command in strict commands mode. --- protocols/twitter/twitter.c | 154 +++++++++++++++++++------------------------- 1 file changed, 66 insertions(+), 88 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c index 91050578..651bf345 100644 --- a/protocols/twitter/twitter.c +++ b/protocols/twitter/twitter.c @@ -552,21 +552,40 @@ static void twitter_buddy_data_free(struct bee_user *bu) * * Returns 0 if the user provides garbage. */ -static guint64 twitter_message_id_from_command_arg(struct im_connection *ic, struct twitter_data *td, char *arg) { +static guint64 twitter_message_id_from_command_arg(struct im_connection *ic, char *arg, bee_user_t **bu_) { + struct twitter_data *td = ic->proto_data; struct twitter_user_data *tud; - bee_user_t *bu; + bee_user_t *bu = NULL; guint64 id = 0; - if (g_str_has_prefix(arg, "#") && - sscanf(arg + 1, "%" G_GINT64_MODIFIER "x", &id) == 1) { - if (id < TWITTER_LOG_LENGTH && td->log) - id = td->log[id].id; - } else if ((bu = bee_user_by_handle(ic->bee, ic, arg)) && - (tud = bu->data) && tud->last_id) - id = tud->last_id; - else if (sscanf(arg, "%" G_GINT64_MODIFIER "x", &id) == 1){ - if (id < TWITTER_LOG_LENGTH && td->log) + + if (bu_) + *bu_ = NULL; + if (!arg || !arg[0]) + return 0; + + if (arg[0] != '#' && (bu = bee_user_by_handle(ic->bee, ic, arg))) { + if ((tud = bu->data)) + id = tud->last_id; + } else { + if (arg[0] == '#') + arg++; + if (sscanf(arg, "%" G_GINT64_MODIFIER "x", &id) == 1 && + id < TWITTER_LOG_LENGTH) { + bu = td->log[id].bu; id = td->log[id].id; + /* Beware of dangling pointers! */ + if (!g_slist_find(ic->bee->users, bu)) + bu = NULL; + } else if (sscanf(arg, "%" G_GINT64_MODIFIER "d", &id) == 1) { + /* Allow normal tweet IDs as well; not a very useful + feature but it's always been there. Just ignore + very low IDs to avoid accidents. */ + if (id < 1000000) + id = 0; + } } + if (bu_) + *bu_ = bu; return id; } @@ -574,70 +593,56 @@ static void twitter_handle_command(struct im_connection *ic, char *message) { struct twitter_data *td = ic->proto_data; char *cmds, **cmd, *new = NULL; - guint64 in_reply_to = 0; - gboolean strict_commands = - g_strcasecmp(set_getstr(&ic->acc->set, "commands"), "strict") == 0; + guint64 in_reply_to = 0, id; + gboolean allow_post = + g_strcasecmp(set_getstr(&ic->acc->set, "commands"), "strict") != 0; + bee_user_t *bu = NULL; cmds = g_strdup(message); cmd = split_command_parts(cmds); if (cmd[0] == NULL) { - g_free(cmds); - return; - } else if (!(strict_commands || set_getbool(&ic->acc->set, "commands"))) { - /* Not supporting commands. */ + goto eof; + } else if (!set_getbool(&ic->acc->set, "commands") && allow_post) { + /* Not supporting commands if "commands" is set to true/strict. */ } else if (g_strcasecmp(cmd[0], "undo") == 0) { - guint64 id; - if (cmd[1] == NULL) twitter_status_destroy(ic, td->last_status_id); - else if (sscanf(cmd[1], "%" G_GINT64_MODIFIER "x", &id) == 1) { - if (id < TWITTER_LOG_LENGTH && td->log) - id = td->log[id].id; - + else if ((id = twitter_message_id_from_command_arg(ic, cmd[1], NULL))) twitter_status_destroy(ic, id); - } else + else twitter_log(ic, "Could not undo last action"); - g_free(cmds); - return; + goto eof; } else if (g_strcasecmp(cmd[0], "favourite") == 0 && cmd[1]) { - guint64 id; - if ((id = twitter_message_id_from_command_arg(ic, td, cmd[1]))) { + if ((id = twitter_message_id_from_command_arg(ic, cmd[1], NULL))) { twitter_favourite_tweet(ic, id); } else { twitter_log(ic, "Please provide a message ID or username."); } - g_free(cmds); - return; + goto eof; } else if (g_strcasecmp(cmd[0], "follow") == 0 && cmd[1]) { twitter_add_buddy(ic, cmd[1], NULL); - g_free(cmds); - return; + goto eof; } else if (g_strcasecmp(cmd[0], "unfollow") == 0 && cmd[1]) { twitter_remove_buddy(ic, cmd[1], NULL); - g_free(cmds); - return; + goto eof; } else if ((g_strcasecmp(cmd[0], "report") == 0 || g_strcasecmp(cmd[0], "spam") == 0) && cmd[1]) { - char * screen_name; - guint64 id; - screen_name = cmd[1]; + char *screen_name; + /* Report nominally works on users but look up the user who posted the given ID if the user wants to do it that way */ - if (g_str_has_prefix(cmd[1], "#") && - sscanf(cmd[1] + 1, "%" G_GINT64_MODIFIER "x", &id) == 1) { - if (id < TWITTER_LOG_LENGTH && td->log) { - if (g_slist_find(ic->bee->users, td->log[id].bu)) { - screen_name = td->log[id].bu->handle; - } - } - } + twitter_message_id_from_command_arg(ic, cmd[1], &bu); + if (bu) + screen_name = bu->handle; + else + screen_name = cmd[1]; + twitter_report_spam(ic, screen_name); - g_free(cmds); - return; + goto eof; } else if (g_strcasecmp(cmd[0], "rt") == 0 && cmd[1]) { - guint64 id = twitter_message_id_from_command_arg(ic, td, cmd[1]); + id = twitter_message_id_from_command_arg(ic, cmd[1], NULL); td->last_status_id = 0; if (id) @@ -646,57 +651,27 @@ static void twitter_handle_command(struct im_connection *ic, char *message) twitter_log(ic, "User `%s' does not exist or didn't " "post any statuses recently", cmd[1]); - g_free(cmds); - return; + goto eof; } else if (g_strcasecmp(cmd[0], "reply") == 0 && cmd[1] && cmd[2]) { - struct twitter_user_data *tud; - bee_user_t *bu = NULL; - guint64 id = 0; - - if (g_str_has_prefix(cmd[1], "#") && - sscanf(cmd[1] + 1, "%" G_GINT64_MODIFIER "x", &id) == 1 && - (id < TWITTER_LOG_LENGTH) && td->log) { - bu = td->log[id].bu; - if (g_slist_find(ic->bee->users, bu)) - id = td->log[id].id; - else - bu = NULL; - } else if ((bu = bee_user_by_handle(ic->bee, ic, cmd[1])) && - (tud = bu->data) && tud->last_id) { - id = tud->last_id; - } else if (sscanf(cmd[1], "%" G_GINT64_MODIFIER "x", &id) == 1 && - (id < TWITTER_LOG_LENGTH) && td->log) { - bu = td->log[id].bu; - if (g_slist_find(ic->bee->users, bu)) - id = td->log[id].id; - else - bu = NULL; - } - + id = twitter_message_id_from_command_arg(ic, cmd[1], &bu); if (!id || !bu) { twitter_log(ic, "User `%s' does not exist or didn't " "post any statuses recently", cmd[1]); - g_free(cmds); - return; + goto eof; } message = new = g_strdup_printf("@%s %s", bu->handle, message + (cmd[2] - cmd[0])); in_reply_to = id; + allow_post = TRUE; } else if (g_strcasecmp(cmd[0], "post") == 0) { message += 5; - strict_commands = FALSE; + allow_post = TRUE; } - if (strict_commands) { - twitter_log(ic, "Unknown command: %s", cmd[0]); - } else { + if (allow_post) { char *s; - bee_user_t *bu; - if (!twitter_length_check(ic, message)) { - g_free(new); - g_free(cmds); - return; - } + if (!twitter_length_check(ic, message)) + goto eof; s = cmd[0] + strlen(cmd[0]) - 1; if (!new && s > cmd[0] && (*s == ':' || *s == ',')) { @@ -719,8 +694,11 @@ static void twitter_handle_command(struct im_connection *ic, char *message) this would delete the second-last Tweet. Prevent that. */ td->last_status_id = 0; twitter_post_status(ic, message, in_reply_to); - g_free(new); + } else { + twitter_log(ic, "Unknown command: %s", cmd[0]); } +eof: + g_free(new); g_free(cmds); } -- cgit v1.2.3 From 2dcde94625f2ae1af1aef674c2aa1e2bc714d144 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Mon, 3 Dec 2012 23:30:33 +0000 Subject: Don't hide own tweets in streaming mode with fetch_mentions off, and move txs_free() to the right place - fixes a memory leak. --- protocols/twitter/twitter_lib.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index c7041a68..a8b203bb 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -813,7 +813,9 @@ static gboolean twitter_stream_handle_object(struct im_connection *ic, json_valu json_value *c; if ((txs = twitter_xt_get_status(o))) { - return twitter_stream_handle_status(ic, txs); + gboolean ret = twitter_stream_handle_status(ic, txs); + txs_free(txs); + return ret; } else if ((c = json_o_get(o, "direct_message")) && (txs = twitter_xt_get_dm(c))) { if (strcmp(txs->user->screen_name, td->user) != 0) @@ -846,12 +848,12 @@ static gboolean twitter_stream_handle_status(struct im_connection *ic, struct tw for (i = 0; i < TWITTER_LOG_LENGTH; i++) { if (td->log[i].id == txs->id) { /* Got a duplicate (RT, probably). Drop it. */ - txs_free(txs); return TRUE; } } - if (!(set_getbool(&ic->acc->set, "fetch_mentions") || + if (!(strcmp(txs->user->screen_name, td->user) == 0 || + set_getbool(&ic->acc->set, "fetch_mentions") || bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) { /* Tweet is from an unknown person and the user does not want to see @mentions, so drop it. twitter_stream_handle_event() @@ -863,7 +865,6 @@ static gboolean twitter_stream_handle_status(struct im_connection *ic, struct tw } twitter_status_show(ic, txs); - txs_free(txs); return TRUE; } -- cgit v1.2.3 From 573e274c58bf7d154b35ab5cd9d0b711f7ede715 Mon Sep 17 00:00:00 2001 From: Wilmer van der Gaast Date: Fri, 21 Dec 2012 22:50:05 +0100 Subject: For finding since_id, don't use RT original IDs of course. :-/ This fixes duplicate-tweet issues in non-streaming mode. --- protocols/twitter/twitter_lib.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'protocols') diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index a8b203bb..3d368d2e 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -476,7 +476,7 @@ static struct twitter_xml_status *twitter_xt_get_status(const json_value *node) } else if (strcmp("user", k) == 0 && v->type == json_object) { txs->user = twitter_xt_get_user(v); } else if (strcmp("id", k) == 0 && v->type == json_integer) { - txs->id = v->u.integer; + txs->rt_id = txs->id = v->u.integer; } else if (strcmp("in_reply_to_status_id", k) == 0 && v->type == json_integer) { txs->reply_to = v->u.integer; } else if (strcmp("entities", k) == 0 && v->type == json_object) { @@ -491,7 +491,6 @@ static struct twitter_xml_status *twitter_xt_get_status(const json_value *node) if (rtxs) { g_free(txs->text); txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text); - txs->rt_id = txs->id; txs->id = rtxs->id; txs_free(rtxs); } @@ -646,7 +645,7 @@ static char *twitter_msg_add_id(struct im_connection *ic, /* This is all getting hairy. :-( If we RT'ed something ourselves, remember OUR id instead so undo will work. In other cases, the original tweet's id should be remembered for deduplicating. */ - if (txs->rt_id && strcmp(txs->user->screen_name, td->user) == 0) + if (strcmp(txs->user->screen_name, td->user) == 0) td->log[td->log_id].id = txs->rt_id; if (set_getbool(&ic->acc->set, "show_ids")) { @@ -745,7 +744,7 @@ static void twitter_status_show(struct im_connection *ic, struct twitter_xml_sta // Update the timeline_id to hold the highest id, so that by the next request // we won't pick up the updates already in the list. - td->timeline_id = MAX(td->timeline_id, status->id); + td->timeline_id = MAX(td->timeline_id, status->rt_id); } static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o); -- cgit v1.2.3