aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile5
-rw-r--r--bitlbee.c79
-rw-r--r--bitlbee.h5
-rw-r--r--conf.c11
-rwxr-xr-xconfigure48
-rwxr-xr-xdebian/bitlbee.init14
-rw-r--r--debian/changelog50
-rwxr-xr-xdebian/config20
-rw-r--r--debian/control2
-rw-r--r--debian/patches/bitlbee.conf.diff19
-rw-r--r--debian/po/POTFILES.in2
-rw-r--r--debian/po/ru.po47
-rwxr-xr-xdebian/postinst9
-rwxr-xr-xdebian/prerm2
-rw-r--r--doc/CHANGES23
-rw-r--r--doc/user-guide/commands.xml113
-rw-r--r--irc.c25
-rw-r--r--irc_commands.c2
-rw-r--r--lib/misc.c35
-rw-r--r--lib/misc.h1
-rw-r--r--log.c2
-rw-r--r--protocols/jabber/jabber.c14
-rw-r--r--protocols/jabber/jabber.h11
-rw-r--r--protocols/jabber/jabber_util.c104
-rw-r--r--protocols/jabber/message.c6
-rw-r--r--protocols/jabber/presence.c3
-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.c77
-rw-r--r--protocols/msn/sb.c56
-rw-r--r--protocols/nogaim.c149
-rw-r--r--protocols/nogaim.h2
-rw-r--r--protocols/oscar/oscar.c17
-rw-r--r--protocols/twitter/Makefile43
-rw-r--r--protocols/twitter/twitter.c248
-rw-r--r--protocols/twitter/twitter.h52
-rw-r--r--protocols/twitter/twitter_http.c161
-rw-r--r--protocols/twitter/twitter_http.h34
-rw-r--r--protocols/twitter/twitter_lib.c677
-rw-r--r--protocols/twitter/twitter_lib.h86
-rw-r--r--root_commands.c31
-rw-r--r--storage_xml.c1
-rw-r--r--tests/check_jabber_util.c30
-rw-r--r--unix.c13
45 files changed, 2170 insertions, 216 deletions
diff --git a/Makefile b/Makefile
index 5f48facd..7c032906 100644
--- a/Makefile
+++ b/Makefile
@@ -127,3 +127,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..89e33223 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 );
@@ -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 );
}
diff --git a/bitlbee.h b/bitlbee.h
index e185e05e..88081e45 100644
--- a/bitlbee.h
+++ b/bitlbee.h
@@ -37,8 +37,10 @@
#define _WIN32_WINNT 0x0501
#define PACKAGE "BitlBee"
-#define BITLBEE_VERSION "1.2.5"
+#define BITLBEE_VERSION "1.2.6"
#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
@@ -167,6 +169,7 @@ void root_command( irc_t *irc, char *command[] );
gboolean bitlbee_shutdown( gpointer data, gint fd, b_input_condition cond );
char *set_eval_root_nick( set_t *set, char *new_nick );
+char *set_eval_control_channel( set_t *set, char *new_name );
extern global_t global;
diff --git a/conf.c b/conf.c
index 6b11f883..f12324cd 100644
--- a/conf.c
+++ b/conf.c
@@ -79,7 +79,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' )
@@ -145,7 +145,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 425a66e3..2841cfdb 100755
--- a/configure
+++ b/configure
@@ -19,12 +19,13 @@ libevent='/usr/'
pidfile='/var/run/bitlbee.pid'
ipcsocket='/var/run/bitlbee.sock'
pcdir='$prefix/lib/pkgconfig'
-systemlibdirs="/lib /usr/lib /usr/local/lib"
+systemlibdirs="/lib /lib64 /usr/lib /usr/lib64 /usr/local/lib /usr/local/lib64"
msn=1
jabber=1
oscar=1
yahoo=1
+twitter=1
debug=0
strip=1
@@ -66,6 +67,7 @@ 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
--debug=0/1 Disable/enable debugging $debug
--strip=0/1 Disable/enable binary stripping $strip
@@ -157,7 +159,7 @@ else
fi
echo CFLAGS=$CFLAGS >> Makefile.settings
-echo CFLAGS+=-I`pwd` -iquote`pwd`/lib -iquote`pwd`/protocols -I. >> Makefile.settings
+echo CFLAGS+=-I`pwd` -I`pwd`/lib -I`pwd`/protocols -I. >> Makefile.settings
echo CFLAGS+=-DHAVE_CONFIG_H >> Makefile.settings
@@ -268,7 +270,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
@@ -296,28 +298,34 @@ int main()
detect_resolv_dynamic()
{
- echo "$RESOLV_TESTCODE" | $CC -o /dev/null -x c - -lresolv >/dev/null 2>/dev/null
+ 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
echo 'EFLAGS+=-lresolv' >> Makefile.settings
- return 0
+ ret=0
fi
- return 1
+ rm -f $TMPFILE
+ return $ret
}
detect_resolv_static()
{
+ TMPFILE=$(mktemp /tmp/bitlbee-configure.XXXXXX)
+ ret=1
for i in $systemlibdirs; do
if [ -f $i/libresolv.a ]; then
- echo "$RESOLV_TESTCODE" | $CC -o /dev/null -x c - -Wl,$i/libresolv.a >/dev/null 2>/dev/null
+ echo "$RESOLV_TESTCODE" | $CC -o $TMPFILE -x c - -Wl,$i/libresolv.a >/dev/null 2>/dev/null
if [ "$?" = "0" ]; then
echo 'EFLAGS+='$i'/libresolv.a' >> Makefile.settings
- return 0
+ ret=0
fi
fi
done
- return 1
+ rm -f $TMPFILE
+ return $ret
}
if [ "$ssl" = "auto" ]; then
@@ -493,6 +501,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
@@ -541,6 +563,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/debian/bitlbee.init b/debian/bitlbee.init
index 1ab1bc43..be1dcd66 100755
--- a/debian/bitlbee.init
+++ b/debian/bitlbee.init
@@ -37,18 +37,10 @@ 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
- # Clean up after the bug between 1.2-5 and 1.2.1-2 where BitlBee ran
- # as root. (#494656 and #495877) Fixing this in the postinst script
- # is not enough since the user will restart his BitlBee after up-
- # grading the package, and the BitlBee running as root will then
- # save its settings, re-setting ownership of the file to root.
- # TODO: Remove this after a few revisions.
- find /var/lib/bitlbee -uid 0 -name '*.xml' -exec chown bitlbee: {} \;
-
- start-stop-daemon --start --quiet \
+ start-stop-daemon --start --quiet --pidfile $PIDFILE \
--exec $DAEMON -- -p $BITLBEE_PORT -P $PIDFILE $BITLBEE_OPTS
}
diff --git a/debian/changelog b/debian/changelog
index a11a67b8..f969b410 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,53 @@
+bitlbee (1.2.5-1) unstable; urgency=low
+
+ * New upstream version.
+ * Fixed issues with server-side MSN nickname corruption. (Closes: #538756)
+ * Debconf translation fixes/additions. (Closes: #541754, #563504)
+
+ -- Wilmer van der Gaast <wilmer@gaast.net> Wed, 17 Mar 2010 14:59:27 +0000
+
+bitlbee (1.2.4-2) unstable; urgency=low
+
+ * Merging in some changes from bzr-head:
+ * Use libresolv.so where possible. (Closes: #551775)
+ * Some include file changes that make the bitlbee-dev package useful again.
+
+ -- Wilmer van der Gaast <wilmer@gaast.net> Thu, 19 Nov 2009 23:02:43 +0000
+
+bitlbee (1.2.4-1) unstable; urgency=low
+
+ * New upstream version.
+ * Fixed issues with Yahoo! (Closes: #536178)
+
+ -- Wilmer van der Gaast <wilmer@gaast.net> Sat, 17 Oct 2009 18:12:45 +0100
+
+bitlbee (1.2.3-2) unstable; urgency=low
+
+ * Fixed bitblee typo in prerm (introduced by NMU 1.2.1-1.1).
+ (Closes: #531287)
+ * Fixed bitlbee.deb dep in bitlbee-dev to deal with binary NMUs.
+ (Closes: #531219)
+ * Fixed free port detection code in debian/config which was a bit limited
+ and also buggy.
+ * Removing code that edits bitlbee.conf from postinst (and chown code in
+ the init script), it's not really necessary anymore; bitlbee may only
+ still run as root if the admin doesn't read conffile diffs.
+ (Closes: #514572)
+ * No longer overwriting port number info in /etc/default/bitlbee with
+ what's in debconf. (Closes: #514148)
+ * Added notes about the above two changes to bitlbee.conf.
+
+ -- Wilmer van der Gaast <wilmer@gaast.net> Sun, 07 Jun 2009 21:17:39 +0100
+
+bitlbee (1.2.3-1) unstable; urgency=critical
+
+ * New upstream version.
+ * Fixes another account hijacking issue. (Closes: #498159)
+ * Restored --pidfile argument to start-stop-daemon, otherwise the init
+ script fails to restart BitlBee when users are connected.
+
+ -- Wilmer van der Gaast <wilmer@gaast.net> Sun, 07 Sep 2008 18:53:04 +0100
+
bitlbee (1.2.2-1) unstable; urgency=critical
* New upstream version.
diff --git a/debian/config b/debian/config
index 3a04813d..9bb78237 100755
--- a/debian/config
+++ b/debian/config
@@ -1,17 +1,23 @@
#!/bin/sh -e
. /usr/share/debconf/confmodule
+[ -f /etc/default/bitlbee ] && . /etc/default/bitlbee
db_title BitlBee
-db_get bitlbee/serveport
-if [ "$RET" = "stillhavetoask" ]; then
- if netstat -ltn | grep ':6667' 2> /dev/null > /dev/null; then
- port=6668;
- else
- port=6667;
+if [ -n "$BITLBEE_PORT" ]; then
+ db_set bitlbee/serveport "$BITLBEE_PORT"
+else
+ db_get bitlbee/serveport
+ if [ "$RET" = "stillhavetoask" ]; then
+ listens=$(netstat -ltn | awk '{print $4}')
+ for port in 6667 6666 6668 6669; do
+ if [ $(expr "$listens " : ".*:$port\s") = "0" ]; then
+ break
+ fi
+ done
+ db_set bitlbee/serveport $port;
fi
- db_set bitlbee/serveport $port;
fi
if db_input medium bitlbee/serveport; then
diff --git a/debian/control b/debian/control
index e6302c13..86488c8a 100644
--- a/debian/control
+++ b/debian/control
@@ -18,7 +18,7 @@ Description: An IRC to other chat networks gateway
Package: bitlbee-dev
Architecture: all
-Depends: bitlbee (= ${binary:Version})
+Depends: bitlbee (>= ${source:Version}), bitlbee (<< ${source:Version}.1~)
Description: An IRC to other chat networks gateway
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 b80bcb4c..c98fa546 100644
--- a/debian/patches/bitlbee.conf.diff
+++ b/debian/patches/bitlbee.conf.diff
@@ -1,13 +1,22 @@
-=== modified file 'bitlbee.conf'
---- debian/bitlbee/etc/bitlbee/bitlbee.conf 2008-08-26 22:33:54 +0000
-+++ debian/bitlbee/etc/bitlbee/bitlbee.conf 2008-08-27 23:18:13 +0000
-@@ -23,7 +23,7 @@
+--- 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
+@@ -23,13 +23,18 @@
## If BitlBee is started by root as a daemon, it can drop root privileges,
## and change to the specified user.
##
-# User = bitlbee
++## DEBIAN NOTE: Without this, BitlBee will run as root!
++##
+User = bitlbee
## DaemonPort/DaemonInterface:
##
-
+ ## For daemon mode, you can specify on what interface and port the daemon
+ ## should be listening for connections.
+ ##
++## DEBIAN NOTE: The init script passes the -p flag to use the port number
++## set using debconf, this overrides the DaemonPort setting here.
++##
+ # DaemonInterface = 0.0.0.0
+ # DaemonPort = 6667
+
diff --git a/debian/po/POTFILES.in b/debian/po/POTFILES.in
index f17ddcfe..cef83a34 100644
--- a/debian/po/POTFILES.in
+++ b/debian/po/POTFILES.in
@@ -1 +1 @@
-[type: gettext/rfc822deb] bitlbee.templates.master
+[type: gettext/rfc822deb] templates
diff --git a/debian/po/ru.po b/debian/po/ru.po
new file mode 100644
index 00000000..4e448133
--- /dev/null
+++ b/debian/po/ru.po
@@ -0,0 +1,47 @@
+# translation of ru.po to Russian
+#
+# Translators, if you are not familiar with the PO format, gettext
+# documentation is worth reading, especially sections dedicated to
+# this format, e.g. by running:
+# info -n '(gettext)PO Files'
+# info -n '(gettext)Header Entry'
+# Some information specific to po-debconf are available at
+# /usr/share/doc/po-debconf/README-trans
+# or http://www.debian.org/intl/l10n/po-debconf/README-trans#
+# Developers do not need to manually edit POT or PO files.
+#
+# Yuri Kozlov <yuray@komyakino.ru>, 2009.
+msgid ""
+msgstr ""
+"Project-Id-Version: bitlbee 1.2.3-2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2004-09-25 18:12+0200\n"
+"PO-Revision-Date: 2009-08-05 20:43+0400\n"
+"Last-Translator: Yuri Kozlov <yuray@komyakino.ru>\n"
+"Language-Team: Russian <debian-l10n-russian@lists.debian.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: KBabel 1.11.4\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+#. Type: string
+#. Description
+#: ../bitlbee.templates.master:4
+msgid "On what TCP port should BitlBee listen for connections?"
+msgstr "Номер порта TCP, на котором BitlBee должен ожидать подключений:"
+
+#. Type: string
+#. Description
+#: ../bitlbee.templates.master:4
+msgid ""
+"BitlBee normally listens on the regular IRC port, 6667. This might not be a "
+"very good idea when you're running a real IRC daemon as well. 6668 might be "
+"a good alternative. Leaving this value blank means that BitlBee will not be "
+"run automatically."
+msgstr ""
+"Обычно, BitlBee прослушивает штатный порт IRC, 6667. Это может быть "
+"не лучшим решением, если у вас также запущена служба IRC. Хорошей "
+"альтернативой является номер 6668. Если оставить поле пустым, то "
+"BitlBee не будет запускаться автоматически."
+
diff --git a/debian/postinst b/debian/postinst
index db324b65..db541f6c 100755
--- a/debian/postinst
+++ b/debian/postinst
@@ -32,7 +32,7 @@ fi
cat<<EOF>/etc/default/bitlbee
## /etc/default/bitlbee: Auto-generated/updated script.
##
-## Don't edit this line, use dpkg-reconfigure bitlbee
+## If running in (fork)daemon mode, listen on this TCP port.
BITLBEE_PORT="$PORT"
## Use single-process or forking daemon mode? Can't be changed from debconf,
@@ -63,13 +63,6 @@ if [ -e /usr/share/bitlbee/help.upgrading ]; then
fi
fi
-if ! grep -qi '^User *= *' /etc/bitlbee/bitlbee.conf; then
- echo 'Updating configuration file, enabling User-setting...'
- if ! sed -i -e 's/# *User *= *.*/User = bitlbee/i' /etc/bitlbee/bitlbee.conf; then
- echo 'Failed! BitlBee may run as root now, please check your configs.'
- fi
-fi
-
if [ -n "$2" -a "$BITLBEE_UPGRADE_DONT_RESTART" != "1" ]; then
if which invoke-rc.d >/dev/null 2>&1; then
invoke-rc.d bitlbee restart
diff --git a/debian/prerm b/debian/prerm
index 8426ab3a..687c2cc1 100755
--- a/debian/prerm
+++ b/debian/prerm
@@ -10,7 +10,7 @@ if [ "$1" = "upgrade" ]; then
fi
else
if which invoke-rc.d >/dev/null 2>&1; then
- invoke-rc.d bitblee stop || exit 0
+ invoke-rc.d bitlbee stop || exit 0
else
/etc/init.d/bitlbee stop || exit 0
fi
diff --git a/doc/CHANGES b/doc/CHANGES
index 1cac2dc7..94330bf9 100644
--- a/doc/CHANGES
+++ b/doc/CHANGES
@@ -3,6 +3,27 @@ found in the bzr commit logs, for example you can try:
http://bugs.bitlbee.org/bitlbee/timeline?daysback=90&changeset=on
+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 +43,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/user-guide/commands.xml b/doc/user-guide/commands.xml
index 1a07c835..c9e3168d 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,24 @@
<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>
+ </description>
+ </bitlbee-command>
<bitlbee-command name="yahoo">
<syntax>account add yahoo &lt;handle&gt; &lt;password&gt;</syntax>
@@ -549,7 +567,7 @@
</bitlbee-setting>
<bitlbee-setting name="auto_reconnect" type="boolean" scope="both">
- <default>false</default>
+ <default>true</default>
<description>
<para>
@@ -667,7 +685,16 @@
If set to true, BitlBee will color incoming encrypted messages according to their fingerprint trust level: untrusted=red, trusted=green.
</para>
</description>
+ </bitlbee-setting>
+ <bitlbee-setting name="control_channel" type="string" scope="global">
+ <default>&amp;bitlbee</default>
+
+ <description>
+ <para>
+ Normally the control channel where you can see all your contacts is called "&amp;bitlbee". If you don't like this name, you can rename it to anything else using the <emphasis>rename</emphasis> command, or by changing this setting.
+ </para>
+ </description>
</bitlbee-setting>
<bitlbee-setting name="debug" type="boolean" scope="global">
@@ -709,6 +736,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>
@@ -749,7 +786,16 @@
If "false", the flag is never set. On "notaway", the flag is removed for users marked as "away" and set for all others. On "encrypted", the flag is set for users with whom we have an encrypted connection. On "trusted", it is set only for encrypted connections using a trusted key.
</para>
</description>
+ </bitlbee-setting>
+
+ <bitlbee-setting name="ignore_auth_requests" type="boolean" scope="account">
+ <default>true</default>
+ <description>
+ <para>
+ Only supported by OSCAR so far, you can use this setting to ignore ICQ authorization requests, which are hardly used for legitimate (i.e. non-spam) reasons anymore.
+ </para>
+ </description>
</bitlbee-setting>
<bitlbee-setting name="lcnicks" type="boolean" scope="global">
@@ -763,6 +809,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>
@@ -824,6 +881,25 @@
</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>
@@ -1032,6 +1108,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/irc.c b/irc.c
index 35a26650..d8656026 100644
--- a/irc.c
+++ b/irc.c
@@ -54,18 +54,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;
}
@@ -174,9 +190,11 @@ irc_t *irc_new( int fd )
s = set_add( &irc->set, "buddy_sendbuffer_delay", "200", set_eval_int, irc );
s = set_add( &irc->set, "charset", "utf-8", set_eval_charset, irc );
s = set_add( &irc->set, "color_encrypted", "true", set_eval_bool, irc );
+ s = set_add( &irc->set, "control_channel", irc->channel, set_eval_control_channel, irc );
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, "halfop_buddies", "encrypted", set_eval_halfop_buddies, irc );
s = set_add( &irc->set, "lcnicks", "true", set_eval_bool, irc );
@@ -191,6 +209,7 @@ irc_t *irc_new( int fd )
s = set_add( &irc->set, "save_on_quit", "true", set_eval_bool, irc );
s = set_add( &irc->set, "simulate_netsplit", "true", set_eval_bool, irc );
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 );
s = set_add( &irc->set, "voice_buddies", "notaway", set_eval_voice_buddies, irc);
diff --git a/irc_commands.c b/irc_commands.c
index 012cf171..28142a12 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/misc.c b/lib/misc.c
index 07928608..af453020 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];
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/log.c b/log.c
index bea046d3..f6f9ab2b 100644
--- a/log.c
+++ b/log.c
@@ -171,5 +171,7 @@ static void log_console(int level, const 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/jabber/jabber.c b/protocols/jabber/jabber.c
index 011f81c2..2841c0db 100644
--- a/protocols/jabber/jabber.c
+++ b/protocols/jabber/jabber.c
@@ -57,6 +57,8 @@ static void jabber_init( account_t *acc )
set_t *s;
char str[16];
+ s = set_add( &acc->set, "activity_timeout", "600", set_eval_int, acc );
+
g_snprintf( str, sizeof( str ), "%d", jabber_port_list[0] );
s = set_add( &acc->set, "port", str, set_eval_int, acc );
s->flags |= ACC_SET_OFFLINE_ONLY;
@@ -306,7 +308,7 @@ static int jabber_buddy_msg( struct im_connection *ic, char *who, char *message,
if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) )
bud = jabber_buddy_by_ext_jid( ic, who, 0 );
else
- bud = jabber_buddy_by_jid( ic, who, 0 );
+ bud = jabber_buddy_by_jid( ic, who, GET_BUDDY_BARE_OK );
node = xt_new_node( "body", message, NULL );
node = jabber_make_packet( "message", "chat", bud ? bud->full_jid : who, node );
@@ -351,17 +353,9 @@ static GList *jabber_away_states( struct im_connection *ic )
static void jabber_get_info( struct im_connection *ic, char *who )
{
- struct jabber_data *jd = ic->proto_data;
struct jabber_buddy *bud;
- if( strchr( who, '/' ) )
- bud = jabber_buddy_by_jid( ic, who, 0 );
- else
- {
- char *s = jabber_normalize( who );
- bud = g_hash_table_lookup( jd->buddies, s );
- g_free( s );
- }
+ bud = jabber_buddy_by_jid( ic, who, GET_BUDDY_FIRST );
while( bud )
{
diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h
index 8e3bf036..40cf3957 100644
--- a/protocols/jabber/jabber.h
+++ b/protocols/jabber/jabber.h
@@ -107,6 +107,13 @@ struct jabber_cache_entry
jabber_cache_event func;
};
+/* Somewhat messy data structure: We have a hash table with the bare JID as
+ the key and the head of a struct jabber_buddy list as the value. The head
+ is always a bare JID. If the JID has other resources (often the case,
+ except for some transports that don't support multiple resources), those
+ follow. In that case, the bare JID at the beginning doesn't actually
+ refer to a real session and should only be used for operations that
+ support incomplete JIDs. */
struct jabber_buddy
{
char *bare_jid;
@@ -120,7 +127,7 @@ struct jabber_buddy
struct jabber_away_state *away_state;
char *away_message;
- time_t last_act;
+ time_t last_msg;
jabber_buddy_flags_t flags;
struct jabber_buddy *next;
@@ -208,6 +215,8 @@ typedef enum
GET_BUDDY_CREAT = 1, /* Try to create it, if necessary. */
GET_BUDDY_EXACT = 2, /* Get an exact match (only makes sense with bare JIDs). */
GET_BUDDY_FIRST = 4, /* No selection, simply get the first resource for this JID. */
+ GET_BUDDY_BARE = 8, /* Get the bare version of the JID (possibly inexistent). */
+ GET_BUDDY_BARE_OK = 16, /* Allow returning a bare JID if that seems better. */
} get_buddy_flags_t;
struct jabber_error
diff --git a/protocols/jabber/jabber_util.c b/protocols/jabber/jabber_util.c
index 185d3878..b8b625f7 100644
--- a/protocols/jabber/jabber_util.c
+++ b/protocols/jabber/jabber_util.c
@@ -3,7 +3,7 @@
* BitlBee - An IRC to IM gateway *
* Jabber module - Misc. stuff *
* *
-* Copyright 2006 Wilmer van der Gaast <wilmer@gaast.net> *
+* Copyright 2006-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 *
@@ -344,6 +344,11 @@ struct jabber_buddy *jabber_buddy_add( struct im_connection *ic, char *full_jid_
if( ( bud = g_hash_table_lookup( jd->buddies, full_jid ) ) )
{
+ /* The first entry is always a bare JID. If there are more, we
+ should ignore the first one here. */
+ if( bud->next )
+ bud = bud->next;
+
/* If this is a transport buddy or whatever, it can't have more
than one instance, so this is always wrong: */
if( s == NULL || bud->resource == NULL )
@@ -378,10 +383,15 @@ struct jabber_buddy *jabber_buddy_add( struct im_connection *ic, char *full_jid_
}
else
{
- /* Keep in mind that full_jid currently isn't really
- a full JID... */
- new->bare_jid = g_strdup( full_jid );
+ new->full_jid = new->bare_jid = g_strdup( full_jid );
g_hash_table_insert( jd->buddies, new->bare_jid, new );
+
+ if( s )
+ {
+ new->next = g_new0( struct jabber_buddy, 1 );
+ new->next->bare_jid = new->bare_jid;
+ new = new->next;
+ }
}
if( s )
@@ -407,7 +417,7 @@ struct jabber_buddy *jabber_buddy_add( struct im_connection *ic, char *full_jid_
struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_, get_buddy_flags_t flags )
{
struct jabber_data *jd = ic->proto_data;
- struct jabber_buddy *bud;
+ struct jabber_buddy *bud, *head;
char *s, *jid;
jid = jabber_normalize( jid_ );
@@ -419,6 +429,11 @@ struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_,
*s = 0;
if( ( bud = g_hash_table_lookup( jd->buddies, jid ) ) )
{
+ bare_exists = 1;
+
+ if( bud->next )
+ bud = bud->next;
+
/* Just return the first one for this bare JID. */
if( flags & GET_BUDDY_FIRST )
{
@@ -440,16 +455,9 @@ struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_,
if( strcmp( bud->resource, s + 1 ) == 0 )
break;
}
- else
- {
- /* This variable tells the if down here that the bare
- JID already exists and we should feel free to add
- more resources, if the caller asked for that. */
- bare_exists = 1;
- }
if( bud == NULL && ( flags & GET_BUDDY_CREAT ) &&
- ( !bare_exists || imcb_find_buddy( ic, jid ) ) )
+ ( bare_exists || imcb_find_buddy( ic, jid ) ) )
{
*s = '/';
bud = jabber_buddy_add( ic, jid );
@@ -463,7 +471,8 @@ struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_,
struct jabber_buddy *best_prio, *best_time;
char *set;
- bud = g_hash_table_lookup( jd->buddies, jid );
+ head = g_hash_table_lookup( jd->buddies, jid );
+ bud = ( head && head->next ) ? head->next : head;
g_free( jid );
@@ -480,22 +489,31 @@ struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_,
else if( flags & GET_BUDDY_FIRST )
/* Looks like the caller doesn't care about details. */
return bud;
+ else if( flags & GET_BUDDY_BARE )
+ return head;
best_prio = best_time = bud;
for( ; bud; bud = bud->next )
{
if( bud->priority > best_prio->priority )
best_prio = bud;
- if( bud->last_act > best_time->last_act )
+ if( bud->last_msg > best_time->last_msg )
best_time = bud;
}
if( ( set = set_getstr( &ic->acc->set, "resource_select" ) ) == NULL )
return NULL;
- else if( strcmp( set, "activity" ) == 0 )
- return best_time;
- else /* if( strcmp( set, "priority" ) == 0 ) */
+ else if( strcmp( set, "priority" ) == 0 )
return best_prio;
+ else if( flags & GET_BUDDY_BARE_OK ) /* && strcmp( set, "activity" ) == 0 */
+ {
+ if( best_time->last_msg + set_getint( &ic->acc->set, "activity_timeout" ) >= time( NULL ) )
+ return best_time;
+ else
+ return head;
+ }
+ else
+ return best_time;
}
}
@@ -537,7 +555,7 @@ struct jabber_buddy *jabber_buddy_by_ext_jid( struct im_connection *ic, char *ji
int jabber_buddy_remove( struct im_connection *ic, char *full_jid_ )
{
struct jabber_data *jd = ic->proto_data;
- struct jabber_buddy *bud, *prev, *bi;
+ struct jabber_buddy *bud, *prev = NULL, *bi;
char *s, *full_jid;
full_jid = jabber_normalize( full_jid_ );
@@ -547,6 +565,9 @@ int jabber_buddy_remove( struct im_connection *ic, char *full_jid_ )
if( ( bud = g_hash_table_lookup( jd->buddies, full_jid ) ) )
{
+ if( bud->next )
+ bud = (prev=bud)->next;
+
/* If there's only one item in the list (and if the resource
matches), removing it is simple. (And the hash reference
should be removed too!) */
@@ -554,16 +575,7 @@ int jabber_buddy_remove( struct im_connection *ic, char *full_jid_ )
( ( s == NULL && bud->resource == NULL ) ||
( bud->resource && s && strcmp( bud->resource, s + 1 ) == 0 ) ) )
{
- g_hash_table_remove( jd->buddies, bud->bare_jid );
- g_free( bud->bare_jid );
- g_free( bud->ext_jid );
- g_free( bud->full_jid );
- g_free( bud->away_message );
- g_free( bud );
-
- g_free( full_jid );
-
- return 1;
+ return jabber_buddy_remove_bare( ic, full_jid );
}
else if( s == NULL || bud->resource == NULL )
{
@@ -574,7 +586,7 @@ int jabber_buddy_remove( struct im_connection *ic, char *full_jid_ )
}
else
{
- for( bi = bud, prev = NULL; bi; bi = (prev=bi)->next )
+ for( bi = bud; bi; bi = (prev=bi)->next )
if( strcmp( bi->resource, s + 1 ) == 0 )
break;
@@ -585,8 +597,7 @@ int jabber_buddy_remove( struct im_connection *ic, char *full_jid_ )
if( prev )
prev->next = bi->next;
else
- /* The hash table should point at the second
- item, because we're removing the first. */
+ /* Don't think this should ever happen anymore. */
g_hash_table_replace( jd->buddies, bi->bare_jid, bi->next );
g_free( bi->ext_jid );
@@ -655,10 +666,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 )
{
@@ -676,30 +686,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 6cb67d42..e8161029 100644
--- a/protocols/jabber/message.c
+++ b/protocols/jabber/message.c
@@ -70,7 +70,7 @@ xt_status jabber_pkt_message( struct xt_node *node, gpointer data )
{
if( bud )
{
- bud->last_act = time( NULL );
+ bud->last_msg = time( NULL );
from = bud->ext_jid ? : bud->bare_jid;
}
else
@@ -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/presence.c b/protocols/jabber/presence.c
index 28aaea1b..006eeead 100644
--- a/protocols/jabber/presence.c
+++ b/protocols/jabber/presence.c
@@ -67,9 +67,6 @@ xt_status jabber_pkt_presence( struct xt_node *node, gpointer data )
else
{
bud->away_state = NULL;
- /* Let's only set last_act if there's *no* away state,
- since it could be some auto-away thingy. */
- bud->last_act = time( NULL );
}
if( ( c = xt_find_node( node->children, "priority" ) ) && c->text_len > 0 )
diff --git a/protocols/msn/msn.c b/protocols/msn/msn.c
index 327fb028..8bf61aa1 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 )
@@ -166,7 +164,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)
@@ -282,18 +280,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 )
{
@@ -301,16 +295,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 84914bc3..83a080a3 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
@@ -82,6 +87,7 @@ struct msn_switchboard
int fd;
gint inp;
struct msn_handler_data *handler;
+ gint keepalive;
int trId;
int ready;
@@ -160,6 +166,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 );
@@ -178,5 +185,7 @@ 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 );
#endif //_MSN_H
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 046fe0af..ca3c38e2 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 )
{
@@ -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" );
@@ -421,8 +407,12 @@ static int msn_ns_command( gpointer data, char **cmd, int num_parts )
}
else if( strcmp( cmd[0], "FLN" ) == 0 )
{
- if( cmd[1] )
- imcb_buddy_status( ic, cmd[1], 0, NULL, NULL );
+ if( cmd[1] == NULL )
+ return 1;
+
+ imcb_buddy_status( ic, cmd[1], 0, NULL, NULL );
+
+ msn_sb_start_keepalives( msn_sb_by_handle( ic, cmd[1] ), TRUE );
}
else if( strcmp( cmd[0], "NLN" ) == 0 )
{
@@ -448,6 +438,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 )
{
@@ -552,6 +544,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 )
@@ -582,6 +577,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." );
@@ -731,3 +727,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 e9526234..e2ee8570 100644
--- a/protocols/msn/sb.c
+++ b/protocols/msn/sb.c
@@ -167,7 +167,18 @@ int msn_sb_sendmessage( struct msn_switchboard *sb, char *text )
int i, j;
/* Build the message. Convert LF to CR-LF for normal messages. */
- if( strcmp( text, TYPING_NOTIFICATION_MESSAGE ) != 0 )
+ if( strcmp( text, TYPING_NOTIFICATION_MESSAGE ) == 0 )
+ {
+ i = strlen( MSN_TYPING_HEADERS ) + strlen( sb->ic->acc->user );
+ buf = g_new0( char, i );
+ i = g_snprintf( buf, i, MSN_TYPING_HEADERS, sb->ic->acc->user );
+ }
+ else if( strcmp( text, SB_KEEPALIVE_MESSAGE ) == 0 )
+ {
+ buf = g_strdup( SB_KEEPALIVE_HEADERS );
+ i = strlen( buf );
+ }
+ else
{
buf = g_new0( char, sizeof( MSN_MESSAGE_HEADERS ) + strlen( text ) * 2 + 1 );
i = strlen( MSN_MESSAGE_HEADERS );
@@ -181,12 +192,6 @@ int msn_sb_sendmessage( struct msn_switchboard *sb, char *text )
buf[i++] = text[j];
}
}
- else
- {
- i = strlen( MSN_TYPING_HEADERS ) + strlen( sb->ic->acc->user );
- buf = g_new0( char, i );
- i = g_snprintf( buf, i, MSN_TYPING_HEADERS, sb->ic->acc->user );
- }
/* Build the final packet (MSG command + the message). */
packet = g_strdup_printf( "MSG %d N %d\r\n%s", ++sb->trId, i, buf );
@@ -249,6 +254,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 );
@@ -470,6 +476,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 )
{
@@ -519,6 +527,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 )
@@ -580,6 +590,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;
@@ -763,3 +775,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 2b186f1c..f0c87c20 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;
@@ -131,6 +132,7 @@ void nogaim_init()
extern void oscar_initmodule();
extern void byahoo_initmodule();
extern void jabber_initmodule();
+ extern void twitter_initmodule();
#ifdef WITH_MSN
msn_initmodule();
@@ -148,6 +150,10 @@ void nogaim_init()
jabber_initmodule();
#endif
+#ifdef WITH_TWITTER
+ twitter_initmodule();
+#endif
+
#ifdef WITH_PLUGINS
load_plugins();
#endif
@@ -726,7 +732,7 @@ void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags,
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;
/* pass the message through OTR */
@@ -775,11 +781,20 @@ 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( msg );
+ g_free( ts );
}
void imcb_buddy_typing( struct im_connection *ic, char *handle, uint32_t flags )
@@ -823,6 +838,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;
@@ -883,7 +927,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
{
@@ -1020,6 +1068,97 @@ static int remove_chat_buddy_silent( struct groupchat *b, const char *handle )
}
+/* Misc. BitlBee stuff which shouldn't really be here */
+
+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, 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: */
@@ -1063,6 +1202,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 ace46053..d3f5847f 100644
--- a/protocols/nogaim.h
+++ b/protocols/nogaim.h
@@ -305,6 +305,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 );
@@ -327,6 +328,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 );
gboolean auto_reconnect( gpointer data, gint fd, b_input_condition cond );
void cancel_auto_reconnect( struct account *a );
diff --git a/protocols/oscar/oscar.c b/protocols/oscar/oscar.c
index cea1e0c4..dbab5c4f 100644
--- a/protocols/oscar/oscar.c
+++ b/protocols/oscar/oscar.c
@@ -372,11 +372,15 @@ static void oscar_init(account_t *acc)
{
set_t *s;
- s = set_add( &acc->set, "server", AIM_DEFAULT_LOGIN_SERVER, set_eval_account, acc );
+ if (isdigit(acc->user[0])) {
+ set_add(&acc->set, "ignore_auth_requests", "false", set_eval_bool, acc);
+ }
+
+ s = set_add(&acc->set, "server", AIM_DEFAULT_LOGIN_SERVER, set_eval_account, acc);
s->flags |= ACC_SET_NOSAVE | ACC_SET_OFFLINE_ONLY;
- if (isdigit(acc->user[0])) {
- s = set_add( &acc->set, "web_aware", "false", set_eval_bool, acc );
+ if(isdigit(acc->user[0])) {
+ s = set_add(&acc->set, "web_aware", "false", set_eval_bool, acc);
s->flags |= ACC_SET_OFFLINE_ONLY;
}
@@ -1211,10 +1215,15 @@ static void gaim_icq_authdeny(void *data_) {
* For when other people ask you for authorization
*/
static void gaim_icq_authask(struct im_connection *ic, guint32 uin, char *msg) {
- struct icq_auth *data = g_new(struct icq_auth, 1);
+ struct icq_auth *data;
char *reason = NULL;
char *dialog_msg;
+
+ if (set_getbool(&ic->acc->set, "ignore_auth_requests"))
+ return;
+ data = g_new(struct icq_auth, 1);
+
if (strlen(msg) > 6)
reason = msg + 6;
diff --git a/protocols/twitter/Makefile b/protocols/twitter/Makefile
new file mode 100644
index 00000000..ca1e4695
--- /dev/null
+++ b/protocols/twitter/Makefile
@@ -0,0 +1,43 @@
+###########################
+## Makefile for BitlBee ##
+## ##
+## Copyright 2002 Lintux ##
+###########################
+
+### DEFINITIONS
+
+-include ../../Makefile.settings
+
+# [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: %.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..29be8a96
--- /dev/null
+++ b/protocols/twitter/twitter.c
@@ -0,0 +1,248 @@
+/***************************************************************************\
+* *
+* 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 "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 char *set_eval_mode( set_t *set, char *value )
+{
+ if( g_strcasecmp( value, "one" ) == 0 ||
+ g_strcasecmp( value, "many" ) == 0 ||
+ g_strcasecmp( value, "char" ) == 0 )
+ return value;
+ else
+ return NULL;
+}
+
+static void twitter_init( account_t *acc )
+{
+ set_t *s;
+
+ s = set_add( &acc->set, "mode", "one", set_eval_mode, acc );
+ s->flags |= ACC_SET_OFFLINE_ONLY;
+}
+
+/**
+ * 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 );
+
+ td->user = acc->user;
+ td->pass = acc->pass;
+ td->home_timeline_id = 0;
+
+ ic->proto_data = td;
+
+ imcb_log( ic, "Connecting to Twitter" );
+
+ // Run this once. After this queue the main loop function.
+ twitter_main_loop(ic, -1, 0);
+
+ // Queue the main_loop
+ // Save the return value, so we can remove the timeout on logout.
+ td->main_loop_id = b_timeout_add(60000, twitter_main_loop, ic);
+
+ sprintf( name, "twitter_%s", acc->user );
+ imcb_add_buddy( ic, name, NULL );
+ imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL );
+}
+
+/**
+ * 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 )
+ {
+ 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 )
+{
+ if (g_strncasecmp(who, "twitter_", 8) == 0 &&
+ g_strcasecmp(who + 8, ic->acc->user) == 0)
+ 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_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..88caa104
--- /dev/null
+++ b/protocols/twitter/twitter.h
@@ -0,0 +1,52 @@
+/***************************************************************************\
+* *
+* 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;
+ 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..3632140f
--- /dev/null
+++ b/protocols/twitter/twitter_http.c
@@ -0,0 +1,161 @@
+/***************************************************************************\
+* *
+* 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_http.h"
+#include "twitter.h"
+#include "bitlbee.h"
+#include "url.h"
+#include "misc.h"
+#include "base64.h"
+#include <ctype.h>
+#include <errno.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, 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 (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..ec4a0b7c
--- /dev/null
+++ b/protocols/twitter/twitter_http.h
@@ -0,0 +1,34 @@
+/***************************************************************************\
+* *
+* 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"
+
+void *twitter_http(char *url_string, http_input_function func, gpointer data, int is_post,
+ char* user, char* pass, 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..d58afd73
--- /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, 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, 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, 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, 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, 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/root_commands.c b/root_commands.c
index 5ce5060a..0f39239a 100644
--- a/root_commands.c
+++ b/root_commands.c
@@ -1,7 +1,7 @@
/********************************************************************\
* BitlBee -- An IRC to other IM-networks gateway *
* *
- * Copyright 2002-2004 Wilmer van der Gaast and others *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
\********************************************************************/
/* User manager (root) commands */
@@ -658,6 +658,21 @@ static void cmd_rename( irc_t *irc, char **cmd )
{
irc_usermsg( irc, "Nick `%s' can't be changed", cmd[1] );
}
+ else if( g_strcasecmp( cmd[1], irc->channel ) == 0 )
+ {
+ if( strchr( CTYPES, cmd[2][0] ) && nick_ok( cmd[2] + 1 ) )
+ {
+ u = user_find( irc, irc->nick );
+
+ irc_part( irc, u, irc->channel );
+ g_free( irc->channel );
+ irc->channel = g_strdup( cmd[2] );
+ irc_join( irc, u, irc->channel );
+
+ if( strcmp( cmd[0], "set_rename" ) != 0 )
+ set_setstr( &irc->set, "control_channel", cmd[2] );
+ }
+ }
else if( user_find( irc, cmd[2] ) && ( nick_cmp( cmd[1], cmd[2] ) != 0 ) )
{
irc_usermsg( irc, "Nick `%s' already exists", cmd[2] );
@@ -707,6 +722,20 @@ char *set_eval_root_nick( set_t *set, char *new_nick )
return strcmp( irc->mynick, new_nick ) == 0 ? new_nick : SET_INVALID;
}
+char *set_eval_control_channel( set_t *set, char *new_name )
+{
+ irc_t *irc = set->data;
+
+ if( strcmp( irc->channel, new_name ) != 0 )
+ {
+ char *cmd[] = { "set_rename", irc->channel, new_name, NULL };
+
+ cmd_rename( irc, cmd );
+ }
+
+ return strcmp( irc->channel, new_name ) == 0 ? new_name : SET_INVALID;
+}
+
static void cmd_remove( irc_t *irc, char **cmd )
{
user_t *u;
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/check_jabber_util.c b/tests/check_jabber_util.c
index 4728c5ee..bf6d3e60 100644
--- a/tests/check_jabber_util.c
+++ b/tests/check_jabber_util.c
@@ -13,12 +13,12 @@ static void check_buddy_add(int l)
struct jabber_buddy *budw1, *budw2, *budw3, *budn, *bud;
budw1 = jabber_buddy_add( ic, "wilmer@gaast.net/BitlBee" );
- budw1->last_act = time( NULL ) - 100;
+ budw1->last_msg = time( NULL ) - 100;
budw2 = jabber_buddy_add( ic, "WILMER@gaast.net/Telepathy" );
budw2->priority = 2;
- budw2->last_act = time( NULL );
+ budw2->last_msg = time( NULL );
budw3 = jabber_buddy_add( ic, "wilmer@GAAST.NET/bitlbee" );
- budw3->last_act = time( NULL ) - 200;
+ budw3->last_msg = time( NULL ) - 200;
budw3->priority = 4;
/* TODO(wilmer): Shouldn't this just return budw3? */
fail_if( jabber_buddy_add( ic, "wilmer@gaast.net/Telepathy" ) != NULL );
@@ -59,18 +59,39 @@ static void check_buddy_add(int l)
fail_if( jabber_buddy_by_jid( ic, "wilmer@GAAST.NET/telepathy", GET_BUDDY_CREAT ) == budw2 );
fail_unless( jabber_buddy_remove( ic, "wilmer@gaast.net/Telepathy" ) );
fail_unless( jabber_buddy_remove( ic, "wilmer@gaast.net/telepathy" ) );
- fail_unless( jabber_buddy_by_jid( ic, "wilmer@gaast.net", 0 ) == budw1 );
+
+ /* Test activity_timeout and GET_BUDDY_BARE_OK. */
+ fail_unless( jabber_buddy_by_jid( ic, "wilmer@gaast.net", GET_BUDDY_BARE_OK ) == budw1 );
+ budw1->last_msg -= 50;
+ fail_unless( ( bud = jabber_buddy_by_jid( ic, "wilmer@gaast.net", GET_BUDDY_BARE_OK ) ) != NULL );
+ fail_unless( strcmp( bud->full_jid, "wilmer@gaast.net" ) == 0 );
fail_if( jabber_buddy_remove( ic, "wilmer@gaast.net" ) );
fail_unless( jabber_buddy_by_jid( ic, "wilmer@gaast.net", 0 ) == budw1 );
+ fail_if( jabber_buddy_remove( ic, "wilmer@gaast.net" ) );
+ fail_unless( jabber_buddy_remove( ic, "wilmer@gaast.net/bitlbee" ) );
+ fail_unless( jabber_buddy_remove( ic, "wilmer@gaast.net/BitlBee" ) );
+ fail_if( jabber_buddy_by_jid( ic, "wilmer@gaast.net", GET_BUDDY_BARE_OK ) );
+
/* Check if remove_bare() indeed gets rid of all. */
+ /* disable this one for now.
fail_unless( jabber_buddy_remove_bare( ic, "wilmer@gaast.net" ) );
fail_if( jabber_buddy_by_jid( ic, "wilmer@gaast.net", 0 ) );
+ */
fail_if( jabber_buddy_remove( ic, "nekkid@lamejab.net/Illegal" ) );
fail_unless( jabber_buddy_remove( ic, "nekkid@lamejab.net" ) );
fail_if( jabber_buddy_by_jid( ic, "nekkid@lamejab.net", 0 ) );
+
+ /* Fixing a bug in this branch that caused information to get lost when
+ removing the first full JID from a list. */
+ jabber_buddy_add( ic, "bugtest@google.com/A" );
+ jabber_buddy_add( ic, "bugtest@google.com/B" );
+ jabber_buddy_add( ic, "bugtest@google.com/C" );
+ fail_unless( jabber_buddy_remove( ic, "bugtest@google.com/A" ) );
+ fail_unless( jabber_buddy_remove( ic, "bugtest@google.com/B" ) );
+ fail_unless( jabber_buddy_remove( ic, "bugtest@google.com/C" ) );
}
Suite *jabber_util_suite (void)
@@ -84,6 +105,7 @@ Suite *jabber_util_suite (void)
ic->proto_data = jd = g_new0( struct jabber_data, 1 );
jd->buddies = g_hash_table_new( g_str_hash, g_str_equal );
set_add( &ic->acc->set, "resource_select", "priority", NULL, ic->acc );
+ set_add( &ic->acc->set, "activity_timeout", "120", NULL, ic->acc );
suite_add_tcase (s, tc_core);
tcase_add_test (tc_core, check_buddy_add);
diff --git a/unix.c b/unix.c
index 101a1233..cd8e5dd7 100644
--- a/unix.c
+++ b/unix.c
@@ -82,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 )
@@ -105,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 );