diff options
author | Wilmer van der Gaast <wilmer@gaast.net> | 2010-05-03 01:52:08 +0100 |
---|---|---|
committer | Wilmer van der Gaast <wilmer@gaast.net> | 2010-05-03 01:52:08 +0100 |
commit | f92456381b2a1487036848fc4e0105b8a439facd (patch) | |
tree | 38dd931c315ad03dafcd4af8f374c48ad45ff553 | |
parent | 6a9d068e73b6d08056302fdc85dd706a2dd647a5 (diff) | |
parent | 6824fb355a31ed46c22740ef184332440d3981ce (diff) |
Mainline merge.
-rw-r--r-- | bitlbee.c | 76 | ||||
-rw-r--r-- | bitlbee.h | 2 | ||||
-rwxr-xr-x | debian/bitlbee.init | 4 | ||||
-rw-r--r-- | debian/changelog | 9 | ||||
-rwxr-xr-x | debian/rules | 2 | ||||
-rw-r--r-- | doc/CHANGES | 15 | ||||
-rw-r--r-- | doc/user-guide/commands.xml | 87 | ||||
-rw-r--r-- | lib/Makefile | 2 | ||||
-rw-r--r-- | lib/misc.c | 3 | ||||
-rw-r--r-- | lib/oauth.c | 453 | ||||
-rw-r--r-- | lib/oauth.h | 90 | ||||
-rw-r--r-- | lib/url.c | 2 | ||||
-rw-r--r-- | lib/url.h | 2 | ||||
-rw-r--r-- | protocols/msn/msn.c | 1 | ||||
-rw-r--r-- | protocols/msn/msn.h | 8 | ||||
-rw-r--r-- | protocols/msn/ns.c | 10 | ||||
-rw-r--r-- | protocols/msn/sb.c | 43 | ||||
-rw-r--r-- | protocols/twitter/twitter.c | 160 | ||||
-rw-r--r-- | protocols/twitter/twitter.h | 1 | ||||
-rw-r--r-- | protocols/twitter/twitter_http.c | 20 | ||||
-rw-r--r-- | protocols/twitter/twitter_http.h | 4 | ||||
-rw-r--r-- | protocols/twitter/twitter_lib.c | 49 |
22 files changed, 953 insertions, 90 deletions
@@ -35,6 +35,43 @@ static gboolean bitlbee_io_new_client( gpointer data, gint fd, b_input_condition condition ); +static gboolean try_listen( struct addrinfo *res ) +{ + int i; + + global.listen_socket = socket( res->ai_family, res->ai_socktype, res->ai_protocol ); + if( global.listen_socket < 0 ) + { + log_error( "socket" ); + return FALSE; + } + +#ifdef IPV6_V6ONLY + if( res->ai_family == AF_INET6 ) + { + i = 0; + setsockopt( global.listen_socket, IPPROTO_IPV6, IPV6_V6ONLY, + (char *) &i, sizeof( i ) ); + } +#endif + + /* TIME_WAIT (?) sucks.. */ + i = 1; + setsockopt( global.listen_socket, SOL_SOCKET, SO_REUSEADDR, &i, sizeof( i ) ); + + i = bind( global.listen_socket, res->ai_addr, res->ai_addrlen ); + if( i == -1 ) + { + closesocket( global.listen_socket ); + global.listen_socket = -1; + + log_error( "bind" ); + return FALSE; + } + + return TRUE; +} + int bitlbee_daemon_init() { struct addrinfo *res, hints, *addrinfo_bind; @@ -62,35 +99,18 @@ int bitlbee_daemon_init() } global.listen_socket = -1; - + + /* Try IPv6 first (which will become an IPv6+IPv4 socket). */ for( res = addrinfo_bind; res; res = res->ai_next ) - { - global.listen_socket = socket( res->ai_family, res->ai_socktype, res->ai_protocol ); - if( global.listen_socket < 0 ) - continue; - -#ifdef IPV6_V6ONLY - if( res->ai_family == AF_INET6 ) - { - i = 0; - setsockopt( global.listen_socket, IPPROTO_IPV6, IPV6_V6ONLY, - (char *) &i, sizeof( i ) ); - } -#endif - - /* TIME_WAIT (?) sucks.. */ - i = 1; - setsockopt( global.listen_socket, SOL_SOCKET, SO_REUSEADDR, &i, sizeof( i ) ); - - i = bind( global.listen_socket, res->ai_addr, res->ai_addrlen ); - if( i == -1 ) - { - log_error( "bind" ); - return( -1 ); - } - break; - } - + if( res->ai_family == AF_INET6 && try_listen( res ) ) + break; + + /* The rest (so IPv4, I guess). */ + if( res == NULL ) + for( res = addrinfo_bind; res; res = res->ai_next ) + if( res->ai_family != AF_INET6 && try_listen( res ) ) + break; + freeaddrinfo( addrinfo_bind ); i = listen( global.listen_socket, 10 ); @@ -34,7 +34,7 @@ #define _WIN32_WINNT 0x0501 #define PACKAGE "BitlBee" -#define BITLBEE_VERSION "1.2.6" +#define BITLBEE_VERSION "1.2.6a" #define VERSION BITLBEE_VERSION #define BITLBEE_VER(a,b,c) (((a) << 16) + ((b) << 8) + (c)) #define BITLBEE_VERSION_CODE BITLBEE_VER(1, 2, 6) diff --git a/debian/bitlbee.init b/debian/bitlbee.init index 4c224ffc..be1dcd66 100755 --- a/debian/bitlbee.init +++ b/debian/bitlbee.init @@ -37,8 +37,8 @@ fi # d_start() { # Make sure BitlBee can actually write its PID... - touch /var/run/bitlbee.pid - chown bitlbee: /var/run/bitlbee.pid + touch $PIDFILE + chown bitlbee: $PIDFILE start-stop-daemon --start --quiet --pidfile $PIDFILE \ --exec $DAEMON -- -p $BITLBEE_PORT -P $PIDFILE $BITLBEE_OPTS diff --git a/debian/changelog b/debian/changelog index f969b410..56d0a551 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +bitlbee (1.2.6a-1) unstable; urgency=low + + * New upstream version. + * Native support for Twitter. + * Fixed /WHOIS response format. (Closes: #576120) + * Problems with bitlbee-skype are solved by now. (Closes: #575572) + + -- Wilmer van der Gaast <wilmer@peer.gaast.net> Tue, 20 Apr 2010 00:34:51 +0200 + bitlbee (1.2.5-1) unstable; urgency=low * New upstream version. diff --git a/debian/rules b/debian/rules index 788e5006..0c656fd8 100755 --- a/debian/rules +++ b/debian/rules @@ -98,7 +98,7 @@ binary-indep: install-indep cd debian/bitlbee-dev; \ find usr -type f -exec md5sum {} \; > DEBIAN/md5sums - dpkg-gencontrol -ldebian/changelog -isp -pbitlbee-dev -Pdebian/bitlbee-dev + dpkg-gencontrol -ldebian/changelog -isp -pbitlbee-dev -Pdebian/bitlbee-dev -v1:$(BITLBEE_VERSION)-0 dpkg --build debian/bitlbee-dev .. diff --git a/doc/CHANGES b/doc/CHANGES index 62d2d4df..45b16e28 100644 --- a/doc/CHANGES +++ b/doc/CHANGES @@ -3,8 +3,16 @@ found in the bzr commit logs, for example you can try: http://bugs.bitlbee.org/bitlbee/timeline?daysback=90&changeset=on +Version 1.2.6a: +- Fixed a typo that renders the Twitter groupchat mode unusable. A last- + minute change that came a few minutes late. + +Finished 19 Apr 2010 + Version 1.2.6: -- Native (very basic) support for Twitter. +- Native (very basic) support for Twitter, implemented by Geert Mulders. + Currently supported are posting tweets, reading the ones of people you + follow, and sending (not yet receiving!) direct messages. - Fixed format of status messages in /WHOIS to improve IRC client compatibility. - Show timestamps of offline messages/channel backlogs. @@ -15,9 +23,12 @@ Version 1.2.6: - Better handling of XMPP contacts with multiple resources on-line. Default behaviour now is to write to wherever the last message came from, or to the bare JID (usually becomes a broadcast) if there wasn't any recent msg. +- Added a switchboard_keepalives setting which should solve some issues with + talking to offline MSN contacts. (Although full support for offline + messages is not ready yet!) - The usual misc. bug fixes. -Finished ... +Finished 19 Apr 2010 Version 1.2.5: - Many bug fixes, including a fix for MSN login issues, Jabber login timing diff --git a/doc/user-guide/commands.xml b/doc/user-guide/commands.xml index 2949c6ce..e681429c 100644 --- a/doc/user-guide/commands.xml +++ b/doc/user-guide/commands.xml @@ -20,7 +20,7 @@ <description> <para> - Adds an account on the given server with the specified protocol, username and password to the account list. Supported protocols right now are: Jabber, MSN, OSCAR (AIM/ICQ) and Yahoo. For more information about adding an account, see <emphasis>help account add <protocol></emphasis>. + Adds an account on the given server with the specified protocol, username and password to the account list. Supported protocols right now are: Jabber, MSN, OSCAR (AIM/ICQ), Yahoo and Twitter. For more information about adding an account, see <emphasis>help account add <protocol></emphasis>. </para> </description> @@ -72,11 +72,15 @@ </para> <para> - By default all your Twitter contacts will show up in your contact list and their tweets will show up as private messages or in &bitlbee depending on your settings. If you want them in a separate channel, use the use_groupchat setting (see <emphasis>help set use_groupchat</emphasis>). + By default all your Twitter contacts will come from a contact called twitter_(yourusername). You can change this behaviour using the <emphasis>mode</emphasis> setting (see <emphasis>help set mode</emphasis>). </para> <para> - To send tweets yourself, send them to any of your Twitter contacts via /query (doesn't matter who), or just write in the groupchat channel if you enabled that option. + To send tweets yourself, send them to the twitter_(yourusername) contact, or just write in the groupchat channel if you enabled that option. + </para> + + <para> + Since Twitter now requires OAuth authentication, you should not enter your Twitter password into BitlBee. Just type a bogus password. The first time you log in, BitlBee will start OAuth authentication. (See <emphasis>help set oauth</emphasis>.) </para> </description> </bitlbee-command> @@ -658,6 +662,26 @@ </bitlbee-setting> + <bitlbee-setting name="mode" type="string" scope="account"> + <possible-values>one, many, chat</possible-values> + <default>one</default> + + <description> + <para> + By default, everything from the Twitter module will come from one nick, twitter_(yourusername). If you prefer to have individual nicks for everyone, you can set this setting to "many" instead. + </para> + + <para> + If you prefer to have all your Twitter things in a separate channel, you can set this setting to "chat". + </para> + + <para> + In the last two modes, you can send direct messages by /msg'ing your contacts directly. Note, however, that incoming DMs are not fetched yet. + </para> + </description> + + </bitlbee-setting> + <bitlbee-setting name="nick" type="string" scope="chat"> <description> @@ -682,6 +706,25 @@ </description> </bitlbee-setting> + <bitlbee-setting name="oauth" type="boolean" scope="account"> + <default>true</default> + + <description> + <para> + This enables OAuth authentication for Twitter accounts. From June 2010 this will be mandatory. + </para> + + <para> + With OAuth enabled, you shouldn't tell BitlBee your Twitter password. Just add your account with a bogus password and type <emphasis>account on</emphasis>. BitlBee will then give you a URL to authenticate with Twitter. If this succeeds, Twitter will return a PIN code which you can give back to BitlBee to finish the process. + </para> + + <para> + The resulting access token will be saved permanently, so you have to do this only once. + </para> + </description> + + </bitlbee-setting> + <bitlbee-setting name="ops" type="string" scope="global"> <default>both</default> <possible-values>both, root, user, none</possible-values> @@ -817,6 +860,16 @@ </description> </bitlbee-setting> + <bitlbee-setting name="show_offline" type="boolean" scope="global"> + <default>false</default> + + <description> + <para> + If enabled causes BitlBee to also show offline users in Channel. Online-users will get op, away-users voice and offline users none of both. This option takes effect as soon as you reconnect. + </para> + </description> + </bitlbee-setting> + <bitlbee-setting name="simulate_netsplit" type="boolean" scope="global"> <default>true</default> @@ -866,6 +919,24 @@ </description> </bitlbee-setting> + <bitlbee-setting name="switchboard_keepalives" type="boolean" scope="account"> + <default>false</default> + + <description> + <para> + Turn on this flag if you have difficulties talking to offline/invisible contacts. + </para> + + <para> + With this setting enabled, BitlBee will send keepalives to MSN switchboards with offline/invisible contacts every twenty seconds. This should keep the server and client on the other side from shutting it down. + </para> + + <para> + This is useful because BitlBee doesn't support MSN offline messages yet and the MSN servers won't let the user reopen switchboards to offline users. Once offline messaging is supported, this flag might be removed. + </para> + </description> + </bitlbee-setting> + <bitlbee-setting name="timezone" type="string" scope="global"> <default>local</default> <possible-values>local, utc, gmt, timezone-spec</possible-values> @@ -919,16 +990,6 @@ </description> </bitlbee-setting> - <bitlbee-setting name="use_groupchat" type="boolean" scope="account"> - <default>false</default> - - <description> - <para> - By default the Twitter module shows all Twitter contacts and their Tweet in &bitlbee and/or private messages. With this setting enabled the module will show all contacts and their Tweets in a separate channel. - </para> - </description> - </bitlbee-setting> - <bitlbee-setting name="web_aware" type="string" scope="account"> <default>false</default> diff --git a/lib/Makefile b/lib/Makefile index 3d128b5a..b686f886 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -9,7 +9,7 @@ -include ../Makefile.settings # [SH] Program variables -objects = arc.o base64.o $(EVENT_HANDLER) http_client.o ini.o md5.o misc.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o ftutil.o +objects = arc.o base64.o $(EVENT_HANDLER) ftutil.o http_client.o ini.o md5.o misc.o oauth.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o CFLAGS += -Wall LFLAGS += -r @@ -305,8 +305,7 @@ void http_encode( char *s ) for( i = j = 0; t[i]; i ++, j ++ ) { - /* if( t[i] <= ' ' || ((unsigned char *)t)[i] >= 128 || t[i] == '%' ) */ - if( !isalnum( t[i] ) ) + if( !isalnum( t[i] ) && !strchr( "._-~", t[i] ) ) { sprintf( s + j, "%%%02X", ((unsigned char*)t)[i] ); j += 2; diff --git a/lib/oauth.c b/lib/oauth.c new file mode 100644 index 00000000..c60a5a52 --- /dev/null +++ b/lib/oauth.c @@ -0,0 +1,453 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Simple OAuth client (consumer) implementation. * +* * +* Copyright 2010 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* 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 <glib.h> +#include <gmodule.h> +#include <stdlib.h> +#include <string.h> +#include "http_client.h" +#include "base64.h" +#include "misc.h" +#include "sha1.h" +#include "url.h" +#include "oauth.h" + +#define HMAC_BLOCK_SIZE 64 + +static char *oauth_sign( const char *method, const char *url, + const char *params, struct oauth_info *oi ) +{ + sha1_state_t sha1; + uint8_t hash[sha1_hash_size]; + uint8_t key[HMAC_BLOCK_SIZE+1]; + char *s; + int i; + + /* Create K. If our current key is >64 chars we have to hash it, + otherwise just pad. */ + memset( key, 0, HMAC_BLOCK_SIZE ); + i = strlen( oi->sp->consumer_secret ) + 1 + ( oi->token_secret ? strlen( oi->token_secret ) : 0 ); + if( i > HMAC_BLOCK_SIZE ) + { + sha1_init( &sha1 ); + sha1_append( &sha1, (uint8_t*) oi->sp->consumer_secret, strlen( oi->sp->consumer_secret ) ); + sha1_append( &sha1, (uint8_t*) "&", 1 ); + if( oi->token_secret ) + sha1_append( &sha1, (uint8_t*) oi->token_secret, strlen( oi->token_secret ) ); + sha1_finish( &sha1, key ); + } + else + { + g_snprintf( (gchar*) key, HMAC_BLOCK_SIZE + 1, "%s&%s", + oi->sp->consumer_secret, oi->token_secret ? : "" ); + } + + /* Inner part: H(K XOR 0x36, text) */ + sha1_init( &sha1 ); + + for( i = 0; i < HMAC_BLOCK_SIZE; i ++ ) + key[i] ^= 0x36; + sha1_append( &sha1, key, HMAC_BLOCK_SIZE ); + + /* OAuth: text = method&url¶ms, all http_encoded. */ + sha1_append( &sha1, (const uint8_t*) method, strlen( method ) ); + sha1_append( &sha1, (const uint8_t*) "&", 1 ); + + s = g_new0( char, strlen( url ) * 3 + 1 ); + strcpy( s, url ); + http_encode( s ); + sha1_append( &sha1, (const uint8_t*) s, strlen( s ) ); + sha1_append( &sha1, (const uint8_t*) "&", 1 ); + g_free( s ); + + s = g_new0( char, strlen( params ) * 3 + 1 ); + strcpy( s, params ); + http_encode( s ); + sha1_append( &sha1, (const uint8_t*) s, strlen( s ) ); + g_free( s ); + + sha1_finish( &sha1, hash ); + + /* Final result: H(K XOR 0x5C, inner stuff) */ + sha1_init( &sha1 ); + for( i = 0; i < HMAC_BLOCK_SIZE; i ++ ) + key[i] ^= 0x36 ^ 0x5c; + sha1_append( &sha1, key, HMAC_BLOCK_SIZE ); + sha1_append( &sha1, hash, sha1_hash_size ); + sha1_finish( &sha1, hash ); + + /* base64_encode + HTTP escape it (both consumers + need it that away) and we're done. */ + s = base64_encode( hash, sha1_hash_size ); + s = g_realloc( s, strlen( s ) * 3 + 1 ); + http_encode( s ); + + return s; +} + +static char *oauth_nonce() +{ + unsigned char bytes[9]; + + random_bytes( bytes, sizeof( bytes ) ); + return base64_encode( bytes, sizeof( bytes ) ); +} + +void oauth_params_add( GSList **params, const char *key, const char *value ) +{ + char *item; + + item = g_strdup_printf( "%s=%s", key, value ); + *params = g_slist_insert_sorted( *params, item, (GCompareFunc) strcmp ); +} + +void oauth_params_del( GSList **params, const char *key ) +{ + int key_len = strlen( key ); + GSList *l, *n; + + for( l = *params; l; l = n ) + { + n = l->next; + + if( strncmp( (char*) l->data, key, key_len ) == 0 && + ((char*)l->data)[key_len] == '=' ) + { + g_free( l->data ); + *params = g_slist_remove( *params, l->data ); + } + } +} + +void oauth_params_set( GSList **params, const char *key, const char *value ) +{ + oauth_params_del( params, key ); + oauth_params_add( params, key, value ); +} + +const char *oauth_params_get( GSList **params, const char *key ) +{ + int key_len = strlen( key ); + GSList *l; + + for( l = *params; l; l = l->next ) + { + if( strncmp( (char*) l->data, key, key_len ) == 0 && + ((char*)l->data)[key_len] == '=' ) + return (const char*) l->data + key_len + 1; + } + + return NULL; +} + +static void oauth_params_parse( GSList **params, char *in ) +{ + char *amp, *eq, *s; + + while( in && *in ) + { + eq = strchr( in, '=' ); + if( !eq ) + break; + + *eq = '\0'; + if( ( amp = strchr( eq + 1, '&' ) ) ) + *amp = '\0'; + + s = g_strdup( eq + 1 ); + http_decode( s ); + oauth_params_add( params, in, s ); + g_free( s ); + + *eq = '='; + if( amp == NULL ) + break; + + *amp = '&'; + in = amp + 1; + } +} + +void oauth_params_free( GSList **params ) +{ + while( params && *params ) + { + g_free( (*params)->data ); + *params = g_slist_remove( *params, (*params)->data ); + } +} + +char *oauth_params_string( GSList *params ) +{ + GSList *l; + GString *str = g_string_new( "" ); + + for( l = params; l; l = l->next ) + { + char *s, *eq; + + s = g_malloc( strlen( l->data ) * 3 + 1 ); + strcpy( s, l->data ); + if( ( eq = strchr( s, '=' ) ) ) + http_encode( eq + 1 ); + g_string_append( str, s ); + g_free( s ); + + if( l->next ) + g_string_append_c( str, '&' ); + } + + return g_string_free( str, FALSE ); +} + +void oauth_info_free( struct oauth_info *info ) +{ + if( info ) + { + g_free( info->auth_url ); + g_free( info->request_token ); + g_free( info->token ); + g_free( info->token_secret ); + g_free( info ); + } +} + +static void oauth_add_default_params( GSList **params, const struct oauth_service *sp ) +{ + char *s; + + oauth_params_set( params, "oauth_consumer_key", sp->consumer_key ); + oauth_params_set( params, "oauth_signature_method", "HMAC-SHA1" ); + + s = g_strdup_printf( "%d", (int) time( NULL ) ); + oauth_params_set( params, "oauth_timestamp", s ); + g_free( s ); + + s = oauth_nonce(); + oauth_params_set( params, "oauth_nonce", s ); + g_free( s ); + + oauth_params_set( params, "oauth_version", "1.0" ); +} + +static void *oauth_post_request( const char *url, GSList **params_, http_input_function func, struct oauth_info *oi ) +{ + GSList *params = NULL; + char *s, *params_s, *post; + void *req; + url_t url_p; + + if( !url_set( &url_p, url ) ) + { + oauth_params_free( params_ ); + return NULL; + } + + if( params_ ) + params = *params_; + + oauth_add_default_params( ¶ms, oi->sp ); + + params_s = oauth_params_string( params ); + oauth_params_free( ¶ms ); + + s = oauth_sign( "POST", url, params_s, oi ); + post = g_strdup_printf( "%s&oauth_signature=%s", params_s, s ); + g_free( params_s ); + g_free( s ); + + s = g_strdup_printf( "POST %s HTTP/1.0\r\n" + "Host: %s\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: %zd\r\n" + "\r\n" + "%s", url_p.file, url_p.host, strlen( post ), post ); + g_free( post ); + + req = http_dorequest( url_p.host, url_p.port, url_p.proto == PROTO_HTTPS, + s, func, oi ); + g_free( s ); + + return req; +} + +static void oauth_request_token_done( struct http_request *req ); + +struct oauth_info *oauth_request_token( const struct oauth_service *sp, oauth_cb func, void *data ) +{ + struct oauth_info *st = g_new0( struct oauth_info, 1 ); + GSList *params = NULL; + + st->func = func; + st->data = data; + st->sp = sp; + + oauth_params_add( ¶ms, "oauth_callback", "oob" ); + + if( !oauth_post_request( sp->url_request_token, ¶ms, oauth_request_token_done, st ) ) + { + oauth_info_free( st ); + return NULL; + } + + return st; +} + +static void oauth_request_token_done( struct http_request *req ) +{ + struct oauth_info *st = req->data; + + st->http = req; + + if( req->status_code == 200 ) + { + GSList *params = NULL; + + st->auth_url = g_strdup_printf( "%s?%s", st->sp->url_authorize, req->reply_body ); + oauth_params_parse( ¶ms, req->reply_body ); + st->request_token = g_strdup( oauth_params_get( ¶ms, "oauth_token" ) ); + oauth_params_free( ¶ms ); + } + + st->stage = OAUTH_REQUEST_TOKEN; + st->func( st ); +} + +static void oauth_access_token_done( struct http_request *req ); + +gboolean oauth_access_token( const char *pin, struct oauth_info *st ) +{ + GSList *params = NULL; + + oauth_params_add( ¶ms, "oauth_token", st->request_token ); + oauth_params_add( ¶ms, "oauth_verifier", pin ); + + return oauth_post_request( st->sp->url_access_token, ¶ms, oauth_access_token_done, st ) != NULL; +} + +static void oauth_access_token_done( struct http_request *req ) +{ + struct oauth_info *st = req->data; + + st->http = req; + + if( req->status_code == 200 ) + { + GSList *params = NULL; + + oauth_params_parse( ¶ms, req->reply_body ); + st->token = g_strdup( oauth_params_get( ¶ms, "oauth_token" ) ); + st->token_secret = g_strdup( oauth_params_get( ¶ms, "oauth_token_secret" ) ); + oauth_params_free( ¶ms ); + } + + st->stage = OAUTH_ACCESS_TOKEN; + if( st->func( st ) ) + { + /* Don't need these anymore, but keep the rest. */ + g_free( st->auth_url ); + st->auth_url = NULL; + g_free( st->request_token ); + st->request_token = NULL; + } +} + +char *oauth_http_header( struct oauth_info *oi, const char *method, const char *url, char *args ) +{ + GSList *params = NULL, *l; + char *sig = NULL, *params_s, *s; + GString *ret = NULL; + + oauth_params_add( ¶ms, "oauth_token", oi->token ); + oauth_add_default_params( ¶ms, oi->sp ); + + /* Start building the OAuth header. 'key="value", '... */ + ret = g_string_new( "OAuth " ); + for( l = params; l; l = l->next ) + { + char *kv = l->data; + char *eq = strchr( kv, '=' ); + char esc[strlen(kv)*3+1]; + + if( eq == NULL ) + break; /* WTF */ + + strcpy( esc, eq + 1 ); + http_encode( esc ); + + g_string_append_len( ret, kv, eq - kv + 1 ); + g_string_append_c( ret, '"' ); + g_string_append( ret, esc ); + g_string_append( ret, "\", " ); + } + + /* Now, before generating the signature, add GET/POST arguments to params + since they should be included in the base signature string (but not in + the HTTP header). */ + if( args ) + oauth_params_parse( ¶ms, args ); + if( ( s = strchr( url, '?' ) ) ) + { + s = g_strdup( s + 1 ); + oauth_params_parse( ¶ms, s ); + g_free( s ); + } + + /* Append the signature and we're done! */ + params_s = oauth_params_string( params ); + sig = oauth_sign( method, url, params_s, oi ); + g_string_append_printf( ret, "oauth_signature=\"%s\"", sig ); + g_free( params_s ); + + oauth_params_free( ¶ms ); + g_free( sig ); + + return ret ? g_string_free( ret, FALSE ) : NULL; +} + +char *oauth_to_string( struct oauth_info *oi ) +{ + GSList *params = NULL; + char *ret; + + oauth_params_add( ¶ms, "oauth_token", oi->token ); + oauth_params_add( ¶ms, "oauth_token_secret", oi->token_secret ); + ret = oauth_params_string( params ); + oauth_params_free( ¶ms ); + + return ret; +} + +struct oauth_info *oauth_from_string( char *in, const struct oauth_service *sp ) +{ + struct oauth_info *oi = g_new0( struct oauth_info, 1 ); + GSList *params = NULL; + + oauth_params_parse( ¶ms, in ); + oi->token = g_strdup( oauth_params_get( ¶ms, "oauth_token" ) ); + oi->token_secret = g_strdup( oauth_params_get( ¶ms, "oauth_token_secret" ) ); + oauth_params_free( ¶ms ); + oi->sp = sp; + + return oi; +} diff --git a/lib/oauth.h b/lib/oauth.h new file mode 100644 index 00000000..5dfe0ae5 --- /dev/null +++ b/lib/oauth.h @@ -0,0 +1,90 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Simple OAuth client (consumer) implementation. * +* * +* Copyright 2010 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program 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 General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License along * +* with this program; if not, write to the Free Software Foundation, Inc., * +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * +* * +\***************************************************************************/ + +/* http://oauth.net/core/1.0a/ */ + +struct oauth_info; + +/* Callback function called twice during the access token request process. + Return FALSE if something broke and the process must be aborted. */ +typedef gboolean (*oauth_cb)( struct oauth_info * ); + +typedef enum +{ + OAUTH_INIT, + OAUTH_REQUEST_TOKEN, + OAUTH_ACCESS_TOKEN, +} oauth_stage_t; + +struct oauth_info +{ + oauth_stage_t stage; + const struct oauth_service *sp; + + oauth_cb func; + void *data; + + struct http_request *http; + + char *auth_url; + char *request_token; + + char *token; + char *token_secret; +}; + +struct oauth_service +{ + char *url_request_token; + char *url_access_token; + char *url_authorize; + + char *consumer_key; + char *consumer_secret; +}; + +/* http://oauth.net/core/1.0a/#auth_step1 (section 6.1) + Request an initial anonymous token which can be used to construct an + authorization URL for the user. This is passed to the callback function + in a struct oauth_info. */ +struct oauth_info *oauth_request_token( const struct oauth_service *sp, oauth_cb func, void *data ); + +/* http://oauth.net/core/1.0a/#auth_step3 (section 6.3) + The user gets a PIN or so which we now exchange for the final access + token. This is passed to the callback function in the same + struct oauth_info. */ +gboolean oauth_access_token( const char *pin, struct oauth_info *st ); + +/* http://oauth.net/core/1.0a/#anchor12 (section 7) + Generate an OAuth Authorization: HTTP header. access_token should be + saved/fetched using the functions above. args can be a string with + whatever's going to be in the POST body of the request. GET args will + automatically be grabbed from url. */ +char *oauth_http_header( struct oauth_info *oi, const char *method, const char *url, char *args ); + +/* Shouldn't normally be required unless the process is aborted by the user. */ +void oauth_info_free( struct oauth_info *info ); + +/* Convert to and back from strings, for easier saving. */ +char *oauth_to_string( struct oauth_info *oi ); +struct oauth_info *oauth_from_string( char *in, const struct oauth_service *sp ); @@ -26,7 +26,7 @@ #include "url.h" /* Convert an URL to a url_t structure */ -int url_set( url_t *url, char *set_url ) +int url_set( url_t *url, const char *set_url ) { char s[MAX_STRING+1]; char *i; @@ -41,4 +41,4 @@ typedef struct url char pass[MAX_STRING+1]; } url_t; -int url_set( url_t *url, char *set_url ); +int url_set( url_t *url, const char *set_url ); diff --git a/protocols/msn/msn.c b/protocols/msn/msn.c index c7f56b7f..85dd22ec 100644 --- a/protocols/msn/msn.c +++ b/protocols/msn/msn.c @@ -37,6 +37,7 @@ static void msn_init( account_t *acc ) set_add( &acc->set, "display_name", NULL, set_eval_display_name, acc ); set_add( &acc->set, "local_display_name", "false", set_eval_bool, acc ); set_add( &acc->set, "mail_notifications", "false", set_eval_bool, acc ); + set_add( &acc->set, "switchboard_keepalives", "false", set_eval_bool, acc ); } static void msn_login( account_t *acc ) diff --git a/protocols/msn/msn.h b/protocols/msn/msn.h index 077203c9..f3cb8635 100644 --- a/protocols/msn/msn.h +++ b/protocols/msn/msn.h @@ -30,6 +30,7 @@ */ #define TYPING_NOTIFICATION_MESSAGE "\r\r\rBEWARE, ME R TYPINK MESSAGE!!!!\r\r\r" #define GROUPCHAT_SWITCHBOARD_MESSAGE "\r\r\rME WANT TALK TO MANY PEOPLE\r\r\r" +#define SB_KEEPALIVE_MESSAGE "\r\r\rDONT HANG UP ON ME!\r\r\r" #ifdef DEBUG_MSN #define debug( text... ) imcb_log( ic, text ); @@ -53,6 +54,10 @@ "TypingUser: %s\r\n" \ "\r\n\r\n" +#define SB_KEEPALIVE_HEADERS "MIME-Version: 1.0\r\n" \ + "Content-Type: text/x-ping\r\n" \ + "\r\n\r\n" + #define PROFILE_URL "http://members.msn.com/" struct msn_data @@ -83,6 +88,7 @@ struct msn_switchboard int fd; gint inp; struct msn_handler_data *handler; + gint keepalive; int trId; int ready; @@ -180,6 +186,8 @@ struct groupchat *msn_sb_to_chat( struct msn_switchboard *sb ); void msn_sb_destroy( struct msn_switchboard *sb ); gboolean msn_sb_connected( gpointer data, gint source, b_input_condition cond ); int msn_sb_write_msg( struct im_connection *ic, struct msn_message *m ); +void msn_sb_start_keepalives( struct msn_switchboard *sb, gboolean initial ); +void msn_sb_stop_keepalives( struct msn_switchboard *sb ); /* invitation.c */ void msn_ftp_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who ); diff --git a/protocols/msn/ns.c b/protocols/msn/ns.c index 8181c1af..2f656ea5 100644 --- a/protocols/msn/ns.c +++ b/protocols/msn/ns.c @@ -421,8 +421,12 @@ static int msn_ns_command( gpointer data, char **cmd, int num_parts ) } else if( strcmp( cmd[0], "FLN" ) == 0 ) { - if( cmd[1] ) - imcb_buddy_status( ic, cmd[1], 0, NULL, NULL ); + if( cmd[1] == NULL ) + return 1; + + imcb_buddy_status( ic, cmd[1], 0, NULL, NULL ); + + msn_sb_start_keepalives( msn_sb_by_handle( ic, cmd[1] ), TRUE ); } else if( strcmp( cmd[0], "NLN" ) == 0 ) { @@ -448,6 +452,8 @@ static int msn_ns_command( gpointer data, char **cmd, int num_parts ) imcb_buddy_status( ic, cmd[2], OPT_LOGGED_IN | ( st != msn_away_state_list ? OPT_AWAY : 0 ), st->name, NULL ); + + msn_sb_stop_keepalives( msn_sb_by_handle( ic, cmd[2] ) ); } else if( strcmp( cmd[0], "RNG" ) == 0 ) { diff --git a/protocols/msn/sb.c b/protocols/msn/sb.c index a935ce97..641af5e7 100644 --- a/protocols/msn/sb.c +++ b/protocols/msn/sb.c @@ -174,6 +174,11 @@ int msn_sb_sendmessage( struct msn_switchboard *sb, char *text ) buf = g_new0( char, i ); i = g_snprintf( buf, i, MSN_TYPING_HEADERS, sb->ic->acc->user ); } + else if( strcmp( text, SB_KEEPALIVE_MESSAGE ) == 0 ) + { + buf = g_strdup( SB_KEEPALIVE_HEADERS ); + i = strlen( buf ); + } else if( strncmp( text, MSN_INVITE_HEADERS, sizeof( MSN_INVITE_HEADERS ) - 1 ) == 0 ) { buf = g_strdup( text ); @@ -255,6 +260,7 @@ void msn_sb_destroy( struct msn_switchboard *sb ) debug( "Destroying switchboard: %s", sb->who ? sb->who : sb->key ? sb->key : "" ); msn_msgq_purge( ic, &sb->msgq ); + msn_sb_stop_keepalives( sb ); if( sb->key ) g_free( sb->key ); if( sb->who ) g_free( sb->who ); @@ -476,6 +482,8 @@ static int msn_sb_command( gpointer data, char **cmd, int num_parts ) } sb->ready = 1; + + msn_sb_start_keepalives( sb, FALSE ); } else if( strcmp( cmd[0], "CAL" ) == 0 ) { @@ -525,6 +533,8 @@ static int msn_sb_command( gpointer data, char **cmd, int num_parts ) sb->msgq = g_slist_remove( sb->msgq, m ); } + msn_sb_start_keepalives( sb, FALSE ); + return( st ); } else if( sb->who ) @@ -586,6 +596,8 @@ static int msn_sb_command( gpointer data, char **cmd, int num_parts ) if( sb->who ) { + msn_sb_stop_keepalives( sb ); + /* This is a single-person chat, and the other person is leaving. */ g_free( sb->who ); sb->who = NULL; @@ -751,3 +763,34 @@ static int msn_sb_message( gpointer data, char *msg, int msglen, char **cmd, int return( 1 ); } + +static gboolean msn_sb_keepalive( gpointer data, gint source, b_input_condition cond ) +{ + struct msn_switchboard *sb = data; + return sb->ready && msn_sb_sendmessage( sb, SB_KEEPALIVE_MESSAGE ); +} + +void msn_sb_start_keepalives( struct msn_switchboard *sb, gboolean initial ) +{ + bee_user_t *bu; + + if( sb && sb->who && sb->keepalive == 0 && + ( bu = bee_user_by_handle( sb->ic->bee, sb->ic, sb->who ) ) && + !( bu->flags & BEE_USER_ONLINE ) && + set_getbool( &sb->ic->acc->set, "switchboard_keepalives" ) ) + { + if( initial ) + msn_sb_keepalive( sb, 0, 0 ); + + sb->keepalive = b_timeout_add( 20000, msn_sb_keepalive, sb ); + } +} + +void msn_sb_stop_keepalives( struct msn_switchboard *sb ) +{ + if( sb && sb->keepalive > 0 ) + { + b_event_remove( sb->keepalive ); + sb->keepalive = 0; + } +} diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c index 727eff91..9c7b060c 100644 --- a/protocols/twitter/twitter.c +++ b/protocols/twitter/twitter.c @@ -22,11 +22,11 @@ ****************************************************************************/ #include "nogaim.h" +#include "oauth.h" #include "twitter.h" #include "twitter_http.h" #include "twitter_lib.h" - /** * Main loop function */ @@ -40,7 +40,7 @@ gboolean twitter_main_loop(gpointer data, gint fd, b_input_condition cond) // If the user uses multiple private message windows we need to get the // users buddies. - if (!set_getbool( &ic->acc->set, "use_groupchat" )) + if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "many") == 0) twitter_get_statuses_friends(ic, -1); // Do stuff.. @@ -50,12 +50,107 @@ gboolean twitter_main_loop(gpointer data, gint fd, b_input_condition cond) 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, "Connecting to Twitter" ); + + // 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 const struct oauth_service twitter_oauth = +{ + "http://api.twitter.com/oauth/request_token", + "http://api.twitter.com/oauth/access_token", + "http://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, "twitter_%s", 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_main_loop_start( 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 void twitter_init( account_t *acc ) { set_t *s; - s = set_add( &acc->set, "use_groupchat", "false", set_eval_bool, acc ); + + s = set_add( &acc->set, "mode", "one", set_eval_mode, acc ); s->flags |= ACC_SET_OFFLINE_ONLY; + + s = set_add( &acc->set, "oauth", "true", set_eval_bool, acc ); } /** @@ -66,23 +161,27 @@ static void twitter_login( account_t *acc ) { struct im_connection *ic = imcb_new( acc ); struct twitter_data *td = g_new0( struct twitter_data, 1 ); + char name[strlen(acc->user)+9]; twitter_connections = g_slist_append( twitter_connections, ic ); - + ic->proto_data = td; + ic->flags |= OPT_DOES_HTML; + td->user = acc->user; - td->pass = acc->pass; + if( !set_getbool( &acc->set, "oauth" ) ) + td->pass = g_strdup( acc->pass ); + else if( strstr( acc->pass, "oauth_token=" ) ) + td->oauth_info = oauth_from_string( acc->pass, &twitter_oauth ); td->home_timeline_id = 0; - - ic->proto_data = td; - - imcb_log( ic, "Connecting to Twitter" ); - - // 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); + + sprintf( name, "twitter_%s", acc->user ); + imcb_add_buddy( ic, name, NULL ); + imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL ); + + if( td->pass || td->oauth_info ) + twitter_main_loop_start( ic ); + else + twitter_oauth_start( ic ); } /** @@ -103,6 +202,8 @@ static void twitter_logout( struct im_connection *ic ) if( td ) { + oauth_info_free( td->oauth_info ); + g_free( td->pass ); g_free( td ); } @@ -114,11 +215,28 @@ static void twitter_logout( struct im_connection *ic ) */ static int twitter_buddy_msg( struct im_connection *ic, char *who, char *message, int away ) { - // Let's just update the status. -// if ( g_strcasecmp(who, ic->acc->user) == 0 ) - twitter_post_status(ic, message); -// else -// twitter_direct_messages_new(ic, who, message); + struct twitter_data *td = ic->proto_data; + + if (g_strncasecmp(who, "twitter_", 8) == 0 && + g_strcasecmp(who + 8, ic->acc->user) == 0) + { + if( set_getbool( &ic->acc->set, "oauth" ) && + td->oauth_info && td->oauth_info->token == NULL ) + { + if( !oauth_access_token( message, td->oauth_info ) ) + { + imcb_error( ic, "OAuth error: %s", "Failed to send access token request" ); + imc_logout( ic, TRUE ); + return FALSE; + } + } + else + twitter_post_status(ic, message); + } + else + { + twitter_direct_messages_new(ic, who, message); + } return( 0 ); } diff --git a/protocols/twitter/twitter.h b/protocols/twitter/twitter.h index 88caa104..24f61e42 100644 --- a/protocols/twitter/twitter.h +++ b/protocols/twitter/twitter.h @@ -36,6 +36,7 @@ struct twitter_data { char* user; char* pass; + struct oauth_info *oauth_info; guint64 home_timeline_id; gint main_loop_id; struct groupchat *home_timeline_gc; diff --git a/protocols/twitter/twitter_http.c b/protocols/twitter/twitter_http.c index 3632140f..51f437df 100644 --- a/protocols/twitter/twitter_http.c +++ b/protocols/twitter/twitter_http.c @@ -28,15 +28,17 @@ * * ****************************************************************************/ -#include "twitter_http.h" #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" + char *twitter_url_append(char *url, char *key, char* value); @@ -44,7 +46,7 @@ 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(char *url_string, http_input_function func, gpointer data, int is_post, char* user, char* pass, char** arguments, int arguments_len) +void *twitter_http(char *url_string, http_input_function func, gpointer data, int is_post, char* user, char* pass, struct oauth_info* oi, char** arguments, int arguments_len) { url_t *url = g_new0( url_t, 1 ); char *tmp; @@ -109,7 +111,19 @@ void *twitter_http(char *url_string, http_input_function func, gpointer data, in is_post ? "POST" : "GET", url->file, url->host ); // If a pass and user are given we append them to the request. - if (userpass_base64) + if (oi) + { + char *full_header; + + full_header = oauth_http_header(oi, is_post ? "POST" : "GET", + url_string, url_arguments); + + tmp = g_strdup_printf("%sAuthorization: %s\r\n", request, full_header); + g_free(request); + g_free(full_header); + request = tmp; + } + else if (userpass_base64) { tmp = g_strdup_printf("%sAuthorization: Basic %s\r\n", request, userpass_base64); g_free(request); diff --git a/protocols/twitter/twitter_http.h b/protocols/twitter/twitter_http.h index ec4a0b7c..5ef2530f 100644 --- a/protocols/twitter/twitter_http.h +++ b/protocols/twitter/twitter_http.h @@ -27,8 +27,10 @@ #include "nogaim.h" #include "http_client.h" +struct oauth_info; + void *twitter_http(char *url_string, http_input_function func, gpointer data, int is_post, - char* user, char* pass, char** arguments, int arguments_len); + char* user, char* pass, struct oauth_info *oi, char** arguments, int arguments_len); #endif //_TWITTER_HTTP_H diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index b6863c02..40352893 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -104,12 +104,14 @@ static void twitter_add_buddy(struct im_connection *ic, char *name, const char * // 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 (set_getbool( &ic->acc->set, "use_groupchat" )) + if (g_strcasecmp(mode, "chat") == 0) imcb_chat_add_buddy( td->home_timeline_gc, name ); - else + else if (g_strcasecmp(mode, "many") == 0) imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL ); } } @@ -127,7 +129,7 @@ void twitter_get_friends_ids(struct im_connection *ic, int next_cursor) char* args[2]; args[0] = "cursor"; args[1] = g_strdup_printf ("%d", next_cursor); - twitter_http(TWITTER_FRIENDS_IDS_URL, twitter_http_get_friends_ids, ic, 0, td->user, td->pass, args, 2); + twitter_http(TWITTER_FRIENDS_IDS_URL, twitter_http_get_friends_ids, ic, 0, td->user, td->pass, td->oauth_info, args, 2); g_free(args[1]); } @@ -393,7 +395,7 @@ void twitter_get_home_timeline(struct im_connection *ic, int next_cursor) args[3] = g_strdup_printf ("%llu", (long long unsigned int) td->home_timeline_id); } - twitter_http(TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, td->user, td->pass, args, td->home_timeline_id ? 4 : 2); + twitter_http(TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, td->user, td->pass, td->oauth_info, args, td->home_timeline_id ? 4 : 2); g_free(args[1]); if (td->home_timeline_id) { @@ -451,14 +453,39 @@ 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, "twitter_%s", ic->acc->user ); + from[MAX_STRING-1] = '\0'; + } + for ( l = list; l ; l = g_slist_next(l) ) { + char *text = NULL; + status = l->data; - imcb_buddy_msg( ic, status->user->screen_name, status->text, 0, status->created_at ); + + 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 ); } } @@ -482,7 +509,7 @@ static void twitter_http_get_home_timeline(struct http_request *req) if (req->status_code == 200) { td->http_fails = 0; - if (!ic->flags & OPT_LOGGED_IN) + if (!(ic->flags & OPT_LOGGED_IN)) imcb_connected(ic); } else if (req->status_code == 401) @@ -511,7 +538,7 @@ static void twitter_http_get_home_timeline(struct http_request *req) xt_free( parser ); // See if the user wants to see the messages in a groupchat window or as private messages. - if (set_getbool( &ic->acc->set, "use_groupchat" )) + if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0) twitter_groupchat(ic, txl->list); else twitter_private_message_chat(ic, txl->list); @@ -592,7 +619,7 @@ void twitter_get_statuses_friends(struct im_connection *ic, int next_cursor) args[0] = "cursor"; args[1] = g_strdup_printf ("%d", next_cursor); - twitter_http(TWITTER_SHOW_FRIENDS_URL, twitter_http_get_statuses_friends, ic, 0, td->user, td->pass, args, 2); + twitter_http(TWITTER_SHOW_FRIENDS_URL, twitter_http_get_statuses_friends, ic, 0, td->user, td->pass, td->oauth_info, args, 2); g_free(args[1]); } @@ -611,7 +638,7 @@ static void twitter_http_post_status(struct http_request *req) // 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, "Could not post tweet... HTTP STATUS: %d", req->status_code); + imcb_error(ic, "Could not post message... HTTP STATUS: %d", req->status_code); return; } } @@ -626,7 +653,7 @@ void twitter_post_status(struct im_connection *ic, char* msg) char* args[2]; args[0] = "status"; args[1] = msg; - twitter_http(TWITTER_STATUS_UPDATE_URL, twitter_http_post_status, ic, 1, td->user, td->pass, args, 2); + twitter_http(TWITTER_STATUS_UPDATE_URL, twitter_http_post_status, ic, 1, td->user, td->pass, td->oauth_info, args, 2); // g_free(args[1]); } @@ -644,7 +671,7 @@ void twitter_direct_messages_new(struct im_connection *ic, char *who, char *msg) args[2] = "text"; args[3] = msg; // Use the same callback as for twitter_post_status, since it does basically the same. - twitter_http(TWITTER_DIRECT_MESSAGES_NEW_URL, twitter_http_post_status, ic, 1, td->user, td->pass, args, 4); + twitter_http(TWITTER_DIRECT_MESSAGES_NEW_URL, twitter_http_post_status, ic, 1, td->user, td->pass, td->oauth_info, args, 4); // g_free(args[1]); // g_free(args[3]); } |