aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWilmer van der Gaast <wilmer@gaast.net>2010-06-06 00:21:02 +0100
committerWilmer van der Gaast <wilmer@gaast.net>2010-06-06 00:21:02 +0100
commitb308cf9bafbdf76da73a57607b65c4763aa3057b (patch)
treec686906b479a0edd52b18a213e1d420f7343855d
parent3ab1d317831a6c1830bb648a1a8d63a41c92f651 (diff)
parente774815bc621af90bb64ca314b84367659c5a005 (diff)
Merging libpurple branch into killerbee. It's fairly usable already, and
Debian packaging is now properly separated. This also picks up a load of stuff from mainline it seems.
-rw-r--r--Makefile16
-rw-r--r--bitlbee.c85
-rw-r--r--bitlbee.h6
-rw-r--r--conf.c11
-rwxr-xr-xconfigure90
-rw-r--r--dcc.c20
-rw-r--r--[-rwxr-xr-x]debian/bitlbee-common.config (renamed from debian/config)0
-rw-r--r--debian/bitlbee-common.docs6
-rw-r--r--debian/bitlbee-common.examples1
-rw-r--r--debian/bitlbee-common.templates (renamed from debian/templates)0
-rw-r--r--[-rwxr-xr-x]debian/bitlbee.init4
-rw-r--r--[-rwxr-xr-x]debian/bitlbee.postinst (renamed from debian/postinst)0
-rw-r--r--[-rwxr-xr-x]debian/bitlbee.postrm (renamed from debian/postrm)0
-rw-r--r--[-rwxr-xr-x]debian/bitlbee.prerm (renamed from debian/prerm)0
-rw-r--r--debian/changelog18
-rw-r--r--debian/compat1
-rw-r--r--debian/conffiles3
-rw-r--r--debian/control42
-rw-r--r--debian/patches/bitlbee.conf.diff4
-rw-r--r--debian/po/POTFILES.in2
-rwxr-xr-xdebian/rules173
-rw-r--r--doc/CHANGES29
-rw-r--r--doc/Makefile7
-rw-r--r--doc/user-guide/Makefile6
-rw-r--r--doc/user-guide/commands.xml144
-rw-r--r--help.c26
-rw-r--r--help.h1
-rw-r--r--ipc.c6
-rw-r--r--irc.c35
-rw-r--r--irc_commands.c2
-rw-r--r--lib/Makefile7
-rw-r--r--lib/events.h6
-rw-r--r--lib/events_glib.c17
-rw-r--r--lib/events_libevent.c16
-rw-r--r--lib/http_client.c6
-rw-r--r--lib/misc.c38
-rw-r--r--lib/misc.h1
-rw-r--r--lib/oauth.c453
-rw-r--r--lib/oauth.h90
-rw-r--r--lib/proxy.c64
-rw-r--r--lib/ssl_bogus.c2
-rw-r--r--lib/ssl_client.h2
-rw-r--r--lib/ssl_gnutls.c4
-rw-r--r--lib/ssl_nss.c2
-rw-r--r--lib/ssl_openssl.c4
-rw-r--r--lib/ssl_sspi.c2
-rw-r--r--lib/url.c2
-rw-r--r--lib/url.h2
-rw-r--r--lib/xmltree.c22
-rw-r--r--log.c2
-rw-r--r--protocols/Makefile5
-rw-r--r--protocols/jabber/Makefile5
-rw-r--r--protocols/jabber/conference.c4
-rw-r--r--protocols/jabber/io.c4
-rw-r--r--protocols/jabber/jabber_util.c29
-rw-r--r--protocols/jabber/message.c4
-rw-r--r--protocols/jabber/s5bytestream.c14
-rw-r--r--protocols/msn/Makefile5
-rw-r--r--protocols/msn/invitation.c12
-rw-r--r--protocols/msn/msn.c32
-rw-r--r--protocols/msn/msn.h9
-rw-r--r--protocols/msn/msn_util.c16
-rw-r--r--protocols/msn/ns.c79
-rw-r--r--protocols/msn/sb.c93
-rw-r--r--protocols/nogaim.c248
-rw-r--r--protocols/nogaim.h3
-rw-r--r--protocols/oscar/Makefile6
-rw-r--r--protocols/oscar/oscar.c151
-rw-r--r--protocols/purple/Makefile44
-rw-r--r--protocols/purple/ft-direct.c239
-rw-r--r--protocols/purple/ft.c353
-rw-r--r--protocols/purple/purple.c1143
-rw-r--r--protocols/twitter/Makefile46
-rw-r--r--protocols/twitter/twitter.c364
-rw-r--r--protocols/twitter/twitter.h53
-rw-r--r--protocols/twitter/twitter_http.c175
-rw-r--r--protocols/twitter/twitter_http.h36
-rw-r--r--protocols/twitter/twitter_lib.c677
-rw-r--r--protocols/twitter/twitter_lib.h86
-rw-r--r--protocols/yahoo/Makefile5
-rw-r--r--protocols/yahoo/yahoo.c13
-rw-r--r--set.c33
-rw-r--r--set.h22
-rw-r--r--storage_xml.c1
-rw-r--r--tests/Makefile5
-rw-r--r--unix.c34
86 files changed, 4972 insertions, 556 deletions
diff --git a/Makefile b/Makefile
index ef34f123..222737b2 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,7 @@
# Program variables
objects = account.o bitlbee.o chat.o dcc.o help.o ipc.o irc.o irc_commands.o nick.o query.o root_commands.o set.o storage.o $(STORAGE_OBJS) user.o
-headers = account.h bitlbee.h commands.h conf.h config.h help.h ipc.h irc.h log.h nick.h query.h set.h sock.h storage.h user.h lib/events.h lib/ftutil.h lib/http_client.h lib/ini.h lib/md5.h lib/misc.h lib/proxy.h lib/sha1.h lib/ssl_client.h lib/url.h protocols/ft.h protocols/nogaim.h
+headers = account.h bitlbee.h commands.h conf.h help.h ipc.h irc.h log.h nick.h query.h set.h sock.h storage.h user.h lib/events.h lib/ftutil.h lib/http_client.h lib/ini.h lib/md5.h lib/misc.h lib/proxy.h lib/sha1.h lib/ssl_client.h lib/url.h protocols/ft.h protocols/nogaim.h
subdirs = lib protocols
ifeq ($(TARGET),i586-mingw32msvc)
@@ -81,7 +81,8 @@ uninstall-bin:
install-dev:
mkdir -p $(DESTDIR)$(INCLUDEDIR)
- install -m 0644 $(headers) $(DESTDIR)$(INCLUDEDIR)
+ install -m 0644 config.h $(DESTDIR)$(INCLUDEDIR)
+ for i in $(headers); do install -m 0644 $(SRCDIR)$$i $(DESTDIR)$(INCLUDEDIR); done
mkdir -p $(DESTDIR)$(PCDIR)
install -m 0644 bitlbee.pc $(DESTDIR)$(PCDIR)
@@ -92,8 +93,8 @@ uninstall-dev:
install-etc:
mkdir -p $(DESTDIR)$(ETCDIR)
- install -m 0644 motd.txt $(DESTDIR)$(ETCDIR)/motd.txt
- install -m 0644 bitlbee.conf $(DESTDIR)$(ETCDIR)/bitlbee.conf
+ install -m 0644 $(SRCDIR)motd.txt $(DESTDIR)$(ETCDIR)/motd.txt
+ install -m 0644 $(SRCDIR)bitlbee.conf $(DESTDIR)$(ETCDIR)/bitlbee.conf
uninstall-etc:
rm -f $(DESTDIR)$(ETCDIR)/motd.txt
@@ -109,7 +110,7 @@ tar:
$(subdirs):
@$(MAKE) -C $@ $(MAKECMDGOALS)
-$(objects): %.o: %.c
+$(objects): %.o: $(SRCDIR)%.c
@echo '*' Compiling $<
@$(CC) -c $(CFLAGS) $< -o $@
@@ -125,3 +126,8 @@ endif
ctags:
ctags `find . -name "*.c"` `find . -name "*.h"`
+
+# Using this as a bogus Make target to test if a GNU-compatible version of
+# make is available.
+helloworld:
+ @echo Hello World
diff --git a/bitlbee.c b/bitlbee.c
index 26d12b6c..d0d95e67 100644
--- a/bitlbee.c
+++ b/bitlbee.c
@@ -35,14 +35,51 @@
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;
int i;
FILE *fp;
- log_link( LOGLVL_ERROR, LOGOUTPUT_SYSLOG );
- log_link( LOGLVL_WARNING, LOGOUTPUT_SYSLOG );
+ log_link( LOGLVL_ERROR, LOGOUTPUT_CONSOLE );
+ log_link( LOGLVL_WARNING, LOGOUTPUT_CONSOLE );
memset( &hints, 0, sizeof( hints ) );
hints.ai_family = PF_UNSPEC;
@@ -62,27 +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;
-
- /* 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 );
@@ -92,7 +120,7 @@ int bitlbee_daemon_init()
return( -1 );
}
- global.listen_watch_source_id = b_input_add( global.listen_socket, GAIM_INPUT_READ, bitlbee_io_new_client, NULL );
+ global.listen_watch_source_id = b_input_add( global.listen_socket, B_EV_IO_READ, bitlbee_io_new_client, NULL );
#ifndef _WIN32
if( !global.conf->nofork )
@@ -106,6 +134,7 @@ int bitlbee_daemon_init()
else if( i != 0 )
exit( 0 );
+ setsid();
chdir( "/" );
if( getenv( "_BITLBEE_RESTART_STATE" ) == NULL )
@@ -136,6 +165,12 @@ int bitlbee_daemon_init()
}
#endif
+ if( !global.conf->nofork )
+ {
+ log_link( LOGLVL_ERROR, LOGOUTPUT_SYSLOG );
+ log_link( LOGLVL_WARNING, LOGOUTPUT_SYSLOG );
+ }
+
return( 0 );
}
@@ -285,7 +320,7 @@ static gboolean bitlbee_io_new_client( gpointer data, gint fd, b_input_condition
child = g_new0( struct bitlbee_child, 1 );
child->pid = client_pid;
child->ipc_fd = fds[0];
- child->ipc_inpa = b_input_add( child->ipc_fd, GAIM_INPUT_READ, ipc_master_read, child );
+ child->ipc_inpa = b_input_add( child->ipc_fd, B_EV_IO_READ, ipc_master_read, child );
child_list = g_slist_append( child_list, child );
log_message( LOGLVL_INFO, "Creating new subprocess with pid %d.", (int) client_pid );
@@ -313,7 +348,7 @@ static gboolean bitlbee_io_new_client( gpointer data, gint fd, b_input_condition
/* We can store the IPC fd there now. */
global.listen_socket = fds[1];
- global.listen_watch_source_id = b_input_add( fds[1], GAIM_INPUT_READ, ipc_child_read, irc );
+ global.listen_watch_source_id = b_input_add( fds[1], B_EV_IO_READ, ipc_child_read, irc );
close( fds[0] );
diff --git a/bitlbee.h b/bitlbee.h
index 5f98deef..1dc434ec 100644
--- a/bitlbee.h
+++ b/bitlbee.h
@@ -34,13 +34,15 @@
#define _WIN32_WINNT 0x0501
#define PACKAGE "BitlBee"
-#define BITLBEE_VERSION "1.2.5"
+#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)
#define MAX_STRING 511
#if HAVE_CONFIG_H
-#include "config.h"
+#include <config.h>
#endif
#include <fcntl.h>
diff --git a/conf.c b/conf.c
index 8687afea..206a4535 100644
--- a/conf.c
+++ b/conf.c
@@ -81,7 +81,7 @@ conf_t *conf_load( int argc, char *argv[] )
at a *valid* configuration file. */
}
- while( argc > 0 && ( opt = getopt( argc, argv, "i:p:P:nvIDFc:d:hR:u:" ) ) >= 0 )
+ while( argc > 0 && ( opt = getopt( argc, argv, "i:p:P:nvIDFc:d:hR:u:V" ) ) >= 0 )
/* ^^^^ Just to make sure we skip this step from the REHASH handler. */
{
if( opt == 'i' )
@@ -147,7 +147,14 @@ conf_t *conf_load( int argc, char *argv[] )
" -c Load alternative configuration file\n"
" -d Specify alternative user configuration directory\n"
" -x Command-line interface to password encryption/hashing\n"
- " -h Show this help page.\n" );
+ " -h Show this help page.\n"
+ " -V Show version info.\n" );
+ return NULL;
+ }
+ else if( opt == 'V' )
+ {
+ printf( "BitlBee %s\nAPI version %06x\n",
+ BITLBEE_VERSION, BITLBEE_VERSION_CODE );
return NULL;
}
else if( opt == 'R' )
diff --git a/configure b/configure
index 7ffd225b..232e0cdc 100755
--- a/configure
+++ b/configure
@@ -25,6 +25,8 @@ msn=1
jabber=1
oscar=1
yahoo=1
+twitter=1
+purple=0
debug=0
strip=1
@@ -65,6 +67,9 @@ Option Description Default
--jabber=0/1 Disable/enable Jabber part $jabber
--oscar=0/1 Disable/enable Oscar part (ICQ, AIM) $oscar
--yahoo=0/1 Disable/enable Yahoo part $yahoo
+--twitter=0/1 Disable/enable Twitter part $twitter
+
+--purple=0/1 Disable/enable libpurple support $purple
--debug=0/1 Disable/enable debugging $debug
--strip=0/1 Disable/enable binary stripping $strip
@@ -118,6 +123,29 @@ LFLAGS=
EFLAGS=
EOF
+srcdir="$(dirname $0)"
+if [ "$srcdir" != "." ]; then
+ echo
+ echo "configure script run from a different directory. Will create some symlinks..."
+ if [ ! -e Makefile -o -L Makefile ]; then
+ COPYDIRS="doc lib protocols tests utils"
+ mkdir -p $(cd "$srcdir"; find $COPYDIRS -type d)
+ find . -name Makefile -type l -print0 | xargs -0 rm 2> /dev/null
+ dst="$PWD"
+ cd "$srcdir"
+ for i in $(find . -name Makefile -type f); do
+ ln -s "$PWD${i#.}" "$dst/$i";
+ done
+ cd "$dst"
+ rm -rf .bzr
+ fi
+
+ echo "SRCDIR=$srcdir/" >> Makefile.settings
+ CFLAGS="$CFLAGS -I${dst}"
+else
+ srcdir=$PWD
+fi
+
cat<<EOF>config.h
/* BitlBee settings, generated by configure
@@ -155,7 +183,7 @@ else
fi
echo CFLAGS=$CFLAGS >> Makefile.settings
-echo CFLAGS+=-I`pwd` -I`pwd`/lib -I`pwd`/protocols -I. >> Makefile.settings
+echo CFLAGS+=-I${srcdir} -I${srcdir}/lib -I${srcdir}/protocols -I. >> Makefile.settings
echo CFLAGS+=-DHAVE_CONFIG_H >> Makefile.settings
@@ -266,7 +294,7 @@ EOF
detect_ldap()
{
- TMPFILE=$(mktemp)
+ TMPFILE=$(mktemp /tmp/bitlbee-configure.XXXXXX)
if $CC -o $TMPFILE -shared -lldap 2>/dev/null >/dev/null; then
cat<<EOF>>Makefile.settings
EFLAGS+=-lldap
@@ -294,7 +322,7 @@ int main()
detect_resolv_dynamic()
{
- TMPFILE=$(mktemp)
+ TMPFILE=$(mktemp /tmp/bitlbee-configure.XXXXXX)
ret=1
echo "$RESOLV_TESTCODE" | $CC -o $TMPFILE -x c - -lresolv >/dev/null 2>/dev/null
if [ "$?" = "0" ]; then
@@ -308,7 +336,7 @@ detect_resolv_dynamic()
detect_resolv_static()
{
- TMPFILE=$(mktemp)
+ TMPFILE=$(mktemp /tmp/bitlbee-configure.XXXXXX)
ret=1
for i in $systemlibdirs; do
if [ -f $i/libresolv.a ]; then
@@ -475,6 +503,20 @@ if [ -n "$BITLBEE_VERSION" ]; then
echo
fi
+if ! make helloworld > /dev/null 2>&1; then
+ echo "WARNING: Your version of make (BSD make?) does not support BitlBee's makefiles."
+ echo "BitlBee needs GNU make to build properly. On most systems GNU make is available"
+ echo "under the name 'gmake'."
+ echo
+ if gmake helloworld > /dev/null 2>&1; then
+ echo "gmake seems to be available on your machine, great."
+ echo
+ else
+ echo "gmake is not installed (or not working). Please try to install it."
+ echo
+ fi
+fi
+
cat <<EOF>bitlbee.pc
prefix=$prefix
includedir=$includedir
@@ -491,6 +533,38 @@ EOF
protocols=''
protoobjs=''
+if [ "$purple" = 0 ]; then
+ echo '#undef WITH_PURPLE' >> config.h
+else
+ if ! $PKG_CONFIG purple; then
+ echo
+ echo 'Cannot find libpurple development libraries, aborting. (Install libpurple-dev?)'
+ exit 1
+ fi
+ echo '#define WITH_PURPLE' >> config.h
+ cat<<EOF>>Makefile.settings
+EFLAGS += $($PKG_CONFIG purple --libs)
+PURPLE_CFLAGS += $($PKG_CONFIG purple --cflags)
+EOF
+ protocols=$protocols'purple '
+ protoobjs=$protoobjs'purple_mod.o '
+
+ # Having both libpurple and native IM modules in one binary may
+ # do strange things. Let's not do that.
+ msn=0
+ jabber=0
+ oscar=0
+ yahoo=0
+ twitter=0
+
+ if [ "$events" = "libevent" ]; then
+ echo
+ echo 'Warning: Some libpurple modules (including msn-pecan) do their event handling'
+ echo 'outside libpurple, talking to GLib directly. At least for now the combination'
+ echo 'libpurple + libevent is *not* recommended!'
+ fi
+fi
+
if [ "$msn" = 0 ]; then
echo '#undef WITH_MSN' >> config.h
else
@@ -523,6 +597,14 @@ else
protoobjs=$protoobjs'yahoo_mod.o '
fi
+if [ "$twitter" = 0 ]; then
+ echo '#undef WITH_TWITTER' >> config.h
+else
+ echo '#define WITH_TWITTER' >> config.h
+ protocols=$protocols'twitter '
+ protoobjs=$protoobjs'twitter_mod.o '
+fi
+
if [ "$protocols" = "PROTOCOLS = " ]; then
echo "Warning: You haven't selected any communication protocol to compile!"
echo " BitlBee will run, but you will be unable to connect to IM servers!"
diff --git a/dcc.c b/dcc.c
index 558d923a..1f8ec611 100644
--- a/dcc.c
+++ b/dcc.c
@@ -153,7 +153,7 @@ file_transfer_t *dccs_send_start( struct im_connection *ic, char *user_nick, cha
return NULL;
/* watch */
- df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_send_proto, df );
+ df->watch_in = b_input_add( df->fd, B_EV_IO_READ, dccs_send_proto, df );
df->ic->irc->file_transfers = g_slist_prepend( df->ic->irc->file_transfers, file );
@@ -266,7 +266,7 @@ gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond )
dcc_file_transfer_t *df = data;
file_transfer_t *file = df->ft;
- if( ( cond & GAIM_INPUT_READ ) &&
+ if( ( cond & B_EV_IO_READ ) &&
( file->status & FT_STATUS_LISTENING ) )
{
struct sockaddr *clt_addr;
@@ -286,12 +286,12 @@ gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond )
file->accept( file );
/* reschedule for reading on new fd */
- df->watch_in = b_input_add( fd, GAIM_INPUT_READ, dccs_send_proto, df );
+ df->watch_in = b_input_add( fd, B_EV_IO_READ, dccs_send_proto, df );
return FALSE;
}
- if( cond & GAIM_INPUT_READ )
+ if( cond & B_EV_IO_READ )
{
int ret;
@@ -363,7 +363,7 @@ gboolean dccs_recv_start( file_transfer_t *ft )
ft->status = FT_STATUS_CONNECTING;
/* watch */
- df->watch_out = b_input_add( df->fd, GAIM_INPUT_WRITE, dccs_recv_proto, df );
+ df->watch_out = b_input_add( df->fd, B_EV_IO_WRITE, dccs_recv_proto, df );
ft->write_request = dccs_recv_write_request;
df->progress_timeout = b_timeout_add( DCC_MAX_STALL * 1000, dcc_progress, df );
@@ -376,18 +376,18 @@ gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond )
dcc_file_transfer_t *df = data;
file_transfer_t *ft = df->ft;
- if( ( cond & GAIM_INPUT_WRITE ) &&
+ if( ( cond & B_EV_IO_WRITE ) &&
( ft->status & FT_STATUS_CONNECTING ) )
{
ft->status = FT_STATUS_TRANSFERRING;
- //df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_recv_proto, df );
+ //df->watch_in = b_input_add( df->fd, B_EV_IO_READ, dccs_recv_proto, df );
df->watch_out = 0;
return FALSE;
}
- if( cond & GAIM_INPUT_READ )
+ if( cond & B_EV_IO_READ )
{
int ret, done;
@@ -444,7 +444,7 @@ gboolean dccs_recv_write_request( file_transfer_t *ft )
if( df->watch_in )
return dcc_abort( df, "BUG: write_request() called while watching" );
- df->watch_in = b_input_add( df->fd, GAIM_INPUT_READ, dccs_recv_proto, df );
+ df->watch_in = b_input_add( df->fd, B_EV_IO_READ, dccs_recv_proto, df );
return TRUE;
}
@@ -487,7 +487,7 @@ gboolean dccs_send_write( file_transfer_t *file, char *data, unsigned int data_l
df->bytes_sent += ret;
if( df->bytes_sent < df->ft->file_size )
- df->watch_out = b_input_add( df->fd, GAIM_INPUT_WRITE, dccs_send_can_write, df );
+ df->watch_out = b_input_add( df->fd, B_EV_IO_WRITE, dccs_send_can_write, df );
return TRUE;
}
diff --git a/debian/config b/debian/bitlbee-common.config
index 9bb78237..9bb78237 100755..100644
--- a/debian/config
+++ b/debian/bitlbee-common.config
diff --git a/debian/bitlbee-common.docs b/debian/bitlbee-common.docs
new file mode 100644
index 00000000..72ff657c
--- /dev/null
+++ b/debian/bitlbee-common.docs
@@ -0,0 +1,6 @@
+doc/user-guide/user-guide.txt
+doc/user-guide/user-guide.html
+doc/AUTHORS
+doc/CREDITS
+doc/FAQ
+doc/README
diff --git a/debian/bitlbee-common.examples b/debian/bitlbee-common.examples
new file mode 100644
index 00000000..81562b9e
--- /dev/null
+++ b/debian/bitlbee-common.examples
@@ -0,0 +1 @@
+utils/*
diff --git a/debian/templates b/debian/bitlbee-common.templates
index 0cd04426..0cd04426 100644
--- a/debian/templates
+++ b/debian/bitlbee-common.templates
diff --git a/debian/bitlbee.init b/debian/bitlbee.init
index 4c224ffc..be1dcd66 100755..100644
--- 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/postinst b/debian/bitlbee.postinst
index db541f6c..db541f6c 100755..100644
--- a/debian/postinst
+++ b/debian/bitlbee.postinst
diff --git a/debian/postrm b/debian/bitlbee.postrm
index 5c3b4b2e..5c3b4b2e 100755..100644
--- a/debian/postrm
+++ b/debian/bitlbee.postrm
diff --git a/debian/prerm b/debian/bitlbee.prerm
index 687c2cc1..687c2cc1 100755..100644
--- a/debian/prerm
+++ b/debian/bitlbee.prerm
diff --git a/debian/changelog b/debian/changelog
index f969b410..ffb23ed8 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,21 @@
+bitlbee (1.3-0) unstable; urgency=low
+
+ * Setting some bogus version number, fix that later.
+ * Now using debhelper to improve maintainability.
+ * Added a bitlbee-libpurple package, and split off docs and stuff into
+ bitlbee-common.
+
+ -- Wilmer van der Gaast <wilmer@gaast.net> Sat, 05 Jun 2010 15:16:38 +0100
+
+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/compat b/debian/compat
new file mode 100644
index 00000000..7f8f011e
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+7
diff --git a/debian/conffiles b/debian/conffiles
deleted file mode 100644
index dcb4078e..00000000
--- a/debian/conffiles
+++ /dev/null
@@ -1,3 +0,0 @@
-/etc/bitlbee/motd.txt
-/etc/bitlbee/bitlbee.conf
-/etc/init.d/bitlbee
diff --git a/debian/control b/debian/control
index 86488c8a..05689940 100644
--- a/debian/control
+++ b/debian/control
@@ -3,23 +3,53 @@ Section: net
Priority: optional
Maintainer: Wilmer van der Gaast <wilmer@gaast.net>
Uploaders: Jelmer Vernooij <jelmer@samba.org>
-Standards-Version: 3.8.0
-Build-Depends: libglib2.0-dev (>= 2.4), libevent-dev, libgnutls-dev | libnss-dev (>= 1.6), debconf-2.0, po-debconf
+Standards-Version: 3.8.4
+Build-Depends: libglib2.0-dev (>= 2.4), libevent-dev, libgnutls-dev | libnss-dev (>= 1.6), po-debconf, libpurple-dev, debhelper (>= 7)
Homepage: http://www.bitlbee.org/
Vcs-Bzr: http://code.bitlbee.org/bitlbee/
DM-Upload-Allowed: yes
Package: bitlbee
Architecture: any
-Depends: ${shlibs:Depends}, adduser, net-tools, ${debconf-depends}, debianutils (>= 1.16)
-Description: An IRC to other chat networks gateway
+Depends: ${shlibs:Depends}, adduser, debianutils (>= 1.16), bitlbee-common (= ${bee:Version})
+Conflicts: bitlbee-libpurple
+Replaces: bitlbee-libpurple
+Description: An IRC to other chat networks gateway (default version)
This program can be used as an IRC server which forwards everything you
say to people on other chat networks: Jabber, ICQ, AIM, MSN and Yahoo.
+Package: bitlbee-libpurple
+Architecture: any
+Depends: ${shlibs:Depends}, adduser, debianutils (>= 1.16), bitlbee-common (= ${bee:Version})
+Conflicts: bitlbee
+Replaces: bitlbee
+Description: An IRC to other chat networks gateway (using libpurple)
+ This program can be used as an IRC server which forwards everything you
+ say to people on other chat networks: Jabber, ICQ, AIM, MSN and Yahoo.
+ .
+ This package contains a version of BitlBee that uses the libpurple instant
+ messaging library instead of built-in code, which adds support for more IM
+ protocols (all protocols supported by Pidgin/Finch) and features (like file
+ transfers), at the price of being less lightweight.
+ .
+ This variant may not be very suitable for BitlBee instances used by many
+ (tens or hundreds) of clients.
+
+Package: bitlbee-common
+Architecture: all
+Depends: ${misc:Depends}, net-tools
+Replaces: bitlbee
+Description: An IRC to other chat networks gateway (common files/docs)
+ This program can be used as an IRC server which forwards everything you
+ say to people on other chat networks: Jabber, ICQ, AIM, MSN and Yahoo.
+ .
+ This package contains common files (mostly documentation) for bitlbee and
+ bitlbee-libpurple.
+
Package: bitlbee-dev
Architecture: all
-Depends: bitlbee (>= ${source:Version}), bitlbee (<< ${source:Version}.1~)
-Description: An IRC to other chat networks gateway
+Depends: ${misc:Depends}, bitlbee (>= ${bee:Version}), bitlbee (<< ${bee:Version}.1~)
+Description: An IRC to other chat networks gateway (dev files)
This program can be used as an IRC server which forwards everything you
say to people on other chat networks: Jabber, ICQ, AIM, MSN and Yahoo.
.
diff --git a/debian/patches/bitlbee.conf.diff b/debian/patches/bitlbee.conf.diff
index c98fa546..339ccd4a 100644
--- a/debian/patches/bitlbee.conf.diff
+++ b/debian/patches/bitlbee.conf.diff
@@ -1,5 +1,5 @@
---- debian/bitlbee/etc/bitlbee/bitlbee.conf 2009-06-01 00:20:24.000000000 +0100
-+++ debian/bitlbee/etc/bitlbee/bitlbee.conf 2009-06-07 21:16:19.000000000 +0100
+--- bitlbee.conf 2009-06-01 00:20:24.000000000 +0100
++++ bitlbee.conf 2009-06-07 21:16:19.000000000 +0100
@@ -23,13 +23,18 @@
## If BitlBee is started by root as a daemon, it can drop root privileges,
## and change to the specified user.
diff --git a/debian/po/POTFILES.in b/debian/po/POTFILES.in
index cef83a34..8d2b570f 100644
--- a/debian/po/POTFILES.in
+++ b/debian/po/POTFILES.in
@@ -1 +1 @@
-[type: gettext/rfc822deb] templates
+[type: gettext/rfc822deb] bitlbee-common.templates
diff --git a/debian/rules b/debian/rules
index 788e5006..f2ede2cf 100755
--- a/debian/rules
+++ b/debian/rules
@@ -1,109 +1,100 @@
#!/usr/bin/make -f
-
+#
+# Finally switching to debhelper.
+#
+# Not using debhelper was an exercise suggested to me by my AM (Gergely
+# Nagy). It was educating at the time but I finally decided that the
+# exercise is over now.
+#
+
+BITLBEE_CONFIGURE_FLAGS ?=
DEBUG ?= 0
-ifdef BITLBEE_VERSION
-BITLBEE_FORCE_VERSION=1
-else
+ifndef BITLBEE_VERSION
# Want to use the full package version number instead of just the release.
-BITLBEE_VERSION ?= "$(shell dpkg-parsechangelog | grep ^Version: | awk '{print $$2}')"
-export BITLBEE_VERSION
+BITLBEE_CONFIGURE_VERSION ?= BITLBEE_VERSION=\"$(shell dpkg-parsechangelog | grep ^Version: | awk '{print $$2}')\"
endif
-build-arch: build-arch-stamp
-build-arch-stamp:
- [ -d debian ]
- ./configure --debug=$(DEBUG) --prefix=/usr --etcdir=/etc/bitlbee --events=libevent
- $(MAKE)
-# $(MAKE) -C doc/ all
- touch build-arch-stamp
+build: build-stamp
+build-stamp:
+ dh_testdir
+
+ mkdir -p debian/build-native
+ ROOT=$$PWD; cd debian/build-native; $(BITLBEE_CONFIGURE_VERSION) $$ROOT/configure --debug=$(DEBUG) --prefix=/usr --etcdir=/etc/bitlbee --events=libevent $(BITLBEE_CONFIGURE_FLAGS)
+ $(MAKE) -C debian/build-native
+
+ mkdir -p debian/build-libpurple
+ ROOT=$$PWD; cd debian/build-libpurple; $(BITLBEE_CONFIGURE_VERSION) $$ROOT/configure --debug=$(DEBUG) --prefix=/usr --etcdir=/etc/bitlbee --purple=1 $(BITLBEE_CONFIGURE_FLAGS)
+ $(MAKE) -C debian/build-libpurple
+
+ touch build-stamp
clean:
- [ "`whoami`" = "root" -a -d debian ]
- rm -rf build-arch-stamp debian/bitlbee debian/*.substvars debian/files debian/bitlbee-dev
+ dh_testdir
+ dh_testroot
+ rm -f build-stamp
+
+ rm -rf build-arch-stamp debian/build-*
$(MAKE) distclean
-# -$(MAKE) -C doc/ clean
-
-
-install-arch: build-arch
- [ "`whoami`" = "root" -a -d debian ]
- mkdir -p debian/bitlbee/DEBIAN/
- $(MAKE) install install-etc DESTDIR=`pwd`/debian/bitlbee
-
- mkdir -p debian/bitlbee/usr/share/doc/bitlbee/
- cp doc/user-guide/user-guide.txt debian/bitlbee/usr/share/doc/bitlbee/
- cp doc/user-guide/user-guide.html debian/bitlbee/usr/share/doc/bitlbee/
-
-install-indep: install-arch
- [ "`whoami`" = "root" -a -d debian ]
- mkdir -p debian/bitlbee-dev/DEBIAN/
- $(MAKE) install-dev DESTDIR=`pwd`/debian/bitlbee-dev
-
- mkdir -p debian/bitlbee-dev/usr/share/doc/bitlbee-dev/
-
-binary-arch: build-arch install-arch
- [ "`whoami`" = "root" -a -d debian ]
-
- chmod 755 debian/post* debian/pre* debian/config debian/bitlbee.init
-
- mkdir -p debian/bitlbee/usr/share/doc/bitlbee/examples/ debian/bitlbee/etc/init.d/
- -cp doc/RELEASE-SPEECH* debian/bitlbee/usr/share/doc/bitlbee/ && gzip -9 debian/bitlbee/usr/share/doc/bitlbee/RELEASE-SPEECH*
- cp doc/CREDITS doc/AUTHORS doc/README doc/FAQ debian/README.Debian debian/bitlbee/usr/share/doc/bitlbee/
- cp debian/changelog debian/bitlbee/usr/share/doc/bitlbee/changelog.Debian
- cp debian/copyright debian/bitlbee/usr/share/doc/bitlbee/copyright
- cp doc/CHANGES debian/bitlbee/usr/share/doc/bitlbee/changelog
- cp utils/* debian/bitlbee/usr/share/doc/bitlbee/examples/
- cp debian/bitlbee.init debian/bitlbee/etc/init.d/bitlbee
- patch -p0 < debian/patches/bitlbee.conf.diff
- cd debian/bitlbee/usr/share/; \
- gzip -9 doc/bitlbee/changelog.Debian doc/bitlbee/changelog doc/bitlbee/user-guide.txt \
- doc/bitlbee/examples/* man/man8/bitlbee.8 man/man5/bitlbee.conf.5
-
- chown -R root:root debian/bitlbee/
- find debian/bitlbee/usr/share/ -type d -exec chmod 755 {} \;
- find debian/bitlbee/usr/share/ -type f -exec chmod 644 {} \;
-
- cp debian/prerm debian/bitlbee/DEBIAN/
- cp debian/postinst debian/bitlbee/DEBIAN/
- cp debian/postrm debian/bitlbee/DEBIAN/
- cp debian/config debian/bitlbee/DEBIAN/
-
- po2debconf debian/templates > debian/bitlbee/DEBIAN/templates
- cp debian/conffiles debian/bitlbee/DEBIAN/
-
- if [ "$(DEBUG)" = "0" ]; then strip -R .comment -R .note debian/bitlbee/usr/sbin/bitlbee; fi
-
- cd debian/bitlbee; \
- find usr -type f -exec md5sum {} \; > DEBIAN/md5sums
- dpkg-shlibdeps -Tdebian/bitlbee.substvars -dDepends debian/bitlbee/usr/sbin/bitlbee
-ifdef BITLBEE_FORCE_VERSION
- dpkg-gencontrol -ldebian/changelog -isp -pbitlbee -Tdebian/bitlbee.substvars -Pdebian/bitlbee -v1:$(BITLBEE_VERSION)-0 -V'debconf-depends=debconf (>= 1.2.0) | debconf-2.0'
-else
- dpkg-gencontrol -ldebian/changelog -isp -pbitlbee -Tdebian/bitlbee.substvars -Pdebian/bitlbee -V'debconf-depends=debconf (>= 1.2.0) | debconf-2.0'
-endif
- dpkg --build debian/bitlbee ..
+ dh_clean
+
+install: build
+ dh_testdir
+ dh_testroot
+ dh_prep
+ dh_installdirs
-binary-indep: install-indep
- [ "`whoami`" = "root" -a -d debian ]
+ $(MAKE) -C debian/build-native install install-etc DESTDIR=`pwd`/debian/bitlbee
+ $(MAKE) -C debian/build-libpurple install install-etc DESTDIR=`pwd`/debian/bitlbee-libpurple
+ $(MAKE) -C debian/build-native install-dev DESTDIR=`pwd`/debian/bitlbee-dev
- chown -R root.root debian/bitlbee-dev/
- find debian/bitlbee-dev/usr/share/ -type d -exec chmod 755 {} \;
- find debian/bitlbee-dev/usr/share/ -type f -exec chmod 644 {} \;
+ patch debian/bitlbee/etc/bitlbee/bitlbee.conf debian/patches/bitlbee.conf.diff
+ patch debian/bitlbee-libpurple/etc/bitlbee/bitlbee.conf debian/patches/bitlbee.conf.diff
- cp debian/changelog debian/bitlbee-dev/usr/share/doc/bitlbee-dev/changelog.Debian
- gzip -9 debian/bitlbee-dev/usr/share/doc/bitlbee-dev/changelog.Debian
- cp debian/copyright debian/bitlbee-dev/usr/share/doc/bitlbee-dev/copyright
+ mkdir -p debian/bitlbee-common/usr
+ mv debian/bitlbee/usr/share debian/bitlbee-common/usr
+ rm -rf debian/bitlbee-libpurple/usr/share
- cd debian/bitlbee-dev; \
- find usr -type f -exec md5sum {} \; > DEBIAN/md5sums
+binary-common:
+ dh_testdir
+ dh_testroot
+
+ dh_installdocs --link-doc=bitlbee-common
+ dh_installchangelogs doc/CHANGES
+ dh_installexamples
+ dh_installdebconf
+ dh_installinit
+ifeq ($(DH_OPTIONS),-a)
+ cp -a debian/bitlbee/etc debian/bitlbee-libpurple
+endif
+ dh_installman
+ dh_strip
+ dh_link
+ dh_compress
+ dh_fixperms
+ dh_installdeb
+ifeq ($(DH_OPTIONS),-a)
+ cp -a debian/bitlbee/DEBIAN/{post,pre}* debian/bitlbee-libpurple/DEBIAN
+endif
+ dh_shlibdeps
+ifdef BITLBEE_VERSION
+ echo source:Version=1:$(BITLBEE_VERSION)-0 > debian/substvars
+ dh_gencontrol -- -v1:$(BITLBEE_VERSION)-0 -Vbee:Version=1:$(BITLBEE_VERSION)-0
+else
+ dh_gencontrol -- -Vbee:Version=$(shell dpkg-parsechangelog | grep ^Version: | awk '{print $$2}' | sed -e 's/+[^+]*$$//')
+endif
+ dh_md5sums
+ dh_builddeb
- dpkg-gencontrol -ldebian/changelog -isp -pbitlbee-dev -Pdebian/bitlbee-dev
+binary-indep: build install
+ $(MAKE) -f debian/rules DH_OPTIONS=-i binary-common
- dpkg --build debian/bitlbee-dev ..
+binary-arch: build install
+ $(MAKE) -f debian/rules DH_OPTIONS=-a binary-common
-binary: binary-arch binary-indep
-build: build-arch
-install: install-arch install-indep
+binary-%: build install
+ make -f debian/rules binary-common DH_OPTIONS=-p$*
-.PHONY: build-arch build clean binary-arch binary install-arch install binary-indep install-indep
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary-common binary install
diff --git a/doc/CHANGES b/doc/CHANGES
index 1cac2dc7..45b16e28 100644
--- a/doc/CHANGES
+++ b/doc/CHANGES
@@ -3,6 +3,33 @@ 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, 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.
+- Allow saving MSN display names locally since sometimes this stuff breaks
+ server-side. (Use the local_display_name per-account setting.)
+- Suppress empty "Headline:" messages for certain new XMPP broadcast
+ messages.
+- 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 19 Apr 2010
+
Version 1.2.5:
- Many bug fixes, including a fix for MSN login issues, Jabber login timing
issues, Yahoo! crashes at login time with huge contact lists,
@@ -22,7 +49,7 @@ Version 1.2.5:
routing issues on Jabber (i.e. messages going someone's phone instead of
the main client).
-Fixed 17 Mar 2010
+Finished 17 Mar 2010
Version 1.2.4:
- Most important change (and main reason for releasing now): Upgraded Yahoo!
diff --git a/doc/Makefile b/doc/Makefile
index 9b473df3..5f59879e 100644
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -1,4 +1,7 @@
-include ../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)doc/
+endif
all:
# Only build the docs if this is a bzr checkout
@@ -6,8 +9,8 @@ all:
install:
mkdir -p $(DESTDIR)$(MANDIR)/man8/ $(DESTDIR)$(MANDIR)/man5/
- install -m 0644 bitlbee.8 $(DESTDIR)$(MANDIR)/man8/
- install -m 0644 bitlbee.conf.5 $(DESTDIR)$(MANDIR)/man5/
+ install -m 0644 $(SRCDIR)bitlbee.8 $(DESTDIR)$(MANDIR)/man8/
+ install -m 0644 $(SRCDIR)bitlbee.conf.5 $(DESTDIR)$(MANDIR)/man5/
$(MAKE) -C user-guide $@
uninstall:
diff --git a/doc/user-guide/Makefile b/doc/user-guide/Makefile
index 9841de8d..2a80ea6c 100644
--- a/doc/user-guide/Makefile
+++ b/doc/user-guide/Makefile
@@ -1,4 +1,8 @@
-include ../../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)doc/user-guide/
+endif
+
EXTRAPARANEWLINE = 1
# EXTRAPARANEWLINE = 0
@@ -37,7 +41,7 @@ install:
mkdir -p $(DESTDIR)$(DATADIR)
chmod 0755 $(DESTDIR)$(DATADIR)
rm -f $(DESTDIR)$(DATADIR)/help.txt # Prevent help function from breaking in running sessions
- install -m 0644 help.txt $(DESTDIR)$(DATADIR)/help.txt
+ install -m 0644 $(SRCDIR)help.txt $(DESTDIR)$(DATADIR)/help.txt
uninstall:
rm -f $(DESTDIR)$(DATADIR)/help.txt
diff --git a/doc/user-guide/commands.xml b/doc/user-guide/commands.xml
index cd943f7a..f3633971 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 &lt;protocol&gt;</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 &lt;protocol&gt;</emphasis>.
</para>
</description>
@@ -62,6 +62,28 @@
<ircline nick="root">Account successfully added</ircline>
</ircexample>
</bitlbee-command>
+
+ <bitlbee-command name="twitter">
+ <syntax>account add twitter &lt;handle&gt; &lt;password&gt;</syntax>
+
+ <description>
+ <para>
+ This module gives you simple access to Twitter. Although it uses the Twitter API, only Twitter itself is supported at the moment.
+ </para>
+
+ <para>
+ 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 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>
<bitlbee-command name="yahoo">
<syntax>account add yahoo &lt;handle&gt; &lt;password&gt;</syntax>
@@ -400,7 +422,7 @@
</bitlbee-setting>
<bitlbee-setting name="auto_reconnect" type="boolean" scope="both">
- <default>false</default>
+ <default>true</default>
<description>
<para>
@@ -559,6 +581,16 @@
</description>
</bitlbee-setting>
+ <bitlbee-setting name="display_timestamps" type="boolean" scope="global">
+ <default>true</default>
+
+ <description>
+ <para>
+ When incoming messages are old (i.e. offline messages and channel backlogs), BitlBee will prepend them with a timestamp. If you find them ugly or useless, you can use this setting to hide them.
+ </para>
+ </description>
+ </bitlbee-setting>
+
<bitlbee-setting name="handle_unknown" type="string" scope="global">
<default>root</default>
<possible-values>root, add, add_private, add_channel, ignore</possible-values>
@@ -608,6 +640,17 @@
</bitlbee-setting>
+ <bitlbee-setting name="local_display_name" type="boolean" scope="account">
+ <default>false</default>
+
+ <description>
+ <para>
+ Mostly meant to work around a bug in MSN servers (forgetting the display name set by the user), this setting tells BitlBee to store your display name locally and set this name on the MSN servers when connecting.
+ </para>
+ </description>
+
+ </bitlbee-setting>
+
<bitlbee-setting name="mail_notifications" type="boolean" scope="account">
<default>false</default>
@@ -619,6 +662,41 @@
</bitlbee-setting>
+ <bitlbee-setting name="message_length" type="integer" scope="account">
+ <default>140</default>
+
+ <description>
+ <para>
+ Since Twitter rejects messages longer than 140 characters, BitlBee can count message length and emit a warning instead of waiting for Twitter to reject it.
+ </para>
+
+ <para>
+ You can change this limit here but this won't disable length checks on Twitter's side. You can also set it to 0 to disable the check in case you believe BitlBee doesn't count the characters correctly.
+ </para>
+ </description>
+
+ </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>
@@ -643,6 +721,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>
@@ -778,6 +875,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>
@@ -827,6 +934,39 @@
</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>
+
+ <description>
+ <para>
+ If message timestamps are available for offline messages or chatroom backlogs, BitlBee will display them as part of the message. By default it will use the local timezone. If you're not in the same timezone as the BitlBee server, you can adjust the timestamps using this setting.
+ </para>
+
+ <para>
+ Values local/utc/gmt should be self-explanatory. timezone-spec is a time offset in hours:minutes, for example: -8 for Pacific Standard Time, +2 for Central European Summer Time, +5:30 for Indian Standard Time.
+ </para>
+ </description>
+ </bitlbee-setting>
+
<bitlbee-setting name="tls" type="boolean" scope="account">
<default>try</default>
diff --git a/help.c b/help.c
index 587b9940..c43d0459 100644
--- a/help.c
+++ b/help.c
@@ -1,7 +1,7 @@
/********************************************************************\
* BitlBee -- An IRC to other IM-networks gateway *
* *
- * Copyright 2002-2005 Wilmer van der Gaast and others *
+ * Copyright 2002-2009 Wilmer van der Gaast and others *
\********************************************************************/
/* Help file control */
@@ -168,3 +168,27 @@ char *help_get( help_t **help, char *title )
return NULL;
}
+
+int help_add_mem( help_t **help, const char *title, const char *content )
+{
+ help_t *h, *l = NULL;
+
+ for( h = *help; h; h = h->next )
+ {
+ if( g_strcasecmp( h->title, title ) == 0 )
+ return 0;
+
+ l = h;
+ }
+
+ if( l )
+ h = l->next = g_new0( struct help, 1 );
+ else
+ *help = h = g_new0( struct help, 1 );
+ h->fd = -1;
+ h->title = g_strdup( title );
+ h->length = strlen( content );
+ h->offset.mem_offset = g_strdup( content );
+
+ return 1;
+}
diff --git a/help.h b/help.h
index 5421220c..e689a93d 100644
--- a/help.h
+++ b/help.h
@@ -45,5 +45,6 @@ typedef struct help
G_GNUC_MALLOC help_t *help_init( help_t **help, const char *helpfile );
void help_free( help_t **help );
char *help_get( help_t **help, char *title );
+int help_add_mem( help_t **help, const char *title, const char *content_ );
#endif
diff --git a/ipc.c b/ipc.c
index d6b850f1..54053322 100644
--- a/ipc.c
+++ b/ipc.c
@@ -513,7 +513,7 @@ static gboolean new_ipc_client( gpointer data, gint serversock, b_input_conditio
return TRUE;
}
- child->ipc_inpa = b_input_add( child->ipc_fd, GAIM_INPUT_READ, ipc_master_read, child );
+ child->ipc_inpa = b_input_add( child->ipc_fd, B_EV_IO_READ, ipc_master_read, child );
child_list = g_slist_append( child_list, child );
@@ -551,7 +551,7 @@ int ipc_master_listen_socket()
return 0;
}
- b_input_add( serversock, GAIM_INPUT_READ, new_ipc_client, NULL );
+ b_input_add( serversock, B_EV_IO_READ, new_ipc_client, NULL );
return 1;
}
@@ -596,7 +596,7 @@ int ipc_master_load_state( char *statefile )
fclose( fp );
return 0;
}
- child->ipc_inpa = b_input_add( child->ipc_fd, GAIM_INPUT_READ, ipc_master_read, child );
+ child->ipc_inpa = b_input_add( child->ipc_fd, B_EV_IO_READ, ipc_master_read, child );
child_list = g_slist_append( child_list, child );
}
diff --git a/irc.c b/irc.c
index edb42dde..5105a7ff 100644
--- a/irc.c
+++ b/irc.c
@@ -51,18 +51,34 @@ static char *set_eval_password( set_t *set, char *value )
static char *set_eval_charset( set_t *set, char *value )
{
irc_t *irc = set->data;
+ char *test;
+ gsize test_bytes = 0;
GIConv ic, oc;
if( g_strcasecmp( value, "none" ) == 0 )
value = g_strdup( "utf-8" );
- if( ( ic = g_iconv_open( "utf-8", value ) ) == (GIConv) -1 )
+ if( ( oc = g_iconv_open( value, "utf-8" ) ) == (GIConv) -1 )
{
return NULL;
}
- if( ( oc = g_iconv_open( value, "utf-8" ) ) == (GIConv) -1 )
+
+ /* Do a test iconv to see if the user picked an IRC-compatible
+ charset (for example utf-16 goes *horribly* wrong). */
+ if( ( test = g_convert_with_iconv( " ", 1, oc, NULL, &test_bytes, NULL ) ) == NULL ||
+ test_bytes > 1 )
+ {
+ g_free( test );
+ g_iconv_close( oc );
+ irc_usermsg( irc, "Unsupported character set: The IRC protocol "
+ "only supports 8-bit character sets." );
+ return NULL;
+ }
+ g_free( test );
+
+ if( ( ic = g_iconv_open( "utf-8", value ) ) == (GIConv) -1 )
{
- g_iconv_close( ic );
+ g_iconv_close( oc );
return NULL;
}
@@ -108,7 +124,7 @@ irc_t *irc_new( int fd )
irc->fd = fd;
sock_make_nonblocking( irc->fd );
- irc->r_watch_source_id = b_input_add( irc->fd, GAIM_INPUT_READ, bitlbee_io_current_client_read, irc );
+ irc->r_watch_source_id = b_input_add( irc->fd, B_EV_IO_READ, bitlbee_io_current_client_read, irc );
irc->status = USTATUS_OFFLINE;
irc->last_pong = gettime();
@@ -174,6 +190,7 @@ irc_t *irc_new( int fd )
s = set_add( &irc->set, "debug", "false", set_eval_bool, irc );
s = set_add( &irc->set, "default_target", "root", NULL, irc );
s = set_add( &irc->set, "display_namechanges", "false", set_eval_bool, irc );
+ s = set_add( &irc->set, "display_timestamps", "true", set_eval_bool, irc );
s = set_add( &irc->set, "handle_unknown", "root", NULL, irc );
s = set_add( &irc->set, "lcnicks", "true", set_eval_bool, irc );
s = set_add( &irc->set, "ops", "both", set_eval_ops, irc );
@@ -183,10 +200,12 @@ irc_t *irc_new( int fd )
s = set_add( &irc->set, "query_order", "lifo", NULL, irc );
s = set_add( &irc->set, "root_nick", irc->mynick, set_eval_root_nick, irc );
s = set_add( &irc->set, "save_on_quit", "true", set_eval_bool, irc );
+ s = set_add( &irc->set, "show_offline", "false", set_eval_bool, irc );
s = set_add( &irc->set, "simulate_netsplit", "true", set_eval_bool, irc );
s = set_add( &irc->set, "status", NULL, set_eval_away_status, irc );
s->flags |= SET_NULL_OK;
s = set_add( &irc->set, "strip_html", "true", NULL, irc );
+ s = set_add( &irc->set, "timezone", "local", set_eval_timezone, irc );
s = set_add( &irc->set, "to_char", ": ", set_eval_to_char, irc );
s = set_add( &irc->set, "typing_notice", "false", set_eval_bool, irc );
@@ -195,6 +214,8 @@ irc_t *irc_new( int fd )
/* Evaluator sets the iconv/oconv structures. */
set_eval_charset( set_find( &irc->set, "charset" ), set_getstr( &irc->set, "charset" ) );
+ nogaim_init();
+
return( irc );
}
@@ -676,10 +697,10 @@ void irc_vawrite( irc_t *irc, char *format, va_list params )
the queue. If it's FALSE, we emptied the buffer and saved ourselves some work
in the event queue. */
/* Really can't be done as long as the code doesn't do error checking very well:
- if( bitlbee_io_current_client_write( irc, irc->fd, GAIM_INPUT_WRITE ) ) */
+ if( bitlbee_io_current_client_write( irc, irc->fd, B_EV_IO_WRITE ) ) */
/* So just always do it via the event handler. */
- irc->w_watch_source_id = b_input_add( irc->fd, GAIM_INPUT_WRITE, bitlbee_io_current_client_write, irc );
+ irc->w_watch_source_id = b_input_add( irc->fd, B_EV_IO_WRITE, bitlbee_io_current_client_write, irc );
}
return;
@@ -705,7 +726,7 @@ void irc_write_all( int now, char *format, ... )
irc_vawrite( temp->data, format, params );
if( now )
{
- bitlbee_io_current_client_write( irc, irc->fd, GAIM_INPUT_WRITE );
+ bitlbee_io_current_client_write( irc, irc->fd, B_EV_IO_WRITE );
}
temp = temp->next;
}
diff --git a/irc_commands.c b/irc_commands.c
index a417e0d9..319d549a 100644
--- a/irc_commands.c
+++ b/irc_commands.c
@@ -497,7 +497,7 @@ static void irc_cmd_whois( irc_t *irc, char **cmd )
else if( u->away )
irc_reply( irc, 301, "%s :%s", u->nick, u->away );
if( u->status_msg )
- irc_reply( irc, 333, "%s :Status: %s", u->nick, u->status_msg );
+ irc_reply( irc, 320, "%s :%s", u->nick, u->status_msg );
irc_reply( irc, 318, "%s :End of /WHOIS list", nick );
}
diff --git a/lib/Makefile b/lib/Makefile
index 3d128b5a..8fd9b19e 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -7,9 +7,12 @@
### DEFINITIONS
-include ../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)lib/
+endif
# [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
@@ -36,6 +39,6 @@ lib.o: $(objects) $(subdirs)
$(objects): ../Makefile.settings Makefile
-$(objects): %.o: %.c
+$(objects): %.o: $(SRCDIR)%.c
@echo '*' Compiling $<
@$(CC) -c $(CFLAGS) $< -o $@
diff --git a/lib/events.h b/lib/events.h
index 4baea7b6..fa30cf27 100644
--- a/lib/events.h
+++ b/lib/events.h
@@ -47,8 +47,10 @@
/* The conditions you can pass to b_input_add()/that will be passed to
the given callback function. */
typedef enum {
- GAIM_INPUT_READ = 1 << 1,
- GAIM_INPUT_WRITE = 1 << 2
+ B_EV_IO_READ = 1 << 0,
+ B_EV_IO_WRITE = 1 << 1,
+ B_EV_FLAG_FORCE_ONCE = 1 << 16,
+ B_EV_FLAG_FORCE_REPEAT = 1 << 17,
} b_input_condition;
typedef gboolean (*b_event_handler)(gpointer data, gint fd, b_input_condition cond);
diff --git a/lib/events_glib.c b/lib/events_glib.c
index 3e194e98..d6ac82cc 100644
--- a/lib/events_glib.c
+++ b/lib/events_glib.c
@@ -48,6 +48,7 @@
typedef struct _GaimIOClosure {
b_event_handler function;
gpointer data;
+ guint flags;
} GaimIOClosure;
static GMainLoop *loop = NULL;
@@ -75,9 +76,9 @@ static gboolean gaim_io_invoke(GIOChannel *source, GIOCondition condition, gpoin
gboolean st;
if (condition & GAIM_READ_COND)
- gaim_cond |= GAIM_INPUT_READ;
+ gaim_cond |= B_EV_IO_READ;
if (condition & GAIM_WRITE_COND)
- gaim_cond |= GAIM_INPUT_WRITE;
+ gaim_cond |= B_EV_IO_WRITE;
event_debug( "gaim_io_invoke( %d, %d, 0x%x )\n", g_io_channel_unix_get_fd(source), condition, data );
@@ -86,7 +87,12 @@ static gboolean gaim_io_invoke(GIOChannel *source, GIOCondition condition, gpoin
if( !st )
event_debug( "Returned FALSE, cancelling.\n" );
- return st;
+ if (closure->flags & B_EV_FLAG_FORCE_ONCE)
+ return FALSE;
+ else if (closure->flags & B_EV_FLAG_FORCE_REPEAT)
+ return TRUE;
+ else
+ return st;
}
static void gaim_io_destroy(gpointer data)
@@ -104,10 +110,11 @@ gint b_input_add(gint source, b_input_condition condition, b_event_handler funct
closure->function = function;
closure->data = data;
+ closure->flags = condition;
- if (condition & GAIM_INPUT_READ)
+ if (condition & B_EV_IO_READ)
cond |= GAIM_READ_COND;
- if (condition & GAIM_INPUT_WRITE)
+ if (condition & B_EV_IO_WRITE)
cond |= GAIM_WRITE_COND;
channel = g_io_channel_unix_new(source);
diff --git a/lib/events_libevent.c b/lib/events_libevent.c
index cf616576..43d770ea 100644
--- a/lib/events_libevent.c
+++ b/lib/events_libevent.c
@@ -59,6 +59,7 @@ struct b_event_data
gint timeout;
b_event_handler function;
void *data;
+ guint flags;
};
void b_main_init()
@@ -125,9 +126,9 @@ static void b_event_passthrough( int fd, short event, void *data )
if( fd >= 0 )
{
if( event & EV_READ )
- cond |= GAIM_INPUT_READ;
+ cond |= B_EV_IO_READ;
if( event & EV_WRITE )
- cond |= GAIM_INPUT_WRITE;
+ cond |= B_EV_IO_WRITE;
}
event_debug( "b_event_passthrough( %d, %d, 0x%x ) (%d)\n", fd, event, (int) data, b_ev->id );
@@ -149,7 +150,7 @@ static void b_event_passthrough( int fd, short event, void *data )
/* This event was killed already, don't touch it! */
return;
}
- else if( !st )
+ else if( !st && !( b_ev->flags & B_EV_FLAG_FORCE_REPEAT ) )
{
event_debug( "Handler returned FALSE: " );
b_event_remove( id_cur );
@@ -173,8 +174,8 @@ gint b_input_add( gint fd, b_input_condition condition, b_event_handler function
event_debug( "b_input_add( %d, %d, 0x%x, 0x%x ) ", fd, condition, function, data );
- if( ( condition & GAIM_INPUT_READ && ( b_ev = g_hash_table_lookup( read_hash, &fd ) ) ) ||
- ( condition & GAIM_INPUT_WRITE && ( b_ev = g_hash_table_lookup( write_hash, &fd ) ) ) )
+ if( ( condition & B_EV_IO_READ && ( b_ev = g_hash_table_lookup( read_hash, &fd ) ) ) ||
+ ( condition & B_EV_IO_WRITE && ( b_ev = g_hash_table_lookup( write_hash, &fd ) ) ) )
{
/* We'll stick with this libevent entry, but give it a new BitlBee id. */
g_hash_table_remove( id_hash, &b_ev->id );
@@ -197,9 +198,9 @@ gint b_input_add( gint fd, b_input_condition condition, b_event_handler function
b_ev->data = data;
out_cond = EV_PERSIST;
- if( condition & GAIM_INPUT_READ )
+ if( condition & B_EV_IO_READ )
out_cond |= EV_READ;
- if( condition & GAIM_INPUT_WRITE )
+ if( condition & B_EV_IO_WRITE )
out_cond |= EV_WRITE;
event_set( &b_ev->evinfo, fd, out_cond, b_event_passthrough, b_ev );
@@ -211,6 +212,7 @@ gint b_input_add( gint fd, b_input_condition condition, b_event_handler function
g_hash_table_insert( write_hash, &b_ev->evinfo.ev_fd, b_ev );
}
+ b_ev->flags = condition;
g_hash_table_insert( id_hash, &b_ev->id, b_ev );
return b_ev->id;
}
diff --git a/lib/http_client.c b/lib/http_client.c
index aae5645b..e9d3c1bb 100644
--- a/lib/http_client.c
+++ b/lib/http_client.c
@@ -148,10 +148,10 @@ static gboolean http_connected( gpointer data, int source, b_input_condition con
if( req->bytes_written < req->request_length )
req->inpa = b_input_add( source,
- req->ssl ? ssl_getdirection( req->ssl ) : GAIM_INPUT_WRITE,
+ req->ssl ? ssl_getdirection( req->ssl ) : B_EV_IO_WRITE,
http_connected, req );
else
- req->inpa = b_input_add( source, GAIM_INPUT_READ, http_incoming_data, req );
+ req->inpa = b_input_add( source, B_EV_IO_READ, http_incoming_data, req );
return FALSE;
@@ -233,7 +233,7 @@ static gboolean http_incoming_data( gpointer data, int source, b_input_condition
/* There will be more! */
req->inpa = b_input_add( req->fd,
- req->ssl ? ssl_getdirection( req->ssl ) : GAIM_INPUT_READ,
+ req->ssl ? ssl_getdirection( req->ssl ) : B_EV_IO_READ,
http_incoming_data, req );
return FALSE;
diff --git a/lib/misc.c b/lib/misc.c
index fe2ff17c..c56b31f3 100644
--- a/lib/misc.c
+++ b/lib/misc.c
@@ -78,6 +78,41 @@ time_t get_time(int year, int month, int day, int hour, int min, int sec)
return mktime(&tm);
}
+time_t mktime_utc( struct tm *tp )
+{
+ struct tm utc;
+ time_t res, tres;
+
+ tp->tm_isdst = -1;
+ res = mktime( tp );
+ /* Problem is, mktime() just gave us the GMT timestamp for the
+ given local time... While the given time WAS NOT local. So
+ we should fix this now.
+
+ Now I could choose between messing with environment variables
+ (kludgy) or using timegm() (not portable)... Or doing the
+ following, which I actually prefer...
+
+ tzset() may also work but in other places I actually want to
+ use local time.
+
+ FFFFFFFFFFFFFFFFFFFFFUUUUUUUUUUUUUUUUUUUU!! */
+ gmtime_r( &res, &utc );
+ utc.tm_isdst = -1;
+ if( utc.tm_hour == tp->tm_hour && utc.tm_min == tp->tm_min )
+ /* Sweet! We're in UTC right now... */
+ return res;
+
+ tres = mktime( &utc );
+ res += res - tres;
+
+ /* Yes, this is a hack. And it will go wrong around DST changes.
+ BUT this is more likely to be threadsafe than messing with
+ environment variables, and possibly more portable... */
+
+ return res;
+}
+
typedef struct htmlentity
{
char code[7];
@@ -270,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/misc.h b/lib/misc.h
index ce36caf5..9f2058b6 100644
--- a/lib/misc.h
+++ b/lib/misc.h
@@ -42,6 +42,7 @@ G_MODULE_EXPORT char *add_cr( char *text );
G_MODULE_EXPORT char *strip_newlines(char *source);
G_MODULE_EXPORT time_t get_time( int year, int month, int day, int hour, int min, int sec );
+G_MODULE_EXPORT time_t mktime_utc( struct tm *tp );
double gettime( void );
G_MODULE_EXPORT void strip_html( char *msg );
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&params, 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( &params, oi->sp );
+
+ params_s = oauth_params_string( params );
+ oauth_params_free( &params );
+
+ 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( &params, "oauth_callback", "oob" );
+
+ if( !oauth_post_request( sp->url_request_token, &params, 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( &params, req->reply_body );
+ st->request_token = g_strdup( oauth_params_get( &params, "oauth_token" ) );
+ oauth_params_free( &params );
+ }
+
+ 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( &params, "oauth_token", st->request_token );
+ oauth_params_add( &params, "oauth_verifier", pin );
+
+ return oauth_post_request( st->sp->url_access_token, &params, 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( &params, req->reply_body );
+ st->token = g_strdup( oauth_params_get( &params, "oauth_token" ) );
+ st->token_secret = g_strdup( oauth_params_get( &params, "oauth_token_secret" ) );
+ oauth_params_free( &params );
+ }
+
+ 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( &params, "oauth_token", oi->token );
+ oauth_add_default_params( &params, 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( &params, args );
+ if( ( s = strchr( url, '?' ) ) )
+ {
+ s = g_strdup( s + 1 );
+ oauth_params_parse( &params, 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( &params );
+ 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( &params, "oauth_token", oi->token );
+ oauth_params_add( &params, "oauth_token_secret", oi->token_secret );
+ ret = oauth_params_string( params );
+ oauth_params_free( &params );
+
+ 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( &params, in );
+ oi->token = g_strdup( oauth_params_get( &params, "oauth_token" ) );
+ oi->token_secret = g_strdup( oauth_params_get( &params, "oauth_token_secret" ) );
+ oauth_params_free( &params );
+ 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 );
diff --git a/lib/proxy.c b/lib/proxy.c
index e52837fe..baf5823a 100644
--- a/lib/proxy.c
+++ b/lib/proxy.c
@@ -90,9 +90,9 @@ static gboolean gaim_io_connected(gpointer data, gint source, b_input_condition
closesocket(source);
b_event_remove(phb->inpa);
if( phb->proxy_func )
- phb->proxy_func(phb->proxy_data, -1, GAIM_INPUT_READ);
+ phb->proxy_func(phb->proxy_data, -1, B_EV_IO_READ);
else {
- phb->func(phb->data, -1, GAIM_INPUT_READ);
+ phb->func(phb->data, -1, B_EV_IO_READ);
g_free(phb);
}
return FALSE;
@@ -101,9 +101,9 @@ static gboolean gaim_io_connected(gpointer data, gint source, b_input_condition
sock_make_blocking(source);
b_event_remove(phb->inpa);
if( phb->proxy_func )
- phb->proxy_func(phb->proxy_data, source, GAIM_INPUT_READ);
+ phb->proxy_func(phb->proxy_data, source, B_EV_IO_READ);
else {
- phb->func(phb->data, source, GAIM_INPUT_READ);
+ phb->func(phb->data, source, B_EV_IO_READ);
g_free(phb);
}
@@ -146,7 +146,7 @@ static int proxy_connect_none(const char *host, unsigned short port, struct PHB
return -1;
} else {
- phb->inpa = b_input_add(fd, GAIM_INPUT_WRITE, gaim_io_connected, phb);
+ phb->inpa = b_input_add(fd, B_EV_IO_WRITE, gaim_io_connected, phb);
phb->fd = fd;
return fd;
@@ -178,14 +178,14 @@ static gboolean http_canread(gpointer data, gint source, b_input_condition cond)
if ((memcmp(HTTP_GOODSTRING, inputline, strlen(HTTP_GOODSTRING)) == 0) ||
(memcmp(HTTP_GOODSTRING2, inputline, strlen(HTTP_GOODSTRING2)) == 0)) {
- phb->func(phb->data, source, GAIM_INPUT_READ);
+ phb->func(phb->data, source, B_EV_IO_READ);
g_free(phb->host);
g_free(phb);
return FALSE;
}
close(source);
- phb->func(phb->data, -1, GAIM_INPUT_READ);
+ phb->func(phb->data, -1, B_EV_IO_READ);
g_free(phb->host);
g_free(phb);
@@ -203,7 +203,7 @@ static gboolean http_canwrite(gpointer data, gint source, b_input_condition cond
len = sizeof(error);
if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
close(source);
- phb->func(phb->data, -1, GAIM_INPUT_READ);
+ phb->func(phb->data, -1, B_EV_IO_READ);
g_free(phb->host);
g_free(phb);
return FALSE;
@@ -214,7 +214,7 @@ static gboolean http_canwrite(gpointer data, gint source, b_input_condition cond
phb->host, phb->port);
if (send(source, cmd, strlen(cmd), 0) < 0) {
close(source);
- phb->func(phb->data, -1, GAIM_INPUT_READ);
+ phb->func(phb->data, -1, B_EV_IO_READ);
g_free(phb->host);
g_free(phb);
return FALSE;
@@ -229,7 +229,7 @@ static gboolean http_canwrite(gpointer data, gint source, b_input_condition cond
g_free(t2);
if (send(source, cmd, strlen(cmd), 0) < 0) {
close(source);
- phb->func(phb->data, -1, GAIM_INPUT_READ);
+ phb->func(phb->data, -1, B_EV_IO_READ);
g_free(phb->host);
g_free(phb);
return FALSE;
@@ -239,13 +239,13 @@ static gboolean http_canwrite(gpointer data, gint source, b_input_condition cond
g_snprintf(cmd, sizeof(cmd), "\r\n");
if (send(source, cmd, strlen(cmd), 0) < 0) {
close(source);
- phb->func(phb->data, -1, GAIM_INPUT_READ);
+ phb->func(phb->data, -1, B_EV_IO_READ);
g_free(phb->host);
g_free(phb);
return FALSE;
}
- phb->inpa = b_input_add(source, GAIM_INPUT_READ, http_canread, phb);
+ phb->inpa = b_input_add(source, B_EV_IO_READ, http_canread, phb);
return FALSE;
}
@@ -272,14 +272,14 @@ static gboolean s4_canread(gpointer data, gint source, b_input_condition cond)
memset(packet, 0, sizeof(packet));
if (read(source, packet, 9) >= 4 && packet[1] == 90) {
- phb->func(phb->data, source, GAIM_INPUT_READ);
+ phb->func(phb->data, source, B_EV_IO_READ);
g_free(phb->host);
g_free(phb);
return FALSE;
}
close(source);
- phb->func(phb->data, -1, GAIM_INPUT_READ);
+ phb->func(phb->data, -1, B_EV_IO_READ);
g_free(phb->host);
g_free(phb);
@@ -298,7 +298,7 @@ static gboolean s4_canwrite(gpointer data, gint source, b_input_condition cond)
len = sizeof(error);
if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
close(source);
- phb->func(phb->data, -1, GAIM_INPUT_READ);
+ phb->func(phb->data, -1, B_EV_IO_READ);
g_free(phb->host);
g_free(phb);
return FALSE;
@@ -308,7 +308,7 @@ static gboolean s4_canwrite(gpointer data, gint source, b_input_condition cond)
/* XXX does socks4 not support host name lookups by the proxy? */
if (!(hp = gethostbyname(phb->host))) {
close(source);
- phb->func(phb->data, -1, GAIM_INPUT_READ);
+ phb->func(phb->data, -1, B_EV_IO_READ);
g_free(phb->host);
g_free(phb);
return FALSE;
@@ -325,13 +325,13 @@ static gboolean s4_canwrite(gpointer data, gint source, b_input_condition cond)
packet[8] = 0;
if (write(source, packet, 9) != 9) {
close(source);
- phb->func(phb->data, -1, GAIM_INPUT_READ);
+ phb->func(phb->data, -1, B_EV_IO_READ);
g_free(phb->host);
g_free(phb);
return FALSE;
}
- phb->inpa = b_input_add(source, GAIM_INPUT_READ, s4_canread, phb);
+ phb->inpa = b_input_add(source, B_EV_IO_READ, s4_canread, phb);
return FALSE;
}
@@ -358,20 +358,20 @@ static gboolean s5_canread_again(gpointer data, gint source, b_input_condition c
if (read(source, buf, 10) < 10) {
close(source);
- phb->func(phb->data, -1, GAIM_INPUT_READ);
+ phb->func(phb->data, -1, B_EV_IO_READ);
g_free(phb->host);
g_free(phb);
return FALSE;
}
if ((buf[0] != 0x05) || (buf[1] != 0x00)) {
close(source);
- phb->func(phb->data, -1, GAIM_INPUT_READ);
+ phb->func(phb->data, -1, B_EV_IO_READ);
g_free(phb->host);
g_free(phb);
return FALSE;
}
- phb->func(phb->data, source, GAIM_INPUT_READ);
+ phb->func(phb->data, source, B_EV_IO_READ);
g_free(phb->host);
g_free(phb);
@@ -395,13 +395,13 @@ static void s5_sendconnect(gpointer data, gint source)
if (write(source, buf, (5 + strlen(phb->host) + 2)) < (5 + strlen(phb->host) + 2)) {
close(source);
- phb->func(phb->data, -1, GAIM_INPUT_READ);
+ phb->func(phb->data, -1, B_EV_IO_READ);
g_free(phb->host);
g_free(phb);
return;
}
- phb->inpa = b_input_add(source, GAIM_INPUT_READ, s5_canread_again, phb);
+ phb->inpa = b_input_add(source, B_EV_IO_READ, s5_canread_again, phb);
}
static gboolean s5_readauth(gpointer data, gint source, b_input_condition cond)
@@ -413,7 +413,7 @@ static gboolean s5_readauth(gpointer data, gint source, b_input_condition cond)
if (read(source, buf, 2) < 2) {
close(source);
- phb->func(phb->data, -1, GAIM_INPUT_READ);
+ phb->func(phb->data, -1, B_EV_IO_READ);
g_free(phb->host);
g_free(phb);
return FALSE;
@@ -421,7 +421,7 @@ static gboolean s5_readauth(gpointer data, gint source, b_input_condition cond)
if ((buf[0] != 0x01) || (buf[1] != 0x00)) {
close(source);
- phb->func(phb->data, -1, GAIM_INPUT_READ);
+ phb->func(phb->data, -1, B_EV_IO_READ);
g_free(phb->host);
g_free(phb);
return FALSE;
@@ -441,7 +441,7 @@ static gboolean s5_canread(gpointer data, gint source, b_input_condition cond)
if (read(source, buf, 2) < 2) {
close(source);
- phb->func(phb->data, -1, GAIM_INPUT_READ);
+ phb->func(phb->data, -1, B_EV_IO_READ);
g_free(phb->host);
g_free(phb);
return FALSE;
@@ -449,7 +449,7 @@ static gboolean s5_canread(gpointer data, gint source, b_input_condition cond)
if ((buf[0] != 0x05) || (buf[1] == 0xff)) {
close(source);
- phb->func(phb->data, -1, GAIM_INPUT_READ);
+ phb->func(phb->data, -1, B_EV_IO_READ);
g_free(phb->host);
g_free(phb);
return FALSE;
@@ -464,13 +464,13 @@ static gboolean s5_canread(gpointer data, gint source, b_input_condition cond)
memcpy(buf + 2 + i + 1, proxypass, j);
if (write(source, buf, 3 + i + j) < 3 + i + j) {
close(source);
- phb->func(phb->data, -1, GAIM_INPUT_READ);
+ phb->func(phb->data, -1, B_EV_IO_READ);
g_free(phb->host);
g_free(phb);
return FALSE;
}
- phb->inpa = b_input_add(source, GAIM_INPUT_READ, s5_readauth, phb);
+ phb->inpa = b_input_add(source, B_EV_IO_READ, s5_readauth, phb);
} else {
s5_sendconnect(phb, source);
}
@@ -490,7 +490,7 @@ static gboolean s5_canwrite(gpointer data, gint source, b_input_condition cond)
len = sizeof(error);
if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
close(source);
- phb->func(phb->data, -1, GAIM_INPUT_READ);
+ phb->func(phb->data, -1, B_EV_IO_READ);
g_free(phb->host);
g_free(phb);
return FALSE;
@@ -512,13 +512,13 @@ static gboolean s5_canwrite(gpointer data, gint source, b_input_condition cond)
if (write(source, buf, i) < i) {
close(source);
- phb->func(phb->data, -1, GAIM_INPUT_READ);
+ phb->func(phb->data, -1, B_EV_IO_READ);
g_free(phb->host);
g_free(phb);
return FALSE;
}
- phb->inpa = b_input_add(source, GAIM_INPUT_READ, s5_canread, phb);
+ phb->inpa = b_input_add(source, B_EV_IO_READ, s5_canread, phb);
return FALSE;
}
diff --git a/lib/ssl_bogus.c b/lib/ssl_bogus.c
index a07ea752..9c368c66 100644
--- a/lib/ssl_bogus.c
+++ b/lib/ssl_bogus.c
@@ -58,7 +58,7 @@ void *ssl_starttls( int fd, ssl_input_function func, gpointer data )
b_input_condition ssl_getdirection( void *conn )
{
- return GAIM_INPUT_READ;
+ return B_EV_IO_READ;
}
int ssl_pending( void *conn )
diff --git a/lib/ssl_client.h b/lib/ssl_client.h
index f91d0d70..0a8e82d8 100644
--- a/lib/ssl_client.h
+++ b/lib/ssl_client.h
@@ -70,7 +70,7 @@ G_MODULE_EXPORT void ssl_disconnect( void *conn_ );
handling. */
G_MODULE_EXPORT int ssl_getfd( void *conn );
-/* This function returns GAIM_INPUT_READ/WRITE. With SSL connections it's
+/* This function returns B_EV_IO_READ/WRITE. With SSL connections it's
possible that something has to be read while actually were trying to
write something (think about key exchange/refresh/etc). So when an
SSL operation returned SSL_AGAIN, *always* use this function when
diff --git a/lib/ssl_gnutls.c b/lib/ssl_gnutls.c
index f5945442..5a14b825 100644
--- a/lib/ssl_gnutls.c
+++ b/lib/ssl_gnutls.c
@@ -105,7 +105,7 @@ static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition
{
struct scd *conn = data;
- return ssl_connected( conn, conn->fd, GAIM_INPUT_WRITE );
+ return ssl_connected( conn, conn->fd, B_EV_IO_WRITE );
}
static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond )
@@ -243,5 +243,5 @@ int ssl_getfd( void *conn )
b_input_condition ssl_getdirection( void *conn )
{
return( gnutls_record_get_direction( ((struct scd*)conn)->session ) ?
- GAIM_INPUT_WRITE : GAIM_INPUT_READ );
+ B_EV_IO_WRITE : B_EV_IO_READ );
}
diff --git a/lib/ssl_nss.c b/lib/ssl_nss.c
index eba3c441..de6e7ec6 100644
--- a/lib/ssl_nss.c
+++ b/lib/ssl_nss.c
@@ -192,5 +192,5 @@ int ssl_getfd( void *conn )
b_input_condition ssl_getdirection( void *conn )
{
/* Just in case someone calls us, let's return the most likely case: */
- return GAIM_INPUT_READ;
+ return B_EV_IO_READ;
}
diff --git a/lib/ssl_openssl.c b/lib/ssl_openssl.c
index fc6d433e..8abff390 100644
--- a/lib/ssl_openssl.c
+++ b/lib/ssl_openssl.c
@@ -101,7 +101,7 @@ static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition
{
struct scd *conn = data;
- return ssl_connected( conn, conn->fd, GAIM_INPUT_WRITE );
+ return ssl_connected( conn, conn->fd, B_EV_IO_WRITE );
}
static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond )
@@ -269,5 +269,5 @@ int ssl_getfd( void *conn )
b_input_condition ssl_getdirection( void *conn )
{
- return( ((struct scd*)conn)->lasterr == SSL_ERROR_WANT_WRITE ? GAIM_INPUT_WRITE : GAIM_INPUT_READ );
+ return( ((struct scd*)conn)->lasterr == SSL_ERROR_WANT_WRITE ? B_EV_IO_WRITE : B_EV_IO_READ );
}
diff --git a/lib/ssl_sspi.c b/lib/ssl_sspi.c
index a16423b1..e14c451e 100644
--- a/lib/ssl_sspi.c
+++ b/lib/ssl_sspi.c
@@ -274,5 +274,5 @@ int ssl_getfd(void *conn)
GaimInputCondition ssl_getdirection( void *conn )
{
- return GAIM_INPUT_WRITE; /* FIXME: or GAIM_INPUT_READ */
+ return B_EV_IO_WRITE; /* FIXME: or B_EV_IO_READ */
}
diff --git a/lib/url.c b/lib/url.c
index de9966b4..9e330f8c 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -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;
diff --git a/lib/url.h b/lib/url.h
index 8c038c91..55107ad2 100644
--- a/lib/url.h
+++ b/lib/url.h
@@ -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/lib/xmltree.c b/lib/xmltree.c
index 67fe46e1..31f8ee9c 100644
--- a/lib/xmltree.c
+++ b/lib/xmltree.c
@@ -448,7 +448,11 @@ struct xt_node *xt_find_node( struct xt_node *node, const char *name )
{
while( node )
{
- if( g_strcasecmp( node->name, name ) == 0 )
+ char *colon;
+
+ if( g_strcasecmp( node->name, name ) == 0 ||
+ ( ( colon = strchr( node->name, ':' ) ) &&
+ g_strcasecmp( colon + 1, name ) == 0 ) )
break;
node = node->next;
@@ -460,6 +464,7 @@ struct xt_node *xt_find_node( struct xt_node *node, const char *name )
char *xt_find_attr( struct xt_node *node, const char *key )
{
int i;
+ char *colon;
if( !node )
return NULL;
@@ -468,6 +473,21 @@ char *xt_find_attr( struct xt_node *node, const char *key )
if( g_strcasecmp( node->attr[i].key, key ) == 0 )
break;
+ /* This is an awful hack that only takes care of namespace prefixes
+ inside a tag. Since IMHO excessive namespace usage in XMPP is
+ massive overkill anyway (this code exists for almost four years
+ now and never really missed it): Meh. */
+ if( !node->attr[i].key && strcmp( key, "xmlns" ) == 0 &&
+ ( colon = strchr( node->name, ':' ) ) )
+ {
+ *colon = '\0';
+ for( i = 0; node->attr[i].key; i ++ )
+ if( strncmp( node->attr[i].key, "xmlns:", 6 ) == 0 &&
+ strcmp( node->attr[i].key + 6, node->name ) == 0 )
+ break;
+ *colon = ':';
+ }
+
return node->attr[i].value;
}
diff --git a/log.c b/log.c
index 4606fb88..d7d971bd 100644
--- a/log.c
+++ b/log.c
@@ -171,5 +171,7 @@ static void log_console(int level, char *message) {
if(level == LOGLVL_DEBUG)
fprintf(stdout, "Debug: %s\n", message);
#endif
+ /* Always log stuff in syslogs too. */
+ log_syslog(level, message);
return;
}
diff --git a/protocols/Makefile b/protocols/Makefile
index 18d79e8d..57fcd7eb 100644
--- a/protocols/Makefile
+++ b/protocols/Makefile
@@ -7,6 +7,9 @@
### DEFINITIONS
-include ../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)protocols/
+endif
# [SH] Program variables
objects = nogaim.o
@@ -48,6 +51,6 @@ protocols.o: $(objects) $(subdirs)
$(objects): ../Makefile.settings Makefile
-$(objects): %.o: %.c
+$(objects): %.o: $(SRCDIR)%.c
@echo '*' Compiling $<
@$(CC) -c $(CFLAGS) $< -o $@
diff --git a/protocols/jabber/Makefile b/protocols/jabber/Makefile
index 78a02696..912ea702 100644
--- a/protocols/jabber/Makefile
+++ b/protocols/jabber/Makefile
@@ -7,6 +7,9 @@
### DEFINITIONS
-include ../../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)protocols/jabber/
+endif
# [SH] Program variables
objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o s5bytestream.o sasl.o si.o
@@ -32,7 +35,7 @@ distclean: clean
$(objects): ../../Makefile.settings Makefile
-$(objects): %.o: %.c
+$(objects): %.o: $(SRCDIR)%.c
@echo '*' Compiling $<
@$(CC) -c $(CFLAGS) $< -o $@
diff --git a/protocols/jabber/conference.c b/protocols/jabber/conference.c
index f434c58a..affe8aef 100644
--- a/protocols/jabber/conference.c
+++ b/protocols/jabber/conference.c
@@ -271,8 +271,10 @@ void jabber_chat_pkt_presence( struct im_connection *ic, struct jabber_buddy *bu
bud->flags |= JBFLAG_IS_ANONYMOUS;
}
- if( bud != jc->me )
+ if( bud != jc->me && bud->flags & JBFLAG_IS_ANONYMOUS )
{
+ /* If JIDs are anonymized, add them to the local
+ list for the duration of this chat. */
imcb_add_buddy( ic, bud->ext_jid, NULL );
imcb_buddy_nick_hint( ic, bud->ext_jid, bud->resource );
}
diff --git a/protocols/jabber/io.c b/protocols/jabber/io.c
index a14ad21c..d6f92a5f 100644
--- a/protocols/jabber/io.c
+++ b/protocols/jabber/io.c
@@ -63,7 +63,7 @@ int jabber_write( struct im_connection *ic, char *buf, int len )
it via the event handler. If not, add the handler. (In
most cases it probably won't be necessary.) */
if( ( ret = jabber_write_queue( ic ) ) && jd->tx_len > 0 )
- jd->w_inpa = b_input_add( jd->fd, GAIM_INPUT_WRITE, jabber_write_callback, ic );
+ jd->w_inpa = b_input_add( jd->fd, B_EV_IO_WRITE, jabber_write_callback, ic );
}
else
{
@@ -503,7 +503,7 @@ gboolean jabber_start_stream( struct im_connection *ic )
jd->xt = xt_new( jabber_handlers, ic );
if( jd->r_inpa <= 0 )
- jd->r_inpa = b_input_add( jd->fd, GAIM_INPUT_READ, jabber_read_callback, ic );
+ jd->r_inpa = b_input_add( jd->fd, B_EV_IO_READ, jabber_read_callback, ic );
greet = g_strdup_printf( "%s<stream:stream to=\"%s\" xmlns=\"jabber:client\" "
"xmlns:stream=\"http://etherx.jabber.org/streams\" version=\"1.0\">",
diff --git a/protocols/jabber/jabber_util.c b/protocols/jabber/jabber_util.c
index bd2fbe8c..4bc9e3a8 100644
--- a/protocols/jabber/jabber_util.c
+++ b/protocols/jabber/jabber_util.c
@@ -670,10 +670,9 @@ int jabber_buddy_remove_bare( struct im_connection *ic, char *bare_jid )
time_t jabber_get_timestamp( struct xt_node *xt )
{
- struct tm tp, utc;
struct xt_node *c;
- time_t res, tres;
char *s = NULL;
+ struct tm tp;
for( c = xt->children; ( c = xt_find_node( c, "x" ) ); c = c->next )
{
@@ -691,30 +690,8 @@ time_t jabber_get_timestamp( struct xt_node *xt )
tp.tm_year -= 1900;
tp.tm_mon --;
- tp.tm_isdst = -1; /* GRRRRRRRRRRR */
-
- res = mktime( &tp );
- /* Problem is, mktime() just gave us the GMT timestamp for the
- given local time... While the given time WAS NOT local. So
- we should fix this now.
-
- Now I could choose between messing with environment variables
- (kludgy) or using timegm() (not portable)... Or doing the
- following, which I actually prefer... */
- gmtime_r( &res, &utc );
- utc.tm_isdst = -1; /* Once more: GRRRRRRRRRRRRRRRRRR!!! */
- if( utc.tm_hour == tp.tm_hour && utc.tm_min == tp.tm_min )
- /* Sweet! We're in UTC right now... */
- return res;
-
- tres = mktime( &utc );
- res += res - tres;
-
- /* Yes, this is a hack. And it will go wrong around DST changes.
- BUT this is more likely to be threadsafe than messing with
- environment variables, and possibly more portable... */
-
- return res;
+
+ return mktime_utc( &tp );
}
struct jabber_error *jabber_error_parse( struct xt_node *node, char *xmlns )
diff --git a/protocols/jabber/message.c b/protocols/jabber/message.c
index a226a225..e8161029 100644
--- a/protocols/jabber/message.c
+++ b/protocols/jabber/message.c
@@ -79,8 +79,8 @@ xt_status jabber_pkt_message( struct xt_node *node, gpointer data )
if( type && strcmp( type, "headline" ) == 0 )
{
- c = xt_find_node( node->children, "subject" );
- g_string_append_printf( fullmsg, "Headline: %s\n", c && c->text_len > 0 ? c->text : "" );
+ if( ( c = xt_find_node( node->children, "subject" ) ) && c->text_len > 0 )
+ g_string_append_printf( fullmsg, "Headline: %s\n", c->text );
/* <x xmlns="jabber:x:oob"><url>http://....</url></x> can contain a URL, it seems. */
for( c = node->children; c; c = c->next )
diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c
index 36a2e438..19d0d81a 100644
--- a/protocols/jabber/s5bytestream.c
+++ b/protocols/jabber/s5bytestream.c
@@ -405,7 +405,7 @@ gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition con
bt->phase = BS_PHASE_CONNECTED;
- bt->tf->watch_out = b_input_add( fd, GAIM_INPUT_WRITE, jabber_bs_recv_handshake, bt );
+ bt->tf->watch_out = b_input_add( fd, B_EV_IO_WRITE, jabber_bs_recv_handshake, bt );
/* since it takes forever(3mins?) till connect() fails on itself we schedule a timeout */
bt->connect_timeout = b_timeout_add( JABBER_BS_CONTIMEOUT * 1000, jabber_bs_connect_timeout, bt );
@@ -432,7 +432,7 @@ gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition con
bt->phase = BS_PHASE_REQUEST;
- bt->tf->watch_in = b_input_add( fd, GAIM_INPUT_READ, jabber_bs_recv_handshake, bt );
+ bt->tf->watch_in = b_input_add( fd, B_EV_IO_READ, jabber_bs_recv_handshake, bt );
bt->tf->watch_out = 0;
return FALSE;
@@ -589,7 +589,7 @@ void jabber_bs_recv_answer_request( struct bs_transfer *bt )
bt->sh->port );
tf->ft->data = tf;
- tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_recv_read, bt );
+ tf->watch_in = b_input_add( tf->fd, B_EV_IO_READ, jabber_bs_recv_read, bt );
tf->ft->write_request = jabber_bs_recv_write_request;
reply = xt_new_node( "streamhost-used", NULL, NULL );
@@ -631,7 +631,7 @@ gboolean jabber_bs_recv_read( gpointer data, gint fd, b_input_condition cond )
if( ( ret == -1 ) && ( errno == EAGAIN ) )
{
- tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_recv_read, bt );
+ tf->watch_in = b_input_add( tf->fd, B_EV_IO_READ, jabber_bs_recv_read, bt );
return FALSE;
}
}
@@ -707,7 +707,7 @@ gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, unsigned int l
if( tf->byteswritten >= ft->file_size )
imcb_file_finished( ft );
else
- bt->tf->watch_out = b_input_add( tf->fd, GAIM_INPUT_WRITE, jabber_bs_send_can_write, bt );
+ bt->tf->watch_out = b_input_add( tf->fd, B_EV_IO_WRITE, jabber_bs_send_can_write, bt );
return TRUE;
}
@@ -918,7 +918,7 @@ void jabber_si_set_proxies( struct bs_transfer *bt )
strcpy( sh->port, port );
bt->streamhosts = g_slist_append( bt->streamhosts, sh );
- bt->tf->watch_in = b_input_add( tf->fd, GAIM_INPUT_READ, jabber_bs_send_handshake, bt );
+ bt->tf->watch_in = b_input_add( tf->fd, B_EV_IO_READ, jabber_bs_send_handshake, bt );
bt->connect_timeout = b_timeout_add( JABBER_BS_LISTEN_TIMEOUT * 1000, jabber_bs_connect_timeout, bt );
} else {
imcb_log( tf->ic, "Transferring file %s: couldn't listen locally(non fatal, check your ft_listen setting in bitlbee.conf): %s",
@@ -1055,7 +1055,7 @@ gboolean jabber_bs_send_handshake( gpointer data, gint fd, b_input_condition con
bt->phase = BS_PHASE_CONNECTED;
- bt->tf->watch_in = b_input_add( fd, GAIM_INPUT_READ, jabber_bs_send_handshake, bt );
+ bt->tf->watch_in = b_input_add( fd, B_EV_IO_READ, jabber_bs_send_handshake, bt );
return FALSE;
}
case BS_PHASE_CONNECTED:
diff --git a/protocols/msn/Makefile b/protocols/msn/Makefile
index 5d199b9e..1de755a8 100644
--- a/protocols/msn/Makefile
+++ b/protocols/msn/Makefile
@@ -7,6 +7,9 @@
### DEFINITIONS
-include ../../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)protocols/msn/
+endif
# [SH] Program variables
objects = invitation.o msn.o msn_util.o ns.o passport.o sb.o tables.o
@@ -32,7 +35,7 @@ distclean: clean
$(objects): ../../Makefile.settings Makefile
-$(objects): %.o: %.c
+$(objects): %.o: $(SRCDIR)%.c
@echo '*' Compiling $<
@$(CC) -c $(CFLAGS) $< -o $@
diff --git a/protocols/msn/invitation.c b/protocols/msn/invitation.c
index d2b2a5c8..9f8b9a6e 100644
--- a/protocols/msn/invitation.c
+++ b/protocols/msn/invitation.c
@@ -208,7 +208,7 @@ gboolean msn_ftps_connected( gpointer data, gint fd, b_input_condition cond )
fd = msn_file->fd;
sock_make_nonblocking( fd );
- msn_file->r_event_id = b_input_add( fd, GAIM_INPUT_READ, msn_ftp_read, file );
+ msn_file->r_event_id = b_input_add( fd, B_EV_IO_READ, msn_ftp_read, file );
return FALSE;
}
@@ -229,7 +229,7 @@ void msn_invitations_accept( msn_filetransfer_t *msn_file, struct msn_switchboar
return;
}
- msn_file->r_event_id = b_input_add( msn_file->fd, GAIM_INPUT_READ, msn_ftps_connected, file );
+ msn_file->r_event_id = b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftps_connected, file );
g_snprintf( buf, sizeof( buf ),
"IP-Address: %s\r\n"
@@ -317,7 +317,7 @@ gboolean msn_ftp_connected( gpointer data, gint fd, b_input_condition cond )
return FALSE;
sock_make_nonblocking( msn_file->fd );
- msn_file->r_event_id = b_input_add( msn_file->fd, GAIM_INPUT_READ, msn_ftp_read, file );
+ msn_file->r_event_id = b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftp_read, file );
return FALSE;
}
@@ -414,7 +414,7 @@ gboolean msn_ftps_write( file_transfer_t *file, char *buffer, unsigned int len )
if( ( msn_file->sbufpos < MSNFTP_PSIZE ) &&
( msn_file->data_sent + msn_file->sbufpos - 3 < file->file_size ) ) {
if( !msn_file->w_event_id )
- msn_file->w_event_id = b_input_add( msn_file->fd, GAIM_INPUT_WRITE, msn_ftp_send, file );
+ msn_file->w_event_id = b_input_add( msn_file->fd, B_EV_IO_WRITE, msn_ftp_send, file );
return TRUE;
}
@@ -451,7 +451,7 @@ gboolean msn_ftps_write( file_transfer_t *file, char *buffer, unsigned int len )
} else {
/* we might already be listening if this is data from an overflow */
if( !msn_file->w_event_id )
- msn_file->w_event_id = b_input_add( msn_file->fd, GAIM_INPUT_WRITE, msn_ftp_send, file );
+ msn_file->w_event_id = b_input_add( msn_file->fd, B_EV_IO_WRITE, msn_ftp_send, file );
}
return TRUE;
@@ -616,7 +616,7 @@ gboolean msn_ftpr_write_request( file_transfer_t *file )
}
msn_file->r_event_id =
- b_input_add( msn_file->fd, GAIM_INPUT_READ, msn_ftp_read, file );
+ b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftp_read, file );
return TRUE;
}
diff --git a/protocols/msn/msn.c b/protocols/msn/msn.c
index 3a8b8f7b..0d67cc17 100644
--- a/protocols/msn/msn.c
+++ b/protocols/msn/msn.c
@@ -30,16 +30,14 @@ int msn_chat_id;
GSList *msn_connections;
GSList *msn_switchboards;
-static char *msn_set_display_name( set_t *set, char *value );
+static char *set_eval_display_name( set_t *set, char *value );
static void msn_init( account_t *acc )
{
- set_t *s;
-
- s = set_add( &acc->set, "display_name", NULL, msn_set_display_name, acc );
- s->flags |= ACC_SET_NOSAVE | ACC_SET_ONLINE_ONLY;
-
- s = set_add( &acc->set, "mail_notifications", "false", set_eval_bool, 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 )
@@ -170,7 +168,7 @@ static void msn_set_away( struct im_connection *ic, char *state, char *message )
static void msn_set_my_name( struct im_connection *ic, char *info )
{
- msn_set_display_name( set_find( &ic->acc->set, "display_name" ), info );
+ msn_set_display_name( ic, info );
}
static void msn_get_info(struct im_connection *ic, char *who)
@@ -286,18 +284,14 @@ static int msn_send_typing( struct im_connection *ic, char *who, int typing )
return( 1 );
}
-static char *msn_set_display_name( set_t *set, char *value )
+static char *set_eval_display_name( set_t *set, char *value )
{
account_t *acc = set->data;
struct im_connection *ic = acc->ic;
- struct msn_data *md;
- char buf[1024], *fn;
- /* Double-check. */
+ /* Allow any name if we're offline. */
if( ic == NULL )
- return NULL;
-
- md = ic->proto_data;
+ return value;
if( strlen( value ) > 129 )
{
@@ -305,16 +299,10 @@ static char *msn_set_display_name( set_t *set, char *value )
return NULL;
}
- fn = msn_http_encode( value );
-
- g_snprintf( buf, sizeof( buf ), "REA %d %s %s\r\n", ++md->trId, ic->acc->user, fn );
- msn_write( ic, buf, strlen( buf ) );
- g_free( fn );
-
/* Returning NULL would be better, because the server still has to
confirm the name change. However, it looks a bit confusing to the
user. */
- return value;
+ return msn_set_display_name( ic, value ) ? value : NULL;
}
void msn_initmodule()
diff --git a/protocols/msn/msn.h b/protocols/msn/msn.h
index 50f273ad..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;
@@ -161,6 +167,7 @@ char **msn_linesplit( char *line );
int msn_handler( struct msn_handler_data *h );
char *msn_http_encode( const char *input );
void msn_msgq_purge( struct im_connection *ic, GSList **list );
+gboolean msn_set_display_name( struct im_connection *ic, const char *rawname );
/* tables.c */
const struct msn_away_state *msn_away_state_by_number( int number );
@@ -179,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/msn_util.c b/protocols/msn/msn_util.c
index 668a8b8a..9c9d2720 100644
--- a/protocols/msn/msn_util.c
+++ b/protocols/msn/msn_util.c
@@ -37,10 +37,10 @@ int msn_write( struct im_connection *ic, char *s, int len )
{
imcb_error( ic, "Short write() to main server" );
imc_logout( ic, TRUE );
- return( 0 );
+ return 0;
}
- return( 1 );
+ return 1;
}
int msn_logged_in( struct im_connection *ic )
@@ -376,3 +376,15 @@ void msn_msgq_purge( struct im_connection *ic, GSList **list )
imcb_log( ic, "%s", ret->str );
g_string_free( ret, TRUE );
}
+
+gboolean msn_set_display_name( struct im_connection *ic, const char *rawname )
+{
+ char *fn = msn_http_encode( rawname );
+ struct msn_data *md = ic->proto_data;
+ char buf[1024];
+
+ g_snprintf( buf, sizeof( buf ), "REA %d %s %s\r\n", ++md->trId, ic->acc->user, fn );
+ g_free( fn );
+
+ return msn_write( ic, buf, strlen( buf ) ) != 0;
+}
diff --git a/protocols/msn/ns.c b/protocols/msn/ns.c
index d78d753a..2b0600a3 100644
--- a/protocols/msn/ns.c
+++ b/protocols/msn/ns.c
@@ -34,6 +34,7 @@ static int msn_ns_command( gpointer data, char **cmd, int num_parts );
static int msn_ns_message( gpointer data, char *msg, int msglen, char **cmd, int num_parts );
static void msn_auth_got_passport_token( struct msn_auth_data *mad );
+static gboolean msn_ns_got_display_name( struct im_connection *ic, char *name );
gboolean msn_ns_connected( gpointer data, gint source, b_input_condition cond )
{
@@ -74,7 +75,7 @@ gboolean msn_ns_connected( gpointer data, gint source, b_input_condition cond )
g_snprintf( s, sizeof( s ), "VER %d MSNP8 CVR0\r\n", ++md->trId );
if( msn_write( ic, s, strlen( s ) ) )
{
- ic->inpa = b_input_add( md->fd, GAIM_INPUT_READ, msn_ns_callback, ic );
+ ic->inpa = b_input_add( md->fd, B_EV_IO_READ, msn_ns_callback, ic );
imcb_log( ic, "Connected to server, waiting for reply" );
}
@@ -230,25 +231,10 @@ static int msn_ns_command( gpointer data, char **cmd, int num_parts )
}
else if( num_parts >= 7 && strcmp( cmd[2], "OK" ) == 0 )
{
- set_t *s;
-
if( num_parts == 7 )
- {
- http_decode( cmd[4] );
-
- strncpy( ic->displayname, cmd[4], sizeof( ic->displayname ) );
- ic->displayname[sizeof(ic->displayname)-1] = 0;
-
- if( ( s = set_find( &ic->acc->set, "display_name" ) ) )
- {
- g_free( s->value );
- s->value = g_strdup( cmd[4] );
- }
- }
+ msn_ns_got_display_name( ic, cmd[4] );
else
- {
imcb_log( ic, "Warning: Friendly name in server response was corrupted" );
- }
imcb_log( ic, "Authenticated, getting buddy list" );
@@ -435,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 )
{
@@ -462,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 )
{
@@ -566,6 +558,9 @@ static int msn_ns_command( gpointer data, char **cmd, int num_parts )
imc_logout( ic, allow_reconnect );
return( 0 );
}
+#if 0
+ /* Discard this one completely for now since I don't care about the ack
+ and since MSN servers can apparently screw up the formatting. */
else if( strcmp( cmd[0], "REA" ) == 0 )
{
if( num_parts != 5 )
@@ -596,6 +591,7 @@ static int msn_ns_command( gpointer data, char **cmd, int num_parts )
imcb_rename_buddy( ic, cmd[3], cmd[4] );
}
}
+#endif
else if( strcmp( cmd[0], "IPG" ) == 0 )
{
imcb_error( ic, "Received IPG command, we don't handle them yet." );
@@ -745,3 +741,48 @@ static void msn_auth_got_passport_token( struct msn_auth_data *mad )
imc_logout( ic, TRUE );
}
}
+
+static gboolean msn_ns_got_display_name( struct im_connection *ic, char *name )
+{
+ set_t *s;
+
+ if( ( s = set_find( &ic->acc->set, "display_name" ) ) == NULL )
+ return FALSE; /* Shouldn't happen.. */
+
+ http_decode( name );
+
+ if( s->value && strcmp( s->value, name ) == 0 )
+ {
+ return TRUE;
+ /* The names match, nothing to worry about. */
+ }
+ else if( s->value != NULL &&
+ ( strcmp( name, ic->acc->user ) == 0 ||
+ set_getbool( &ic->acc->set, "local_display_name" ) ) )
+ {
+ /* The server thinks our display name is our e-mail address
+ which is probably wrong, or the user *wants* us to do this:
+ Always use the locally set display_name. */
+ return msn_set_display_name( ic, s->value );
+ }
+ else
+ {
+ if( s->value && *s->value )
+ imcb_log( ic, "BitlBee thinks your display name is `%s' but "
+ "the MSN server says it's `%s'. Using the MSN "
+ "server's name. Set local_display_name to true "
+ "to use the local name.", s->value, name );
+
+ if( g_utf8_validate( name, -1, NULL ) )
+ {
+ g_free( s->value );
+ s->value = g_strdup( name );
+ }
+ else
+ {
+ imcb_log( ic, "Warning: Friendly name in server response was corrupted" );
+ }
+
+ return TRUE;
+ }
+}
diff --git a/protocols/msn/sb.c b/protocols/msn/sb.c
index c3302e57..1614f69f 100644
--- a/protocols/msn/sb.c
+++ b/protocols/msn/sb.c
@@ -179,6 +179,11 @@ int msn_sb_sendmessage( struct msn_switchboard *sb, char *text )
buf = g_strdup( text );
i = strlen( buf );
}
+ else if( strcmp( text, SB_KEEPALIVE_MESSAGE ) == 0 )
+ {
+ buf = g_strdup( SB_KEEPALIVE_HEADERS );
+ i = strlen( buf );
+ }
else
{
buf = g_new0( char, sizeof( MSN_MESSAGE_HEADERS ) + strlen( text ) * 2 + 1 );
@@ -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 );
@@ -314,7 +320,7 @@ gboolean msn_sb_connected( gpointer data, gint source, b_input_condition cond )
g_snprintf( buf, sizeof( buf ), "ANS %d %s %s %d\r\n", ++sb->trId, ic->acc->user, sb->key, sb->session );
if( msn_sb_write( sb, buf, strlen( buf ) ) )
- sb->inp = b_input_add( sb->fd, GAIM_INPUT_READ, msn_sb_callback, sb );
+ sb->inp = b_input_add( sb->fd, B_EV_IO_READ, msn_sb_callback, sb );
else
debug( "Error %d while connecting to switchboard server", 2 );
@@ -327,9 +333,13 @@ static gboolean msn_sb_callback( gpointer data, gint source, b_input_condition c
struct im_connection *ic = sb->ic;
struct msn_data *md = ic->proto_data;
- if( msn_handler( sb->handler ) == -1 )
+ if( msn_handler( sb->handler ) != -1 )
+ return TRUE;
+
+ if( sb->msgq != NULL )
{
time_t now = time( NULL );
+ char buf[1024];
if( now - md->first_sb_failure > 600 )
{
@@ -346,37 +356,28 @@ static gboolean msn_sb_callback( gpointer data, gint source, b_input_condition c
imcb_log( ic, "Warning: Many switchboard failures on MSN connection. "
"There might be problems delivering your messages." );
- if( sb->msgq != NULL )
+ if( md->msgq == NULL )
{
- char buf[1024];
-
- if( md->msgq == NULL )
- {
- md->msgq = sb->msgq;
- }
- else
- {
- GSList *l;
-
- for( l = md->msgq; l->next; l = l->next );
- l->next = sb->msgq;
- }
- sb->msgq = NULL;
+ md->msgq = sb->msgq;
+ }
+ else
+ {
+ GSList *l;
- debug( "Moved queued messages back to the main queue, creating a new switchboard to retry." );
- g_snprintf( buf, sizeof( buf ), "XFR %d SB\r\n", ++md->trId );
- if( !msn_write( ic, buf, strlen( buf ) ) )
- return FALSE;
+ for( l = md->msgq; l->next; l = l->next );
+ l->next = sb->msgq;
}
+ sb->msgq = NULL;
- msn_sb_destroy( sb );
-
- return FALSE;
- }
- else
- {
- return TRUE;
+ debug( "Moved queued messages back to the main queue, "
+ "creating a new switchboard to retry." );
+ g_snprintf( buf, sizeof( buf ), "XFR %d SB\r\n", ++md->trId );
+ if( !msn_write( ic, buf, strlen( buf ) ) )
+ return FALSE;
}
+
+ msn_sb_destroy( sb );
+ return FALSE;
}
static int msn_sb_command( gpointer data, char **cmd, int num_parts )
@@ -476,6 +477,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 +528,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 +591,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;
@@ -748,3 +755,33 @@ 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 )
+{
+ struct buddy *b;
+
+ if( sb && sb->who && sb->keepalive == 0 &&
+ ( b = imcb_find_buddy( sb->ic, sb->who ) ) && !b->present &&
+ 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/nogaim.c b/protocols/nogaim.c
index c326e378..cd57a289 100644
--- a/protocols/nogaim.c
+++ b/protocols/nogaim.c
@@ -38,6 +38,7 @@
#include "chat.h"
static int remove_chat_buddy_silent( struct groupchat *b, const char *handle );
+static char *format_timestamp( irc_t *irc, time_t msg_ts );
GSList *connections;
@@ -115,12 +116,15 @@ void register_protocol (struct prpl *p)
struct prpl *find_protocol(const char *name)
{
GList *gl;
- for (gl = protocols; gl; gl = gl->next)
+
+ for( gl = protocols; gl; gl = gl->next )
{
struct prpl *proto = gl->data;
- if(!g_strcasecmp(proto->name, name))
+
+ if( g_strcasecmp( proto->name, name ) == 0 )
return proto;
}
+
return NULL;
}
@@ -131,6 +135,8 @@ void nogaim_init()
extern void oscar_initmodule();
extern void byahoo_initmodule();
extern void jabber_initmodule();
+ extern void twitter_initmodule();
+ extern void purple_initmodule();
#ifdef WITH_MSN
msn_initmodule();
@@ -148,6 +154,14 @@ void nogaim_init()
jabber_initmodule();
#endif
+#ifdef WITH_TWITTER
+ twitter_initmodule();
+#endif
+
+#ifdef WITH_PURPLE
+ purple_initmodule();
+#endif
+
#ifdef WITH_PLUGINS
load_plugins();
#endif
@@ -650,7 +664,18 @@ void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags,
g_free( u->status_msg );
u->away = u->status_msg = NULL;
- if( ( flags & OPT_LOGGED_IN ) && !u->online )
+ if( set_getbool( &ic->irc->set, "show_offline" ) && !u->online )
+ {
+ /* always set users as online */
+ irc_spawn( ic->irc, u );
+ u->online = 1;
+ if( !( flags & OPT_LOGGED_IN ) )
+ {
+ /* set away message if user isn't really online */
+ u->away = g_strdup( "User is offline" );
+ }
+ }
+ else if( ( flags & OPT_LOGGED_IN ) && !u->online )
{
irc_spawn( ic->irc, u );
u->online = 1;
@@ -659,14 +684,30 @@ void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags,
{
struct groupchat *c;
- irc_kill( ic->irc, u );
- u->online = 0;
-
- /* Remove him/her from the groupchats to prevent PART messages after he/she QUIT already */
- for( c = ic->groupchats; c; c = c->next )
- remove_chat_buddy_silent( c, handle );
+ if( set_getbool( &ic->irc->set, "show_offline" ) )
+ {
+ /* keep offline users in channel and set away message to "offline" */
+ u->away = g_strdup( "User is offline" );
+
+ /* Keep showing him/her in the control channel but not in groupchats. */
+ for( c = ic->groupchats; c; c = c->next )
+ {
+ if( remove_chat_buddy_silent( c, handle ) && c->joined )
+ irc_part( c->ic->irc, u, c->channel );
+ }
+ }
+ else
+ {
+ /* kill offline users */
+ irc_kill( ic->irc, u );
+ u->online = 0;
+
+ /* Remove him/her from the groupchats to prevent PART messages after he/she QUIT already */
+ for( c = ic->groupchats; c; c = c->next )
+ remove_chat_buddy_silent( c, handle );
+ }
}
-
+
if( flags & OPT_AWAY )
{
if( state && message )
@@ -691,11 +732,8 @@ void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags,
u->status_msg = g_strdup( message );
}
- /* LISPy... */
- if( ( set_getbool( &ic->irc->set, "away_devoice" ) ) && /* Don't do a thing when user doesn't want it */
- ( u->online ) && /* Don't touch offline people */
- ( ( ( u->online != oo ) && !u->away ) || /* Voice joining people */
- ( ( u->online == oo ) && ( oa == !u->away ) ) ) ) /* (De)voice people changing state */
+ /* early if-clause for show_offline even if there is some redundant code here because this isn't LISP but C ;) */
+ if( set_getbool( &ic->irc->set, "show_offline" ) && set_getbool( &ic->irc->set, "away_devoice" ) )
{
char *from;
@@ -708,16 +746,50 @@ void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags,
from = g_strdup_printf( "%s!%s@%s", ic->irc->mynick, ic->irc->mynick,
ic->irc->myhost );
}
- irc_write( ic->irc, ":%s MODE %s %cv %s", from, ic->irc->channel,
- u->away?'-':'+', u->nick );
- g_free( from );
+
+ /* if we use show_offline, we op online users, voice away users, and devoice/deop offline users */
+ if( flags & OPT_LOGGED_IN )
+ {
+ /* user is "online" (either really online or away) */
+ irc_write( ic->irc, ":%s MODE %s %cv%co %s %s", from, ic->irc->channel,
+ u->away?'+':'-', u->away?'-':'+', u->nick, u->nick );
+ }
+ else
+ {
+ /* user is offline */
+ irc_write( ic->irc, ":%s MODE %s -vo %s %s", from, ic->irc->channel, u->nick, u->nick );
+ }
+ }
+ else
+ {
+ /* LISPy... */
+ if( ( set_getbool( &ic->irc->set, "away_devoice" ) ) && /* Don't do a thing when user doesn't want it */
+ ( u->online ) && /* Don't touch offline people */
+ ( ( ( u->online != oo ) && !u->away ) || /* Voice joining people */
+ ( ( u->online == oo ) && ( oa == !u->away ) ) ) ) /* (De)voice people changing state */
+ {
+ char *from;
+
+ if( set_getbool( &ic->irc->set, "simulate_netsplit" ) )
+ {
+ from = g_strdup( ic->irc->myhost );
+ }
+ else
+ {
+ from = g_strdup_printf( "%s!%s@%s", ic->irc->mynick, ic->irc->mynick,
+ ic->irc->myhost );
+ }
+ irc_write( ic->irc, ":%s MODE %s %cv %s", from, ic->irc->channel,
+ u->away?'-':'+', u->nick );
+ g_free( from );
+ }
}
}
void imcb_buddy_msg( struct im_connection *ic, const char *handle, char *msg, uint32_t flags, time_t sent_at )
{
irc_t *irc = ic->irc;
- char *wrapped;
+ char *wrapped, *ts = NULL;
user_t *u;
u = user_findhandle( ic, handle );
@@ -759,10 +831,19 @@ void imcb_buddy_msg( struct im_connection *ic, const char *handle, char *msg, ui
if( ( g_strcasecmp( set_getstr( &ic->irc->set, "strip_html" ), "always" ) == 0 ) ||
( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->irc->set, "strip_html" ) ) )
strip_html( msg );
-
+
+ if( set_getbool( &ic->irc->set, "display_timestamps" ) &&
+ ( ts = format_timestamp( irc, sent_at ) ) )
+ {
+ char *new = g_strconcat( ts, msg, NULL );
+ g_free( ts );
+ ts = msg = new;
+ }
+
wrapped = word_wrap( msg, 425 );
irc_msgfrom( irc, u->nick, wrapped );
g_free( wrapped );
+ g_free( ts );
}
void imcb_buddy_typing( struct im_connection *ic, char *handle, uint32_t flags )
@@ -806,6 +887,35 @@ struct groupchat *imcb_chat_new( struct im_connection *ic, const char *handle )
return c;
}
+void imcb_chat_name_hint( struct groupchat *c, const char *name )
+{
+ if( !c->joined )
+ {
+ struct im_connection *ic = c->ic;
+ char stripped[MAX_NICK_LENGTH+1], *full_name;
+
+ strncpy( stripped, name, MAX_NICK_LENGTH );
+ stripped[MAX_NICK_LENGTH] = '\0';
+ nick_strip( stripped );
+ if( set_getbool( &ic->irc->set, "lcnicks" ) )
+ nick_lc( stripped );
+
+ full_name = g_strdup_printf( "&%s", stripped );
+
+ if( stripped[0] &&
+ nick_cmp( stripped, ic->irc->channel + 1 ) != 0 &&
+ irc_chat_by_channel( ic->irc, full_name ) == NULL )
+ {
+ g_free( c->channel );
+ c->channel = full_name;
+ }
+ else
+ {
+ g_free( full_name );
+ }
+ }
+}
+
void imcb_chat_free( struct groupchat *c )
{
struct im_connection *ic = c->ic;
@@ -866,7 +976,11 @@ void imcb_chat_msg( struct groupchat *c, const char *who, char *msg, uint32_t fl
wrapped = word_wrap( msg, 425 );
if( c && u )
{
- irc_privmsg( ic->irc, u, "PRIVMSG", c->channel, "", wrapped );
+ char *ts = NULL;
+ if( set_getbool( &ic->irc->set, "display_timestamps" ) )
+ ts = format_timestamp( ic->irc, sent_at );
+ irc_privmsg( ic->irc, u, "PRIVMSG", c->channel, ts ? : "", wrapped );
+ g_free( ts );
}
else
{
@@ -1060,8 +1174,94 @@ char *set_eval_away_devoice( set_t *set, char *value )
return value;
}
+char *set_eval_timezone( set_t *set, char *value )
+{
+ char *s;
+
+ if( strcmp( value, "local" ) == 0 ||
+ strcmp( value, "gmt" ) == 0 || strcmp( value, "utc" ) == 0 )
+ return value;
+
+ /* Otherwise: +/- at the beginning optional, then one or more numbers,
+ possibly followed by a colon and more numbers. Don't bother bound-
+ checking them since users are free to shoot themselves in the foot. */
+ s = value;
+ if( *s == '+' || *s == '-' )
+ s ++;
+
+ /* \d+ */
+ if( !isdigit( *s ) )
+ return SET_INVALID;
+ while( *s && isdigit( *s ) ) s ++;
+
+ /* EOS? */
+ if( *s == '\0' )
+ return value;
+
+ /* Otherwise, colon */
+ if( *s != ':' )
+ return SET_INVALID;
+ s ++;
+
+ /* \d+ */
+ if( !isdigit( *s ) )
+ return SET_INVALID;
+ while( *s && isdigit( *s ) ) s ++;
+
+ /* EOS */
+ return *s == '\0' ? value : SET_INVALID;
+}
-
+static char *format_timestamp( irc_t *irc, time_t msg_ts )
+{
+ time_t now_ts = time( NULL );
+ struct tm now, msg;
+ char *set;
+
+ /* If the timestamp is <= 0 or less than a minute ago, discard it as
+ it doesn't seem to add to much useful info and/or might be noise. */
+ if( msg_ts <= 0 || msg_ts > now_ts - 60 )
+ return NULL;
+
+ set = set_getstr( &irc->set, "timezone" );
+ if( strcmp( set, "local" ) == 0 )
+ {
+ localtime_r( &now_ts, &now );
+ localtime_r( &msg_ts, &msg );
+ }
+ else
+ {
+ int hr, min = 0, sign = 60;
+
+ if( set[0] == '-' )
+ {
+ sign *= -1;
+ set ++;
+ }
+ else if( set[0] == '+' )
+ {
+ set ++;
+ }
+
+ if( sscanf( set, "%d:%d", &hr, &min ) >= 1 )
+ {
+ msg_ts += sign * ( hr * 60 + min );
+ now_ts += sign * ( hr * 60 + min );
+ }
+
+ gmtime_r( &now_ts, &now );
+ gmtime_r( &msg_ts, &msg );
+ }
+
+ if( msg.tm_year == now.tm_year && msg.tm_yday == now.tm_yday )
+ return g_strdup_printf( "\x02[\x02\x02\x02%02d:%02d:%02d\x02]\x02 ",
+ msg.tm_hour, msg.tm_min, msg.tm_sec );
+ else
+ return g_strdup_printf( "\x02[\x02\x02\x02%04d-%02d-%02d "
+ "%02d:%02d:%02d\x02]\x02 ",
+ msg.tm_year + 1900, msg.tm_mon + 1, msg.tm_mday,
+ msg.tm_hour, msg.tm_min, msg.tm_sec );
+}
/* The plan is to not allow straight calls to prpl functions anymore, but do
them all from some wrappers. We'll start to define some down here: */
@@ -1105,6 +1305,10 @@ int imc_away_send_update( struct im_connection *ic )
{
char *away, *msg = NULL;
+ if( ic->acc->prpl->away_states == NULL ||
+ ic->acc->prpl->set_away == NULL )
+ return 0;
+
away = set_getstr( &ic->acc->set, "away" ) ?
: set_getstr( &ic->irc->set, "away" );
if( away && *away )
diff --git a/protocols/nogaim.h b/protocols/nogaim.h
index 324a2b46..21b461f8 100644
--- a/protocols/nogaim.h
+++ b/protocols/nogaim.h
@@ -133,6 +133,7 @@ struct prpl {
/* You should set this to the name of your protocol.
* - The user sees this name ie. when imcb_log() is used. */
const char *name;
+ void *data;
/* Added this one to be able to add per-account settings, don't think
* it should be used for anything else. You are supposed to use the
@@ -306,6 +307,7 @@ G_MODULE_EXPORT void imcb_chat_invited( struct im_connection *ic, char *handle,
* the user her/himself. At that point the group chat will be visible to the
* user, too. */
G_MODULE_EXPORT struct groupchat *imcb_chat_new( struct im_connection *ic, const char *handle );
+G_MODULE_EXPORT void imcb_chat_name_hint( struct groupchat *c, const char *name );
G_MODULE_EXPORT void imcb_chat_add_buddy( struct groupchat *b, const char *handle );
/* To remove a handle from a group chat. Reason can be NULL. */
G_MODULE_EXPORT void imcb_chat_remove_buddy( struct groupchat *b, const char *handle, const char *reason );
@@ -328,6 +330,7 @@ void imc_add_block( struct im_connection *ic, char *handle );
void imc_rem_block( struct im_connection *ic, char *handle );
/* Misc. stuff */
+char *set_eval_timezone( set_t *set, char *value );
char *set_eval_away_devoice( set_t *set, char *value );
gboolean auto_reconnect( gpointer data, gint fd, b_input_condition cond );
void cancel_auto_reconnect( struct account *a );
diff --git a/protocols/oscar/Makefile b/protocols/oscar/Makefile
index 2792f22a..0ec7436b 100644
--- a/protocols/oscar/Makefile
+++ b/protocols/oscar/Makefile
@@ -7,6 +7,10 @@
### DEFINITIONS
-include ../../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)protocols/oscar/
+CFLAGS += -I$(SRCDIR)
+endif
# [SH] Program variables
objects = admin.o auth.o bos.o buddylist.o chat.o chatnav.o conn.o icq.o im.o info.o misc.o msgcookie.o rxhandlers.o rxqueue.o search.o service.o snac.o ssi.o stats.o tlv.o txqueue.o oscar_util.o oscar.o
@@ -32,7 +36,7 @@ distclean: clean
$(objects): ../../Makefile.settings Makefile
-$(objects): %.o: %.c
+$(objects): %.o: $(SRCDIR)%.c
@echo '*' Compiling $<
@$(CC) -c $(CFLAGS) $< -o $@
diff --git a/protocols/oscar/oscar.c b/protocols/oscar/oscar.c
index e0c32257..5633d399 100644
--- a/protocols/oscar/oscar.c
+++ b/protocols/oscar/oscar.c
@@ -204,7 +204,6 @@ static int gaim_parse_buddyrights(aim_session_t *, aim_frame_t *, ...);
static int gaim_parse_locerr (aim_session_t *, aim_frame_t *, ...);
static int gaim_icbm_param_info (aim_session_t *, aim_frame_t *, ...);
static int gaim_parse_genericerr (aim_session_t *, aim_frame_t *, ...);
-static int gaim_memrequest (aim_session_t *, aim_frame_t *, ...);
static int gaim_selfinfo (aim_session_t *, aim_frame_t *, ...);
static int gaim_offlinemsg (aim_session_t *, aim_frame_t *, ...);
static int gaim_offlinemsgdone (aim_session_t *, aim_frame_t *, ...);
@@ -290,7 +289,7 @@ static gboolean oscar_callback(gpointer data, gint source,
odata = (struct oscar_data *)ic->proto_data;
- if (condition & GAIM_INPUT_READ) {
+ if (condition & B_EV_IO_READ) {
if (aim_get_command(odata->sess, conn) >= 0) {
aim_rxdispatch(odata->sess);
if (odata->killme)
@@ -362,7 +361,7 @@ static gboolean oscar_login_connect(gpointer data, gint source, b_input_conditio
}
aim_conn_completeconnect(sess, conn);
- ic->inpa = b_input_add(conn->fd, GAIM_INPUT_READ,
+ ic->inpa = b_input_add(conn->fd, B_EV_IO_READ,
oscar_callback, conn);
return FALSE;
@@ -492,7 +491,7 @@ static gboolean oscar_bos_connect(gpointer data, gint source, b_input_condition
}
aim_conn_completeconnect(sess, bosconn);
- ic->inpa = b_input_add(bosconn->fd, GAIM_INPUT_READ,
+ ic->inpa = b_input_add(bosconn->fd, B_EV_IO_READ,
oscar_callback, bosconn);
imcb_log(ic, _("Connection established, cookie sent"));
@@ -569,7 +568,6 @@ static int gaim_parse_auth_resp(aim_session_t *sess, aim_frame_t *fr, ...) {
aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_ERROR, gaim_parse_genericerr, 0);
aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_ERROR, gaim_parse_genericerr, 0);
aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BOS, AIM_CB_BOS_ERROR, gaim_parse_genericerr, 0);
- aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, 0x1f, gaim_memrequest, 0);
aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_SELFINFO, gaim_selfinfo, 0);
aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSG, gaim_offlinemsg, 0);
aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSGCOMPLETE, gaim_offlinemsgdone, 0);
@@ -603,143 +601,9 @@ static int gaim_parse_auth_resp(aim_session_t *sess, aim_frame_t *fr, ...) {
return 1;
}
-struct pieceofcrap {
- struct im_connection *ic;
- unsigned long offset;
- unsigned long len;
- char *modname;
- int fd;
- aim_conn_t *conn;
- unsigned int inpa;
-};
-
-static gboolean damn_you(gpointer data, gint source, b_input_condition c)
-{
- struct pieceofcrap *pos = data;
- struct oscar_data *od = pos->ic->proto_data;
- char in = '\0';
- int x = 0;
- unsigned char m[17];
-
- while (read(pos->fd, &in, 1) == 1) {
- if (in == '\n')
- x++;
- else if (in != '\r')
- x = 0;
- if (x == 2)
- break;
- in = '\0';
- }
- if (in != '\n') {
- imcb_error(pos->ic, "Gaim was unable to get a valid hash for logging into AIM."
- " You may be disconnected shortly.");
- b_event_remove(pos->inpa);
- closesocket(pos->fd);
- g_free(pos);
- return FALSE;
- }
- /* [WvG] Wheeeee! Who needs error checking anyway? ;-) */
- read(pos->fd, m, 16);
- m[16] = '\0';
- b_event_remove(pos->inpa);
- closesocket(pos->fd);
- aim_sendmemblock(od->sess, pos->conn, 0, 16, m, AIM_SENDMEMBLOCK_FLAG_ISHASH);
- g_free(pos);
-
- return FALSE;
-}
-
-static gboolean straight_to_hell(gpointer data, gint source, b_input_condition cond) {
- struct pieceofcrap *pos = data;
- char buf[BUF_LONG];
-
- if (source < 0) {
- imcb_error(pos->ic, "Gaim was unable to get a valid hash for logging into AIM."
- " You may be disconnected shortly.");
- if (pos->modname)
- g_free(pos->modname);
- g_free(pos);
- return FALSE;
- }
-
- g_snprintf(buf, sizeof(buf), "GET " AIMHASHDATA
- "?offset=%ld&len=%ld&modname=%s HTTP/1.0\n\n",
- pos->offset, pos->len, pos->modname ? pos->modname : "");
- write(pos->fd, buf, strlen(buf));
- if (pos->modname)
- g_free(pos->modname);
- pos->inpa = b_input_add(pos->fd, GAIM_INPUT_READ, damn_you, pos);
- return FALSE;
-}
-
/* size of icbmui.ocm, the largest module in AIM 3.5 */
#define AIM_MAX_FILE_SIZE 98304
-int gaim_memrequest(aim_session_t *sess, aim_frame_t *fr, ...) {
- va_list ap;
- struct pieceofcrap *pos;
- guint32 offset, len;
- char *modname;
- int fd;
-
- va_start(ap, fr);
- offset = (guint32)va_arg(ap, unsigned long);
- len = (guint32)va_arg(ap, unsigned long);
- modname = va_arg(ap, char *);
- va_end(ap);
-
- if (len == 0) {
- aim_sendmemblock(sess, fr->conn, offset, len, NULL,
- AIM_SENDMEMBLOCK_FLAG_ISREQUEST);
- return 1;
- }
- /* uncomment this when you're convinced it's right. remember, it's been wrong before.
- if (offset > AIM_MAX_FILE_SIZE || len > AIM_MAX_FILE_SIZE) {
- char *buf;
- int i = 8;
- if (modname)
- i += strlen(modname);
- buf = g_malloc(i);
- i = 0;
- if (modname) {
- memcpy(buf, modname, strlen(modname));
- i += strlen(modname);
- }
- buf[i++] = offset & 0xff;
- buf[i++] = (offset >> 8) & 0xff;
- buf[i++] = (offset >> 16) & 0xff;
- buf[i++] = (offset >> 24) & 0xff;
- buf[i++] = len & 0xff;
- buf[i++] = (len >> 8) & 0xff;
- buf[i++] = (len >> 16) & 0xff;
- buf[i++] = (len >> 24) & 0xff;
- aim_sendmemblock(sess, command->conn, offset, i, buf, AIM_SENDMEMBLOCK_FLAG_ISREQUEST);
- g_free(buf);
- return 1;
- }
- */
-
- pos = g_new0(struct pieceofcrap, 1);
- pos->ic = sess->aux_data;
- pos->conn = fr->conn;
-
- pos->offset = offset;
- pos->len = len;
- pos->modname = modname ? g_strdup(modname) : NULL;
-
- fd = proxy_connect("gaim.sourceforge.net", 80, straight_to_hell, pos);
- if (fd < 0) {
- if (pos->modname)
- g_free(pos->modname);
- g_free(pos);
- imcb_error(sess->aux_data, "Gaim was unable to get a valid hash for logging into AIM."
- " You may be disconnected shortly.");
- }
- pos->fd = fd;
-
- return 1;
-}
-
static int gaim_parse_login(aim_session_t *sess, aim_frame_t *fr, ...) {
#if 0
struct client_info_s info = {"gaim", 4, 1, 2010, "us", "en", 0x0004, 0x0000, 0x04b};
@@ -837,7 +701,7 @@ static gboolean oscar_chatnav_connect(gpointer data, gint source, b_input_condit
}
aim_conn_completeconnect(sess, tstconn);
- odata->cnpa = b_input_add(tstconn->fd, GAIM_INPUT_READ,
+ odata->cnpa = b_input_add(tstconn->fd, B_EV_IO_READ,
oscar_callback, tstconn);
return FALSE;
@@ -865,7 +729,7 @@ static gboolean oscar_auth_connect(gpointer data, gint source, b_input_condition
}
aim_conn_completeconnect(sess, tstconn);
- odata->paspa = b_input_add(tstconn->fd, GAIM_INPUT_READ,
+ odata->paspa = b_input_add(tstconn->fd, B_EV_IO_READ,
oscar_callback, tstconn);
return FALSE;
@@ -901,7 +765,7 @@ static gboolean oscar_chat_connect(gpointer data, gint source, b_input_condition
aim_conn_completeconnect(sess, ccon->conn);
ccon->inpa = b_input_add(tstconn->fd,
- GAIM_INPUT_READ,
+ B_EV_IO_READ,
oscar_callback, tstconn);
odata->oscar_chats = g_slist_append(odata->oscar_chats, ccon);
@@ -2650,7 +2514,8 @@ struct groupchat *oscar_chat_with(struct im_connection * ic, char *who)
static int chat_id = 0;
char * chatname;
- chatname = g_strdup_printf("%s%d", ic->acc->user, chat_id++);
+ chatname = g_strdup_printf("%s%s_%d", isdigit(*ic->acc->user) ? "icq_" : "",
+ ic->acc->user, chat_id++);
ret = oscar_chat_join(ic, chatname, NULL, NULL);
diff --git a/protocols/purple/Makefile b/protocols/purple/Makefile
new file mode 100644
index 00000000..97a5bb6a
--- /dev/null
+++ b/protocols/purple/Makefile
@@ -0,0 +1,44 @@
+###########################
+## Makefile for BitlBee ##
+## ##
+## Copyright 2002 Lintux ##
+###########################
+
+### DEFINITIONS
+
+-include ../../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)protocols/purple/
+endif
+
+# [SH] Program variables
+objects = ft.o purple.o
+
+CFLAGS += -Wall $(PURPLE_CFLAGS)
+LFLAGS += -r
+
+# [SH] Phony targets
+all: purple_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 $@
+
+purple_mod.o: $(objects)
+ @echo '*' Linking purple_mod.o
+ $(LD) $(LFLAGS) $(objects) -o purple_mod.o
diff --git a/protocols/purple/ft-direct.c b/protocols/purple/ft-direct.c
new file mode 100644
index 00000000..98a16d75
--- /dev/null
+++ b/protocols/purple/ft-direct.c
@@ -0,0 +1,239 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* libpurple module - File transfer stuff *
+* *
+* Copyright 2009-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. *
+* *
+\***************************************************************************/
+
+/* This code tries to do direct file transfers, i.e. without caching the file
+ locally on disk first. Since libpurple can only do this since version 2.6.0
+ and even then very unreliably (and not with all IM modules), I'm canning
+ this code for now. */
+
+#include "bitlbee.h"
+
+#include <stdarg.h>
+
+#include <glib.h>
+#include <purple.h>
+
+struct prpl_xfer_data
+{
+ PurpleXfer *xfer;
+ file_transfer_t *ft;
+ gint ready_timer;
+ char *buf;
+ int buf_len;
+};
+
+static file_transfer_t *next_ft;
+
+struct im_connection *purple_ic_by_pa( PurpleAccount *pa );
+
+/* Glorious hack: We seem to have to remind at least some libpurple plugins
+ that we're ready because this info may get lost if we give it too early.
+ So just do it ten times a second. :-/ */
+static gboolean prplcb_xfer_write_request_cb( gpointer data, gint fd, b_input_condition cond )
+{
+ struct prpl_xfer_data *px = data;
+
+ purple_xfer_ui_ready( px->xfer );
+
+ return purple_xfer_get_type( px->xfer ) == PURPLE_XFER_RECEIVE;
+}
+
+static gboolean prpl_xfer_write_request( struct file_transfer *ft )
+{
+ struct prpl_xfer_data *px = ft->data;
+ px->ready_timer = b_timeout_add( 100, prplcb_xfer_write_request_cb, px );
+ return TRUE;
+}
+
+static gboolean prpl_xfer_write( struct file_transfer *ft, char *buffer, unsigned int len )
+{
+ struct prpl_xfer_data *px = ft->data;
+
+ px->buf = g_memdup( buffer, len );
+ px->buf_len = len;
+
+ //purple_xfer_ui_ready( px->xfer );
+ px->ready_timer = b_timeout_add( 0, prplcb_xfer_write_request_cb, px );
+
+ return TRUE;
+}
+
+static void prpl_xfer_accept( struct file_transfer *ft )
+{
+ struct prpl_xfer_data *px = ft->data;
+ purple_xfer_request_accepted( px->xfer, NULL );
+ prpl_xfer_write_request( ft );
+}
+
+static void prpl_xfer_canceled( struct file_transfer *ft, char *reason )
+{
+ struct prpl_xfer_data *px = ft->data;
+ purple_xfer_request_denied( px->xfer );
+}
+
+static gboolean prplcb_xfer_new_send_cb( gpointer data, gint fd, b_input_condition cond )
+{
+ PurpleXfer *xfer = data;
+ struct im_connection *ic = purple_ic_by_pa( xfer->account );
+ struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 );
+ PurpleBuddy *buddy;
+ const char *who;
+
+ buddy = purple_find_buddy( xfer->account, xfer->who );
+ who = buddy ? purple_buddy_get_name( buddy ) : xfer->who;
+
+ /* TODO(wilmer): After spreading some more const goodness in BitlBee,
+ remove the evil cast below. */
+ px->ft = imcb_file_send_start( ic, (char*) who, xfer->filename, xfer->size );
+ px->ft->data = px;
+ px->xfer = data;
+ px->xfer->ui_data = px;
+
+ px->ft->accept = prpl_xfer_accept;
+ px->ft->canceled = prpl_xfer_canceled;
+ px->ft->write_request = prpl_xfer_write_request;
+
+ return FALSE;
+}
+
+static void prplcb_xfer_new( PurpleXfer *xfer )
+{
+ if( purple_xfer_get_type( xfer ) == PURPLE_XFER_RECEIVE )
+ {
+ /* This should suppress the stupid file dialog. */
+ purple_xfer_set_local_filename( xfer, "/tmp/wtf123" );
+
+ /* Sadly the xfer struct is still empty ATM so come back after
+ the caller is done. */
+ b_timeout_add( 0, prplcb_xfer_new_send_cb, xfer );
+ }
+ else
+ {
+ struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 );
+
+ px->ft = next_ft;
+ px->ft->data = px;
+ px->xfer = xfer;
+ px->xfer->ui_data = px;
+
+ purple_xfer_set_filename( xfer, px->ft->file_name );
+ purple_xfer_set_size( xfer, px->ft->file_size );
+
+ next_ft = NULL;
+ }
+}
+
+static void prplcb_xfer_progress( PurpleXfer *xfer, double percent )
+{
+ fprintf( stderr, "prplcb_xfer_dbg 0x%p %f\n", xfer, percent );
+}
+
+static void prplcb_xfer_dbg( PurpleXfer *xfer )
+{
+ fprintf( stderr, "prplcb_xfer_dbg 0x%p\n", xfer );
+}
+
+static gssize prplcb_xfer_write( PurpleXfer *xfer, const guchar *buffer, gssize size )
+{
+ struct prpl_xfer_data *px = xfer->ui_data;
+ gboolean st;
+
+ fprintf( stderr, "xfer_write %d %d\n", size, px->buf_len );
+
+ b_event_remove( px->ready_timer );
+ px->ready_timer = 0;
+
+ st = px->ft->write( px->ft, (char*) buffer, size );
+
+ if( st && xfer->bytes_remaining == size )
+ imcb_file_finished( px->ft );
+
+ return st ? size : 0;
+}
+
+gssize prplcb_xfer_read( PurpleXfer *xfer, guchar **buffer, gssize size )
+{
+ struct prpl_xfer_data *px = xfer->ui_data;
+
+ fprintf( stderr, "xfer_read %d %d\n", size, px->buf_len );
+
+ if( px->buf )
+ {
+ *buffer = px->buf;
+ px->buf = NULL;
+
+ px->ft->write_request( px->ft );
+
+ return px->buf_len;
+ }
+
+ return 0;
+}
+
+PurpleXferUiOps bee_xfer_uiops =
+{
+ prplcb_xfer_new,
+ prplcb_xfer_dbg,
+ prplcb_xfer_dbg,
+ prplcb_xfer_progress,
+ prplcb_xfer_dbg,
+ prplcb_xfer_dbg,
+ prplcb_xfer_write,
+ prplcb_xfer_read,
+ prplcb_xfer_dbg,
+};
+
+static gboolean prplcb_xfer_send_cb( gpointer data, gint fd, b_input_condition cond );
+
+void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle )
+{
+ PurpleAccount *pa = ic->proto_data;
+ struct prpl_xfer_data *px;
+
+ /* xfer_new() will pick up this variable. It's a hack but we're not
+ multi-threaded anyway. */
+ next_ft = ft;
+ serv_send_file( purple_account_get_connection( pa ), handle, ft->file_name );
+
+ ft->write = prpl_xfer_write;
+
+ px = ft->data;
+ imcb_file_recv_start( ft );
+
+ px->ready_timer = b_timeout_add( 100, prplcb_xfer_send_cb, px );
+}
+
+static gboolean prplcb_xfer_send_cb( gpointer data, gint fd, b_input_condition cond )
+{
+ struct prpl_xfer_data *px = data;
+
+ if( px->ft->status & FT_STATUS_TRANSFERRING )
+ {
+ fprintf( stderr, "The ft, it is ready...\n" );
+ px->ft->write_request( px->ft );
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/protocols/purple/ft.c b/protocols/purple/ft.c
new file mode 100644
index 00000000..e3a89524
--- /dev/null
+++ b/protocols/purple/ft.c
@@ -0,0 +1,353 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* libpurple module - File transfer stuff *
+* *
+* Copyright 2009-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. *
+* *
+\***************************************************************************/
+
+/* Do file transfers via disk for now, since libpurple was really designed
+ for straight-to/from disk fts and is only just learning how to pass the
+ file contents the the UI instead (2.6.0 and higher it seems, and with
+ varying levels of success). */
+
+#include "bitlbee.h"
+
+#include <stdarg.h>
+
+#include <glib.h>
+#include <purple.h>
+
+struct prpl_xfer_data
+{
+ PurpleXfer *xfer;
+ file_transfer_t *ft;
+ struct im_connection *ic;
+ int fd;
+ char *fn, *handle;
+ gboolean ui_wants_data;
+};
+
+static file_transfer_t *next_ft;
+
+struct im_connection *purple_ic_by_pa( PurpleAccount *pa );
+static gboolean prplcb_xfer_new_send_cb( gpointer data, gint fd, b_input_condition cond );
+static gboolean prpl_xfer_write_request( struct file_transfer *ft );
+
+
+/* Receiving files (IM->UI): */
+static void prpl_xfer_accept( struct file_transfer *ft )
+{
+ struct prpl_xfer_data *px = ft->data;
+ purple_xfer_request_accepted( px->xfer, NULL );
+ prpl_xfer_write_request( ft );
+}
+
+static void prpl_xfer_canceled( struct file_transfer *ft, char *reason )
+{
+ struct prpl_xfer_data *px = ft->data;
+ purple_xfer_request_denied( px->xfer );
+}
+
+static void prplcb_xfer_new( PurpleXfer *xfer )
+{
+ if( purple_xfer_get_type( xfer ) == PURPLE_XFER_RECEIVE )
+ {
+ struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 );
+
+ xfer->ui_data = px;
+ px->xfer = xfer;
+ px->fn = mktemp( g_strdup( "/tmp/bitlbee-purple-ft.XXXXXX" ) );
+ px->fd = -1;
+
+ purple_xfer_set_local_filename( xfer, px->fn );
+
+ /* Sadly the xfer struct is still empty ATM so come back after
+ the caller is done. */
+ b_timeout_add( 0, prplcb_xfer_new_send_cb, xfer );
+ }
+ else
+ {
+ struct file_transfer *ft = next_ft;
+ struct prpl_xfer_data *px = ft->data;
+
+ xfer->ui_data = px;
+ px->xfer = xfer;
+
+ next_ft = NULL;
+ }
+}
+
+static gboolean prplcb_xfer_new_send_cb( gpointer data, gint fd, b_input_condition cond )
+{
+ PurpleXfer *xfer = data;
+ struct im_connection *ic = purple_ic_by_pa( xfer->account );
+ struct prpl_xfer_data *px = xfer->ui_data;
+ PurpleBuddy *buddy;
+ const char *who;
+
+ buddy = purple_find_buddy( xfer->account, xfer->who );
+ who = buddy ? purple_buddy_get_name( buddy ) : xfer->who;
+
+ /* TODO(wilmer): After spreading some more const goodness in BitlBee,
+ remove the evil cast below. */
+ px->ft = imcb_file_send_start( ic, (char*) who, xfer->filename, xfer->size );
+ px->ft->data = px;
+
+ px->ft->accept = prpl_xfer_accept;
+ px->ft->canceled = prpl_xfer_canceled;
+ px->ft->write_request = prpl_xfer_write_request;
+
+ return FALSE;
+}
+
+gboolean try_write_to_ui( gpointer data, gint fd, b_input_condition cond )
+{
+ struct file_transfer *ft = data;
+ struct prpl_xfer_data *px = ft->data;
+ struct stat fs;
+ off_t tx_bytes;
+
+ /* If we don't have the file opened yet, there's no data so wait. */
+ if( px->fd < 0 || !px->ui_wants_data )
+ return FALSE;
+
+ tx_bytes = lseek( px->fd, 0, SEEK_CUR );
+ fstat( px->fd, &fs );
+
+ if( fs.st_size > tx_bytes )
+ {
+ char buf[1024];
+ size_t n = MIN( fs.st_size - tx_bytes, sizeof( buf ) );
+
+ if( read( px->fd, buf, n ) == n && ft->write( ft, buf, n ) )
+ {
+ px->ui_wants_data = FALSE;
+ }
+ else
+ {
+ purple_xfer_cancel_local( px->xfer );
+ imcb_file_canceled( ft, "Read error" );
+ }
+ }
+
+ if( lseek( px->fd, 0, SEEK_CUR ) == px->xfer->size )
+ {
+ /*purple_xfer_end( px->xfer );*/
+ imcb_file_finished( ft );
+ }
+
+ return FALSE;
+}
+
+/* UI calls this when its buffer is empty and wants more data to send to the user. */
+static gboolean prpl_xfer_write_request( struct file_transfer *ft )
+{
+ struct prpl_xfer_data *px = ft->data;
+
+ px->ui_wants_data = TRUE;
+ try_write_to_ui( ft, 0, 0 );
+
+ return FALSE;
+}
+
+
+/* Generic (IM<>UI): */
+static void prplcb_xfer_destroy( PurpleXfer *xfer )
+{
+ struct prpl_xfer_data *px = xfer->ui_data;
+
+ g_free( px->fn );
+ g_free( px->handle );
+ if( px->fd >= 0 )
+ close( px->fd );
+ g_free( px );
+}
+
+static void prplcb_xfer_progress( PurpleXfer *xfer, double percent )
+{
+ struct prpl_xfer_data *px = xfer->ui_data;
+
+ if( px == NULL )
+ return;
+
+ if( purple_xfer_get_type( xfer ) == PURPLE_XFER_SEND )
+ {
+ if( *px->fn )
+ {
+ char *slash;
+
+ unlink( px->fn );
+ if( ( slash = strrchr( px->fn, '/' ) ) )
+ {
+ *slash = '\0';
+ rmdir( px->fn );
+ }
+ *px->fn = '\0';
+ }
+
+ return;
+ }
+
+ if( px->fd == -1 && percent > 0 )
+ {
+ /* Weeeeeeeee, we're getting data! That means the file exists
+ by now so open it and start sending to the UI. */
+ px->fd = open( px->fn, O_RDONLY );
+
+ /* Unlink it now, because we don't need it after this. */
+ unlink( px->fn );
+ }
+
+ if( percent < 1 )
+ try_write_to_ui( px->ft, 0, 0 );
+ else
+ /* Another nice problem: If we have the whole file, it only
+ gets closed when we return. Problem: There may still be
+ stuff buffered and not written, we'll only see it after
+ the caller close()s the file. So poll the file after that. */
+ b_timeout_add( 0, try_write_to_ui, px->ft );
+}
+
+static void prplcb_xfer_cancel_remote( PurpleXfer *xfer )
+{
+ struct prpl_xfer_data *px = xfer->ui_data;
+
+ if( px->ft )
+ imcb_file_canceled( px->ft, "Canceled by remote end" );
+ else
+ /* px->ft == NULL for sends, because of the two stages. :-/ */
+ imcb_error( px->ic, "File transfer cancelled by remote end" );
+}
+
+static void prplcb_xfer_dbg( PurpleXfer *xfer )
+{
+ fprintf( stderr, "prplcb_xfer_dbg 0x%p\n", xfer );
+}
+
+
+/* Sending files (UI->IM): */
+static gboolean prpl_xfer_write( struct file_transfer *ft, char *buffer, unsigned int len );
+static gboolean purple_transfer_request_cb( gpointer data, gint fd, b_input_condition cond );
+
+void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle )
+{
+ struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 );
+ char *dir, *basename;
+
+ ft->data = px;
+ px->ft = ft;
+
+ dir = g_strdup( "/tmp/bitlbee-purple-ft.XXXXXX" );
+ if( !mkdtemp( dir ) )
+ {
+ imcb_error( ic, "Could not create temporary file for file transfer" );
+ g_free( px );
+ g_free( dir );
+ return;
+ }
+
+ if( ( basename = strrchr( ft->file_name, '/' ) ) )
+ basename++;
+ else
+ basename = ft->file_name;
+ px->fn = g_strdup_printf( "%s/%s", dir, basename );
+ px->fd = open( px->fn, O_WRONLY | O_CREAT, 0600 );
+ g_free( dir );
+
+ if( px->fd < 0 )
+ {
+ imcb_error( ic, "Could not create temporary file for file transfer" );
+ g_free( px );
+ g_free( px->fn );
+ return;
+ }
+
+ px->ic = ic;
+ px->handle = g_strdup( handle );
+
+ imcb_log( ic, "Due to libpurple limitations, the file has to be cached locally before proceeding with the actual file transfer. Please wait..." );
+
+ b_timeout_add( 0, purple_transfer_request_cb, ft );
+}
+
+static void purple_transfer_forward( struct file_transfer *ft )
+{
+ struct prpl_xfer_data *px = ft->data;
+ PurpleAccount *pa = px->ic->proto_data;
+
+ /* xfer_new() will pick up this variable. It's a hack but we're not
+ multi-threaded anyway. */
+ next_ft = ft;
+ serv_send_file( purple_account_get_connection( pa ), px->handle, px->fn );
+}
+
+static gboolean purple_transfer_request_cb( gpointer data, gint fd, b_input_condition cond )
+{
+ file_transfer_t *ft = data;
+
+ if( ft->write == NULL )
+ {
+ ft->write = prpl_xfer_write;
+ imcb_file_recv_start( ft );
+ }
+
+ ft->write_request( ft );
+
+ return FALSE;
+}
+
+static gboolean prpl_xfer_write( struct file_transfer *ft, char *buffer, unsigned int len )
+{
+ struct prpl_xfer_data *px = ft->data;
+
+ if( write( px->fd, buffer, len ) != len )
+ {
+ imcb_file_canceled( ft, "Error while writing temporary file" );
+ return FALSE;
+ }
+
+ if( lseek( px->fd, 0, SEEK_CUR ) >= ft->file_size )
+ {
+ close( px->fd );
+ px->fd = -1;
+
+ purple_transfer_forward( ft );
+ imcb_file_finished( ft );
+ px->ft = NULL;
+ }
+ else
+ b_timeout_add( 0, purple_transfer_request_cb, ft );
+
+ return TRUE;
+}
+
+
+
+PurpleXferUiOps bee_xfer_uiops =
+{
+ prplcb_xfer_new,
+ prplcb_xfer_destroy,
+ NULL, /* prplcb_xfer_add, */
+ prplcb_xfer_progress,
+ prplcb_xfer_dbg,
+ prplcb_xfer_cancel_remote,
+ NULL,
+ NULL,
+ prplcb_xfer_dbg,
+};
diff --git a/protocols/purple/purple.c b/protocols/purple/purple.c
new file mode 100644
index 00000000..98cd2241
--- /dev/null
+++ b/protocols/purple/purple.c
@@ -0,0 +1,1143 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* libpurple module - Main file *
+* *
+* Copyright 2009-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. *
+* *
+\***************************************************************************/
+
+#include "bitlbee.h"
+#include "help.h"
+
+#include <stdarg.h>
+
+#include <glib.h>
+#include <purple.h>
+
+GSList *purple_connections;
+
+/* This makes me VERY sad... :-( But some libpurple callbacks come in without
+ any context so this is the only way to get that. Don't want to support
+ libpurple in daemon mode anyway. */
+static irc_t *local_irc;
+
+static char *set_eval_display_name( set_t *set, char *value );
+
+struct im_connection *purple_ic_by_pa( PurpleAccount *pa )
+{
+ GSList *i;
+
+ for( i = purple_connections; i; i = i->next )
+ if( ((struct im_connection *)i->data)->proto_data == pa )
+ return i->data;
+
+ return NULL;
+}
+
+static struct im_connection *purple_ic_by_gc( PurpleConnection *gc )
+{
+ return purple_ic_by_pa( purple_connection_get_account( gc ) );
+}
+
+static gboolean purple_menu_cmp( const char *a, const char *b )
+{
+ while( *a && *b )
+ {
+ while( *a == '_' ) a ++;
+ while( *b == '_' ) b ++;
+ if( tolower( *a ) != tolower( *b ) )
+ return FALSE;
+
+ a ++;
+ b ++;
+ }
+
+ return ( *a == '\0' && *b == '\0' );
+}
+
+static void purple_init( account_t *acc )
+{
+ PurplePlugin *prpl = purple_plugins_find_with_id( (char*) acc->prpl->data );
+ PurplePluginProtocolInfo *pi = prpl->info->extra_info;
+ PurpleAccount *pa;
+ GList *i, *st;
+ set_t *s;
+ char help_title[64];
+ GString *help;
+
+ help = g_string_new( "" );
+ g_string_printf( help, "BitlBee libpurple module %s (%s).\n\nSupported settings:",
+ (char*) acc->prpl->name, prpl->info->name );
+
+ /* Convert all protocol_options into per-account setting variables. */
+ for( i = pi->protocol_options; i; i = i->next )
+ {
+ PurpleAccountOption *o = i->data;
+ const char *name;
+ char *def = NULL;
+ set_eval eval = NULL;
+ void *eval_data = NULL;
+ GList *io = NULL;
+ GSList *opts = NULL;
+
+ name = purple_account_option_get_setting( o );
+
+ switch( purple_account_option_get_type( o ) )
+ {
+ case PURPLE_PREF_STRING:
+ def = g_strdup( purple_account_option_get_default_string( o ) );
+
+ g_string_append_printf( help, "\n* %s (%s), %s, default: %s",
+ name, purple_account_option_get_text( o ),
+ "string", def );
+
+ break;
+
+ case PURPLE_PREF_INT:
+ def = g_strdup_printf( "%d", purple_account_option_get_default_int( o ) );
+ eval = set_eval_int;
+
+ g_string_append_printf( help, "\n* %s (%s), %s, default: %s",
+ name, purple_account_option_get_text( o ),
+ "integer", def );
+
+ break;
+
+ case PURPLE_PREF_BOOLEAN:
+ if( purple_account_option_get_default_bool( o ) )
+ def = g_strdup( "true" );
+ else
+ def = g_strdup( "false" );
+ eval = set_eval_bool;
+
+ g_string_append_printf( help, "\n* %s (%s), %s, default: %s",
+ name, purple_account_option_get_text( o ),
+ "boolean", def );
+
+ break;
+
+ case PURPLE_PREF_STRING_LIST:
+ def = g_strdup( purple_account_option_get_default_list_value( o ) );
+
+ g_string_append_printf( help, "\n* %s (%s), %s, default: %s",
+ name, purple_account_option_get_text( o ),
+ "list", def );
+ g_string_append( help, "\n Possible values: " );
+
+ for( io = purple_account_option_get_list( o ); io; io = io->next )
+ {
+ PurpleKeyValuePair *kv = io->data;
+ opts = g_slist_append( opts, kv->value );
+ /* TODO: kv->value is not a char*, WTF? */
+ if( strcmp( kv->value, kv->key ) != 0 )
+ g_string_append_printf( help, "%s (%s), ", (char*) kv->value, kv->key );
+ else
+ g_string_append_printf( help, "%s, ", (char*) kv->value );
+ }
+ g_string_truncate( help, help->len - 2 );
+ eval = set_eval_list;
+ eval_data = opts;
+
+ break;
+
+ default:
+ irc_usermsg( acc->irc, "Setting with unknown type: %s (%d) Expect stuff to break..\n",
+ name, purple_account_option_get_type( o ) );
+ name = NULL;
+ }
+
+ if( name != NULL )
+ {
+ s = set_add( &acc->set, name, def, eval, acc );
+ s->flags |= ACC_SET_OFFLINE_ONLY;
+ s->eval_data = eval_data;
+ g_free( def );
+ }
+ }
+
+ g_snprintf( help_title, sizeof( help_title ), "purple %s", (char*) acc->prpl->name );
+ help_add_mem( &global.help, help_title, help->str );
+ g_string_free( help, TRUE );
+
+ s = set_add( &acc->set, "display_name", NULL, set_eval_display_name, acc );
+ s->flags |= ACC_SET_ONLINE_ONLY;
+
+ if( pi->options & OPT_PROTO_MAIL_CHECK )
+ {
+ s = set_add( &acc->set, "mail_notifications", "false", set_eval_bool, acc );
+ s->flags |= ACC_SET_OFFLINE_ONLY;
+ }
+
+ /* Go through all away states to figure out if away/status messages
+ are possible. */
+ pa = purple_account_new( acc->user, (char*) acc->prpl->data );
+ for( st = purple_account_get_status_types( pa ); st; st = st->next )
+ {
+ PurpleStatusPrimitive prim = purple_status_type_get_primitive( st->data );
+
+ if( prim == PURPLE_STATUS_AVAILABLE )
+ {
+ if( purple_status_type_get_attr( st->data, "message" ) )
+ acc->flags |= ACC_FLAG_STATUS_MESSAGE;
+ }
+ else if( prim != PURPLE_STATUS_OFFLINE )
+ {
+ if( purple_status_type_get_attr( st->data, "message" ) )
+ acc->flags |= ACC_FLAG_AWAY_MESSAGE;
+ }
+ }
+ purple_accounts_remove( pa );
+}
+
+static void purple_sync_settings( account_t *acc, PurpleAccount *pa )
+{
+ PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id );
+ PurplePluginProtocolInfo *pi = prpl->info->extra_info;
+ GList *i;
+
+ for( i = pi->protocol_options; i; i = i->next )
+ {
+ PurpleAccountOption *o = i->data;
+ const char *name;
+ set_t *s;
+
+ name = purple_account_option_get_setting( o );
+ s = set_find( &acc->set, name );
+ if( s->value == NULL )
+ continue;
+
+ switch( purple_account_option_get_type( o ) )
+ {
+ case PURPLE_PREF_STRING:
+ case PURPLE_PREF_STRING_LIST:
+ purple_account_set_string( pa, name, set_getstr( &acc->set, name ) );
+ break;
+
+ case PURPLE_PREF_INT:
+ purple_account_set_int( pa, name, set_getint( &acc->set, name ) );
+ break;
+
+ case PURPLE_PREF_BOOLEAN:
+ purple_account_set_bool( pa, name, set_getbool( &acc->set, name ) );
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if( pi->options & OPT_PROTO_MAIL_CHECK )
+ purple_account_set_check_mail( pa, set_getbool( &acc->set, "mail_notifications" ) );
+}
+
+static void purple_login( account_t *acc )
+{
+ struct im_connection *ic = imcb_new( acc );
+ PurpleAccount *pa;
+
+ if( local_irc != NULL && local_irc != acc->irc )
+ {
+ irc_usermsg( acc->irc, "Daemon mode detected. Do *not* try to use libpurple in daemon mode! "
+ "Please use inetd or ForkDaemon mode instead." );
+ return;
+ }
+ local_irc = acc->irc;
+
+ /* For now this is needed in the _connected() handlers if using
+ GLib event handling, to make sure we're not handling events
+ on dead connections. */
+ purple_connections = g_slist_prepend( purple_connections, ic );
+
+ ic->proto_data = pa = purple_account_new( acc->user, (char*) acc->prpl->data );
+ purple_account_set_password( pa, acc->pass );
+ purple_sync_settings( acc, pa );
+
+ purple_account_set_enabled( pa, "BitlBee", TRUE );
+}
+
+static void purple_logout( struct im_connection *ic )
+{
+ PurpleAccount *pa = ic->proto_data;
+
+ purple_account_set_enabled( pa, "BitlBee", FALSE );
+ purple_connections = g_slist_remove( purple_connections, ic );
+ purple_accounts_remove( pa );
+}
+
+static int purple_buddy_msg( struct im_connection *ic, char *who, char *message, int flags )
+{
+ PurpleConversation *conv;
+
+ if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM,
+ who, ic->proto_data ) ) == NULL )
+ {
+ conv = purple_conversation_new( PURPLE_CONV_TYPE_IM,
+ ic->proto_data, who );
+ }
+
+ purple_conv_im_send( purple_conversation_get_im_data( conv ), message );
+
+ return 1;
+}
+
+static GList *purple_away_states( struct im_connection *ic )
+{
+ PurpleAccount *pa = ic->proto_data;
+ GList *st, *ret = NULL;
+
+ for( st = purple_account_get_status_types( pa ); st; st = st->next )
+ {
+ PurpleStatusPrimitive prim = purple_status_type_get_primitive( st->data );
+ if( prim != PURPLE_STATUS_AVAILABLE && prim != PURPLE_STATUS_OFFLINE )
+ ret = g_list_append( ret, (void*) purple_status_type_get_name( st->data ) );
+ }
+
+ return ret;
+}
+
+static void purple_set_away( struct im_connection *ic, char *state_txt, char *message )
+{
+ PurpleAccount *pa = ic->proto_data;
+ GList *status_types = purple_account_get_status_types( pa ), *st;
+ PurpleStatusType *pst = NULL;
+ GList *args = NULL;
+
+ for( st = status_types; st; st = st->next )
+ {
+ pst = st->data;
+
+ if( state_txt == NULL &&
+ purple_status_type_get_primitive( pst ) == PURPLE_STATUS_AVAILABLE )
+ break;
+
+ if( state_txt != NULL &&
+ g_strcasecmp( state_txt, purple_status_type_get_name( pst ) ) == 0 )
+ break;
+ }
+
+ if( message && purple_status_type_get_attr( pst, "message" ) )
+ {
+ args = g_list_append( args, "message" );
+ args = g_list_append( args, message );
+ }
+
+ purple_account_set_status_list( pa, st ? purple_status_type_get_id( pst ) : "away",
+ TRUE, args );
+
+ g_list_free( args );
+}
+
+static char *set_eval_display_name( set_t *set, char *value )
+{
+ account_t *acc = set->data;
+ struct im_connection *ic = acc->ic;
+
+ return NULL;
+}
+
+static void purple_add_buddy( struct im_connection *ic, char *who, char *group )
+{
+ PurpleBuddy *pb;
+
+ pb = purple_buddy_new( (PurpleAccount*) ic->proto_data, who, NULL );
+ purple_blist_add_buddy( pb, NULL, NULL, NULL );
+ purple_account_add_buddy( (PurpleAccount*) ic->proto_data, pb );
+}
+
+static void purple_remove_buddy( struct im_connection *ic, char *who, char *group )
+{
+ PurpleBuddy *pb;
+
+ pb = purple_find_buddy( (PurpleAccount*) ic->proto_data, who );
+ if( pb != NULL )
+ {
+ purple_account_remove_buddy( (PurpleAccount*) ic->proto_data, pb, NULL );
+ purple_blist_remove_buddy( pb );
+ }
+}
+
+static void purple_add_permit( struct im_connection *ic, char *who )
+{
+ PurpleAccount *pa = ic->proto_data;
+
+ purple_privacy_permit_add( pa, who, FALSE );
+}
+
+static void purple_add_deny( struct im_connection *ic, char *who )
+{
+ PurpleAccount *pa = ic->proto_data;
+
+ purple_privacy_deny_add( pa, who, FALSE );
+}
+
+static void purple_rem_permit( struct im_connection *ic, char *who )
+{
+ PurpleAccount *pa = ic->proto_data;
+
+ purple_privacy_permit_remove( pa, who, FALSE );
+}
+
+static void purple_rem_deny( struct im_connection *ic, char *who )
+{
+ PurpleAccount *pa = ic->proto_data;
+
+ purple_privacy_deny_remove( pa, who, FALSE );
+}
+
+static void purple_get_info( struct im_connection *ic, char *who )
+{
+ serv_get_info( purple_account_get_connection( ic->proto_data ), who );
+}
+
+static void purple_keepalive( struct im_connection *ic )
+{
+}
+
+static int purple_send_typing( struct im_connection *ic, char *who, int flags )
+{
+ PurpleTypingState state = PURPLE_NOT_TYPING;
+ PurpleConversation *conv;
+
+ if( flags & OPT_TYPING )
+ state = PURPLE_TYPING;
+ else if( flags & OPT_THINKING )
+ state = PURPLE_TYPED;
+
+ if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM,
+ who, ic->proto_data ) ) == NULL )
+ {
+ purple_conv_im_set_typing_state( purple_conversation_get_im_data( conv ), state );
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+static void purple_chat_msg( struct groupchat *gc, char *message, int flags )
+{
+ PurpleConversation *pc = gc->data;
+
+ purple_conv_chat_send( purple_conversation_get_chat_data( pc ), message );
+}
+
+struct groupchat *purple_chat_with( struct im_connection *ic, char *who )
+{
+ /* No, "of course" this won't work this way. Or in fact, it almost
+ does, but it only lets you send msgs to it, you won't receive
+ any. Instead, we have to click the virtual menu item.
+ PurpleAccount *pa = ic->proto_data;
+ PurpleConversation *pc;
+ PurpleConvChat *pcc;
+ struct groupchat *gc;
+
+ gc = imcb_chat_new( ic, "BitlBee-libpurple groupchat" );
+ gc->data = pc = purple_conversation_new( PURPLE_CONV_TYPE_CHAT, pa, "BitlBee-libpurple groupchat" );
+ pc->ui_data = gc;
+
+ pcc = PURPLE_CONV_CHAT( pc );
+ purple_conv_chat_add_user( pcc, ic->acc->user, "", 0, TRUE );
+ purple_conv_chat_invite_user( pcc, who, "Please join my chat", FALSE );
+ //purple_conv_chat_add_user( pcc, who, "", 0, TRUE );
+ */
+
+ /* There went my nice afternoon. :-( */
+
+ PurpleAccount *pa = ic->proto_data;
+ PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id );
+ PurplePluginProtocolInfo *pi = prpl->info->extra_info;
+ PurpleBuddy *pb = purple_find_buddy( (PurpleAccount*) ic->proto_data, who );
+ PurpleMenuAction *mi;
+ GList *menu;
+ void (*callback)(PurpleBlistNode *, gpointer); /* FFFFFFFFFFFFFUUUUUUUUUUUUUU */
+
+ if( !pb || !pi || !pi->blist_node_menu )
+ return NULL;
+
+ menu = pi->blist_node_menu( &pb->node );
+ while( menu )
+ {
+ mi = menu->data;
+ if( purple_menu_cmp( mi->label, "initiate chat" ) ||
+ purple_menu_cmp( mi->label, "initiate conference" ) )
+ break;
+ menu = menu->next;
+ }
+
+ if( menu == NULL )
+ return NULL;
+
+ /* Call the fucker. */
+ callback = (void*) mi->callback;
+ callback( &pb->node, menu->data );
+
+ return NULL;
+}
+
+void purple_chat_invite( struct groupchat *gc, char *who, char *message )
+{
+ PurpleConversation *pc = gc->data;
+ PurpleConvChat *pcc = PURPLE_CONV_CHAT( pc );
+
+ serv_chat_invite( purple_account_get_connection( gc->ic->proto_data ),
+ purple_conv_chat_get_id( pcc ),
+ message && *message ? message : "Please join my chat",
+ who );
+}
+
+void purple_chat_leave( struct groupchat *gc )
+{
+ PurpleConversation *pc = gc->data;
+
+ purple_conversation_destroy( pc );
+}
+
+struct groupchat *purple_chat_join( struct im_connection *ic, const char *room, const char *nick, const char *password )
+{
+ PurpleAccount *pa = ic->proto_data;
+ PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id );
+ PurplePluginProtocolInfo *pi = prpl->info->extra_info;
+ GHashTable *chat_hash;
+ PurpleConversation *conv;
+ GList *info, *l;
+
+ if( !pi->chat_info || !pi->chat_info_defaults ||
+ !( info = pi->chat_info( purple_account_get_connection( pa ) ) ) )
+ {
+ imcb_error( ic, "Joining chatrooms not supported by this protocol" );
+ return NULL;
+ }
+
+ if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_CHAT, room, pa ) ) )
+ purple_conversation_destroy( conv );
+
+ chat_hash = pi->chat_info_defaults( purple_account_get_connection( pa ), room );
+
+ for( l = info; l; l = l->next )
+ {
+ struct proto_chat_entry *pce = l->data;
+
+ if( strcmp( pce->identifier, "handle" ) == 0 )
+ g_hash_table_replace( chat_hash, "handle", g_strdup( nick ) );
+ else if( strcmp( pce->identifier, "password" ) == 0 )
+ g_hash_table_replace( chat_hash, "password", g_strdup( password ) );
+ else if( strcmp( pce->identifier, "passwd" ) == 0 )
+ g_hash_table_replace( chat_hash, "passwd", g_strdup( password ) );
+ }
+
+ serv_join_chat( purple_account_get_connection( pa ), chat_hash );
+
+ return NULL;
+}
+
+void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle );
+
+static void purple_ui_init();
+
+GHashTable *prplcb_ui_info()
+{
+ static GHashTable *ret;
+
+ if( ret == NULL )
+ {
+ ret = g_hash_table_new(g_str_hash, g_str_equal);
+ g_hash_table_insert( ret, "name", "BitlBee" );
+ g_hash_table_insert( ret, "version", BITLBEE_VERSION );
+ }
+
+ return ret;
+}
+
+static PurpleCoreUiOps bee_core_uiops =
+{
+ NULL,
+ NULL,
+ purple_ui_init,
+ NULL,
+ prplcb_ui_info,
+};
+
+static void prplcb_conn_progress( PurpleConnection *gc, const char *text, size_t step, size_t step_count )
+{
+ struct im_connection *ic = purple_ic_by_gc( gc );
+
+ imcb_log( ic, "%s", text );
+}
+
+static void prplcb_conn_connected( PurpleConnection *gc )
+{
+ struct im_connection *ic = purple_ic_by_gc( gc );
+ const char *dn;
+ set_t *s;
+
+ imcb_connected( ic );
+
+ if( ( dn = purple_connection_get_display_name( gc ) ) &&
+ ( s = set_find( &ic->acc->set, "display_name" ) ) )
+ {
+ g_free( s->value );
+ s->value = g_strdup( dn );
+ }
+
+ if( gc->flags & PURPLE_CONNECTION_HTML )
+ ic->flags |= OPT_DOES_HTML;
+}
+
+static void prplcb_conn_disconnected( PurpleConnection *gc )
+{
+ struct im_connection *ic = purple_ic_by_gc( gc );
+
+ if( ic != NULL )
+ {
+ imc_logout( ic, !gc->wants_to_die );
+ }
+}
+
+static void prplcb_conn_notice( PurpleConnection *gc, const char *text )
+{
+ struct im_connection *ic = purple_ic_by_gc( gc );
+
+ if( ic != NULL )
+ imcb_log( ic, "%s", text );
+}
+
+static void prplcb_conn_report_disconnect_reason( PurpleConnection *gc, PurpleConnectionError reason, const char *text )
+{
+ struct im_connection *ic = purple_ic_by_gc( gc );
+
+ /* PURPLE_CONNECTION_ERROR_NAME_IN_USE means concurrent login,
+ should probably handle that. */
+ if( ic != NULL )
+ imcb_error( ic, "%s", text );
+}
+
+static PurpleConnectionUiOps bee_conn_uiops =
+{
+ prplcb_conn_progress,
+ prplcb_conn_connected,
+ prplcb_conn_disconnected,
+ prplcb_conn_notice,
+ NULL,
+ NULL,
+ NULL,
+ prplcb_conn_report_disconnect_reason,
+};
+
+static void prplcb_blist_new( PurpleBlistNode *node )
+{
+ PurpleBuddy *bud = (PurpleBuddy*) node;
+
+ if( node->type == PURPLE_BLIST_BUDDY_NODE )
+ {
+ struct im_connection *ic = purple_ic_by_pa( bud->account );
+
+ if( ic == NULL )
+ return;
+
+ imcb_add_buddy( ic, bud->name, NULL );
+ if( bud->server_alias )
+ {
+ imcb_rename_buddy( ic, bud->name, bud->server_alias );
+ imcb_buddy_nick_hint( ic, bud->name, bud->server_alias );
+ }
+ }
+}
+
+static void prplcb_blist_update( PurpleBuddyList *list, PurpleBlistNode *node )
+{
+ PurpleBuddy *bud = (PurpleBuddy*) node;
+
+ if( node->type == PURPLE_BLIST_BUDDY_NODE )
+ {
+ struct im_connection *ic = purple_ic_by_pa( bud->account );
+ PurpleStatus *as;
+ int flags = 0;
+
+ if( ic == NULL )
+ return;
+
+ if( bud->server_alias )
+ imcb_rename_buddy( ic, bud->name, bud->server_alias );
+
+ flags |= purple_presence_is_online( bud->presence ) ? OPT_LOGGED_IN : 0;
+ flags |= purple_presence_is_available( bud->presence ) ? 0 : OPT_AWAY;
+
+ as = purple_presence_get_active_status( bud->presence );
+
+ imcb_buddy_status( ic, bud->name, flags, purple_status_get_name( as ),
+ purple_status_get_attr_string( as, "message" ) );
+ }
+}
+
+static void prplcb_blist_remove( PurpleBuddyList *list, PurpleBlistNode *node )
+{
+ /*
+ PurpleBuddy *bud = (PurpleBuddy*) node;
+
+ if( node->type == PURPLE_BLIST_BUDDY_NODE )
+ {
+ struct im_connection *ic = purple_ic_by_pa( bud->account );
+
+ if( ic == NULL )
+ return;
+
+ imcb_remove_buddy( ic, bud->name, NULL );
+ }
+ */
+}
+
+static PurpleBlistUiOps bee_blist_uiops =
+{
+ NULL,
+ prplcb_blist_new,
+ NULL,
+ prplcb_blist_update,
+ prplcb_blist_remove,
+};
+
+void prplcb_conv_new( PurpleConversation *conv )
+{
+ if( conv->type == PURPLE_CONV_TYPE_CHAT )
+ {
+ struct im_connection *ic = purple_ic_by_pa( conv->account );
+ struct groupchat *gc;
+
+ gc = imcb_chat_new( ic, conv->name );
+ conv->ui_data = gc;
+ gc->data = conv;
+
+ /* libpurple brokenness: Whatever. Show that we join right away,
+ there's no clear "This is you!" signaling in _add_users so
+ don't even try. */
+ imcb_chat_add_buddy( gc, gc->ic->acc->user );
+ }
+}
+
+void prplcb_conv_free( PurpleConversation *conv )
+{
+ struct groupchat *gc = conv->ui_data;
+
+ imcb_chat_free( gc );
+}
+
+void prplcb_conv_add_users( PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals )
+{
+ struct groupchat *gc = conv->ui_data;
+ GList *b;
+
+ for( b = cbuddies; b; b = b->next )
+ {
+ PurpleConvChatBuddy *pcb = b->data;
+
+ imcb_chat_add_buddy( gc, pcb->name );
+ }
+}
+
+void prplcb_conv_del_users( PurpleConversation *conv, GList *cbuddies )
+{
+ struct groupchat *gc = conv->ui_data;
+ GList *b;
+
+ for( b = cbuddies; b; b = b->next )
+ imcb_chat_remove_buddy( gc, b->data, "" );
+}
+
+void prplcb_conv_chat_msg( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime )
+{
+ struct groupchat *gc = conv->ui_data;
+ PurpleBuddy *buddy;
+
+ /* ..._SEND means it's an outgoing message, no need to echo those. */
+ if( flags & PURPLE_MESSAGE_SEND )
+ return;
+
+ buddy = purple_find_buddy( conv->account, who );
+ if( buddy != NULL )
+ who = purple_buddy_get_name( buddy );
+
+ imcb_chat_msg( gc, who, (char*) message, 0, mtime );
+}
+
+static void prplcb_conv_im( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime )
+{
+ struct im_connection *ic = purple_ic_by_pa( conv->account );
+ PurpleBuddy *buddy;
+
+ /* ..._SEND means it's an outgoing message, no need to echo those. */
+ if( flags & PURPLE_MESSAGE_SEND )
+ return;
+
+ buddy = purple_find_buddy( conv->account, who );
+ if( buddy != NULL )
+ who = purple_buddy_get_name( buddy );
+
+ imcb_buddy_msg( ic, (char*) who, (char*) message, 0, mtime );
+}
+
+static PurpleConversationUiOps bee_conv_uiops =
+{
+ prplcb_conv_new, /* create_conversation */
+ prplcb_conv_free, /* destroy_conversation */
+ prplcb_conv_chat_msg, /* write_chat */
+ prplcb_conv_im, /* write_im */
+ NULL, /* write_conv */
+ prplcb_conv_add_users, /* chat_add_users */
+ NULL, /* chat_rename_user */
+ prplcb_conv_del_users, /* chat_remove_users */
+ NULL, /* chat_update_user */
+ NULL, /* present */
+ NULL, /* has_focus */
+ NULL, /* custom_smiley_add */
+ NULL, /* custom_smiley_write */
+ NULL, /* custom_smiley_close */
+ NULL, /* send_confirm */
+};
+
+struct prplcb_request_action_data
+{
+ void *user_data, *bee_data;
+ PurpleRequestActionCb yes, no;
+ int yes_i, no_i;
+};
+
+static void prplcb_request_action_yes( void *data )
+{
+ struct prplcb_request_action_data *pqad = data;
+
+ pqad->yes( pqad->user_data, pqad->yes_i );
+ g_free( pqad );
+}
+
+static void prplcb_request_action_no( void *data )
+{
+ struct prplcb_request_action_data *pqad = data;
+
+ pqad->no( pqad->user_data, pqad->no_i );
+ g_free( pqad );
+}
+
+static void *prplcb_request_action( const char *title, const char *primary, const char *secondary,
+ int default_action, PurpleAccount *account, const char *who,
+ PurpleConversation *conv, void *user_data, size_t action_count,
+ va_list actions )
+{
+ struct prplcb_request_action_data *pqad;
+ int i;
+ char *q;
+
+ pqad = g_new0( struct prplcb_request_action_data, 1 );
+
+ for( i = 0; i < action_count; i ++ )
+ {
+ char *caption;
+ void *fn;
+
+ caption = va_arg( actions, char* );
+ fn = va_arg( actions, void* );
+
+ if( strstr( caption, "Accept" ) )
+ {
+ pqad->yes = fn;
+ pqad->yes_i = i;
+ }
+ else if( strstr( caption, "Reject" ) || strstr( caption, "Cancel" ) )
+ {
+ pqad->no = fn;
+ pqad->no_i = i;
+ }
+ }
+
+ pqad->user_data = user_data;
+
+ q = g_strdup_printf( "Request: %s\n\n%s\n\n%s", title, primary, secondary );
+ pqad->bee_data = query_add( local_irc, purple_ic_by_pa( account ), q,
+ prplcb_request_action_yes, prplcb_request_action_no, pqad );
+
+ g_free( q );
+
+ return pqad;
+}
+
+static PurpleRequestUiOps bee_request_uiops =
+{
+ NULL,
+ NULL,
+ prplcb_request_action,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+
+static void prplcb_privacy_permit_added( PurpleAccount *account, const char *name )
+{
+ struct im_connection *ic = purple_ic_by_pa( account );
+
+ if( !g_slist_find_custom( ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp ) )
+ ic->permit = g_slist_prepend( ic->permit, g_strdup( name ) );
+}
+
+static void prplcb_privacy_permit_removed( PurpleAccount *account, const char *name )
+{
+ struct im_connection *ic = purple_ic_by_pa( account );
+ void *n;
+
+ n = g_slist_find_custom( ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp );
+ ic->permit = g_slist_remove( ic->permit, n );
+}
+
+static void prplcb_privacy_deny_added( PurpleAccount *account, const char *name )
+{
+ struct im_connection *ic = purple_ic_by_pa( account );
+
+ if( !g_slist_find_custom( ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp ) )
+ ic->deny = g_slist_prepend( ic->deny, g_strdup( name ) );
+}
+
+static void prplcb_privacy_deny_removed( PurpleAccount *account, const char *name )
+{
+ struct im_connection *ic = purple_ic_by_pa( account );
+ void *n;
+
+ n = g_slist_find_custom( ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp );
+ ic->deny = g_slist_remove( ic->deny, n );
+}
+
+static PurplePrivacyUiOps bee_privacy_uiops =
+{
+ prplcb_privacy_permit_added,
+ prplcb_privacy_permit_removed,
+ prplcb_privacy_deny_added,
+ prplcb_privacy_deny_removed,
+};
+
+static void prplcb_debug_print( PurpleDebugLevel level, const char *category, const char *arg_s )
+{
+ fprintf( stderr, "DEBUG %s: %s", category, arg_s );
+}
+
+static PurpleDebugUiOps bee_debug_uiops =
+{
+ prplcb_debug_print,
+};
+
+static guint prplcb_ev_timeout_add( guint interval, GSourceFunc func, gpointer udata )
+{
+ return b_timeout_add( interval, (b_event_handler) func, udata );
+}
+
+static guint prplcb_ev_input_add( int fd, PurpleInputCondition cond, PurpleInputFunction func, gpointer udata )
+{
+ return b_input_add( fd, cond | B_EV_FLAG_FORCE_REPEAT, (b_event_handler) func, udata );
+}
+
+static gboolean prplcb_ev_remove( guint id )
+{
+ b_event_remove( (gint) id );
+ return TRUE;
+}
+
+static PurpleEventLoopUiOps glib_eventloops =
+{
+ prplcb_ev_timeout_add,
+ prplcb_ev_remove,
+ prplcb_ev_input_add,
+ prplcb_ev_remove,
+};
+
+static void *prplcb_notify_email( PurpleConnection *gc, const char *subject, const char *from,
+ const char *to, const char *url )
+{
+ struct im_connection *ic = purple_ic_by_gc( gc );
+
+ imcb_log( ic, "Received e-mail from %s for %s: %s <%s>", from, to, subject, url );
+
+ return NULL;
+}
+
+static void *prplcb_notify_userinfo( PurpleConnection *gc, const char *who, PurpleNotifyUserInfo *user_info )
+{
+ struct im_connection *ic = purple_ic_by_gc( gc );
+ GString *info = g_string_new( "" );
+ GList *l = purple_notify_user_info_get_entries( user_info );
+ char *key;
+ const char *value;
+ int n;
+
+ while( l )
+ {
+ PurpleNotifyUserInfoEntry *e = l->data;
+
+ switch( purple_notify_user_info_entry_get_type( e ) )
+ {
+ case PURPLE_NOTIFY_USER_INFO_ENTRY_PAIR:
+ case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_HEADER:
+ key = g_strdup( purple_notify_user_info_entry_get_label( e ) );
+ value = purple_notify_user_info_entry_get_value( e );
+
+ if( key )
+ {
+ strip_html( key );
+ g_string_append_printf( info, "%s: ", key );
+
+ if( value )
+ {
+ n = strlen( value ) - 1;
+ while( isspace( value[n] ) )
+ n --;
+ g_string_append_len( info, value, n + 1 );
+ }
+ g_string_append_c( info, '\n' );
+ g_free( key );
+ }
+
+ break;
+ case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_BREAK:
+ g_string_append( info, "------------------------\n" );
+ break;
+ }
+
+ l = l->next;
+ }
+
+ imcb_log( ic, "User %s info:\n%s", who, info->str );
+ g_string_free( info, TRUE );
+
+ return NULL;
+}
+
+static PurpleNotifyUiOps bee_notify_uiops =
+{
+ NULL,
+ prplcb_notify_email,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ prplcb_notify_userinfo,
+};
+
+extern PurpleXferUiOps bee_xfer_uiops;
+
+static void purple_ui_init()
+{
+ purple_blist_set_ui_ops( &bee_blist_uiops );
+ purple_connections_set_ui_ops( &bee_conn_uiops );
+ purple_conversations_set_ui_ops( &bee_conv_uiops );
+ purple_request_set_ui_ops( &bee_request_uiops );
+ purple_notify_set_ui_ops( &bee_notify_uiops );
+ purple_xfers_set_ui_ops( &bee_xfer_uiops );
+ purple_privacy_set_ui_ops( &bee_privacy_uiops );
+
+ if( getenv( "BITLBEE_DEBUG" ) )
+ purple_debug_set_ui_ops( &bee_debug_uiops );
+}
+
+void purple_initmodule()
+{
+ struct prpl funcs;
+ GList *prots;
+ GString *help;
+
+ if( B_EV_IO_READ != PURPLE_INPUT_READ ||
+ B_EV_IO_WRITE != PURPLE_INPUT_WRITE )
+ {
+ /* FIXME FIXME FIXME FIXME FIXME :-) */
+ exit( 1 );
+ }
+
+ purple_util_set_user_dir("/tmp");
+ purple_debug_set_enabled(FALSE);
+ purple_core_set_ui_ops(&bee_core_uiops);
+ purple_eventloop_set_ui_ops(&glib_eventloops);
+ if( !purple_core_init( "BitlBee") )
+ {
+ /* Initializing the core failed. Terminate. */
+ fprintf( stderr, "libpurple initialization failed.\n" );
+ abort();
+ }
+
+ /* This seems like stateful shit we don't want... */
+ purple_set_blist(purple_blist_new());
+ purple_blist_load();
+
+ /* Meh? */
+ purple_prefs_load();
+
+ memset( &funcs, 0, sizeof( funcs ) );
+ funcs.login = purple_login;
+ funcs.init = purple_init;
+ funcs.logout = purple_logout;
+ funcs.buddy_msg = purple_buddy_msg;
+ funcs.away_states = purple_away_states;
+ funcs.set_away = purple_set_away;
+ funcs.add_buddy = purple_add_buddy;
+ funcs.remove_buddy = purple_remove_buddy;
+ funcs.add_permit = purple_add_permit;
+ funcs.add_deny = purple_add_deny;
+ funcs.rem_permit = purple_rem_permit;
+ funcs.rem_deny = purple_rem_deny;
+ funcs.get_info = purple_get_info;
+ funcs.keepalive = purple_keepalive;
+ funcs.send_typing = purple_send_typing;
+ funcs.handle_cmp = g_strcasecmp;
+ /* TODO(wilmer): Set these only for protocols that support them? */
+ funcs.chat_msg = purple_chat_msg;
+ funcs.chat_with = purple_chat_with;
+ funcs.chat_invite = purple_chat_invite;
+ funcs.chat_leave = purple_chat_leave;
+ funcs.chat_join = purple_chat_join;
+ funcs.transfer_request = purple_transfer_request;
+
+ help = g_string_new("BitlBee libpurple module supports the following IM protocols:\n");
+
+ /* Add a protocol entry to BitlBee's structures for every protocol
+ supported by this libpurple instance. */
+ for( prots = purple_plugins_get_protocols(); prots; prots = prots->next )
+ {
+ PurplePlugin *prot = prots->data;
+ struct prpl *ret;
+
+ ret = g_memdup( &funcs, sizeof( funcs ) );
+ ret->name = ret->data = prot->info->id;
+ if( strncmp( ret->name, "prpl-", 5 ) == 0 )
+ ret->name += 5;
+ register_protocol( ret );
+
+ g_string_append_printf( help, "\n* %s (%s)", ret->name, prot->info->name );
+
+ /* libpurple doesn't define a protocol called OSCAR, but we
+ need it to be compatible with normal BitlBee. */
+ if( g_strcasecmp( prot->info->id, "prpl-aim" ) == 0 )
+ {
+ ret = g_memdup( &funcs, sizeof( funcs ) );
+ ret->name = "oscar";
+ ret->data = prot->info->id;
+ register_protocol( ret );
+ }
+ }
+
+ g_string_append( help, "\n\nFor used protocols, more information about available "
+ "settings can be found using \x02help purple <protocol name>\x02" );
+
+ /* Add a simple dynamically-generated help item listing all
+ the supported protocols. */
+ help_add_mem( &global.help, "purple", help->str );
+ g_string_free( help, TRUE );
+}
diff --git a/protocols/twitter/Makefile b/protocols/twitter/Makefile
new file mode 100644
index 00000000..8a4b97f9
--- /dev/null
+++ b/protocols/twitter/Makefile
@@ -0,0 +1,46 @@
+###########################
+## 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
+
+CFLAGS += -Wall
+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..98e85641
--- /dev/null
+++ b/protocols/twitter/twitter.c
@@ -0,0 +1,364 @@
+/***************************************************************************\
+* *
+* 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"
+
+/**
+ * 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;
+
+ // If the user uses multiple private message windows we need to get the
+ // users buddies.
+ if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "many") == 0)
+ twitter_get_statuses_friends(ic, -1);
+
+ // 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, "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 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;
+
+ 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", "true", 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 = 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;
+ 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;
+
+ 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 );
+}
+
+/**
+ * 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->pass );
+ g_free( td );
+ }
+
+ twitter_connections = g_slist_remove( twitter_connections, ic );
+}
+
+/**
+ *
+ */
+static int twitter_buddy_msg( struct im_connection *ic, char *who, char *message, int away )
+{
+ 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 if( twitter_length_check(ic, message) )
+ twitter_post_status(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 )
+{
+}
+
+static void twitter_remove_buddy( struct im_connection *ic, char *who, char *group )
+{
+}
+
+static void twitter_chat_msg( struct groupchat *c, char *message, int flags )
+{
+ if( c && message && twitter_length_check(c->ic, message))
+ twitter_post_status(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;
+//}
+
+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->handle_cmp = g_strcasecmp;
+
+ 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..24f61e42
--- /dev/null
+++ b/protocols/twitter/twitter.h
@@ -0,0 +1,53 @@
+/***************************************************************************\
+* *
+* 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
+
+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;
+ gint http_fails;
+};
+
+/**
+ * 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;
+
+#endif //_TWITTER_H
diff --git a/protocols/twitter/twitter_http.c b/protocols/twitter/twitter_http.c
new file mode 100644
index 00000000..51f437df
--- /dev/null
+++ b/protocols/twitter/twitter_http.c
@@ -0,0 +1,175 @@
+/***************************************************************************\
+* *
+* 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"
+
+
+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, struct oauth_info* oi, char** arguments, int arguments_len)
+{
+ url_t *url = g_new0( url_t, 1 );
+ char *tmp;
+ char *request;
+ void *ret;
+ char *userpass = NULL;
+ char *userpass_base64;
+ char *url_arguments;
+
+ // Fill the url structure.
+ if( !url_set( url, url_string ) )
+ {
+ g_free( url );
+ return NULL;
+ }
+
+ if( url->proto != PROTO_HTTP && url->proto != PROTO_HTTPS )
+ {
+ g_free( url );
+ return NULL;
+ }
+
+ // Concatenate user and pass
+ if (user && pass) {
+ userpass = g_strdup_printf("%s:%s", user, pass);
+ userpass_base64 = base64_encode((unsigned char*)userpass, strlen(userpass));
+ } else {
+ userpass_base64 = NULL;
+ }
+
+ url_arguments = g_malloc(1);
+ url_arguments[0] = '\0';
+
+ // 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;
+ }
+ }
+
+ // Do GET stuff...
+ if (!is_post)
+ {
+ // Find the char-pointer of the end of the string.
+ tmp = url->file + strlen(url->file);
+ tmp[0] = '?';
+ // append the url_arguments to the end of the url->file.
+ // TODO GM: Check the length?
+ g_stpcpy (tmp+1, url_arguments);
+ }
+
+
+ // Make the request.
+ request = g_strdup_printf( "%s %s HTTP/1.0\r\n"
+ "Host: %s\r\n"
+ "User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n",
+ is_post ? "POST" : "GET", url->file, url->host );
+
+ // If a pass and user are given we append them to the request.
+ 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);
+ request = tmp;
+ }
+
+ // Do POST stuff..
+ if (is_post)
+ {
+ // Append the Content-Type and url-encoded arguments.
+ tmp = g_strdup_printf("%sContent-Type: application/x-www-form-urlencoded\r\nContent-Length: %zd\r\n\r\n%s",
+ request, strlen(url_arguments), url_arguments);
+ g_free(request);
+ request = tmp;
+ } else {
+ // Append an extra \r\n to end the request...
+ tmp = g_strdup_printf("%s\r\n", request);
+ g_free(request);
+ request = tmp;
+ }
+
+ ret = http_dorequest( url->host, url->port, url->proto == PROTO_HTTPS, request, func, data );
+
+ g_free( url );
+ g_free( userpass );
+ g_free( userpass_base64 );
+ g_free( url_arguments );
+ g_free( request );
+ return ret;
+}
+
+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..5ef2530f
--- /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(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);
+
+#endif //_TWITTER_HTTP_H
+
diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c
new file mode 100644
index 00000000..ee6e39fe
--- /dev/null
+++ b/protocols/twitter/twitter_lib.c
@@ -0,0 +1,677 @@
+/***************************************************************************\
+* *
+* 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(): */
+#define _XOPEN_SOURCE
+
+#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>
+
+#define TXL_STATUS 1
+#define TXL_USER 2
+#define TXL_ID 3
+
+struct twitter_xml_list {
+ int type;
+ int 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;
+};
+
+/**
+ * Frees a twitter_xml_user struct.
+ */
+static void txu_free(struct twitter_xml_user *txu)
+{
+ 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;
+ 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 (!imcb_find_buddy( 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)
+ 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 );
+ }
+}
+
+static void twitter_http_get_friends_ids(struct http_request *req);
+
+/**
+ * Get the friends ids.
+ */
+void twitter_get_friends_ids(struct im_connection *ic, int next_cursor)
+{
+ struct twitter_data *td = ic->proto_data;
+
+ // Primitive, but hey! It works...
+ 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, td->oauth_info, 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 )
+{
+ // Do something with the cursor.
+ txl->next_cursor = node->text != NULL ? atoi(node->text) : -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. HTTP STATUS: %d", req->status_code);
+
+ 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;
+
+ // 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( "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);
+ }
+ }
+ 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 xt_node *node, struct twitter_xml_list *txl )
+{
+ struct twitter_xml_status *txs;
+ struct xt_node *child;
+
+ // 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);
+ }
+ 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, int next_cursor)
+{
+ struct twitter_data *td = ic->proto_data;
+
+ char* args[4];
+ args[0] = "cursor";
+ args[1] = g_strdup_printf ("%d", 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(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) {
+ g_free(args[3]);
+ }
+}
+
+/**
+ * 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)
+ {
+ char *name_hint = g_strdup_printf( "Twitter_%s", ic->acc->user );
+ td->home_timeline_gc = gc = imcb_chat_new( ic, "home/timeline" );
+ imcb_chat_name_hint( gc, name_hint );
+ g_free( name_hint );
+ // Add the current user to the chat...
+ imcb_chat_add_buddy( gc, ic->acc->user );
+ }
+ else
+ {
+ gc = td->home_timeline_gc;
+ }
+
+ for ( l = list; l ; l = g_slist_next(l) )
+ {
+ status = l->data;
+ twitter_add_buddy(ic, status->user->screen_name, status->user->name);
+
+ // 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, "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;
+
+ 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 ". HTTP STATUS: %d", req->status_code);
+
+ 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(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 != 200) {
+ // It didn't go well, output the error and return.
+ if (++td->http_fails >= 5)
+ imcb_error(ic, "Could not retrieve " TWITTER_SHOW_FRIENDS_URL " HTTP STATUS: %d", req->status_code);
+
+ return;
+ } else {
+ td->http_fails = 0;
+ }
+
+ 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);
+
+ // Free the structure.
+ txl_free(txl);
+ g_free(txl);
+}
+
+/**
+ * Get the friends.
+ */
+void twitter_get_statuses_friends(struct im_connection *ic, int next_cursor)
+{
+ struct twitter_data *td = ic->proto_data;
+
+ char* args[2];
+ 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, td->oauth_info, args, 2);
+
+ g_free(args[1]);
+}
+
+/**
+ * Callback after sending a new update to twitter.
+ */
+static void twitter_http_post_status(struct http_request *req)
+{
+ struct im_connection *ic = req->data;
+
+ // Check if the connection is still active.
+ if( !g_slist_find( twitter_connections, ic ) )
+ return;
+
+ // 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 message... HTTP STATUS: %d", req->status_code);
+ return;
+ }
+}
+
+/**
+ * Function to POST a new status to twitter.
+ */
+void twitter_post_status(struct im_connection *ic, char* msg)
+{
+ struct twitter_data *td = ic->proto_data;
+
+ 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, td->oauth_info, args, 2);
+// g_free(args[1]);
+}
+
+
+/**
+ * Function to POST a new message to twitter.
+ */
+void twitter_direct_messages_new(struct im_connection *ic, char *who, char *msg)
+{
+ struct twitter_data *td = ic->proto_data;
+
+ 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(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]);
+}
diff --git a/protocols/twitter/twitter_lib.h b/protocols/twitter/twitter_lib.h
new file mode 100644
index 00000000..e47bfd95
--- /dev/null
+++ b/protocols/twitter/twitter_lib.h
@@ -0,0 +1,86 @@
+/***************************************************************************\
+* *
+* 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"
+
+/* Status URLs */
+#define TWITTER_STATUS_UPDATE_URL TWITTER_API_URL "/statuses/update.xml"
+#define TWITTER_STATUS_SHOW_URL TWITTER_API_URL "/statuses/show/"
+#define TWITTER_STATUS_DESTROY_URL TWITTER_API_URL "/statuses/destroy/"
+
+/* Timeline URLs */
+#define TWITTER_PUBLIC_TIMELINE_URL TWITTER_API_URL "/statuses/public_timeline.xml"
+#define TWITTER_FEATURED_USERS_URL TWITTER_API_URL "/statuses/featured.xml"
+#define TWITTER_FRIENDS_TIMELINE_URL TWITTER_API_URL "/statuses/friends_timeline.xml"
+#define TWITTER_HOME_TIMELINE_URL TWITTER_API_URL "/statuses/home_timeline.xml"
+#define TWITTER_MENTIONS_URL TWITTER_API_URL "/statuses/mentions.xml"
+#define TWITTER_USER_TIMELINE_URL TWITTER_API_URL "/statuses/user_timeline.xml"
+
+/* Users URLs */
+#define TWITTER_SHOW_USERS_URL TWITTER_API_URL "/users/show.xml"
+#define TWITTER_SHOW_FRIENDS_URL TWITTER_API_URL "/statuses/friends.xml"
+#define TWITTER_SHOW_FOLLOWERS_URL TWITTER_API_URL "/statuses/followers.xml"
+
+/* Direct messages URLs */
+#define TWITTER_DIRECT_MESSAGES_URL TWITTER_API_URL "/direct_messages.xml"
+#define TWITTER_DIRECT_MESSAGES_NEW_URL TWITTER_API_URL "/direct_messages/new.xml"
+#define TWITTER_DIRECT_MESSAGES_SENT_URL TWITTER_API_URL "/direct_messages/sent.xml"
+#define TWITTER_DIRECT_MESSAGES_DESTROY_URL TWITTER_API_URL "/direct_messages/destroy/"
+
+/* Friendships URLs */
+#define TWITTER_FRIENDSHIPS_CREATE_URL TWITTER_API_URL "/friendships/create.xml"
+#define TWITTER_FRIENDSHIPS_DESTROY_URL TWITTER_API_URL "/friendships/destroy.xml"
+#define TWITTER_FRIENDSHIPS_SHOW_URL TWITTER_API_URL "/friendships/show.xml"
+
+/* Social graphs URLs */
+#define TWITTER_FRIENDS_IDS_URL TWITTER_API_URL "/friends/ids.xml"
+#define TWITTER_FOLLOWERS_IDS_URL TWITTER_API_URL "/followers/ids.xml"
+
+/* Account URLs */
+#define TWITTER_ACCOUNT_RATE_LIMIT_URL TWITTER_API_URL "/account/rate_limit_status.xml"
+
+/* Favorites URLs */
+#define TWITTER_FAVORITES_GET_URL TWITTER_API_URL "/favorites.xml"
+#define TWITTER_FAVORITE_CREATE_URL TWITTER_API_URL "/favorites/create/"
+#define TWITTER_FAVORITE_DESTROY_URL TWITTER_API_URL "/favorites/destroy/"
+
+/* Block URLs */
+#define TWITTER_BLOCKS_CREATE_URL TWITTER_API_URL "/blocks/create/"
+#define TWITTER_BLOCKS_DESTROY_URL TWITTER_API_URL "/blocks/destroy/"
+
+void twitter_get_friends_ids(struct im_connection *ic, int next_cursor);
+void twitter_get_home_timeline(struct im_connection *ic, int next_cursor);
+void twitter_get_statuses_friends(struct im_connection *ic, int next_cursor);
+
+void twitter_post_status(struct im_connection *ic, char *msg);
+void twitter_direct_messages_new(struct im_connection *ic, char *who, char *message);
+
+#endif //_TWITTER_LIB_H
+
diff --git a/protocols/yahoo/Makefile b/protocols/yahoo/Makefile
index b4fe56e2..20ecce71 100644
--- a/protocols/yahoo/Makefile
+++ b/protocols/yahoo/Makefile
@@ -7,6 +7,9 @@
### DEFINITIONS
-include ../../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)protocols/yahoo/
+endif
# [SH] Program variables
objects = yahoo.o crypt.o libyahoo2.o yahoo_fn.o yahoo_httplib.o yahoo_util.o
@@ -32,7 +35,7 @@ distclean: clean
$(objects): ../../Makefile.settings Makefile
-$(objects): %.o: %.c
+$(objects): %.o: $(SRCDIR)%.c
@echo '*' Compiling $<
@$(CC) -c $(CFLAGS) $< -o $@
diff --git a/protocols/yahoo/yahoo.c b/protocols/yahoo/yahoo.c
index b61f6ff9..c3ec7bff 100644
--- a/protocols/yahoo/yahoo.c
+++ b/protocols/yahoo/yahoo.c
@@ -137,10 +137,15 @@ static void byahoo_login( account_t *acc )
{
struct im_connection *ic = imcb_new( acc );
struct byahoo_data *yd = ic->proto_data = g_new0( struct byahoo_data, 1 );
+ char *s;
yd->logged_in = FALSE;
yd->current_status = YAHOO_STATUS_AVAILABLE;
+ if( ( s = strchr( acc->user, '@' ) ) && g_strcasecmp( s, "@yahoo.com" ) == 0 )
+ imcb_error( ic, "Your Yahoo! username should just be a username. "
+ "Do not include any @domain part." );
+
imcb_log( ic, "Connecting" );
yd->y2_id = yahoo_init( acc->user, acc->pass );
yahoo_login( yd->y2_id, yd->current_status );
@@ -680,7 +685,7 @@ int ext_yahoo_add_handler( int id, int fd, yahoo_input_condition cond, void *dat
d->data = data;
inp->d = d;
- d->tag = inp->h = b_input_add( fd, GAIM_INPUT_READ, (b_event_handler) byahoo_read_ready_callback, (gpointer) d );
+ d->tag = inp->h = b_input_add( fd, B_EV_IO_READ, (b_event_handler) byahoo_read_ready_callback, (gpointer) d );
}
else if( cond == YAHOO_INPUT_WRITE )
{
@@ -691,7 +696,7 @@ int ext_yahoo_add_handler( int id, int fd, yahoo_input_condition cond, void *dat
d->data = data;
inp->d = d;
- d->tag = inp->h = b_input_add( fd, GAIM_INPUT_WRITE, (b_event_handler) byahoo_write_ready_callback, (gpointer) d );
+ d->tag = inp->h = b_input_add( fd, B_EV_IO_WRITE, (b_event_handler) byahoo_write_ready_callback, (gpointer) d );
}
else
{
@@ -827,6 +832,10 @@ void ext_yahoo_got_conf_invite( int id, const char *ignored,
char txt[1024];
YList *m;
+ if( g_strcasecmp( who, ic->acc->user ) == 0 )
+ /* WTF, Yahoo! seems to echo these now? */
+ return;
+
inv = g_malloc( sizeof( struct byahoo_conf_invitation ) );
memset( inv, 0, sizeof( struct byahoo_conf_invitation ) );
inv->name = g_strdup( room );
diff --git a/set.c b/set.c
index 18d5a50d..baf85261 100644
--- a/set.c
+++ b/set.c
@@ -28,7 +28,7 @@
/* Used to use NULL for this, but NULL is actually a "valid" value. */
char *SET_INVALID = "nee";
-set_t *set_add( set_t **head, char *key, char *def, set_eval eval, void *data )
+set_t *set_add( set_t **head, const char *key, const char *def, set_eval eval, void *data )
{
set_t *s = set_find( head, key );
@@ -62,7 +62,7 @@ set_t *set_add( set_t **head, char *key, char *def, set_eval eval, void *data )
return s;
}
-set_t *set_find( set_t **head, char *key )
+set_t *set_find( set_t **head, const char *key )
{
set_t *s = *head;
@@ -76,7 +76,7 @@ set_t *set_find( set_t **head, char *key )
return s;
}
-char *set_getstr( set_t **head, char *key )
+char *set_getstr( set_t **head, const char *key )
{
set_t *s = set_find( head, key );
@@ -86,7 +86,7 @@ char *set_getstr( set_t **head, char *key )
return s->value ? s->value : s->def;
}
-int set_getint( set_t **head, char *key )
+int set_getint( set_t **head, const char *key )
{
char *s = set_getstr( head, key );
int i = 0;
@@ -100,7 +100,7 @@ int set_getint( set_t **head, char *key )
return i;
}
-int set_getbool( set_t **head, char *key )
+int set_getbool( set_t **head, const char *key )
{
char *s = set_getstr( head, key );
@@ -110,7 +110,7 @@ int set_getbool( set_t **head, char *key )
return bool2int( s );
}
-int set_setstr( set_t **head, char *key, char *value )
+int set_setstr( set_t **head, const char *key, char *value )
{
set_t *s = set_find( head, key );
char *nv = value;
@@ -149,7 +149,7 @@ int set_setstr( set_t **head, char *key, char *value )
return 1;
}
-int set_setint( set_t **head, char *key, int value )
+int set_setint( set_t **head, const char *key, int value )
{
char s[24]; /* Not quite 128-bit clean eh? ;-) */
@@ -157,7 +157,7 @@ int set_setint( set_t **head, char *key, int value )
return set_setstr( head, key, s );
}
-void set_del( set_t **head, char *key )
+void set_del( set_t **head, const char *key )
{
set_t *s = *head, *t = NULL;
@@ -181,7 +181,7 @@ void set_del( set_t **head, char *key )
}
}
-int set_reset( set_t **head, char *key )
+int set_reset( set_t **head, const char *key )
{
set_t *s;
@@ -212,6 +212,21 @@ char *set_eval_bool( set_t *set, char *value )
return is_bool( value ) ? value : SET_INVALID;
}
+char *set_eval_list( set_t *set, char *value )
+{
+ GSList *options = set->eval_data, *opt;
+
+ for( opt = options; opt; opt = opt->next )
+ if( strcmp( value, opt->data ) == 0 )
+ return value;
+
+ /* TODO: It'd be nice to show the user a list of allowed values,
+ but we don't have enough context here to do that. May
+ want to fix that. */
+
+ return NULL;
+}
+
char *set_eval_to_char( set_t *set, char *value )
{
char *s = g_new( char, 3 );
diff --git a/set.h b/set.h
index 19ea73fb..7ff1f985 100644
--- a/set.h
+++ b/set.h
@@ -68,35 +68,39 @@ typedef struct set
the passed value variable. When returning a corrected value,
set_setstr() should be able to free() the returned string! */
set_eval eval;
+ void *eval_data;
struct set *next;
} set_t;
/* Should be pretty clear. */
-set_t *set_add( set_t **head, char *key, char *def, set_eval eval, void *data );
+set_t *set_add( set_t **head, const char *key, const char *def, set_eval eval, void *data );
/* Returns the raw set_t. Might be useful sometimes. */
-set_t *set_find( set_t **head, char *key );
+set_t *set_find( set_t **head, const char *key );
/* Returns a pointer to the string value of this setting. Don't modify the
returned string, and don't free() it! */
-G_MODULE_EXPORT char *set_getstr( set_t **head, char *key );
+G_MODULE_EXPORT char *set_getstr( set_t **head, const char *key );
/* Get an integer. In previous versions set_getint() was also used to read
boolean values, but this SHOULD be done with set_getbool() now! */
-G_MODULE_EXPORT int set_getint( set_t **head, char *key );
-G_MODULE_EXPORT int set_getbool( set_t **head, char *key );
+G_MODULE_EXPORT int set_getint( set_t **head, const char *key );
+G_MODULE_EXPORT int set_getbool( set_t **head, const char *key );
/* set_setstr() strdup()s the given value, so after using this function
you can free() it, if you want. */
-int set_setstr( set_t **head, char *key, char *value );
-int set_setint( set_t **head, char *key, int value );
-void set_del( set_t **head, char *key );
-int set_reset( set_t **head, char *key );
+int set_setstr( set_t **head, const char *key, char *value );
+int set_setint( set_t **head, const char *key, int value );
+void set_del( set_t **head, const char *key );
+int set_reset( set_t **head, const char *key );
/* Two very useful generic evaluators. */
char *set_eval_int( set_t *set, char *value );
char *set_eval_bool( set_t *set, char *value );
+/* Another more complicated one. */
+char *set_eval_list( set_t *set, char *value );
+
/* Some not very generic evaluators that really shouldn't be here... */
char *set_eval_to_char( set_t *set, char *value );
char *set_eval_ops( set_t *set, char *value );
diff --git a/storage_xml.c b/storage_xml.c
index b6745c75..8c524ca9 100644
--- a/storage_xml.c
+++ b/storage_xml.c
@@ -495,6 +495,7 @@ static storage_status_t xml_save( irc_t *irc, int overwrite )
if( !xml_printf( fd, 0, "</user>\n" ) )
goto write_error;
+ fsync( fd );
close( fd );
path2 = g_strndup( path, strlen( path ) - 1 );
diff --git a/tests/Makefile b/tests/Makefile
index 1bcf8f72..7c876cec 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -1,4 +1,7 @@
-include ../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)tests/
+endif
LFLAGS +=-lcheck
@@ -18,6 +21,6 @@ check: $(test_objs) $(addprefix ../, $(main_objs)) ../protocols/protocols.o ../l
@echo '*' Linking $@
@$(CC) $(CFLAGS) -o $@ $^ $(LFLAGS) $(EFLAGS)
-%.o: %.c
+%.o: $(SRCDIR)%.c
@echo '*' Compiling $<
@$(CC) -c $(CFLAGS) $< -o $@
diff --git a/unix.c b/unix.c
index 7088d0f8..f559705e 100644
--- a/unix.c
+++ b/unix.c
@@ -55,16 +55,26 @@ int main( int argc, char *argv[] )
return crypt_main( argc, argv );
log_init();
+
global.conf_file = g_strdup( CONF_FILE_DEF );
global.conf = conf_load( argc, argv );
if( global.conf == NULL )
return( 1 );
b_main_init();
- nogaim_init();
srand( time( NULL ) ^ getpid() );
+
global.helpfile = g_strdup( HELP_FILE );
+ if( help_init( &global.help, global.helpfile ) == NULL )
+ log_message( LOGLVL_WARNING, "Error opening helpfile %s.", HELP_FILE );
+
+ global.storage = storage_init( global.conf->primary_storage, global.conf->migrate_storage );
+ if( global.storage == NULL )
+ {
+ log_message( LOGLVL_ERROR, "Unable to load storage backend '%s'", global.conf->primary_storage );
+ return( 1 );
+ }
if( global.conf->runmode == RUNMODE_INETD )
{
@@ -72,19 +82,22 @@ int main( int argc, char *argv[] )
log_link( LOGLVL_WARNING, LOGOUTPUT_IRC );
i = bitlbee_inetd_init();
- log_message( LOGLVL_INFO, "Bitlbee %s starting in inetd mode.", BITLBEE_VERSION );
+ log_message( LOGLVL_INFO, "BitlBee %s starting in inetd mode.", BITLBEE_VERSION );
}
else if( global.conf->runmode == RUNMODE_DAEMON )
{
- log_link( LOGLVL_ERROR, LOGOUTPUT_SYSLOG );
- log_link( LOGLVL_WARNING, LOGOUTPUT_SYSLOG );
+ log_link( LOGLVL_ERROR, LOGOUTPUT_CONSOLE );
+ log_link( LOGLVL_WARNING, LOGOUTPUT_CONSOLE );
i = bitlbee_daemon_init();
- log_message( LOGLVL_INFO, "Bitlbee %s starting in daemon mode.", BITLBEE_VERSION );
+ log_message( LOGLVL_INFO, "BitlBee %s starting in daemon mode.", BITLBEE_VERSION );
}
else if( global.conf->runmode == RUNMODE_FORKDAEMON )
{
+ log_link( LOGLVL_ERROR, LOGOUTPUT_CONSOLE );
+ log_link( LOGLVL_WARNING, LOGOUTPUT_CONSOLE );
+
/* In case the operator requests a restart, we need this. */
old_cwd = g_malloc( 256 );
if( getcwd( old_cwd, 255 ) == NULL )
@@ -95,7 +108,7 @@ int main( int argc, char *argv[] )
}
i = bitlbee_daemon_init();
- log_message( LOGLVL_INFO, "Bitlbee %s starting in forking daemon mode.", BITLBEE_VERSION );
+ log_message( LOGLVL_INFO, "BitlBee %s starting in forking daemon mode.", BITLBEE_VERSION );
}
if( i != 0 )
return( i );
@@ -113,13 +126,6 @@ int main( int argc, char *argv[] )
setuid( pw->pw_uid );
}
}
-
- global.storage = storage_init( global.conf->primary_storage, global.conf->migrate_storage );
- if( global.storage == NULL )
- {
- log_message( LOGLVL_ERROR, "Unable to load storage backend '%s'", global.conf->primary_storage );
- return( 1 );
- }
/* Catch some signals to tell the user what's happening before quitting */
memset( &sig, 0, sizeof( sig ) );
@@ -138,8 +144,6 @@ int main( int argc, char *argv[] )
if( !getuid() || !geteuid() )
log_message( LOGLVL_WARNING, "BitlBee is running with root privileges. Why?" );
- if( help_init( &global.help, global.helpfile ) == NULL )
- log_message( LOGLVL_WARNING, "Error opening helpfile %s.", HELP_FILE );
b_main_run();