1 /***************************************************************************\
3 * BitlBee - An IRC to IM gateway *
4 * Simple module to facilitate twitter functionality. *
6 * Copyright 2009-2010 Geert Mulders <g.c.w.m.mulders@gmail.com> *
7 * Copyright 2010-2011 Wilmer van der Gaast <wilmer@gaast.net> *
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 *
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. *
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 *
23 ****************************************************************************/
31 #include "twitter_http.h"
38 #include "twitter_lib.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)
47 #define g_ascii_strtoll strtoll
54 struct twitter_xml_list
{
60 struct twitter_xml_user
{
65 struct twitter_xml_status
{
68 struct twitter_xml_user
*user
;
72 static void twitter_groupchat_init(struct im_connection
*ic
);
75 * Frees a twitter_xml_user struct.
77 static void txu_free(struct twitter_xml_user
*txu
)
83 g_free(txu
->screen_name
);
88 * Frees a twitter_xml_status struct.
90 static void txs_free(struct twitter_xml_status
*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
)
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
) {
115 } else if (txl
->type
== TXL_USER
) {
120 g_slist_free(txl
->list
);
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
) {
134 } else if (a_status
->created_at
> b_status
->created_at
) {
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
;
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
);
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...
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);
209 * Function to help fill a list.
211 static xt_status
twitter_xt_next_cursor(struct xt_node
*node
, struct twitter_xml_list
*txl
)
216 txl
->next_cursor
= g_ascii_strtoll(node
->text
, &end
, 10);
218 txl
->next_cursor
= -1;
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.
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) {
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
);
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
;
264 // Check if the connection is still active.
265 if (!g_slist_find(twitter_connections
, ic
))
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
);
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
);
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
;
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
);
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
);
305 /* Now to convert all those numbers into names.. */
306 twitter_get_users_lookup(ic
);
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
;
322 GString
*ids
= g_string_new("");
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
);
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);
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
;
357 struct twitter_xml_user
*user
;
359 // Check if the connection is still active.
360 if (!g_slist_find(twitter_connections
, ic
))
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
);
375 txl
= g_new0(struct twitter_xml_list
, 1);
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
);
386 // Add the users as buddies.
387 for (l
= txl
->list
; l
; l
= g_slist_next(l
)) {
389 twitter_add_buddy(ic
, user
->screen_name
, user
->name
);
392 // Free the structure.
395 twitter_get_users_lookup(ic
);
399 * Function to fill a twitter_xml_user struct.
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);
420 * Function to fill a twitter_xml_list struct.
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
);
447 #define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S %z %Y"
449 #define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S +0000 %Y"
453 * Function to fill a twitter_xml_status struct.
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) {
470 } else if (g_strcasecmp("created_at", child
->name
) == 0) {
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. */
491 struct twitter_xml_status
*rtxs
= g_new0(struct twitter_xml_status
, 1);
492 if (twitter_xt_get_status(rt
, rtxs
) != XT_HANDLED
) {
498 txs
->text
= g_strdup_printf("RT @%s: %s", rtxs
->user
->screen_name
, rtxs
->text
);
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");
510 if (!kort
|| !kort
->text
|| !disp
|| !disp
->text
||
511 !(pos
= strstr(txs
->text
, kort
->text
)))
515 new = g_strdup_printf("%s%s <%s>%s", txs
->text
, kort
->text
,
516 disp
->text
, pos
+ strlen(kort
->text
));
527 * Function to fill a twitter_xml_list struct.
529 * - all <status>es within the <status> element and
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
;
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
);
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
;
574 if (!set_getbool(&ic
->acc
->set
, "show_ids")) {
576 return g_strconcat(prefix
, txs
->text
, 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
);
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
);
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
;
599 static void twitter_groupchat_init(struct im_connection
*ic
)
602 struct groupchat
*gc
;
603 struct twitter_data
*td
= ic
->proto_data
;
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
);
612 for (l
= ic
->bee
->users
; l
; l
= l
->next
) {
613 bee_user_t
*bu
= l
->data
;
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
;
626 struct twitter_xml_status
*status
;
627 struct groupchat
*gc
;
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
;
636 imcb_chat_add_buddy(gc
, ic
->acc
->user
);
638 for (l
= list
; l
; l
= g_slist_next(l
)) {
642 if (status
->user
== NULL
|| status
->text
== NULL
|| last_id
== status
->id
)
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
, "");
655 if (g_strcasecmp(td
->user
, status
->user
->screen_name
) == 0) {
656 imcb_chat_log(gc
, "You: %s", msg
? msg
: status
->text
);
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
);
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
;
679 struct twitter_xml_status
*status
;
680 char from
[MAX_STRING
];
684 mode_one
= g_strcasecmp(set_getstr(&ic
->acc
->set
, "mode"), "one") == 0;
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
;
695 if (status
->user
== NULL
|| status
->text
== NULL
|| last_id
== status
->id
)
698 last_id
= status
->id
;
700 strip_html(status
->text
);
702 prefix
= g_strdup_printf("\002<\002%s\002>\002 ",
703 status
->user
->screen_name
);
705 twitter_add_buddy(ic
, status
->user
->screen_name
, status
->user
->name
);
707 text
= twitter_msg_add_id(ic
, status
, prefix
? prefix
: "");
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
);
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
) {
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
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
;
760 if (!(td
->flags
& TWITTER_GOT_TIMELINE
)) {
764 if (include_mentions
&& !(td
->flags
& TWITTER_GOT_MENTIONS
)) {
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) {
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
);
788 twitter_private_message_chat(ic
, output
);
790 g_slist_free(output
);
792 txl_free(home_timeline
);
795 td
->flags
&= ~(TWITTER_DOING_TIMELINE
| TWITTER_GOT_TIMELINE
| TWITTER_GOT_MENTIONS
);
796 td
->home_timeline_obj
= td
->mentions_obj
= NULL
;
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
;
812 args
[1] = g_strdup_printf("%lld", (long long) next_cursor
);
813 args
[2] = "include_entities";
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
);
830 if (td
->timeline_id
) {
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
;
848 args
[1] = g_strdup_printf("%lld", (long long) next_cursor
);
849 args
[2] = "include_entities";
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
);
866 if (td
->timeline_id
) {
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
))
887 // Check if the HTTP request went well.
888 if (req
->status_code
== 200) {
890 if (!(ic
->flags
& OPT_LOGGED_IN
))
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
));
901 txl
= g_new0(struct twitter_xml_list
, 1);
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
);
911 td
->home_timeline_obj
= txl
;
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
))
935 // Check if the HTTP request went well.
936 if (req
->status_code
== 200) {
938 if (!(ic
->flags
& OPT_LOGGED_IN
))
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
));
949 txl
= g_new0(struct twitter_xml_list
, 1);
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
);
959 td
->mentions_obj
= txl
;
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
))
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
));
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);
1006 * Function to POST a new status to twitter.
1008 void twitter_post_status(struct im_connection
*ic
, char *msg
, guint64 in_reply_to
)
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);
1022 * Function to POST a new message to twitter.
1024 void twitter_direct_messages_new(struct im_connection
*ic
, char *who
, char *msg
)
1027 args
[0] = "screen_name";
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
)
1038 args
[0] = "screen_name";
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
)
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);
1053 void twitter_status_retweet(struct im_connection
*ic
, guint64 id
)
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);