Index: protocols/skype/.gitignore
===================================================================
--- protocols/skype/.gitignore	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
+++ protocols/skype/.gitignore	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
@@ -0,0 +1,19 @@
+Changelog
+HEADER.html
+*.gz
+*.asc
+.htaccess
+shot
+*.swp
+aclocal.m4
+autom4te.cache
+config.log
+config.mak
+config.status
+configure
+etc
+install-sh
+skype.so
+skyped.conf
+skyped.conf.dist
+skype.dylib*
Index: protocols/skype/.mailmap
===================================================================
--- protocols/skype/.mailmap	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
+++ protocols/skype/.mailmap	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
@@ -0,0 +1,1 @@
+Miklos Vajna <vmiklos@frugalware.org>
Index: protocols/skype/HACKING
===================================================================
--- protocols/skype/HACKING	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
+++ protocols/skype/HACKING	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
@@ -0,0 +1,26 @@
+== Tabs
+
+I use the following tabs during the development:
+
+1) bitlbee-skype:
+
+vim, make, etc.
+
+2) bitlbee:
+
+gdb --args ./bitlbee -v -n -D
+run
+
+3) skyped:
+
+python skyped.py -n -d
+
+4) irssi
+
+== Get the code from git
+
+To get the code directly from git, you need:
+
+git clone git://vmiklos.hu/bitlbee-skype
+cd bitlbee-skype
+make autogen
Index: protocols/skype/Makefile
===================================================================
--- protocols/skype/Makefile	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
+++ protocols/skype/Makefile	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
@@ -0,0 +1,97 @@
+-include config.mak
+
+VERSION = 0.9.0
+DATE := $(shell date +%Y-%m-%d)
+# latest stable
+BITLBEE_VERSION = 3.0.1
+
+ifeq ($(ASCIIDOC),yes)
+MANPAGES = skyped.1
+else
+MANPAGES =
+endif
+
+ifeq ($(BITLBEE),yes)
+LIBS = skype.$(SHARED_EXT)
+else
+LIBS =
+endif
+
+all: $(LIBS) $(MANPAGES)
+
+skype.$(SHARED_EXT): skype.c config.mak
+ifeq ($(BITLBEE),yes)
+	$(CC) $(CFLAGS) $(SHARED_FLAGS) -o skype.$(SHARED_EXT) skype.c $(LDFLAGS)
+endif
+
+install: all
+ifeq ($(ASCIIDOC),yes)
+	$(INSTALL) -d $(DESTDIR)$(mandir)/man1
+	$(INSTALL) -m644 $(MANPAGES) $(DESTDIR)$(mandir)/man1
+endif
+ifeq ($(BITLBEE),yes)
+	$(INSTALL) -d $(DESTDIR)$(plugindir)
+	$(INSTALL) skype.$(SHARED_EXT) $(DESTDIR)$(plugindir)
+endif
+ifeq ($(SKYPE4PY),yes)
+	$(INSTALL) -d $(DESTDIR)$(bindir)
+	$(INSTALL) -d $(DESTDIR)$(sysconfdir)
+	$(INSTALL) skyped.py $(DESTDIR)$(bindir)/skyped
+	perl -p -i -e 's|/usr/local/etc/skyped|$(sysconfdir)|' $(DESTDIR)$(bindir)/skyped
+	$(INSTALL) -m644 skyped.conf.dist $(DESTDIR)$(sysconfdir)/skyped.conf
+	perl -p -i -e 's|\$${prefix}|$(prefix)|' $(DESTDIR)$(sysconfdir)/skyped.conf
+	$(INSTALL) -m644 skyped.cnf $(DESTDIR)$(sysconfdir)
+endif
+
+client: client.c
+
+autogen: configure.ac
+	cp $(shell ls /usr/share/automake-*/install-sh | tail -n1) ./
+	autoconf
+
+clean:
+	rm -f $(LIBS) $(MANPAGES)
+
+distclean: clean
+	rm -f config.log config.mak config.status
+
+autoclean: distclean
+	rm -rf aclocal.m4 autom4te.cache configure install-sh
+
+# take this from the kernel
+check:
+	perl checkpatch.pl --no-tree --file skype.c
+
+test: all
+	$(MAKE) -C t/ all
+
+dist:
+	git archive --format=tar --prefix=bitlbee-skype-$(VERSION)/ HEAD | tar xf -
+	mkdir -p bitlbee-skype-$(VERSION)
+	git log --no-merges |git name-rev --tags --stdin > bitlbee-skype-$(VERSION)/Changelog
+	make -C bitlbee-skype-$(VERSION) autogen
+	tar czf bitlbee-skype-$(VERSION).tar.gz bitlbee-skype-$(VERSION)
+	rm -rf bitlbee-skype-$(VERSION)
+
+release:
+	git tag $(VERSION)
+	$(MAKE) dist
+	gpg --comment "See http://vmiklos.hu/gpg/ for info" \
+		-ba bitlbee-skype-$(VERSION).tar.gz
+
+doc: HEADER.html Changelog AUTHORS
+
+HEADER.html: README Makefile
+	asciidoc -a toc -a numbered -a sectids -o HEADER.html -a icons -a data-uri --attribute iconsdir=./images/icons README
+	sed -i 's|@VERSION@|$(VERSION)|g' HEADER.html
+	sed -i 's|@BITLBEE_VERSION@|$(BITLBEE_VERSION)|g' HEADER.html
+
+Changelog: .git/refs/heads/master
+	git log --no-merges |git name-rev --tags --stdin >Changelog
+
+AUTHORS: .git/refs/heads/master
+	git shortlog -s -n |sed 's/.*\t//'> AUTHORS
+
+%.1: %.txt asciidoc.conf
+	a2x --asciidoc-opts="-f asciidoc.conf" \
+		-a bs_version=$(VERSION) -a bs_date=$(DATE) -f manpage $<
Index: protocols/skype/NEWS
===================================================================
--- protocols/skype/NEWS	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
+++ protocols/skype/NEWS	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
@@ -0,0 +1,131 @@
+VERSION         DESCRIPTION
+-----------------------------------------------------------------------------
+0.9.0         - merge support for building the plugin on OpenBSD
+	      - merge support for running skyped without gobject and
+	        pygnutls/pyopenssl - as a side effect this adds Windows support
+	      - add /ctcp call|hangup support (you need BitlBee from bzr to use
+	        this)
+              - add group support (see http://wiki.bitlbee.org/UiFix)
+0.8.4         - now using python2.7 directly in case python would point to python3k
+              - merge patch to avoid a crash when failing to connect to skyped
+              - merge support for building the plugin on NetBSD
+              - merge Debian patches
+0.8.3         - support for BitlBee 1.3dev
+              - fixed --debug switch (-d was fine)
+              - documentation fixes
+0.8.2         - building documentation is now optional
+              - new settings: test_join and show_moods
+              - '~' in skyped.conf is now expanded to the user's home directory
+	      - groupchat channel names are now persistent (requires
+	        BitlBee-1.2.6)
+0.8.1         - support for BitlBee 1.2.5
+              - support for Skype 2.1.0.81 and Skype4Py 1.0.32.0
+              - the plugin part now supports FreeBSD
+              - fix for edited messages, the prefix can now be configured
+0.8.0         - fix build on x86_64 (-fPIC usage)
+              - debug messages now have a timestamp
+              - more work on having the default config under ~/.skyped
+              - added a manual page for skyped
+0.7.2         - add --log option to skyped to allow logging while it the
+                daemon is in the background.
+              - prefer config files from ~/.skyped over /etc/skyped
+              - handle the case when LANG and LC_ALL env vars are empty
+0.7.1         - mostly internal changes, the monster read callback is
+                now replaced by tiny parser functions
+0.7.0         - made 'make config' more portable
+              - add 'skypeconsole' buddy for debugging purposes
+              - support autojoin for bookmarked groupchats
+              - skyped: make hardwired '/dev/null' portable and fix
+                Python-2.6 warnings
+0.6.3         - various osx-specific improvements (see the new screenshot!)
+              - added python-gnutls install instructions
+              - bitlbee.pc is now searched under
+                /usr/local/lib/pkgconfig by default to help LFS monkeys ;)
+0.6.2         - bugfix: make install required the plugin even in case
+                its build was disabled
+0.6.1         - added keepalive traffic to avoid disconnects in bitlbee
+                when there is no traffic for a long time
+              - now the plugin or skyped is automatically disabled if
+		the dependencies are not available; useful in case the
+		plugin is to be installed on a public server, or the
+		skyped is to be used with a public server only
+0.6.0         - works with BitlBee 1.2.1
+0.5.1         - configure now automatically detects the right prefix to
+                match witl BitlBee's one
+              - minor documentation improvements (public chats, bug
+                reporting address)
+0.5.0         - skyped now uses gnutls if possible, which seem to be
+                more stable, compared to openssl.
+              - skyped now tries to handle all read/write errors from/to
+                clients, and always just warn about failures, never exit.
+              - installation for Debian users should be more simple
+              - improved documentation
+              - this .0 release should be quite stable, only about 100
+                lines of new code
+0.4.2         - skyped should be now more responsive
+              - new skypeout_offline setting for hiding/showing SkypeOut
+                contacts
+              - support for SkypeOut calls
+              - support for querying the balance from Skype
+              - all setting should be documented now
+0.4.1         - support for building the plugin on Mac OSX
+              - tested with BitlBee 1.2 and Skype 2.0.0.63
+              - avoid ${prefix} (by autoconf) in the config file as we
+                don't handle such a variable
+              - now you can call echo123 (patch by Riskó Gergely)
+0.4.0         - support for starting, accepting and rejecting calls
+              - also updated documentation (the key is the account set
+                skype/call command)
+	      - as usual with the .0 releases, be careful, ~200 lines of
+	        new code
+0.3.2         - support for Skype 2.0.0.43
+              - skyped now automatically starts/shuts down skype
+              - improved 'make prepare' to handle more automake versions
+              - documentation improvements
+0.3.1         - beautify output when skyped is interrupted using ^C
+              - 'nick skype foo' now really sets display name, not the mood
+                text
+              - documentation fixups
+              - this version should be again as stable as 0.2.6 was
+0.3.0         - authentication support in skyped via ssl
+              - ~200 lines of new code, so be careful :)
+	      - upgraders: please read the documentation about how to set up
+	        your config the ssl certificate, this was no necessary till now
+0.2.6         - the server setting has a default value, 'localhost' so in most
+		cases you no longer have to set it explicitly
+	      - support for receiving emoted messages, ie. when the user types
+	        '/me foo'
+	      - support for setting the display name (nick 0 "foo bar") - it
+	        sets the mood text
+0.2.5         - now bitlbee's info command is supported (it displays full name,
+		birthday, homepage, age, etc.)
+0.2.4         - improve documentation based on feedback from people on #bitlbee
+	      - fixed for Skype4Py >= 0.9.28.4
+	      - tested with latest Skype beta, too (the one which supports
+	        video)
+0.2.3         - fixed that annoying "creating groupchat failed" warning
+0.2.2         - don't change the topic if skype does not report a successful
+                topic change
+              - fixed for the recent bitlbee API changes
+0.2.1         - topic support in group chats
+              - bugfixes for multiline messages
+              - this version should be again as stable as 0.1.4 was
+0.2.0         - group chat support
+              - ~300 lines of new code, so be careful :)
+              - the version number mentions that this is not a minor change
+0.1.4         - documentation: mention the version of all deps (requirements
+                section)
+              - fix sending / sending accents
+              - don't use internal functions of skype4py
+              - skyped no longer dies when skype is killed
+0.1.3         - support for edited messages
+              - ignore empty messages (skype does the same)
+              - support for multiline messages
+              - switch to the x11 api instead of dbus (it's much more stable)
+0.1.2         - notification when a new call arrives in
+              - more documentation (vnc)
+              - first release which works with unpatched bitlbee
+0.1.1         - skyped now runs as daemon in the background by default
+              - skyped now automatically reconnects on Skype restarts
+0.1.0         - initial release
+              - see README for major features
Index: protocols/skype/README
===================================================================
--- protocols/skype/README	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
+++ protocols/skype/README	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
@@ -0,0 +1,488 @@
+= Skype plugin for BitlBee
+Miklos Vajna <vmiklos-at-vmiklos-dot-hu>
+
+== Status
+
+[quote, Wilmer van der Gaast (author of BitlBee)]
+____
+Okay, this exists now, with lots of thanks to vmiklos for his *excellent*
+work!!
+
+It's not in the main BitlBee and it'll never be for various reasons, but
+because it's a plugin that shouldn't be a problem.
+____
+
+One day I browsed the BitlBee bugtracker and found
+http://bugs.bitlbee.org/bitlbee/ticket/82[this] ticket. Then after a while I
+returned and saw that it was still open. So I wrote it.
+
+It's pretty stable (one day I wanted to restart it because of an upgrade
+and just noticed it was running for 2+ months without crashing), I use
+it for my daily work. Being a plug-in, no patching is required, you can
+just install it after installing BitlBee itself.
+
+NOTE: You will see that this implementation of the Skype plug-in still requires
+a Skype instance to be running. This is because I'm not motivated to reverse
+engineer Skype's
+http://en.wikipedia.org/wiki/Skype_Protocol#Obfuscation_Layer[obfuscation
+layer]. (Not mentioning that you should ask your lawyer about if it is legal or
+not..)
+
+== Requirements
+
+* Skype >= 1.4.0.99. The latest version I've tested is 2.1.0.81.
+* BitlBee >= 3.0. The latest version I've tested is @BITLBEE_VERSION@. Use
+  old versions (see the NEWS file about which one) if you have older BitlBee
+  installed.
+* Skype4Py >= 0.9.28.7. Previous versions won't work due to API changes.
+  The latest version I've tested is 1.0.32.0.
+
+* Python >= 2.5. Skype4Py does not work with 2.4.
+
+* OS: `bitlbee-skype` has been tested under Linux and Mac OS X. The plugin part
+  has been tested under Free/Open/NetBSD as well. The daemon part has been
+  tested on Windows, too.
+
+== How to set it up
+
+Before you start. The setup is the following: BitlBee can't connect directly to
+Skype servers (the company's ones). It needs a running Skype client to do so.
+In fact BitlBee will connect to `skyped` (a tcp server, provided in this
+package) and `skyped` will connect to to your Skype client.
+
+The benefit of this architecture is that you can run Skype and `skyped`
+on a machine different to the one where you run BitlBee (it can be even
+a public server) and/or your IRC client.
+
+NOTE: The order is important. First `skyped` starts Skype. Then `skyped`
+connects to Skype, finally BitlBee can connect to `skyped`.
+
+=== Installing under Frugalware or Debian
+
+- Install the necessary packages:
+
+----
+# pacman-g2 -S bitlbee-skype
+----
+
+or
+
+----
+# apt-get install skyped bitlbee-plugin-skype
+----
+
+(the later from the unstable repo)
+
+and you don't have to compile anything manually.
+
+=== Installing under OS X
+
+- Install the necessary packages from ports:
+
+NOTE: You have to edit the Portfile manually to include the install-dev target,
+just append install-dev after install-etc.
+
+----
+# port -v install bitlbee
+----
+
+and you have to install `bitlbee-skype` and `skype4py` from
+source.
+
+=== Installing from source
+
+NOTE: bitlbee-skype by default builds and installs skyped and the
+plugin. In case you just want to install the plugin for a public server
+or you want to use skyped with a public server (like
+`bitlbee1.asnetinc.net`), you don't need both.
+
+- You need the latest stable BitlBee release (unless you want to use a
+  public server):
+
+----
+$ wget http://get.bitlbee.org/src/bitlbee-@BITLBEE_VERSION@.tar.gz
+$ tar xf bitlbee-@BITLBEE_VERSION@.tar.gz
+$ cd bitlbee-@BITLBEE_VERSION@
+----
+
+- Now compile and install it:
+
+----
+$ ./configure
+$ make
+# make install install-dev
+----
+
+- To install http://skype4py.sourceforge.net/[Skype4Py] from source
+  (unless you want to install the plugin for a public server):
+
+----
+$ tar -zxvf Skype4Py-x.x.x.x.tar.gz
+$ cd Skype4Py-x.x.x.x
+# python setup.py install
+----
+
+- Get the plugin code (in an empty dir, or whereever you want, it does
+  not matter):
+
+----
+$ wget http://vmiklos.hu/project/bitlbee-skype/bitlbee-skype-@VERSION@.tar.gz
+$ tar xf bitlbee-skype-@VERSION@.tar.gz
+$ cd bitlbee-skype-@VERSION@
+----
+
+- Compile and install it:
+
+----
+$ ./configure
+$ make
+# make install
+----
+
+This will install the plugin to where BitlBee expects them, which is
+`/usr/local/lib/bitlbee` if you installed BitlBee from source.
+
+=== Configuring
+
+- Set up `~/.skyped/skyped.conf`: Create the `~/.skyped` directory, copy
+  `skyped.conf` and `skyped.cnf` from
+  `/usr/local/etc/skyped/skyped.conf` to `~/.skyped`, adjust `username`
+  and `password`. The `username` should be your Skype login and the
+  `password` can be whatever you want, but you will have to specify that
+  one when adding the Skype account to BitlBee (see later).
+
+NOTE: Here, and later - `/usr/local/etc` can be different on your installation
+if you used the `--sysconfdir` switch when running bitlbee-skype's `configure`.
+
+- Generate the SSL pem files:
+
+----
+# cd ~/.skyped
+# openssl req -new -x509 -days 365 -nodes -config skyped.cnf -out skyped.cert.pem \
+	-keyout skyped.key.pem
+----
+
+NOTE: Maybe you want to adjust the permissions in the `~/.skyped`
+dir. For example make it readable by just your user.
+
+- Start `skyped` (the tcp server):
+
+----
+$ skyped
+----
+
+- Start your `IRC` client, connect to BitlBee and add your account:
+
+----
+account add skype <user> <pass>
+account skype set server localhost
+----
+
+<user> should be your Skype account name, <pass> should be the one you declared
+in `skyped.conf`. If you want to run skyped on a remote machine, replace
+`localhost` with the name of the machine.
+
+If you are running skyped on a custom port:
+
+----
+account skype set port <port>
+----
+
+If you want to set your full name (optional):
+
+----
+account skype set display_name "John Smith"
+----
+
+If you want to see your skypeout contacts online as well (they are
+offline by default):
+
+----
+account skype set skypeout_offline false
+----
+
+== Setting up Skype in a VNC server (optional)
+
+Optionally, if you want to run Skype on a server, you might want to setup up
+a `VNC` server as well. I used `tightvnc` but probably other `VNC` servers will
+work, too.
+
+First run
+
+----
+$ vncpasswd ~/.vnc/passwd
+----
+
+and create a password. You will need it at least once.
+
+Now create `~/.vnc/xstartup` with the following contents:
+
+----
+#!/bin/sh
+
+blackbox
+----
+
+Adjust the permissions:
+
+----
+$ chmod +x ~/.vnc/xstartup
+----
+
+Then start the server:
+
+----
+$ vncserver
+----
+
+Then connect to it, start an `xterm`, set up Skype (username, password,
+enable X11 API and allow the `Skype4Py` client), quit from Skype, and
+start `skyped`. If you want to watch its traffic, enable debug messages
+and foreground mode:
+
+----
+$ skyped -n -d
+----
+
+== Features
+
+- Download nicks and away statuses from Skype
+
+- Noticing joins / parts while we're connected
+
+- Sending messages
+
+- Receiving messages
+
+- Receiving away status changes
+
+- `skyped` (the tcp daemon that is a gateway between Skype and tcp)
+
+- Error handling when `skyped` is not running and when it exits
+
+- Marking received messages as seen so that Skype won't say there are unread messages
+
+- Adding / removing contacts
+
+- Set away state when you do a `/away`.
+
+- When you `account off`, Skype will set status to `Offline`
+
+- When you `account on`, Skype will set status to `Online`
+
+- Detect when somebody wants to add you and ask for confirmation
+
+- Detect when somebody wants to transfer a file
+
+- Group chat support:
+
+  * Detect if we're invited
+
+  * Send / receive group chat messages
+
+  * Invite others (using `/invite <nick>`)
+
+  * Part from group chats
+
+  * Starting a group chat (using `/j #nick`)
+
+- Topic changes in group chats:
+
+  * Show the current topic (if any) on join
+
+  * Notice when someone changes the topic
+
+  * Support changing the topic using `/topic`
+
+- Viewing the profile using the `info` command.
+
+- Handling skype actions (when the `CHATMESSAGE` has `EMOTED` type)
+
+- Setting your display name using the `nick` command.
+
+- Running Skype on a machine different to BitlBee is possible, the
+  communication is encrypted.
+
+- Managing outgoing calls (with call duration at the end):
+
+  * `/ctcp nick call`
+  * `/ctcp nick hangup`
+
+- Managing outgoing SkypeOut or conference calls:
+
+  * `account skype set call +18005551234`
+  * `account skype set call nick1 nick2`
+  * `account skype set -del call`
+
+- Managing incoming calls via questions, just like when you add / remove
+  contacts.
+
+- Querying the current SkypeOut balance:
+
+  * `account skype set balance query`
+
+- For debug purposes, it's possible to send any command to `skyped`. To
+  achieve this, you need to:
+
+  * `account skype set skypeconsole true`
+
+  * then writing `skypeconsole: <command>` will work in the control
+    channel.
+
+  * `account skype set skypeconsole_receive true` will make the
+    `skypeconsole` account dump all the recieved raw traffic for you
+
+- If you want to automatically join bookmarked groupchats right after
+  you logged in, do:
+
+  * `account skype set auto_join true`
+
+- Edited messages are shown with the `EDIT:` prefix. If you don't like
+  this, you can set your own prefix using:
+
+  * `account skype set edit_prefix "updated message:"`
+
+- The `echo123` test account is hidden by default. If you want to see it:
+
+  * `account skype set test_join true`
+
+- Mood texts are not shown by default. If you want to see it:
+
+  * `account skype set show_moods true`
+
+- Group support:
+
+  * Skype groups are told to BitlBee
+  * The usual `/invite` in a group channel adds the buddy to the group in skype
+    as well (and if necessary, it creates a new group in Skype)
+
+== What needs to be done (aka. TODO)
+
+- Notice if foo invites bar. Currently you can see only that bar joined.
+
+- Public chats. See
+  link:https://developer.skype.com/jira/browse/SCL-381[this feature
+  request], this is because it is still not possible (under Linux) to
+  `join_chat` to a public chat..
+
+- Add yasrd (Yet Another Skype-Related Daemon) to allow using a public
+  server for users who are behind NAT.
+
+== I would like to have support for ...
+
+If something does not work and it's not in the TODO section, then please
+contact me! Please also try the link:HACKING[git version] before reporting a bug, your
+problem may be already fixed there.
+
+In fact, of course, I wrote this documentation after figured out how to do this
+setup, so maybe I left out some steps. If you needed 'any' additional tricks,
+then it would be nice to include them here.
+
+== Known bugs
+
+- File transfers are view-only from BitlBee. Quoting the
+  https://developer.skype.com/Docs/ApiDoc/FILETRANSFER_object[relevant
+  documentation]: 'File transfers cannot be initiated nor accepted via
+  API commands.' So it's not something I can add support for, sadly.
+
+== Screenshots
+
+You can reach some screenshots link:shot[here].
+
+== Additional resources
+
+You can reach the Changelog link:Changelog[here], and a gitweb interface
+http://vmiklos.hu/gitweb/?p=bitlbee-skype.git[here].
+
+The Skype API documentation is
+http://developer.skype.com/resources/public_api_ref.zip[here] if you're
+interested.
+
+== Testimonials
+
+----
+00:56 < scathe> I like your skype plugin :)
+----
+
+----
+It's really working great so far.
+
+Good Job and thank you!
+Sebastian
+----
+
+----
+Big respect for your work, i really appreciate it.
+
+Martin
+----
+
+----
+Thanks for bitlbee-skype. As a blind Linux user, I cannot use the
+skype GUI client because qt apps ar not accessible yet with the
+available screen readers. bitlbee-skype allows me to make use of skype
+without having to interact much with the GUI client, which helps me a
+lot.
+
+Lukas
+----
+
+----
+02:12 < newton> i must say, i love this little bee ;)
+02:15 < newton> tried it out today with the skype plugin, good work!
+----
+
+----
+18:10 < miCSu> it works fine
+----
+
+----
+13:56 < seo> i just want to thank you :)
+13:56 < seo> for bitlbee-skype
+13:57 < seo> it's working very well, so, again, thank you for your work, and for sharing it
+----
+
+----
+22:16 < ecraven> vmiklos: thanks a lot for the skype plugin for bitlbee!
+----
+
+----
+I'm blind and so I have to use a screen reader, in my case Gnome-Orca.
+But since Skype is written in QT, while Orca uses gtk+, I have no direct
+access to the Skype interface. That's why I desided to use Skyped and
+Erc.
+The text console is fully accessible.
+Thank you very much.
+
+Hermann
+----
+
+----
+i love that bitlbeeplugin. big thx for that.
+
+michael
+----
+
+----
+23:47 < krisfremen> thanks for creating this fabulous piece of software vmiklos :)
+----
+
+== Thanks
+
+to the following people:
+
+* people in link:AUTHORS[AUTHORS] for their contributions
+
+* Arkadiusz Wahlig, author of skype4py, for making suggestions to skyped
+
+* Gabor Adam Toth (tg), for noticing extra code is needed to handle multiline
+  messages
+
+* Cristobal Palmer (tarheelcoxn), for helping to testing the plugin in a
+  timezone different to mine
+
+* people on `#bitlbee` for feedback
+
+Back to my link:/projects[projects page].
+
+// vim: ft=asciidoc
Index: protocols/skype/asciidoc.conf
===================================================================
--- protocols/skype/asciidoc.conf	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
+++ protocols/skype/asciidoc.conf	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
@@ -0,0 +1,21 @@
+ifdef::doctype-manpage[]
+ifdef::backend-docbook[]
+[header]
+template::[header-declarations]
+<refentry>
+	<refentryinfo>
+		<date>{bs_date}</date>
+	</refentryinfo>
+	<refmeta>
+		<refentrytitle>{mantitle}</refentrytitle>
+		<manvolnum>{manvolnum}</manvolnum>
+		<refmiscinfo class="source">bitlbee-skype</refmiscinfo>
+		<refmiscinfo class="version">{bs_version}</refmiscinfo>
+		<refmiscinfo class="manual">bitlbee-skype manual</refmiscinfo>
+	</refmeta>
+	<refnamediv>
+		<refname>{manname}</refname>
+		<refpurpose>{manpurpose}</refpurpose>
+	</refnamediv>
+endif::backend-docbook[]
+endif::doctype-manpage[]
Index: protocols/skype/client.sh
===================================================================
--- protocols/skype/client.sh	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
+++ protocols/skype/client.sh	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
@@ -0,0 +1,1 @@
+openssl s_client -host localhost -port 2727 -verify 0
Index: protocols/skype/config.mak.in
===================================================================
--- protocols/skype/config.mak.in	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
+++ protocols/skype/config.mak.in	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
@@ -0,0 +1,16 @@
+CFLAGS = @CFLAGS@
+LDFLAGS = @LDFLAGS@
+SHARED_FLAGS = @SHARED_FLAGS@
+SHARED_EXT = @SHARED_EXT@
+SKYPE4PY = @SKYPE4PY@
+BITLBEE = @BITLBEE@
+ASCIIDOC = @ASCIIDOC@
+INSTALL = @INSTALL@
+prefix = @prefix@
+sysconfdir = @sysconfdir@/skyped
+exec_prefix = @exec_prefix@
+bindir = @bindir@
+libdir = @libdir@
+plugindir = ${libdir}/bitlbee
+datarootdir = @datarootdir@
+mandir = @mandir@
Index: protocols/skype/configure.ac
===================================================================
--- protocols/skype/configure.ac	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
+++ protocols/skype/configure.ac	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
@@ -0,0 +1,96 @@
+AC_INIT([Skype plugin for BitlBee], 1.0, [vmiklos@vmiklos.hu], bitlbee-skype)
+AC_PROG_CC
+AC_PROG_INSTALL
+
+AC_ARG_ENABLE([debug], AS_HELP_STRING([--enable-debug], [Enable debug support (default: disabled)]), debug=yes)
+AC_MSG_CHECKING(for debug mode request)
+if test "x$debug" = "xyes" ; then
+	CFLAGS="-g -Wall -Werror"
+	AC_MSG_RESULT(yes)
+else
+	AC_MSG_RESULT(no)
+fi
+
+case "`$CC -dumpmachine`" in
+	*linux*|*freebsd*|*netbsd*|*openbsd*)
+		SHARED_FLAGS="-fPIC -shared"
+		SHARED_EXT="so"
+		;;
+	*apple-darwin*)
+		SHARED_FLAGS="-dynamiclib -undefined dynamic_lookup"
+		SHARED_EXT="dylib"
+		;;
+	*)
+		AC_MSG_ERROR([Your machine is not yet supported])
+		;;
+esac
+AC_SUBST(SHARED_FLAGS)
+AC_SUBST(SHARED_EXT)
+
+dnl Check for bitlbee
+AC_MSG_CHECKING(for BitlBee)
+if test -d /usr/local/lib/pkgconfig; then
+	PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig
+	export PKG_CONFIG_PATH
+fi
+pkg-config --exists bitlbee
+if test "$?" != "0"; then
+	AC_MSG_RESULT(no)
+	BITLBEE="no"
+else
+	AC_MSG_RESULT(yes)
+	BITLBEE="yes"
+	if test -z "$CFLAGS"; then
+		CFLAGS="`pkg-config --cflags bitlbee`"
+	else
+		CFLAGS="$CFLAGS `pkg-config --cflags bitlbee`"
+	fi
+	if test -z "$LDFLAGS"; then
+		LDFLAGS="`pkg-config --libs bitlbee`"
+	else
+		LDFLAGS="$LDFLAGS `pkg-config --libs bitlbee`"
+	fi
+	prefix=`pkg-config --variable=prefix bitlbee`
+fi
+AC_SUBST(BITLBEE)
+
+dnl Check for a2x
+AC_ARG_ENABLE([asciidoc], AS_HELP_STRING([--disable-asciidoc], [Disable asciidoc support (default: test)]))
+if test "$enable_asciidoc" != "no"; then
+	AC_CHECK_PROG(ASCIIDOC, asciidoc, yes, [])
+fi
+
+dnl Check for Skype4Py
+AC_MSG_CHECKING(for Python module Skype4Py)
+python2.7 -c "import Skype4Py" 2>&AS_MESSAGE_LOG_FD
+if test "$?" != "0"; then
+	AC_MSG_RESULT(no)
+	SKYPE4PY="no"
+else
+	AC_MSG_RESULT(yes)
+	SKYPE4PY="yes"
+fi
+AC_SUBST(SKYPE4PY)
+
+if test "$BITLBEE" = "no" -a "$SKYPE4PY" = "no"; then
+	AC_ERROR([In order to use bitlbee-skype you need at least BitlBee or Skype4Py installed.])
+fi
+
+AC_OUTPUT(config.mak)
+AC_OUTPUT(skyped.conf.dist)
+
+echo "
+	BitlBee plugin:                 $BITLBEE
+	skyped:                         $SKYPE4PY
+	prefix:				$prefix
+	install program:		$INSTALL"
+if test "$BITLBEE" = "yes"; then
+	echo "	compiler flags:			$CFLAGS
+	linker flags:			$LDFLAGS
+	shared object flags:		$SHARED_FLAGS
+	shared object extension:	$SHARED_EXT"
+fi
+if test "$SKYPE4PY" = "yes"; then
+	echo "	sysconfig dir:			$sysconfdir/skyped"
+fi
+echo
Index: protocols/skype/skype.c
===================================================================
--- protocols/skype/skype.c	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
+++ protocols/skype/skype.c	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
@@ -0,0 +1,1562 @@
+/*
+ *  skype.c - Skype plugin for BitlBee
+ *
+ *  Copyright (c) 2007, 2008, 2009, 2010, 2011 by Miklos Vajna <vmiklos@frugalware.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ *  USA.
+ */
+
+#define _XOPEN_SOURCE
+#define _BSD_SOURCE
+#include <poll.h>
+#include <bitlbee.h>
+#include <ssl_client.h>
+
+#define SKYPE_DEFAULT_SERVER "localhost"
+#define SKYPE_DEFAULT_PORT "2727"
+#define IRC_LINE_SIZE 1024
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+
+/*
+ * Enumerations
+ */
+
+enum {
+	SKYPE_CALL_RINGING = 1,
+	SKYPE_CALL_MISSED,
+	SKYPE_CALL_CANCELLED,
+	SKYPE_CALL_FINISHED,
+	SKYPE_CALL_REFUSED
+};
+
+enum {
+	SKYPE_FILETRANSFER_NEW = 1,
+	SKYPE_FILETRANSFER_FAILED
+};
+
+/*
+ * Structures
+ */
+
+struct skype_data {
+	struct im_connection *ic;
+	char *username;
+	/* The effective file descriptor. We store it here so any function can
+	 * write() to it. */
+	int fd;
+	/* File descriptor returned by bitlbee. we store it so we know when
+	 * we're connected and when we aren't. */
+	int bfd;
+	/* ssl_getfd() uses this to get the file desciptor. */
+	void *ssl;
+	/* When we receive a new message id, we query the properties, finally
+	 * the chatname. Store the properties here so that we can use
+	 * imcb_buddy_msg() when we got the chatname. */
+	char *handle;
+	/* List, because of multiline messages. */
+	GList *body;
+	char *type;
+	/* This is necessary because we send a notification when we get the
+	 * handle. So we store the state here and then we can send a
+	 * notification about the handle is in a given status. */
+	int call_status;
+	char *call_id;
+	char *call_duration;
+	/* If the call is outgoing or not */
+	int call_out;
+	/* Same for file transfers. */
+	int filetransfer_status;
+	/* Using /j #nick we want to have a groupchat with two people. Usually
+	 * not (default). */
+	char *groupchat_with;
+	/* The user who invited us to the chat. */
+	char *adder;
+	/* If we are waiting for a confirmation about we changed the topic. */
+	int topic_wait;
+	/* These are used by the info command. */
+	char *info_fullname;
+	char *info_phonehome;
+	char *info_phoneoffice;
+	char *info_phonemobile;
+	char *info_nrbuddies;
+	char *info_tz;
+	char *info_seen;
+	char *info_birthday;
+	char *info_sex;
+	char *info_language;
+	char *info_country;
+	char *info_province;
+	char *info_city;
+	char *info_homepage;
+	char *info_about;
+	/* When a call fails, we get the reason and later we get the failure
+	 * event, so store the failure code here till then */
+	int failurereason;
+	/* If this is just an update of an already received message. */
+	int is_edit;
+	/* List of struct skype_group* */
+	GList *groups;
+	/* Pending user which has to be added to the next group which is
+	 * created. */
+	char *pending_user;
+};
+
+struct skype_away_state {
+	char *code;
+	char *full_name;
+};
+
+struct skype_buddy_ask_data {
+	struct im_connection *ic;
+	/* This is also used for call IDs for simplicity */
+	char *handle;
+};
+
+struct skype_group {
+	int id;
+	char *name;
+	GList *users;
+};
+
+/*
+ * Tables
+ */
+
+const struct skype_away_state skype_away_state_list[] = {
+	{ "AWAY", "Away" },
+	{ "NA", "Not available" },
+	{ "DND", "Do Not Disturb" },
+	{ "INVISIBLE", "Invisible" },
+	{ "OFFLINE", "Offline" },
+	{ "SKYPEME", "Skype Me" },
+	{ "ONLINE", "Online" },
+	{ NULL, NULL}
+};
+
+/*
+ * Functions
+ */
+
+int skype_write(struct im_connection *ic, char *buf, int len)
+{
+	struct skype_data *sd = ic->proto_data;
+	struct pollfd pfd[1];
+
+	if (!sd->ssl)
+		return FALSE;
+
+	pfd[0].fd = sd->fd;
+	pfd[0].events = POLLOUT;
+
+	/* This poll is necessary or we'll get a SIGPIPE when we write() to
+	 * sd->fd. */
+	poll(pfd, 1, 1000);
+	if (pfd[0].revents & POLLHUP) {
+		imc_logout(ic, TRUE);
+		return FALSE;
+	}
+	ssl_write(sd->ssl, buf, len);
+
+	return TRUE;
+}
+
+int skype_printf(struct im_connection *ic, char *fmt, ...)
+{
+	va_list args;
+	char str[IRC_LINE_SIZE];
+
+	va_start(args, fmt);
+	vsnprintf(str, IRC_LINE_SIZE, fmt, args);
+	va_end(args);
+
+	return skype_write(ic, str, strlen(str));
+}
+
+static void skype_buddy_ask_yes(void *data)
+{
+	struct skype_buddy_ask_data *bla = data;
+	skype_printf(bla->ic, "SET USER %s ISAUTHORIZED TRUE",
+		bla->handle);
+	g_free(bla->handle);
+	g_free(bla);
+}
+
+static void skype_buddy_ask_no(void *data)
+{
+	struct skype_buddy_ask_data *bla = data;
+	skype_printf(bla->ic, "SET USER %s ISAUTHORIZED FALSE",
+		bla->handle);
+	g_free(bla->handle);
+	g_free(bla);
+}
+
+void skype_buddy_ask(struct im_connection *ic, char *handle, char *message)
+{
+	struct skype_buddy_ask_data *bla = g_new0(struct skype_buddy_ask_data,
+		1);
+	char *buf;
+
+	bla->ic = ic;
+	bla->handle = g_strdup(handle);
+
+	buf = g_strdup_printf("The user %s wants to add you to "
+		"his/her buddy list, saying: '%s'.", handle, message);
+	imcb_ask(ic, buf, bla, skype_buddy_ask_yes, skype_buddy_ask_no);
+	g_free(buf);
+}
+
+static void skype_call_ask_yes(void *data)
+{
+	struct skype_buddy_ask_data *bla = data;
+	skype_printf(bla->ic, "SET CALL %s STATUS INPROGRESS",
+		bla->handle);
+	g_free(bla->handle);
+	g_free(bla);
+}
+
+static void skype_call_ask_no(void *data)
+{
+	struct skype_buddy_ask_data *bla = data;
+	skype_printf(bla->ic, "SET CALL %s STATUS FINISHED",
+		bla->handle);
+	g_free(bla->handle);
+	g_free(bla);
+}
+
+void skype_call_ask(struct im_connection *ic, char *call_id, char *message)
+{
+	struct skype_buddy_ask_data *bla = g_new0(struct skype_buddy_ask_data,
+		1);
+
+	bla->ic = ic;
+	bla->handle = g_strdup(call_id);
+
+	imcb_ask(ic, message, bla, skype_call_ask_yes, skype_call_ask_no);
+}
+
+static char *skype_call_strerror(int err)
+{
+	switch (err) {
+	case 1:
+		return "Miscellaneous error";
+	case 2:
+		return "User or phone number does not exist.";
+	case 3:
+		return "User is offline";
+	case 4:
+		return "No proxy found";
+	case 5:
+		return "Session terminated.";
+	case 6:
+		return "No common codec found.";
+	case 7:
+		return "Sound I/O error.";
+	case 8:
+		return "Problem with remote sound device.";
+	case 9:
+		return "Call blocked by recipient.";
+	case 10:
+		return "Recipient not a friend.";
+	case 11:
+		return "Current user not authorized by recipient.";
+	case 12:
+		return "Sound recording error.";
+	default:
+		return "Unknown error";
+	}
+}
+
+static char *skype_group_by_username(struct im_connection *ic, char *username)
+{
+	struct skype_data *sd = ic->proto_data;
+	int i, j;
+
+	/* NEEDSWORK: we just search for the first group of the user, multiple
+	 * groups / user is not yet supported by BitlBee. */
+
+	for (i = 0; i < g_list_length(sd->groups); i++) {
+		struct skype_group *sg = g_list_nth_data(sd->groups, i);
+		for (j = 0; j < g_list_length(sg->users); j++) {
+			if (!strcmp(g_list_nth_data(sg->users, j), username))
+				return sg->name;
+		}
+	}
+	return NULL;
+}
+
+static struct skype_group *skype_group_by_name(struct im_connection *ic, char *name)
+{
+	struct skype_data *sd = ic->proto_data;
+	int i;
+
+	for (i = 0; i < g_list_length(sd->groups); i++) {
+		struct skype_group *sg = g_list_nth_data(sd->groups, i);
+		if (!strcmp(sg->name, name))
+			return sg;
+	}
+	return NULL;
+}
+
+static void skype_parse_users(struct im_connection *ic, char *line)
+{
+	char **i, **nicks;
+
+	nicks = g_strsplit(line + 6, ", ", 0);
+	for (i = nicks; *i; i++)
+		skype_printf(ic, "GET USER %s ONLINESTATUS\n", *i);
+	g_strfreev(nicks);
+}
+
+static void skype_parse_user(struct im_connection *ic, char *line)
+{
+	int flags = 0;
+	char *ptr;
+	struct skype_data *sd = ic->proto_data;
+	char *user = strchr(line, ' ');
+	char *status = strrchr(line, ' ');
+
+	status++;
+	ptr = strchr(++user, ' ');
+	if (!ptr)
+		return;
+	*ptr = '\0';
+	ptr++;
+	if (!strncmp(ptr, "ONLINESTATUS ", 13)) {
+			if (!strcmp(user, sd->username))
+				return;
+			if (!set_getbool(&ic->acc->set, "test_join")
+				&& !strcmp(user, "echo123"))
+				return;
+		ptr = g_strdup_printf("%s@skype.com", user);
+		imcb_add_buddy(ic, ptr, skype_group_by_username(ic, user));
+		if (strcmp(status, "OFFLINE") && (strcmp(status, "SKYPEOUT") ||
+			!set_getbool(&ic->acc->set, "skypeout_offline")))
+			flags |= OPT_LOGGED_IN;
+		if (strcmp(status, "ONLINE") && strcmp(status, "SKYPEME"))
+			flags |= OPT_AWAY;
+		imcb_buddy_status(ic, ptr, flags, NULL, NULL);
+		g_free(ptr);
+	} else if (!strncmp(ptr, "RECEIVEDAUTHREQUEST ", 20)) {
+		char *message = ptr + 20;
+		if (strlen(message))
+			skype_buddy_ask(ic, user, message);
+	} else if (!strncmp(ptr, "BUDDYSTATUS ", 12)) {
+		char *st = ptr + 12;
+		if (!strcmp(st, "3")) {
+			char *buf = g_strdup_printf("%s@skype.com", user);
+			imcb_add_buddy(ic, buf, skype_group_by_username(ic, user));
+			g_free(buf);
+		}
+	} else if (!strncmp(ptr, "MOOD_TEXT ", 10)) {
+		char *buf = g_strdup_printf("%s@skype.com", user);
+		bee_user_t *bu = bee_user_by_handle(ic->bee, ic, buf);
+		g_free(buf);
+		buf = ptr + 10;
+		if (bu)
+			imcb_buddy_status(ic, bu->handle, bu->flags, NULL,
+					*buf ? buf : NULL);
+		if (set_getbool(&ic->acc->set, "show_moods"))
+			imcb_log(ic, "User `%s' changed mood text to `%s'", user, buf);
+	} else if (!strncmp(ptr, "FULLNAME ", 9))
+		sd->info_fullname = g_strdup(ptr + 9);
+	else if (!strncmp(ptr, "PHONE_HOME ", 11))
+		sd->info_phonehome = g_strdup(ptr + 11);
+	else if (!strncmp(ptr, "PHONE_OFFICE ", 13))
+		sd->info_phoneoffice = g_strdup(ptr + 13);
+	else if (!strncmp(ptr, "PHONE_MOBILE ", 13))
+		sd->info_phonemobile = g_strdup(ptr + 13);
+	else if (!strncmp(ptr, "NROF_AUTHED_BUDDIES ", 20))
+		sd->info_nrbuddies = g_strdup(ptr + 20);
+	else if (!strncmp(ptr, "TIMEZONE ", 9))
+		sd->info_tz = g_strdup(ptr + 9);
+	else if (!strncmp(ptr, "LASTONLINETIMESTAMP ", 20))
+		sd->info_seen = g_strdup(ptr + 20);
+	else if (!strncmp(ptr, "BIRTHDAY ", 9))
+		sd->info_birthday = g_strdup(ptr + 9);
+	else if (!strncmp(ptr, "SEX ", 4))
+		sd->info_sex = g_strdup(ptr + 4);
+	else if (!strncmp(ptr, "LANGUAGE ", 9))
+		sd->info_language = g_strdup(ptr + 9);
+	else if (!strncmp(ptr, "COUNTRY ", 8))
+		sd->info_country = g_strdup(ptr + 8);
+	else if (!strncmp(ptr, "PROVINCE ", 9))
+		sd->info_province = g_strdup(ptr + 9);
+	else if (!strncmp(ptr, "CITY ", 5))
+		sd->info_city = g_strdup(ptr + 5);
+	else if (!strncmp(ptr, "HOMEPAGE ", 9))
+		sd->info_homepage = g_strdup(ptr + 9);
+	else if (!strncmp(ptr, "ABOUT ", 6)) {
+		sd->info_about = g_strdup(ptr + 6);
+
+		GString *st = g_string_new("Contact Information\n");
+		g_string_append_printf(st, "Skype Name: %s\n", user);
+		if (sd->info_fullname) {
+			if (strlen(sd->info_fullname))
+				g_string_append_printf(st, "Full Name: %s\n",
+					sd->info_fullname);
+			g_free(sd->info_fullname);
+		}
+		if (sd->info_phonehome) {
+			if (strlen(sd->info_phonehome))
+				g_string_append_printf(st, "Home Phone: %s\n",
+					sd->info_phonehome);
+			g_free(sd->info_phonehome);
+		}
+		if (sd->info_phoneoffice) {
+			if (strlen(sd->info_phoneoffice))
+				g_string_append_printf(st, "Office Phone: %s\n",
+					sd->info_phoneoffice);
+			g_free(sd->info_phoneoffice);
+		}
+		if (sd->info_phonemobile) {
+			if (strlen(sd->info_phonemobile))
+				g_string_append_printf(st, "Mobile Phone: %s\n",
+					sd->info_phonemobile);
+			g_free(sd->info_phonemobile);
+		}
+		g_string_append_printf(st, "Personal Information\n");
+		if (sd->info_nrbuddies) {
+			if (strlen(sd->info_nrbuddies))
+				g_string_append_printf(st,
+					"Contacts: %s\n", sd->info_nrbuddies);
+			g_free(sd->info_nrbuddies);
+		}
+		if (sd->info_tz) {
+			if (strlen(sd->info_tz)) {
+				char ib[256];
+				time_t t = time(NULL);
+				t += atoi(sd->info_tz)-(60*60*24);
+				struct tm *gt = gmtime(&t);
+				strftime(ib, 256, "%H:%M:%S", gt);
+				g_string_append_printf(st,
+					"Local Time: %s\n", ib);
+			}
+			g_free(sd->info_tz);
+		}
+		if (sd->info_seen) {
+			if (strlen(sd->info_seen)) {
+				char ib[256];
+				time_t it = atoi(sd->info_seen);
+				struct tm *tm = localtime(&it);
+				strftime(ib, 256, ("%Y. %m. %d. %H:%M"), tm);
+				g_string_append_printf(st,
+					"Last Seen: %s\n", ib);
+			}
+			g_free(sd->info_seen);
+		}
+		if (sd->info_birthday) {
+			if (strlen(sd->info_birthday) &&
+				strcmp(sd->info_birthday, "0")) {
+				char ib[256];
+				struct tm tm;
+				strptime(sd->info_birthday, "%Y%m%d", &tm);
+				strftime(ib, 256, "%B %d, %Y", &tm);
+				g_string_append_printf(st,
+					"Birthday: %s\n", ib);
+
+				strftime(ib, 256, "%Y", &tm);
+				int year = atoi(ib);
+				time_t t = time(NULL);
+				struct tm *lt = localtime(&t);
+				g_string_append_printf(st,
+					"Age: %d\n", lt->tm_year+1900-year);
+			}
+			g_free(sd->info_birthday);
+		}
+		if (sd->info_sex) {
+			if (strlen(sd->info_sex)) {
+				char *iptr = sd->info_sex;
+				while (*iptr++)
+					*iptr = tolower(*iptr);
+				g_string_append_printf(st,
+					"Gender: %s\n", sd->info_sex);
+			}
+			g_free(sd->info_sex);
+		}
+		if (sd->info_language) {
+			if (strlen(sd->info_language)) {
+				char *iptr = strchr(sd->info_language, ' ');
+				if (iptr)
+					iptr++;
+				else
+					iptr = sd->info_language;
+				g_string_append_printf(st,
+					"Language: %s\n", iptr);
+			}
+			g_free(sd->info_language);
+		}
+		if (sd->info_country) {
+			if (strlen(sd->info_country)) {
+				char *iptr = strchr(sd->info_country, ' ');
+				if (iptr)
+					iptr++;
+				else
+					iptr = sd->info_country;
+				g_string_append_printf(st,
+					"Country: %s\n", iptr);
+			}
+			g_free(sd->info_country);
+		}
+		if (sd->info_province) {
+			if (strlen(sd->info_province))
+				g_string_append_printf(st,
+					"Region: %s\n", sd->info_province);
+			g_free(sd->info_province);
+		}
+		if (sd->info_city) {
+			if (strlen(sd->info_city))
+				g_string_append_printf(st,
+					"City: %s\n", sd->info_city);
+			g_free(sd->info_city);
+		}
+		if (sd->info_homepage) {
+			if (strlen(sd->info_homepage))
+				g_string_append_printf(st,
+					"Homepage: %s\n", sd->info_homepage);
+			g_free(sd->info_homepage);
+		}
+		if (sd->info_about) {
+			if (strlen(sd->info_about))
+				g_string_append_printf(st, "%s\n",
+					sd->info_about);
+			g_free(sd->info_about);
+		}
+		imcb_log(ic, "%s", st->str);
+		g_string_free(st, TRUE);
+	}
+}
+
+static void skype_parse_chatmessage(struct im_connection *ic, char *line)
+{
+	struct skype_data *sd = ic->proto_data;
+	char buf[IRC_LINE_SIZE];
+	char *id = strchr(line, ' ');
+
+	if (!++id)
+		return;
+	char *info = strchr(id, ' ');
+
+	if (!info)
+		return;
+	*info = '\0';
+	info++;
+	if (!strcmp(info, "STATUS RECEIVED") || !strncmp(info, "EDITED_TIMESTAMP", 16)) {
+		/* New message ID:
+		 * (1) Request its from field
+		 * (2) Request its body
+		 * (3) Request its type
+		 * (4) Query chatname
+		 */
+		skype_printf(ic, "GET CHATMESSAGE %s FROM_HANDLE\n", id);
+		if (!strcmp(info, "STATUS RECEIVED"))
+			skype_printf(ic, "GET CHATMESSAGE %s BODY\n", id);
+		else
+			sd->is_edit = 1;
+		skype_printf(ic, "GET CHATMESSAGE %s TYPE\n", id);
+		skype_printf(ic, "GET CHATMESSAGE %s CHATNAME\n", id);
+	} else if (!strncmp(info, "FROM_HANDLE ", 12)) {
+		info += 12;
+		/* New from field value. Store
+		 * it, then we can later use it
+		 * when we got the message's
+		 * body. */
+		g_free(sd->handle);
+		sd->handle = g_strdup_printf("%s@skype.com", info);
+	} else if (!strncmp(info, "EDITED_BY ", 10)) {
+		info += 10;
+		/* This is the same as
+		 * FROM_HANDLE, except that we
+		 * never request these lines
+		 * from Skype, we just get
+		 * them. */
+		g_free(sd->handle);
+		sd->handle = g_strdup_printf("%s@skype.com", info);
+	} else if (!strncmp(info, "BODY ", 5)) {
+		info += 5;
+		sd->body = g_list_append(sd->body, g_strdup(info));
+	}	else if (!strncmp(info, "TYPE ", 5)) {
+		info += 5;
+		g_free(sd->type);
+		sd->type = g_strdup(info);
+	} else if (!strncmp(info, "CHATNAME ", 9)) {
+		info += 9;
+		if (sd->handle && sd->body && sd->type) {
+			struct groupchat *gc = bee_chat_by_title(ic->bee, ic, info);
+			int i;
+			for (i = 0; i < g_list_length(sd->body); i++) {
+				char *body = g_list_nth_data(sd->body, i);
+				if (!strcmp(sd->type, "SAID") ||
+					!strcmp(sd->type, "EMOTED")) {
+					if (!strcmp(sd->type, "SAID")) {
+						if (!sd->is_edit)
+							g_snprintf(buf, IRC_LINE_SIZE, "%s",
+								body);
+						else {
+							g_snprintf(buf, IRC_LINE_SIZE, "%s %s",
+								set_getstr(&ic->acc->set, "edit_prefix"),
+								body);
+							sd->is_edit = 0;
+						}
+					} else
+						g_snprintf(buf, IRC_LINE_SIZE, "/me %s",
+							body);
+					if (!gc)
+						/* Private message */
+						imcb_buddy_msg(ic,
+							sd->handle, buf, 0, 0);
+					else
+						/* Groupchat message */
+						imcb_chat_msg(gc,
+							sd->handle, buf, 0, 0);
+				} else if (!strcmp(sd->type, "SETTOPIC") && gc)
+					imcb_chat_topic(gc,
+						sd->handle, body, 0);
+				else if (!strcmp(sd->type, "LEFT") && gc)
+					imcb_chat_remove_buddy(gc,
+						sd->handle, NULL);
+			}
+			g_list_free(sd->body);
+			sd->body = NULL;
+		}
+	}
+}
+
+static void skype_parse_call(struct im_connection *ic, char *line)
+{
+	struct skype_data *sd = ic->proto_data;
+	char *id = strchr(line, ' ');
+	char buf[IRC_LINE_SIZE];
+
+	if (!++id)
+		return;
+	char *info = strchr(id, ' ');
+
+	if (!info)
+		return;
+	*info = '\0';
+	info++;
+	if (!strncmp(info, "FAILUREREASON ", 14))
+		sd->failurereason = atoi(strchr(info, ' '));
+	else if (!strcmp(info, "STATUS RINGING")) {
+		if (sd->call_id)
+			g_free(sd->call_id);
+		sd->call_id = g_strdup(id);
+		skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id);
+		sd->call_status = SKYPE_CALL_RINGING;
+	} else if (!strcmp(info, "STATUS MISSED")) {
+		skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id);
+		sd->call_status = SKYPE_CALL_MISSED;
+	} else if (!strcmp(info, "STATUS CANCELLED")) {
+		skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id);
+		sd->call_status = SKYPE_CALL_CANCELLED;
+	} else if (!strcmp(info, "STATUS FINISHED")) {
+		skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id);
+		sd->call_status = SKYPE_CALL_FINISHED;
+	} else if (!strcmp(info, "STATUS REFUSED")) {
+		skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id);
+		sd->call_status = SKYPE_CALL_REFUSED;
+	} else if (!strcmp(info, "STATUS UNPLACED")) {
+		if (sd->call_id)
+			g_free(sd->call_id);
+		/* Save the ID for later usage (Cancel/Finish). */
+		sd->call_id = g_strdup(id);
+		sd->call_out = TRUE;
+	} else if (!strcmp(info, "STATUS FAILED")) {
+		imcb_error(ic, "Call failed: %s",
+			skype_call_strerror(sd->failurereason));
+		sd->call_id = NULL;
+	} else if (!strncmp(info, "DURATION ", 9)) {
+		if (sd->call_duration)
+			g_free(sd->call_duration);
+		sd->call_duration = g_strdup(info+9);
+	} else if (!strncmp(info, "PARTNER_HANDLE ", 15)) {
+		info += 15;
+		if (!sd->call_status)
+			return;
+		switch (sd->call_status) {
+		case SKYPE_CALL_RINGING:
+			if (sd->call_out)
+				imcb_log(ic, "You are currently ringing "
+					"the user %s.", info);
+			else {
+				g_snprintf(buf, IRC_LINE_SIZE,
+					"The user %s is currently ringing you.",
+					info);
+				skype_call_ask(ic, sd->call_id, buf);
+			}
+			break;
+		case SKYPE_CALL_MISSED:
+			imcb_log(ic, "You have missed a call from user %s.",
+				info);
+			break;
+		case SKYPE_CALL_CANCELLED:
+			imcb_log(ic, "You cancelled the call to the user %s.",
+				info);
+			sd->call_status = 0;
+			sd->call_out = FALSE;
+			break;
+		case SKYPE_CALL_REFUSED:
+			if (sd->call_out)
+				imcb_log(ic, "The user %s refused the call.",
+					info);
+			else
+				imcb_log(ic,
+					"You refused the call from user %s.",
+					info);
+			sd->call_out = FALSE;
+			break;
+		case SKYPE_CALL_FINISHED:
+			if (sd->call_duration)
+				imcb_log(ic,
+					"You finished the call to the user %s "
+					"(duration: %s seconds).",
+					info, sd->call_duration);
+			else
+				imcb_log(ic,
+					"You finished the call to the user %s.",
+					info);
+			sd->call_out = FALSE;
+			break;
+		default:
+			/* Don't be noisy, ignore other statuses for now. */
+			break;
+		}
+		sd->call_status = 0;
+	}
+}
+
+static void skype_parse_filetransfer(struct im_connection *ic, char *line)
+{
+	struct skype_data *sd = ic->proto_data;
+	char *id = strchr(line, ' ');
+
+	if (!++id)
+		return;
+	char *info = strchr(id, ' ');
+
+	if (!info)
+		return;
+	*info = '\0';
+	info++;
+	if (!strcmp(info, "STATUS NEW")) {
+		skype_printf(ic, "GET FILETRANSFER %s PARTNER_HANDLE\n",
+			id);
+		sd->filetransfer_status = SKYPE_FILETRANSFER_NEW;
+	} else if (!strcmp(info, "STATUS FAILED")) {
+		skype_printf(ic, "GET FILETRANSFER %s PARTNER_HANDLE\n",
+			id);
+		sd->filetransfer_status = SKYPE_FILETRANSFER_FAILED;
+	} else if (!strncmp(info, "PARTNER_HANDLE ", 15)) {
+		info += 15;
+		if (!sd->filetransfer_status)
+			return;
+		switch (sd->filetransfer_status) {
+		case SKYPE_FILETRANSFER_NEW:
+			imcb_log(ic, "The user %s offered a new file for you.",
+				info);
+			break;
+		case SKYPE_FILETRANSFER_FAILED:
+			imcb_log(ic, "Failed to transfer file from user %s.",
+				info);
+			break;
+		}
+		sd->filetransfer_status = 0;
+	}
+}
+
+static struct skype_group *skype_group_by_id(struct im_connection *ic, int id)
+{
+	struct skype_data *sd = ic->proto_data;
+	int i;
+
+	for (i = 0; i < g_list_length(sd->groups); i++) {
+		struct skype_group *sg = (struct skype_group *)g_list_nth_data(sd->groups, i);
+
+		if (sg->id == id)
+			return sg;
+	}
+	return NULL;
+}
+
+static void skype_group_free(struct skype_group *sg, gboolean usersonly)
+{
+	int i;
+
+	for (i = 0; i < g_list_length(sg->users); i++) {
+		char *user = g_list_nth_data(sg->users, i);
+		g_free(user);
+	}
+	sg->users = NULL;
+	if (usersonly)
+		return;
+	g_free(sg->name);
+	g_free(sg);
+}
+
+/* Update the group of each user in this group */
+static void skype_group_users(struct im_connection *ic, struct skype_group *sg)
+{
+	int i;
+
+	for (i = 0; i < g_list_length(sg->users); i++) {
+		char *user = g_list_nth_data(sg->users, i);
+		char *buf = g_strdup_printf("%s@skype.com", user);
+		imcb_add_buddy(ic, buf, sg->name);
+		g_free(buf);
+	}
+}
+
+static void skype_parse_group(struct im_connection *ic, char *line)
+{
+	struct skype_data *sd = ic->proto_data;
+	char *id = strchr(line, ' ');
+
+	if (!++id)
+		return;
+
+	char *info = strchr(id, ' ');
+
+	if (!info)
+		return;
+	*info = '\0';
+	info++;
+
+	if (!strncmp(info, "DISPLAYNAME ", 12)) {
+		info += 12;
+
+		/* Name given for a group ID: try to update it or insert a new
+		 * one if not found */
+		struct skype_group *sg = skype_group_by_id(ic, atoi(id));
+		if (sg) {
+			g_free(sg->name);
+			sg->name = g_strdup(info);
+		} else {
+			sg = g_new0(struct skype_group, 1);
+			sg->id = atoi(id);
+			sg->name = g_strdup(info);
+			sd->groups = g_list_append(sd->groups, sg);
+		}
+	} else if (!strncmp(info, "USERS ", 6)) {
+		struct skype_group *sg = skype_group_by_id(ic, atoi(id));
+
+		if (sg) {
+			char **i;
+			char **users = g_strsplit(info + 6, ", ", 0);
+
+			skype_group_free(sg, TRUE);
+			i = users;
+			while (*i) {
+				sg->users = g_list_append(sg->users, g_strdup(*i));
+				i++;
+			}
+			g_strfreev(users);
+			skype_group_users(ic, sg);
+		} else
+			log_message(LOGLVL_ERROR,
+				"No skype group with id %s. That's probably a bug.", id);
+	} else if (!strncmp(info, "NROFUSERS ", 10)) {
+		if (!sd->pending_user) {
+			/* Number of users changed in this group, query its type to see
+			 * if it's a custom one we should care about. */
+			skype_printf(ic, "GET GROUP %s TYPE", id);
+			return;
+		}
+
+		/* This is a newly created group, we have a single user
+		 * to add. */
+		struct skype_group *sg = skype_group_by_id(ic, atoi(id));
+
+		if (sg) {
+			skype_printf(ic, "ALTER GROUP %d ADDUSER %s", sg->id, sd->pending_user);
+			g_free(sd->pending_user);
+			sd->pending_user = NULL;
+		} else
+			log_message(LOGLVL_ERROR,
+					"No skype group with id %s. That's probably a bug.", id);
+	} else if (!strcmp(info, "TYPE CUSTOM_GROUP"))
+		/* This one is interesting, query its users. */
+		skype_printf(ic, "GET GROUP %s USERS", id);
+}
+
+static void skype_parse_chat(struct im_connection *ic, char *line)
+{
+	struct skype_data *sd = ic->proto_data;
+	char buf[IRC_LINE_SIZE];
+	char *id = strchr(line, ' ');
+
+	if (!++id)
+		return;
+	struct groupchat *gc;
+	char *info = strchr(id, ' ');
+
+	if (!info)
+		return;
+	*info = '\0';
+	info++;
+	/* Remove fake chat if we created one in skype_chat_with() */
+	gc = bee_chat_by_title(ic->bee, ic, "");
+	if (gc)
+		imcb_chat_free(gc);
+	if (!strcmp(info, "STATUS MULTI_SUBSCRIBED")) {
+		gc = imcb_chat_new(ic, id);
+		imcb_chat_name_hint(gc, id);
+		skype_printf(ic, "GET CHAT %s ADDER\n", id);
+		skype_printf(ic, "GET CHAT %s TOPIC\n", id);
+	} else if (!strcmp(info, "STATUS DIALOG") && sd->groupchat_with) {
+		gc = imcb_chat_new(ic, id);
+		imcb_chat_name_hint(gc, id);
+		/* According to the docs this
+		 * is necessary. However it
+		 * does not seem the situation
+		 * and it would open an extra
+		 * window on our client, so
+		 * just leave it out. */
+		/*skype_printf(ic, "OPEN CHAT %s\n", id);*/
+		g_snprintf(buf, IRC_LINE_SIZE, "%s@skype.com",
+				sd->groupchat_with);
+		imcb_chat_add_buddy(gc, buf);
+		imcb_chat_add_buddy(gc, sd->username);
+		g_free(sd->groupchat_with);
+		sd->groupchat_with = NULL;
+		skype_printf(ic, "GET CHAT %s ADDER\n", id);
+		skype_printf(ic, "GET CHAT %s TOPIC\n", id);
+	} else if (!strcmp(info, "STATUS UNSUBSCRIBED")) {
+		gc = bee_chat_by_title(ic->bee, ic, id);
+		if (gc)
+			gc->data = (void *)FALSE;
+	} else if (!strncmp(info, "ADDER ", 6)) {
+		info += 6;
+		g_free(sd->adder);
+		sd->adder = g_strdup_printf("%s@skype.com", info);
+	} else if (!strncmp(info, "TOPIC ", 6)) {
+		info += 6;
+		gc = bee_chat_by_title(ic->bee, ic, id);
+		if (gc && (sd->adder || sd->topic_wait)) {
+			if (sd->topic_wait) {
+				sd->adder = g_strdup(sd->username);
+				sd->topic_wait = 0;
+			}
+			imcb_chat_topic(gc, sd->adder, info, 0);
+			g_free(sd->adder);
+			sd->adder = NULL;
+		}
+	} else if (!strncmp(info, "ACTIVEMEMBERS ", 14)) {
+		info += 14;
+		gc = bee_chat_by_title(ic->bee, ic, id);
+		/* Hack! We set ->data to TRUE
+		 * while we're on the channel
+		 * so that we won't rejoin
+		 * after a /part. */
+		if (!gc || gc->data)
+			return;
+		char **members = g_strsplit(info, " ", 0);
+		int i;
+		for (i = 0; members[i]; i++) {
+			if (!strcmp(members[i], sd->username))
+				continue;
+			g_snprintf(buf, IRC_LINE_SIZE, "%s@skype.com",
+					members[i]);
+			if (!g_list_find_custom(gc->in_room, buf,
+				(GCompareFunc)strcmp))
+				imcb_chat_add_buddy(gc, buf);
+		}
+		imcb_chat_add_buddy(gc, sd->username);
+		g_strfreev(members);
+	}
+}
+
+static void skype_parse_password(struct im_connection *ic, char *line)
+{
+	if (!strncmp(line+9, "OK", 2))
+		imcb_connected(ic);
+	else {
+		imcb_error(ic, "Authentication Failed");
+		imc_logout(ic, TRUE);
+	}
+}
+
+static void skype_parse_profile(struct im_connection *ic, char *line)
+{
+	imcb_log(ic, "SkypeOut balance value is '%s'.", line+21);
+}
+
+static void skype_parse_ping(struct im_connection *ic, char *line)
+{
+	/* Unused parameter */
+	line = line;
+	skype_printf(ic, "PONG\n");
+}
+
+static void skype_parse_chats(struct im_connection *ic, char *line)
+{
+	char **i;
+	char **chats = g_strsplit(line + 6, ", ", 0);
+
+	i = chats;
+	while (*i) {
+		skype_printf(ic, "GET CHAT %s STATUS\n", *i);
+		skype_printf(ic, "GET CHAT %s ACTIVEMEMBERS\n", *i);
+		i++;
+	}
+	g_strfreev(chats);
+}
+
+static void skype_parse_groups(struct im_connection *ic, char *line)
+{
+	char **i;
+	char **groups = g_strsplit(line + 7, ", ", 0);
+
+	i = groups;
+	while (*i) {
+		skype_printf(ic, "GET GROUP %s DISPLAYNAME\n", *i);
+		skype_printf(ic, "GET GROUP %s USERS\n", *i);
+		i++;
+	}
+	g_strfreev(groups);
+}
+
+static void skype_parse_alter_group(struct im_connection *ic, char *line)
+{
+	char *id = line + strlen("ALTER GROUP");
+
+	if (!++id)
+		return;
+
+	char *info = strchr(id, ' ');
+
+	if (!info)
+		return;
+	*info = '\0';
+	info++;
+
+	if (!strncmp(info, "ADDUSER ", 8)) {
+		struct skype_group *sg = skype_group_by_id(ic, atoi(id));
+
+		info += 8;
+		if (sg) {
+			char *buf = g_strdup_printf("%s@skype.com", info);
+			sg->users = g_list_append(sg->users, g_strdup(info));
+			imcb_add_buddy(ic, buf, sg->name);
+			g_free(buf);
+		} else
+			log_message(LOGLVL_ERROR,
+				"No skype group with id %s. That's probably a bug.", id);
+	}
+}
+
+typedef void (*skype_parser)(struct im_connection *ic, char *line);
+
+static gboolean skype_read_callback(gpointer data, gint fd,
+				    b_input_condition cond)
+{
+	struct im_connection *ic = data;
+	struct skype_data *sd = ic->proto_data;
+	char buf[IRC_LINE_SIZE];
+	int st, i;
+	char **lines, **lineptr, *line;
+	static struct parse_map {
+		char *k;
+		skype_parser v;
+	} parsers[] = {
+		{ "USERS ", skype_parse_users },
+		{ "USER ", skype_parse_user },
+		{ "CHATMESSAGE ", skype_parse_chatmessage },
+		{ "CALL ", skype_parse_call },
+		{ "FILETRANSFER ", skype_parse_filetransfer },
+		{ "CHAT ", skype_parse_chat },
+		{ "GROUP ", skype_parse_group },
+		{ "PASSWORD ", skype_parse_password },
+		{ "PROFILE PSTN_BALANCE ", skype_parse_profile },
+		{ "PING", skype_parse_ping },
+		{ "CHATS ", skype_parse_chats },
+		{ "GROUPS ", skype_parse_groups },
+		{ "ALTER GROUP ", skype_parse_alter_group },
+	};
+
+	/* Unused parameters */
+	fd = fd;
+	cond = cond;
+
+	if (!sd || sd->fd == -1)
+		return FALSE;
+	/* Read the whole data. */
+	st = ssl_read(sd->ssl, buf, sizeof(buf));
+	if (st > 0) {
+		buf[st] = '\0';
+		/* Then split it up to lines. */
+		lines = g_strsplit(buf, "\n", 0);
+		lineptr = lines;
+		while ((line = *lineptr)) {
+			if (!strlen(line))
+				break;
+			if (set_getbool(&ic->acc->set, "skypeconsole_receive"))
+				imcb_buddy_msg(ic, "skypeconsole", line, 0, 0);
+			for (i = 0; i < ARRAY_SIZE(parsers); i++)
+				if (!strncmp(line, parsers[i].k,
+					strlen(parsers[i].k))) {
+					parsers[i].v(ic, line);
+					break;
+				}
+			lineptr++;
+		}
+		g_strfreev(lines);
+	} else if (st == 0 || (st < 0 && !sockerr_again())) {
+		closesocket(sd->fd);
+		sd->fd = -1;
+
+		imcb_error(ic, "Error while reading from server");
+		imc_logout(ic, TRUE);
+		return FALSE;
+	}
+	return TRUE;
+}
+
+gboolean skype_start_stream(struct im_connection *ic)
+{
+	struct skype_data *sd = ic->proto_data;
+	int st;
+
+	if (!sd)
+		return FALSE;
+
+	if (sd->bfd <= 0)
+		sd->bfd = b_input_add(sd->fd, B_EV_IO_READ,
+			skype_read_callback, ic);
+
+	/* Log in */
+	skype_printf(ic, "USERNAME %s\n", ic->acc->user);
+	skype_printf(ic, "PASSWORD %s\n", ic->acc->pass);
+
+	/* This will download all buddies and groups. */
+	st = skype_printf(ic, "SEARCH GROUPS CUSTOM\n");
+	skype_printf(ic, "SEARCH FRIENDS\n");
+
+	skype_printf(ic, "SET USERSTATUS ONLINE\n");
+
+	/* Auto join to bookmarked chats if requested.*/
+	if (set_getbool(&ic->acc->set, "auto_join"))
+		skype_printf(ic, "SEARCH BOOKMARKEDCHATS\n");
+	return st;
+}
+
+gboolean skype_connected(gpointer data, void *source, b_input_condition cond)
+{
+	struct im_connection *ic = data;
+	struct skype_data *sd = ic->proto_data;
+
+	/* Unused parameter */
+	cond = cond;
+
+	if (!source) {
+		sd->ssl = NULL;
+		imcb_error(ic, "Could not connect to server");
+		imc_logout(ic, TRUE);
+		return FALSE;
+	}
+	imcb_log(ic, "Connected to server, logging in");
+
+	return skype_start_stream(ic);
+}
+
+static void skype_login(account_t *acc)
+{
+	struct im_connection *ic = imcb_new(acc);
+	struct skype_data *sd = g_new0(struct skype_data, 1);
+
+	ic->proto_data = sd;
+
+	imcb_log(ic, "Connecting");
+	sd->ssl = ssl_connect(set_getstr(&acc->set, "server"),
+		set_getint(&acc->set, "port"), skype_connected, ic);
+	sd->fd = sd->ssl ? ssl_getfd(sd->ssl) : -1;
+	sd->username = g_strdup(acc->user);
+
+	sd->ic = ic;
+
+	if (set_getbool(&acc->set, "skypeconsole"))
+		imcb_add_buddy(ic, "skypeconsole", NULL);
+}
+
+static void skype_logout(struct im_connection *ic)
+{
+	struct skype_data *sd = ic->proto_data;
+	int i;
+
+	skype_printf(ic, "SET USERSTATUS OFFLINE\n");
+
+	for (i = 0; i < g_list_length(sd->groups); i++) {
+		struct skype_group *sg = (struct skype_group *)g_list_nth_data(sd->groups, i);
+		skype_group_free(sg, FALSE);
+	}
+	g_free(sd->username);
+	g_free(sd->handle);
+	g_free(sd);
+	ic->proto_data = NULL;
+}
+
+static int skype_buddy_msg(struct im_connection *ic, char *who, char *message,
+			   int flags)
+{
+	char *ptr, *nick;
+	int st;
+
+	/* Unused parameter */
+	flags = flags;
+
+	nick = g_strdup(who);
+	ptr = strchr(nick, '@');
+	if (ptr)
+		*ptr = '\0';
+
+	if (!strncmp(who, "skypeconsole", 12))
+		st = skype_printf(ic, "%s\n", message);
+	else
+		st = skype_printf(ic, "MESSAGE %s %s\n", nick, message);
+	g_free(nick);
+
+	return st;
+}
+
+const struct skype_away_state *skype_away_state_by_name(char *name)
+{
+	int i;
+
+	for (i = 0; skype_away_state_list[i].full_name; i++)
+		if (g_strcasecmp(skype_away_state_list[i].full_name, name) == 0)
+			return skype_away_state_list + i;
+
+	return NULL;
+}
+
+static void skype_set_away(struct im_connection *ic, char *state_txt,
+			   char *message)
+{
+	const struct skype_away_state *state;
+
+	/* Unused parameter */
+	message = message;
+
+	if (state_txt == NULL)
+		state = skype_away_state_by_name("Online");
+	else
+		state = skype_away_state_by_name(state_txt);
+	skype_printf(ic, "SET USERSTATUS %s\n", state->code);
+}
+
+static GList *skype_away_states(struct im_connection *ic)
+{
+	static GList *l;
+	int i;
+
+	/* Unused parameter */
+	ic = ic;
+
+	if (l == NULL)
+		for (i = 0; skype_away_state_list[i].full_name; i++)
+			l = g_list_append(l,
+				(void *)skype_away_state_list[i].full_name);
+
+	return l;
+}
+
+static char *skype_set_display_name(set_t *set, char *value)
+{
+	account_t *acc = set->data;
+	struct im_connection *ic = acc->ic;
+
+	skype_printf(ic, "SET PROFILE FULLNAME %s", value);
+	return value;
+}
+
+static char *skype_set_balance(set_t *set, char *value)
+{
+	account_t *acc = set->data;
+	struct im_connection *ic = acc->ic;
+
+	skype_printf(ic, "GET PROFILE PSTN_BALANCE");
+	return value;
+}
+
+static void skype_call(struct im_connection *ic, char *value)
+{
+	char *nick = g_strdup(value);
+	char *ptr = strchr(nick, '@');
+
+	if (ptr)
+		*ptr = '\0';
+	skype_printf(ic, "CALL %s", nick);
+	g_free(nick);
+}
+
+static void skype_hangup(struct im_connection *ic)
+{
+	struct skype_data *sd = ic->proto_data;
+
+	if (sd->call_id) {
+		skype_printf(ic, "SET CALL %s STATUS FINISHED",
+				sd->call_id);
+		g_free(sd->call_id);
+		sd->call_id = 0;
+	} else
+		imcb_error(ic, "There are no active calls currently.");
+}
+
+static char *skype_set_call(set_t *set, char *value)
+{
+	account_t *acc = set->data;
+	struct im_connection *ic = acc->ic;
+
+	if (value)
+		skype_call(ic, value);
+	else
+		skype_hangup(ic);
+	return value;
+}
+
+static void skype_add_buddy(struct im_connection *ic, char *who, char *group)
+{
+	struct skype_data *sd = ic->proto_data;
+	char *nick, *ptr;
+
+	nick = g_strdup(who);
+	ptr = strchr(nick, '@');
+	if (ptr)
+		*ptr = '\0';
+
+	if (!group) {
+		skype_printf(ic, "SET USER %s BUDDYSTATUS 2 Please authorize me\n",
+				nick);
+		g_free(nick);
+	} else {
+		struct skype_group *sg = skype_group_by_name(ic, group);
+
+		if (!sg) {
+			/* No such group, we need to create it, then have to
+			 * add the user once it's created. */
+			skype_printf(ic, "CREATE GROUP %s", group);
+			sd->pending_user = g_strdup(nick);
+		} else {
+			skype_printf(ic, "ALTER GROUP %d ADDUSER %s", sg->id, nick);
+		}
+	}
+}
+
+static void skype_remove_buddy(struct im_connection *ic, char *who, char *group)
+{
+	char *nick, *ptr;
+
+	/* Unused parameter */
+	group = group;
+
+	nick = g_strdup(who);
+	ptr = strchr(nick, '@');
+	if (ptr)
+		*ptr = '\0';
+	skype_printf(ic, "SET USER %s BUDDYSTATUS 1\n", nick);
+	g_free(nick);
+}
+
+void skype_chat_msg(struct groupchat *gc, char *message, int flags)
+{
+	struct im_connection *ic = gc->ic;
+
+	/* Unused parameter */
+	flags = flags;
+
+	skype_printf(ic, "CHATMESSAGE %s %s\n", gc->title, message);
+}
+
+void skype_chat_leave(struct groupchat *gc)
+{
+	struct im_connection *ic = gc->ic;
+	skype_printf(ic, "ALTER CHAT %s LEAVE\n", gc->title);
+	gc->data = (void *)TRUE;
+}
+
+void skype_chat_invite(struct groupchat *gc, char *who, char *message)
+{
+	struct im_connection *ic = gc->ic;
+	char *ptr, *nick;
+
+	/* Unused parameter */
+	who = who;
+
+	nick = g_strdup(message);
+	ptr = strchr(nick, '@');
+	if (ptr)
+		*ptr = '\0';
+	skype_printf(ic, "ALTER CHAT %s ADDMEMBERS %s\n", gc->title, nick);
+	g_free(nick);
+}
+
+void skype_chat_topic(struct groupchat *gc, char *message)
+{
+	struct im_connection *ic = gc->ic;
+	struct skype_data *sd = ic->proto_data;
+	skype_printf(ic, "ALTER CHAT %s SETTOPIC %s\n",
+		gc->title, message);
+	sd->topic_wait = 1;
+}
+
+struct groupchat *skype_chat_with(struct im_connection *ic, char *who)
+{
+	struct skype_data *sd = ic->proto_data;
+	char *ptr, *nick;
+	nick = g_strdup(who);
+	ptr = strchr(nick, '@');
+	if (ptr)
+		*ptr = '\0';
+	skype_printf(ic, "CHAT CREATE %s\n", nick);
+	sd->groupchat_with = g_strdup(nick);
+	g_free(nick);
+	/* We create a fake chat for now. We will replace it with a real one in
+	 * the real callback. */
+	return imcb_chat_new(ic, "");
+}
+
+static void skype_get_info(struct im_connection *ic, char *who)
+{
+	char *ptr, *nick;
+	nick = g_strdup(who);
+	ptr = strchr(nick, '@');
+	if (ptr)
+		*ptr = '\0';
+	skype_printf(ic, "GET USER %s FULLNAME\n", nick);
+	skype_printf(ic, "GET USER %s PHONE_HOME\n", nick);
+	skype_printf(ic, "GET USER %s PHONE_OFFICE\n", nick);
+	skype_printf(ic, "GET USER %s PHONE_MOBILE\n", nick);
+	skype_printf(ic, "GET USER %s NROF_AUTHED_BUDDIES\n", nick);
+	skype_printf(ic, "GET USER %s TIMEZONE\n", nick);
+	skype_printf(ic, "GET USER %s LASTONLINETIMESTAMP\n", nick);
+	skype_printf(ic, "GET USER %s BIRTHDAY\n", nick);
+	skype_printf(ic, "GET USER %s SEX\n", nick);
+	skype_printf(ic, "GET USER %s LANGUAGE\n", nick);
+	skype_printf(ic, "GET USER %s COUNTRY\n", nick);
+	skype_printf(ic, "GET USER %s PROVINCE\n", nick);
+	skype_printf(ic, "GET USER %s CITY\n", nick);
+	skype_printf(ic, "GET USER %s HOMEPAGE\n", nick);
+	skype_printf(ic, "GET USER %s ABOUT\n", nick);
+}
+
+static void skype_set_my_name(struct im_connection *ic, char *info)
+{
+	skype_set_display_name(set_find(&ic->acc->set, "display_name"), info);
+}
+
+static void skype_init(account_t *acc)
+{
+	set_t *s;
+
+	s = set_add(&acc->set, "server", SKYPE_DEFAULT_SERVER, set_eval_account,
+		acc);
+	s->flags |= ACC_SET_OFFLINE_ONLY;
+
+	s = set_add(&acc->set, "port", SKYPE_DEFAULT_PORT, set_eval_int, acc);
+	s->flags |= ACC_SET_OFFLINE_ONLY;
+
+	s = set_add(&acc->set, "display_name", NULL, skype_set_display_name,
+		acc);
+	s->flags |= ACC_SET_NOSAVE | ACC_SET_ONLINE_ONLY;
+
+	s = set_add(&acc->set, "call", NULL, skype_set_call, acc);
+	s->flags |= ACC_SET_NOSAVE | ACC_SET_ONLINE_ONLY;
+
+	s = set_add(&acc->set, "balance", NULL, skype_set_balance, acc);
+	s->flags |= ACC_SET_NOSAVE | ACC_SET_ONLINE_ONLY;
+
+	s = set_add(&acc->set, "skypeout_offline", "true", set_eval_bool, acc);
+
+	s = set_add(&acc->set, "skypeconsole", "false", set_eval_bool, acc);
+	s->flags |= ACC_SET_OFFLINE_ONLY;
+
+	s = set_add(&acc->set, "skypeconsole_receive", "false", set_eval_bool,
+		acc);
+	s->flags |= ACC_SET_OFFLINE_ONLY;
+
+	s = set_add(&acc->set, "auto_join", "false", set_eval_bool, acc);
+	s->flags |= ACC_SET_OFFLINE_ONLY;
+
+	s = set_add(&acc->set, "test_join", "false", set_eval_bool, acc);
+	s->flags |= ACC_SET_OFFLINE_ONLY;
+
+	s = set_add(&acc->set, "show_moods", "false", set_eval_bool, acc);
+
+	s = set_add(&acc->set, "edit_prefix", "EDIT:",
+			NULL, acc);
+}
+
+#if BITLBEE_VERSION_CODE > BITLBEE_VER(3, 0, 1)
+GList *skype_buddy_action_list(bee_user_t *bu)
+{
+	static GList *ret;
+
+	/* Unused parameter */
+	bu = bu;
+
+	if (ret == NULL) {
+		static const struct buddy_action ba[3] = {
+			{"CALL", "Initiate a call" },
+			{"HANGUP", "Hang up a call" },
+		};
+
+		ret = g_list_prepend(ret, (void *) ba + 0);
+	}
+
+	return ret;
+}
+
+void *skype_buddy_action(struct bee_user *bu, const char *action, char * const args[], void *data)
+{
+	/* Unused parameters */
+	args = args;
+	data = data;
+
+	if (!g_strcasecmp(action, "CALL"))
+		skype_call(bu->ic, bu->handle);
+	else if (!g_strcasecmp(action, "HANGUP"))
+		skype_hangup(bu->ic);
+
+	return NULL;
+}
+#endif
+
+void init_plugin(void)
+{
+	struct prpl *ret = g_new0(struct prpl, 1);
+
+	ret->name = "skype";
+	ret->login = skype_login;
+	ret->init = skype_init;
+	ret->logout = skype_logout;
+	ret->buddy_msg = skype_buddy_msg;
+	ret->get_info = skype_get_info;
+	ret->set_my_name = skype_set_my_name;
+	ret->away_states = skype_away_states;
+	ret->set_away = skype_set_away;
+	ret->add_buddy = skype_add_buddy;
+	ret->remove_buddy = skype_remove_buddy;
+	ret->chat_msg = skype_chat_msg;
+	ret->chat_leave = skype_chat_leave;
+	ret->chat_invite = skype_chat_invite;
+	ret->chat_with = skype_chat_with;
+	ret->handle_cmp = g_strcasecmp;
+	ret->chat_topic = skype_chat_topic;
+#if BITLBEE_VERSION_CODE > BITLBEE_VER(3, 0, 1)
+	ret->buddy_action_list = skype_buddy_action_list;
+	ret->buddy_action = skype_buddy_action;
+#endif
+	register_protocol(ret);
+}
Index: protocols/skype/skyped.cnf
===================================================================
--- protocols/skype/skyped.cnf	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
+++ protocols/skype/skyped.cnf	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
@@ -0,0 +1,40 @@
+# create RSA certs - Server
+
+RANDFILE = skyped.rnd
+
+[ req ]
+default_bits = 1024
+encrypt_key = yes
+distinguished_name = req_dn
+x509_extensions = cert_type
+
+[ req_dn ]
+countryName = Country Name (2 letter code)
+countryName_default             = HU
+countryName_min                 = 2
+countryName_max                 = 2
+
+stateOrProvinceName             = State or Province Name (full name)
+stateOrProvinceName_default     = Some-State
+
+localityName                    = Locality Name (eg, city)
+
+0.organizationName              = Organization Name (eg, company)
+0.organizationName_default      = Stunnel Developers Ltd
+
+organizationalUnitName          = Organizational Unit Name (eg, section)
+#organizationalUnitName_default =
+
+0.commonName                    = Common Name (FQDN of your server)
+0.commonName_default            = localhost
+
+# To create a certificate for more than one name uncomment:
+# 1.commonName                  = DNS alias of your server
+# 2.commonName                  = DNS alias of your server
+# ...
+# See http://home.netscape.com/eng/security/ssl_2.0_certificate.html
+# to see how Netscape understands commonName.
+
+[ cert_type ]
+nsCertType = server
+
Index: protocols/skype/skyped.conf.dist.in
===================================================================
--- protocols/skype/skyped.conf.dist.in	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
+++ protocols/skype/skyped.conf.dist.in	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
@@ -0,0 +1,10 @@
+[skyped]
+# change to your skype username
+username = john
+# use `echo -n foo|sha1sum` to generate this hash for your password
+password = 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33
+
+# you have to change the following paths to your home directory:
+cert = /home/YOUR_USER/.skyped/skyped.cert.pem
+key  = /home/YOUR_USER/.skyped/skyped.key.pem
+port = 2727
Index: protocols/skype/skyped.py
===================================================================
--- protocols/skype/skyped.py	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
+++ protocols/skype/skyped.py	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
@@ -0,0 +1,488 @@
+#!/usr/bin/env python2.7
+# 
+#   skyped.py
+#  
+#   Copyright (c) 2007, 2008, 2009, 2010, 2011 by Miklos Vajna <vmiklos@frugalware.org>
+#
+#   This program is free software; you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation; either version 2 of the License, or
+#   (at your option) any later version.
+# 
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#  
+#   You should have received a copy of the GNU General Public License
+#   along with this program; if not, write to the Free Software
+#   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 
+#   USA.
+#
+
+import sys
+import os
+import signal
+import locale
+import time
+import socket
+import getopt
+import Skype4Py
+import hashlib
+from ConfigParser import ConfigParser, NoOptionError
+from traceback import print_exception
+import ssl
+
+__version__ = "0.1.1"
+
+try:
+	import gobject
+	hasgobject = True
+except ImportError:
+	import select
+	import threading
+	hasgobject = False
+
+def eh(type, value, tb):
+	global options
+
+	if type != KeyboardInterrupt:
+		print_exception(type, value, tb)
+	if hasgobject:
+		gobject.MainLoop().quit()
+	if options.conn:
+		options.conn.close()
+	# shut down client if it's running
+	try:
+		skype.skype.Client.Shutdown()
+	except NameError:
+		pass
+	sys.exit("Exiting.")
+
+sys.excepthook = eh
+
+def wait_for_lock(lock, timeout_to_print, timeout, msg):
+	start = time.time()
+	locked = lock.acquire(0)
+	while not(locked):
+		time.sleep(0.5)
+		if timeout_to_print and (time.time() - timeout_to_print > start):
+			dprint("%s: Waited %f seconds" % \
+					(msg, time.time() - start))
+			timeout_to_print = False
+		if timeout and (time.time() - timeout > start):
+			dprint("%s: Waited %f seconds, giving up" % \
+					(msg, time.time() - start))
+			return False
+		locked = lock.acquire(0)
+	return True
+
+def input_handler(fd, io_condition = None):
+	global options
+	global skype
+	if options.buf:
+		for i in options.buf:
+			skype.send(i.strip())
+		options.buf = None
+		if not hasgobject:
+			return True
+	else:
+		if not hasgobject:
+			close_socket = False
+			if wait_for_lock(options.lock, 3, 10, "input_handler"):
+				try:
+						input = fd.recv(1024)
+						options.lock.release()
+				except Exception, s:
+					dprint("Warning, receiving 1024 bytes failed (%s)." % s)
+					fd.close()
+					options.conn = False
+					options.lock.release()
+					return False
+				for i in input.split("\n"):
+					if i.strip() == "SET USERSTATUS OFFLINE":
+						close_socket = True
+					skype.send(i.strip())
+			return not(close_socket)
+		try:
+			input = fd.recv(1024)
+		except Exception, s:
+			dprint("Warning, receiving 1024 bytes failed (%s)." % s)
+			fd.close()
+			return False
+		for i in input.split("\n"):
+			skype.send(i.strip())
+		return True
+
+def skype_idle_handler(skype):
+	try:
+		c = skype.skype.Command("PING", Block=True)
+		skype.skype.SendCommand(c)
+	except Skype4Py.SkypeAPIError, s:
+		dprint("Warning, pinging Skype failed (%s)." % (s))
+	return True
+
+def send(sock, txt):
+	global options
+	from time import sleep
+	count = 1
+	done = False
+	if hasgobject:
+		while (not done) and (count < 10):
+			try:
+				sock.send(txt)
+				done = True
+			except Exception, s:
+				count += 1
+				dprint("Warning, sending '%s' failed (%s). count=%d" % (txt, s, count))
+				sleep(1)
+		if not done:
+			options.conn.close()
+	else:
+		while (not done) and (count < 10) and options.conn:
+			if wait_for_lock(options.lock, 3, 10, "socket send"):
+				try:
+					 if options.conn: sock.send(txt)
+					 options.lock.release()
+					 done = True
+				except Exception, s:
+					options.lock.release()
+					count += 1
+					dprint("Warning, sending '%s' failed (%s). count=%d" % (txt, s, count))
+					sleep(1)
+		if not done:
+			if options.conn:
+				options.conn.close()
+			options.conn = False
+		return done
+
+def bitlbee_idle_handler(skype):
+	global options
+	done = False
+	if options.conn:
+		try:
+			e = "PING"
+			done = send(options.conn, "%s\n" % e)
+		except Exception, s:
+			dprint("Warning, sending '%s' failed (%s)." % (e, s))
+			if hasgobject:
+				options.conn.close()
+			else:
+				if options.conn: options.conn.close()
+				options.conn = False
+				done = False
+	if hasgobject:
+		return True
+	else:
+		return done
+	return True
+
+def server(host, port, skype = None):
+	global options
+	sock = socket.socket()
+	sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+	sock.bind((host, port))
+	sock.listen(1)
+	if hasgobject:
+		gobject.io_add_watch(sock, gobject.IO_IN, listener)
+	else:
+		dprint("Waiting for connection...")
+		listener(sock, skype)
+
+def listener(sock, skype):
+	global options
+	if not hasgobject:
+		if not(wait_for_lock(options.lock, 3, 10, "listener")): return False
+	rawsock, addr = sock.accept()
+	options.conn = ssl.wrap_socket(rawsock,
+		server_side=True,
+		certfile=options.config.sslcert,
+		keyfile=options.config.sslkey,
+		ssl_version=ssl.PROTOCOL_TLSv1)
+	if hasattr(options.conn, 'handshake'):
+		try:
+			options.conn.handshake()
+		except Exception:
+			if not hasgobject:
+				options.lock.release()
+			dprint("Warning, handshake failed, closing connection.")
+			return False
+	ret = 0
+	try:
+		line = options.conn.recv(1024)
+		if line.startswith("USERNAME") and line.split(' ')[1].strip() == options.config.username:
+			ret += 1
+		line = options.conn.recv(1024)
+		if line.startswith("PASSWORD") and hashlib.sha1(line.split(' ')[1].strip()).hexdigest() == options.config.password:
+			ret += 1
+	except Exception, s:
+		dprint("Warning, receiving 1024 bytes failed (%s)." % s)
+		options.conn.close()
+		if not hasgobject:
+			options.conn = False
+			options.lock.release()
+		return False
+	if ret == 2:
+		dprint("Username and password OK.")
+		options.conn.send("PASSWORD OK\n")
+		if hasgobject:
+			gobject.io_add_watch(options.conn, gobject.IO_IN, input_handler)
+		else:
+			options.lock.release()
+			serverloop(options, skype)
+		return True
+	else:
+		dprint("Username and/or password WRONG.")
+		options.conn.send("PASSWORD KO\n")
+		if not hasgobject:
+			options.conn.close()
+			options.conn = False
+			options.lock.release()
+		return False
+
+def dprint(msg):
+	from time import strftime
+	global options
+
+	now = strftime("%Y-%m-%d %H:%M:%S")
+
+	if options.debug:
+		try:
+			print now + ": " + msg
+		except Exception, s:
+			try:
+				sanitized = msg.encode("ascii", "backslashreplace")
+			except Error, s:
+				try:
+					sanitized = "hex [" + msg.encode("hex") + "]"
+				except Error, s:
+					sanitized = "[unable to print debug message]"
+			print now + "~=" + sanitized
+		sys.stdout.flush()
+	if options.log:
+		sock = open(options.log, "a")
+		sock.write("%s: %s\n" % (now, msg))
+		sock.close()
+
+class SkypeApi:
+	def __init__(self):
+		self.skype = Skype4Py.Skype()
+		self.skype.OnNotify = self.recv
+		self.skype.Client.Start()
+
+	def recv(self, msg_text):
+		global options
+		if msg_text == "PONG":
+			return
+		if "\n" in msg_text:
+			# crappy skype prefixes only the first line for
+			# multiline messages so we need to do so for the other
+			# lines, too. this is something like:
+			# 'CHATMESSAGE id BODY first line\nsecond line' ->
+			# 'CHATMESSAGE id BODY first line\nCHATMESSAGE id BODY second line'
+			prefix = " ".join(msg_text.split(" ")[:3])
+			msg_text = ["%s %s" % (prefix, i) for i in " ".join(msg_text.split(" ")[3:]).split("\n")]
+		else:
+			msg_text = [msg_text]
+		for i in msg_text:
+			try:
+				# Internally, BitlBee always uses UTF-8 and encodes/decodes as
+				# necessary to communicate with the IRC client; thus send the
+				# UTF-8 it expects
+				e = i.encode('UTF-8')
+			except:
+				# Should never happen, but it's better to send difficult to
+				# read data than crash because some message couldn't be encoded
+				e = i.encode('ascii', 'backslashreplace')
+			if options.conn:
+				dprint('<< ' + e)
+				try:
+					send(options.conn, e + "\n")
+				except Exception, s:
+					dprint("Warning, sending '%s' failed (%s)." % (e, s))
+					if options.conn: options.conn.close()
+					options.conn = False
+			else:
+				dprint('-- ' + e)
+
+	def send(self, msg_text):
+		if not len(msg_text) or msg_text == "PONG":
+			if msg_text == "PONG":
+				options.last_bitlbee_pong = time.time()
+			return
+		try:
+			# Internally, BitlBee always uses UTF-8 and encodes/decodes as
+			# necessary to communicate with the IRC client; thus decode the
+			# UTF-8 it sent us
+			e = msg_text.decode('UTF-8')
+		except:
+			# Should never happen, but it's better to send difficult to read
+			# data to Skype than to crash
+			e = msg_text.decode('ascii', 'backslashreplace')
+		dprint('>> ' + e)
+		try:
+			c = self.skype.Command(e, Block=True)
+			self.skype.SendCommand(c)
+			self.recv(c.Reply)
+		except Skype4Py.SkypeError:
+			pass
+		except Skype4Py.SkypeAPIError, s:
+			dprint("Warning, sending '%s' failed (%s)." % (e, s))
+
+class Options:
+	def __init__(self):
+		self.cfgpath = os.path.join(os.environ['HOME'], ".skyped", "skyped.conf")
+		# for backwards compatibility
+		self.syscfgpath = "/usr/local/etc/skyped/skyped.conf"
+		if os.path.exists(self.syscfgpath):
+			self.cfgpath = self.syscfgpath
+		self.daemon = True
+		self.debug = False
+		self.help = False
+		self.host = "0.0.0.0"
+		self.log = None
+		self.port = None
+		self.version = False
+		# well, this is a bit hackish. we store the socket of the last connected client
+		# here and notify it. maybe later notify all connected clients?
+		self.conn = None
+		# this will be read first by the input handler
+		self.buf = None
+
+
+	def usage(self, ret):
+		print """Usage: skyped [OPTION]...
+
+skyped is a daemon that acts as a tcp server on top of a Skype instance.
+
+Options:
+	-c      --config        path to configuration file (default: %s)
+	-d	--debug		enable debug messages
+	-h	--help		this help
+	-H	--host		set the tcp host (default: %s)
+	-l      --log           set the log file in background mode (default: none)
+	-n	--nofork	don't run as daemon in the background
+	-p	--port		set the tcp port (default: %s)
+	-v	--version	display version information""" % (self.cfgpath, self.host, self.port)
+		sys.exit(ret)
+
+def serverloop(options, skype):
+	timeout = 1; # in seconds
+	skype_ping_period = 5
+	bitlbee_ping_period = 10
+	bitlbee_pong_timeout = 30
+	now = time.time()
+	skype_ping_start_time = now
+	bitlbee_ping_start_time = now
+	options.last_bitlbee_pong = now
+	in_error = []
+	handler_ok = True
+	while (len(in_error) == 0) and handler_ok and options.conn:
+		ready_to_read, ready_to_write, in_error = \
+			select.select([options.conn], [], [options.conn], \
+				timeout)
+		now = time.time()
+		handler_ok = len(in_error) == 0
+		if (len(ready_to_read) == 1) and handler_ok:
+			handler_ok = input_handler(ready_to_read.pop())
+			# don't ping bitlbee/skype if they already received data
+			now = time.time() # allow for the input_handler to take some time
+			bitlbee_ping_start_time = now
+			skype_ping_start_time = now
+			options.last_bitlbee_pong = now
+		if (now - skype_ping_period > skype_ping_start_time) and handler_ok:
+			handler_ok = skype_idle_handler(skype)
+			skype_ping_start_time = now
+		if now - bitlbee_ping_period > bitlbee_ping_start_time:
+			handler_ok = bitlbee_idle_handler(skype)
+			bitlbee_ping_start_time = now
+			if options.last_bitlbee_pong:
+				if (now - options.last_bitlbee_pong) > bitlbee_pong_timeout:
+					dprint("Bitlbee pong timeout")
+					# TODO is following line necessary? Should there be a options.conn.unwrap() somewhere?
+					# options.conn.shutdown()
+					if options.conn:
+						options.conn.close()
+					options.conn = False
+			else:
+				options.last_bitlbee_pong = now
+
+if __name__=='__main__':
+	options = Options()
+	try:
+		opts, args = getopt.getopt(sys.argv[1:], "c:dhH:l:np:v", ["config=", "debug", "help", "host=", "log=", "nofork", "port=", "version"])
+	except getopt.GetoptError:
+		options.usage(1)
+	for opt, arg in opts:
+		if opt in ("-c", "--config"):
+			options.cfgpath = arg
+		elif opt in ("-d", "--debug"):
+			options.debug = True
+		elif opt in ("-h", "--help"):
+			options.help = True
+		elif opt in ("-H", "--host"):
+			options.host = arg
+		elif opt in ("-l", "--log"):
+			options.log = arg
+		elif opt in ("-n", "--nofork"):
+			options.daemon = False
+		elif opt in ("-p", "--port"):
+			options.port = int(arg)
+		elif opt in ("-v", "--version"):
+			options.version = True
+	if options.help:
+		options.usage(0)
+	elif options.version:
+		print "skyped %s" % __version__
+		sys.exit(0)
+	# parse our config
+	if not os.path.exists(options.cfgpath):
+		print "Can't find configuration file at '%s'." % options.cfgpath
+		print "Use the -c option to specify an alternate one."
+		sys.exit(1)
+	options.config = ConfigParser()
+	options.config.read(options.cfgpath)
+	options.config.username = options.config.get('skyped', 'username').split('#')[0]
+	options.config.password = options.config.get('skyped', 'password').split('#')[0]
+	options.config.sslkey = os.path.expanduser(options.config.get('skyped', 'key').split('#')[0])
+	options.config.sslcert = os.path.expanduser(options.config.get('skyped', 'cert').split('#')[0])
+	# hack: we have to parse the parameters first to locate the
+	# config file but the -p option should overwrite the value from
+	# the config file
+	try:
+		options.config.port = int(options.config.get('skyped', 'port').split('#')[0])
+		if not options.port:
+			options.port = options.config.port
+	except NoOptionError:
+		pass
+	if not options.port:
+		options.port = 2727
+	dprint("Parsing config file '%s' done, username is '%s'." % (options.cfgpath, options.config.username))
+	if options.daemon:
+		pid = os.fork()
+		if pid == 0:
+			nullin = file(os.devnull, 'r')
+			nullout = file(os.devnull, 'w')
+			os.dup2(nullin.fileno(), sys.stdin.fileno())
+			os.dup2(nullout.fileno(), sys.stdout.fileno())
+			os.dup2(nullout.fileno(), sys.stderr.fileno())
+		else:
+			print 'skyped is started on port %s, pid: %d' % (options.port, pid)
+			sys.exit(0)
+	else:
+		dprint('skyped is started on port %s' % options.port)
+	if hasgobject:
+		server(options.host, options.port)
+	try:
+		skype = SkypeApi()
+	except Skype4Py.SkypeAPIError, s:
+		sys.exit("%s. Are you sure you have started Skype?" % s)
+	if hasgobject:
+		gobject.timeout_add(2000, skype_idle_handler, skype)
+		gobject.timeout_add(60000, bitlbee_idle_handler, skype)
+		gobject.MainLoop().run()
+	else:
+		while 1:
+			options.conn = False
+			options.lock = threading.Lock()
+			server(options.host, options.port, skype)
Index: protocols/skype/skyped.txt
===================================================================
--- protocols/skype/skyped.txt	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
+++ protocols/skype/skyped.txt	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
@@ -0,0 +1,52 @@
+= skyped(1)
+
+== NAME
+
+skyped - allows remote control of the Skype GUI client
+
+== SYNOPSIS
+
+skyped [<options>]
+
+== DESCRIPTION
+
+Skype supports remote control of the GUI client only via X11 or DBus
+messages. This is hard in care you want remote control. This daemon
+listens on a TCP port and runs on the same machine where the GUI client
+runs. It passes all the input it gets to Skype directly, except for a
+few commands which is related to authentication. The whole communication
+is done via SSL.
+
+== CONFIGURATION
+
+See the README for information about how to configure this daemon.
+
+== OPTIONS
+
+-c, --config::
+	Path to configuration file (default: $HOME/.skyped/skyped.conf)
+
+-d, --debug::
+	Enable debug messages
+
+-h, --help::
+	Show short summary of options
+
+-H, --host::
+	Set the tcp host (default: 0.0.0.0)
+
+-l, --log::
+	Set the log file in background mode (default: none)
+
+-n, --nofork::
+	Don't run as daemon in the background
+
+-p, --port::
+	Set the tcp port (default: 2727)
+
+-v, --version::
+	Display version information
+
+== AUTHOR
+
+Written by Miklos Vajna <vmiklos@frugalware.org>
Index: protocols/skype/t/Makefile
===================================================================
--- protocols/skype/t/Makefile	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
+++ protocols/skype/t/Makefile	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
@@ -0,0 +1,33 @@
+PORT=9876
+BITLBEE=/usr/sbin/bitlbee
+
+export TEST_SKYPE_ID=user
+export TEST_SKYPE_PASSWORD=pass
+
+testfiles := $(wildcard irssi/*.test)
+tests := $(patsubst %.test,%,$(testfiles))
+
+.PHONY: $(tests)
+
+all: $(tests)
+	@echo "passed $$(echo $(testfiles)|wc -w) tests."
+
+$(tests): % : %.test
+	@echo "--- Running test $@ ---"; \
+		if [ -r "$(BITLBEE)" -a -x "$(BITLBEE)" ]; then \
+			bitlbee_binary="$(BITLBEE)"; \
+		else \
+			bitlbee_basename=`basename $(BITLBEE)`; \
+			bitlbee_binary=`which $$bitlbee_basename`; \
+		fi; \
+		if ! ./livetest-bitlbee.sh "$$bitlbee_binary" $(PORT) irssi/livetest-irssi.sh $< >$@.log; then \
+			echo Test failed, log: ;\
+			cat $@.log;\
+			exit 1;\
+		fi;\
+	echo "--- OK ---" ;\
+	sleep 1
+clean:
+	rm -r irssi/*.log bitlbeetest.pid dotirssi livetest
+
+
Index: protocols/skype/t/irssi/livetest-irssi.sh
===================================================================
--- protocols/skype/t/irssi/livetest-irssi.sh	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
+++ protocols/skype/t/irssi/livetest-irssi.sh	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
@@ -0,0 +1,109 @@
+#!/usr/bin/env bash
+ISCRIPT=$1
+OPT=$2
+
+[ -n "$ISCRIPT" ] || { echo Syntax: `basename "$0"` irssi-test-script; exit 1; }
+
+# Load variables from test
+eval `sed -e '1,/^###/!d;/^###/d' "$ISCRIPT"`
+
+#if [ "$OPT" == "checkvars" ]; then echo $TESTNEEDEDVARS; fi
+RET=0
+
+# Check if we have the neccessary environment variables for this test
+for var in $TESTNEEDEDVARS; do
+	if [ -z `eval echo \$\{$var\}` ]; then
+		if [ "$OPT" != "checkvars" ]; then 
+			echo Need environment variable "$var" for this test.
+			exit 66
+		else
+			echo $var
+			RET=66
+		fi
+	fi
+done
+
+# if we got this far we're OK
+if [ "$OPT" == "checkvars" ]; then exit $RET; fi
+
+[ -n "$PORT" ] || { echo 'Need the bitlbee listening port as environment variable PORT'; exit 1; }
+
+# Setup the irssi dir
+(
+	rm -r dotirssi
+	mkdir -p dotirssi/scripts dotirssi/logs
+	cp "`dirname $0`"/trigger.pl dotirssi/scripts &&
+	echo 'script load trigger.pl' >dotirssi/startup
+) &>/dev/null || { echo Failed to setup irssi testdir; exit 1; }
+
+# write irssi config
+
+echo '
+
+aliases = { 
+	runtest = "'`sed -e "1,/^###/d;s/@LOGIN@/$TESTLOGIN/;s/@PASSWORD@/$TESTPASSWORD/" "$ISCRIPT" | tr '\n' ';'`'"; 
+	expectbee  = "/trigger add -publics -channels &bitlbee -regexp";
+	expectjoin = "/trigger add -joins -masks *!$0@* $1-";
+	expectmsg  = "/trigger add -privmsgs -masks *!$0@* $1-";
+};
+
+servers = ( { address = "localhost"; chatnet = "local"; port = "'$PORT'"; autoconnect="yes";});
+
+settings = {
+  settings_autosave = "no";
+  core = { real_name = "bitlbee-test"; user_name = "bitlbee-test"; nick = "bitlbeetest"; };
+  "fe-text" = { actlist_sort = "refnum"; };
+};
+
+chatnets = { local = { type = "IRC"; autosendcmd = "/runtest"; }; };
+
+logs = {
+"dotirssi/logs/status.log" = { auto_open = "yes"; level = "ALL"; items = ( { type = "window"; name = "1"; } ); };
+"dotirssi/logs/control.log" = { auto_open = "yes"; level = "ALL"; items = ( { type = "target"; name = "&bitlbee"; } ); };
+' >dotirssi/config
+
+for nick in $TESTLOGNICKS; do 
+	echo '
+	"dotirssi/logs/'$nick'.log" = { auto_open = "yes"; level = "ALL"; items = ( { type = "target"; name = "'$nick'"; } ); };
+		' >>dotirssi/config
+done
+
+echo '};' >>dotirssi/config
+
+# Go!
+
+echo Running irssi...
+screen -D -m irssi --config=dotirssi/config --home=dotirssi/ & 
+
+# output logs
+
+submitlogs() {
+	perl -p -i -e "s/$TESTLOGIN/---TESTLOGIN---/;s/$TESTPASSWORD/---TESTPASSWORD---/" dotirssi/logs/*.log
+
+	if [ "$OPT" == "tgz" ]; then
+		tar czf "`dirname $0`"/"`basename "$ISCRIPT"`".logs.tgz dotirssi/logs/*.log
+	elif [ "$OPT" == "ctest" ]; then 
+		echo CTEST_FULL_OUTPUT
+		for log in dotirssi/logs/*.log; do
+			echo -n '<DartMeasurement name="'$log'" type="text/string"><![CDATA['
+			cat "$log"
+			echo "]]></DartMeasurement>"
+		done
+	else
+		echo Test logs: dotirssi/logs/*.log
+	fi
+}
+
+# timeout stuff
+
+t=$TESTDURATION
+intval=1
+while (( t >= intval )); do
+	sleep $intval
+	kill -0 $! &>/dev/null || { echo screen/irssi terminated.; submitlogs; bash -c "cd dotirssi/logs && $TESTCHECKRESULT" >/dev/null; exit $?; }
+	t=$(( t - $intval ))
+done
+echo Killing screen/irssi...
+kill $!
+submitlogs
+exit 22
Index: protocols/skype/t/irssi/skype-call.test
===================================================================
--- protocols/skype/t/irssi/skype-call.test	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
+++ protocols/skype/t/irssi/skype-call.test	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
@@ -0,0 +1,13 @@
+TESTNEEDEDVARS="TEST_SKYPE_ID TEST_SKYPE_PASSWORD"
+TESTDURATION=60
+TESTCHECKRESULT="grep '\[Test Passed\]' status.log"
+TESTLOGIN="$TEST_SKYPE_ID"
+TESTPASSWORD="$TEST_SKYPE_PASSWORD"
+### Test receiving call output
+/expectbee 'Welcome to the BitlBee' -command 'msg $$C register testing'
+/expectbee 'Account successfully created' -command 'msg $$C account add skype @LOGIN@ @PASSWORD@'
+/expectbee 'Account successfully added' -command 'msg $$C account 0 set test_join true'
+/expectbee 'test_join' -command 'msg $$C account 0 on'
+/expectjoin echo123 -command 'ctcp echo123 call'
+/expectbee 'You are currently ringing the user' -command 'ctcp echo123 hangup'
+/expectbee '(You cancelled the call|You finished the call)' -command 'quit Test Passed'
Index: protocols/skype/t/irssi/skype-info.test
===================================================================
--- protocols/skype/t/irssi/skype-info.test	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
+++ protocols/skype/t/irssi/skype-info.test	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
@@ -0,0 +1,12 @@
+TESTNEEDEDVARS="TEST_SKYPE_ID TEST_SKYPE_PASSWORD"
+TESTDURATION=60
+TESTCHECKRESULT="grep '\[Test Passed\]' status.log"
+TESTLOGIN="$TEST_SKYPE_ID"
+TESTPASSWORD="$TEST_SKYPE_PASSWORD"
+### Test receiving info output
+/expectbee 'Welcome to the BitlBee' -command 'msg $$C register testing'
+/expectbee 'Account successfully created' -command 'msg $$C account add skype @LOGIN@ @PASSWORD@'
+/expectbee 'Account successfully added' -command 'msg $$C account 0 set test_join true'
+/expectbee 'test_join' -command 'msg $$C account 0 on'
+/expectjoin echo123 -command 'msg $$C info echo123'
+/expectbee 'Full Name: Echo / Sound Test Service' -command 'quit Test Passed'
Index: protocols/skype/t/irssi/skype-login.test
===================================================================
--- protocols/skype/t/irssi/skype-login.test	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
+++ protocols/skype/t/irssi/skype-login.test	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
@@ -0,0 +1,10 @@
+TESTNEEDEDVARS="TEST_SKYPE_ID TEST_SKYPE_PASSWORD"
+TESTDURATION=10
+TESTCHECKRESULT="grep '\[Test Passed\]' status.log"
+TESTLOGIN="$TEST_SKYPE_ID"
+TESTPASSWORD="$TEST_SKYPE_PASSWORD"
+### Test login
+/expectbee 'Welcome to the BitlBee' -command 'msg $$C register testing'
+/expectbee 'Account successfully created' -command 'msg $$C account add skype @LOGIN@ @PASSWORD@'
+/expectbee 'Account successfully added' -command 'msg $$C account 0 on'
+/expectbee 'Logged in' -command 'quit Test Passed'
Index: protocols/skype/t/irssi/skype-msg.test
===================================================================
--- protocols/skype/t/irssi/skype-msg.test	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
+++ protocols/skype/t/irssi/skype-msg.test	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
@@ -0,0 +1,17 @@
+TESTNEEDEDVARS="TEST_SKYPE_ID TEST_SKYPE_PASSWORD"
+TESTDURATION=60
+TESTCHECKRESULT="grep '\[Test Passed\]' status.log"
+TESTLOGIN="$TEST_SKYPE_ID"
+TESTPASSWORD="$TEST_SKYPE_PASSWORD"
+### Test sending and receiving messages
+/expectbee 'Welcome to the BitlBee' -command 'msg $$C register testing'
+/expectbee 'Account successfully created' -command 'msg $$C account add skype @LOGIN@ @PASSWORD@'
+/expectbee 'Account successfully added' -command 'msg $$C account 0 set test_join true'
+/expectbee 'test_join' -command 'msg $$C account 0 on'
+# use builtin test service
+/expectjoin echo123 -command 'msg $$C echo123: ping, say pong'
+/expectbee 'pong' -command 'quit Test Passed'
+# use a public bot as well, just in case the above one would fail
+/expectjoin echo123 -command 'msg $$C add skype pam_bot'
+/expectjoin pam_bot -command 'msg $$C pam_bot: pambot help'
+/expectbee 'PamBot, thanks for chatting with me' -command 'quit Test Passed'
Index: protocols/skype/t/irssi/trigger.pl
===================================================================
--- protocols/skype/t/irssi/trigger.pl	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
+++ protocols/skype/t/irssi/trigger.pl	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
@@ -0,0 +1,1225 @@
+# trigger.pl - execute a command or replace text, triggered by an event in irssi
+# Do /TRIGGER HELP or look at http://wouter.coekaerts.be/irssi/ for help
+
+# Copyright (C) 2002-2006  Wouter Coekaerts <wouter@coekaerts.be>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+use strict;
+use Irssi 20020324 qw(command_bind command_runsub command signal_add_first signal_continue signal_stop signal_remove);
+use Text::ParseWords;
+use IO::File;
+use vars qw($VERSION %IRSSI);
+
+$VERSION = '1.0';
+%IRSSI = (
+	authors     => 'Wouter Coekaerts',
+	contact     => 'wouter@coekaerts.be',
+	name        => 'trigger',
+	description => 'execute a command or replace text, triggered by an event in irssi',
+	license     => 'GPLv2 or later',
+	url         => 'http://wouter.coekaerts.be/irssi/',
+	changed     => '$LastChangedDate: 2006-01-23 13:10:19 +0100 (Mon, 23 Jan 2006) $',
+);
+
+sub cmd_help {
+	Irssi::print (<<'SCRIPTHELP_EOF', MSGLEVEL_CLIENTCRAP);
+
+TRIGGER LIST
+TRIGGER SAVE
+TRIGGER RELOAD
+TRIGGER MOVE <number> <number>
+TRIGGER DELETE <number>
+TRIGGER CHANGE <number> ...
+TRIGGER ADD ...
+
+When to match:
+On which types of event to trigger:
+     These are simply specified by -name_of_the_type
+     The normal IRC event types are:
+          publics, %|privmsgs, pubactions, privactions, pubnotices, privnotices, joins, parts, quits, kicks, topics, invites, nick_changes, dcc_msgs, dcc_actions, dcc_ctcps
+          mode_channel: %|a mode on the (whole) channel (like +t, +i, +b)
+          mode_nick: %|a mode on someone in the channel (like +o, +v)
+     -all is an alias for all of those.
+     Additionally, there is:
+          rawin: %|raw text incoming from the server
+          send_command: %|commands you give to irssi
+          send_text: %|lines you type that aren't commands
+          beep: %|when irssi beeps
+          notify_join: %|someone in you notify list comes online
+          notify_part: %|someone in your notify list goes offline
+          notify_away: %|someone in your notify list goes away
+          notify_unaway: %|someone in your notify list goes unaway
+          notify_unidle: %|someone in your notify list stops idling
+
+Filters (conditions) the event has to satisfy. They all take one parameter.
+If you can give a list, seperate elements by space and use quotes around the list.
+     -pattern: %|The message must match the given pattern. ? and * can be used as wildcards
+     -regexp: %|The message must match the given regexp. (see man perlre)
+       %|if -nocase is given as an option, the regexp or pattern is matched case insensitive
+     -tags: %|The servertag must be in the given list of tags
+     -channels: %|The event must be in one of the given list of channels.
+                Examples: %|-channels '#chan1 #chan2' or -channels 'IRCNet/#channel'
+                          %|-channels 'EFNet/' means every channel on EFNet and is the same as -tags 'EFNet'
+     -masks: %|The person who triggers it must match one of the given list of masks
+     -hasmode: %|The person who triggers it must have the give mode
+               Examples: %|'-o' means not opped, '+ov' means opped OR voiced, '-o&-v' means not opped AND not voiced
+     -hasflag: %|Only trigger if if friends.pl (friends_shasta.pl) or people.pl is loaded and the person who triggers it has the given flag in the script (same syntax as -hasmode)
+     -other_masks
+     -other_hasmode
+     -other_hasflag: %|Same as above but for the victim for kicks or mode_nick.
+
+What to do when it matches:
+     -command: Execute the given Irssi-command
+                %|You are able to use $1, $2 and so on generated by your regexp pattern.
+                %|For multiple commands ; (or $;) can be used as seperator
+                %|The following variables are also expanded:
+                   $T: %|Server tag
+                   $C: %|Channel name
+                   $N: %|Nickname of the person who triggered this command
+                   $A: %|His address (foo@bar.com),
+                   $I: %|His ident (foo)
+                   $H: %|His hostname (bar.com)
+                   $M: %|The complete message
+                   ${other}: %|The victim for kicks or mode_nick
+                   ${mode_type}: %|The type ('+' or '-') for a mode_channel or mode_nick
+                   ${mode_char}: %|The mode char ('o' for ops, 'b' for ban,...)
+                   ${mode_arg} : %|The argument to the mode (if there is one)
+                %|$\X, with X being one of the above expands (e.g. $\M), escapes all non-alphanumeric characters, so it can be used with /eval or /exec. Don't use /eval or /exec without this, it's not safe.
+
+     -replace: %|replaces the matching part with the given replacement in the event (requires a -regexp or -pattern)
+     -once: %|remove the trigger if it is triggered, so it only executes once and then is forgotten.
+     -stop: %|stops the signal. It won't get displayed by Irssi. Like /IGNORE
+     -debug: %|print some debugging info
+
+Other options:
+     -disabled: %|Same as removing it, but keeps it in case you might need it later
+     -name: %|Give the trigger a name. You can refer to the trigger with this name in add/del/change commands
+
+Examples:
+ Knockout people who do a !list:
+   /TRIGGER ADD %|-publics -channels "#channel1 #channel2" -nocase -regexp ^!list -command "KN $N This is not a warez channel!"
+ React to !echo commands from people who are +o in your friends-script:
+   /TRIGGER ADD %|-publics -regexp '^!echo (.*)' -hasflag '+o' -command 'say echo: $1'
+ Ignore all non-ops on #channel:
+   /TRIGGER ADD %|-publics -actions -channels "#channel" -hasmode '-o' -stop
+ Send a mail to yourself every time a topic is changed:
+   /TRIGGER ADD %|-topics -command 'exec echo $\N changed topic of $\C to: $\M | mail you@somewhere.com -s topic'
+
+
+Examples with -replace:
+ %|Replace every occurence of shit with sh*t, case insensitive:
+   /TRIGGER ADD %|-all -nocase -regexp shit -replace sh*t
+ %|Strip all colorcodes from *!lamer@*:
+   /TRIGGER ADD %|-all -masks *!lamer@* -regexp '\x03\d?\d?(,\d\d?)?|\x02|\x1f|\x16|\x06' -replace ''
+ %|Never let *!bot1@foo.bar or *!bot2@foo.bar hilight you
+ %|(this works by cutting your nick in 2 different parts, 'myn' and 'ick' here)
+ %|you don't need to understand the -replace argument, just trust that it works if the 2 parts separately don't hilight:
+   /TRIGGER ADD %|-all masks '*!bot1@foo.bar *!bot2@foo.bar' -regexp '(myn)(ick)' -nocase -replace '$1\x02\x02$2'
+ %|Avoid being hilighted by !top10 in eggdrops with stats.mod (but show your nick in bold):
+   /TRIGGER ADD %|-publics -regexp '(Top.0\(.*\): 1.*)(my)(nick)' -replace '$1\x02$2\x02\x02$3\x02'
+ %|Convert a Windows-1252 Euro to an ISO-8859-15 Euro (same effect as euro.pl):
+   /TRIGGER ADD %|-regexp '\x80' -replace '\xA4'
+ %|Show tabs as spaces, not the inverted I (same effect as tab_stop.pl):
+   /TRIGGER ADD %|-all -regexp '\t' -replace '    '
+SCRIPTHELP_EOF
+} # /
+
+my @triggers; # array of all triggers
+my %triggers_by_type; # hash mapping types on triggers of that type
+my $recursion_depth = 0;
+my $changed_since_last_save = 0;
+
+###############
+### formats ###
+###############
+
+Irssi::theme_register([
+	'trigger_header' => 'Triggers:',
+	'trigger_line' => '%#$[-4]0 $1',
+	'trigger_added' => 'Trigger $0 added: $1',
+	'trigger_not_found' => 'Trigger {hilight $0} not found',
+	'trigger_saved' => 'Triggers saved to $0',
+	'trigger_loaded' => 'Triggers loaded from $0'
+]);
+
+#########################################
+### catch the signals & do your thing ###
+#########################################
+
+# trigger types with a message and a channel
+my @allchanmsg_types = qw(publics pubactions pubnotices pubctcps pubctcpreplies parts quits kicks topics);
+# trigger types with a message
+my @allmsg_types = (@allchanmsg_types, qw(privmsgs privactions privnotices privctcps privctcpreplies dcc_msgs dcc_actions dcc_ctcps));
+# trigger types with a channel
+my @allchan_types = (@allchanmsg_types, qw(mode_channel mode_nick joins invites));
+# trigger types in -all
+my @all_types = (@allmsg_types, qw(mode_channel mode_nick joins invites nick_changes));
+# trigger types with a server
+my @all_server_types = (@all_types, qw(rawin notify_join notify_part notify_away notify_unaway notify_unidle));
+# all trigger types
+my @trigger_types = (@all_server_types, qw(send_command send_text beep));
+#trigger types that are not in -all
+#my @notall_types = grep {my $a=$_; return (!grep {$_ eq $a} @all_types);} @trigger_types;
+my @notall_types = qw(rawin notify_join notify_part notify_away notify_unaway notify_unidle send_command send_text beep);
+
+my @signals = (
+# "message public", SERVER_REC, char *msg, char *nick, char *address, char *target
+{
+	'types' => ['publics'],
+	'signal' => 'message public',
+	'sub' => sub {check_signal_message(\@_,1,$_[0],$_[4],$_[2],$_[3],'publics');},
+},
+# "message private", SERVER_REC, char *msg, char *nick, char *address
+{
+	'types' => ['privmsgs'],
+	'signal' => 'message private',
+	'sub' => sub {check_signal_message(\@_,1,$_[0],undef,$_[2],$_[3],'privmsgs');},
+},
+# "message irc action", SERVER_REC, char *msg, char *nick, char *address, char *target
+{
+	'types' => ['privactions','pubactions'],
+	'signal' => 'message irc action',
+	'sub' => sub {
+		if ($_[4] eq $_[0]->{nick}) {
+			check_signal_message(\@_,1,$_[0],undef,$_[2],$_[3],'privactions');
+		} else {
+			check_signal_message(\@_,1,$_[0],$_[4],$_[2],$_[3],'pubactions');
+		}
+	},
+},
+# "message irc notice", SERVER_REC, char *msg, char *nick, char *address, char *target
+{
+	'types' => ['privnotices','pubnotices'],
+	'signal' => 'message irc notice',
+	'sub' => sub {
+		if ($_[4] eq $_[0]->{nick}) {
+			check_signal_message(\@_,1,$_[0],undef,$_[2],$_[3],'privnotices');
+		} else {
+			check_signal_message(\@_,1,$_[0],$_[4],$_[2],$_[3],'pubnotices');
+		}
+	}
+},
+# "message join", SERVER_REC, char *channel, char *nick, char *address
+{
+	'types' => ['joins'],
+	'signal' => 'message join',
+	'sub' => sub {check_signal_message(\@_,-1,$_[0],$_[1],$_[2],$_[3],'joins');}
+},
+# "message part", SERVER_REC, char *channel, char *nick, char *address, char *reason
+{
+	'types' => ['parts'],
+	'signal' => 'message part',
+	'sub' => sub {check_signal_message(\@_,4,$_[0],$_[1],$_[2],$_[3],'parts');}
+},
+# "message quit", SERVER_REC, char *nick, char *address, char *reason
+{
+	'types' => ['quits'],
+	'signal' => 'message quit',
+	'sub' => sub {check_signal_message(\@_,3,$_[0],undef,$_[1],$_[2],'quits');}
+},
+# "message kick", SERVER_REC, char *channel, char *nick, char *kicker, char *address, char *reason
+{
+	'types' => ['kicks'],
+	'signal' => 'message kick',
+	'sub' => sub {check_signal_message(\@_,5,$_[0],$_[1],$_[3],$_[4],'kicks',{'other'=>$_[2]});}
+},
+# "message topic", SERVER_REC, char *channel, char *topic, char *nick, char *address
+{
+	'types' => ['topics'],
+	'signal' => 'message topic',
+	'sub' => sub {check_signal_message(\@_,2,$_[0],$_[1],$_[3],$_[4],'topics');}
+},
+# "message invite", SERVER_REC, char *channel, char *nick, char *address
+{
+	'types' => ['invites'],
+	'signal' => 'message invite',
+	'sub' => sub {check_signal_message(\@_,-1,$_[0],$_[1],$_[2],$_[3],'invites');}
+},
+# "message nick", SERVER_REC, char *newnick, char *oldnick, char *address
+{
+	'types' => ['nick_changes'],
+	'signal' => 'message nick',
+	'sub' => sub {check_signal_message(\@_,-1,$_[0],undef,$_[1],$_[3],'nick_changes');}
+},
+# "message dcc", DCC_REC *dcc, char *msg
+{
+	'types' => ['dcc_msgs'],
+	'signal' => 'message dcc',
+	'sub' => sub {check_signal_message(\@_,1,$_[0]->{'server'},undef,$_[0]->{'nick'},undef,'dcc_msgs');
+	}
+},
+# "message dcc action", DCC_REC *dcc, char *msg
+{
+	'types' => ['dcc_actions'],
+	'signal' => 'message dcc action',
+	'sub' => sub {check_signal_message(\@_,1,$_[0]->{'server'},undef,$_[0]->{'nick'},undef,'dcc_actions');}
+},
+# "message dcc ctcp", DCC_REC *dcc, char *cmd, char *data
+{
+	'types' => ['dcc_ctcps'],
+	'signal' => 'message dcc ctcp',
+	'sub' => sub {check_signal_message(\@_,1,$_[0]->{'server'},undef,$_[0]->{'nick'},undef,'dcc_ctcps');}
+},
+# "server incoming", SERVER_REC, char *data
+{
+	'types' => ['rawin'],
+	'signal' => 'server incoming',
+	'sub' => sub {check_signal_message(\@_,1,$_[0],undef,undef,undef,'rawin');}
+},
+# "send command", char *args, SERVER_REC, WI_ITEM_REC
+{
+	'types' => ['send_command'],
+	'signal' => 'send command',
+	'sub' => sub {
+		sig_send_text_or_command(\@_,1);
+	}
+},
+# "send text", char *line, SERVER_REC, WI_ITEM_REC
+{
+	'types' => ['send_text'],
+	'signal' => 'send text',
+	'sub' => sub {
+		sig_send_text_or_command(\@_,0);
+	}
+},
+# "beep"
+{
+	'types' => ['beep'],
+	'signal' => 'beep',
+	'sub' => sub {check_signal_message(\@_,-1,undef,undef,undef,undef,'beep');}
+},
+# "event "<cmd>, SERVER_REC, char *args, char *sender_nick, char *sender_address
+{
+	'types' => ['mode_channel', 'mode_nick'],
+	'signal' => 'event mode',
+	'sub' => sub {
+		my ($server, $event_args, $nickname, $address) = @_;
+		my ($target, $modes, $modeargs) = split(/ /, $event_args, 3);
+		return if (!$server->ischannel($target));
+		my (@modeargs) = split(/ /,$modeargs);
+		my ($pos, $type, $event_type, $arg) = (0, '+');
+		foreach my $char (split(//,$modes)) {
+			if ($char eq "+" || $char eq "-") {
+				$type = $char;
+			} else {
+				if ($char =~ /[Oovh]/) { # mode_nick
+					$event_type = 'mode_nick';
+					$arg = $modeargs[$pos++];
+				} elsif ($char =~ /[beIqdk]/ || ( $char =~ /[lfJ]/ && $type eq '+')) { # chan_mode with arg
+					$event_type = 'mode_channel';
+					$arg = $modeargs[$pos++];
+				} else { # chan_mode without arg
+					$event_type = 'mode_channel';
+					$arg = undef;
+				}
+				check_signal_message(\@_,-1,$server,$target,$nickname,$address,$event_type,{
+					'mode_type' => $type,
+					'mode_char' => $char,
+					'mode_arg' => $arg,
+					'other' => ($event_type eq 'mode_nick') ? $arg : undef
+				});
+			}
+		}
+	}
+},
+# "notifylist joined", SERVER_REC, char *nick, char *user, char *host, char *realname, char *awaymsg
+{
+	'types' => ['notify_join'],
+	'signal' => 'notifylist joined',
+	'sub' => sub {check_signal_message(\@_, 5, $_[0], undef, $_[1], $_[2].'@'.$_[3], 'notify_join', {'realname' => $_[4]});}
+},
+{
+	'types' => ['notify_part'],
+	'signal' => 'notifylist left',
+	'sub' => sub {check_signal_message(\@_, 5, $_[0], undef, $_[1], $_[2].'@'.$_[3], 'notify_left', {'realname' => $_[4]});}
+},
+{
+	'types' => ['notify_unidle'],
+	'signal' => 'notifylist unidle',
+	'sub' => sub {check_signal_message(\@_, 5, $_[0], undef, $_[1], $_[2].'@'.$_[3], 'notify_unidle', {'realname' => $_[4]});}
+},
+{
+	'types' => ['notify_away', 'notify_unaway'],
+	'signal' => 'notifylist away changed',
+	'sub' => sub {check_signal_message(\@_, 5, $_[0], undef, $_[1], $_[2].'@'.$_[3], ($_[5] ? 'notify_away' : 'notify_unaway'), {'realname' => $_[4]});}
+},
+# "ctcp msg", SERVER_REC, char *args, char *nick, char *addr, char *target
+{
+	'types' => ['pubctcps', 'privctcps'],
+	'signal' => 'ctcp msg',
+	'sub' => sub {
+		my ($server, $args, $nick, $addr, $target) = @_;
+		if ($target eq $server->{'nick'}) {
+			check_signal_message(\@_, 1, $server, undef, $nick, $addr, 'privctcps');
+		} else {
+			check_signal_message(\@_, 1, $server, $target, $nick, $addr, 'pubctcps');
+		}
+	}
+},
+# "ctcp reply", SERVER_REC, char *args, char *nick, char *addr, char *target
+{
+	'types' => ['pubctcpreplies', 'privctcpreplies'],
+	'signal' => 'ctcp reply',
+	'sub' => sub {
+		my ($server, $args, $nick, $addr, $target) = @_;
+		if ($target eq $server->{'nick'}) {
+			check_signal_message(\@_, 1, $server, undef, $nick, $addr, 'privctcps');
+		} else {
+			check_signal_message(\@_, 1, $server, $target, $nick, $addr, 'pubctcps');
+		}
+	}
+}
+);
+
+sub sig_send_text_or_command {
+	my ($signal, $iscommand) = @_;
+	my ($line, $server, $item) = @$signal;
+	my ($channelname,$nickname,$address) = (undef,undef,undef);
+	if ($item && (ref($item) eq 'Irssi::Irc::Channel' || ref($item) eq 'Irssi::Silc::Channel')) {
+		$channelname = $item->{'name'};
+	} elsif ($item && ref($item) eq 'Irssi::Irc::Query') { # TODO Silc query ?
+		$nickname = $item->{'name'};
+		$address = $item->{'address'}
+	}
+	# TODO pass context also for non-channels (queries and other stuff)
+	check_signal_message($signal,0,$server,$channelname,$nickname,$address,$iscommand ? 'send_command' : 'send_text');
+
+}
+
+my %filters = (
+'tags' => {
+	'types' => \@all_server_types,
+	'sub' => sub {
+		my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
+		
+		if (!defined($server)) {
+			return 0;
+		}
+		my $matches = 0;
+		foreach my $tag (split(/ /,$param)) {
+			if (lc($server->{'tag'}) eq lc($tag)) {
+				$matches = 1;
+				last;
+			}
+		}
+		return $matches;
+	}
+},
+'channels' => {
+	'types' => \@allchan_types,
+	'sub' => sub {
+		my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
+		
+		if (!defined($channelname) || !defined($server)) {
+			return 0;
+		}
+		my $matches = 0;
+		foreach my $trigger_channel (split(/ /,$param)) {
+			if (lc($channelname) eq lc($trigger_channel)
+				|| lc($server->{'tag'}.'/'.$channelname) eq lc($trigger_channel)
+				|| lc($server->{'tag'}.'/') eq lc($trigger_channel)) {
+				$matches = 1;
+				last; # this channel matches, stop checking channels
+			}
+		}
+		return $matches;
+	}
+},
+'masks' => {
+	'types' => \@all_types,
+	'sub' => sub {
+		my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
+		return  (defined($nickname) && defined($address) && defined($server) && $server->masks_match($param, $nickname, $address));
+	}
+},
+'other_masks' => {
+	'types' => ['kicks', 'mode_nick'],
+	'sub' => sub {
+		my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
+		return 0 unless defined($extra->{'other'});
+		my $other_address = get_address($extra->{'other'}, $server, $channelname);
+		return defined($other_address) && $server->masks_match($param, $extra->{'other'}, $other_address);
+	}
+},
+'hasmode' => {
+	'types' => \@all_types,
+	'sub' => sub {
+		my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
+		return hasmode($param, $nickname, $server, $channelname);
+	}
+},
+'other_hasmode' => {
+	'types' => ['kicks', 'mode_nick'],
+	'sub' => sub {
+		my ($param,$signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
+		return defined($extra->{'other'}) && hasmode($param, $extra->{'other'}, $server, $channelname);
+	}
+},
+'hasflag' => {
+	'types' => \@all_types,
+	'sub' => sub {
+		my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
+		return 0 unless defined($nickname) && defined($address) && defined($server);
+		my $flags = get_flags ($server->{'chatnet'},$channelname,$nickname,$address);
+		return defined($flags) && check_modes($flags,$param);
+	}
+},
+'other_hasflag' => {
+	'types' => ['kicks', 'mode_nick'],
+	'sub' => sub {
+		my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
+		return 0 unless defined($extra->{'other'});
+		my $other_address = get_address($extra->{'other'}, $server, $channelname);
+		return 0 unless defined($other_address);
+		my $flags = get_flags ($server->{'chatnet'},$channelname,$extra->{'other'},$other_address);
+		return defined($flags) && check_modes($flags,$param);
+	}
+},
+'mode_type' => {
+	'types' => ['mode_channel', 'mode_nick'],
+	'sub' => sub {
+		my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
+		return (($param) eq $extra->{'mode_type'});
+	}
+},
+'mode_char' => {
+	'types' => ['mode_channel', 'mode_nick'],
+	'sub' => sub {
+		my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
+		return (($param) eq $extra->{'mode_char'});
+	}
+},
+'mode_arg' => {
+	'types' => ['mode_channel', 'mode_nick'],
+	'sub' => sub {
+		my ($param, $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra) = @_;
+		return (($param) eq $extra->{'mode_arg'});
+	}
+}
+);
+
+sub get_address {
+	my ($nick, $server, $channel) = @_;
+	my $nickrec = get_nickrec($nick, $server, $channel);
+	return $nickrec ? $nickrec->{'host'} : undef;
+}
+sub get_nickrec {
+	my ($nick, $server, $channel) = @_;
+	return unless defined($server) && defined($channel) && defined($nick);
+	my $chanrec = $server->channel_find($channel);
+	return $chanrec ? $chanrec->nick_find($nick) : undef;
+}
+
+sub hasmode {
+	my ($param, $nickname, $server, $channelname) = @_;
+	my $nickrec = get_nickrec($nickname, $server, $channelname);
+	return 0 unless defined $nickrec;
+	my $modes =
+		($nickrec->{'op'} ? 'o' : '')
+		. ($nickrec->{'voice'} ? 'v' : '')
+		. ($nickrec->{'halfop'} ? 'h' : '')
+	;
+	return check_modes($modes, $param);
+}
+
+# list of all switches
+my @trigger_switches = (@trigger_types, qw(all nocase stop once debug disabled));
+# parameters (with an argument)
+my @trigger_params = qw(pattern regexp command replace name);
+# list of all options (including switches) for /TRIGGER ADD
+my @trigger_add_options = (@trigger_switches, @trigger_params, keys(%filters));
+# same for /TRIGGER CHANGE, this includes the -no<option>'s
+my @trigger_options = map(($_,'no'.$_) ,@trigger_add_options);
+
+# check the triggers on $signal's $parammessage parameter, for triggers with $condition set
+#  on $server in $channelname, for $nickname!$address
+# set $parammessage to -1 if the signal doesn't have a message
+# for signal without channel, nick or address, set to undef
+sub check_signal_message {
+	my ($signal, $parammessage, $server, $channelname, $nickname, $address, $condition, $extra) = @_;
+	my ($changed, $stopped, $context, $need_rebuild);
+	my $message = ($parammessage == -1) ? '' : $signal->[$parammessage];
+
+	return if (!$triggers_by_type{$condition});
+	
+	if ($recursion_depth > 10) {
+		Irssi::print("Trigger error: Maximum recursion depth reached, aborting trigger.", MSGLEVEL_CLIENTERROR);
+		return;
+	}
+	$recursion_depth++;
+
+TRIGGER:	
+	foreach my $trigger (@{$triggers_by_type{$condition}}) {
+		# check filters
+		foreach my $trigfilter (@{$trigger->{'filters'}}) {
+			if (! ($trigfilter->[2]($trigfilter->[1], $signal,$parammessage,$server,$channelname,$nickname,$address,$condition,$extra))) {
+			
+				next TRIGGER;
+			}
+		}
+		
+		# check regexp (and keep matches in @- and @+, so don't make a this a {block})
+		next if ($trigger->{'compregexp'} && ($parammessage == -1 || $message !~ m/$trigger->{'compregexp'}/));
+		
+		# if we got this far, it fully matched, and we need to do the replace/command/stop/once
+		my $expands = $extra;
+		$expands->{'M'} = $message,;
+		$expands->{'T'} = (defined($server)) ? $server->{'tag'} : '';
+		$expands->{'C'} = $channelname;
+		$expands->{'N'} = $nickname;
+		$expands->{'A'} = $address;
+		$expands->{'I'} = ((!defined($address)) ? '' : substr($address,0,index($address,'@')));
+		$expands->{'H'} = ((!defined($address)) ? '' : substr($address,index($address,'@')+1));
+		$expands->{'$'} = '$';
+		$expands->{';'} = ';';
+
+		if (defined($trigger->{'replace'})) { # it's a -replace
+			$message =~ s/$trigger->{'compregexp'}/do_expands($trigger->{'compreplace'},$expands,$message)/ge;
+			$changed = 1;
+		}
+		
+		if ($trigger->{'command'}) { # it's a (nonempty) -command
+			my $command = $trigger->{'command'};
+			# $1 = the stuff behind the $ we want to expand: a number, or a character from %expands
+			$command = do_expands($command, $expands, $message);
+			
+			if (defined($server)) {
+				if (defined($channelname) && $server->channel_find($channelname)) {
+					$context = $server->channel_find($channelname);
+				} else {
+					$context = $server;
+				}
+			} else {
+				$context = undef;
+			}
+			
+			if (defined($context)) {
+				$context->command("eval $command");
+			} else {
+				Irssi::command("eval $command");
+			}
+		}
+
+		if ($trigger->{'debug'}) {
+			print("DEBUG: trigger $condition pmesg=$parammessage message=$message server=$server->{tag} channel=$channelname nick=$nickname address=$address " . join(' ',map {$_ . '=' . $extra->{$_}} keys(%$extra)));
+		}
+		
+		if ($trigger->{'stop'}) {
+			$stopped = 1;
+		}
+		
+		if ($trigger->{'once'}) {
+			# find this trigger in the real trigger list, and remove it
+			for (my $realindex=0; $realindex < scalar(@triggers); $realindex++) {
+				if ($triggers[$realindex] == $trigger) {
+					splice (@triggers,$realindex,1);
+					last;
+				}
+			}
+			$need_rebuild = 1;
+		}
+	}
+
+	if ($need_rebuild) {
+		rebuild();
+		$changed_since_last_save = 1;
+	}
+	if ($stopped) { # stopped with -stop
+		signal_stop();
+	} elsif ($changed) { # changed with -replace
+		$signal->[$parammessage] = $message;
+		signal_continue(@$signal);
+	}
+	$recursion_depth--;
+}
+
+# used in check_signal_message to expand $'s
+# $inthis is a string that can contain $ stuff (like 'foo$1bar$N')
+sub do_expands {
+	my ($inthis, $expands, $from) = @_;
+	# @+ and @- are copied because there are two s/// nested, and the inner needs the $1 and $2,... of the outer one
+	my @plus = @+;
+	my @min = @-;
+	my $p = \@plus; my $m = \@min;
+	$inthis =~ s/\$(\\*(\d+|[^0-9x{]|x[0-9a-fA-F][0-9a-fA-F]|{.*?}))/expand_and_escape($1,$expands,$m,$p,$from)/ge;	
+	return $inthis;
+}
+
+# \ $ and ; need extra escaping because we use eval
+sub expand_and_escape {
+	my $retval = expand(@_);
+	$retval =~ s/([\\\$;])/\\\1/g;
+	return $retval;
+}
+
+# used in do_expands (via expand_and_escape), to_expand is the part after the $
+sub expand {
+	my ($to_expand, $expands, $min, $plus, $from) = @_;
+	if ($to_expand =~ /^\d+$/) { # a number => look up in $vars
+		# from man perlvar:
+		# $3 is the same as "substr $var, $-[3], $+[3] - $-[3])"
+		return ($to_expand > @{$min} ? '' : substr($from,$min->[$to_expand],$plus->[$to_expand]-$min->[$to_expand]));
+	} elsif ($to_expand =~ s/^\\//) { # begins with \, so strip that from to_expand
+		my $exp = expand($to_expand,$expands,$min,$plus,$from); # first expand without \
+		$exp =~ s/([^a-zA-Z0-9])/\\\1/g; # escape non-word chars
+		return $exp;
+	} elsif ($to_expand =~ /^x([0-9a-fA-F]{2})/) { # $xAA
+		return chr(hex($1));
+	} elsif ($to_expand =~ /^{(.*?)}$/) { # ${foo}
+		return expand($1, $expands, $min, $plus, $from);
+	} else { # look up in $expands
+		return $expands->{$to_expand};
+	}
+}
+
+sub check_modes {
+	my ($has_modes, $need_modes) = @_;
+	my $matches;
+	my $switch = 1; # if a '-' if found, will be 0 (meaning the modes should not be set)
+	foreach my $need_mode (split /&/, $need_modes) {
+		$matches = 0;
+		foreach my $char (split //, $need_mode) {
+			if ($char eq '-') {
+				$switch = 0;
+			} elsif ($char eq '+') {
+				$switch = 1;
+			} elsif ((index($has_modes, $char) != -1) == $switch) {
+				$matches = 1;
+				last;
+			}
+		}
+		if (!$matches) {
+			return 0;
+		}
+	}
+	return 1;
+}
+
+# get someones flags from people.pl or friends(_shasta).pl
+sub get_flags {
+	my ($chatnet, $channel, $nick, $address) = @_;
+	my $flags;
+	no strict 'refs';
+	if (defined %{ 'Irssi::Script::people::' }) {
+		if (defined ($channel)) {
+			$flags = (&{ 'Irssi::Script::people::find_local_flags' }($chatnet,$channel,$nick,$address));
+		} else {
+			$flags = (&{ 'Irssi::Script::people::find_global_flags' }($chatnet,$nick,$address));
+		}
+		$flags = join('',keys(%{$flags}));
+	} else {
+		my $shasta;
+		if (defined %{ 'Irssi::Script::friends_shasta::' }) {
+			$shasta = 'friends_shasta';
+		} elsif (defined &{ 'Irssi::Script::friends::get_idx' }) {
+			$shasta = 'friends';
+		} else {
+			return undef;
+		}
+		my $idx = (&{ 'Irssi::Script::'.$shasta.'::get_idx' }($nick, $address));
+		if ($idx == -1) {
+			return '';
+		}
+		$flags = (&{ 'Irssi::Script::'.$shasta.'::get_friends_flags' }($idx,undef));
+		if ($channel) {
+			$flags .= (&{ 'Irssi::Script::'.$shasta.'::get_friends_flags' }($idx,$channel));
+		}
+	}
+	return $flags;
+}
+
+########################################################
+### internal stuff called by manage, needed by above ###
+########################################################
+
+my %mask_to_regexp = ();
+foreach my $i (0..255) {
+    my $ch = chr $i;
+    $mask_to_regexp{$ch} = "\Q$ch\E";
+}
+$mask_to_regexp{'?'} = '(.)';
+$mask_to_regexp{'*'} = '(.*)';
+
+sub compile_trigger {
+	my ($trigger) = @_;
+	my $regexp;
+	
+	if ($trigger->{'regexp'}) {
+		$regexp = $trigger->{'regexp'};
+	} elsif ($trigger->{'pattern'}) {
+		$regexp = $trigger->{'pattern'};
+		$regexp =~ s/(.)/$mask_to_regexp{$1}/g;
+	} else {
+		delete $trigger->{'compregexp'};
+		return;
+	}
+	
+	if ($trigger->{'nocase'}) {
+		$regexp = '(?i)' . $regexp;
+	}
+	
+	$trigger->{'compregexp'} = qr/$regexp/;
+	
+	if(defined($trigger->{'replace'})) {
+		(my $replace = $trigger->{'replace'}) =~ s/\$/\$\$/g;
+		$trigger->{'compreplace'} = Irssi::parse_special($replace);
+	}
+}
+
+# rebuilds triggers_by_type and updates signal binds
+sub rebuild {
+	%triggers_by_type = ();
+	foreach my $trigger (@triggers) {
+		if (!$trigger->{'disabled'}) {
+			if ($trigger->{'all'}) {
+				# -all is an alias for all types in @all_types for which the filters can apply
+ALLTYPES:
+				foreach my $type (@all_types) {
+					# check if all filters can apply to $type
+					foreach my $filter (@{$trigger->{'filters'}}) {
+						if (! grep {$_ eq $type} $filters{$filter->[0]}->{'types'}) {
+							next ALLTYPES;
+						}
+					}
+					push @{$triggers_by_type{$type}}, ($trigger);
+				}
+			}
+			
+			foreach my $type ($trigger->{'all'} ? @notall_types : @trigger_types) {
+				if ($trigger->{$type}) {
+					push @{$triggers_by_type{$type}}, ($trigger);
+				}
+			}
+		}
+	}
+	
+	foreach my $signal (@signals) {
+		my $should_bind = 0;
+		foreach my $type (@{$signal->{'types'}}) {
+			if (defined($triggers_by_type{$type})) {
+				$should_bind = 1;
+			}
+		}
+		if ($should_bind && !$signal->{'bind'}) {
+			signal_add_first($signal->{'signal'}, $signal->{'sub'});
+			$signal->{'bind'} = 1;
+		} elsif (!$should_bind && $signal->{'bind'}) {
+			signal_remove($signal->{'signal'}, $signal->{'sub'});
+			$signal->{'bind'} = 0;
+		}
+	}
+}
+
+################################
+### manage the triggers-list ###
+################################
+
+my $trigger_file; # cached setting
+
+sub sig_setup_changed {
+	$trigger_file = Irssi::settings_get_str('trigger_file');
+}
+
+sub autosave {
+	cmd_save() if ($changed_since_last_save);
+}
+
+# TRIGGER SAVE
+sub cmd_save {
+	my $io = new IO::File $trigger_file, "w";
+	if (defined $io) {
+		$io->print("#Triggers file version $VERSION\n");
+		foreach my $trigger (@triggers) {
+			$io->print(to_string($trigger) . "\n");
+		}
+		$io->close;
+	}
+	Irssi::printformat(MSGLEVEL_CLIENTNOTICE, 'trigger_saved', $trigger_file);
+	$changed_since_last_save = 0;
+}
+
+# save on unload
+sub UNLOAD {
+	cmd_save();
+}
+
+# TRIGGER LOAD
+sub cmd_load {
+	sig_setup_changed(); # make sure we've read the trigger_file setting
+	my $converted = 0;
+	my $io = new IO::File $trigger_file, "r";
+	if (not defined $io) {
+		if (-e $trigger_file) {
+			Irssi::print("Error opening triggers file", MSGLEVEL_CLIENTERROR);
+		}
+		return;
+	}
+	if (defined $io) {
+		@triggers = ();
+		my $text;
+		$text = $io->getline;
+		my $file_version = '';
+		if ($text =~ /^#Triggers file version (.*)\n/) {
+			$file_version = $1;
+		}
+		if ($file_version lt '0.6.1+2') {
+			no strict 'vars';
+			$text .= $_ foreach ($io->getlines);
+			my $rep = eval "$text";
+			if (! ref $rep) {
+				Irssi::print("Error in triggers file");
+				return;
+			}
+			my @old_triggers = @$rep;
+		
+			for (my $index=0;$index < scalar(@old_triggers);$index++) { 
+				my $trigger = $old_triggers[$index];
+	
+				if ($file_version lt '0.6.1') {
+					# convert old names: notices => pubnotices, actions => pubactions
+					foreach $oldname ('notices','actions') {
+						if ($trigger->{$oldname}) {
+							delete $trigger->{$oldname};
+							$trigger->{'pub'.$oldname} = 1;
+							$converted = 1;
+						}
+					}
+				}
+				if ($file_version lt '0.6.1+1' && $trigger->{'modifiers'}) {
+					if ($trigger->{'modifiers'} =~ /i/) {
+						$trigger->{'nocase'} = 1;
+						Irssi::print("Trigger: trigger ".($index+1)." had 'i' in it's modifiers, it has been converted to -nocase");
+					}
+					if ($trigger->{'modifiers'} !~ /^[ig]*$/) {
+						Irssi::print("Trigger: trigger ".($index+1)." had unrecognised modifier '". $trigger->{'modifiers'} ."', which couldn't be converted.");
+					}
+					delete $trigger->{'modifiers'};
+					$converted = 1;
+				}
+				
+				if (defined($trigger->{'replace'}) && ! $trigger->{'regexp'}) {
+					Irssi::print("Trigger: trigger ".($index+1)." had -replace but no -regexp, removed it");
+					splice (@old_triggers,$index,1);
+					$index--; # nr of next trigger now is the same as this one was
+				}
+				
+				# convert to text with compat, and then to new trigger hash
+				$text = to_string($trigger,1);
+				my @args = &shellwords($text . ' a');
+				my $trigger = parse_options({},@args);
+				if ($trigger) {
+					push @triggers, $trigger;
+				}
+			}
+		} else { # new format
+			while ( $text = $io->getline ) {
+				chop($text);
+				my @args = &shellwords($text . ' a');
+				my $trigger = parse_options({},@args);
+				if ($trigger) {
+					push @triggers, $trigger;
+				}
+			}
+		}
+	}
+	Irssi::printformat(MSGLEVEL_CLIENTNOTICE, 'trigger_loaded', $trigger_file);
+	if ($converted) {
+		Irssi::print("Trigger: Triggers file will be in new format next time it's saved.");
+	}
+	rebuild();
+}
+
+# escape for printing with to_string
+# <<abcdef>>      => << 'abcdef' >>
+# <<abc'def>>     => << "abc'def" >>
+# <<abc'def\x02>> => << 'abc'\''def\x02' >>
+sub param_to_string {
+	my ($text) = @_;
+	# avoid ugly escaping if we can use "-quotes without other escaping (no " or \)
+	if ($text =~ /^[^"\\]*'[^"\\]$/) {
+		return ' "' . $text . '" ';
+	}
+	# "'" signs without a (odd number of) \ in front of them, need be to escaped as '\''
+	# this is ugly :(
+	$text =~ s/(^|[^\\](\\\\)*)'/$1'\\''/g;
+	return " '$text' ";
+}
+
+# converts a trigger back to "-switch -options 'foo'" form
+# if $compat, $trigger is in the old format (used to convert)
+sub to_string {
+	my ($trigger, $compat) = @_;
+	my $string;
+	
+	foreach my $switch (@trigger_switches) {
+		if ($trigger->{$switch}) {
+			$string .= '-'.$switch.' ';
+		}
+	}
+	
+	if ($compat) {
+		foreach my $filter (keys(%filters)) {
+			if ($trigger->{$filter}) {
+				$string .= '-' . $filter . param_to_string($trigger->{$filter});
+			}
+		}
+	} else {
+		foreach my $trigfilter (@{$trigger->{'filters'}}) {
+			$string .= '-' . $trigfilter->[0] . param_to_string($trigfilter->[1]);
+		}
+	}
+
+	foreach my $param (@trigger_params) {
+		if ($trigger->{$param} || ($param eq 'replace' && defined($trigger->{'replace'}))) {
+			$string .= '-' . $param . param_to_string($trigger->{$param});
+		}
+	}
+	return $string;
+}
+
+# find a trigger (for REPLACE and DELETE), returns index of trigger, or -1 if not found
+sub find_trigger {
+	my ($data) = @_;
+	if ($data =~ /^[0-9]*$/ and defined($triggers[$data-1])) {
+		return $data-1;
+	} else {
+		for (my $i=0; $i < scalar(@triggers); $i++) {
+			if ($triggers[$i]->{'name'} eq $data) {
+				return $i;
+			}
+		}
+	}
+	Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'trigger_not_found', $data);
+	return -1; # not found
+}
+
+
+# TRIGGER ADD <options>
+sub cmd_add {
+	my ($data, $server, $item) = @_;
+	my @args = shellwords($data . ' a');
+	
+	my $trigger = parse_options({}, @args);
+	if ($trigger) {
+		push @triggers, $trigger;
+		Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'trigger_added', scalar(@triggers), to_string($trigger));
+		rebuild();
+		$changed_since_last_save = 1;
+	}
+}
+
+# TRIGGER CHANGE <nr> <options>
+sub cmd_change {
+	my ($data, $server, $item) = @_;
+	my @args = shellwords($data . ' a');
+	my $index = find_trigger(shift @args);
+	if ($index != -1) {
+		if(parse_options($triggers[$index], @args)) {
+			Irssi::print("Trigger " . ($index+1) ." changed to: ". to_string($triggers[$index]));
+		}
+		rebuild();
+		$changed_since_last_save = 1;
+	}
+}
+
+# parses options for TRIGGER ADD and TRIGGER CHANGE
+# if invalid args returns undef, else changes $thetrigger and returns it
+sub parse_options {
+	my ($thetrigger,@args) = @_;
+	my ($trigger, $option);
+	
+	if (pop(@args) ne 'a') {
+		Irssi::print("Syntax error, probably missing a closing quote", MSGLEVEL_CLIENTERROR);
+		return undef;
+	}
+	
+	%$trigger = %$thetrigger; # make a copy to prevent changing the given trigger if args doesn't parse
+ARGS:	for (my $arg = shift @args; $arg; $arg = shift @args) {
+		# expand abbreviated options, put in $option
+		$arg =~ s/^-//;
+		$option = undef;
+		foreach my $ioption (@trigger_options) {
+			if (index($ioption, $arg) == 0) { # -$opt starts with $arg
+				if ($option) { # another already matched
+					Irssi::print("Ambiguous option: $arg", MSGLEVEL_CLIENTERROR);
+					return undef;
+				}
+				$option = $ioption;
+				last if ($arg eq $ioption); # exact match is unambiguous
+			}
+		}
+		if (!$option) {
+			Irssi::print("Unknown option: $arg", MSGLEVEL_CLIENTERROR);
+			return undef;
+		}
+
+		# -<param> <value> or -no<param>
+		foreach my $param (@trigger_params) {
+			if ($option eq $param) {
+				$trigger->{$param} = shift @args;
+				next ARGS;
+			}
+			if ($option eq 'no'.$param) {
+				$trigger->{$param} = undef;
+				next ARGS;
+			}
+		}
+
+		# -[no]<switch>
+		foreach my $switch (@trigger_switches) {
+			# -<switch>
+			if ($option eq $switch) {
+				$trigger->{$switch} = 1;
+				next ARGS;
+			}
+			# -no<switch>
+			elsif ($option eq 'no'.$switch) {
+				$trigger->{$switch} = undef;
+				next ARGS;
+			}
+		}
+		
+		# -<filter> <value>
+		if ($filters{$option}) {
+			push @{$trigger->{'filters'}}, [$option, shift @args, $filters{$option}->{'sub'}];
+			next ARGS;
+		}
+		
+		# -<nofilter>
+		if ($option =~ /^no(.*)$/ && $filters{$1}) {
+			my $filter = $1;
+			# the new filters are the old grepped for everything except ones with name $filter
+			@{$trigger->{'filters'}} = grep( $_->[0] ne $filter, @{$trigger->{'filters'}} );
+		}
+	}
+	
+	if (defined($trigger->{'replace'}) && ! $trigger->{'regexp'} && !$trigger->{'pattern'}) {
+		Irssi::print("Trigger error: Can't have -replace without -regexp", MSGLEVEL_CLIENTERROR);
+		return undef;
+	}
+
+	if ($trigger->{'pattern'} && $trigger->{'regexp'}) {
+		Irssi::print("Trigger error: Can't have -pattern and -regexp in same trigger", MSGLEVEL_CLIENTERROR);
+		return undef;
+	}
+	
+	# remove types that are implied by -all
+	if ($trigger->{'all'}) {
+		foreach my $type (@all_types) {
+			delete $trigger->{$type};
+		}
+	}
+	
+	# remove types for which the filters don't apply
+	foreach my $type (@trigger_types) {
+		if ($trigger->{$type}) {
+			foreach my $filter (@{$trigger->{'filters'}}) {
+				if (!grep {$_ eq $type} @{$filters{$filter->[0]}->{'types'}}) {
+					Irssi::print("Warning: the filter -" . $filter->[0] . " can't apply to an event of type -$type, so I'm removing that type from this trigger.");
+					delete $trigger->{$type};
+				}
+			}
+		}
+	}
+
+	# check if it has at least one type
+	my $has_a_type;
+	foreach my $type (@trigger_types) {
+		if ($trigger->{$type}) {
+			$has_a_type = 1;
+			last;
+		}
+	}
+	if (!$has_a_type && !$trigger->{'all'}) {
+		Irssi::print("Warning: this trigger doesn't trigger on any type of message. you probably want to add -publics or -all");
+	}
+	
+	compile_trigger($trigger);
+	%$thetrigger = %$trigger; # copy changes to real trigger
+	return $thetrigger;
+}
+
+# TRIGGER DELETE <num>
+sub cmd_del {
+	my ($data, $server, $item) = @_;
+	my @args = shellwords($data);
+	my $index = find_trigger(shift @args);
+	if ($index != -1) {
+		Irssi::print("Deleted ". ($index+1) .": ". to_string($triggers[$index]));
+		splice (@triggers,$index,1);
+		rebuild();
+		$changed_since_last_save = 1;
+	}
+}
+
+# TRIGGER MOVE <num> <num>
+sub cmd_move {
+	my ($data, $server, $item) = @_;
+	my @args = &shellwords($data);
+	my $index = find_trigger(shift @args);
+	if ($index != -1) {
+		my $newindex = shift @args;
+		if ($newindex < 1 || $newindex > scalar(@triggers)) {
+			Irssi::print("$newindex is not a valid trigger number");
+			return;
+		}
+		Irssi::print("Moved from ". ($index+1) ." to $newindex: ". to_string($triggers[$index]));
+		$newindex -= 1; # array starts counting from 0
+		my $trigger = splice (@triggers,$index,1); # remove from old place
+		splice (@triggers,$newindex,0,($trigger)); # insert at new place
+		rebuild();
+		$changed_since_last_save = 1;
+	}
+}
+
+# TRIGGER LIST
+sub cmd_list {
+	Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'trigger_header');
+	my $i=1;
+	foreach my $trigger (@triggers) {
+		Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'trigger_line', $i++, to_string($trigger));
+	}
+}
+
+######################
+### initialisation ###
+######################
+
+command_bind('trigger help',\&cmd_help);
+command_bind('help trigger',\&cmd_help);
+command_bind('trigger add',\&cmd_add);
+command_bind('trigger change',\&cmd_change);
+command_bind('trigger move',\&cmd_move);
+command_bind('trigger list',\&cmd_list);
+command_bind('trigger delete',\&cmd_del);
+command_bind('trigger save',\&cmd_save);
+command_bind('trigger reload',\&cmd_load);
+command_bind 'trigger' => sub {
+    my ( $data, $server, $item ) = @_;
+    $data =~ s/\s+$//g;
+    command_runsub('trigger', $data, $server, $item);
+};
+
+Irssi::signal_add('setup saved', \&autosave);
+Irssi::signal_add('setup changed', \&sig_setup_changed);
+
+# This makes tab completion work
+Irssi::command_set_options('trigger add',join(' ',@trigger_add_options));
+Irssi::command_set_options('trigger change',join(' ',@trigger_options));
+
+Irssi::settings_add_str($IRSSI{'name'}, 'trigger_file', Irssi::get_irssi_dir()."/triggers");
+
+cmd_load();
Index: protocols/skype/t/livetest-bitlbee.sh
===================================================================
--- protocols/skype/t/livetest-bitlbee.sh	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
+++ protocols/skype/t/livetest-bitlbee.sh	(revision 43b1cd7ac2a55145f9a80c5af505308c788f4b90)
@@ -0,0 +1,116 @@
+#!/usr/bin/env bash
+
+start_skyped()
+{
+	python ../skyped.py "$@" > skypedtest.pid
+	while true
+	do
+		[ -e skypedtest.pid ] || break
+		pid=$(sed 's/.*: //' skypedtest.pid)
+		if [ -n "$(ps -p $pid -o pid=)" ]; then
+			sleep 5
+		else
+			start_skyped "$@"
+			break
+		fi
+	done
+}
+
+BITLBEE=$1
+typeset -ix PORT=`echo $2 | egrep '^[0-9]{1,5}$'`
+SCRIPT=$3
+shift 3
+
+[ -n "$SCRIPT" -a -n "$BITLBEE" -a -e "$SCRIPT" -a "$PORT" -ne 0 ] || { echo Syntax: `basename "$0"` bitlbee-executable listening-port test-script test-script-args; exit 1; }
+
+# Create or empty test dir
+mkdir livetest 2>/dev/null || rm livetest/bitlbeetest*.xml bitlbeetest.pid 2>/dev/null
+
+# Run the bee
+echo Running bitlbee...
+$VALGRIND $BITLBEE -n -c bitlbee.conf -d livetest/ -D -P bitlbeetest.pid -p $PORT 2>bitlbee.log &
+sleep 2
+
+# Check if it's really running
+kill -0 `cat bitlbeetest.pid 2>/dev/null ` 2>/dev/null || { echo Failed to run bitlbee daemon on port $PORT; exit 1; }
+
+if [ -z "$TUNNELED_MODE" ]; then
+	# Set up skyped
+
+	rm -rf etc
+	mkdir etc
+	cd etc
+	cp ../../skyped.cnf .
+	cp ~/.skyped/skyped.cert.pem .
+	cp ~/.skyped/skyped.key.pem .
+	cd ..
+	echo "[skyped]" > skyped.conf
+	echo "username = $TEST_SKYPE_ID" >> skyped.conf
+	SHA1=`which sha1sum`
+	if [ -z "$SHA1" ]; then
+		SHA1=`which sha1`
+	fi
+	if [ -z "$SHA1" ]; then
+		echo Test failed
+		echo "(Can't compute password for skyped.conf)"
+		exit 77
+	fi
+	echo "password = $(echo -n $TEST_SKYPE_PASSWORD|$SHA1|sed 's/ *-$//')" >> skyped.conf
+	# we use ~ here to test that resolve that syntax works
+	echo "cert = $(pwd|sed "s|$HOME|~|")/etc/skyped.cert.pem" >> skyped.conf
+	echo "key = $(pwd|sed "s|$HOME|~|")/etc/skyped.key.pem" >> skyped.conf
+	echo "port = 2727" >> skyped.conf
+
+	# Run skyped
+	start_skyped -c skyped.conf -l skypedtest.log &
+	sleep 2
+fi
+
+if [ "$TUNNELED_MODE" = "yes" ]; then
+	rm -f tunnel.pid
+	if [ -n "$TUNNEL_SCRIPT" ]; then
+		$TUNNEL_SCRIPT &
+		echo $! > tunnel.pid
+		sleep 5
+	fi
+fi
+
+# Run the test
+echo Running test script...
+"$SCRIPT" $*
+RET=$?
+
+if [ -z "$TUNNELED_MODE" ]; then
+	# skyped runs on another host: no means to kill it
+	# Kill skyped
+	killall -TERM skype
+	if [ -f skypedtest.pid ]; then
+		pid=$(sed 's/.*: //' skypedtest.pid)
+		rm skypedtest.pid
+		[ -n "$(ps -p $pid -o pid=)" ] && kill -TERM $pid
+	fi
+fi
+
+if [ "$TUNNELED_MODE" = "yes" ]; then
+	if [ -n "$TUNNEL_SCRIPT" ]; then
+		cat tunnel.pid >> /tmp/tunnel.pid
+		kill `cat tunnel.pid`
+		rm -f tunnel.pid
+	fi
+fi
+
+# Kill bee
+echo Killing bitlbee...
+kill `cat bitlbeetest.pid`
+
+if [ "$TUNNELED_MODE" = "yes" ]; then
+	# give the skyped a chance to timeout
+	sleep 30
+fi
+
+# Return test result
+[ $RET -eq 0 ] && echo Test passed
+[ $RET -ne 0 ] && echo Test failed
+[ $RET -eq 22 ] && echo '(timed out)'
+[ $RET -eq 66 ] && echo '(environment variables missing)'
+exit $RET
