Merging strip_newlines Twitter setting.
[bitlbee.git] / protocols / twitter / twitter_lib.c
blobbd9573330add7a7e540335b6d9d30c1281770cd7
1 /***************************************************************************\
2 * *
3 * BitlBee - An IRC to IM gateway *
4 * Simple module to facilitate twitter functionality. *
5 * *
6 * Copyright 2009-2010 Geert Mulders <g.c.w.m.mulders@gmail.com> *
7 * Copyright 2010-2011 Wilmer van der Gaast <wilmer@gaast.net> *
8 * *
9 * This library is free software; you can redistribute it and/or *
10 * modify it under the terms of the GNU Lesser General Public *
11 * License as published by the Free Software Foundation, version *
12 * 2.1. *
13 * *
14 * This library is distributed in the hope that it will be useful, *
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
17 * Lesser General Public License for more details. *
18 * *
19 * You should have received a copy of the GNU Lesser General Public License *
20 * along with this library; if not, write to the Free Software Foundation, *
21 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *
22 * *
23 ****************************************************************************/
25 /* For strptime(): */
26 #if(__sun)
27 #else
28 #define _XOPEN_SOURCE
29 #endif
31 #include "twitter_http.h"
32 #include "twitter.h"
33 #include "bitlbee.h"
34 #include "url.h"
35 #include "misc.h"
36 #include "base64.h"
37 #include "xmltree.h"
38 #include "twitter_lib.h"
39 #include <ctype.h>
40 #include <errno.h>
42 /* GLib < 2.12.0 doesn't have g_ascii_strtoll(), work around using system strtoll(). */
43 /* GLib < 2.12.4 can be buggy: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=488013 */
44 #if !GLIB_CHECK_VERSION(2,12,5)
45 #include <stdlib.h>
46 #include <limits.h>
47 #define g_ascii_strtoll strtoll
48 #endif
50 #define TXL_STATUS 1
51 #define TXL_USER 2
52 #define TXL_ID 3
54 struct twitter_xml_list {
55 int type;
56 gint64 next_cursor;
57 GSList *list;
60 struct twitter_xml_user {
61 char *name;
62 char *screen_name;
65 struct twitter_xml_status {
66 time_t created_at;
67 char *text;
68 struct twitter_xml_user *user;
69 guint64 id, reply_to;
72 static void twitter_groupchat_init(struct im_connection *ic);
74 /**
75 * Frees a twitter_xml_user struct.
77 static void txu_free(struct twitter_xml_user *txu)
79 if (txu == NULL)
80 return;
82 g_free(txu->name);
83 g_free(txu->screen_name);
84 g_free(txu);
87 /**
88 * Frees a twitter_xml_status struct.
90 static void txs_free(struct twitter_xml_status *txs)
92 if (txs == NULL)
93 return;
95 g_free(txs->text);
96 txu_free(txs->user);
97 g_free(txs);
101 * Free a twitter_xml_list struct.
102 * type is the type of list the struct holds.
104 static void txl_free(struct twitter_xml_list *txl)
106 GSList *l;
107 if (txl == NULL)
108 return;
110 for (l = txl->list; l; l = g_slist_next(l)) {
111 if (txl->type == TXL_STATUS) {
112 txs_free((struct twitter_xml_status *) l->data);
113 } else if (txl->type == TXL_ID) {
114 g_free(l->data);
115 } else if (txl->type == TXL_USER) {
116 txu_free(l->data);
120 g_slist_free(txl->list);
121 g_free(txl);
125 * Compare status elements
127 static gint twitter_compare_elements(gconstpointer a, gconstpointer b)
129 struct twitter_xml_status *a_status = (struct twitter_xml_status *) a;
130 struct twitter_xml_status *b_status = (struct twitter_xml_status *) b;
132 if (a_status->created_at < b_status->created_at) {
133 return -1;
134 } else if (a_status->created_at > b_status->created_at) {
135 return 1;
136 } else {
137 return 0;
142 * Add a buddy if it is not already added, set the status to logged in.
144 static void twitter_add_buddy(struct im_connection *ic, char *name, const char *fullname)
146 struct twitter_data *td = ic->proto_data;
148 // Check if the buddy is already in the buddy list.
149 if (!bee_user_by_handle(ic->bee, ic, name)) {
150 char *mode = set_getstr(&ic->acc->set, "mode");
152 // The buddy is not in the list, add the buddy and set the status to logged in.
153 imcb_add_buddy(ic, name, NULL);
154 imcb_rename_buddy(ic, name, fullname);
155 if (g_strcasecmp(mode, "chat") == 0) {
156 /* Necessary so that nicks always get translated to the
157 exact Twitter username. */
158 imcb_buddy_nick_hint(ic, name, name);
159 imcb_chat_add_buddy(td->timeline_gc, name);
160 } else if (g_strcasecmp(mode, "many") == 0)
161 imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL);
165 /* Warning: May return a malloc()ed value, which will be free()d on the next
166 call. Only for short-term use. NOT THREADSAFE! */
167 char *twitter_parse_error(struct http_request *req)
169 static char *ret = NULL;
170 struct xt_parser *xp = NULL;
171 struct xt_node *node, *err;
173 g_free(ret);
174 ret = NULL;
176 if (req->body_size > 0) {
177 xp = xt_new(NULL, NULL);
178 xt_feed(xp, req->reply_body, req->body_size);
180 for (node = xp->root; node; node = node->next)
181 if ((err = xt_find_node(node->children, "error")) && err->text_len > 0) {
182 ret = g_strdup_printf("%s (%s)", req->status_string, err->text);
183 break;
186 xt_free(xp);
189 return ret ? ret : req->status_string;
192 static void twitter_http_get_friends_ids(struct http_request *req);
195 * Get the friends ids.
197 void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor)
199 // Primitive, but hey! It works...
200 char *args[2];
201 args[0] = "cursor";
202 args[1] = g_strdup_printf("%lld", (long long) next_cursor);
203 twitter_http(ic, TWITTER_FRIENDS_IDS_URL, twitter_http_get_friends_ids, ic, 0, args, 2);
205 g_free(args[1]);
209 * Function to help fill a list.
211 static xt_status twitter_xt_next_cursor(struct xt_node *node, struct twitter_xml_list *txl)
213 char *end = NULL;
215 if (node->text)
216 txl->next_cursor = g_ascii_strtoll(node->text, &end, 10);
217 if (end == NULL)
218 txl->next_cursor = -1;
220 return XT_HANDLED;
224 * Fill a list of ids.
226 static xt_status twitter_xt_get_friends_id_list(struct xt_node *node, struct twitter_xml_list *txl)
228 struct xt_node *child;
230 // Set the list type.
231 txl->type = TXL_ID;
233 // The root <statuses> node should hold the list of statuses <status>
234 // Walk over the nodes children.
235 for (child = node->children; child; child = child->next) {
236 if (g_strcasecmp("ids", child->name) == 0) {
237 struct xt_node *idc;
238 for (idc = child->children; idc; idc = idc->next)
239 if (g_strcasecmp(idc->name, "id") == 0)
240 txl->list = g_slist_prepend(txl->list,
241 g_memdup(idc->text, idc->text_len + 1));
242 } else if (g_strcasecmp("next_cursor", child->name) == 0) {
243 twitter_xt_next_cursor(child, txl);
247 return XT_HANDLED;
250 static void twitter_get_users_lookup(struct im_connection *ic);
253 * Callback for getting the friends ids.
255 static void twitter_http_get_friends_ids(struct http_request *req)
257 struct im_connection *ic;
258 struct xt_parser *parser;
259 struct twitter_xml_list *txl;
260 struct twitter_data *td;
262 ic = req->data;
264 // Check if the connection is still active.
265 if (!g_slist_find(twitter_connections, ic))
266 return;
268 td = ic->proto_data;
270 // Check if the HTTP request went well. More strict checks as this is
271 // the first request we do in a session.
272 if (req->status_code == 401) {
273 imcb_error(ic, "Authentication failure");
274 imc_logout(ic, FALSE);
275 return;
276 } else if (req->status_code != 200) {
277 // It didn't go well, output the error and return.
278 imcb_error(ic, "Could not retrieve %s: %s",
279 TWITTER_FRIENDS_IDS_URL, twitter_parse_error(req));
280 imc_logout(ic, TRUE);
281 return;
282 } else {
283 td->http_fails = 0;
286 /* Create the room now that we "logged in". */
287 if (!td->timeline_gc && g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0)
288 twitter_groupchat_init(ic);
290 txl = g_new0(struct twitter_xml_list, 1);
291 txl->list = td->follow_ids;
293 // Parse the data.
294 parser = xt_new(NULL, txl);
295 xt_feed(parser, req->reply_body, req->body_size);
296 twitter_xt_get_friends_id_list(parser->root, txl);
297 xt_free(parser);
299 td->follow_ids = txl->list;
300 if (txl->next_cursor)
301 /* These were just numbers. Up to 4000 in a response AFAIK so if we get here
302 we may be using a spammer account. \o/ */
303 twitter_get_friends_ids(ic, txl->next_cursor);
304 else
305 /* Now to convert all those numbers into names.. */
306 twitter_get_users_lookup(ic);
308 txl->list = NULL;
309 txl_free(txl);
312 static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl);
313 static void twitter_http_get_users_lookup(struct http_request *req);
315 static void twitter_get_users_lookup(struct im_connection *ic)
317 struct twitter_data *td = ic->proto_data;
318 char *args[2] = {
319 "user_id",
320 NULL,
322 GString *ids = g_string_new("");
323 int i;
325 /* We can request up to 100 users at a time. */
326 for (i = 0; i < 100 && td->follow_ids; i ++) {
327 g_string_append_printf(ids, ",%s", (char*) td->follow_ids->data);
328 g_free(td->follow_ids->data);
329 td->follow_ids = g_slist_remove(td->follow_ids, td->follow_ids->data);
331 if (ids->len > 0) {
332 args[1] = ids->str + 1;
333 /* POST, because I think ids can be up to 1KB long. */
334 twitter_http(ic, TWITTER_USERS_LOOKUP_URL, twitter_http_get_users_lookup, ic, 1, args, 2);
335 } else {
336 /* We have all users. Continue with login. (Get statuses.) */
337 td->flags |= TWITTER_HAVE_FRIENDS;
338 twitter_login_finish(ic);
340 g_string_free(ids, TRUE);
344 * Callback for getting (twitter)friends...
346 * Be afraid, be very afraid! This function will potentially add hundreds of "friends". "Who has
347 * hundreds of friends?" you wonder? You probably not, since you are reading the source of
348 * BitlBee... Get a life and meet new people!
350 static void twitter_http_get_users_lookup(struct http_request *req)
352 struct im_connection *ic = req->data;
353 struct twitter_data *td;
354 struct xt_parser *parser;
355 struct twitter_xml_list *txl;
356 GSList *l = NULL;
357 struct twitter_xml_user *user;
359 // Check if the connection is still active.
360 if (!g_slist_find(twitter_connections, ic))
361 return;
363 td = ic->proto_data;
365 if (req->status_code != 200) {
366 // It didn't go well, output the error and return.
367 imcb_error(ic, "Could not retrieve %s: %s",
368 TWITTER_USERS_LOOKUP_URL, twitter_parse_error(req));
369 imc_logout(ic, TRUE);
370 return;
371 } else {
372 td->http_fails = 0;
375 txl = g_new0(struct twitter_xml_list, 1);
376 txl->list = NULL;
378 // Parse the data.
379 parser = xt_new(NULL, txl);
380 xt_feed(parser, req->reply_body, req->body_size);
382 // Get the user list from the parsed xml feed.
383 twitter_xt_get_users(parser->root, txl);
384 xt_free(parser);
386 // Add the users as buddies.
387 for (l = txl->list; l; l = g_slist_next(l)) {
388 user = l->data;
389 twitter_add_buddy(ic, user->screen_name, user->name);
392 // Free the structure.
393 txl_free(txl);
395 twitter_get_users_lookup(ic);
399 * Function to fill a twitter_xml_user struct.
400 * It sets:
401 * - the name and
402 * - the screen_name.
404 static xt_status twitter_xt_get_user(struct xt_node *node, struct twitter_xml_user *txu)
406 struct xt_node *child;
408 // Walk over the nodes children.
409 for (child = node->children; child; child = child->next) {
410 if (g_strcasecmp("name", child->name) == 0) {
411 txu->name = g_memdup(child->text, child->text_len + 1);
412 } else if (g_strcasecmp("screen_name", child->name) == 0) {
413 txu->screen_name = g_memdup(child->text, child->text_len + 1);
416 return XT_HANDLED;
420 * Function to fill a twitter_xml_list struct.
421 * It sets:
422 * - all <user>s from the <users> element.
424 static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl)
426 struct twitter_xml_user *txu;
427 struct xt_node *child;
429 // Set the type of the list.
430 txl->type = TXL_USER;
432 // The root <users> node should hold the list of users <user>
433 // Walk over the nodes children.
434 for (child = node->children; child; child = child->next) {
435 if (g_strcasecmp("user", child->name) == 0) {
436 txu = g_new0(struct twitter_xml_user, 1);
437 twitter_xt_get_user(child, txu);
438 // Put the item in the front of the list.
439 txl->list = g_slist_prepend(txl->list, txu);
443 return XT_HANDLED;
446 #ifdef __GLIBC__
447 #define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S %z %Y"
448 #else
449 #define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S +0000 %Y"
450 #endif
453 * Function to fill a twitter_xml_status struct.
454 * It sets:
455 * - the status text and
456 * - the created_at timestamp and
457 * - the status id and
458 * - the user in a twitter_xml_user struct.
460 static xt_status twitter_xt_get_status(struct xt_node *node, struct twitter_xml_status *txs)
462 struct xt_node *child, *rt = NULL;
464 // Walk over the nodes children.
465 for (child = node->children; child; child = child->next) {
466 if (g_strcasecmp("text", child->name) == 0) {
467 txs->text = g_memdup(child->text, child->text_len + 1);
468 } else if (g_strcasecmp("retweeted_status", child->name) == 0) {
469 rt = child;
470 } else if (g_strcasecmp("created_at", child->name) == 0) {
471 struct tm parsed;
473 /* Very sensitive to changes to the formatting of
474 this field. :-( Also assumes the timezone used
475 is UTC since C time handling functions suck. */
476 if (strptime(child->text, TWITTER_TIME_FORMAT, &parsed) != NULL)
477 txs->created_at = mktime_utc(&parsed);
478 } else if (g_strcasecmp("user", child->name) == 0) {
479 txs->user = g_new0(struct twitter_xml_user, 1);
480 twitter_xt_get_user(child, txs->user);
481 } else if (g_strcasecmp("id", child->name) == 0) {
482 txs->id = g_ascii_strtoull(child->text, NULL, 10);
483 } else if (g_strcasecmp("in_reply_to_status_id", child->name) == 0) {
484 txs->reply_to = g_ascii_strtoull(child->text, NULL, 10);
488 /* If it's a (truncated) retweet, get the original. Even if the API claims it
489 wasn't truncated because it may be lying. */
490 if (rt) {
491 struct twitter_xml_status *rtxs = g_new0(struct twitter_xml_status, 1);
492 if (twitter_xt_get_status(rt, rtxs) != XT_HANDLED) {
493 txs_free(rtxs);
494 return XT_HANDLED;
497 g_free(txs->text);
498 txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text);
499 txs_free(rtxs);
500 } else {
501 struct xt_node *urls, *url;
503 urls = xt_find_path(node, "entities/urls");
504 for (url = urls ? urls->children : NULL; url; url = url->next) {
505 /* "short" is a reserved word. :-P */
506 struct xt_node *kort = xt_find_node(url->children, "url");
507 struct xt_node *disp = xt_find_node(url->children, "display_url");
508 char *pos, *new;
510 if (!kort || !kort->text || !disp || !disp->text ||
511 !(pos = strstr(txs->text, kort->text)))
512 continue;
514 *pos = '\0';
515 new = g_strdup_printf("%s%s &lt;%s&gt;%s", txs->text, kort->text,
516 disp->text, pos + strlen(kort->text));
518 g_free(txs->text);
519 txs->text = new;
523 return XT_HANDLED;
527 * Function to fill a twitter_xml_list struct.
528 * It sets:
529 * - all <status>es within the <status> element and
530 * - the next_cursor.
532 static xt_status twitter_xt_get_status_list(struct im_connection *ic, struct xt_node *node,
533 struct twitter_xml_list *txl)
535 struct twitter_xml_status *txs;
536 struct xt_node *child;
537 bee_user_t *bu;
539 // Set the type of the list.
540 txl->type = TXL_STATUS;
542 // The root <statuses> node should hold the list of statuses <status>
543 // Walk over the nodes children.
544 for (child = node->children; child; child = child->next) {
545 if (g_strcasecmp("status", child->name) == 0) {
546 txs = g_new0(struct twitter_xml_status, 1);
547 twitter_xt_get_status(child, txs);
548 // Put the item in the front of the list.
549 txl->list = g_slist_prepend(txl->list, txs);
551 if (txs->user && txs->user->screen_name &&
552 (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) {
553 struct twitter_user_data *tud = bu->data;
555 if (txs->id > tud->last_id) {
556 tud->last_id = txs->id;
557 tud->last_time = txs->created_at;
560 } else if (g_strcasecmp("next_cursor", child->name) == 0) {
561 twitter_xt_next_cursor(child, txl);
565 return XT_HANDLED;
568 static char *twitter_msg_add_id(struct im_connection *ic,
569 struct twitter_xml_status *txs, const char *prefix)
571 struct twitter_data *td = ic->proto_data;
572 char *ret = NULL;
574 if (!set_getbool(&ic->acc->set, "show_ids")) {
575 if (*prefix)
576 return g_strconcat(prefix, txs->text, NULL);
577 else
578 return NULL;
581 td->log[td->log_id].id = txs->id;
582 td->log[td->log_id].bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name);
583 if (txs->reply_to) {
584 int i;
585 for (i = 0; i < TWITTER_LOG_LENGTH; i++)
586 if (td->log[i].id == txs->reply_to) {
587 ret = g_strdup_printf("\002[\002%02d->%02d\002]\002 %s%s",
588 td->log_id, i, prefix, txs->text);
589 break;
592 if (ret == NULL)
593 ret = g_strdup_printf("\002[\002%02d\002]\002 %s%s", td->log_id, prefix, txs->text);
594 td->log_id = (td->log_id + 1) % TWITTER_LOG_LENGTH;
596 return ret;
599 static void twitter_groupchat_init(struct im_connection *ic)
601 char *name_hint;
602 struct groupchat *gc;
603 struct twitter_data *td = ic->proto_data;
604 GSList *l;
606 td->timeline_gc = gc = imcb_chat_new(ic, "twitter/timeline");
608 name_hint = g_strdup_printf("%s_%s", td->prefix, ic->acc->user);
609 imcb_chat_name_hint(gc, name_hint);
610 g_free(name_hint);
612 for (l = ic->bee->users; l; l = l->next) {
613 bee_user_t *bu = l->data;
614 if (bu->ic == ic)
615 imcb_chat_add_buddy(td->timeline_gc, bu->handle);
620 * Function that is called to see the statuses in a groupchat window.
622 static void twitter_groupchat(struct im_connection *ic, GSList * list)
624 struct twitter_data *td = ic->proto_data;
625 GSList *l = NULL;
626 struct twitter_xml_status *status;
627 struct groupchat *gc;
628 guint64 last_id = 0;
630 // Create a new groupchat if it does not exsist.
631 if (!td->timeline_gc)
632 twitter_groupchat_init(ic);
634 gc = td->timeline_gc;
635 if (!gc->joined)
636 imcb_chat_add_buddy(gc, ic->acc->user);
638 for (l = list; l; l = g_slist_next(l)) {
639 char *msg;
641 status = l->data;
642 if (status->user == NULL || status->text == NULL || last_id == status->id)
643 continue;
645 last_id = status->id;
647 strip_html(status->text);
649 if (set_getbool(&ic->acc->set, "strip_newlines"))
650 strip_newlines(status->text);
652 msg = twitter_msg_add_id(ic, status, "");
654 // Say it!
655 if (g_strcasecmp(td->user, status->user->screen_name) == 0) {
656 imcb_chat_log(gc, "You: %s", msg ? msg : status->text);
657 } else {
658 twitter_add_buddy(ic, status->user->screen_name, status->user->name);
660 imcb_chat_msg(gc, status->user->screen_name,
661 msg ? msg : status->text, 0, status->created_at);
664 g_free(msg);
666 // Update the timeline_id to hold the highest id, so that by the next request
667 // we won't pick up the updates already in the list.
668 td->timeline_id = MAX(td->timeline_id, status->id);
673 * Function that is called to see statuses as private messages.
675 static void twitter_private_message_chat(struct im_connection *ic, GSList * list)
677 struct twitter_data *td = ic->proto_data;
678 GSList *l = NULL;
679 struct twitter_xml_status *status;
680 char from[MAX_STRING];
681 gboolean mode_one;
682 guint64 last_id = 0;
684 mode_one = g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "one") == 0;
686 if (mode_one) {
687 g_snprintf(from, sizeof(from) - 1, "%s_%s", td->prefix, ic->acc->user);
688 from[MAX_STRING - 1] = '\0';
691 for (l = list; l; l = g_slist_next(l)) {
692 char *prefix = NULL, *text = NULL;
694 status = l->data;
695 if (status->user == NULL || status->text == NULL || last_id == status->id)
696 continue;
698 last_id = status->id;
700 strip_html(status->text);
701 if (mode_one)
702 prefix = g_strdup_printf("\002<\002%s\002>\002 ",
703 status->user->screen_name);
704 else
705 twitter_add_buddy(ic, status->user->screen_name, status->user->name);
707 text = twitter_msg_add_id(ic, status, prefix ? prefix : "");
709 imcb_buddy_msg(ic,
710 mode_one ? from : status->user->screen_name,
711 text ? text : status->text, 0, status->created_at);
713 // Update the timeline_id to hold the highest id, so that by the next request
714 // we won't pick up the updates already in the list.
715 td->timeline_id = MAX(td->timeline_id, status->id);
717 g_free(text);
718 g_free(prefix);
722 static void twitter_http_get_home_timeline(struct http_request *req);
723 static void twitter_http_get_mentions(struct http_request *req);
726 * Get the timeline with optionally mentions
728 void twitter_get_timeline(struct im_connection *ic, gint64 next_cursor)
730 struct twitter_data *td = ic->proto_data;
731 gboolean include_mentions = set_getbool(&ic->acc->set, "fetch_mentions");
733 if (td->flags & TWITTER_DOING_TIMELINE) {
734 return;
737 td->flags |= TWITTER_DOING_TIMELINE;
739 twitter_get_home_timeline(ic, next_cursor);
741 if (include_mentions) {
742 twitter_get_mentions(ic, next_cursor);
747 * Call this one after receiving timeline/mentions. Show to user once we have
748 * both.
750 void twitter_flush_timeline(struct im_connection *ic)
752 struct twitter_data *td = ic->proto_data;
753 gboolean include_mentions = set_getbool(&ic->acc->set, "fetch_mentions");
754 gboolean show_old_mentions = set_getbool(&ic->acc->set, "show_old_mentions");
755 struct twitter_xml_list *home_timeline = td->home_timeline_obj;
756 struct twitter_xml_list *mentions = td->mentions_obj;
757 GSList *output = NULL;
758 GSList *l;
760 if (!(td->flags & TWITTER_GOT_TIMELINE)) {
761 return;
764 if (include_mentions && !(td->flags & TWITTER_GOT_MENTIONS)) {
765 return;
768 if (home_timeline && home_timeline->list) {
769 for (l = home_timeline->list; l; l = g_slist_next(l)) {
770 output = g_slist_insert_sorted(output, l->data, twitter_compare_elements);
774 if (include_mentions && mentions && mentions->list) {
775 for (l = mentions->list; l; l = g_slist_next(l)) {
776 if (!show_old_mentions && output && twitter_compare_elements(l->data, output->data) < 0) {
777 continue;
780 output = g_slist_insert_sorted(output, l->data, twitter_compare_elements);
784 // See if the user wants to see the messages in a groupchat window or as private messages.
785 if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0)
786 twitter_groupchat(ic, output);
787 else
788 twitter_private_message_chat(ic, output);
790 g_slist_free(output);
792 txl_free(home_timeline);
793 txl_free(mentions);
795 td->flags &= ~(TWITTER_DOING_TIMELINE | TWITTER_GOT_TIMELINE | TWITTER_GOT_MENTIONS);
796 td->home_timeline_obj = td->mentions_obj = NULL;
800 * Get the timeline.
802 void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
804 struct twitter_data *td = ic->proto_data;
806 txl_free(td->home_timeline_obj);
807 td->home_timeline_obj = NULL;
808 td->flags &= ~TWITTER_GOT_TIMELINE;
810 char *args[6];
811 args[0] = "cursor";
812 args[1] = g_strdup_printf("%lld", (long long) next_cursor);
813 args[2] = "include_entities";
814 args[3] = "true";
815 if (td->timeline_id) {
816 args[4] = "since_id";
817 args[5] = g_strdup_printf("%llu", (long long unsigned int) td->timeline_id);
820 if (twitter_http(ic, TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, args,
821 td->timeline_id ? 6 : 4) == NULL) {
822 if (++td->http_fails >= 5)
823 imcb_error(ic, "Could not retrieve %s: %s",
824 TWITTER_HOME_TIMELINE_URL, "connection failed");
825 td->flags |= TWITTER_GOT_TIMELINE;
826 twitter_flush_timeline(ic);
829 g_free(args[1]);
830 if (td->timeline_id) {
831 g_free(args[5]);
836 * Get mentions.
838 void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)
840 struct twitter_data *td = ic->proto_data;
842 txl_free(td->mentions_obj);
843 td->mentions_obj = NULL;
844 td->flags &= ~TWITTER_GOT_MENTIONS;
846 char *args[6];
847 args[0] = "cursor";
848 args[1] = g_strdup_printf("%lld", (long long) next_cursor);
849 args[2] = "include_entities";
850 args[3] = "true";
851 if (td->timeline_id) {
852 args[4] = "since_id";
853 args[5] = g_strdup_printf("%llu", (long long unsigned int) td->timeline_id);
856 if (twitter_http(ic, TWITTER_MENTIONS_URL, twitter_http_get_mentions, ic, 0, args,
857 td->timeline_id ? 6 : 4) == NULL) {
858 if (++td->http_fails >= 5)
859 imcb_error(ic, "Could not retrieve %s: %s",
860 TWITTER_MENTIONS_URL, "connection failed");
861 td->flags |= TWITTER_GOT_MENTIONS;
862 twitter_flush_timeline(ic);
865 g_free(args[1]);
866 if (td->timeline_id) {
867 g_free(args[5]);
872 * Callback for getting the home timeline.
874 static void twitter_http_get_home_timeline(struct http_request *req)
876 struct im_connection *ic = req->data;
877 struct twitter_data *td;
878 struct xt_parser *parser;
879 struct twitter_xml_list *txl;
881 // Check if the connection is still active.
882 if (!g_slist_find(twitter_connections, ic))
883 return;
885 td = ic->proto_data;
887 // Check if the HTTP request went well.
888 if (req->status_code == 200) {
889 td->http_fails = 0;
890 if (!(ic->flags & OPT_LOGGED_IN))
891 imcb_connected(ic);
892 } else {
893 // It didn't go well, output the error and return.
894 if (++td->http_fails >= 5)
895 imcb_error(ic, "Could not retrieve %s: %s",
896 TWITTER_HOME_TIMELINE_URL, twitter_parse_error(req));
898 goto end;
901 txl = g_new0(struct twitter_xml_list, 1);
902 txl->list = NULL;
904 // Parse the data.
905 parser = xt_new(NULL, txl);
906 xt_feed(parser, req->reply_body, req->body_size);
907 // The root <statuses> node should hold the list of statuses <status>
908 twitter_xt_get_status_list(ic, parser->root, txl);
909 xt_free(parser);
911 td->home_timeline_obj = txl;
913 end:
914 td->flags |= TWITTER_GOT_TIMELINE;
916 twitter_flush_timeline(ic);
920 * Callback for getting mentions.
922 static void twitter_http_get_mentions(struct http_request *req)
924 struct im_connection *ic = req->data;
925 struct twitter_data *td;
926 struct xt_parser *parser;
927 struct twitter_xml_list *txl;
929 // Check if the connection is still active.
930 if (!g_slist_find(twitter_connections, ic))
931 return;
933 td = ic->proto_data;
935 // Check if the HTTP request went well.
936 if (req->status_code == 200) {
937 td->http_fails = 0;
938 if (!(ic->flags & OPT_LOGGED_IN))
939 imcb_connected(ic);
940 } else {
941 // It didn't go well, output the error and return.
942 if (++td->http_fails >= 5)
943 imcb_error(ic, "Could not retrieve %s: %s",
944 TWITTER_MENTIONS_URL, twitter_parse_error(req));
946 goto end;
949 txl = g_new0(struct twitter_xml_list, 1);
950 txl->list = NULL;
952 // Parse the data.
953 parser = xt_new(NULL, txl);
954 xt_feed(parser, req->reply_body, req->body_size);
955 // The root <statuses> node should hold the list of statuses <status>
956 twitter_xt_get_status_list(ic, parser->root, txl);
957 xt_free(parser);
959 td->mentions_obj = txl;
961 end:
962 td->flags |= TWITTER_GOT_MENTIONS;
964 twitter_flush_timeline(ic);
968 * Callback to use after sending a POST request to twitter.
969 * (Generic, used for a few kinds of queries.)
971 static void twitter_http_post(struct http_request *req)
973 struct im_connection *ic = req->data;
974 struct twitter_data *td;
976 // Check if the connection is still active.
977 if (!g_slist_find(twitter_connections, ic))
978 return;
980 td = ic->proto_data;
981 td->last_status_id = 0;
983 // Check if the HTTP request went well.
984 if (req->status_code != 200) {
985 // It didn't go well, output the error and return.
986 imcb_error(ic, "HTTP error: %s", twitter_parse_error(req));
987 return;
990 if (req->body_size > 0) {
991 struct xt_parser *xp = NULL;
992 struct xt_node *node;
994 xp = xt_new(NULL, NULL);
995 xt_feed(xp, req->reply_body, req->body_size);
997 if ((node = xt_find_node(xp->root, "status")) &&
998 (node = xt_find_node(node->children, "id")) && node->text)
999 td->last_status_id = g_ascii_strtoull(node->text, NULL, 10);
1001 xt_free(xp);
1006 * Function to POST a new status to twitter.
1008 void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to)
1010 char *args[4] = {
1011 "status", msg,
1012 "in_reply_to_status_id",
1013 g_strdup_printf("%llu", (unsigned long long) in_reply_to)
1015 twitter_http(ic, TWITTER_STATUS_UPDATE_URL, twitter_http_post, ic, 1,
1016 args, in_reply_to ? 4 : 2);
1017 g_free(args[3]);
1022 * Function to POST a new message to twitter.
1024 void twitter_direct_messages_new(struct im_connection *ic, char *who, char *msg)
1026 char *args[4];
1027 args[0] = "screen_name";
1028 args[1] = who;
1029 args[2] = "text";
1030 args[3] = msg;
1031 // Use the same callback as for twitter_post_status, since it does basically the same.
1032 twitter_http(ic, TWITTER_DIRECT_MESSAGES_NEW_URL, twitter_http_post, ic, 1, args, 4);
1035 void twitter_friendships_create_destroy(struct im_connection *ic, char *who, int create)
1037 char *args[2];
1038 args[0] = "screen_name";
1039 args[1] = who;
1040 twitter_http(ic, create ? TWITTER_FRIENDSHIPS_CREATE_URL : TWITTER_FRIENDSHIPS_DESTROY_URL,
1041 twitter_http_post, ic, 1, args, 2);
1044 void twitter_status_destroy(struct im_connection *ic, guint64 id)
1046 char *url;
1047 url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_DESTROY_URL,
1048 (unsigned long long) id, ".xml");
1049 twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);
1050 g_free(url);
1053 void twitter_status_retweet(struct im_connection *ic, guint64 id)
1055 char *url;
1056 url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_RETWEET_URL,
1057 (unsigned long long) id, ".xml");
1058 twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);
1059 g_free(url);