diff options
Diffstat (limited to 'protocols/twitter')
-rw-r--r-- | protocols/twitter/Makefile | 45 | ||||
-rw-r--r-- | protocols/twitter/twitter.c | 553 | ||||
-rw-r--r-- | protocols/twitter/twitter.h | 75 | ||||
-rw-r--r-- | protocols/twitter/twitter_http.c | 142 | ||||
-rw-r--r-- | protocols/twitter/twitter_http.h | 36 | ||||
-rw-r--r-- | protocols/twitter/twitter_lib.c | 837 | ||||
-rw-r--r-- | protocols/twitter/twitter_lib.h | 91 |
7 files changed, 1779 insertions, 0 deletions
diff --git a/protocols/twitter/Makefile b/protocols/twitter/Makefile new file mode 100644 index 00000000..3fa9b61e --- /dev/null +++ b/protocols/twitter/Makefile @@ -0,0 +1,45 @@ +########################### +## Makefile for BitlBee ## +## ## +## Copyright 2002 Lintux ## +########################### + +### DEFINITIONS + +-include ../../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)protocols/twitter/ +endif + +# [SH] Program variables +objects = twitter.o twitter_http.o twitter_lib.o + +LFLAGS += -r + +# [SH] Phony targets +all: twitter_mod.o +check: all +lcov: check +gcov: + gcov *.c + +.PHONY: all clean distclean + +clean: + rm -f *.o core + +distclean: clean + +### MAIN PROGRAM + +$(objects): ../../Makefile.settings Makefile + +$(objects): %.o: $(SRCDIR)%.c + @echo '*' Compiling $< + @$(CC) -c $(CFLAGS) $< -o $@ + +twitter_mod.o: $(objects) + @echo '*' Linking twitter_mod.o + @$(LD) $(LFLAGS) $(objects) -o twitter_mod.o + + diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c new file mode 100644 index 00000000..a2f2325c --- /dev/null +++ b/protocols/twitter/twitter.c @@ -0,0 +1,553 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Simple module to facilitate twitter functionality. * +* * +* Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Lesser General Public * +* License as published by the Free Software Foundation, version * +* 2.1. * +* * +* This library is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * +* Lesser General Public License for more details. * +* * +* You should have received a copy of the GNU Lesser General Public License * +* along with this library; if not, write to the Free Software Foundation, * +* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * +* * +****************************************************************************/ + +#include "nogaim.h" +#include "oauth.h" +#include "twitter.h" +#include "twitter_http.h" +#include "twitter_lib.h" +#include "url.h" + +/** + * Main loop function + */ +gboolean twitter_main_loop(gpointer data, gint fd, b_input_condition cond) +{ + struct im_connection *ic = data; + + // Check if we are still logged in... + if (!g_slist_find( twitter_connections, ic )) + return 0; + + // Do stuff.. + twitter_get_home_timeline(ic, -1); + + // If we are still logged in run this function again after timeout. + return (ic->flags & OPT_LOGGED_IN) == OPT_LOGGED_IN; +} + +static void twitter_main_loop_start( struct im_connection *ic ) +{ + struct twitter_data *td = ic->proto_data; + + imcb_log( ic, "Getting initial statuses" ); + + // 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(60000, twitter_main_loop, ic); +} + +static void twitter_oauth_start( struct im_connection *ic ); + +void twitter_login_finish( struct im_connection *ic ) +{ + struct twitter_data *td = ic->proto_data; + + 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 ) ) + { + imcb_log( ic, "Getting contact list" ); + twitter_get_statuses_friends( ic, -1 ); + } + else + twitter_main_loop_start( ic ); +} + +static const struct oauth_service twitter_oauth = +{ + "http://api.twitter.com/oauth/request_token", + "http://api.twitter.com/oauth/access_token", + "https://api.twitter.com/oauth/authorize", + .consumer_key = "xsDNKJuNZYkZyMcu914uEA", + .consumer_secret = "FCxqcr0pXKzsF9ajmP57S3VQ8V6Drk4o2QYtqMcOszo", +}; + +static gboolean twitter_oauth_callback( struct oauth_info *info ); + +static void twitter_oauth_start( struct im_connection *ic ) +{ + struct twitter_data *td = ic->proto_data; + + imcb_log( ic, "Requesting OAuth request token" ); + + td->oauth_info = oauth_request_token( &twitter_oauth, twitter_oauth_callback, ic ); +} + +static gboolean twitter_oauth_callback( struct oauth_info *info ) +{ + struct im_connection *ic = info->data; + struct twitter_data *td; + + if( !g_slist_find( twitter_connections, ic ) ) + return FALSE; + + td = ic->proto_data; + if( info->stage == OAUTH_REQUEST_TOKEN ) + { + char name[strlen(ic->acc->user)+9], *msg; + + if( info->request_token == NULL ) + { + imcb_error( ic, "OAuth error: %s", info->http->status_string ); + imc_logout( ic, TRUE ); + return FALSE; + } + + sprintf( name, "%s_%s", td->prefix, ic->acc->user ); + msg = g_strdup_printf( "To finish OAuth authentication, please visit " + "%s and respond with the resulting PIN code.", + info->auth_url ); + imcb_buddy_msg( ic, name, msg, 0, 0 ); + g_free( msg ); + } + else if( info->stage == OAUTH_ACCESS_TOKEN ) + { + if( info->token == NULL || info->token_secret == NULL ) + { + imcb_error( ic, "OAuth error: %s", info->http->status_string ); + imc_logout( ic, TRUE ); + return FALSE; + } + + /* IM mods didn't do this so far and it's ugly but I should + be able to get away with it... */ + g_free( ic->acc->pass ); + ic->acc->pass = oauth_to_string( info ); + + twitter_login_finish( ic ); + } + + 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; +} + +static gboolean twitter_length_check( struct im_connection *ic, gchar *msg ) +{ + int max = set_getint( &ic->acc->set, "message_length" ), len; + + if( max == 0 || ( len = g_utf8_strlen( msg, -1 ) ) <= max ) + return TRUE; + + imcb_error( ic, "Maximum message length exceeded: %d > %d", len, max ); + + return FALSE; +} + +static void twitter_init( account_t *acc ) +{ + set_t *s; + char *def_url; + char *def_oauth; + + if( strcmp( acc->prpl->name, "twitter" ) == 0 ) + { + def_url = TWITTER_API_URL; + def_oauth = "true"; + } + else /* if( strcmp( acc->prpl->name, "identica" ) == 0 ) */ + { + def_url = IDENTICA_API_URL; + def_oauth = "false"; + } + + s = set_add( &acc->set, "auto_reply_timeout", "10800", set_eval_int, 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, "message_length", "140", set_eval_int, acc ); + + s = set_add( &acc->set, "mode", "one", set_eval_mode, acc ); + s->flags |= ACC_SET_OFFLINE_ONLY; + + s = set_add( &acc->set, "oauth", def_oauth, set_eval_bool, acc ); +} + +/** + * Login method. Since the twitter API works with seperate HTTP request we + * only save the user and pass to the twitter_data object. + */ +static void twitter_login( account_t *acc ) +{ + struct im_connection *ic = imcb_new( acc ); + struct twitter_data *td; + char name[strlen(acc->user)+9]; + url_t url; + + if( !url_set( &url, set_getstr( &ic->acc->set, "base_url" ) ) || + ( url.proto != PROTO_HTTP && url.proto != PROTO_HTTPS ) ) + { + imcb_error( ic, "Incorrect API base URL: %s", set_getstr( &ic->acc->set, "base_url" ) ); + imc_logout( ic, FALSE ); + return; + } + + twitter_connections = g_slist_append( twitter_connections, ic ); + td = g_new0( struct twitter_data, 1 ); + ic->proto_data = td; + + td->url_ssl = url.proto == PROTO_HTTPS; + td->url_port = url.port; + td->url_host = g_strdup( url.host ); + if( strcmp( url.file, "/" ) != 0 ) + td->url_path = g_strdup( url.file ); + else + td->url_path = g_strdup( "" ); + if( g_str_has_suffix( url.host, ".com" ) ) + td->prefix = g_strndup( url.host, strlen( url.host ) - 4 ); + else + td->prefix = g_strdup( url.host ); + + td->user = acc->user; + if( strstr( acc->pass, "oauth_token=" ) ) + td->oauth_info = oauth_from_string( acc->pass, &twitter_oauth ); + + sprintf( name, "%s_%s", td->prefix, acc->user ); + imcb_add_buddy( ic, name, NULL ); + imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL ); + + imcb_log( ic, "Connecting" ); + + twitter_login_finish( ic ); +} + +/** + * Logout method. Just free the twitter_data. + */ +static void twitter_logout( struct im_connection *ic ) +{ + struct twitter_data *td = ic->proto_data; + + // Set the status to logged out. + ic->flags = 0; + + // Remove the main_loop function from the function queue. + b_event_remove(td->main_loop_id); + + if(td->home_timeline_gc) + imcb_chat_free(td->home_timeline_gc); + + if( td ) + { + oauth_info_free( td->oauth_info ); + g_free( td->prefix ); + g_free( td->url_host ); + g_free( td->url_path ); + g_free( td->pass ); + g_free( td ); + } + + twitter_connections = g_slist_remove( twitter_connections, ic ); +} + +static void twitter_handle_command( struct im_connection *ic, char *message ); + +/** + * + */ +static int twitter_buddy_msg( struct im_connection *ic, char *who, char *message, int away ) +{ + struct twitter_data *td = ic->proto_data; + int plen = strlen( td->prefix ); + + if (g_strncasecmp(who, td->prefix, plen) == 0 && who[plen] == '_' && + g_strcasecmp(who + plen + 1, ic->acc->user) == 0) + { + if( set_getbool( &ic->acc->set, "oauth" ) && + td->oauth_info && td->oauth_info->token == NULL ) + { + char pin[strlen(message)+1], *s; + + strcpy( pin, message ); + for( s = pin + sizeof( pin ) - 2; s > pin && isspace( *s ); s -- ) + *s = '\0'; + for( s = pin; *s && isspace( *s ); s ++ ) {} + + if( !oauth_access_token( s, td->oauth_info ) ) + { + imcb_error( ic, "OAuth error: %s", "Failed to send access token request" ); + imc_logout( ic, TRUE ); + return FALSE; + } + } + else + twitter_handle_command(ic, message); + } + else + { + twitter_direct_messages_new(ic, who, message); + } + return( 0 ); +} + +/** + * + */ +static void twitter_set_my_name( struct im_connection *ic, char *info ) +{ +} + +static void twitter_get_info(struct im_connection *ic, char *who) +{ +} + +static void twitter_add_buddy( struct im_connection *ic, char *who, char *group ) +{ + twitter_friendships_create_destroy(ic, who, 1); +} + +static void twitter_remove_buddy( struct im_connection *ic, char *who, char *group ) +{ + twitter_friendships_create_destroy(ic, who, 0); +} + +static void twitter_chat_msg( struct groupchat *c, char *message, int flags ) +{ + if( c && message ) + twitter_handle_command( c->ic, message ); +} + +static void twitter_chat_invite( struct groupchat *c, char *who, char *message ) +{ +} + +static void twitter_chat_leave( struct groupchat *c ) +{ + struct twitter_data *td = c->ic->proto_data; + + if( c != td->home_timeline_gc ) + return; /* WTF? */ + + /* If the user leaves the channel: Fine. Rejoin him/her once new + tweets come in. */ + imcb_chat_free(td->home_timeline_gc); + td->home_timeline_gc = NULL; +} + +static struct groupchat *twitter_chat_with( struct im_connection *ic, char *who ) +{ + return NULL; +} + +static void twitter_keepalive( struct im_connection *ic ) +{ +} + +static void twitter_add_permit( struct im_connection *ic, char *who ) +{ +} + +static void twitter_rem_permit( struct im_connection *ic, char *who ) +{ +} + +static void twitter_add_deny( struct im_connection *ic, char *who ) +{ +} + +static void twitter_rem_deny( struct im_connection *ic, char *who ) +{ +} + +static int twitter_send_typing( struct im_connection *ic, char *who, int typing ) +{ + return( 1 ); +} + +//static char *twitter_set_display_name( set_t *set, char *value ) +//{ +// return value; +//} + +static void twitter_buddy_data_add( struct bee_user *bu ) +{ + bu->data = g_new0( struct twitter_user_data, 1 ); +} + +static void twitter_buddy_data_free( struct bee_user *bu ) +{ + g_free( bu->data ); +} + +static void twitter_handle_command( struct im_connection *ic, char *message ) +{ + struct twitter_data *td = ic->proto_data; + char *cmds, **cmd; + + cmds = g_strdup( message ); + cmd = split_command_parts( cmds ); + + if( cmd[0] == NULL ) + { + g_free( cmds ); + return; + } + else if( !set_getbool( &ic->acc->set, "commands" ) ) + { + /* Not supporting commands. */ + } + else if( g_strcasecmp( cmd[0], "undo" ) == 0 ) + { + guint64 id; + + if( cmd[1] ) + id = g_ascii_strtoull( cmd[1], NULL, 10 ); + else + id = td->last_status_id; + + /* TODO: User feedback. */ + if( id ) + twitter_status_destroy( ic, id ); + + g_free( cmds ); + return; + } + else if( g_strcasecmp( cmd[0], "follow" ) == 0 && cmd[1] ) + { + twitter_add_buddy( ic, cmd[1], NULL ); + g_free( cmds ); + return; + } + else if( g_strcasecmp( cmd[0], "unfollow" ) == 0 && cmd[1] ) + { + twitter_remove_buddy( ic, cmd[1], NULL ); + g_free( cmds ); + return; + } + else if( g_strcasecmp( cmd[0], "rt" ) == 0 && cmd[1] ) + { + struct twitter_user_data *tud; + bee_user_t *bu; + guint64 id; + + if( ( bu = bee_user_by_handle( ic->bee, ic, cmd[1] ) ) && + ( tud = bu->data ) && tud->last_id ) + id = tud->last_id; + else + id = g_ascii_strtoull( cmd[1], NULL, 10 ); + + td->last_status_id = 0; + if( id ) + twitter_status_retweet( ic, id ); + + g_free( cmds ); + return; + } + else if( g_strcasecmp( cmd[0], "post" ) == 0 ) + { + message += 5; + } + + { + guint64 in_reply_to = 0; + char *s, *new = NULL; + bee_user_t *bu; + + if( !twitter_length_check( ic, message ) ) + { + g_free( cmds ); + return; + } + + s = cmd[0] + strlen( cmd[0] ) - 1; + if( s > cmd[0] && ( *s == ':' || *s == ',' ) ) + { + *s = '\0'; + + if( ( bu = bee_user_by_handle( ic->bee, ic, cmd[0] ) ) ) + { + struct twitter_user_data *tud = bu->data; + + new = g_strdup_printf( "@%s %s", bu->handle, + message + ( s - cmd[0] ) + 2 ); + message = new; + + if( time( NULL ) < tud->last_time + + set_getint( &ic->acc->set, "auto_reply_timeout" ) ) + in_reply_to = tud->last_id; + } + } + + /* If the user runs undo between this request and its response + 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 ); + } + g_free( cmds ); +} + +void twitter_initmodule() +{ + struct prpl *ret = g_new0(struct prpl, 1); + + ret->name = "twitter"; + ret->login = twitter_login; + ret->init = twitter_init; + ret->logout = twitter_logout; + ret->buddy_msg = twitter_buddy_msg; + ret->get_info = twitter_get_info; + ret->set_my_name = twitter_set_my_name; + ret->add_buddy = twitter_add_buddy; + ret->remove_buddy = twitter_remove_buddy; + ret->chat_msg = twitter_chat_msg; + ret->chat_invite = twitter_chat_invite; + ret->chat_leave = twitter_chat_leave; + ret->chat_with = twitter_chat_with; + ret->keepalive = twitter_keepalive; + ret->add_permit = twitter_add_permit; + ret->rem_permit = twitter_rem_permit; + ret->add_deny = twitter_add_deny; + ret->rem_deny = twitter_rem_deny; + ret->send_typing = twitter_send_typing; + ret->buddy_data_add = twitter_buddy_data_add; + ret->buddy_data_free = twitter_buddy_data_free; + ret->handle_cmp = g_strcasecmp; + + register_protocol(ret); + + /* And an identi.ca variant: */ + ret = g_memdup(ret, sizeof(struct prpl)); + ret->name = "identica"; + register_protocol(ret); + + // Initialise the twitter_connections GSList. + twitter_connections = NULL; +} diff --git a/protocols/twitter/twitter.h b/protocols/twitter/twitter.h new file mode 100644 index 00000000..0acb3b4d --- /dev/null +++ b/protocols/twitter/twitter.h @@ -0,0 +1,75 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Simple module to facilitate twitter functionality. * +* * +* Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Lesser General Public * +* License as published by the Free Software Foundation, version * +* 2.1. * +* * +* This library is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * +* Lesser General Public License for more details. * +* * +* You should have received a copy of the GNU Lesser General Public License * +* along with this library; if not, write to the Free Software Foundation, * +* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * +* * +****************************************************************************/ + +#include "nogaim.h" + +#ifndef _TWITTER_H +#define _TWITTER_H + +#ifdef DEBUG_TWITTER +#define debug( text... ) imcb_log( ic, text ); +#else +#define debug( text... ) +#endif + +typedef enum +{ + TWITTER_HAVE_FRIENDS = 1, +} twitter_flags_t; + +struct twitter_data +{ + char* user; + char* pass; + struct oauth_info *oauth_info; + guint64 home_timeline_id; + guint64 last_status_id; /* For undo */ + gint main_loop_id; + struct groupchat *home_timeline_gc; + gint http_fails; + twitter_flags_t flags; + + gboolean url_ssl; + int url_port; + char *url_host; + char *url_path; + + char *prefix; /* Used to generate contact + channel name. */ +}; + +struct twitter_user_data +{ + guint64 last_id; + time_t last_time; +}; + +/** + * This has the same function as the msn_connections GSList. We use this to + * make sure the connection is still alive in callbacks before we do anything + * else. + */ +GSList *twitter_connections; + +void twitter_login_finish( struct im_connection *ic ); + +#endif //_TWITTER_H diff --git a/protocols/twitter/twitter_http.c b/protocols/twitter/twitter_http.c new file mode 100644 index 00000000..ff17f5f4 --- /dev/null +++ b/protocols/twitter/twitter_http.c @@ -0,0 +1,142 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Simple module to facilitate twitter functionality. * +* * +* Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Lesser General Public * +* License as published by the Free Software Foundation, version * +* 2.1. * +* * +* This library is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * +* Lesser General Public License for more details. * +* * +* You should have received a copy of the GNU Lesser General Public License * +* along with this library; if not, write to the Free Software Foundation, * +* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * +* * +****************************************************************************/ + +/***************************************************************************\ +* * +* Some funtions within this file have been copied from other files within * +* BitlBee. * +* * +****************************************************************************/ + +#include "twitter.h" +#include "bitlbee.h" +#include "url.h" +#include "misc.h" +#include "base64.h" +#include "oauth.h" +#include <ctype.h> +#include <errno.h> + +#include "twitter_http.h" + + +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 twitter_data *td = ic->proto_data; + char *tmp; + GString *request = g_string_new(""); + void *ret; + char *url_arguments; + + url_arguments = g_strdup(""); + + // Construct the url arguments. + if (arguments_len != 0) + { + int i; + for (i=0; i<arguments_len; i+=2) + { + tmp = twitter_url_append(url_arguments, arguments[i], arguments[i+1]); + g_free(url_arguments); + url_arguments = tmp; + } + } + + // 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); + + // 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); + full_header = oauth_http_header(td->oauth_info, is_post ? "POST" : "GET", + full_url, url_arguments); + + g_string_append_printf(request, "Authorization: %s\r\n", full_header); + g_free(full_header); + g_free(full_url); + } + else + { + char userpass[strlen(ic->acc->user)+2+strlen(ic->acc->pass)]; + char *userpass_base64; + + g_snprintf(userpass, sizeof(userpass), "%s:%s", ic->acc->user, ic->acc->pass); + userpass_base64 = base64_encode((unsigned char*)userpass, strlen(userpass)); + g_string_append_printf(request, "Authorization: Basic %s\r\n", userpass_base64); + g_free( userpass_base64 ); + } + + // Do POST stuff.. + if (is_post) + { + // Append the Content-Type and url-encoded arguments. + g_string_append_printf(request, + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: %zd\r\n\r\n%s", + strlen(url_arguments), url_arguments); + } else { + // Append an extra \r\n to end the request... + g_string_append(request, "\r\n"); + } + + 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 ); + return ret; +} + +static char *twitter_url_append(char *url, char *key, char* value) +{ + char *key_encoded = g_strndup(key, 3 * strlen(key)); + http_encode(key_encoded); + char *value_encoded = g_strndup(value, 3 * strlen(value)); + http_encode(value_encoded); + + char *retval; + if (strlen(url) != 0) + retval = g_strdup_printf("%s&%s=%s", url, key_encoded, value_encoded); + else + retval = g_strdup_printf("%s=%s", key_encoded, value_encoded); + + g_free(key_encoded); + g_free(value_encoded); + + return retval; +} diff --git a/protocols/twitter/twitter_http.h b/protocols/twitter/twitter_http.h new file mode 100644 index 00000000..393a1c26 --- /dev/null +++ b/protocols/twitter/twitter_http.h @@ -0,0 +1,36 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Simple module to facilitate twitter functionality. * +* * +* Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Lesser General Public * +* License as published by the Free Software Foundation, version * +* 2.1. * +* * +* This library is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * +* Lesser General Public License for more details. * +* * +* You should have received a copy of the GNU Lesser General Public License * +* along with this library; if not, write to the Free Software Foundation, * +* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * +* * +****************************************************************************/ + +#ifndef _TWITTER_HTTP_H +#define _TWITTER_HTTP_H + +#include "nogaim.h" +#include "http_client.h" + +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); + +#endif //_TWITTER_HTTP_H + diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c new file mode 100644 index 00000000..f9e808f7 --- /dev/null +++ b/protocols/twitter/twitter_lib.c @@ -0,0 +1,837 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Simple module to facilitate twitter functionality. * +* * +* Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Lesser General Public * +* License as published by the Free Software Foundation, version * +* 2.1. * +* * +* This library is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * +* Lesser General Public License for more details. * +* * +* You should have received a copy of the GNU Lesser General Public License * +* along with this library; if not, write to the Free Software Foundation, * +* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * +* * +****************************************************************************/ + +/* For strptime(): */ +#if(__sun) +#else +#define _XOPEN_SOURCE +#endif + +#include "twitter_http.h" +#include "twitter.h" +#include "bitlbee.h" +#include "url.h" +#include "misc.h" +#include "base64.h" +#include "xmltree.h" +#include "twitter_lib.h" +#include <ctype.h> +#include <errno.h> + +/* GLib < 2.12.0 doesn't have g_ascii_strtoll(), work around using system strtoll(). */ +/* GLib < 2.12.4 can be buggy: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=488013 */ +#if !GLIB_CHECK_VERSION(2,12,5) +#include <stdlib.h> +#include <limits.h> +#define g_ascii_strtoll strtoll +#endif + +#define TXL_STATUS 1 +#define TXL_USER 2 +#define TXL_ID 3 + +struct twitter_xml_list { + int type; + gint64 next_cursor; + GSList *list; + gpointer data; +}; + +struct twitter_xml_user { + char *name; + char *screen_name; +}; + +struct twitter_xml_status { + time_t created_at; + char *text; + struct twitter_xml_user *user; + guint64 id; +}; + +static void twitter_groupchat_init(struct im_connection *ic); + +/** + * Frees a twitter_xml_user struct. + */ +static void txu_free(struct twitter_xml_user *txu) +{ + if (txu == NULL) + return; + g_free(txu->name); + g_free(txu->screen_name); + g_free(txu); +} + + +/** + * Frees a twitter_xml_status struct. + */ +static void txs_free(struct twitter_xml_status *txs) +{ + g_free(txs->text); + txu_free(txs->user); + g_free(txs); +} + +/** + * Free a twitter_xml_list struct. + * type is the type of list the struct holds. + */ +static void txl_free(struct twitter_xml_list *txl) +{ + GSList *l; + if (txl == NULL) + return; + for ( l = txl->list; l ; l = g_slist_next(l) ) + if (txl->type == TXL_STATUS) + txs_free((struct twitter_xml_status *)l->data); + else if (txl->type == TXL_ID) + g_free(l->data); + g_slist_free(txl->list); +} + +/** + * Add a buddy if it is not allready added, set the status to logged in. + */ +static void twitter_add_buddy(struct im_connection *ic, char *name, const char *fullname) +{ + struct twitter_data *td = ic->proto_data; + + // Check if the buddy is allready 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) + { + /* Necessary so that nicks always get translated to the + exact Twitter username. */ + imcb_buddy_nick_hint( ic, name, name ); + imcb_chat_add_buddy( td->home_timeline_gc, name ); + } + else if (g_strcasecmp(mode, "many") == 0) + imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL ); + } +} + +/* Warning: May return a malloc()ed value, which will be free()d on the next + call. Only for short-term use. */ +static char *twitter_parse_error(struct http_request *req) +{ + static char *ret = NULL; + struct xt_parser *xp = NULL; + struct xt_node *node; + + g_free(ret); + ret = NULL; + + if (req->body_size > 0) + { + xp = xt_new(NULL, NULL); + xt_feed(xp, req->reply_body, req->body_size); + + if ((node = xt_find_node(xp->root, "hash")) && + (node = xt_find_node(node->children, "error")) && + node->text_len > 0) + { + ret = g_strdup_printf("%s (%s)", req->status_string, node->text); + xt_free(xp); + return ret; + } + + xt_free(xp); + } + + return req->status_string; +} + +static void twitter_http_get_friends_ids(struct http_request *req); + +/** + * Get the friends ids. + */ +void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor) +{ + // Primitive, but hey! It works... + char* args[2]; + args[0] = "cursor"; + args[1] = g_strdup_printf ("%lld", (long long) next_cursor); + twitter_http(ic, TWITTER_FRIENDS_IDS_URL, twitter_http_get_friends_ids, ic, 0, args, 2); + + 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 ) +{ + struct xt_node *child; + + // Set the list type. + txl->type = TXL_ID; + + // The root <statuses> node should hold the list of statuses <status> + // Walk over the nodes children. + for( child = node->children ; child ; child = child->next ) + { + if ( g_strcasecmp( "id", child->name ) == 0) + { + // Add the item to the list. + txl->list = g_slist_append (txl->list, g_memdup( child->text, child->text_len + 1 )); + } + else if ( g_strcasecmp( "next_cursor", child->name ) == 0) + { + twitter_xt_next_cursor(child, txl); + } + } + + return XT_HANDLED; +} + +/** + * Callback for getting the friends ids. + */ +static void twitter_http_get_friends_ids(struct http_request *req) +{ + struct im_connection *ic; + struct xt_parser *parser; + struct twitter_xml_list *txl; + struct twitter_data *td; + + ic = req->data; + + // Check if the connection is still active. + if( !g_slist_find( twitter_connections, ic ) ) + return; + + td = ic->proto_data; + + // Check if the HTTP request went well. + if (req->status_code != 200) { + // It didn't go well, output the error and return. + if (++td->http_fails >= 5) + imcb_error(ic, "Could not retrieve friends: %s", twitter_parse_error(req)); + + return; + } else { + td->http_fails = 0; + } + + txl = g_new0(struct twitter_xml_list, 1); + + // Parse the data. + parser = xt_new( NULL, txl ); + xt_feed( parser, req->reply_body, req->body_size ); + twitter_xt_get_friends_id_list(parser->root, txl); + xt_free( parser ); + + if (txl->next_cursor) + twitter_get_friends_ids(ic, txl->next_cursor); + + txl_free(txl); + g_free(txl); +} + +/** + * 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 <user>s from the <users> element. + */ +static xt_status twitter_xt_get_users( struct xt_node *node, struct twitter_xml_list *txl ) +{ + struct twitter_xml_user *txu; + struct xt_node *child; + + // Set the type of the list. + txl->type = TXL_USER; + + // The root <users> node should hold the list of users <user> + // 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); + } + } + + return XT_HANDLED; +} + +/** + * Function to fill a twitter_xml_list struct. + * It calls twitter_xt_get_users to get the <user>s from a <users> element. + * It sets: + * - the next_cursor. + */ +static xt_status twitter_xt_get_user_list( struct xt_node *node, struct twitter_xml_list *txl ) +{ + struct xt_node *child; + + // Set the type of the list. + txl->type = TXL_USER; + + // The root <user_list> node should hold a users <users> element + // Walk over the nodes children. + for( child = node->children ; child ; child = child->next ) + { + if ( g_strcasecmp( "users", child->name ) == 0) + { + twitter_xt_get_users(child, txl); + } + else if ( g_strcasecmp( "next_cursor", child->name ) == 0) + { + twitter_xt_next_cursor(child, txl); + } + } + + return XT_HANDLED; +} + + +/** + * Function to fill a twitter_xml_status struct. + * It sets: + * - the status text and + * - the created_at timestamp and + * - 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 ) +{ + struct xt_node *child, *rt; + gboolean truncated = FALSE; + + // 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( "truncated", child->name ) == 0 && child->text) + { + truncated = bool2int(child->text); + } + else if (g_strcasecmp( "retweeted_status", child->name ) == 0) + { + rt = child; + } + else if (g_strcasecmp( "created_at", child->name ) == 0) + { + 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, "%a %b %d %H:%M:%S %z %Y", &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); + } + } + + /* If it's a truncated retweet, get the original because dots suck. */ + if (truncated && rt) + { + struct twitter_xml_status *rtxs = g_new0(struct twitter_xml_status, 1); + if (twitter_xt_get_status(rt, rtxs) != XT_HANDLED) + { + txs_free(rtxs); + return XT_HANDLED; + } + + g_free(txs->text); + txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text); + txs_free(rtxs); + } + + return XT_HANDLED; +} + +/** + * Function to fill a twitter_xml_list struct. + * It sets: + * - all <status>es within the <status> element and + * - the next_cursor. + */ +static xt_status twitter_xt_get_status_list( struct im_connection *ic, struct xt_node *node, struct twitter_xml_list *txl ) +{ + struct twitter_xml_status *txs; + struct xt_node *child; + bee_user_t *bu; + + // Set the type of the list. + txl->type = TXL_STATUS; + + // The root <statuses> node should hold the list of statuses <status> + // 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; + } + } + } + else if ( g_strcasecmp( "next_cursor", child->name ) == 0) + { + twitter_xt_next_cursor(child, txl); + } + } + + return XT_HANDLED; +} + +static void twitter_http_get_home_timeline(struct http_request *req); + +/** + * Get the timeline. + */ +void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor) +{ + struct twitter_data *td = ic->proto_data; + + char* args[4]; + args[0] = "cursor"; + args[1] = g_strdup_printf ("%lld", (long long) next_cursor); + if (td->home_timeline_id) { + args[2] = "since_id"; + args[3] = g_strdup_printf ("%llu", (long long unsigned int) td->home_timeline_id); + } + + twitter_http(ic, TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, args, td->home_timeline_id ? 4 : 2); + + g_free(args[1]); + if (td->home_timeline_id) { + g_free(args[3]); + } +} + +static void twitter_groupchat_init(struct im_connection *ic) +{ + char *name_hint; + struct groupchat *gc; + struct twitter_data *td = ic->proto_data; + + td->home_timeline_gc = gc = imcb_chat_new( ic, "home/timeline" ); + + name_hint = g_strdup_printf( "%s_%s", td->prefix, ic->acc->user ); + imcb_chat_name_hint( gc, name_hint ); + g_free( name_hint ); +} + +/** + * Function that is called to see the statuses in a groupchat window. + */ +static void twitter_groupchat(struct im_connection *ic, GSList *list) +{ + struct twitter_data *td = ic->proto_data; + GSList *l = NULL; + struct twitter_xml_status *status; + struct groupchat *gc; + + // Create a new groupchat if it does not exsist. + if (!td->home_timeline_gc) + twitter_groupchat_init(ic); + + gc = td->home_timeline_gc; + if (!gc->joined) + imcb_chat_add_buddy( gc, ic->acc->user ); + + for ( l = list; l ; l = g_slist_next(l) ) + { + status = l->data; + if (status->user == NULL || status->text == NULL) + continue; + + twitter_add_buddy(ic, status->user->screen_name, status->user->name); + + strip_html(status->text); + + // Say it! + if (g_strcasecmp(td->user, status->user->screen_name) == 0) + imcb_chat_log (gc, "Your Tweet: %s", status->text); + else + imcb_chat_msg (gc, status->user->screen_name, status->text, 0, status->created_at ); + + // Update the home_timeline_id to hold the highest id, so that by the next request + // we won't pick up the updates allready in the list. + td->home_timeline_id = td->home_timeline_id < status->id ? status->id : td->home_timeline_id; + } +} + +/** + * Function that is called to see statuses as private messages. + */ +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; + + mode_one = g_strcasecmp( set_getstr( &ic->acc->set, "mode" ), "one" ) == 0; + + if( 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 *text = NULL; + + status = l->data; + + strip_html( status->text ); + if( mode_one ) + text = g_strdup_printf( "\002<\002%s\002>\002 %s", + status->user->screen_name, status->text ); + else + twitter_add_buddy(ic, status->user->screen_name, status->user->name); + + imcb_buddy_msg( ic, + mode_one ? from : status->user->screen_name, + mode_one ? text : status->text, + 0, status->created_at ); + + // Update the home_timeline_id to hold the highest id, so that by the next request + // we won't pick up the updates allready in the list. + td->home_timeline_id = td->home_timeline_id < status->id ? status->id : td->home_timeline_id; + + g_free( text ); + } +} + +/** + * Callback for getting the home timeline. + */ +static void twitter_http_get_home_timeline(struct http_request *req) +{ + struct im_connection *ic = req->data; + struct twitter_data *td; + struct xt_parser *parser; + struct twitter_xml_list *txl; + + // Check if the connection is still active. + if( !g_slist_find( twitter_connections, ic ) ) + return; + + td = ic->proto_data; + + // Check if the HTTP request went well. + if (req->status_code == 200) + { + td->http_fails = 0; + if (!(ic->flags & OPT_LOGGED_IN)) + imcb_connected(ic); + } + else if (req->status_code == 401) + { + imcb_error( ic, "Authentication failure" ); + imc_logout( ic, FALSE ); + return; + } + else + { + // It didn't go well, output the error and return. + if (++td->http_fails >= 5) + imcb_error(ic, "Could not retrieve " TWITTER_HOME_TIMELINE_URL ": %s", twitter_parse_error(req)); + + return; + } + + txl = g_new0(struct twitter_xml_list, 1); + txl->list = NULL; + + // Parse the data. + parser = xt_new( NULL, txl ); + xt_feed( parser, req->reply_body, req->body_size ); + // The root <statuses> node should hold the list of statuses <status> + twitter_xt_get_status_list(ic, parser->root, txl); + xt_free( parser ); + + // 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) + twitter_groupchat(ic, txl->list); + else + twitter_private_message_chat(ic, txl->list); + + // Free the structure. + txl_free(txl); + g_free(txl); +} + +/** + * Callback for getting (twitter)friends... + * + * Be afraid, be very afraid! This function will potentially add hundreds of "friends". "Who has + * hundreds of friends?" you wonder? You probably not, since you are reading the source of + * BitlBee... Get a life and meet new people! + */ +static void twitter_http_get_statuses_friends(struct http_request *req) +{ + struct im_connection *ic = req->data; + struct twitter_data *td; + struct xt_parser *parser; + struct twitter_xml_list *txl; + GSList *l = NULL; + struct twitter_xml_user *user; + + // Check if the connection is still active. + if( !g_slist_find( twitter_connections, ic ) ) + return; + + td = ic->proto_data; + + // Check if the HTTP request went well. + if (req->status_code == 401) + { + imcb_error( ic, "Authentication failure" ); + imc_logout( ic, FALSE ); + return; + } else if (req->status_code != 200) { + // It didn't go well, output the error and return. + imcb_error(ic, "Could not retrieve " TWITTER_SHOW_FRIENDS_URL ": %s", twitter_parse_error(req)); + imc_logout( ic, TRUE ); + return; + } else { + td->http_fails = 0; + } + + if( !td->home_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 = NULL; + + // Parse the data. + parser = xt_new( NULL, txl ); + xt_feed( parser, req->reply_body, req->body_size ); + + // Get the user list from the parsed xml feed. + twitter_xt_get_user_list(parser->root, txl); + xt_free( parser ); + + // Add the users as buddies. + for ( l = txl->list; l ; l = g_slist_next(l) ) + { + user = l->data; + twitter_add_buddy(ic, user->screen_name, user->name); + } + + // if the next_cursor is set to something bigger then 0 there are more friends to gather. + if (txl->next_cursor > 0) + { + twitter_get_statuses_friends(ic, txl->next_cursor); + } + else + { + td->flags |= TWITTER_HAVE_FRIENDS; + twitter_login_finish(ic); + } + + // Free the structure. + txl_free(txl); + g_free(txl); +} + +/** + * Get the friends. + */ +void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor) +{ + char* args[2]; + args[0] = "cursor"; + args[1] = g_strdup_printf ("%lld", (long long) next_cursor); + + twitter_http(ic, TWITTER_SHOW_FRIENDS_URL, twitter_http_get_statuses_friends, ic, 0, args, 2); + + g_free(args[1]); +} + +/** + * Callback to use after sending a post request to twitter. + */ +static void twitter_http_post(struct http_request *req) +{ + struct im_connection *ic = req->data; + struct twitter_data *td; + + // Check if the connection is still active. + if( !g_slist_find( twitter_connections, ic ) ) + return; + + td = ic->proto_data; + td->last_status_id = 0; + + // Check if the HTTP request went well. + if (req->status_code != 200) { + // It didn't go well, output the error and return. + imcb_error(ic, "HTTP error: %s", twitter_parse_error(req)); + return; + } + + if (req->body_size > 0) + { + struct xt_parser *xp = NULL; + struct xt_node *node; + + xp = xt_new(NULL, NULL); + xt_feed(xp, req->reply_body, req->body_size); + + if ((node = xt_find_node(xp->root, "status")) && + (node = xt_find_node(node->children, "id")) && node->text) + td->last_status_id = g_ascii_strtoull( node->text, NULL, 10 ); + + xt_free(xp); + } +} + +/** + * Function to POST a new status to twitter. + */ +void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to) +{ + char* args[4] = { + "status", msg, + "in_reply_to_status_id", + g_strdup_printf("%llu", (unsigned long long) in_reply_to) + }; + twitter_http(ic, TWITTER_STATUS_UPDATE_URL, twitter_http_post, ic, 1, + args, in_reply_to ? 4 : 2); + g_free(args[3]); +} + + +/** + * Function to POST a new message to twitter. + */ +void twitter_direct_messages_new(struct im_connection *ic, char *who, char *msg) +{ + char* args[4]; + args[0] = "screen_name"; + args[1] = who; + args[2] = "text"; + args[3] = msg; + // Use the same callback as for twitter_post_status, since it does basically the same. + twitter_http(ic, TWITTER_DIRECT_MESSAGES_NEW_URL, twitter_http_post, ic, 1, args, 4); +// g_free(args[1]); +// g_free(args[3]); +} + +void twitter_friendships_create_destroy(struct im_connection *ic, char *who, int create) +{ + char* args[2]; + args[0] = "screen_name"; + args[1] = who; + twitter_http(ic, create ? TWITTER_FRIENDSHIPS_CREATE_URL : TWITTER_FRIENDSHIPS_DESTROY_URL, twitter_http_post, ic, 1, args, 2); +} + +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"); + twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0); + g_free(url); +} + +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"); + 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 new file mode 100644 index 00000000..24b4a089 --- /dev/null +++ b/protocols/twitter/twitter_lib.h @@ -0,0 +1,91 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Simple module to facilitate twitter functionality. * +* * +* Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com> * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Lesser General Public * +* License as published by the Free Software Foundation, version * +* 2.1. * +* * +* This library is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * +* Lesser General Public License for more details. * +* * +* You should have received a copy of the GNU Lesser General Public License * +* along with this library; if not, write to the Free Software Foundation, * +* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * +* * +****************************************************************************/ + + +#ifndef _TWITTER_LIB_H +#define _TWITTER_LIB_H + +#include "nogaim.h" +#include "twitter_http.h" + +#define TWITTER_API_URL "http://twitter.com" +#define IDENTICA_API_URL "http://identi.ca/api" + +/* Status URLs */ +#define TWITTER_STATUS_UPDATE_URL "/statuses/update.xml" +#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" + +/* Users URLs */ +#define TWITTER_SHOW_USERS_URL "/users/show.xml" +#define TWITTER_SHOW_FRIENDS_URL "/statuses/friends.xml" +#define TWITTER_SHOW_FOLLOWERS_URL "/statuses/followers.xml" + +/* 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_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" + +/* Social graphs URLs */ +#define TWITTER_FRIENDS_IDS_URL "/friends/ids.xml" +#define TWITTER_FOLLOWERS_IDS_URL "/followers/ids.xml" + +/* Account URLs */ +#define TWITTER_ACCOUNT_RATE_LIMIT_URL "/account/rate_limit_status.xml" + +/* Favorites URLs */ +#define TWITTER_FAVORITES_GET_URL "/favorites.xml" +#define TWITTER_FAVORITE_CREATE_URL "/favorites/create/" +#define TWITTER_FAVORITE_DESTROY_URL "/favorites/destroy/" + +/* Block URLs */ +#define TWITTER_BLOCKS_CREATE_URL "/blocks/create/" +#define TWITTER_BLOCKS_DESTROY_URL "/blocks/destroy/" + +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_statuses_friends(struct im_connection *ic, gint64 next_cursor); + +void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to); +void twitter_direct_messages_new(struct im_connection *ic, char *who, char *message); +void twitter_friendships_create_destroy(struct im_connection *ic, char *who, int create); +void twitter_status_destroy(struct im_connection *ic, guint64 id); +void twitter_status_retweet(struct im_connection *ic, guint64 id); + +#endif //_TWITTER_LIB_H + |