aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile39
-rw-r--r--bitlbee.h4
-rwxr-xr-xconfigure35
-rw-r--r--debian/bitlbee-plugin-skype.docs2
-rw-r--r--debian/changelog21
-rw-r--r--debian/control27
-rwxr-xr-xdebian/rules16
-rw-r--r--debian/skyped.README.Debian18
-rw-r--r--debian/skyped.docs2
-rw-r--r--doc/CHANGES15
-rw-r--r--doc/user-guide/commands.xml6
-rw-r--r--doc/user-guide/help.xsl2
-rw-r--r--doc/user-guide/misc.xml6
-rw-r--r--doc/user-guide/quickstart.xml62
-rw-r--r--ipc.c9
-rw-r--r--irc.c8
-rw-r--r--irc.h5
-rw-r--r--irc_commands.c8
-rw-r--r--irc_im.c23
-rw-r--r--irc_send.c59
-rw-r--r--lib/http_client.c69
-rw-r--r--lib/ssl_client.h10
-rw-r--r--lib/ssl_gnutls.c26
-rw-r--r--lib/ssl_nss.c1
-rw-r--r--lib/ssl_openssl.c9
-rw-r--r--lib/xmltree.c47
-rw-r--r--nick.c8
-rw-r--r--otr.c448
-rw-r--r--protocols/jabber/io.c4
-rw-r--r--protocols/jabber/jabber.c3
-rw-r--r--protocols/jabber/s5bytestream.c7
-rw-r--r--protocols/msn/ns.c3
-rw-r--r--protocols/msn/sb.c6
-rw-r--r--protocols/msn/soap.c21
-rw-r--r--protocols/nogaim.c4
-rw-r--r--protocols/oscar/chat.c3
-rw-r--r--protocols/oscar/chatnav.c12
-rw-r--r--protocols/oscar/icq.c7
-rw-r--r--protocols/oscar/im.c7
-rw-r--r--protocols/oscar/misc.c3
-rw-r--r--protocols/oscar/oscar.c99
-rw-r--r--protocols/oscar/rxqueue.c4
-rw-r--r--protocols/oscar/service.c10
-rw-r--r--protocols/purple/purple.c4
-rw-r--r--protocols/skype/.bzrignore19
-rw-r--r--protocols/skype/.mailmap1
-rw-r--r--protocols/skype/HACKING26
-rw-r--r--protocols/skype/Makefile107
-rw-r--r--protocols/skype/NEWS131
-rw-r--r--protocols/skype/README488
-rw-r--r--protocols/skype/asciidoc.conf21
-rw-r--r--protocols/skype/client.sh1
-rw-r--r--protocols/skype/skype.c1566
-rw-r--r--protocols/skype/skyped.cnf40
-rw-r--r--protocols/skype/skyped.conf.dist10
-rw-r--r--protocols/skype/skyped.py495
-rw-r--r--protocols/skype/skyped.txt52
-rw-r--r--protocols/skype/t/Makefile33
-rw-r--r--protocols/skype/t/bitlbee.conf0
-rwxr-xr-xprotocols/skype/t/irssi/livetest-irssi.sh109
-rw-r--r--protocols/skype/t/irssi/skype-call.test13
-rw-r--r--protocols/skype/t/irssi/skype-info.test12
-rw-r--r--protocols/skype/t/irssi/skype-login.test10
-rw-r--r--protocols/skype/t/irssi/skype-msg.test17
-rw-r--r--protocols/skype/t/irssi/trigger.pl1225
-rwxr-xr-xprotocols/skype/t/livetest-bitlbee.sh116
-rw-r--r--protocols/twitter/twitter.c80
-rw-r--r--protocols/twitter/twitter.h12
-rw-r--r--protocols/twitter/twitter_lib.c347
-rw-r--r--protocols/twitter/twitter_lib.h2
-rw-r--r--protocols/yahoo/libyahoo2.c61
-rw-r--r--query.c6
-rw-r--r--root_commands.c271
-rw-r--r--set.c8
-rw-r--r--set.h4
-rw-r--r--storage_xml.c10
76 files changed, 5747 insertions, 728 deletions
diff --git a/Makefile b/Makefile
index cb682287..45e858cf 100644
--- a/Makefile
+++ b/Makefile
@@ -26,8 +26,11 @@ endif
# Expansion of variables
subdirobjs = $(foreach dir,$(subdirs),$(dir)/$(dir).o)
-all: $(OUTFILE) $(OTR_PI) systemd
+all: $(OUTFILE) $(OTR_PI) $(SKYPE_PI) systemd
$(MAKE) -C doc
+ifdef SKYPE_PI
+ $(MAKE) -C protocols/skype doc
+endif
uninstall: uninstall-bin uninstall-doc
@echo -e '\nmake uninstall does not remove files in '$(DESTDIR)$(ETCDIR)', you can use make uninstall-etc to do that.\n'
@@ -70,16 +73,22 @@ lcov: check
install-doc:
$(MAKE) -C doc install
+ifdef SKYPE_PI
+ $(MAKE) -C protocols/skype install-doc
+endif
uninstall-doc:
$(MAKE) -C doc uninstall
+ifdef SKYPE_PI
+ $(MAKE) -C protocols/skype uninstall-doc
+endif
install-bin:
- mkdir -p $(DESTDIR)$(BINDIR)
- install -m 0755 $(OUTFILE) $(DESTDIR)$(BINDIR)/$(OUTFILE)
+ mkdir -p $(DESTDIR)$(SBINDIR)
+ install -m 0755 $(OUTFILE) $(DESTDIR)$(SBINDIR)/$(OUTFILE)
uninstall-bin:
- rm -f $(DESTDIR)$(BINDIR)/$(OUTFILE)
+ rm -f $(DESTDIR)$(SBINDIR)/$(OUTFILE)
install-dev:
mkdir -p $(DESTDIR)$(INCLUDEDIR)
@@ -103,12 +112,25 @@ uninstall-etc:
rm -f $(DESTDIR)$(ETCDIR)/bitlbee.conf
-rmdir $(DESTDIR)$(ETCDIR)
-install-plugins:
+install-plugins: install-plugin-otr install-plugin-skype
+
+install-plugin-otr:
ifdef OTR_PI
mkdir -p $(DESTDIR)$(PLUGINDIR)
install -m 0755 otr.so $(DESTDIR)$(PLUGINDIR)
endif
+install-plugin-skype:
+ifdef SKYPE_PI
+ mkdir -p $(DESTDIR)$(PLUGINDIR)
+ install -m 0755 skype.so $(DESTDIR)$(PLUGINDIR)
+ mkdir -p $(DESTDIR)$(ETCDIR)/../skyped $(DESTDIR)$(BINDIR)
+ install -m 0644 $(SRCDIR)protocols/skype/skyped.cnf $(DESTDIR)$(ETCDIR)/../skyped/skyped.cnf
+ install -m 0644 $(SRCDIR)protocols/skype/skyped.conf.dist $(DESTDIR)$(ETCDIR)/../skyped/skyped.conf
+ install -m 0755 $(SRCDIR)protocols/skype/skyped.py $(DESTDIR)$(BINDIR)/skyped
+ make -C protocols/skype install-doc
+endif
+
systemd:
ifdef SYSTEMDSYSTEMUNITDIR
sed 's|@sbindir@|$(BINDIR)|' init/bitlbee.service.in > init/bitlbee.service
@@ -138,7 +160,11 @@ $(subdirs):
$(OTR_PI): %.so: $(SRCDIR)%.c
@echo '*' Building plugin $@
- @$(CC) $(CFLAGS) $(OTRFLAGS) -fPIC -shared $(LDFLAGS) $< -o $@
+ @$(CC) $(CFLAGS) -fPIC -shared $(LDFLAGS) $< -o $@ $(OTRFLAGS)
+
+$(SKYPE_PI): $(SRCDIR)protocols/skype/skype.c
+ @echo '*' Building plugin skype
+ @$(CC) $(CFLAGS) -fPIC -shared $< -o $@
$(objects): %.o: $(SRCDIR)%.c
@echo '*' Compiling $<
@@ -163,3 +189,4 @@ helloworld:
@echo Hello World
-include .depend/*.d
+# DO NOT DELETE
diff --git a/bitlbee.h b/bitlbee.h
index e5cfa6ee..ace78880 100644
--- a/bitlbee.h
+++ b/bitlbee.h
@@ -34,10 +34,10 @@
#define _WIN32_WINNT 0x0501
#define PACKAGE "BitlBee"
-#define BITLBEE_VERSION "3.0.3"
+#define BITLBEE_VERSION "3.0.4"
#define VERSION BITLBEE_VERSION
#define BITLBEE_VER(a,b,c) (((a) << 16) + ((b) << 8) + (c))
-#define BITLBEE_VERSION_CODE BITLBEE_VER(3, 0, 3)
+#define BITLBEE_VERSION_CODE BITLBEE_VER(3, 0, 4)
#define MAX_STRING 511
diff --git a/configure b/configure
index 77dc560a..8fd61af5 100755
--- a/configure
+++ b/configure
@@ -8,7 +8,8 @@
##############################
prefix='/usr/local/'
-bindir='$prefix/sbin/'
+bindir='$prefix/bin/'
+sbindir='$prefix/sbin/'
etcdir='$prefix/etc/bitlbee/'
mandir='$prefix/share/man/'
datadir='$prefix/share/bitlbee/'
@@ -18,7 +19,7 @@ includedir='$prefix/include/bitlbee/'
systemdsystemunitdir=''
libevent='/usr/'
pidfile='/var/run/bitlbee.pid'
-ipcsocket='/var/run/bitlbee.sock'
+ipcsocket=''
pcdir='$prefix/lib/pkgconfig'
systemlibdirs="/lib /lib64 /usr/lib /usr/lib64 /usr/local/lib /usr/local/lib64"
@@ -27,7 +28,6 @@ jabber=1
oscar=1
yahoo=1
twitter=1
-twitter=1
purple=0
debug=0
@@ -35,6 +35,7 @@ strip=1
gcov=0
plugins=1
otr=0
+skype=0
events=glib
ldap=0
@@ -58,6 +59,7 @@ Option Description Default
--prefix=... Directories to put files in $prefix
--bindir=... $bindir
+--sbindir=... $sbindir
--etcdir=... $etcdir
--mandir=... $mandir
--datadir=... $datadir
@@ -65,7 +67,6 @@ Option Description Default
--systemdsystemunitdir=... $systemdsystemunitdir
--pidfile=... $pidfile
--config=... $config
---ipcsocket=... $ipcsocket
--msn=0/1 Disable/enable MSN part $msn
--jabber=0/1 Disable/enable Jabber part $jabber
@@ -82,6 +83,8 @@ Option Description Default
--plugins=0/1 Disable/enable plugins support $plugins
--otr=0/1/auto/plugin
Disable/enable OTR encryption support $otr
+--skype=0/1/plugin
+ Disable/enable Skype support $skype
--events=... Event handler (glib, libevent) $events
--ssl=... SSL library to use (gnutls, nss, openssl, bogus, auto)
@@ -97,6 +100,7 @@ done
# Expand $prefix and get rid of double slashes
bindir=`eval echo "$bindir/" | sed 's/\/\{1,\}/\//g'`
+sbindir=`eval echo "$sbindir/" | sed 's/\/\{1,\}/\//g'`
etcdir=`eval echo "$etcdir/" | sed 's/\/\{1,\}/\//g'`
mandir=`eval echo "$mandir/" | sed 's/\/\{1,\}/\//g'`
datadir=`eval echo "$datadir/" | sed 's/\/\{1,\}/\//g'`
@@ -109,10 +113,13 @@ pidfile=`eval echo "$pidfile" | sed 's/\/\{1,\}/\//g'`
ipcsocket=`eval echo "$ipcsocket" | sed 's/\/\{1,\}/\//g'`
pcdir=`eval echo "$pcdir" | sed 's/\/\{1,\}/\//g'`
+protocols_mods=""
+
cat<<EOF>Makefile.settings
## BitlBee settings, generated by configure
PREFIX=$prefix
BINDIR=$bindir
+SBINDIR=$sbindir
ETCDIR=$etcdir
MANDIR=$mandir
DATADIR=$datadir
@@ -322,6 +329,8 @@ EOF
}
RESOLV_TESTCODE='
+#include <sys/types.h>
+#include <netinet/in.h>
#include <arpa/nameser.h>
#include <resolv.h>
@@ -334,11 +343,18 @@ int main()
detect_resolv_dynamic()
{
+ case "$arch" in
+ FreeBSD )
+ # In FreeBSD res_* routines are present in libc.so
+ LIBRESOLV=;;
+ * )
+ LIBRESOLV=-lresolv;;
+ esac
TMPFILE=$(mktemp /tmp/bitlbee-configure.XXXXXX)
ret=1
- echo "$RESOLV_TESTCODE" | $CC -o $TMPFILE -x c - -lresolv >/dev/null 2>/dev/null
+ echo "$RESOLV_TESTCODE" | $CC -o $TMPFILE -x c - $LIBRESOLV >/dev/null 2>/dev/null
if [ "$?" = "0" ]; then
- echo 'EFLAGS+=-lresolv' >> Makefile.settings
+ echo "EFLAGS+=$LIBRESOLV" >> Makefile.settings
ret=0
fi
@@ -537,6 +553,11 @@ elif [ "$otr" = "plugin" ]; then
echo 'OTR_PI=otr.so' >> Makefile.settings
fi
+if [ "$skype" = "1" -o "$skype" = "plugin" ]; then
+ echo 'SKYPE_PI=skype.so' >> Makefile.settings
+ protocols_mods="$protocol_mods skype(plugin)"
+fi
+
if [ ! -e doc/user-guide/help.txt ] && ! type xmlto > /dev/null 2> /dev/null; then
echo
echo 'WARNING: Building from an unreleased source tree without prebuilt helpfile.'
@@ -750,7 +771,7 @@ echo ' Using SSL library: '$ssl
#echo ' Building with these storage backends: '$STORAGES
if [ -n "$protocols" ]; then
- echo ' Building with these protocols:' $protocols
+ echo ' Building with these protocols:' $protocols$protocols_mods
case "$protocols" in
*purple*)
echo " Note that BitlBee-libpurple is supported on a best-effort basis. It's"
diff --git a/debian/bitlbee-plugin-skype.docs b/debian/bitlbee-plugin-skype.docs
new file mode 100644
index 00000000..3f99862a
--- /dev/null
+++ b/debian/bitlbee-plugin-skype.docs
@@ -0,0 +1,2 @@
+protocols/skype/NEWS
+protocols/skype/README
diff --git a/debian/changelog b/debian/changelog
index a7d008f3..5702b417 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,24 @@
+bitlbee (3.0.4-1) unstable; urgency=low
+
+ * New upstream release.
+ * Added bitlbee-plugin-skype and skyped packages, now part of BitlBee
+ instead of a separate package.
+ * Fixed dependencies of bitlbee-plugin-otr package to not break with
+ binary MTUs. (Closes: #651612)
+ * ^B and some other things are stripped in outgoing XMPP stanzas.
+ (Closes: #507856)
+ * OTR module linking fix. Not with the fix from the Debian bug but with
+ one from bugs.bitlbee.org. I hope that covers it. (Closes: #646369)
+ * Closing a few old bugs that were filed against the Debian package
+ instead of upstream:
+ - Joining password-protected MUCs is working for a while already, set
+ the password using "chan set". (Closes: #615624)
+ - "Headline:" msgs (Closes: #605459)
+ - Yahoo! was fixed long ago and Etch is deprecated. (Closes: #476529)
+ - identi.ca support is documented. (Closes: #613789)
+
+ -- Wilmer van der Gaast <wilmer@gaast.net> Sun, 11 Dec 2011 16:53:31 +0000
+
bitlbee (3.0.3-1) unstable; urgency=low
* New upstream release. (Skipped 3.0.2, sorry!)
diff --git a/debian/control b/debian/control
index 497ed91a..41fc0366 100644
--- a/debian/control
+++ b/debian/control
@@ -4,7 +4,7 @@ Priority: optional
Maintainer: Wilmer van der Gaast <wilmer@gaast.net>
Uploaders: Jelmer Vernooij <jelmer@samba.org>
Standards-Version: 3.9.1
-Build-Depends: libglib2.0-dev (>= 2.4), libevent-dev, libgnutls-dev | libnss-dev (>= 1.6), po-debconf, libpurple-dev, libotr2-dev, debhelper (>= 6.0.7~)
+Build-Depends: libglib2.0-dev (>= 2.4), libevent-dev, libgnutls-dev | libnss-dev (>= 1.6), po-debconf, libpurple-dev, libotr2-dev, debhelper (>= 6.0.7~), asciidoc
Homepage: http://www.bitlbee.org/
Vcs-Bzr: http://code.bitlbee.org/bitlbee/
DM-Upload-Allowed: yes
@@ -60,7 +60,7 @@ Description: An IRC to other chat networks gateway (dev files)
Package: bitlbee-plugin-otr
Architecture: any
-Depends: ${misc:Depends}, ${shlibs:Depends}, bitlbee (= ${bee:Version}) | bitlbee-libpurple (= ${bee:Version}), bitlbee-common (= ${bee:Version})
+Depends: ${misc:Depends}, ${shlibs:Depends}, bitlbee (>= ${bee:Version}) | bitlbee-libpurple (>= ${bee:Version}), bitlbee (<< ${bee:Version}.1~) | bitlbee-libpurple (<< ${bee:Version}.1~), bitlbee-common (= ${bee:Version})
Description: An IRC to other chat networks gateway (OTR plugin)
This program can be used as an IRC server which forwards everything you
say to people on other chat networks: Jabber (which includes Google Talk
@@ -68,3 +68,26 @@ Description: An IRC to other chat networks gateway (OTR plugin)
.
This package contains a plugin that adds support for Off-The-Record
encryption of instant messages.
+
+Package: bitlbee-plugin-skype
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}, bitlbee (>= ${bee:Version}) | bitlbee-libpurple (>= ${bee:Version}), bitlbee (<< ${bee:Version}.1~) | bitlbee-libpurple (<< ${bee:Version}.1~)
+Recommends: skyped
+Description: An IRC to other chat networks gateway (Skype plugin)
+ This program can be used as an IRC server which forwards everything you
+ say to people on other chat networks: Jabber (which includes Google Talk
+ and Facebook Chat), ICQ, AIM, MSN, Yahoo! and Twitter/Identica/Status.net.
+ .
+ This package contains a plugin that adds support for the Skype IM network.
+ You need to download and install the Skype client for this to work.
+
+Package: skyped
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}, python (>= 2.5), python-gnutls, python-skype (>=0.9.28.7)
+Recommends: skype
+Description: Daemon to control Skype remotely
+ Daemon to control the GUI Skype client. Currently required to control Skype
+ from the BitlBee IRC2IM gateway. Skyped and Skype can run on a different
+ host than the BitlBee server, the communication is encrypted.
+ .
+ You need to download and install the Skype client for this to work.
diff --git a/debian/rules b/debian/rules
index 2afadd90..57d60090 100755
--- a/debian/rules
+++ b/debian/rules
@@ -10,6 +10,7 @@
# Include the bitlbee-libpurple variant and OTR plugin by default
BITLBEE_LIBPURPLE ?= 1
BITLBEE_OTR ?= plugin
+BITLBEE_SKYPE ?= plugin
BITLBEE_CONFIGURE_FLAGS ?=
DEBUG ?= 0
@@ -26,12 +27,16 @@ ifneq ($(BITLBEE_OTR),plugin)
DH_OPTIONS += -Nbitlbee-plugin-otr
endif
+ifneq ($(BITLBEE_SKYPE),plugin)
+DH_OPTIONS += -Nbitlbee-plugin-skype -Nskyped
+endif
+
build: build-stamp
build-stamp:
dh_testdir
mkdir -p debian/build-native
- ROOT=$$PWD; cd debian/build-native; $(BITLBEE_CONFIGURE_VERSION) $$ROOT/configure --debug=$(DEBUG) --prefix=/usr --etcdir=/etc/bitlbee --events=libevent --otr=$(BITLBEE_OTR) $(BITLBEE_CONFIGURE_FLAGS)
+ ROOT=$$PWD; cd debian/build-native; $(BITLBEE_CONFIGURE_VERSION) $$ROOT/configure --debug=$(DEBUG) --prefix=/usr --etcdir=/etc/bitlbee --events=libevent --otr=$(BITLBEE_OTR) --skype=$(BITLBEE_SKYPE) $(BITLBEE_CONFIGURE_FLAGS)
$(MAKE) -C debian/build-native
ifeq ($(BITLBEE_LIBPURPLE),1)
@@ -63,7 +68,14 @@ install: build
$(MAKE) -C debian/build-native install-bin DESTDIR=`pwd`/debian/bitlbee
$(MAKE) -C debian/build-native install-etc install-doc DESTDIR=`pwd`/debian/bitlbee-common
$(MAKE) -C debian/build-native install-dev DESTDIR=`pwd`/debian/bitlbee-dev
- $(MAKE) -C debian/build-native install-plugins DESTDIR=`pwd`/debian/bitlbee-plugin-otr
+ $(MAKE) -C debian/build-native install-plugin-otr DESTDIR=`pwd`/debian/bitlbee-plugin-otr
+ $(MAKE) -C debian/build-native install-plugin-skype DESTDIR=`pwd`/debian/skyped
+
+ mkdir -p debian/bitlbee-plugin-skype/usr
+ mv debian/skyped/usr/lib debian/bitlbee-plugin-skype/usr
+
+ mkdir -p debian/skyped/usr/share/man/man1
+ mv debian/bitlbee-common/usr/share/man/man1/skyped* debian/skyped/usr/share/man/man1
ifeq ($(BITLBEE_LIBPURPLE),1)
$(MAKE) -C debian/build-libpurple install-bin DESTDIR=`pwd`/debian/bitlbee-libpurple
diff --git a/debian/skyped.README.Debian b/debian/skyped.README.Debian
new file mode 100644
index 00000000..502e6147
--- /dev/null
+++ b/debian/skyped.README.Debian
@@ -0,0 +1,18 @@
+bitlbee-skype for Debian
+------------------------
+
+The upstream package installs global configuration files in /etc. Since
+configuration cannot be used by multiple users, however, the default for
+Debian packages is in ~/.skyped/. Please copy the configuration files
+from ./examples to ~/.skyped/.
+
+You will also need to change the configuration values in skyped.conf
+and skyped.cnf to suit your needs and create the keys. More information
+in the respective files as well as the README.gz.
+
+Without these measures, skyped will NOT work.
+
+You need to download the official skype client for this to be of any
+use. Go to http://www.skype.com/.
+
+ -- David Spreen <netzwurm@debian.org>, Thu, 2 Apr 2009 15:01:25 -0700
diff --git a/debian/skyped.docs b/debian/skyped.docs
new file mode 100644
index 00000000..3f99862a
--- /dev/null
+++ b/debian/skyped.docs
@@ -0,0 +1,2 @@
+protocols/skype/NEWS
+protocols/skype/README
diff --git a/doc/CHANGES b/doc/CHANGES
index 31be73f4..58ea5492 100644
--- a/doc/CHANGES
+++ b/doc/CHANGES
@@ -3,6 +3,21 @@ found in the bzr commit logs, for example you can try:
http://bugs.bitlbee.org/bitlbee/timeline?daysback=90&changeset=on
+Version 3.0.4:
+- Merged Skype support. This used to be a separate plugin, and it still is,
+ but by including it with BitlBee by default it will be easier to keep it
+ in sync with changes to BitlBee.
+- Fixed a file descriptor leak bug that may have caused strange behaviour
+ in BitlBee sessions running for a long time.
+- Now fetches Twitter mentions as well if the "fetch_mentions" account
+ setting is enabled.
+- With t.co now all over Twitter, show the original (but truncated) URL
+ between <brackets>.
+- Fixed MSN Messenger login issues ("timeout" while fetching buddy list).
+- Another (related) GnuTLS compatibility fix (now 2.13+?).
+
+Finished 4 Dec 2011 (Exactly 6 years since 1.0!)
+
Version 3.0.3:
- Fixed Twitter compatibility. (The API call used to get the following list
was deprecated.)
diff --git a/doc/user-guide/commands.xml b/doc/user-guide/commands.xml
index a3b68fa9..3329c9b3 100644
--- a/doc/user-guide/commands.xml
+++ b/doc/user-guide/commands.xml
@@ -851,9 +851,9 @@
</para>
<variablelist>
- <varlistentry><term>undo [&lt;id&gt;]</term><listitem><para>Delete your last Tweet (or one with the given ID)</para></listitem></varlistentry>
- <varlistentry><term>rt &lt;screenname|id&gt;</term><listitem><para>Retweet someone's last Tweet (or one with the given ID)</para></listitem></varlistentry>
- <varlistentry><term>reply &lt;screenname|id&gt;</term><listitem><para>Reply to a Tweet (with a reply-to reference)</para></listitem></varlistentry>
+ <varlistentry><term>undo #[&lt;id&gt;]</term><listitem><para>Delete your last Tweet (or one with the given ID)</para></listitem></varlistentry>
+ <varlistentry><term>rt &lt;screenname|#id&gt;</term><listitem><para>Retweet someone's last Tweet (or one with the given ID)</para></listitem></varlistentry>
+ <varlistentry><term>reply &lt;screenname|#id&gt;</term><listitem><para>Reply to a Tweet (with a reply-to reference)</para></listitem></varlistentry>
<varlistentry><term>follow &lt;screenname&gt;</term><listitem><para>Start following a person</para></listitem></varlistentry>
<varlistentry><term>unfollow &lt;screenname&gt;</term><listitem><para>Stop following a person</para></listitem></varlistentry>
<varlistentry><term>post &lt;message&gt;</term><listitem><para>Post a tweet</para></listitem></varlistentry>
diff --git a/doc/user-guide/help.xsl b/doc/user-guide/help.xsl
index b7e3c371..5aa16e4f 100644
--- a/doc/user-guide/help.xsl
+++ b/doc/user-guide/help.xsl
@@ -72,9 +72,11 @@
</xsl:template>
<xsl:template match="command-list">
+ <xsl:text>These are all root commands. See _b_help &lt;command name&gt;_b_ for more details on each command.&#10;&#10;</xsl:text>
<xsl:for-each select="../bitlbee-command">
<xsl:text> * _b_</xsl:text><xsl:value-of select="@name"/><xsl:text>_b_ - </xsl:text><xsl:value-of select="short-description"/><xsl:text>&#10;</xsl:text>
</xsl:for-each>
+ <xsl:text>&#10;Most commands can be shortened. For example instead of _b_account list_b_, try _b_ac l_b_.&#10;</xsl:text>
<xsl:text>&#10;</xsl:text>
</xsl:template>
diff --git a/doc/user-guide/misc.xml b/doc/user-guide/misc.xml
index 1aceab56..e081fc74 100644
--- a/doc/user-guide/misc.xml
+++ b/doc/user-guide/misc.xml
@@ -190,9 +190,9 @@ also have to set the <emphasis>account</emphasis> setting. Example:
</para>
<ircexample>
- <ircline nick="wilmer">chan set &amp;wlm fill_by account</ircline>
+ <ircline nick="wilmer">chan &amp;wlm set fill_by account</ircline>
<ircline nick="root">fill_by = `account'</ircline>
- <ircline nick="wilmer">chan set &amp;wlm account msn</ircline>
+ <ircline nick="wilmer">chan &amp;wlm set account msn</ircline>
<ircline nick="root">account = `msn'</ircline>
</ircexample>
@@ -203,7 +203,7 @@ also/just offline contacts. Example:
</para>
<ircexample>
- <ircline nick="wilmer">chan set &amp;offline show_users offline</ircline>
+ <ircline nick="wilmer">chan &amp;offline set show_users offline</ircline>
<ircline nick="root">show_users = `offline'</ircline>
</ircexample>
diff --git a/doc/user-guide/quickstart.xml b/doc/user-guide/quickstart.xml
index 80c16ffb..23b0bf9b 100644
--- a/doc/user-guide/quickstart.xml
+++ b/doc/user-guide/quickstart.xml
@@ -52,43 +52,7 @@ When you are finished adding your account(s) use the <emphasis>account on</empha
</sect1>
<sect1 id="quickstart3">
-<title>Managing Contact Lists: Rename</title>
-
-<!--quickstart3-->
-<para>
-<emphasis>Step Three: Managing Contact Lists: Rename</emphasis>
-</para>
-
-<para>
-Now BitlBee logs in and downloads the contact list from the IM server. In a few seconds, all your on-line buddies should show up in the control channel.
-</para>
-
-<para>
-BitlBee will convert names into IRC-friendly form (for instance: tux@example.com will be given the nickname tux). If you have more than one person who would have the same name by this logic (for instance: tux@example.com and tux@bitlbee.org) the second one to log on will be tux_. The same is true if you have a tux log on to AOL and a tux log on from Yahoo.
-</para>
-
-<para>
-It would be easy to get these two mixed up, so BitlBee has a <emphasis>rename</emphasis> command to change the nickname into something more suitable: <emphasis>rename &lt;oldnick&gt; &lt;newnick&gt;</emphasis>
-</para>
-
-<ircexample>
- <ircline nick="you">rename tux_ bitlbeetux</ircline>
- <ircaction nick="tux_">is now known as <emphasis>bitlbeetux</emphasis></ircaction>
- <ircline nick="root">Nick successfully changed</ircline>
-</ircexample>
-
-<para>
-When finished, type <emphasis>help quickstart4</emphasis> to continue.
-</para>
-
-</sect1>
-
-<sect1 id="quickstart4">
-<title>Step Four: Managing Contact Lists: Add and Remove.</title>
-
-<para>
-<emphasis>Step Four: Managing Contact Lists: Add and Remove.</emphasis>
-</para>
+<title>Step Four: Managing Contact Lists: Add, Remove and Rename</title>
<para>
Now you might want to add some contacts, to do this we will use the <emphasis>add</emphasis> command. It needs two arguments: a connection ID (which can be a number (try <emphasis>account list</emphasis>), protocol name or (part of) the screenname) and the user's handle. It is used in the following way: <emphasis>add &lt;connection&gt; &lt;handle&gt;</emphasis>
@@ -104,16 +68,20 @@ In this case r2d2 is online, since he/she joins the channel immediately. If the
</para>
<para>
-Lets say you accidentally added r2d3@example.com rather than r2d2@example.com, or maybe you just want to remove a user from your list because you never talk to them. To remove a name you will want to use the <emphasis>remove</emphasis> command: <emphasis>remove &lt;nick&gt;</emphasis>
+Lets say you accidentally added r2d3@example.com rather than r2d2@example.com, or maybe you just want to remove a user from your list because you never talk to them. To remove a name you will want to use the <emphasis>remove</emphasis> command: <emphasis>remove r2d3</emphasis>
</para>
<para>
-When finished, type <emphasis>help quickstart5</emphasis> to continue.
+Finally, if you have multiple users with similar names you may use the <emphasis>rename</emphasis> command to make it easier to remember: <emphasis>rename r2d2_ r2d2_aim</emphasis>
+</para>
+
+<para>
+When finished, type <emphasis>help quickstart4</emphasis> to continue.
</para>
</sect1>
-<sect1 id="quickstart5">
+<sect1 id="quickstart4">
<title>Chatting</title>
<para>
@@ -138,16 +106,24 @@ If you prefer chatting in a separate window, use the <emphasis>/msg</emphasis> o
</para>
<para>
-You know the basics. If you want to get to know more about BitlBee, please type <emphasis>help quickstart6</emphasis>.
+You know the basics. If you want to know about some of the neat features BitlBee offers, please type <emphasis>help quickstart5</emphasis>.
</para>
</sect1>
-<sect1 id="quickstart6">
+<sect1 id="quickstart5">
<title>Further Resources</title>
<para>
-<emphasis>So you want more than just chatting? Or maybe you're just looking for a feature?</emphasis>
+<emphasis>So you want more than just chatting? Or maybe you're just looking for more features?</emphasis>
+</para>
+
+<para>
+With multiple channel support you can have contacts for specific protocols in their own channels, for instance, if you <emphasis>/join &amp;msn</emphasis> you will join a channel that only contains your MSN contacts.
+</para>
+
+<para>
+Account tagging allows you to use the given account name rather than a number when referencing your account. If you wish to turn off your gtalk account, you may <emphasis>account gtalk off</emphasis> rather than <emphasis>account 3 off</emphasis> where "3" is the account number.
</para>
<para>
diff --git a/ipc.c b/ipc.c
index 884039d2..26102d46 100644
--- a/ipc.c
+++ b/ipc.c
@@ -355,7 +355,7 @@ static void ipc_child_cmd_takeover( irc_t *irc, char **cmd )
{
irc_switch_fd( irc, ipc_child_recv_fd );
irc_sync( irc );
- irc_usermsg( irc, "You've successfully taken over your old session" );
+ irc_rootmsg( irc, "You've successfully taken over your old session" );
ipc_child_recv_fd = -1;
ipc_to_master_str( "TAKEOVER DONE\r\n" );
@@ -373,7 +373,7 @@ static void ipc_child_cmd_takeover( irc_t *irc, char **cmd )
else if( strcmp( cmd[1], "FAIL" ) == 0 )
{
/* Master->New connection */
- irc_usermsg( irc, "Could not take over old session" );
+ irc_rootmsg( irc, "Could not take over old session" );
}
}
@@ -411,7 +411,7 @@ static void ipc_child_cmd_takeover_yes( void *data )
/* Drop credentials, we'll shut down soon and shouldn't overwrite
any settings. */
- irc_usermsg( irc, "Trying to take over existing session" );
+ irc_rootmsg( irc, "Trying to take over existing session" );
irc_desync( irc );
@@ -908,6 +908,9 @@ int ipc_master_listen_socket()
struct sockaddr_un un_addr;
int serversock;
+ if (!IPCSOCKET || !*IPCSOCKET)
+ return 1;
+
/* Clean up old socket files that were hanging around.. */
if (unlink(IPCSOCKET) == -1 && errno != ENOENT) {
log_message( LOGLVL_ERROR, "Could not remove old IPC socket at %s: %s", IPCSOCKET, strerror(errno) );
diff --git a/irc.c b/irc.c
index c9d1cc39..ffcc6cb3 100644
--- a/irc.c
+++ b/irc.c
@@ -362,7 +362,7 @@ void irc_process( irc_t *irc )
so let's be a little bit paranoid here: */
if( irc->status & USTATUS_LOGGED_IN )
{
- irc_usermsg( irc, "Error: Charset mismatch detected. The charset "
+ irc_rootmsg( irc, "Error: Charset mismatch detected. The charset "
"setting is currently set to %s, so please make "
"sure your IRC client will send and accept text in "
"that charset, or tell BitlBee which charset to "
@@ -766,7 +766,7 @@ int irc_check_login( irc_t *irc )
irc->root->last_channel = irc->default_channel;
- irc_usermsg( irc,
+ irc_rootmsg( irc,
"Welcome to the BitlBee gateway!\n\n"
"If you've never used BitlBee before, please do read the help "
"information using the \x02help\x02 command. Lots of FAQs are "
@@ -909,7 +909,7 @@ static char *set_eval_charset( set_t *set, char *value )
{
g_free( test );
g_iconv_close( oc );
- irc_usermsg( irc, "Unsupported character set: The IRC protocol "
+ irc_rootmsg( irc, "Unsupported character set: The IRC protocol "
"only supports 8-bit character sets." );
return NULL;
}
@@ -940,7 +940,7 @@ static char *set_eval_bw_compat( set_t *set, char *value )
char *val;
GSList *l;
- irc_usermsg( irc, "Setting `%s' is obsolete, use the `show_users' "
+ irc_rootmsg( irc, "Setting `%s' is obsolete, use the `show_users' "
"channel setting instead.", set->key );
if( strcmp( set->key, "away_devoice" ) == 0 && !bool2int( value ) )
diff --git a/irc.h b/irc.h
index fec63967..f186b96a 100644
--- a/irc.h
+++ b/irc.h
@@ -316,7 +316,10 @@ void irc_exec( irc_t *irc, char **cmd );
void irc_send_num( irc_t *irc, int code, char *format, ... ) G_GNUC_PRINTF( 3, 4 );
void irc_send_login( irc_t *irc );
void irc_send_motd( irc_t *irc );
-void irc_usermsg( irc_t *irc, char *format, ... );
+const char *irc_user_msgdest( irc_user_t *iu );
+void irc_rootmsg( irc_t *irc, char *format, ... );
+void irc_usermsg( irc_user_t *iu, char *format, ... );
+void irc_usernotice( irc_user_t *iu, char *format, ... );
void irc_send_join( irc_channel_t *ic, irc_user_t *iu );
void irc_send_part( irc_channel_t *ic, irc_user_t *iu, const char *reason );
void irc_send_quit( irc_user_t *iu, const char *reason );
diff --git a/irc_commands.c b/irc_commands.c
index a1933fa6..52cf31d0 100644
--- a/irc_commands.c
+++ b/irc_commands.c
@@ -91,7 +91,7 @@ static void irc_cmd_nick( irc_t *irc, char **cmd )
irc_setpass( irc, NULL );
irc->status &= ~USTATUS_IDENTIFIED;
irc_umode_set( irc, "-R", 1 );
- irc_usermsg( irc, "Changing nicks resets your identify status. "
+ irc_rootmsg( irc, "Changing nicks resets your identify status. "
"Re-identify or register a new account if you want "
"your configuration to be saved. See \x02help "
"nick_changes\x02." );
@@ -432,13 +432,13 @@ static void irc_cmd_oper_hack( irc_t *irc, char **cmd )
if( strcmp( a->pass, PASSWORD_PENDING ) == 0 )
{
set_setstr( &a->set, "password", password );
- irc_usermsg( irc, "Password added to IM account "
- "%s(%s)", a->prpl->name, a->user );
+ irc_rootmsg( irc, "Password added to IM account "
+ "%s", a->tag );
/* The IRC client may expect this. 491 suggests the OPER
password was wrong, so the client won't expect a +o.
It may however repeat the password prompt. We'll see. */
irc_send_num( irc, 491, ":Password added to IM account "
- "%s(%s)", a->prpl->name, a->user );
+ "%s", a->tag );
}
}
else if( irc->status & OPER_HACK_IDENTIFY )
diff --git a/irc_im.c b/irc_im.c
index 986693aa..a883d467 100644
--- a/irc_im.c
+++ b/irc_im.c
@@ -196,34 +196,25 @@ static gboolean bee_irc_user_msg( bee_t *bee, bee_user_t *bu, const char *msg_,
{
irc_t *irc = bee->ui_data;
irc_user_t *iu = (irc_user_t *) bu->ui_data;
- char *dst, *prefix = NULL;
+ const char *dst;
+ char *prefix = NULL;
char *wrapped, *ts = NULL;
- irc_channel_t *ic = NULL;
char *msg = g_strdup( msg_ );
GSList *l;
if( sent_at > 0 && set_getbool( &irc->b->set, "display_timestamps" ) )
ts = irc_format_timestamp( irc, sent_at );
- /* Too similar to irc_usermsg()... */
- if( iu->last_channel )
+ dst = irc_user_msgdest( iu );
+ if( dst != irc->user->nick )
{
- if( iu->last_channel->flags & IRC_CHANNEL_JOINED )
- ic = iu->last_channel;
- else
- ic = irc_channel_with_user( irc, iu );
- }
-
- if( ic )
- {
- dst = ic->name;
+ /* if not messaging directly, call user by name */
prefix = g_strdup_printf( "%s%s%s", irc->user->nick, set_getstr( &bee->set, "to_char" ), ts ? : "" );
}
else
{
- dst = irc->user->nick;
prefix = ts;
- ts = NULL;
+ ts = NULL; /* don't double-free */
}
for( l = irc_plugins; l; l = l->next )
@@ -994,7 +985,7 @@ static char *set_eval_room_account( set_t *set, char *value )
return SET_INVALID;
else if( !acc->prpl->chat_join )
{
- irc_usermsg( ic->irc, "Named chatrooms not supported on that account." );
+ irc_rootmsg( ic->irc, "Named chatrooms not supported on that account." );
return SET_INVALID;
}
diff --git a/irc_send.c b/irc_send.c
index 681fc95f..7739f798 100644
--- a/irc_send.c
+++ b/irc_send.c
@@ -109,33 +109,62 @@ void irc_send_motd( irc_t *irc )
close( fd );
}
-void irc_usermsg( irc_t *irc, char *format, ... )
+/* Used by some funcs that generate PRIVMSGs to figure out if we're talking to
+ this person in /query or in a control channel. WARNING: callers rely on
+ this returning a pointer at irc->user_nick, not a copy of it. */
+const char *irc_user_msgdest( irc_user_t *iu )
{
+ irc_t *irc = iu->irc;
irc_channel_t *ic = NULL;
- irc_user_t *iu = irc->root;
- char text[2048];
- va_list params;
- char *dst;
-
- va_start( params, format );
- g_vsnprintf( text, sizeof( text ), format, params );
- va_end( params );
-
- /* Too similar to bee_irc_user_msg()... */
+
if( iu->last_channel )
{
if( iu->last_channel->flags & IRC_CHANNEL_JOINED )
ic = iu->last_channel;
else
- ic = irc_channel_with_user( irc, irc->root );
+ ic = irc_channel_with_user( irc, iu );
}
if( ic )
- dst = ic->name;
+ return ic->name;
else
- dst = irc->user->nick;
+ return irc->user->nick;
+}
+
+/* cmd = "PRIVMSG" or "NOTICE" */
+static void irc_usermsg_( const char *cmd, irc_user_t *iu, const char *format, va_list params )
+{
+ char text[2048];
+ const char *dst;
- irc_send_msg( irc->root, "PRIVMSG", dst, text, NULL );
+ g_vsnprintf( text, sizeof( text ), format, params );
+
+ dst = irc_user_msgdest( iu );
+ irc_send_msg( iu, cmd, dst, text, NULL );
+}
+
+void irc_usermsg(irc_user_t *iu, char *format, ... )
+{
+ va_list params;
+ va_start( params, format );
+ irc_usermsg_( "PRIVMSG", iu, format, params );
+ va_end( params );
+}
+
+void irc_usernotice(irc_user_t *iu, char *format, ... )
+{
+ va_list params;
+ va_start( params, format );
+ irc_usermsg_( "NOTICE", iu, format, params );
+ va_end( params );
+}
+
+void irc_rootmsg( irc_t *irc, char *format, ... )
+{
+ va_list params;
+ va_start( params, format );
+ irc_usermsg_( "PRIVMSG", irc->root, format, params );
+ va_end( params );
}
void irc_send_join( irc_channel_t *ic, irc_user_t *iu )
diff --git a/lib/http_client.c b/lib/http_client.c
index 8b045414..9d986412 100644
--- a/lib/http_client.c
+++ b/lib/http_client.c
@@ -1,7 +1,7 @@
/********************************************************************\
* BitlBee -- An IRC to other IM-networks gateway *
* *
- * Copyright 2002-2005 Wilmer van der Gaast and others *
+ * Copyright 2002-2011 Wilmer van der Gaast and others *
\********************************************************************/
/* HTTP(S) module */
@@ -69,6 +69,9 @@ struct http_request *http_dorequest( char *host, int port, int ssl, char *reques
req->request_length = strlen( request );
req->redir_ttl = 3;
+ if( getenv( "BITLBEE_DEBUG" ) )
+ printf( "About to send HTTP request:\n%s\n", req->request );
+
return( req );
}
@@ -239,7 +242,10 @@ static gboolean http_incoming_data( gpointer data, int source, b_input_condition
req->ssl ? ssl_getdirection( req->ssl ) : B_EV_IO_READ,
http_incoming_data, req );
- return FALSE;
+ if( ssl_pending( req->ssl ) )
+ return http_incoming_data( data, source, cond );
+ else
+ return FALSE;
got_reply:
/* Maybe if the webserver is overloaded, or when there's bad SSL
@@ -275,6 +281,9 @@ got_reply:
*end1 = 0;
+ if( getenv( "BITLBEE_DEBUG" ) )
+ printf( "HTTP response headers:\n%s\n", req->reply_headers );
+
if( evil_server )
req->reply_body = end1 + 1;
else
@@ -313,7 +322,8 @@ got_reply:
req->status_code = -1;
}
- if( ( req->status_code == 301 || req->status_code == 302 ) && req->redir_ttl-- > 0 )
+ if( ( ( req->status_code >= 301 && req->status_code <= 303 ) ||
+ req->status_code == 307 ) && req->redir_ttl-- > 0 )
{
char *loc, *new_request, *new_host;
int error = 0, new_port, new_proto;
@@ -353,6 +363,7 @@ got_reply:
/* A whole URL */
url_t *url;
char *s;
+ const char *new_method;
s = strstr( loc, "\r\n" );
if( s == NULL )
@@ -368,28 +379,49 @@ got_reply:
goto cleanup;
}
- /* Okay, this isn't fun! We have to rebuild the request... :-( */
- new_request = g_malloc( req->request_length + strlen( url->file ) );
-
- /* So, now I just allocated enough memory, so I'm
- going to use strcat(), whether you like it or not. :-) */
-
- sprintf( new_request, "GET %s HTTP/1.0", url->file );
-
- s = strstr( req->request, "\r\n" );
- if( s == NULL )
+ /* Find all headers and, if necessary, the POST request contents.
+ Skip the old Host: header though. This crappy code here means
+ anything using this http_client MUST put the Host: header at
+ the top. */
+ if( !( ( s = strstr( req->request, "\r\nHost: " ) ) &&
+ ( s = strstr( s + strlen( "\r\nHost: " ), "\r\n" ) ) ) )
{
req->status_string = g_strdup( "Error while rebuilding request string" );
- g_free( new_request );
g_free( url );
goto cleanup;
}
- strcat( new_request, s );
+ /* More or less HTTP/1.0 compliant, from my reading of RFC 2616.
+ Always perform a GET request unless we received a 301. 303 was
+ meant for this but it's HTTP/1.1-only and we're specifically
+ speaking HTTP/1.0. ...
+
+ Well except someone at identi.ca's didn't bother reading any
+ RFCs and just return HTTP/1.1-specific status codes to HTTP/1.0
+ requests. Fuckers. So here we are, handle 301..303,307. */
+ if( strncmp( req->request, "GET", 3 ) == 0 )
+ /* GETs never become POSTs. */
+ new_method = "GET";
+ else if( req->status_code == 302 || req->status_code == 303 )
+ /* 302 de-facto becomes GET, 303 as specified by RFC 2616#10.3.3 */
+ new_method = "GET";
+ else
+ /* 301 de-facto should stay POST, 307 specifally RFC 2616#10.3.8 */
+ new_method = "POST";
+
+ /* Okay, this isn't fun! We have to rebuild the request... :-( */
+ new_request = g_strdup_printf( "%s %s HTTP/1.0\r\nHost: %s%s",
+ new_method, url->file, url->host, s );
+
new_host = g_strdup( url->host );
new_port = url->port;
new_proto = url->proto;
+ /* If we went from POST to GET, truncate the request content. */
+ if( new_request[0] != req->request[0] && new_request[0] == 'G' &&
+ ( s = strstr( new_request, "\r\n\r\n" ) ) )
+ s[4] = '\0';
+
g_free( url );
}
@@ -401,6 +433,9 @@ got_reply:
req->fd = -1;
req->ssl = NULL;
+ if( getenv( "BITLBEE_DEBUG" ) )
+ printf( "New headers for redirected HTTP request:\n%s\n", new_request );
+
if( new_proto == PROTO_HTTPS )
{
req->ssl = ssl_connect( new_host, new_port, http_ssl_connected, req );
@@ -442,6 +477,10 @@ cleanup:
else
closesocket( req->fd );
+ if( getenv( "BITLBEE_DEBUG" ) && req )
+ printf( "Finishing HTTP request with status: %s\n",
+ req->status_string ? req->status_string : "NULL" );
+
req->func( req );
http_free( req );
return FALSE;
diff --git a/lib/ssl_client.h b/lib/ssl_client.h
index d0340840..091335c5 100644
--- a/lib/ssl_client.h
+++ b/lib/ssl_client.h
@@ -62,7 +62,15 @@ G_MODULE_EXPORT void *ssl_starttls( int fd, ssl_input_function func, gpointer da
G_MODULE_EXPORT int ssl_read( void *conn, char *buf, int len );
G_MODULE_EXPORT int ssl_write( void *conn, const char *buf, int len );
-/* See ssl_openssl.c for an explanation. */
+/* Now needed by most SSL libs. See for more info:
+ http://www.gnu.org/software/gnutls/manual/gnutls.html#index-gnutls_005frecord_005fcheck_005fpending-209
+ http://www.openssl.org/docs/ssl/SSL_pending.html
+
+ Required because OpenSSL empties the TCP buffer completely but doesn't
+ necessarily give us all the unencrypted data. Or maybe you didn't ask
+ for all of it because your buffer is too small.
+
+ Returns 0 if there's nothing left, 1 if there's more data. */
G_MODULE_EXPORT int ssl_pending( void *conn );
/* Abort the SSL connection and disconnect the socket. Do not use close()
diff --git a/lib/ssl_gnutls.c b/lib/ssl_gnutls.c
index 72517e72..ccab8aca 100644
--- a/lib/ssl_gnutls.c
+++ b/lib/ssl_gnutls.c
@@ -44,6 +44,8 @@ static gboolean initialized = FALSE;
#define GNUTLS_STUPID_CAST (int)
#endif
+#define SSLDEBUG 0
+
struct scd
{
ssl_input_function func;
@@ -134,7 +136,9 @@ static gboolean ssl_connected( gpointer data, gint source, b_input_condition con
gnutls_certificate_allocate_credentials( &conn->xcred );
gnutls_init( &conn->session, GNUTLS_CLIENT );
- gnutls_transport_set_lowat( conn->session, 1 );
+#if GNUTLS_VERSION_NUMBER < 0x020c00
+ gnutls_transport_set_lowat( conn->session, 0 );
+#endif
gnutls_set_default_priority( conn->session );
gnutls_credentials_set( conn->session, GNUTLS_CRD_CERTIFICATE, conn->xcred );
@@ -186,7 +190,7 @@ int ssl_read( void *conn, char *buf, int len )
if( !((struct scd*)conn)->established )
{
ssl_errno = SSL_NOHANDSHAKE;
- return( -1 );
+ return -1;
}
st = gnutls_record_recv( ((struct scd*)conn)->session, buf, len );
@@ -195,7 +199,7 @@ int ssl_read( void *conn, char *buf, int len )
if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
ssl_errno = SSL_AGAIN;
- if( 0 && getenv( "BITLBEE_DEBUG" ) && st > 0 ) len = write( 1, buf, st );
+ if( SSLDEBUG && getenv( "BITLBEE_DEBUG" ) && st > 0 ) len = write( 2, buf, st );
return st;
}
@@ -207,7 +211,7 @@ int ssl_write( void *conn, const char *buf, int len )
if( !((struct scd*)conn)->established )
{
ssl_errno = SSL_NOHANDSHAKE;
- return( -1 );
+ return -1;
}
st = gnutls_record_send( ((struct scd*)conn)->session, buf, len );
@@ -216,15 +220,23 @@ int ssl_write( void *conn, const char *buf, int len )
if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED )
ssl_errno = SSL_AGAIN;
- if( 0 && getenv( "BITLBEE_DEBUG" ) && st > 0 ) len = write( 1, buf, st );
+ if( SSLDEBUG && getenv( "BITLBEE_DEBUG" ) && st > 0 ) len = write( 2, buf, st );
return st;
}
-/* See ssl_openssl.c for an explanation. */
int ssl_pending( void *conn )
{
- return 0;
+ if( conn == NULL )
+ return 0;
+
+ if( !((struct scd*)conn)->established )
+ {
+ ssl_errno = SSL_NOHANDSHAKE;
+ return 0;
+ }
+
+ return gnutls_record_check_pending( ((struct scd*)conn)->session ) != 0;
}
void ssl_disconnect( void *conn_ )
diff --git a/lib/ssl_nss.c b/lib/ssl_nss.c
index 512c7655..ec524ca6 100644
--- a/lib/ssl_nss.c
+++ b/lib/ssl_nss.c
@@ -206,7 +206,6 @@ int ssl_write( void *conn, const char *buf, int len )
return( PR_Write ( ((struct scd*)conn)->prfd, buf, len ) );
}
-/* See ssl_openssl.c for an explanation. */
int ssl_pending( void *conn )
{
struct scd *c = (struct scd *) conn;
diff --git a/lib/ssl_openssl.c b/lib/ssl_openssl.c
index 64bc9257..5f64042d 100644
--- a/lib/ssl_openssl.c
+++ b/lib/ssl_openssl.c
@@ -240,15 +240,6 @@ int ssl_write( void *conn, const char *buf, int len )
return st;
}
-/* Only OpenSSL *really* needs this (and well, maybe NSS). See for more info:
- http://www.gnu.org/software/gnutls/manual/gnutls.html#index-gnutls_005frecord_005fcheck_005fpending-209
- http://www.openssl.org/docs/ssl/SSL_pending.html
-
- Required because OpenSSL empties the TCP buffer completely but doesn't
- necessarily give us all the unencrypted data.
-
- Returns 0 if there's nothing left or if we don't have to care (GnuTLS),
- 1 if there's more data. */
int ssl_pending( void *conn )
{
return ( ((struct scd*)conn) && ((struct scd*)conn)->established ) ?
diff --git a/lib/xmltree.c b/lib/xmltree.c
index 54a7dd13..e2654579 100644
--- a/lib/xmltree.c
+++ b/lib/xmltree.c
@@ -322,7 +322,6 @@ char *xt_to_string( struct xt_node *node )
return real;
}
-#ifdef DEBUG
void xt_print( struct xt_node *node )
{
int i;
@@ -330,16 +329,16 @@ void xt_print( struct xt_node *node )
/* Indentation */
for( c = node; c->parent; c = c->parent )
- printf( " " );
+ fprintf( stderr, " " );
/* Start the tag */
- printf( "<%s", node->name );
+ fprintf( stderr, "<%s", node->name );
/* Print the attributes */
for( i = 0; node->attr[i].key; i ++ )
{
char *v = g_markup_escape_text( node->attr[i].value, -1 );
- printf( " %s=\"%s\"", node->attr[i].key, v );
+ fprintf( stderr, " %s=\"%s\"", node->attr[i].key, v );
g_free( v );
}
@@ -348,13 +347,13 @@ void xt_print( struct xt_node *node )
/* If this tag doesn't have any content at all... */
if( node->text == NULL && node->children == NULL )
{
- printf( "/>\n" );
+ fprintf( stderr, "/>\n" );
return;
/* Then we're finished! */
}
/* Otherwise... */
- printf( ">" );
+ fprintf( stderr, ">" );
/* Only print the text if it contains more than whitespace (TEST). */
if( node->text_len > 0 )
@@ -363,25 +362,24 @@ void xt_print( struct xt_node *node )
if( node->text[i] )
{
char *v = g_markup_escape_text( node->text, -1 );
- printf( "%s", v );
+ fprintf( stderr, "%s", v );
g_free( v );
}
}
if( node->children )
- printf( "\n" );
+ fprintf( stderr, "\n" );
for( c = node->children; c; c = c->next )
xt_print( c );
if( node->children )
for( c = node; c->parent; c = c->parent )
- printf( " " );
+ fprintf( stderr, " " );
/* Non-empty tag is now finished. */
- printf( "</%s>\n", node->name );
+ fprintf( stderr, "</%s>\n", node->name );
}
-#endif
struct xt_node *xt_dup( struct xt_node *node )
{
@@ -556,6 +554,28 @@ char *xt_find_attr( struct xt_node *node, const char *key )
return node->attr[i].value;
}
+/* Strip a few non-printable characters that aren't allowed in XML streams
+ (and upset some XMPP servers for example). */
+void xt_strip_text( char *in )
+{
+ char *out = in;
+ static const char nonprint[32] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, /* 0..7 */
+ 0, 1, 1, 0, 0, 1, 0, 0, /* 9 (tab), 10 (\n), 13 (\r) */
+ };
+
+ if( !in )
+ return;
+
+ while( *in )
+ {
+ if( (unsigned int) *in >= ' ' || nonprint[(unsigned int) *in] )
+ *out ++ = *in;
+ in ++;
+ }
+ *out = *in;
+}
+
struct xt_node *xt_new_node( char *name, const char *text, struct xt_node *children )
{
struct xt_node *node, *c;
@@ -567,8 +587,9 @@ struct xt_node *xt_new_node( char *name, const char *text, struct xt_node *child
if( text )
{
- node->text_len = strlen( text );
- node->text = g_memdup( text, node->text_len + 1 );
+ node->text = g_strdup( text );
+ xt_strip_text( node->text );
+ node->text_len = strlen( node->text );
}
for( c = children; c; c = c->next )
diff --git a/nick.c b/nick.c
index 92d4c258..0d394ecb 100644
--- a/nick.c
+++ b/nick.c
@@ -242,17 +242,17 @@ void nick_dedupe( bee_user_t *bu, char nick[MAX_NICK_LENGTH+1] )
{
int i;
- irc_usermsg( irc, "Warning: Almost had an infinite loop in nick_get()! "
+ irc_rootmsg( irc, "Warning: Almost had an infinite loop in nick_get()! "
"This used to be a fatal BitlBee bug, but we tried to fix it. "
"This message should *never* appear anymore. "
"If it does, please *do* send us a bug report! "
"Please send all the following lines in your report:" );
- irc_usermsg( irc, "Trying to get a sane nick for handle %s", bu->handle );
+ irc_rootmsg( irc, "Trying to get a sane nick for handle %s", bu->handle );
for( i = 0; i < MAX_NICK_LENGTH; i ++ )
- irc_usermsg( irc, "Char %d: %c/%d", i, nick[i], nick[i] );
+ irc_rootmsg( irc, "Char %d: %c/%d", i, nick[i], nick[i] );
- irc_usermsg( irc, "FAILED. Returning an insane nick now. Things might break. "
+ irc_rootmsg( irc, "FAILED. Returning an insane nick now. Things might break. "
"Good luck, and please don't forget to paste the lines up here "
"in #bitlbee on OFTC or in a mail to wilmer@gaast.net" );
diff --git a/otr.c b/otr.c
index 1bea9c44..67e27474 100644
--- a/otr.c
+++ b/otr.c
@@ -7,7 +7,7 @@
/*
OTR support (cf. http://www.cypherpunks.ca/otr/)
- (c) 2008-2010 Sven Moritz Hallberg <pesco@khjk.org>
+ (c) 2008-2011 Sven Moritz Hallberg <pesco@khjk.org>
(c) 2008 funded by stonedcoder.org
files used to store OTR data:
@@ -162,6 +162,9 @@ void otr_handle_smp(struct im_connection *ic, const char *handle, OtrlTLV *tlvs)
void otr_smp_or_smpq(irc_t *irc, const char *nick, const char *question,
const char *secret);
+/* update flags within the irc_user structure to reflect OTR status of context */
+void otr_update_uflags(ConnContext *context, irc_user_t *u);
+
/* update op/voice flag of given user according to encryption state and settings
returns 0 if neither op_buddies nor voice_buddies is set to "encrypted",
i.e. msgstate should be announced seperately */
@@ -182,6 +185,9 @@ Fingerprint *match_fingerprint(irc_t *irc, ConnContext *ctx, const char **args);
/* find a private key by fingerprint prefix (given as any number of hex strings) */
OtrlPrivKey *match_privkey(irc_t *irc, const char **args);
+/* check whether a string is safe to use in a path component */
+int strsane(const char *s);
+
/* functions to be called for certain events */
static const struct irc_plugin otr_plugin;
@@ -236,6 +242,8 @@ gboolean otr_irc_new(irc_t *irc)
l = g_slist_prepend( l, "manual" );
l = g_slist_prepend( l, "always" );
s->eval_data = l;
+
+ s = set_add( &irc->b->set, "otr_does_html", "true", set_eval_bool, irc );
return TRUE;
}
@@ -269,15 +277,17 @@ void otr_load(irc_t *irc)
gcry_error_t enoent = gcry_error_from_errno(ENOENT);
int kg=0;
- g_snprintf(s, 511, "%s%s.otr_keys", global.conf->configdir, irc->user->nick);
- e = otrl_privkey_read(irc->otr->us, s);
- if(e && e!=enoent) {
- irc_usermsg(irc, "otr load: %s: %s", s, gcry_strerror(e));
- }
- g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, irc->user->nick);
- e = otrl_privkey_read_fingerprints(irc->otr->us, s, NULL, NULL);
- if(e && e!=enoent) {
- irc_usermsg(irc, "otr load: %s: %s", s, gcry_strerror(e));
+ if(strsane(irc->user->nick)) {
+ g_snprintf(s, 511, "%s%s.otr_keys", global.conf->configdir, irc->user->nick);
+ e = otrl_privkey_read(irc->otr->us, s);
+ if(e && e!=enoent) {
+ irc_rootmsg(irc, "otr load: %s: %s", s, gcry_strerror(e));
+ }
+ g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, irc->user->nick);
+ e = otrl_privkey_read_fingerprints(irc->otr->us, s, NULL, NULL);
+ if(e && e!=enoent) {
+ irc_rootmsg(irc, "otr load: %s: %s", s, gcry_strerror(e));
+ }
}
/* check for otr keys on all accounts */
@@ -285,7 +295,7 @@ void otr_load(irc_t *irc)
kg = otr_check_for_key(a) || kg;
}
if(kg) {
- irc_usermsg(irc, "Notice: "
+ irc_rootmsg(irc, "Notice: "
"The accounts above do not have OTR encryption keys associated with them, yet. "
"These keys are now being generated in the background. "
"You will be notified as they are completed. "
@@ -300,34 +310,40 @@ void otr_save(irc_t *irc)
char s[512];
gcry_error_t e;
- g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, irc->user->nick);
- e = otrl_privkey_write_fingerprints(irc->otr->us, s);
- if(e) {
- irc_usermsg(irc, "otr save: %s: %s", s, gcry_strerror(e));
+ if(strsane(irc->user->nick)) {
+ g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, irc->user->nick);
+ e = otrl_privkey_write_fingerprints(irc->otr->us, s);
+ if(e) {
+ irc_rootmsg(irc, "otr save: %s: %s", s, gcry_strerror(e));
+ }
+ chmod(s, 0600);
}
- chmod(s, 0600);
}
void otr_remove(const char *nick)
{
char s[512];
- g_snprintf(s, 511, "%s%s.otr_keys", global.conf->configdir, nick);
- unlink(s);
- g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, nick);
- unlink(s);
+ if(strsane(nick)) {
+ g_snprintf(s, 511, "%s%s.otr_keys", global.conf->configdir, nick);
+ unlink(s);
+ g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, nick);
+ unlink(s);
+ }
}
void otr_rename(const char *onick, const char *nnick)
{
char s[512], t[512];
- g_snprintf(s, 511, "%s%s.otr_keys", global.conf->configdir, onick);
- g_snprintf(t, 511, "%s%s.otr_keys", global.conf->configdir, nnick);
- rename(s,t);
- g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, onick);
- g_snprintf(t, 511, "%s%s.otr_fprints", global.conf->configdir, nnick);
- rename(s,t);
+ if(strsane(nnick) && strsane(onick)) {
+ g_snprintf(s, 511, "%s%s.otr_keys", global.conf->configdir, onick);
+ g_snprintf(t, 511, "%s%s.otr_keys", global.conf->configdir, nnick);
+ rename(s,t);
+ g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, onick);
+ g_snprintf(t, 511, "%s%s.otr_fprints", global.conf->configdir, nnick);
+ rename(s,t);
+ }
}
int otr_check_for_key(account_t *a)
@@ -342,13 +358,13 @@ int otr_check_for_key(account_t *a)
k = otrl_privkey_find(irc->otr->us, a->user, a->prpl->name);
if(k) {
- irc_usermsg(irc, "otr: %s/%s ready", a->user, a->prpl->name);
+ irc_rootmsg(irc, "otr: %s/%s ready", a->user, a->prpl->name);
return 0;
} if(keygen_in_progress(irc, a->user, a->prpl->name)) {
- irc_usermsg(irc, "otr: keygen for %s/%s already in progress", a->user, a->prpl->name);
+ irc_rootmsg(irc, "otr: keygen for %s/%s already in progress", a->user, a->prpl->name);
return 0;
} else {
- irc_usermsg(irc, "otr: starting background keygen for %s/%s", a->user, a->prpl->name);
+ irc_rootmsg(irc, "otr: starting background keygen for %s/%s", a->user, a->prpl->name);
otr_keygen(irc, a->user, a->prpl->name);
return 1;
}
@@ -359,7 +375,6 @@ char *otr_filter_msg_in(irc_user_t *iu, char *msg, int flags)
int ignore_msg;
char *newmsg = NULL;
OtrlTLV *tlvs = NULL;
- char *colormsg;
irc_t *irc = iu->irc;
struct im_connection *ic = iu->bu->ic;
@@ -379,33 +394,59 @@ char *otr_filter_msg_in(irc_user_t *iu, char *msg, int flags)
return NULL;
} else if(!newmsg) {
/* this was a non-OTR message */
- return g_strdup(msg);
+ return msg;
} else {
/* OTR has processed this message */
ConnContext *context = otrl_context_find(irc->otr->us, iu->bu->handle,
ic->acc->user, ic->acc->prpl->name, 0, NULL, NULL, NULL);
- if(context && context->msgstate == OTRL_MSGSTATE_ENCRYPTED &&
- set_getbool(&ic->bee->set, "otr_color_encrypted")) {
- /* color according to f'print trust */
- int color;
- const char *trust = context->active_fingerprint->trust;
- if(trust && trust[0] != '\0')
- color=3; /* green */
- else
- color=5; /* red */
-
- if(newmsg[0] == ',') {
- /* could be a problem with the color code */
- /* insert a space between color spec and message */
- colormsg = g_strdup_printf("\x03%.2d %s\x0F", color, newmsg);
- } else {
- colormsg = g_strdup_printf("\x03%.2d%s\x0F", color, newmsg);
+
+ /* we're done with the original msg, which will be caller-freed. */
+ /* NB: must not change the newmsg pointer, since we free it. */
+ msg = newmsg;
+
+ if(context && context->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
+ /* HTML decoding */
+ /* perform any necessary stripping that the top level would miss */
+ if(set_getbool(&ic->bee->set, "otr_does_html") &&
+ !(ic->flags & OPT_DOES_HTML) &&
+ set_getbool(&ic->bee->set, "strip_html")) {
+ strip_html(msg);
}
- } else {
- colormsg = g_strdup(newmsg);
+
+ /* coloring */
+ if(set_getbool(&ic->bee->set, "otr_color_encrypted")) {
+ int color; /* color according to f'print trust */
+ char *pre="", *sep=""; /* optional parts */
+ const char *trust = context->active_fingerprint->trust;
+
+ if(trust && trust[0] != '\0')
+ color=3; /* green */
+ else
+ color=5; /* red */
+
+ /* in a query window, keep "/me " uncolored at the beginning */
+ if(g_strncasecmp(msg, "/me ", 4) == 0
+ && irc_user_msgdest(iu) == irc->user->nick) {
+ msg += 4; /* skip */
+ pre = "/me ";
+ }
+
+ /* comma in first place could mess with the color code */
+ if(msg[0] == ',') {
+ /* insert a space between color spec and message */
+ sep = " ";
+ }
+
+ msg = g_strdup_printf("%s\x03%.2d%s%s\x0F", pre,
+ color, sep, msg);
+ }
+ }
+
+ if(msg == newmsg) {
+ msg = g_strdup(newmsg);
}
otrl_message_free(newmsg);
- return colormsg;
+ return msg;
}
}
@@ -413,6 +454,7 @@ char *otr_filter_msg_out(irc_user_t *iu, char *msg, int flags)
{
int st;
char *otrmsg = NULL;
+ char *emsg = msg; /* the message as we hand it to libotr */
ConnContext *ctx = NULL;
irc_t *irc = iu->irc;
struct im_connection *ic = iu->bu->ic;
@@ -421,18 +463,29 @@ char *otr_filter_msg_out(irc_user_t *iu, char *msg, int flags)
if(ic->acc->prpl->options & OPT_NOOTR) {
return msg;
}
+
+ ctx = otrl_context_find(irc->otr->us,
+ iu->bu->handle, ic->acc->user, ic->acc->prpl->name,
+ 1, NULL, NULL, NULL);
+
+ /* HTML encoding */
+ /* consider OTR plaintext to be HTML if otr_does_html is set */
+ if(ctx && ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED &&
+ set_getbool(&ic->bee->set, "otr_does_html") &&
+ (g_strncasecmp(msg, "<html>", 6) != 0)) {
+ emsg = escape_html(msg);
+ }
st = otrl_message_sending(irc->otr->us, &otr_ops, ic,
ic->acc->user, ic->acc->prpl->name, iu->bu->handle,
- msg, NULL, &otrmsg, NULL, NULL);
+ emsg, NULL, &otrmsg, NULL, NULL);
+ if(emsg != msg) {
+ g_free(emsg); /* we're done with this one */
+ }
if(st) {
return NULL;
}
- ctx = otrl_context_find(irc->otr->us,
- iu->bu->handle, ic->acc->user, ic->acc->prpl->name,
- 1, NULL, NULL, NULL);
-
if(otrmsg) {
if(!ctx) {
otrl_message_free(otrmsg);
@@ -479,13 +532,13 @@ static void cmd_otr(irc_t *irc, char **args)
}
if(!cmd->command) {
- irc_usermsg(irc, "%s: unknown subcommand \"%s\", see \x02help otr\x02",
+ irc_rootmsg(irc, "%s: unknown subcommand \"%s\", see \x02help otr\x02",
args[0], args[1]);
return;
}
if(!args[cmd->required_parameters+1]) {
- irc_usermsg(irc, "%s %s: not enough arguments (%d req.)",
+ irc_rootmsg(irc, "%s %s: not enough arguments (%d req.)",
args[0], args[1], cmd->required_parameters);
return;
}
@@ -557,7 +610,7 @@ void op_inject_message(void *opdata, const char *accountname,
if (strcmp(accountname, recipient) == 0) {
/* huh? injecting messages to myself? */
- irc_usermsg(irc, "note to self: %s", message);
+ irc_rootmsg(irc, "note to self: %s", message);
} else {
/* need to drop some consts here :-( */
/* TODO: get flags into op_inject_message?! */
@@ -572,9 +625,15 @@ int op_display_otr_message(void *opdata, const char *accountname,
struct im_connection *ic = check_imc(opdata, accountname, protocol);
char *msg = g_strdup(message);
irc_t *irc = ic->bee->ui_data;
+ irc_user_t *u = peeruser(irc, username, protocol);
strip_html(msg);
- irc_usermsg(irc, "otr: %s", msg);
+ if(u) {
+ /* display as a notice from this particular user */
+ irc_usernotice(u, "%s", msg);
+ } else {
+ irc_rootmsg(irc, "[otr] %s", msg);
+ }
g_free(msg);
return 0;
@@ -586,11 +645,17 @@ void op_new_fingerprint(void *opdata, OtrlUserState us,
{
struct im_connection *ic = check_imc(opdata, accountname, protocol);
irc_t *irc = ic->bee->ui_data;
+ irc_user_t *u = peeruser(irc, username, protocol);
char hunam[45]; /* anybody looking? ;-) */
otrl_privkey_hash_to_human(hunam, fingerprint);
- irc_usermsg(irc, "new fingerprint for %s: %s",
- peernick(irc, username, protocol), hunam);
+ if(u) {
+ irc_usernotice(u, "new fingerprint: %s", hunam);
+ } else {
+ /* this case shouldn't normally happen */
+ irc_rootmsg(irc, "new fingerprint for %s/%s: %s",
+ username, protocol, hunam);
+ }
}
void op_write_fingerprints(void *opdata)
@@ -607,7 +672,6 @@ void op_gone_secure(void *opdata, ConnContext *context)
check_imc(opdata, context->accountname, context->protocol);
irc_user_t *u;
irc_t *irc = ic->bee->ui_data;
- const char *trust;
u = peeruser(irc, context->username, context->protocol);
if(!u) {
@@ -617,13 +681,11 @@ void op_gone_secure(void *opdata, ConnContext *context)
return;
}
- trust = context->active_fingerprint->trust;
- if(trust && trust[0])
- u->flags |= IRC_USER_OTR_ENCRYPTED | IRC_USER_OTR_TRUSTED;
- else
- u->flags = ( u->flags & ~IRC_USER_OTR_TRUSTED ) | IRC_USER_OTR_ENCRYPTED;
- if(!otr_update_modeflags(irc, u))
- irc_usermsg(irc, "conversation with %s is now off the record", u->nick);
+ otr_update_uflags(context, u);
+ if(!otr_update_modeflags(irc, u)) {
+ char *trust = u->flags & IRC_USER_OTR_TRUSTED ? "trusted" : "untrusted!";
+ irc_usernotice(u, "conversation is now off the record (%s)", trust);
+ }
}
void op_gone_insecure(void *opdata, ConnContext *context)
@@ -640,9 +702,9 @@ void op_gone_insecure(void *opdata, ConnContext *context)
context->username, context->protocol, context->accountname);
return;
}
- u->flags &= ~( IRC_USER_OTR_ENCRYPTED | IRC_USER_OTR_TRUSTED );
+ otr_update_uflags(context, u);
if(!otr_update_modeflags(irc, u))
- irc_usermsg(irc, "conversation with %s is now in the clear", u->nick);
+ irc_usernotice(u, "conversation is now in cleartext");
}
void op_still_secure(void *opdata, ConnContext *context, int is_reply)
@@ -659,12 +721,12 @@ void op_still_secure(void *opdata, ConnContext *context, int is_reply)
context->username, context->protocol, context->accountname);
return;
}
- if(context->active_fingerprint->trust[0])
- u->flags |= IRC_USER_OTR_ENCRYPTED | IRC_USER_OTR_TRUSTED;
- else
- u->flags = ( u->flags & ~IRC_USER_OTR_TRUSTED ) | IRC_USER_OTR_ENCRYPTED;
- if(!otr_update_modeflags(irc, u))
- irc_usermsg(irc, "otr connection with %s has been refreshed", u->nick);
+
+ otr_update_uflags(context, u);
+ if(!otr_update_modeflags(irc, u)) {
+ char *trust = u->flags & IRC_USER_OTR_TRUSTED ? "trusted" : "untrusted!";
+ irc_usernotice(u, "otr connection has been refreshed (%s)", trust);
+ }
}
void op_log_message(void *opdata, const char *message)
@@ -707,7 +769,7 @@ void cmd_otr_disconnect(irc_t *irc, char **args)
u = irc_user_by_name(irc, args[1]);
if(!u || !u->bu || !u->bu->ic) {
- irc_usermsg(irc, "%s: unknown user", args[1]);
+ irc_rootmsg(irc, "%s: unknown user", args[1]);
return;
}
@@ -732,11 +794,11 @@ void cmd_otr_connect(irc_t *irc, char **args)
u = irc_user_by_name(irc, args[1]);
if(!u || !u->bu || !u->bu->ic) {
- irc_usermsg(irc, "%s: unknown user", args[1]);
+ irc_rootmsg(irc, "%s: unknown user", args[1]);
return;
}
if(!(u->bu->flags & BEE_USER_ONLINE)) {
- irc_usermsg(irc, "%s is offline", args[1]);
+ irc_rootmsg(irc, "%s is offline", args[1]);
return;
}
@@ -763,14 +825,14 @@ void cmd_otr_trust(irc_t *irc, char **args)
u = irc_user_by_name(irc, args[1]);
if(!u || !u->bu || !u->bu->ic) {
- irc_usermsg(irc, "%s: unknown user", args[1]);
+ irc_rootmsg(irc, "%s: unknown user", args[1]);
return;
}
ctx = otrl_context_find(irc->otr->us, u->bu->handle,
u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL);
if(!ctx) {
- irc_usermsg(irc, "%s: no otr context with user", args[1]);
+ irc_rootmsg(irc, "%s: no otr context with user", args[1]);
return;
}
@@ -782,18 +844,18 @@ void cmd_otr_trust(irc_t *irc, char **args)
int x, y;
if(!*p || !*q) {
- irc_usermsg(irc, "failed: truncated fingerprint block %d", i+1);
+ irc_rootmsg(irc, "failed: truncated fingerprint block %d", i+1);
return;
}
x = hexval(*p);
y = hexval(*q);
if(x<0) {
- irc_usermsg(irc, "failed: %d. hex digit of block %d out of range", 2*j+1, i+1);
+ irc_rootmsg(irc, "failed: %d. hex digit of block %d out of range", 2*j+1, i+1);
return;
}
if(y<0) {
- irc_usermsg(irc, "failed: %d. hex digit of block %d out of range", 2*j+2, i+1);
+ irc_rootmsg(irc, "failed: %d. hex digit of block %d out of range", 2*j+2, i+1);
return;
}
@@ -802,11 +864,11 @@ void cmd_otr_trust(irc_t *irc, char **args)
}
fp = otrl_context_find_fingerprint(ctx, raw, 0, NULL);
if(!fp) {
- irc_usermsg(irc, "failed: no such fingerprint for %s", args[1]);
+ irc_rootmsg(irc, "failed: no such fingerprint for %s", args[1]);
} else {
char *trust = args[7] ? args[7] : "affirmed";
otrl_context_set_trust(fp, trust);
- irc_usermsg(irc, "fingerprint match, trust set to \"%s\"", trust);
+ irc_rootmsg(irc, "fingerprint match, trust set to \"%s\"", trust);
if(u->flags & IRC_USER_OTR_ENCRYPTED)
u->flags |= IRC_USER_OTR_TRUSTED;
otr_update_modeflags(irc, u);
@@ -834,21 +896,21 @@ void cmd_otr_info(irc_t *irc, char **args)
handle = arg;
ctx = otrl_context_find(irc->otr->us, handle, myhandle, protocol, 0, NULL, NULL, NULL);
if(!ctx) {
- irc_usermsg(irc, "no such context");
+ irc_rootmsg(irc, "no such context");
g_free(arg);
return;
}
} else {
irc_user_t *u = irc_user_by_name(irc, args[1]);
if(!u || !u->bu || !u->bu->ic) {
- irc_usermsg(irc, "%s: unknown user", args[1]);
+ irc_rootmsg(irc, "%s: unknown user", args[1]);
g_free(arg);
return;
}
ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user,
u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL);
if(!ctx) {
- irc_usermsg(irc, "no otr context with %s", args[1]);
+ irc_rootmsg(irc, "no otr context with %s", args[1]);
g_free(arg);
return;
}
@@ -856,7 +918,7 @@ void cmd_otr_info(irc_t *irc, char **args)
/* show how we resolved the (nick) argument, if we did */
if(handle!=arg) {
- irc_usermsg(irc, "%s is %s/%s; we are %s/%s to them", args[1],
+ irc_rootmsg(irc, "%s is %s/%s; we are %s/%s to them", args[1],
ctx->username, ctx->protocol, ctx->accountname, ctx->protocol);
}
show_otr_context_info(irc, ctx);
@@ -871,19 +933,19 @@ void cmd_otr_keygen(irc_t *irc, char **args)
n = atoi(args[1]);
if(n<0 || (!n && strcmp(args[1], "0"))) {
- irc_usermsg(irc, "%s: invalid account number", args[1]);
+ irc_rootmsg(irc, "%s: invalid account number", args[1]);
return;
}
a = irc->b->accounts;
for(i=0; i<n && a; i++, a=a->next);
if(!a) {
- irc_usermsg(irc, "%s: no such account", args[1]);
+ irc_rootmsg(irc, "%s: no such account", args[1]);
return;
}
if(keygen_in_progress(irc, a->user, a->prpl->name)) {
- irc_usermsg(irc, "keygen for account %d already in progress", n);
+ irc_rootmsg(irc, "keygen for account %d already in progress", n);
return;
}
@@ -905,7 +967,7 @@ void yes_forget_fingerprint(void *data)
g_free(p);
if(fp == fp->context->active_fingerprint) {
- irc_usermsg(irc, "that fingerprint is active, terminate otr connection first");
+ irc_rootmsg(irc, "that fingerprint is active, terminate otr connection first");
return;
}
@@ -921,7 +983,7 @@ void yes_forget_context(void *data)
g_free(p);
if(ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
- irc_usermsg(irc, "active otr connection with %s, terminate it first",
+ irc_rootmsg(irc, "active otr connection with %s, terminate it first",
peernick(irc, ctx->username, ctx->protocol));
return;
}
@@ -953,21 +1015,21 @@ void cmd_otr_forget(irc_t *irc, char **args)
pair_t *p;
if(!args[3]) {
- irc_usermsg(irc, "otr %s %s: not enough arguments (2 req.)", args[0], args[1]);
+ irc_rootmsg(irc, "otr %s %s: not enough arguments (2 req.)", args[0], args[1]);
return;
}
/* TODO: allow context specs ("user/proto/account") in 'otr forget fingerprint'? */
u = irc_user_by_name(irc, args[2]);
if(!u || !u->bu || !u->bu->ic) {
- irc_usermsg(irc, "%s: unknown user", args[2]);
+ irc_rootmsg(irc, "%s: unknown user", args[2]);
return;
}
ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user,
u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL);
if(!ctx) {
- irc_usermsg(irc, "no otr context with %s", args[2]);
+ irc_rootmsg(irc, "no otr context with %s", args[2]);
return;
}
@@ -978,7 +1040,7 @@ void cmd_otr_forget(irc_t *irc, char **args)
}
if(fp == ctx->active_fingerprint) {
- irc_usermsg(irc, "that fingerprint is active, terminate otr connection first");
+ irc_rootmsg(irc, "that fingerprint is active, terminate otr connection first");
return;
}
@@ -1003,19 +1065,19 @@ void cmd_otr_forget(irc_t *irc, char **args)
/* TODO: allow context specs ("user/proto/account") in 'otr forget contex'? */
u = irc_user_by_name(irc, args[2]);
if(!u || !u->bu || !u->bu->ic) {
- irc_usermsg(irc, "%s: unknown user", args[2]);
+ irc_rootmsg(irc, "%s: unknown user", args[2]);
return;
}
ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user,
u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL);
if(!ctx) {
- irc_usermsg(irc, "no otr context with %s", args[2]);
+ irc_rootmsg(irc, "no otr context with %s", args[2]);
return;
}
if(ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
- irc_usermsg(irc, "active otr connection with %s, terminate it first", args[2]);
+ irc_rootmsg(irc, "active otr connection with %s, terminate it first", args[2]);
return;
}
@@ -1048,7 +1110,7 @@ void cmd_otr_forget(irc_t *irc, char **args)
else
{
- irc_usermsg(irc, "otr %s: unknown subcommand \"%s\", see \x02help otr forget\x02",
+ irc_rootmsg(irc, "otr %s: unknown subcommand \"%s\", see \x02help otr forget\x02",
args[0], args[1]);
}
}
@@ -1074,7 +1136,7 @@ void otr_handle_smp(struct im_connection *ic, const char *handle, OtrlTLV *tlvs)
ic->acc->user, ic->acc->prpl->name, 1, NULL, NULL, NULL);
if(!context) {
/* huh? out of memory or what? */
- irc_usermsg(irc, "smp: failed to get otr context for %s", u->nick);
+ irc_rootmsg(irc, "smp: failed to get otr context for %s", u->nick);
otrl_message_abort_smp(us, ops, u->bu->ic, context);
otrl_sm_state_free(context->smstate);
return;
@@ -1082,7 +1144,7 @@ void otr_handle_smp(struct im_connection *ic, const char *handle, OtrlTLV *tlvs)
nextMsg = context->smstate->nextExpected;
if (context->smstate->sm_prog_state == OTRL_SMP_PROG_CHEATED) {
- irc_usermsg(irc, "smp %s: opponent violated protocol, aborting",
+ irc_rootmsg(irc, "smp %s: opponent violated protocol, aborting",
u->nick);
otrl_message_abort_smp(us, ops, u->bu->ic, context);
otrl_sm_state_free(context->smstate);
@@ -1092,14 +1154,14 @@ void otr_handle_smp(struct im_connection *ic, const char *handle, OtrlTLV *tlvs)
tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1Q);
if (tlv) {
if (nextMsg != OTRL_SMP_EXPECT1) {
- irc_usermsg(irc, "smp %s: spurious SMP1Q received, aborting", u->nick);
+ irc_rootmsg(irc, "smp %s: spurious SMP1Q received, aborting", u->nick);
otrl_message_abort_smp(us, ops, u->bu->ic, context);
otrl_sm_state_free(context->smstate);
} else {
char *question = g_strndup((char *)tlv->data, tlv->len);
- irc_usermsg(irc, "smp: initiated by %s with question: \x02\"%s\"\x02", u->nick,
+ irc_rootmsg(irc, "smp: initiated by %s with question: \x02\"%s\"\x02", u->nick,
question);
- irc_usermsg(irc, "smp: respond with \x02otr smp %s <answer>\x02",
+ irc_rootmsg(irc, "smp: respond with \x02otr smp %s <answer>\x02",
u->nick);
g_free(question);
/* smp stays in EXPECT1 until user responds */
@@ -1108,11 +1170,11 @@ void otr_handle_smp(struct im_connection *ic, const char *handle, OtrlTLV *tlvs)
tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1);
if (tlv) {
if (nextMsg != OTRL_SMP_EXPECT1) {
- irc_usermsg(irc, "smp %s: spurious SMP1 received, aborting", u->nick);
+ irc_rootmsg(irc, "smp %s: spurious SMP1 received, aborting", u->nick);
otrl_message_abort_smp(us, ops, u->bu->ic, context);
otrl_sm_state_free(context->smstate);
} else {
- irc_usermsg(irc, "smp: initiated by %s"
+ irc_rootmsg(irc, "smp: initiated by %s"
" - respond with \x02otr smp %s <secret>\x02",
u->nick, u->nick);
/* smp stays in EXPECT1 until user responds */
@@ -1121,7 +1183,7 @@ void otr_handle_smp(struct im_connection *ic, const char *handle, OtrlTLV *tlvs)
tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP2);
if (tlv) {
if (nextMsg != OTRL_SMP_EXPECT2) {
- irc_usermsg(irc, "smp %s: spurious SMP2 received, aborting", u->nick);
+ irc_rootmsg(irc, "smp %s: spurious SMP2 received, aborting", u->nick);
otrl_message_abort_smp(us, ops, u->bu->ic, context);
otrl_sm_state_free(context->smstate);
} else {
@@ -1132,25 +1194,25 @@ void otr_handle_smp(struct im_connection *ic, const char *handle, OtrlTLV *tlvs)
tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP3);
if (tlv) {
if (nextMsg != OTRL_SMP_EXPECT3) {
- irc_usermsg(irc, "smp %s: spurious SMP3 received, aborting", u->nick);
+ irc_rootmsg(irc, "smp %s: spurious SMP3 received, aborting", u->nick);
otrl_message_abort_smp(us, ops, u->bu->ic, context);
otrl_sm_state_free(context->smstate);
} else {
/* SMP3 received, otrl_message_receiving will have sent SMP4 */
if(context->smstate->sm_prog_state == OTRL_SMP_PROG_SUCCEEDED) {
if(context->smstate->received_question) {
- irc_usermsg(irc, "smp %s: correct answer, you are trusted",
+ irc_rootmsg(irc, "smp %s: correct answer, you are trusted",
u->nick);
} else {
- irc_usermsg(irc, "smp %s: secrets proved equal, fingerprint trusted",
+ irc_rootmsg(irc, "smp %s: secrets proved equal, fingerprint trusted",
u->nick);
}
} else {
if(context->smstate->received_question) {
- irc_usermsg(irc, "smp %s: wrong answer, you are not trusted",
+ irc_rootmsg(irc, "smp %s: wrong answer, you are not trusted",
u->nick);
} else {
- irc_usermsg(irc, "smp %s: secrets did not match, fingerprint not trusted",
+ irc_rootmsg(irc, "smp %s: secrets did not match, fingerprint not trusted",
u->nick);
}
}
@@ -1161,16 +1223,16 @@ void otr_handle_smp(struct im_connection *ic, const char *handle, OtrlTLV *tlvs)
tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP4);
if (tlv) {
if (nextMsg != OTRL_SMP_EXPECT4) {
- irc_usermsg(irc, "smp %s: spurious SMP4 received, aborting", u->nick);
+ irc_rootmsg(irc, "smp %s: spurious SMP4 received, aborting", u->nick);
otrl_message_abort_smp(us, ops, u->bu->ic, context);
otrl_sm_state_free(context->smstate);
} else {
/* SMP4 received, otrl_message_receiving will have set fp trust */
if(context->smstate->sm_prog_state == OTRL_SMP_PROG_SUCCEEDED) {
- irc_usermsg(irc, "smp %s: secrets proved equal, fingerprint trusted",
+ irc_rootmsg(irc, "smp %s: secrets proved equal, fingerprint trusted",
u->nick);
} else {
- irc_usermsg(irc, "smp %s: secrets did not match, fingerprint not trusted",
+ irc_rootmsg(irc, "smp %s: secrets did not match, fingerprint not trusted",
u->nick);
}
otrl_sm_state_free(context->smstate);
@@ -1179,7 +1241,7 @@ void otr_handle_smp(struct im_connection *ic, const char *handle, OtrlTLV *tlvs)
}
tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP_ABORT);
if (tlv) {
- irc_usermsg(irc, "smp: received abort from %s", u->nick);
+ irc_rootmsg(irc, "smp: received abort from %s", u->nick);
otrl_sm_state_free(context->smstate);
/* smp is in back in EXPECT1 */
}
@@ -1194,18 +1256,18 @@ void otr_smp_or_smpq(irc_t *irc, const char *nick, const char *question,
u = irc_user_by_name(irc, nick);
if(!u || !u->bu || !u->bu->ic) {
- irc_usermsg(irc, "%s: unknown user", nick);
+ irc_rootmsg(irc, "%s: unknown user", nick);
return;
}
if(!(u->bu->flags & BEE_USER_ONLINE)) {
- irc_usermsg(irc, "%s is offline", nick);
+ irc_rootmsg(irc, "%s is offline", nick);
return;
}
ctx = otrl_context_find(irc->otr->us, u->bu->handle,
u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL);
if(!ctx || ctx->msgstate != OTRL_MSGSTATE_ENCRYPTED) {
- irc_usermsg(irc, "smp: otr inactive with %s, try \x02otr connect"
+ irc_rootmsg(irc, "smp: otr inactive with %s, try \x02otr connect"
" %s\x02", nick, nick);
return;
}
@@ -1220,7 +1282,7 @@ void otr_smp_or_smpq(irc_t *irc, const char *nick, const char *question,
if(question) {
/* this was 'otr smpq', just initiate */
- irc_usermsg(irc, "smp: initiating with %s...", u->nick);
+ irc_rootmsg(irc, "smp: initiating with %s...", u->nick);
otrl_message_initiate_smp_q(irc->otr->us, &otr_ops, u->bu->ic, ctx,
question, (unsigned char *)secret, strlen(secret));
/* smp is now in EXPECT2 */
@@ -1229,14 +1291,14 @@ void otr_smp_or_smpq(irc_t *irc, const char *nick, const char *question,
/* warning: the following assumes that smstates are cleared whenever an SMP
is completed or aborted! */
if(ctx->smstate->secret == NULL) {
- irc_usermsg(irc, "smp: initiating with %s...", u->nick);
+ irc_rootmsg(irc, "smp: initiating with %s...", u->nick);
otrl_message_initiate_smp(irc->otr->us, &otr_ops,
u->bu->ic, ctx, (unsigned char *)secret, strlen(secret));
/* smp is now in EXPECT2 */
} else {
/* if we're still in EXPECT1 but smstate is initialized, we must have
received the SMP1, so let's issue a response */
- irc_usermsg(irc, "smp: responding to %s...", u->nick);
+ irc_rootmsg(irc, "smp: responding to %s...", u->nick);
otrl_message_respond_smp(irc->otr->us, &otr_ops,
u->bu->ic, ctx, (unsigned char *)secret, strlen(secret));
/* smp is now in EXPECT3 */
@@ -1311,9 +1373,26 @@ const char *peernick(irc_t *irc, const char *handle, const char *protocol)
}
}
+void otr_update_uflags(ConnContext *context, irc_user_t *u)
+{
+ const char *trust;
+
+ if(context->active_fingerprint) {
+ u->flags |= IRC_USER_OTR_ENCRYPTED;
+
+ trust = context->active_fingerprint->trust;
+ if(trust && trust[0])
+ u->flags |= IRC_USER_OTR_TRUSTED;
+ else
+ u->flags &= ~IRC_USER_OTR_TRUSTED;
+ } else {
+ u->flags &= ~IRC_USER_OTR_ENCRYPTED;
+ }
+}
+
int otr_update_modeflags(irc_t *irc, irc_user_t *u)
{
- return 1;
+ return 0;
}
void show_fingerprints(irc_t *irc, ConnContext *ctx)
@@ -1334,13 +1413,13 @@ void show_fingerprints(irc_t *irc, ConnContext *ctx)
trust=fp->trust;
}
if(fp == ctx->active_fingerprint) {
- irc_usermsg(irc, " \x02%s (%s)\x02", human, trust);
+ irc_rootmsg(irc, " \x02%s (%s)\x02", human, trust);
} else {
- irc_usermsg(irc, " %s (%s)", human, trust);
+ irc_rootmsg(irc, " %s (%s)", human, trust);
}
}
if(count==0)
- irc_usermsg(irc, " (none)");
+ irc_rootmsg(irc, " (none)");
}
Fingerprint *match_fingerprint(irc_t *irc, ConnContext *ctx, const char **args)
@@ -1359,14 +1438,14 @@ Fingerprint *match_fingerprint(irc_t *irc, ConnContext *ctx, const char **args)
char c = toupper(args[i][j]);
if(n>=40) {
- irc_usermsg(irc, "too many fingerprint digits given, expected at most 40");
+ irc_rootmsg(irc, "too many fingerprint digits given, expected at most 40");
return NULL;
}
if( (c>='A' && c<='F') || (c>='0' && c<='9') ) {
*(p++) = c;
} else {
- irc_usermsg(irc, "invalid hex digit '%c' in block %d", args[i][j], i+1);
+ irc_rootmsg(irc, "invalid hex digit '%c' in block %d", args[i][j], i+1);
return NULL;
}
@@ -1387,7 +1466,7 @@ Fingerprint *match_fingerprint(irc_t *irc, ConnContext *ctx, const char **args)
break;
}
if(!fp) {
- irc_usermsg(irc, "%s: no match", prefix);
+ irc_rootmsg(irc, "%s: no match", prefix);
return NULL;
}
@@ -1400,7 +1479,7 @@ Fingerprint *match_fingerprint(irc_t *irc, ConnContext *ctx, const char **args)
break;
}
if(fp2) {
- irc_usermsg(irc, "%s: multiple matches", prefix);
+ irc_rootmsg(irc, "%s: multiple matches", prefix);
return NULL;
}
@@ -1423,14 +1502,14 @@ OtrlPrivKey *match_privkey(irc_t *irc, const char **args)
char c = toupper(args[i][j]);
if(n>=40) {
- irc_usermsg(irc, "too many fingerprint digits given, expected at most 40");
+ irc_rootmsg(irc, "too many fingerprint digits given, expected at most 40");
return NULL;
}
if( (c>='A' && c<='F') || (c>='0' && c<='9') ) {
*(p++) = c;
} else {
- irc_usermsg(irc, "invalid hex digit '%c' in block %d", args[i][j], i+1);
+ irc_rootmsg(irc, "invalid hex digit '%c' in block %d", args[i][j], i+1);
return NULL;
}
@@ -1451,7 +1530,7 @@ OtrlPrivKey *match_privkey(irc_t *irc, const char **args)
break;
}
if(!k) {
- irc_usermsg(irc, "%s: no match", prefix);
+ irc_rootmsg(irc, "%s: no match", prefix);
return NULL;
}
@@ -1464,7 +1543,7 @@ OtrlPrivKey *match_privkey(irc_t *irc, const char **args)
break;
}
if(k2) {
- irc_usermsg(irc, "%s: multiple matches", prefix);
+ irc_rootmsg(irc, "%s: multiple matches", prefix);
return NULL;
}
@@ -1479,16 +1558,16 @@ void show_general_otr_info(irc_t *irc)
kg_t *kg;
/* list all privkeys (including ones being generated) */
- irc_usermsg(irc, "\x1fprivate keys:\x1f");
+ irc_rootmsg(irc, "\x1fprivate keys:\x1f");
for(key=irc->otr->us->privkey_root; key; key=key->next) {
const char *hash;
switch(key->pubkey_type) {
case OTRL_PUBKEY_TYPE_DSA:
- irc_usermsg(irc, " %s/%s - DSA", key->accountname, key->protocol);
+ irc_rootmsg(irc, " %s/%s - DSA", key->accountname, key->protocol);
break;
default:
- irc_usermsg(irc, " %s/%s - type %d", key->accountname, key->protocol,
+ irc_rootmsg(irc, " %s/%s - type %d", key->accountname, key->protocol,
key->pubkey_type);
}
@@ -1497,25 +1576,25 @@ void show_general_otr_info(irc_t *irc)
for hashing a given 'OtrlPrivKey'... */
hash = otrl_privkey_fingerprint(irc->otr->us, human, key->accountname, key->protocol);
if(hash) /* should always succeed */
- irc_usermsg(irc, " %s", human);
+ irc_rootmsg(irc, " %s", human);
}
if(irc->otr->sent_accountname) {
- irc_usermsg(irc, " %s/%s - DSA", irc->otr->sent_accountname,
+ irc_rootmsg(irc, " %s/%s - DSA", irc->otr->sent_accountname,
irc->otr->sent_protocol);
- irc_usermsg(irc, " (being generated)");
+ irc_rootmsg(irc, " (being generated)");
}
for(kg=irc->otr->todo; kg; kg=kg->next) {
- irc_usermsg(irc, " %s/%s - DSA", kg->accountname, kg->protocol);
- irc_usermsg(irc, " (queued)");
+ irc_rootmsg(irc, " %s/%s - DSA", kg->accountname, kg->protocol);
+ irc_rootmsg(irc, " (queued)");
}
if(key == irc->otr->us->privkey_root &&
!irc->otr->sent_accountname &&
kg == irc->otr->todo)
- irc_usermsg(irc, " (none)");
+ irc_rootmsg(irc, " (none)");
/* list all contexts */
- irc_usermsg(irc, "%s", "");
- irc_usermsg(irc, "\x1f" "connection contexts:\x1f (bold=currently encrypted)");
+ irc_rootmsg(irc, "%s", "");
+ irc_rootmsg(irc, "\x1f" "connection contexts:\x1f (bold=currently encrypted)");
for(ctx=irc->otr->us->context_root; ctx; ctx=ctx->next) {\
irc_user_t *u;
char *userstring;
@@ -1529,51 +1608,51 @@ void show_general_otr_info(irc_t *irc)
ctx->username, ctx->protocol, ctx->accountname);
if(ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
- irc_usermsg(irc, " \x02%s\x02", userstring);
+ irc_rootmsg(irc, " \x02%s\x02", userstring);
} else {
- irc_usermsg(irc, " %s", userstring);
+ irc_rootmsg(irc, " %s", userstring);
}
g_free(userstring);
}
if(ctx == irc->otr->us->context_root)
- irc_usermsg(irc, " (none)");
+ irc_rootmsg(irc, " (none)");
}
void show_otr_context_info(irc_t *irc, ConnContext *ctx)
{
switch(ctx->otr_offer) {
case OFFER_NOT:
- irc_usermsg(irc, " otr offer status: none sent");
+ irc_rootmsg(irc, " otr offer status: none sent");
break;
case OFFER_SENT:
- irc_usermsg(irc, " otr offer status: awaiting reply");
+ irc_rootmsg(irc, " otr offer status: awaiting reply");
break;
case OFFER_ACCEPTED:
- irc_usermsg(irc, " otr offer status: accepted our offer");
+ irc_rootmsg(irc, " otr offer status: accepted our offer");
break;
case OFFER_REJECTED:
- irc_usermsg(irc, " otr offer status: ignored our offer");
+ irc_rootmsg(irc, " otr offer status: ignored our offer");
break;
default:
- irc_usermsg(irc, " otr offer status: %d", ctx->otr_offer);
+ irc_rootmsg(irc, " otr offer status: %d", ctx->otr_offer);
}
switch(ctx->msgstate) {
case OTRL_MSGSTATE_PLAINTEXT:
- irc_usermsg(irc, " connection state: cleartext");
+ irc_rootmsg(irc, " connection state: cleartext");
break;
case OTRL_MSGSTATE_ENCRYPTED:
- irc_usermsg(irc, " connection state: encrypted (v%d)", ctx->protocol_version);
+ irc_rootmsg(irc, " connection state: encrypted (v%d)", ctx->protocol_version);
break;
case OTRL_MSGSTATE_FINISHED:
- irc_usermsg(irc, " connection state: shut down");
+ irc_rootmsg(irc, " connection state: shut down");
break;
default:
- irc_usermsg(irc, " connection state: %d", ctx->msgstate);
+ irc_rootmsg(irc, " connection state: %d", ctx->msgstate);
}
- irc_usermsg(irc, " fingerprints: (bold=active)");
+ irc_rootmsg(irc, " fingerprints: (bold=active)");
show_fingerprints(irc, ctx);
}
@@ -1613,20 +1692,20 @@ void otr_keygen(irc_t *irc, const char *handle, const char *protocol)
FILE *tof, *fromf;
if(pipe(to) < 0 || pipe(from) < 0) {
- irc_usermsg(irc, "otr keygen: couldn't create pipe: %s", strerror(errno));
+ irc_rootmsg(irc, "otr keygen: couldn't create pipe: %s", strerror(errno));
return;
}
tof = fdopen(to[1], "w");
fromf = fdopen(from[0], "r");
if(!tof || !fromf) {
- irc_usermsg(irc, "otr keygen: couldn't streamify pipe: %s", strerror(errno));
+ irc_rootmsg(irc, "otr keygen: couldn't streamify pipe: %s", strerror(errno));
return;
}
p = fork();
if(p<0) {
- irc_usermsg(irc, "otr keygen: couldn't fork: %s", strerror(errno));
+ irc_rootmsg(irc, "otr keygen: couldn't fork: %s", strerror(errno));
return;
}
@@ -1706,16 +1785,21 @@ gboolean keygen_finish_handler(gpointer data, gint fd, b_input_condition cond)
myfgets(filename, 512, irc->otr->from);
myfgets(msg, 512, irc->otr->from);
- irc_usermsg(irc, "%s", msg);
+ irc_rootmsg(irc, "%s", msg);
if(filename[0]) {
- char *kf = g_strdup_printf("%s%s.otr_keys", global.conf->configdir, irc->user->nick);
- char *tmp = g_strdup_printf("%s.new", kf);
- copyfile(filename, tmp);
- unlink(filename);
- rename(tmp,kf);
- otrl_privkey_read(irc->otr->us, kf);
- g_free(kf);
- g_free(tmp);
+ if(strsane(irc->user->nick)) {
+ char *kf = g_strdup_printf("%s%s.otr_keys", global.conf->configdir, irc->user->nick);
+ char *tmp = g_strdup_printf("%s.new", kf);
+ copyfile(filename, tmp);
+ unlink(filename);
+ rename(tmp,kf);
+ otrl_privkey_read(irc->otr->us, kf);
+ g_free(kf);
+ g_free(tmp);
+ } else {
+ otrl_privkey_read(irc->otr->us, filename);
+ unlink(filename);
+ }
}
/* forget this job */
@@ -1780,12 +1864,20 @@ void yes_keygen(void *data)
irc_t *irc = acc->bee->ui_data;
if(keygen_in_progress(irc, acc->user, acc->prpl->name)) {
- irc_usermsg(irc, "keygen for %s/%s already in progress",
+ irc_rootmsg(irc, "keygen for %s/%s already in progress",
acc->user, acc->prpl->name);
} else {
- irc_usermsg(irc, "starting background keygen for %s/%s",
+ irc_rootmsg(irc, "starting background keygen for %s/%s",
acc->user, acc->prpl->name);
- irc_usermsg(irc, "you will be notified when it completes");
+ irc_rootmsg(irc, "you will be notified when it completes");
otr_keygen(irc, acc->user, acc->prpl->name);
}
}
+
+/* check whether a string is safe to use in a path component */
+int strsane(const char *s)
+{
+ return strpbrk(s, "/\\") == NULL;
+}
+
+/* vim: set noet ts=4 sw=4: */
diff --git a/protocols/jabber/io.c b/protocols/jabber/io.c
index ef7d5c13..a28eea90 100644
--- a/protocols/jabber/io.c
+++ b/protocols/jabber/io.c
@@ -211,7 +211,7 @@ static gboolean jabber_read_callback( gpointer data, gint fd, b_input_condition
/* If there's no version attribute, assume
this is an old server that can't do SASL
authentication. */
- if( !sasl_supported( ic ) )
+ if( !set_getbool( &ic->acc->set, "sasl") || !sasl_supported( ic ) )
{
/* If there's no version= tag, we suppose
this server does NOT implement: XMPP 1.0,
@@ -374,7 +374,7 @@ static xt_status jabber_pkt_features( struct xt_node *node, gpointer data )
support it after all, we should try to do authentication the
other way. jabber.com doesn't seem to do SASL while it pretends
to be XMPP 1.0 compliant! */
- else if( !( jd->flags & JFLAG_AUTHENTICATED ) && sasl_supported( ic ) )
+ else if( !( jd->flags & JFLAG_AUTHENTICATED ) && set_getbool( &ic->acc->set, "sasl") && sasl_supported( ic ) )
{
if( !jabber_init_iq_auth( ic ) )
return XT_ABORT;
diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c
index e7692484..9b94b21d 100644
--- a/protocols/jabber/jabber.c
+++ b/protocols/jabber/jabber.c
@@ -83,6 +83,9 @@ static void jabber_init( account_t *acc )
s = set_add( &acc->set, "tls", "try", set_eval_tls, acc );
s->flags |= ACC_SET_OFFLINE_ONLY;
+ s = set_add( &acc->set, "sasl", "true", set_eval_bool, acc );
+ s->flags |= ACC_SET_OFFLINE_ONLY | SET_HIDDEN_DEFAULT;
+
s = set_add( &acc->set, "user_agent", "BitlBee", NULL, acc );
s = set_add( &acc->set, "xmlconsole", "false", set_eval_bool, acc );
diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c
index 3304d99e..f4618cef 100644
--- a/protocols/jabber/s5bytestream.c
+++ b/protocols/jabber/s5bytestream.c
@@ -876,7 +876,8 @@ jabber_streamhost_t *jabber_si_parse_proxy( struct im_connection *ic, char *prox
jabber_streamhost_t *sh;
if( ( ( host = strchr( proxy, ',' ) ) == 0 ) ||
- ( ( port = strchr( host+1, ',' ) ) == 0 ) ) {
+ ( ( port = strchr( host+1, ',' ) ) == 0 ) )
+ {
imcb_log( ic, "Error parsing proxy setting: \"%s\" (ignored)", proxy );
return NULL;
}
@@ -888,7 +889,7 @@ jabber_streamhost_t *jabber_si_parse_proxy( struct im_connection *ic, char *prox
sh = g_new0( jabber_streamhost_t, 1 );
sh->jid = g_strdup( jid );
sh->host = g_strdup( host );
- strcpy( sh->port, port );
+ g_snprintf( sh->port, sizeof( sh->port ), "%s", port );
return sh;
}
@@ -914,7 +915,7 @@ void jabber_si_set_proxies( struct bs_transfer *bt )
sh = g_new0( jabber_streamhost_t, 1 );
sh->jid = g_strdup( tf->ini_jid );
sh->host = g_strdup( host );
- strcpy( sh->port, port );
+ g_snprintf( sh->port, sizeof( sh->port ), "%s", port );
bt->streamhosts = g_slist_append( bt->streamhosts, sh );
bt->tf->watch_in = b_input_add( tf->fd, B_EV_IO_READ, jabber_bs_send_handshake, bt );
diff --git a/protocols/msn/ns.c b/protocols/msn/ns.c
index 604e2f4e..e144a8d2 100644
--- a/protocols/msn/ns.c
+++ b/protocols/msn/ns.c
@@ -479,7 +479,8 @@ static int msn_ns_command( struct msn_handler_data *handler, char **cmd, int num
}
else
{
- imcb_error( ic, "Session terminated by remote server (reason unknown)" );
+ imcb_error( ic, "Session terminated by remote server (%s)",
+ cmd[1] ? cmd[1] : "reason unknown)" );
}
imc_logout( ic, allow_reconnect );
diff --git a/protocols/msn/sb.c b/protocols/msn/sb.c
index 37ac2889..69114469 100644
--- a/protocols/msn/sb.c
+++ b/protocols/msn/sb.c
@@ -307,7 +307,6 @@ gboolean msn_sb_connected( gpointer data, gint source, b_input_condition cond )
{
struct msn_switchboard *sb = data;
struct im_connection *ic;
- struct msn_data *md;
char buf[1024];
/* Are we still alive? */
@@ -315,7 +314,6 @@ gboolean msn_sb_connected( gpointer data, gint source, b_input_condition cond )
return FALSE;
ic = sb->ic;
- md = ic->proto_data;
if( source != sb->fd )
{
@@ -674,16 +672,12 @@ static int msn_sb_message( struct msn_handler_data *handler, char *msg, int msgl
struct msn_switchboard *sb = handler->data;
struct im_connection *ic = sb->ic;
char *body;
- int blen = 0;
if( !num_parts )
return( 1 );
if( ( body = strstr( msg, "\r\n\r\n" ) ) )
- {
body += 4;
- blen = msglen - ( body - msg );
- }
if( strcmp( cmd[0], "MSG" ) == 0 )
{
diff --git a/protocols/msn/soap.c b/protocols/msn/soap.c
index dac46a75..7d9f3791 100644
--- a/protocols/msn/soap.c
+++ b/protocols/msn/soap.c
@@ -209,24 +209,25 @@ static char *msn_soap_abservice_build( const char *body_fmt, const char *scenari
static void msn_soap_debug_print( const char *headers, const char *payload )
{
char *s;
- int st;
if( !getenv( "BITLBEE_DEBUG" ) )
return;
- if( ( s = strstr( headers, "\r\n\r\n" ) ) )
- st = write( 1, s, s - headers + 4 );
- else
- st = write( 1, headers, strlen( headers ) );
+ if( headers )
+ {
+ if( ( s = strstr( headers, "\r\n\r\n" ) ) )
+ write( 2, headers, s - headers + 4 );
+ else
+ write( 2, headers, strlen( headers ) );
+ }
-#ifdef DEBUG
+ if( payload )
{
struct xt_node *xt = xt_from_string( payload );
if( xt )
xt_print( xt );
xt_free_node( xt );
}
-#endif
}
int msn_soapq_flush( struct im_connection *ic, gboolean resend )
@@ -660,7 +661,7 @@ static xt_status msn_soap_memlist_member( struct xt_node *node, gpointer data )
bd->flags |= MSN_BUDDY_PL;
if( getenv( "BITLBEE_DEBUG" ) )
- printf( "%p %s %d\n", bu, handle, bd->flags );
+ fprintf( stderr, "%p %s %d\n", bu, handle, bd->flags );
return XT_HANDLED;
}
@@ -807,7 +808,7 @@ static xt_status msn_soap_addressbook_group( struct xt_node *node, gpointer data
}
if( getenv( "BITLBEE_DEBUG" ) )
- printf( "%s %s\n", id, name );
+ fprintf( stderr, "%s %s\n", id, name );
return XT_HANDLED;
}
@@ -868,7 +869,7 @@ static xt_status msn_soap_addressbook_contact( struct xt_node *node, gpointer da
imcb_add_buddy( ic, handle, group->name );
if( getenv( "BITLBEE_DEBUG" ) )
- printf( "%s %s %s %s\n", id, type, handle, display_name );
+ fprintf( stderr, "%s %s %s %s\n", id, type, handle, display_name );
return XT_HANDLED;
}
diff --git a/protocols/nogaim.c b/protocols/nogaim.c
index 8fb85ea7..a47e0e84 100644
--- a/protocols/nogaim.c
+++ b/protocols/nogaim.c
@@ -215,9 +215,9 @@ static void serv_got_crap( struct im_connection *ic, char *format, ... )
/* If we found one, include the screenname in the message. */
if( a )
/* FIXME(wilmer): ui_log callback or so */
- irc_usermsg( ic->bee->ui_data, "%s(%s) - %s", ic->acc->prpl->name, ic->acc->user, text );
+ irc_rootmsg( ic->bee->ui_data, "%s - %s", ic->acc->tag, text );
else
- irc_usermsg( ic->bee->ui_data, "%s - %s", ic->acc->prpl->name, text );
+ irc_rootmsg( ic->bee->ui_data, "%s - %s", ic->acc->prpl->name, text );
g_free( text );
}
diff --git a/protocols/oscar/chat.c b/protocols/oscar/chat.c
index fbf45693..a5db8fba 100644
--- a/protocols/oscar/chat.c
+++ b/protocols/oscar/chat.c
@@ -383,7 +383,6 @@ static int infoupdate(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, a
guint8 detaillevel = 0;
char *roomname = NULL;
struct aim_chat_roominfo roominfo;
- guint16 tlvcount = 0;
aim_tlvlist_t *tlvlist;
char *roomdesc = NULL;
guint16 flags = 0;
@@ -400,7 +399,7 @@ static int infoupdate(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, a
return 1;
}
- tlvcount = aimbs_get16(bs);
+ aimbs_get16(bs); /* tlv count */
/*
* Everything else are TLVs.
diff --git a/protocols/oscar/chatnav.c b/protocols/oscar/chatnav.c
index 7cfc52af..1aefd6e7 100644
--- a/protocols/oscar/chatnav.c
+++ b/protocols/oscar/chatnav.c
@@ -138,12 +138,8 @@ static int parseinfo_perms(aim_session_t *sess, aim_module_t *mod, aim_frame_t *
/*
* Type 0x0002: Unknown
*/
- if (aim_gettlv(innerlist, 0x0002, 1)) {
- guint16 classperms;
-
- classperms = aim_gettlv16(innerlist, 0x0002, 1);
-
- }
+ if (aim_gettlv(innerlist, 0x0002, 1))
+ ;
/*
* Type 0x00c9: Flags
@@ -204,9 +200,7 @@ static int parseinfo_perms(aim_session_t *sess, aim_module_t *mod, aim_frame_t *
*
*/
if (aim_gettlv(innerlist, 0x00d5, 1)) {
- guint8 createperms;
-
- createperms = aim_gettlv8(innerlist, 0x00d5, 1);
+ aim_gettlv8(innerlist, 0x00d5, 1); /* createperms */
}
/*
diff --git a/protocols/oscar/icq.c b/protocols/oscar/icq.c
index f7c02e04..c2f8dda6 100644
--- a/protocols/oscar/icq.c
+++ b/protocols/oscar/icq.c
@@ -234,8 +234,7 @@ static int icqresponse(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx,
aim_tlvlist_t *tl;
aim_tlv_t *datatlv;
aim_bstream_t qbs;
- guint32 ouruin;
- guint16 cmdlen, cmd, reqid;
+ guint16 cmd, reqid;
if (!(tl = aim_readtlvchain(bs)) || !(datatlv = aim_gettlv(tl, 0x0001, 1))) {
aim_freetlvchain(&tl);
@@ -245,8 +244,8 @@ static int icqresponse(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx,
aim_bstream_init(&qbs, datatlv->value, datatlv->length);
- cmdlen = aimbs_getle16(&qbs);
- ouruin = aimbs_getle32(&qbs);
+ aimbs_getle16(&qbs); /* cmdlen */
+ aimbs_getle32(&qbs); /* ouruin */
cmd = aimbs_getle16(&qbs);
reqid = aimbs_getle16(&qbs);
diff --git a/protocols/oscar/im.c b/protocols/oscar/im.c
index 4169ea4d..231dd959 100644
--- a/protocols/oscar/im.c
+++ b/protocols/oscar/im.c
@@ -918,7 +918,6 @@ static int outgoingim(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, a
{
int i, ret = 0;
aim_rxcallback_t userfunc;
- guint8 cookie[8];
guint16 channel;
aim_tlvlist_t *tlvlist;
char *sn;
@@ -930,7 +929,7 @@ static int outgoingim(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, a
/* ICBM Cookie. */
for (i = 0; i < 8; i++)
- cookie[i] = aimbs_get8(bs);
+ aimbs_get8(bs);
/* Channel ID */
channel = aimbs_get16(bs);
@@ -1413,7 +1412,7 @@ static void incomingim_ch2_icqserverrelay_free(aim_session_t *sess, struct aim_i
static void incomingim_ch2_icqserverrelay(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_userinfo_t *userinfo, struct aim_incomingim_ch2_args *args, aim_bstream_t *servdata)
{
guint16 hdrlen, msglen, dc;
- guint8 msgtype, msgflags;
+ guint8 msgtype;
guint8 *plugin;
int i = 0, tmp = 0;
struct im_connection *ic = sess->aux_data;
@@ -1441,7 +1440,7 @@ static void incomingim_ch2_icqserverrelay(aim_session_t *sess, aim_module_t *mod
if (!tmp) { /* message follows */
msgtype = aimbs_getle8(servdata);
- msgflags = aimbs_getle8(servdata);
+ aimbs_getle8(servdata); /* msgflags */
aim_bstream_advance(servdata, 0x04); /* status code and priority code */
diff --git a/protocols/oscar/misc.c b/protocols/oscar/misc.c
index e5c5c26f..58fb6c31 100644
--- a/protocols/oscar/misc.c
+++ b/protocols/oscar/misc.c
@@ -309,7 +309,6 @@ int aim_setdirectoryinfo(aim_session_t *sess, aim_conn_t *conn, const char *firs
int aim_setuserinterests(aim_session_t *sess, aim_conn_t *conn, const char *interest1, const char *interest2, const char *interest3, const char *interest4, const char *interest5, guint16 privacy)
{
aim_frame_t *fr;
- aim_snacid_t snacid;
aim_tlvlist_t *tl = NULL;
/* ?? privacy ?? */
@@ -329,7 +328,7 @@ int aim_setuserinterests(aim_session_t *sess, aim_conn_t *conn, const char *inte
if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+aim_sizetlvchain(&tl))))
return -ENOMEM;
- snacid = aim_cachesnac(sess, 0x0002, 0x000f, 0x0000, NULL, 0);
+ aim_cachesnac(sess, 0x0002, 0x000f, 0x0000, NULL, 0);
aim_putsnac(&fr->data, 0x0002, 0x000f, 0x0000, 0);
aim_writetlvchain(&fr->data, &tl);
diff --git a/protocols/oscar/oscar.c b/protocols/oscar/oscar.c
index 0d23b7e8..de4efb6a 100644
--- a/protocols/oscar/oscar.c
+++ b/protocols/oscar/oscar.c
@@ -1071,12 +1071,12 @@ static int incomingim_chan2(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_
static void gaim_icq_authgrant(void *data_) {
struct icq_auth *data = data_;
- char *uin, message;
+ char *uin;
struct oscar_data *od = (struct oscar_data *)data->ic->proto_data;
uin = g_strdup_printf("%u", data->uin);
- message = 0;
aim_ssi_auth_reply(od->sess, od->conn, uin, 1, "");
+ // char *message = 0;
// aim_send_im_ch4(od->sess, uin, AIM_ICQMSG_AUTHGRANTED, &message);
imcb_ask_add(data->ic, uin, NULL);
@@ -1218,11 +1218,11 @@ static int gaim_parse_incoming_im(aim_session_t *sess, aim_frame_t *fr, ...) {
static int gaim_parse_misses(aim_session_t *sess, aim_frame_t *fr, ...) {
va_list ap;
- guint16 chan, nummissed, reason;
+ guint16 nummissed, reason;
aim_userinfo_t *userinfo;
va_start(ap, fr);
- chan = (guint16)va_arg(ap, unsigned int);
+ va_arg(ap, unsigned int); /* chan */
userinfo = va_arg(ap, aim_userinfo_t *);
nummissed = (guint16)va_arg(ap, unsigned int);
reason = (guint16)va_arg(ap, unsigned int);
@@ -1334,13 +1334,12 @@ static int gaim_parse_locerr(aim_session_t *sess, aim_frame_t *fr, ...) {
}
static int gaim_parse_motd(aim_session_t *sess, aim_frame_t *fr, ...) {
- char *msg;
guint16 id;
va_list ap;
va_start(ap, fr);
id = (guint16)va_arg(ap, unsigned int);
- msg = va_arg(ap, char *);
+ va_arg(ap, char *); /* msg */
va_end(ap);
if (id < 4)
@@ -1360,13 +1359,9 @@ static int gaim_chatnav_info(aim_session_t *sess, aim_frame_t *fr, ...) {
switch(type) {
case 0x0002: {
- guint8 maxrooms;
- struct aim_chat_exchangeinfo *exchanges;
- int exchangecount; // i;
-
- maxrooms = (guint8)va_arg(ap, unsigned int);
- exchangecount = va_arg(ap, int);
- exchanges = va_arg(ap, struct aim_chat_exchangeinfo *);
+ va_arg(ap, unsigned int); /* maxrooms */
+ va_arg(ap, int); /* exchangecount */
+ va_arg(ap, struct aim_chat_exchangeinfo *); /* exchanges */
va_end(ap);
while (odata->create_rooms) {
@@ -1379,21 +1374,19 @@ static int gaim_chatnav_info(aim_session_t *sess, aim_frame_t *fr, ...) {
}
break;
case 0x0008: {
- char *fqcn, *name, *ck;
- guint16 instance, flags, maxmsglen, maxoccupancy, unknown, exchange;
- guint8 createperms;
- guint32 createtime;
+ char *ck;
+ guint16 instance, exchange;
- fqcn = va_arg(ap, char *);
+ va_arg(ap, char *); /* fqcn */
instance = (guint16)va_arg(ap, unsigned int);
exchange = (guint16)va_arg(ap, unsigned int);
- flags = (guint16)va_arg(ap, unsigned int);
- createtime = va_arg(ap, guint32);
- maxmsglen = (guint16)va_arg(ap, unsigned int);
- maxoccupancy = (guint16)va_arg(ap, unsigned int);
- createperms = (guint8)va_arg(ap, int);
- unknown = (guint16)va_arg(ap, unsigned int);
- name = va_arg(ap, char *);
+ va_arg(ap, unsigned int); /* flags */
+ va_arg(ap, guint32); /* createtime */
+ va_arg(ap, unsigned int); /* maxmsglen */
+ va_arg(ap, unsigned int); /* maxoccupancy */
+ va_arg(ap, int); /* createperms */
+ va_arg(ap, unsigned int); /* unknown */
+ va_arg(ap, char *); /* name */
ck = va_arg(ap, char *);
va_end(ap);
@@ -1455,27 +1448,21 @@ static int gaim_chat_leave(aim_session_t *sess, aim_frame_t *fr, ...) {
static int gaim_chat_info_update(aim_session_t *sess, aim_frame_t *fr, ...) {
va_list ap;
- aim_userinfo_t *userinfo;
- struct aim_chat_roominfo *roominfo;
- char *roomname;
- int usercount;
- char *roomdesc;
- guint16 unknown_c9, unknown_d2, unknown_d5, maxmsglen, maxvisiblemsglen;
- guint32 creationtime;
+ guint16 maxmsglen, maxvisiblemsglen;
struct im_connection *ic = sess->aux_data;
struct chat_connection *ccon = find_oscar_chat_by_conn(ic, fr->conn);
va_start(ap, fr);
- roominfo = va_arg(ap, struct aim_chat_roominfo *);
- roomname = va_arg(ap, char *);
- usercount= va_arg(ap, int);
- userinfo = va_arg(ap, aim_userinfo_t *);
- roomdesc = va_arg(ap, char *);
- unknown_c9 = (guint16)va_arg(ap, int);
- creationtime = (guint32)va_arg(ap, unsigned long);
+ va_arg(ap, struct aim_chat_roominfo *); /* roominfo */
+ va_arg(ap, char *); /* roomname */
+ va_arg(ap, int); /* usercount */
+ va_arg(ap, aim_userinfo_t *); /* userinfo */
+ va_arg(ap, char *); /* roomdesc */
+ va_arg(ap, int); /* unknown_c9 */
+ va_arg(ap, unsigned long); /* creationtime */
maxmsglen = (guint16)va_arg(ap, int);
- unknown_d2 = (guint16)va_arg(ap, int);
- unknown_d5 = (guint16)va_arg(ap, int);
+ va_arg(ap, int); /* unknown_d2 */
+ va_arg(ap, int); /* unknown_d5 */
maxvisiblemsglen = (guint16)va_arg(ap, int);
va_end(ap);
@@ -1516,19 +1503,19 @@ static int gaim_parse_ratechange(aim_session_t *sess, aim_frame_t *fr, ...) {
};
#endif
va_list ap;
- guint16 code, rateclass;
- guint32 windowsize, clear, alert, limit, disconnect, currentavg, maxavg;
+ guint16 code;
+ guint32 windowsize, clear, currentavg;
va_start(ap, fr);
code = (guint16)va_arg(ap, unsigned int);
- rateclass= (guint16)va_arg(ap, unsigned int);
+ va_arg(ap, unsigned int); /* rateclass */
windowsize = (guint32)va_arg(ap, unsigned long);
clear = (guint32)va_arg(ap, unsigned long);
- alert = (guint32)va_arg(ap, unsigned long);
- limit = (guint32)va_arg(ap, unsigned long);
- disconnect = (guint32)va_arg(ap, unsigned long);
+ va_arg(ap, unsigned long); /* alert */
+ va_arg(ap, unsigned long); /* limit */
+ va_arg(ap, unsigned long); /* disconnect */
currentavg = (guint32)va_arg(ap, unsigned long);
- maxavg = (guint32)va_arg(ap, unsigned long);
+ va_arg(ap, unsigned long); /* maxavg */
va_end(ap);
/* XXX fix these values */
@@ -2101,6 +2088,10 @@ static int gaim_ssi_parseack( aim_session_t *sess, aim_frame_t *fr, ... )
aim_ssi_auth_request( sess, fr->conn, list, "" );
aim_ssi_addbuddies( sess, fr->conn, OSCAR_GROUP, &list, 1, 1 );
}
+ else if( st == 0x0A )
+ {
+ imcb_error( sess->aux_data, "Buddy %s is already in your list", list );
+ }
else
{
imcb_error( sess->aux_data, "Error while adding buddy: 0x%04x", st );
@@ -2412,11 +2403,11 @@ int gaim_parsemtn(aim_session_t *sess, aim_frame_t *fr, ...)
{
struct im_connection * ic = sess->aux_data;
va_list ap;
- guint16 type1, type2;
+ guint16 type2;
char * sn;
va_start(ap, fr);
- type1 = va_arg(ap, int);
+ va_arg(ap, int); /* type1 */
sn = va_arg(ap, char*);
type2 = va_arg(ap, int);
va_end(ap);
@@ -2536,9 +2527,7 @@ struct groupchat *oscar_chat_join_internal(struct im_connection *ic, const char
aim_conn_t * cur;
if((cur = aim_getconn_type(od->sess, AIM_CONN_TYPE_CHATNAV))) {
- int st;
-
- st = aim_chatnav_createroom(od->sess, cur, room, exchange_number);
+ aim_chatnav_createroom(od->sess, cur, room, exchange_number);
return ret;
} else {
@@ -2565,7 +2554,6 @@ struct groupchat *oscar_chat_with(struct im_connection * ic, char *who)
struct groupchat *ret;
static int chat_id = 0;
char * chatname, *s;
- struct groupchat *c;
chatname = g_strdup_printf("%s%s%d", isdigit(*ic->acc->user) ? "icq" : "",
ic->acc->user, chat_id++);
@@ -2574,13 +2562,12 @@ struct groupchat *oscar_chat_with(struct im_connection * ic, char *who)
if (!isalnum(*s))
*s = '0';
- c = imcb_chat_new(ic, chatname);
ret = oscar_chat_join_internal(ic, chatname, NULL, NULL, 4);
aim_chat_invite(od->sess, od->conn, who, "", 4, chatname, 0x0);
g_free(chatname);
- return NULL;
+ return ret;
}
void oscar_accept_chat(void *data)
diff --git a/protocols/oscar/rxqueue.c b/protocols/oscar/rxqueue.c
index 34f389af..081e967c 100644
--- a/protocols/oscar/rxqueue.c
+++ b/protocols/oscar/rxqueue.c
@@ -387,10 +387,8 @@ int aim_get_command(aim_session_t *sess, aim_conn_t *conn)
* or we break. We must handle it just in case.
*/
if (aimbs_get8(&flaphdr) != 0x2a) {
- guint8 start;
-
aim_bstream_rewind(&flaphdr);
- start = aimbs_get8(&flaphdr);
+ aimbs_get8(&flaphdr);
imcb_error(sess->aux_data, "FLAP framing disrupted");
aim_conn_close(conn);
return -1;
diff --git a/protocols/oscar/service.c b/protocols/oscar/service.c
index acd09150..a2678764 100644
--- a/protocols/oscar/service.c
+++ b/protocols/oscar/service.c
@@ -562,9 +562,7 @@ static int migrate(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_
*/
groupcount = aimbs_get16(bs);
for (i = 0; i < groupcount; i++) {
- guint16 group;
-
- group = aimbs_get16(bs);
+ aimbs_get16(bs);
imcb_error(sess->aux_data, "bifurcated migration unsupported");
}
@@ -700,11 +698,10 @@ int aim_setversions(aim_session_t *sess, aim_conn_t *conn)
/* Host versions (group 1, subtype 0x18) */
static int hostversions(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
{
- int vercount;
guint8 *versions;
/* This is frivolous. (Thank you SmarterChild.) */
- vercount = aim_bstream_empty(bs)/4;
+ aim_bstream_empty(bs); /* == vercount * 4 */
versions = aimbs_getraw(bs, aim_bstream_empty(bs));
g_free(versions);
@@ -730,7 +727,6 @@ int aim_setextstatus(aim_session_t *sess, aim_conn_t *conn, guint32 status)
aim_snacid_t snacid;
aim_tlvlist_t *tl = NULL;
guint32 data;
- int tlvlen;
struct im_connection *ic = sess ? sess->aux_data : NULL;
data = AIM_ICQ_STATE_HIDEIP | status; /* yay for error checking ;^) */
@@ -738,7 +734,7 @@ int aim_setextstatus(aim_session_t *sess, aim_conn_t *conn, guint32 status)
if (ic && set_getbool(&ic->acc->set, "web_aware"))
data |= AIM_ICQ_STATE_WEBAWARE;
- tlvlen = aim_addtlvtochain32(&tl, 0x0006, data);
+ aim_addtlvtochain32(&tl, 0x0006, data); /* tlvlen */
if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 8)))
return -ENOMEM;
diff --git a/protocols/purple/purple.c b/protocols/purple/purple.c
index 55678f92..3dedd20c 100644
--- a/protocols/purple/purple.c
+++ b/protocols/purple/purple.c
@@ -180,7 +180,7 @@ static void purple_init( account_t *acc )
default:
/** No way to talk to the user right now, invent one when
this becomes important.
- irc_usermsg( acc->irc, "Setting with unknown type: %s (%d) Expect stuff to break..\n",
+ irc_rootmsg( acc->irc, "Setting with unknown type: %s (%d) Expect stuff to break..\n",
name, purple_account_option_get_type( o ) );
*/
name = NULL;
@@ -749,6 +749,8 @@ static void prplcb_blist_update( PurpleBuddyList *list, PurpleBlistNode *node )
if( bud->server_alias )
imcb_rename_buddy( ic, bud->name, bud->server_alias );
+ else if( bud->alias )
+ imcb_rename_buddy( ic, bud->name, bud->alias );
if( group )
imcb_add_buddy( ic, bud->name, purple_group_get_name( group ) );
diff --git a/protocols/skype/.bzrignore b/protocols/skype/.bzrignore
new file mode 100644
index 00000000..e90a033b
--- /dev/null
+++ b/protocols/skype/.bzrignore
@@ -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*
diff --git a/protocols/skype/.mailmap b/protocols/skype/.mailmap
new file mode 100644
index 00000000..cc8d43f9
--- /dev/null
+++ b/protocols/skype/.mailmap
@@ -0,0 +1 @@
+Miklos Vajna <vmiklos@frugalware.org>
diff --git a/protocols/skype/HACKING b/protocols/skype/HACKING
new file mode 100644
index 00000000..f5516832
--- /dev/null
+++ b/protocols/skype/HACKING
@@ -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
diff --git a/protocols/skype/Makefile b/protocols/skype/Makefile
new file mode 100644
index 00000000..e048e0bc
--- /dev/null
+++ b/protocols/skype/Makefile
@@ -0,0 +1,107 @@
+-include ../../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)protocols/skype/
+endif
+
+VERSION = 0.9.0
+DATE := $(shell date +%Y-%m-%d)
+# latest stable
+BITLBEE_VERSION = 3.0.1
+INSTALL = install
+ASCIIDOC = yes
+
+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): $(SRCDIR)skype.c config.mak
+ifeq ($(BITLBEE),yes)
+ $(CC) $(CFLAGS) $(SHARED_FLAGS) -o skype.$(SHARED_EXT) $(SRCDIR)skype.c $(LDFLAGS)
+endif
+
+install: all install-doc
+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: $(SRCDIR)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 $(MANPAGES)
+
+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: $(MANPAGES)
+
+install-doc: doc
+ifeq ($(ASCIIDOC),yes)
+ $(INSTALL) -d $(DESTDIR)$(MANDIR)/man1
+ $(INSTALL) -m644 $(MANPAGES) $(DESTDIR)$(MANDIR)/man1
+endif
+
+uninstall-doc:
+ rm -f $(DESTDIR)$(MANDIR)/man1/skyped.1*
+
+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: $(SRCDIR)%.txt $(SRCDIR)asciidoc.conf
+ a2x --asciidoc-opts="-f $(SRCDIR)asciidoc.conf" \
+ -a bs_version=$(VERSION) -a bs_date=$(DATE) -f manpage -D . $<
diff --git a/protocols/skype/NEWS b/protocols/skype/NEWS
new file mode 100644
index 00000000..b55b34c6
--- /dev/null
+++ b/protocols/skype/NEWS
@@ -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
diff --git a/protocols/skype/README b/protocols/skype/README
new file mode 100644
index 00000000..2c962d54
--- /dev/null
+++ b/protocols/skype/README
@@ -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
diff --git a/protocols/skype/asciidoc.conf b/protocols/skype/asciidoc.conf
new file mode 100644
index 00000000..24a649c1
--- /dev/null
+++ b/protocols/skype/asciidoc.conf
@@ -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[]
diff --git a/protocols/skype/client.sh b/protocols/skype/client.sh
new file mode 100644
index 00000000..7d7689a8
--- /dev/null
+++ b/protocols/skype/client.sh
@@ -0,0 +1 @@
+openssl s_client -host localhost -port 2727 -verify 0
diff --git a/protocols/skype/skype.c b/protocols/skype/skype.c
new file mode 100644
index 00000000..5b1a6c30
--- /dev/null
+++ b/protocols/skype/skype.c
@@ -0,0 +1,1566 @@
+/*
+ * 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 <stdio.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 = bee_chat_by_title(ic->bee, ic, id);
+ if (!gc) {
+ 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");
+
+ while( ic->groupchats )
+ imcb_chat_free(ic->groupchats->data);
+
+ 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;
+
+ nick = g_strdup(who);
+ 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);
+}
diff --git a/protocols/skype/skyped.cnf b/protocols/skype/skyped.cnf
new file mode 100644
index 00000000..c7dc9098
--- /dev/null
+++ b/protocols/skype/skyped.cnf
@@ -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
+
diff --git a/protocols/skype/skyped.conf.dist b/protocols/skype/skyped.conf.dist
new file mode 100644
index 00000000..21e07796
--- /dev/null
+++ b/protocols/skype/skyped.conf.dist
@@ -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
diff --git a/protocols/skype/skyped.py b/protocols/skype/skyped.py
new file mode 100644
index 00000000..3b6499c1
--- /dev/null
+++ b/protocols/skype/skyped.py
@@ -0,0 +1,495 @@
+#!/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
+ if ":" in host:
+ sock = socket.socket(socket.AF_INET6)
+ else:
+ 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()
+ try:
+ options.conn = ssl.wrap_socket(rawsock,
+ server_side=True,
+ certfile=options.config.sslcert,
+ keyfile=options.config.sslkey,
+ ssl_version=ssl.PROTOCOL_TLSv1)
+ except ssl.SSLError:
+ dprint("Warning, SSL init failed, did you create your certificate?")
+ return False
+ 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")
+ # fall back to system-wide settings
+ self.syscfgpath = "/usr/local/etc/skyped/skyped.conf"
+ if os.path.exists(self.syscfgpath) and not os.path.exists(self.cfgpath):
+ 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, supports IPv4 and IPv6 (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)
diff --git a/protocols/skype/skyped.txt b/protocols/skype/skyped.txt
new file mode 100644
index 00000000..53f2626d
--- /dev/null
+++ b/protocols/skype/skyped.txt
@@ -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>
diff --git a/protocols/skype/t/Makefile b/protocols/skype/t/Makefile
new file mode 100644
index 00000000..9c5e95f9
--- /dev/null
+++ b/protocols/skype/t/Makefile
@@ -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
+
+
diff --git a/protocols/skype/t/bitlbee.conf b/protocols/skype/t/bitlbee.conf
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/protocols/skype/t/bitlbee.conf
diff --git a/protocols/skype/t/irssi/livetest-irssi.sh b/protocols/skype/t/irssi/livetest-irssi.sh
new file mode 100755
index 00000000..a8e136cf
--- /dev/null
+++ b/protocols/skype/t/irssi/livetest-irssi.sh
@@ -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
diff --git a/protocols/skype/t/irssi/skype-call.test b/protocols/skype/t/irssi/skype-call.test
new file mode 100644
index 00000000..8f502a59
--- /dev/null
+++ b/protocols/skype/t/irssi/skype-call.test
@@ -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'
diff --git a/protocols/skype/t/irssi/skype-info.test b/protocols/skype/t/irssi/skype-info.test
new file mode 100644
index 00000000..e8507321
--- /dev/null
+++ b/protocols/skype/t/irssi/skype-info.test
@@ -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'
diff --git a/protocols/skype/t/irssi/skype-login.test b/protocols/skype/t/irssi/skype-login.test
new file mode 100644
index 00000000..ca627002
--- /dev/null
+++ b/protocols/skype/t/irssi/skype-login.test
@@ -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'
diff --git a/protocols/skype/t/irssi/skype-msg.test b/protocols/skype/t/irssi/skype-msg.test
new file mode 100644
index 00000000..d35615cd
--- /dev/null
+++ b/protocols/skype/t/irssi/skype-msg.test
@@ -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'
diff --git a/protocols/skype/t/irssi/trigger.pl b/protocols/skype/t/irssi/trigger.pl
new file mode 100644
index 00000000..02f8951f
--- /dev/null
+++ b/protocols/skype/t/irssi/trigger.pl
@@ -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();
diff --git a/protocols/skype/t/livetest-bitlbee.sh b/protocols/skype/t/livetest-bitlbee.sh
new file mode 100755
index 00000000..7cbfbf6e
--- /dev/null
+++ b/protocols/skype/t/livetest-bitlbee.sh
@@ -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
diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c
index 57a1ed80..76ccc3eb 100644
--- a/protocols/twitter/twitter.c
+++ b/protocols/twitter/twitter.c
@@ -29,12 +29,12 @@
#include "url.h"
#define twitter_msg( ic, fmt... ) \
- do { \
- struct twitter_data *td = ic->proto_data; \
- if( td->home_timeline_gc ) \
- imcb_chat_log( td->home_timeline_gc, fmt ); \
- else \
- imcb_log( ic, fmt ); \
+ do { \
+ struct twitter_data *td = ic->proto_data; \
+ if( td->timeline_gc ) \
+ imcb_chat_log( td->timeline_gc, fmt ); \
+ else \
+ imcb_log( ic, fmt ); \
} while( 0 );
GSList *twitter_connections = NULL;
@@ -51,7 +51,7 @@ gboolean twitter_main_loop(gpointer data, gint fd, b_input_condition cond)
return 0;
// Do stuff..
- twitter_get_home_timeline(ic, -1);
+ twitter_get_timeline(ic, -1);
// If we are still logged in run this function again after timeout.
return (ic->flags & OPT_LOGGED_IN) == OPT_LOGGED_IN;
@@ -68,7 +68,8 @@ static void twitter_main_loop_start(struct im_connection *ic)
// Queue the main_loop
// Save the return value, so we can remove the timeout on logout.
- td->main_loop_id = b_timeout_add(60000, twitter_main_loop, ic);
+ td->main_loop_id =
+ b_timeout_add(set_getint(&ic->acc->set, "fetch_interval") * 1000, twitter_main_loop, ic);
}
static void twitter_oauth_start(struct im_connection *ic);
@@ -77,6 +78,8 @@ void twitter_login_finish(struct im_connection *ic)
{
struct twitter_data *td = ic->proto_data;
+ td->flags &= ~TWITTER_DOING_TIMELINE;
+
if (set_getbool(&ic->acc->set, "oauth") && !td->oauth_info)
twitter_oauth_start(ic);
else if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "one") != 0 &&
@@ -89,16 +92,16 @@ void twitter_login_finish(struct im_connection *ic)
}
static const struct oauth_service twitter_oauth = {
- "http://api.twitter.com/oauth/request_token",
- "http://api.twitter.com/oauth/access_token",
+ "https://api.twitter.com/oauth/request_token",
+ "https://api.twitter.com/oauth/access_token",
"https://api.twitter.com/oauth/authorize",
.consumer_key = "xsDNKJuNZYkZyMcu914uEA",
.consumer_secret = "FCxqcr0pXKzsF9ajmP57S3VQ8V6Drk4o2QYtqMcOszo",
};
static const struct oauth_service identica_oauth = {
- "http://identi.ca/api/oauth/request_token",
- "http://identi.ca/api/oauth/access_token",
+ "https://identi.ca/api/oauth/request_token",
+ "https://identi.ca/api/oauth/access_token",
"https://identi.ca/api/oauth/authorize",
.consumer_key = "e147ff789fcbd8a5a07963afbb43f9da",
.consumer_secret = "c596267f277457ec0ce1ab7bb788d828",
@@ -215,7 +218,6 @@ static void twitter_init(account_t * acc)
def_url = TWITTER_API_URL;
def_oauth = "true";
} else { /* if( strcmp( acc->prpl->name, "identica" ) == 0 ) */
-
def_url = IDENTICA_API_URL;
def_oauth = "false";
}
@@ -227,6 +229,11 @@ static void twitter_init(account_t * acc)
s = set_add(&acc->set, "commands", "true", set_eval_bool, acc);
+ s = set_add(&acc->set, "fetch_interval", "60", set_eval_int, acc);
+ s->flags |= ACC_SET_OFFLINE_ONLY;
+
+ s = set_add(&acc->set, "fetch_mentions", "true", set_eval_bool, acc);
+
s = set_add(&acc->set, "message_length", "140", set_eval_int, acc);
s = set_add(&acc->set, "mode", "chat", set_eval_mode, acc);
@@ -235,6 +242,8 @@ static void twitter_init(account_t * acc)
s = set_add(&acc->set, "show_ids", "false", set_eval_bool, acc);
s->flags |= ACC_SET_OFFLINE_ONLY;
+ s = set_add(&acc->set, "show_old_mentions", "true", set_eval_bool, acc);
+
s = set_add(&acc->set, "oauth", def_oauth, set_eval_bool, acc);
}
@@ -316,8 +325,8 @@ static void twitter_logout(struct im_connection *ic)
// Remove the main_loop function from the function queue.
b_event_remove(td->main_loop_id);
- if (td->home_timeline_gc)
- imcb_chat_free(td->home_timeline_gc);
+ if (td->timeline_gc)
+ imcb_chat_free(td->timeline_gc);
if (td) {
oauth_info_free(td->oauth_info);
@@ -403,13 +412,13 @@ static void twitter_chat_leave(struct groupchat *c)
{
struct twitter_data *td = c->ic->proto_data;
- if (c != td->home_timeline_gc)
+ if (c != td->timeline_gc)
return; /* WTF? */
/* If the user leaves the channel: Fine. Rejoin him/her once new
tweets come in. */
- imcb_chat_free(td->home_timeline_gc);
- td->home_timeline_gc = NULL;
+ imcb_chat_free(td->timeline_gc);
+ td->timeline_gc = NULL;
}
static void twitter_keepalive(struct im_connection *ic)
@@ -464,15 +473,14 @@ static void twitter_handle_command(struct im_connection *ic, char *message)
} else if (g_strcasecmp(cmd[0], "undo") == 0) {
guint64 id;
- if (cmd[1])
- id = g_ascii_strtoull(cmd[1], NULL, 10);
- else
- id = td->last_status_id;
-
- /* TODO: User feedback. */
- if (id)
+ if (cmd[1] == NULL)
+ twitter_status_destroy(ic, td->last_status_id);
+ else if (sscanf(cmd[1], "%" G_GUINT64_FORMAT, &id) == 1) {
+ if (id < TWITTER_LOG_LENGTH && td->log)
+ id = td->log[id].id;
+
twitter_status_destroy(ic, id);
- else
+ } else
twitter_msg(ic, "Could not undo last action");
g_free(cmds);
@@ -490,11 +498,14 @@ static void twitter_handle_command(struct im_connection *ic, char *message)
bee_user_t *bu;
guint64 id;
- if ((bu = bee_user_by_handle(ic->bee, ic, cmd[1])) &&
+ if (g_str_has_prefix(cmd[1], "#") &&
+ sscanf(cmd[1] + 1, "%" G_GUINT64_FORMAT, &id) == 1) {
+ if (id < TWITTER_LOG_LENGTH && td->log)
+ id = td->log[id].id;
+ } else if ((bu = bee_user_by_handle(ic->bee, ic, cmd[1])) &&
(tud = bu->data) && tud->last_id)
id = tud->last_id;
- else {
- id = g_ascii_strtoull(cmd[1], NULL, 10);
+ else if (sscanf(cmd[1], "%" G_GUINT64_FORMAT, &id) == 1){
if (id < TWITTER_LOG_LENGTH && td->log)
id = td->log[id].id;
}
@@ -513,7 +524,15 @@ static void twitter_handle_command(struct im_connection *ic, char *message)
bee_user_t *bu = NULL;
guint64 id = 0;
- if ((bu = bee_user_by_handle(ic->bee, ic, cmd[1])) &&
+ if (g_str_has_prefix(cmd[1], "#") &&
+ sscanf(cmd[1] + 1, "%" G_GUINT64_FORMAT, &id) == 1 &&
+ (id < TWITTER_LOG_LENGTH) && td->log) {
+ bu = td->log[id].bu;
+ if (g_slist_find(ic->bee->users, bu))
+ id = td->log[id].id;
+ else
+ bu = NULL;
+ } else if ((bu = bee_user_by_handle(ic->bee, ic, cmd[1])) &&
(tud = bu->data) && tud->last_id) {
id = tud->last_id;
} else if (sscanf(cmd[1], "%" G_GUINT64_FORMAT, &id) == 1 &&
@@ -524,6 +543,7 @@ static void twitter_handle_command(struct im_connection *ic, char *message)
else
bu = NULL;
}
+
if (!id || !bu) {
twitter_msg(ic, "User `%s' does not exist or didn't "
"post any statuses recently", cmd[1]);
diff --git a/protocols/twitter/twitter.h b/protocols/twitter/twitter.h
index c38d9b86..14e43824 100644
--- a/protocols/twitter/twitter.h
+++ b/protocols/twitter/twitter.h
@@ -35,6 +35,9 @@
typedef enum
{
TWITTER_HAVE_FRIENDS = 1,
+ TWITTER_DOING_TIMELINE = 0x10000,
+ TWITTER_GOT_TIMELINE = 0x20000,
+ TWITTER_GOT_MENTIONS = 0x40000,
} twitter_flags_t;
struct twitter_log_data;
@@ -43,12 +46,17 @@ struct twitter_data
{
char* user;
struct oauth_info *oauth_info;
+
+ gpointer home_timeline_obj;
+ gpointer mentions_obj;
+
+ guint64 timeline_id;
+
GSList *follow_ids;
- guint64 home_timeline_id;
guint64 last_status_id; /* For undo */
gint main_loop_id;
- struct groupchat *home_timeline_gc;
+ struct groupchat *timeline_gc;
gint http_fails;
twitter_flags_t flags;
diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c
index 14e98c53..d52c29ff 100644
--- a/protocols/twitter/twitter_lib.c
+++ b/protocols/twitter/twitter_lib.c
@@ -77,17 +77,20 @@ static void txu_free(struct twitter_xml_user *txu)
{
if (txu == NULL)
return;
+
g_free(txu->name);
g_free(txu->screen_name);
g_free(txu);
}
-
/**
* Frees a twitter_xml_status struct.
*/
static void txs_free(struct twitter_xml_status *txs)
{
+ if (txs == NULL)
+ return;
+
g_free(txs->text);
txu_free(txs->user);
g_free(txs);
@@ -102,19 +105,40 @@ static void txl_free(struct twitter_xml_list *txl)
GSList *l;
if (txl == NULL)
return;
- for (l = txl->list; l; l = g_slist_next(l))
- if (txl->type == TXL_STATUS)
+
+ for (l = txl->list; l; l = g_slist_next(l)) {
+ if (txl->type == TXL_STATUS) {
txs_free((struct twitter_xml_status *) l->data);
- else if (txl->type == TXL_ID)
+ } else if (txl->type == TXL_ID) {
g_free(l->data);
- else if (txl->type == TXL_USER)
+ } else if (txl->type == TXL_USER) {
txu_free(l->data);
+ }
+ }
+
g_slist_free(txl->list);
g_free(txl);
}
/**
- * Add a buddy if it is not allready added, set the status to logged in.
+ * Compare status elements
+ */
+static gint twitter_compare_elements(gconstpointer a, gconstpointer b)
+{
+ struct twitter_xml_status *a_status = (struct twitter_xml_status *) a;
+ struct twitter_xml_status *b_status = (struct twitter_xml_status *) b;
+
+ if (a_status->created_at < b_status->created_at) {
+ return -1;
+ } else if (a_status->created_at > b_status->created_at) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/**
+ * Add a buddy if it is not already added, set the status to logged in.
*/
static void twitter_add_buddy(struct im_connection *ic, char *name, const char *fullname)
{
@@ -131,7 +155,7 @@ static void twitter_add_buddy(struct im_connection *ic, char *name, const char *
/* Necessary so that nicks always get translated to the
exact Twitter username. */
imcb_buddy_nick_hint(ic, name, name);
- imcb_chat_add_buddy(td->home_timeline_gc, name);
+ imcb_chat_add_buddy(td->timeline_gc, name);
} else if (g_strcasecmp(mode, "many") == 0)
imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL);
}
@@ -259,7 +283,7 @@ static void twitter_http_get_friends_ids(struct http_request *req)
}
/* Create the room now that we "logged in". */
- if (!td->home_timeline_gc && g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0)
+ if (!td->timeline_gc && g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0)
twitter_groupchat_init(ic);
txl = g_new0(struct twitter_xml_list, 1);
@@ -435,14 +459,11 @@ static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_l
static xt_status twitter_xt_get_status(struct xt_node *node, struct twitter_xml_status *txs)
{
struct xt_node *child, *rt = NULL;
- gboolean truncated = FALSE;
// Walk over the nodes children.
for (child = node->children; child; child = child->next) {
if (g_strcasecmp("text", child->name) == 0) {
txs->text = g_memdup(child->text, child->text_len + 1);
- } else if (g_strcasecmp("truncated", child->name) == 0 && child->text) {
- truncated = bool2int(child->text);
} else if (g_strcasecmp("retweeted_status", child->name) == 0) {
rt = child;
} else if (g_strcasecmp("created_at", child->name) == 0) {
@@ -463,8 +484,9 @@ static xt_status twitter_xt_get_status(struct xt_node *node, struct twitter_xml_
}
}
- /* If it's a truncated retweet, get the original because dots suck. */
- if (truncated && rt) {
+ /* If it's a (truncated) retweet, get the original. Even if the API claims it
+ wasn't truncated because it may be lying. */
+ if (rt) {
struct twitter_xml_status *rtxs = g_new0(struct twitter_xml_status, 1);
if (twitter_xt_get_status(rt, rtxs) != XT_HANDLED) {
txs_free(rtxs);
@@ -474,6 +496,27 @@ static xt_status twitter_xt_get_status(struct xt_node *node, struct twitter_xml_
g_free(txs->text);
txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text);
txs_free(rtxs);
+ } else {
+ struct xt_node *urls, *url;
+
+ urls = xt_find_path(node, "entities/urls");
+ for (url = urls ? urls->children : NULL; url; url = url->next) {
+ /* "short" is a reserved word. :-P */
+ struct xt_node *kort = xt_find_node(url->children, "url");
+ struct xt_node *disp = xt_find_node(url->children, "display_url");
+ char *pos, *new;
+
+ if (!kort || !kort->text || !disp || !disp->text ||
+ !(pos = strstr(txs->text, kort->text)))
+ continue;
+
+ *pos = '\0';
+ new = g_strdup_printf("%s%s &lt;%s&gt;%s", txs->text, kort->text,
+ disp->text, pos + strlen(kort->text));
+
+ g_free(txs->text);
+ txs->text = new;
+ }
}
return XT_HANDLED;
@@ -521,32 +564,6 @@ static xt_status twitter_xt_get_status_list(struct im_connection *ic, struct xt_
return XT_HANDLED;
}
-static void twitter_http_get_home_timeline(struct http_request *req);
-
-/**
- * Get the timeline.
- */
-void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
-{
- struct twitter_data *td = ic->proto_data;
-
- char *args[4];
- args[0] = "cursor";
- args[1] = g_strdup_printf("%lld", (long long) next_cursor);
- if (td->home_timeline_id) {
- args[2] = "since_id";
- args[3] = g_strdup_printf("%llu", (long long unsigned int) td->home_timeline_id);
- }
-
- twitter_http(ic, TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, args,
- td->home_timeline_id ? 4 : 2);
-
- g_free(args[1]);
- if (td->home_timeline_id) {
- g_free(args[3]);
- }
-}
-
static char *twitter_msg_add_id(struct im_connection *ic,
struct twitter_xml_status *txs, const char *prefix)
{
@@ -585,7 +602,7 @@ static void twitter_groupchat_init(struct im_connection *ic)
struct twitter_data *td = ic->proto_data;
GSList *l;
- td->home_timeline_gc = gc = imcb_chat_new(ic, "home/timeline");
+ td->timeline_gc = gc = imcb_chat_new(ic, "twitter/timeline");
name_hint = g_strdup_printf("%s_%s", td->prefix, ic->acc->user);
imcb_chat_name_hint(gc, name_hint);
@@ -594,7 +611,7 @@ static void twitter_groupchat_init(struct im_connection *ic)
for (l = ic->bee->users; l; l = l->next) {
bee_user_t *bu = l->data;
if (bu->ic == ic)
- imcb_chat_add_buddy(td->home_timeline_gc, bu->handle);
+ imcb_chat_add_buddy(td->timeline_gc, bu->handle);
}
}
@@ -607,12 +624,13 @@ static void twitter_groupchat(struct im_connection *ic, GSList * list)
GSList *l = NULL;
struct twitter_xml_status *status;
struct groupchat *gc;
+ guint64 last_id = 0;
// Create a new groupchat if it does not exsist.
- if (!td->home_timeline_gc)
+ if (!td->timeline_gc)
twitter_groupchat_init(ic);
- gc = td->home_timeline_gc;
+ gc = td->timeline_gc;
if (!gc->joined)
imcb_chat_add_buddy(gc, ic->acc->user);
@@ -620,26 +638,30 @@ static void twitter_groupchat(struct im_connection *ic, GSList * list)
char *msg;
status = l->data;
- if (status->user == NULL || status->text == NULL)
+ if (status->user == NULL || status->text == NULL || last_id == status->id)
continue;
- twitter_add_buddy(ic, status->user->screen_name, status->user->name);
+ last_id = status->id;
strip_html(status->text);
+
msg = twitter_msg_add_id(ic, status, "");
// Say it!
- if (g_strcasecmp(td->user, status->user->screen_name) == 0)
+ if (g_strcasecmp(td->user, status->user->screen_name) == 0) {
imcb_chat_log(gc, "You: %s", msg ? msg : status->text);
- else
+ } else {
+ twitter_add_buddy(ic, status->user->screen_name, status->user->name);
+
imcb_chat_msg(gc, status->user->screen_name,
msg ? msg : status->text, 0, status->created_at);
+ }
g_free(msg);
- // Update the home_timeline_id to hold the highest id, so that by the next request
+ // Update the timeline_id to hold the highest id, so that by the next request
// we won't pick up the updates already in the list.
- td->home_timeline_id = MAX(td->home_timeline_id, status->id);
+ td->timeline_id = MAX(td->timeline_id, status->id);
}
}
@@ -653,6 +675,7 @@ static void twitter_private_message_chat(struct im_connection *ic, GSList * list
struct twitter_xml_status *status;
char from[MAX_STRING];
gboolean mode_one;
+ guint64 last_id = 0;
mode_one = g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "one") == 0;
@@ -665,6 +688,10 @@ static void twitter_private_message_chat(struct im_connection *ic, GSList * list
char *prefix = NULL, *text = NULL;
status = l->data;
+ if (status->user == NULL || status->text == NULL || last_id == status->id)
+ continue;
+
+ last_id = status->id;
strip_html(status->text);
if (mode_one)
@@ -679,15 +706,166 @@ static void twitter_private_message_chat(struct im_connection *ic, GSList * list
mode_one ? from : status->user->screen_name,
text ? text : status->text, 0, status->created_at);
- // Update the home_timeline_id to hold the highest id, so that by the next request
+ // Update the timeline_id to hold the highest id, so that by the next request
// we won't pick up the updates already in the list.
- td->home_timeline_id = MAX(td->home_timeline_id, status->id);
+ td->timeline_id = MAX(td->timeline_id, status->id);
g_free(text);
g_free(prefix);
}
}
+static void twitter_http_get_home_timeline(struct http_request *req);
+static void twitter_http_get_mentions(struct http_request *req);
+
+/**
+ * Get the timeline with optionally mentions
+ */
+void twitter_get_timeline(struct im_connection *ic, gint64 next_cursor)
+{
+ struct twitter_data *td = ic->proto_data;
+ gboolean include_mentions = set_getbool(&ic->acc->set, "fetch_mentions");
+
+ if (td->flags & TWITTER_DOING_TIMELINE) {
+ return;
+ }
+
+ td->flags |= TWITTER_DOING_TIMELINE;
+
+ twitter_get_home_timeline(ic, next_cursor);
+
+ if (include_mentions) {
+ twitter_get_mentions(ic, next_cursor);
+ }
+}
+
+/**
+ * Call this one after receiving timeline/mentions. Show to user once we have
+ * both.
+ */
+void twitter_flush_timeline(struct im_connection *ic)
+{
+ struct twitter_data *td = ic->proto_data;
+ gboolean include_mentions = set_getbool(&ic->acc->set, "fetch_mentions");
+ gboolean show_old_mentions = set_getbool(&ic->acc->set, "show_old_mentions");
+ struct twitter_xml_list *home_timeline = td->home_timeline_obj;
+ struct twitter_xml_list *mentions = td->mentions_obj;
+ GSList *output = NULL;
+ GSList *l;
+
+ if (!(td->flags & TWITTER_GOT_TIMELINE)) {
+ return;
+ }
+
+ if (include_mentions && !(td->flags & TWITTER_GOT_MENTIONS)) {
+ return;
+ }
+
+ if (home_timeline && home_timeline->list) {
+ for (l = home_timeline->list; l; l = g_slist_next(l)) {
+ output = g_slist_insert_sorted(output, l->data, twitter_compare_elements);
+ }
+ }
+
+ if (include_mentions && mentions && mentions->list) {
+ for (l = mentions->list; l; l = g_slist_next(l)) {
+ if (!show_old_mentions && output && twitter_compare_elements(l->data, output->data) < 0) {
+ continue;
+ }
+
+ output = g_slist_insert_sorted(output, l->data, twitter_compare_elements);
+ }
+ }
+
+ // See if the user wants to see the messages in a groupchat window or as private messages.
+ if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0)
+ twitter_groupchat(ic, output);
+ else
+ twitter_private_message_chat(ic, output);
+
+ g_slist_free(output);
+
+ if (home_timeline && home_timeline->list) {
+ txl_free(home_timeline);
+ }
+
+ if (mentions && mentions->list) {
+ txl_free(mentions);
+ }
+
+ td->flags &= ~(TWITTER_DOING_TIMELINE | TWITTER_GOT_TIMELINE | TWITTER_GOT_MENTIONS);
+}
+
+/**
+ * Get the timeline.
+ */
+void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
+{
+ struct twitter_data *td = ic->proto_data;
+
+ td->home_timeline_obj = NULL;
+ td->flags &= ~TWITTER_GOT_TIMELINE;
+
+ char *args[6];
+ args[0] = "cursor";
+ args[1] = g_strdup_printf("%lld", (long long) next_cursor);
+ args[2] = "include_entities";
+ args[3] = "true";
+ if (td->timeline_id) {
+ args[4] = "since_id";
+ args[5] = g_strdup_printf("%llu", (long long unsigned int) td->timeline_id);
+ }
+
+ if (twitter_http(ic, TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, args,
+ td->timeline_id ? 6 : 4) == NULL) {
+ if (++td->http_fails >= 5)
+ imcb_error(ic, "Could not retrieve %s: %s",
+ TWITTER_HOME_TIMELINE_URL, "connection failed");
+ td->flags |= TWITTER_GOT_TIMELINE;
+ twitter_flush_timeline(ic);
+ }
+
+ g_free(args[1]);
+ if (td->timeline_id) {
+ g_free(args[5]);
+ }
+}
+
+/**
+ * Get mentions.
+ */
+void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)
+{
+ struct twitter_data *td = ic->proto_data;
+
+ td->mentions_obj = NULL;
+ td->flags &= ~TWITTER_GOT_MENTIONS;
+
+ char *args[6];
+ args[0] = "cursor";
+ args[1] = g_strdup_printf("%lld", (long long) next_cursor);
+ args[2] = "include_entities";
+ args[3] = "true";
+ if (td->timeline_id) {
+ args[4] = "since_id";
+ args[5] = g_strdup_printf("%llu", (long long unsigned int) td->timeline_id);
+ }
+
+ if (twitter_http(ic, TWITTER_MENTIONS_URL, twitter_http_get_mentions, ic, 0, args,
+ td->timeline_id ? 6 : 4) == NULL) {
+ if (++td->http_fails >= 5)
+ imcb_error(ic, "Could not retrieve %s: %s",
+ TWITTER_MENTIONS_URL, "connection failed");
+ td->flags |= TWITTER_GOT_MENTIONS;
+ twitter_flush_timeline(ic);
+ }
+
+ g_free(args[1]);
+ if (td->timeline_id) {
+ g_free(args[5]);
+ }
+}
+
/**
* Callback for getting the home timeline.
*/
@@ -712,14 +890,66 @@ static void twitter_http_get_home_timeline(struct http_request *req)
} else if (req->status_code == 401) {
imcb_error(ic, "Authentication failure");
imc_logout(ic, FALSE);
- return;
+ goto end;
} else {
// It didn't go well, output the error and return.
if (++td->http_fails >= 5)
imcb_error(ic, "Could not retrieve %s: %s",
TWITTER_HOME_TIMELINE_URL, twitter_parse_error(req));
+ goto end;
+ }
+
+ txl = g_new0(struct twitter_xml_list, 1);
+ txl->list = NULL;
+
+ // Parse the data.
+ parser = xt_new(NULL, txl);
+ xt_feed(parser, req->reply_body, req->body_size);
+ // The root <statuses> node should hold the list of statuses <status>
+ twitter_xt_get_status_list(ic, parser->root, txl);
+ xt_free(parser);
+
+ td->home_timeline_obj = txl;
+
+ end:
+ td->flags |= TWITTER_GOT_TIMELINE;
+
+ twitter_flush_timeline(ic);
+}
+
+/**
+ * Callback for getting mentions.
+ */
+static void twitter_http_get_mentions(struct http_request *req)
+{
+ struct im_connection *ic = req->data;
+ struct twitter_data *td;
+ struct xt_parser *parser;
+ struct twitter_xml_list *txl;
+
+ // Check if the connection is still active.
+ if (!g_slist_find(twitter_connections, ic))
return;
+
+ td = ic->proto_data;
+
+ // Check if the HTTP request went well.
+ if (req->status_code == 200) {
+ td->http_fails = 0;
+ if (!(ic->flags & OPT_LOGGED_IN))
+ imcb_connected(ic);
+ } else if (req->status_code == 401) {
+ imcb_error(ic, "Authentication failure");
+ imc_logout(ic, FALSE);
+ goto end;
+ } else {
+ // It didn't go well, output the error and return.
+ if (++td->http_fails >= 5)
+ imcb_error(ic, "Could not retrieve %s: %s",
+ TWITTER_MENTIONS_URL, twitter_parse_error(req));
+
+ goto end;
}
txl = g_new0(struct twitter_xml_list, 1);
@@ -732,15 +962,12 @@ static void twitter_http_get_home_timeline(struct http_request *req)
twitter_xt_get_status_list(ic, parser->root, txl);
xt_free(parser);
- // See if the user wants to see the messages in a groupchat window or as private messages.
- if (txl->list == NULL);
- else if (g_strcasecmp(set_getstr(&ic->acc->set, "mode"), "chat") == 0)
- twitter_groupchat(ic, txl->list);
- else
- twitter_private_message_chat(ic, txl->list);
+ td->mentions_obj = txl;
- // Free the structure.
- txl_free(txl);
+ end:
+ td->flags |= TWITTER_GOT_MENTIONS;
+
+ twitter_flush_timeline(ic);
}
/**
diff --git a/protocols/twitter/twitter_lib.h b/protocols/twitter/twitter_lib.h
index c33b2dfc..b06f5055 100644
--- a/protocols/twitter/twitter_lib.h
+++ b/protocols/twitter/twitter_lib.h
@@ -75,8 +75,10 @@
#define TWITTER_BLOCKS_CREATE_URL "/blocks/create/"
#define TWITTER_BLOCKS_DESTROY_URL "/blocks/destroy/"
+void twitter_get_timeline(struct im_connection *ic, gint64 next_cursor);
void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor);
void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor);
+void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor);
void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor);
void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to);
diff --git a/protocols/yahoo/libyahoo2.c b/protocols/yahoo/libyahoo2.c
index 07689809..ca2a161e 100644
--- a/protocols/yahoo/libyahoo2.c
+++ b/protocols/yahoo/libyahoo2.c
@@ -1808,7 +1808,6 @@ static void yahoo_https_auth_token_init(struct yahoo_https_auth_data *had)
{
struct yahoo_input_data *yid = had->yid;
struct yahoo_data *yd = yid->yd;
- struct http_request *req;
char *login, *passwd, *chal;
char *url;
@@ -1822,7 +1821,7 @@ static void yahoo_https_auth_token_init(struct yahoo_https_auth_data *had)
url = g_strdup_printf("https://login.yahoo.com/config/pwtoken_get?src=ymsgr&ts=%d&login=%s&passwd=%s&chal=%s",
(int) time(NULL), login, passwd, chal);
- req = http_dorequest_url(url, yahoo_https_auth_token_finish, had);
+ http_dorequest_url(url, yahoo_https_auth_token_finish, had);
g_free(url);
g_free(chal);
@@ -1869,13 +1868,12 @@ fail:
static void yahoo_https_auth_init(struct yahoo_https_auth_data *had)
{
- struct http_request *req;
char *url;
url = g_strdup_printf("https://login.yahoo.com/config/pwtoken_login?src=ymsgr&ts=%d&token=%s",
(int) time(NULL), had->token);
- req = http_dorequest_url(url, yahoo_https_auth_finish, had);
+ http_dorequest_url(url, yahoo_https_auth_finish, had);
g_free(url);
}
@@ -1989,8 +1987,6 @@ static void yahoo_process_auth_resp(struct yahoo_input_data *yid,
struct yahoo_packet *pkt)
{
struct yahoo_data *yd = yid->yd;
- char *login_id;
- char *handle;
char *url = NULL;
int login_status = -1;
@@ -1999,9 +1995,9 @@ static void yahoo_process_auth_resp(struct yahoo_input_data *yid,
for (l = pkt->hash; l; l = l->next) {
struct yahoo_pair *pair = l->data;
if (pair->key == 0)
- login_id = pair->value;
+ ; /* login_id */
else if (pair->key == 1)
- handle = pair->value;
+ ; /* handle */
else if (pair->key == 20)
url = pair->value;
else if (pair->key == 66)
@@ -2088,9 +2084,7 @@ static void yahoo_process_contact(struct yahoo_input_data *yid,
char *who = NULL;
char *msg = NULL;
char *name = NULL;
- long tm = 0L;
int state = YAHOO_STATUS_AVAILABLE;
- int online = 0;
int away = 0;
int idle = 0;
int mobile = 0;
@@ -2110,9 +2104,9 @@ static void yahoo_process_contact(struct yahoo_input_data *yid,
else if (pair->key == 10)
state = strtol(pair->value, NULL, 10);
else if (pair->key == 15)
- tm = strtol(pair->value, NULL, 10);
+ ; /* tm */
else if (pair->key == 13)
- online = strtol(pair->value, NULL, 10);
+ ; /* online */
else if (pair->key == 47)
away = strtol(pair->value, NULL, 10);
else if (pair->key == 137)
@@ -2139,7 +2133,6 @@ static void yahoo_process_buddyadd(struct yahoo_input_data *yid,
char *who = NULL;
char *where = NULL;
int status = 0;
- char *me = NULL;
struct yahoo_buddy *bud = NULL;
@@ -2147,7 +2140,7 @@ static void yahoo_process_buddyadd(struct yahoo_input_data *yid,
for (l = pkt->hash; l; l = l->next) {
struct yahoo_pair *pair = l->data;
if (pair->key == 1)
- me = pair->value;
+ ; /* Me... don't care */
if (pair->key == 7)
who = pair->value;
if (pair->key == 65)
@@ -2203,8 +2196,6 @@ static void yahoo_process_buddydel(struct yahoo_input_data *yid,
struct yahoo_data *yd = yid->yd;
char *who = NULL;
char *where = NULL;
- int unk_66 = 0;
- char *me = NULL;
struct yahoo_buddy *bud;
YList *buddy;
@@ -2213,13 +2204,13 @@ static void yahoo_process_buddydel(struct yahoo_input_data *yid,
for (l = pkt->hash; l; l = l->next) {
struct yahoo_pair *pair = l->data;
if (pair->key == 1)
- me = pair->value;
+ ; /* Me... don't care */
else if (pair->key == 7)
who = pair->value;
else if (pair->key == 65)
where = pair->value;
else if (pair->key == 66)
- unk_66 = strtol(pair->value, NULL, 10);
+ ; /* unk_66 */
else
DEBUG_MSG(("unknown key: %d = %s", pair->key,
pair->value));
@@ -2255,22 +2246,17 @@ static void yahoo_process_buddydel(struct yahoo_input_data *yid,
static void yahoo_process_ignore(struct yahoo_input_data *yid,
struct yahoo_packet *pkt)
{
- char *who = NULL;
- int status = 0;
- char *me = NULL;
- int un_ignore = 0;
-
YList *l;
for (l = pkt->hash; l; l = l->next) {
struct yahoo_pair *pair = l->data;
if (pair->key == 0)
- who = pair->value;
+ ; /* who */
if (pair->key == 1)
- me = pair->value;
+ ; /* Me... don't care */
if (pair->key == 13) /* 1 == ignore, 2 == unignore */
- un_ignore = strtol(pair->value, NULL, 10);
+ ;
if (pair->key == 66)
- status = strtol(pair->value, NULL, 10);
+ ; /* status */
}
/*
@@ -2292,7 +2278,6 @@ static void yahoo_process_voicechat(struct yahoo_input_data *yid,
char *who = NULL;
char *me = NULL;
char *room = NULL;
- char *voice_room = NULL;
YList *l;
for (l = pkt->hash; l; l = l->next) {
@@ -2302,7 +2287,7 @@ static void yahoo_process_voicechat(struct yahoo_input_data *yid,
if (pair->key == 5)
me = pair->value;
if (pair->key == 13)
- voice_room = pair->value;
+ ; /* voice room */
if (pair->key == 57)
room = pair->value;
}
@@ -2437,7 +2422,6 @@ static YList *webcam_queue = NULL;
static void yahoo_process_webcam_key(struct yahoo_input_data *yid,
struct yahoo_packet *pkt)
{
- char *me = NULL;
char *key = NULL;
char *who = NULL;
@@ -2446,7 +2430,7 @@ static void yahoo_process_webcam_key(struct yahoo_input_data *yid,
for (l = pkt->hash; l; l = l->next) {
struct yahoo_pair *pair = l->data;
if (pair->key == 5)
- me = pair->value;
+ ; /* me */
if (pair->key == 61)
key = pair->value;
}
@@ -3368,7 +3352,6 @@ static void yahoo_webcam_connect(struct yahoo_input_data *y)
{
struct yahoo_webcam *wcm = y->wcm;
struct yahoo_input_data *yid;
- struct yahoo_server_settings *yss;
if (!wcm || !wcm->server || !wcm->key)
return;
@@ -3381,8 +3364,6 @@ static void yahoo_webcam_connect(struct yahoo_input_data *y)
yid->wcm = y->wcm;
y->wcm = NULL;
- yss = y->yd->server_settings;
-
yid->wcd = y_new0(struct yahoo_webcam_data, 1);
LOG(("Connecting to: %s:%d", wcm->server, wcm->port));
@@ -4974,8 +4955,6 @@ static void yahoo_process_filetransferaccept(struct yahoo_input_data *yid,
{
YList *l;
struct send_file_data *sfd;
- char *who = NULL;
- char *filename = NULL;
char *id = NULL;
char *token = NULL;
@@ -4983,7 +4962,7 @@ static void yahoo_process_filetransferaccept(struct yahoo_input_data *yid,
struct yahoo_pair *pair = l->data;
switch (pair->key) {
case 4:
- who = pair->value;
+ /* who */
break;
case 5:
/* Me... don't care */
@@ -4997,7 +4976,7 @@ static void yahoo_process_filetransferaccept(struct yahoo_input_data *yid,
token = pair->value;
break;
case 27:
- filename = pair->value;
+ /* filename */
break;
}
}
@@ -5022,8 +5001,6 @@ static void yahoo_process_filetransferinfo(struct yahoo_input_data *yid,
struct yahoo_packet *pkt)
{
YList *l;
- char *who = NULL;
- char *filename = NULL;
char *id = NULL;
char *token = NULL;
char *ip_addr = NULL;
@@ -5035,7 +5012,7 @@ static void yahoo_process_filetransferinfo(struct yahoo_input_data *yid,
switch (pair->key) {
case 1:
case 4:
- who = pair->value;
+ /* who */
break;
case 5:
/* Me... don't care */
@@ -5052,7 +5029,7 @@ static void yahoo_process_filetransferinfo(struct yahoo_input_data *yid,
token = pair->value;
break;
case 27:
- filename = pair->value;
+ /* filename */
break;
}
}
diff --git a/query.c b/query.c
index 9429f2d2..aa03b8b5 100644
--- a/query.c
+++ b/query.c
@@ -149,7 +149,7 @@ void query_answer( irc_t *irc, query_t *q, int ans )
if( q->ic )
imcb_log( q->ic, "Accepted: %s", q->question );
else
- irc_usermsg( irc, "Accepted: %s", q->question );
+ irc_rootmsg( irc, "Accepted: %s", q->question );
if( q->yes )
q->yes( q->data );
}
@@ -158,7 +158,7 @@ void query_answer( irc_t *irc, query_t *q, int ans )
if( q->ic )
imcb_log( q->ic, "Rejected: %s", q->question );
else
- irc_usermsg( irc, "Rejected: %s", q->question );
+ irc_rootmsg( irc, "Rejected: %s", q->question );
if( q->no )
q->no( q->data );
}
@@ -178,7 +178,7 @@ static void query_display( irc_t *irc, query_t *q )
}
else
{
- irc_usermsg( irc, "New request: %s\nYou can use the \2yes\2/\2no\2 commands to accept/reject this request.", q->question );
+ irc_rootmsg( irc, "New request: %s\nYou can use the \2yes\2/\2no\2 commands to accept/reject this request.", q->question );
}
}
diff --git a/root_commands.c b/root_commands.c
index a74d4580..734cb0e8 100644
--- a/root_commands.c
+++ b/root_commands.c
@@ -41,7 +41,7 @@ void root_command_string( irc_t *irc, char *command )
for( blaat = 0; blaat <= x; blaat ++ ) \
if( cmd[blaat] == NULL ) \
{ \
- irc_usermsg( irc, "Not enough parameters given (need %d).", x ); \
+ irc_rootmsg( irc, "Not enough parameters given (need %d).", x ); \
return y; \
} \
} while( 0 )
@@ -68,7 +68,7 @@ void root_command( irc_t *irc, char *cmd[] )
return;
}
- irc_usermsg( irc, "Unknown command: %s. Please use \x02help commands\x02 to get a list of available commands.", cmd[0] );
+ irc_rootmsg( irc, "Unknown command: %s. Please use \x02help commands\x02 to get a list of available commands.", cmd[0] );
}
static void cmd_help( irc_t *irc, char **cmd )
@@ -89,12 +89,12 @@ static void cmd_help( irc_t *irc, char **cmd )
if( s )
{
- irc_usermsg( irc, "%s", s );
+ irc_rootmsg( irc, "%s", s );
g_free( s );
}
else
{
- irc_usermsg( irc, "Error opening helpfile." );
+ irc_rootmsg( irc, "Error opening helpfile." );
}
}
@@ -109,7 +109,7 @@ static void cmd_identify( irc_t *irc, char **cmd )
if( irc->status & USTATUS_IDENTIFIED )
{
- irc_usermsg( irc, "You're already logged in." );
+ irc_rootmsg( irc, "You're already logged in." );
return;
}
@@ -127,7 +127,7 @@ static void cmd_identify( irc_t *irc, char **cmd )
}
else if( irc->b->accounts != NULL )
{
- irc_usermsg( irc,
+ irc_rootmsg( irc,
"You're trying to identify yourself, but already have "
"at least one IM account set up. "
"Use \x02identify -noload\x02 or \x02identify -force\x02 "
@@ -137,7 +137,7 @@ static void cmd_identify( irc_t *irc, char **cmd )
if( password == NULL )
{
- irc_usermsg( irc, "About to identify, use /OPER to enter the password" );
+ irc_rootmsg( irc, "About to identify, use /OPER to enter the password" );
irc->status |= OPER_HACK_IDENTIFY;
return;
}
@@ -149,13 +149,13 @@ static void cmd_identify( irc_t *irc, char **cmd )
switch (status) {
case STORAGE_INVALID_PASSWORD:
- irc_usermsg( irc, "Incorrect password" );
+ irc_rootmsg( irc, "Incorrect password" );
break;
case STORAGE_NO_SUCH_USER:
- irc_usermsg( irc, "The nick is (probably) not registered" );
+ irc_rootmsg( irc, "The nick is (probably) not registered" );
break;
case STORAGE_OK:
- irc_usermsg( irc, "Password accepted%s",
+ irc_rootmsg( irc, "Password accepted%s",
load ? ", settings and accounts loaded" : "" );
irc_setpass( irc, password );
irc->status |= USTATUS_IDENTIFIED;
@@ -191,7 +191,7 @@ static void cmd_identify( irc_t *irc, char **cmd )
break;
case STORAGE_OTHER_ERROR:
default:
- irc_usermsg( irc, "Unknown error while loading configuration" );
+ irc_rootmsg( irc, "Unknown error while loading configuration" );
break;
}
}
@@ -214,24 +214,24 @@ static void cmd_register( irc_t *irc, char **cmd )
if( global.conf->authmode == AUTHMODE_REGISTERED )
{
- irc_usermsg( irc, "This server does not allow registering new accounts" );
+ irc_rootmsg( irc, "This server does not allow registering new accounts" );
return;
}
if( cmd[1] == NULL )
{
- irc_usermsg( irc, "About to register, use /OPER to enter the password" );
+ irc_rootmsg( irc, "About to register, use /OPER to enter the password" );
irc->status |= OPER_HACK_REGISTER;
return;
}
switch( storage_save( irc, cmd[1], FALSE ) ) {
case STORAGE_ALREADY_EXISTS:
- irc_usermsg( irc, "Nick is already registered" );
+ irc_rootmsg( irc, "Nick is already registered" );
break;
case STORAGE_OK:
- irc_usermsg( irc, "Account successfully created" );
+ irc_rootmsg( irc, "Account successfully created" );
irc_setpass( irc, cmd[1] );
irc->status |= USTATUS_IDENTIFIED;
irc_umode_set( irc, "+R", 1 );
@@ -244,7 +244,7 @@ static void cmd_register( irc_t *irc, char **cmd )
break;
default:
- irc_usermsg( irc, "Error registering" );
+ irc_rootmsg( irc, "Error registering" );
break;
}
}
@@ -256,19 +256,19 @@ static void cmd_drop( irc_t *irc, char **cmd )
status = storage_remove (irc->user->nick, cmd[1]);
switch (status) {
case STORAGE_NO_SUCH_USER:
- irc_usermsg( irc, "That account does not exist" );
+ irc_rootmsg( irc, "That account does not exist" );
break;
case STORAGE_INVALID_PASSWORD:
- irc_usermsg( irc, "Password invalid" );
+ irc_rootmsg( irc, "Password invalid" );
break;
case STORAGE_OK:
irc_setpass( irc, NULL );
irc->status &= ~USTATUS_IDENTIFIED;
irc_umode_set( irc, "-R", 1 );
- irc_usermsg( irc, "Account `%s' removed", irc->user->nick );
+ irc_rootmsg( irc, "Account `%s' removed", irc->user->nick );
break;
default:
- irc_usermsg( irc, "Error: `%d'", status );
+ irc_rootmsg( irc, "Error: `%d'", status );
break;
}
}
@@ -276,11 +276,11 @@ static void cmd_drop( irc_t *irc, char **cmd )
static void cmd_save( irc_t *irc, char **cmd )
{
if( ( irc->status & USTATUS_IDENTIFIED ) == 0 )
- irc_usermsg( irc, "Please create an account first" );
+ irc_rootmsg( irc, "Please create an account first" );
else if( storage_save( irc, NULL, TRUE ) == STORAGE_OK )
- irc_usermsg( irc, "Configuration saved" );
+ irc_rootmsg( irc, "Configuration saved" );
else
- irc_usermsg( irc, "Configuration could not be saved!" );
+ irc_rootmsg( irc, "Configuration could not be saved!" );
}
static void cmd_showset( irc_t *irc, set_t **head, char *key )
@@ -289,18 +289,18 @@ static void cmd_showset( irc_t *irc, set_t **head, char *key )
char *val;
if( ( val = set_getstr( head, key ) ) )
- irc_usermsg( irc, "%s = `%s'", key, val );
+ irc_rootmsg( irc, "%s = `%s'", key, val );
else if( !( set = set_find( head, key ) ) )
{
- irc_usermsg( irc, "Setting `%s' does not exist.", key );
+ irc_rootmsg( irc, "Setting `%s' does not exist.", key );
if( *head == irc->b->set )
- irc_usermsg( irc, "It might be an account or channel setting. "
+ irc_rootmsg( irc, "It might be an account or channel setting. "
"See \x02help account set\x02 and \x02help channel set\x02." );
}
else if( set->flags & SET_PASSWORD )
- irc_usermsg( irc, "%s = `********' (hidden)", key );
+ irc_rootmsg( irc, "%s = `********' (hidden)", key );
else
- irc_usermsg( irc, "%s is empty", key );
+ irc_rootmsg( irc, "%s is empty", key );
}
typedef set_t** (*cmd_set_findhead)( irc_t*, char* );
@@ -343,9 +343,9 @@ static int cmd_set_real( irc_t *irc, char **cmd, set_t **head, cmd_set_checkflag
Showing these msgs instead gives slightly clearer
feedback. */
if( st )
- irc_usermsg( irc, "Setting changed successfully" );
+ irc_rootmsg( irc, "Setting changed successfully" );
else
- irc_usermsg( irc, "Failed to change setting" );
+ irc_rootmsg( irc, "Failed to change setting" );
}
else
{
@@ -361,7 +361,7 @@ static int cmd_set_real( irc_t *irc, char **cmd, set_t **head, cmd_set_checkflag
set_t *s = *head;
while( s )
{
- if( !( s->flags & SET_HIDDEN ) )
+ if( set_isvisible( s ) )
cmd_showset( irc, &s, s->key );
s = s->next;
}
@@ -376,12 +376,12 @@ static int cmd_account_set_checkflags( irc_t *irc, set_t *s )
if( a->ic && s && s->flags & ACC_SET_OFFLINE_ONLY )
{
- irc_usermsg( irc, "This setting can only be changed when the account is %s-line", "off" );
+ irc_rootmsg( irc, "This setting can only be changed when the account is %s-line", "off" );
return 0;
}
else if( !a->ic && s && s->flags & ACC_SET_ONLINE_ONLY )
{
- irc_usermsg( irc, "This setting can only be changed when the account is %s-line", "on" );
+ irc_rootmsg( irc, "This setting can only be changed when the account is %s-line", "on" );
return 0;
}
@@ -395,7 +395,7 @@ static void cmd_account( irc_t *irc, char **cmd )
if( global.conf->authmode == AUTHMODE_REGISTERED && !( irc->status & USTATUS_IDENTIFIED ) )
{
- irc_usermsg( irc, "This server only accepts registered users" );
+ irc_rootmsg( irc, "This server only accepts registered users" );
return;
}
@@ -412,8 +412,8 @@ static void cmd_account( irc_t *irc, char **cmd )
for( a = irc->b->accounts; a; a = a->next )
if( strcmp( a->pass, PASSWORD_PENDING ) == 0 )
{
- irc_usermsg( irc, "Enter password for account %s(%s) "
- "first (use /OPER)", a->prpl->name, a->user );
+ irc_rootmsg( irc, "Enter password for account %s "
+ "first (use /OPER)", a->tag );
return;
}
@@ -424,25 +424,26 @@ static void cmd_account( irc_t *irc, char **cmd )
if( prpl == NULL )
{
- irc_usermsg( irc, "Unknown protocol" );
+ irc_rootmsg( irc, "Unknown protocol" );
return;
}
for( a = irc->b->accounts; a; a = a->next )
if( a->prpl == prpl && prpl->handle_cmp( a->user, cmd[3] ) == 0 )
- irc_usermsg( irc, "Warning: You already have an account with "
+ irc_rootmsg( irc, "Warning: You already have an account with "
"protocol `%s' and username `%s'. Are you accidentally "
"trying to add it twice?", prpl->name, cmd[3] );
a = account_add( irc->b, prpl, cmd[3], cmd[4] ? cmd[4] : PASSWORD_PENDING );
if( cmd[5] )
{
- irc_usermsg( irc, "Warning: Passing a servername/other flags to `account add' "
+ irc_rootmsg( irc, "Warning: Passing a servername/other flags to `account add' "
"is now deprecated. Use `account set' instead." );
set_setstr( &a->set, "server", cmd[5] );
}
- irc_usermsg( irc, "Account successfully added%s", cmd[4] ? "" :
+ irc_rootmsg( irc, "Account successfully added with tag %s%s",
+ a->tag, cmd[4] ? "" :
", now use /OPER to enter the password" );
return;
@@ -452,7 +453,7 @@ static void cmd_account( irc_t *irc, char **cmd )
int i = 0;
if( strchr( irc->umode, 'b' ) )
- irc_usermsg( irc, "Account list:" );
+ irc_rootmsg( irc, "Account list:" );
for( a = irc->b->accounts; a; a = a->next )
{
@@ -467,11 +468,11 @@ static void cmd_account( irc_t *irc, char **cmd )
else
con = "";
- irc_usermsg( irc, "%2d (%s): %s, %s%s", i, a->tag, a->prpl->name, a->user, con );
+ irc_rootmsg( irc, "%2d (%s): %s, %s%s", i, a->tag, a->prpl->name, a->user, con );
i ++;
}
- irc_usermsg( irc, "End of account list" );
+ irc_rootmsg( irc, "End of account list" );
return;
}
@@ -483,28 +484,28 @@ static void cmd_account( irc_t *irc, char **cmd )
{
if ( irc->b->accounts )
{
- irc_usermsg( irc, "Trying to get all accounts connected..." );
+ irc_rootmsg( irc, "Trying to get all accounts connected..." );
for( a = irc->b->accounts; a; a = a->next )
if( !a->ic && a->auto_connect )
{
if( strcmp( a->pass, PASSWORD_PENDING ) == 0 )
- irc_usermsg( irc, "Enter password for account %s(%s) "
- "first (use /OPER)", a->prpl->name, a->user );
+ irc_rootmsg( irc, "Enter password for account %s "
+ "first (use /OPER)", a->tag );
else
account_on( irc->b, a );
}
}
else
{
- irc_usermsg( irc, "No accounts known. Use `account add' to add one." );
+ irc_rootmsg( irc, "No accounts known. Use `account add' to add one." );
}
return;
}
else if( len >= 2 && g_strncasecmp( cmd[1], "off", len ) == 0 )
{
- irc_usermsg( irc, "Deactivating all active (re)connections..." );
+ irc_rootmsg( irc, "Deactivating all active (re)connections..." );
for( a = irc->b->accounts; a; a = a->next )
{
@@ -529,7 +530,7 @@ static void cmd_account( irc_t *irc, char **cmd )
g_strcasecmp( cmd[1], "del" ) == 0 ||
( a = account_get( irc->b, cmd[1] ) ) == NULL )
{
- irc_usermsg( irc, "Could not find account `%s'. Note that the syntax "
+ irc_rootmsg( irc, "Could not find account `%s'. Note that the syntax "
"of the account command changed, see \x02help account\x02.", cmd[1] );
return;
@@ -539,21 +540,21 @@ static void cmd_account( irc_t *irc, char **cmd )
{
if( a->ic )
{
- irc_usermsg( irc, "Account is still logged in, can't delete" );
+ irc_rootmsg( irc, "Account is still logged in, can't delete" );
}
else
{
account_del( irc->b, a );
- irc_usermsg( irc, "Account deleted" );
+ irc_rootmsg( irc, "Account deleted" );
}
}
else if( len >= 2 && g_strncasecmp( cmd[2], "on", len ) == 0 )
{
if( a->ic )
- irc_usermsg( irc, "Account already online" );
+ irc_rootmsg( irc, "Account already online" );
else if( strcmp( a->pass, PASSWORD_PENDING ) == 0 )
- irc_usermsg( irc, "Enter password for account %s(%s) "
- "first (use /OPER)", a->prpl->name, a->user );
+ irc_rootmsg( irc, "Enter password for account %s "
+ "first (use /OPER)", a->tag );
else
account_on( irc->b, a );
}
@@ -566,11 +567,11 @@ static void cmd_account( irc_t *irc, char **cmd )
else if( a->reconnect )
{
cancel_auto_reconnect( a );
- irc_usermsg( irc, "Reconnect cancelled" );
+ irc_rootmsg( irc, "Reconnect cancelled" );
}
else
{
- irc_usermsg( irc, "Account already offline" );
+ irc_rootmsg( irc, "Account already offline" );
}
}
else if( len >= 1 && g_strncasecmp( cmd[2], "set", len ) == 0 )
@@ -579,7 +580,7 @@ static void cmd_account( irc_t *irc, char **cmd )
}
else
{
- irc_usermsg( irc, "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "account", cmd[2] );
+ irc_rootmsg( irc, "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "account", cmd[2] );
}
}
@@ -596,19 +597,19 @@ static void cmd_channel( irc_t *irc, char **cmd )
int i = 0;
if( strchr( irc->umode, 'b' ) )
- irc_usermsg( irc, "Channel list:" );
+ irc_rootmsg( irc, "Channel list:" );
for( l = irc->channels; l; l = l->next )
{
irc_channel_t *ic = l->data;
- irc_usermsg( irc, "%2d. %s, %s channel%s", i, ic->name,
+ irc_rootmsg( irc, "%2d. %s, %s channel%s", i, ic->name,
set_getstr( &ic->set, "type" ),
ic->flags & IRC_CHANNEL_JOINED ? " (joined)" : "" );
i ++;
}
- irc_usermsg( irc, "End of channel list" );
+ irc_rootmsg( irc, "End of channel list" );
return;
}
@@ -622,7 +623,7 @@ static void cmd_channel( irc_t *irc, char **cmd )
g_strncasecmp( cmd[1], "set", len ) == 0 )
cmd_set_real( irc, cmd + 1, &ic->set, NULL );
else
- irc_usermsg( irc, "Could not find channel `%s'", cmd[1] );
+ irc_rootmsg( irc, "Could not find channel `%s'", cmd[1] );
return;
}
@@ -639,17 +640,17 @@ static void cmd_channel( irc_t *irc, char **cmd )
if( !( ic->flags & IRC_CHANNEL_JOINED ) &&
ic != ic->irc->default_channel )
{
- irc_usermsg( irc, "Channel %s deleted.", ic->name );
+ irc_rootmsg( irc, "Channel %s deleted.", ic->name );
irc_channel_free( ic );
}
else
- irc_usermsg( irc, "Couldn't remove channel (main channel %s or "
+ irc_rootmsg( irc, "Couldn't remove channel (main channel %s or "
"channels you're still in cannot be deleted).",
irc->default_channel->name );
}
else
{
- irc_usermsg( irc, "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "channel", cmd[1] );
+ irc_rootmsg( irc, "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "channel", cmd[1] );
}
}
@@ -667,12 +668,12 @@ static void cmd_add( irc_t *irc, char **cmd )
if( !( a = account_get( irc->b, cmd[1] ) ) )
{
- irc_usermsg( irc, "Invalid account" );
+ irc_rootmsg( irc, "Invalid account" );
return;
}
else if( !( a->ic && ( a->ic->flags & OPT_LOGGED_IN ) ) )
{
- irc_usermsg( irc, "That account is not on-line" );
+ irc_rootmsg( irc, "That account is not on-line" );
return;
}
@@ -680,12 +681,12 @@ static void cmd_add( irc_t *irc, char **cmd )
{
if( !nick_ok( cmd[3] ) )
{
- irc_usermsg( irc, "The requested nick `%s' is invalid", cmd[3] );
+ irc_rootmsg( irc, "The requested nick `%s' is invalid", cmd[3] );
return;
}
else if( irc_user_by_name( irc, cmd[3] ) )
{
- irc_usermsg( irc, "The requested nick `%s' already exists", cmd[3] );
+ irc_rootmsg( irc, "The requested nick `%s' already exists", cmd[3] );
return;
}
else
@@ -703,10 +704,10 @@ static void cmd_add( irc_t *irc, char **cmd )
( s = set_getstr( &ic->set, "fill_by" ) ) &&
strcmp( s, "group" ) == 0 &&
( group = set_getstr( &ic->set, "group" ) ) )
- irc_usermsg( irc, "Adding `%s' to contact list (group %s)",
+ irc_rootmsg( irc, "Adding `%s' to contact list (group %s)",
cmd[2], group );
else
- irc_usermsg( irc, "Adding `%s' to contact list", cmd[2] );
+ irc_rootmsg( irc, "Adding `%s' to contact list", cmd[2] );
a->prpl->add_buddy( a->ic, cmd[2], group );
}
@@ -719,7 +720,7 @@ static void cmd_add( irc_t *irc, char **cmd )
be called once the IM server confirms. */
if( ( bu = bee_user_new( irc->b, a->ic, cmd[2], BEE_USER_LOCAL ) ) &&
( iu = bu->ui_data ) )
- irc_usermsg( irc, "Temporarily assigned nickname `%s' "
+ irc_rootmsg( irc, "Temporarily assigned nickname `%s' "
"to contact `%s'", iu->nick, cmd[2] );
}
@@ -733,7 +734,7 @@ static void cmd_remove( irc_t *irc, char **cmd )
if( !( iu = irc_user_by_name( irc, cmd[1] ) ) || !( bu = iu->bu ) )
{
- irc_usermsg( irc, "Buddy `%s' not found", cmd[1] );
+ irc_rootmsg( irc, "Buddy `%s' not found", cmd[1] );
return;
}
s = g_strdup( bu->handle );
@@ -743,7 +744,7 @@ static void cmd_remove( irc_t *irc, char **cmd )
if( g_slist_find( irc->users, iu ) )
bee_user_free( irc->b, bu );
- irc_usermsg( irc, "Buddy `%s' (nick %s) removed from contact list", s, cmd[1] );
+ irc_rootmsg( irc, "Buddy `%s' (nick %s) removed from contact list", s, cmd[1] );
g_free( s );
return;
@@ -759,7 +760,7 @@ static void cmd_info( irc_t *irc, char **cmd )
irc_user_t *iu = irc_user_by_name( irc, cmd[1] );
if( !iu || !iu->bu )
{
- irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] );
+ irc_rootmsg( irc, "Nick `%s' does not exist", cmd[1] );
return;
}
ic = iu->bu->ic;
@@ -767,18 +768,18 @@ static void cmd_info( irc_t *irc, char **cmd )
}
else if( !( a = account_get( irc->b, cmd[1] ) ) )
{
- irc_usermsg( irc, "Invalid account" );
+ irc_rootmsg( irc, "Invalid account" );
return;
}
else if( !( ( ic = a->ic ) && ( a->ic->flags & OPT_LOGGED_IN ) ) )
{
- irc_usermsg( irc, "That account is not on-line" );
+ irc_rootmsg( irc, "That account is not on-line" );
return;
}
if( !ic->acc->prpl->get_info )
{
- irc_usermsg( irc, "Command `%s' not supported by this protocol", cmd[0] );
+ irc_rootmsg( irc, "Command `%s' not supported by this protocol", cmd[0] );
}
else
{
@@ -795,31 +796,31 @@ static void cmd_rename( irc_t *irc, char **cmd )
if( iu == NULL )
{
- irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] );
+ irc_rootmsg( irc, "Nick `%s' does not exist", cmd[1] );
}
else if( del )
{
if( iu->bu )
bee_irc_user_nick_reset( iu );
- irc_usermsg( irc, "Nickname reset to `%s'", iu->nick );
+ irc_rootmsg( irc, "Nickname reset to `%s'", iu->nick );
}
else if( iu == irc->user )
{
- irc_usermsg( irc, "Use /nick to change your own nickname" );
+ irc_rootmsg( irc, "Use /nick to change your own nickname" );
}
else if( !nick_ok( cmd[2] ) )
{
- irc_usermsg( irc, "Nick `%s' is invalid", cmd[2] );
+ irc_rootmsg( irc, "Nick `%s' is invalid", cmd[2] );
}
else if( ( old = irc_user_by_name( irc, cmd[2] ) ) && old != iu )
{
- irc_usermsg( irc, "Nick `%s' already exists", cmd[2] );
+ irc_rootmsg( irc, "Nick `%s' already exists", cmd[2] );
}
else
{
if( !irc_user_set_nick( iu, cmd[2] ) )
{
- irc_usermsg( irc, "Error while changing nick" );
+ irc_rootmsg( irc, "Error while changing nick" );
return;
}
@@ -835,7 +836,7 @@ static void cmd_rename( irc_t *irc, char **cmd )
nick_set( iu->bu, cmd[2] );
}
- irc_usermsg( irc, "Nick successfully changed" );
+ irc_rootmsg( irc, "Nick successfully changed" );
}
}
@@ -868,14 +869,14 @@ static void cmd_block( irc_t *irc, char **cmd )
else
format = "%-32.32s %-16.16s";
- irc_usermsg( irc, format, "Handle", "Nickname" );
+ irc_rootmsg( irc, format, "Handle", "Nickname" );
for( l = a->ic->deny; l; l = l->next )
{
bee_user_t *bu = bee_user_by_handle( irc->b, a->ic, l->data );
irc_user_t *iu = bu ? bu->ui_data : NULL;
- irc_usermsg( irc, format, l->data, iu ? iu->nick : "(none)" );
+ irc_rootmsg( irc, format, l->data, iu ? iu->nick : "(none)" );
}
- irc_usermsg( irc, "End of list." );
+ irc_rootmsg( irc, "End of list." );
return;
}
@@ -884,7 +885,7 @@ static void cmd_block( irc_t *irc, char **cmd )
irc_user_t *iu = irc_user_by_name( irc, cmd[1] );
if( !iu || !iu->bu )
{
- irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] );
+ irc_rootmsg( irc, "Nick `%s' does not exist", cmd[1] );
return;
}
ic = iu->bu->ic;
@@ -892,24 +893,24 @@ static void cmd_block( irc_t *irc, char **cmd )
}
else if( !( a = account_get( irc->b, cmd[1] ) ) )
{
- irc_usermsg( irc, "Invalid account" );
+ irc_rootmsg( irc, "Invalid account" );
return;
}
else if( !( ( ic = a->ic ) && ( a->ic->flags & OPT_LOGGED_IN ) ) )
{
- irc_usermsg( irc, "That account is not on-line" );
+ irc_rootmsg( irc, "That account is not on-line" );
return;
}
if( !ic->acc->prpl->add_deny || !ic->acc->prpl->rem_permit )
{
- irc_usermsg( irc, "Command `%s' not supported by this protocol", cmd[0] );
+ irc_rootmsg( irc, "Command `%s' not supported by this protocol", cmd[0] );
}
else
{
imc_rem_allow( ic, cmd[2] );
imc_add_block( ic, cmd[2] );
- irc_usermsg( irc, "Buddy `%s' moved from allow- to block-list", cmd[2] );
+ irc_rootmsg( irc, "Buddy `%s' moved from allow- to block-list", cmd[2] );
}
}
@@ -928,14 +929,14 @@ static void cmd_allow( irc_t *irc, char **cmd )
else
format = "%-32.32s %-16.16s";
- irc_usermsg( irc, format, "Handle", "Nickname" );
+ irc_rootmsg( irc, format, "Handle", "Nickname" );
for( l = a->ic->permit; l; l = l->next )
{
bee_user_t *bu = bee_user_by_handle( irc->b, a->ic, l->data );
irc_user_t *iu = bu ? bu->ui_data : NULL;
- irc_usermsg( irc, format, l->data, iu ? iu->nick : "(none)" );
+ irc_rootmsg( irc, format, l->data, iu ? iu->nick : "(none)" );
}
- irc_usermsg( irc, "End of list." );
+ irc_rootmsg( irc, "End of list." );
return;
}
@@ -944,7 +945,7 @@ static void cmd_allow( irc_t *irc, char **cmd )
irc_user_t *iu = irc_user_by_name( irc, cmd[1] );
if( !iu || !iu->bu )
{
- irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] );
+ irc_rootmsg( irc, "Nick `%s' does not exist", cmd[1] );
return;
}
ic = iu->bu->ic;
@@ -952,25 +953,25 @@ static void cmd_allow( irc_t *irc, char **cmd )
}
else if( !( a = account_get( irc->b, cmd[1] ) ) )
{
- irc_usermsg( irc, "Invalid account" );
+ irc_rootmsg( irc, "Invalid account" );
return;
}
else if( !( ( ic = a->ic ) && ( a->ic->flags & OPT_LOGGED_IN ) ) )
{
- irc_usermsg( irc, "That account is not on-line" );
+ irc_rootmsg( irc, "That account is not on-line" );
return;
}
if( !ic->acc->prpl->rem_deny || !ic->acc->prpl->add_permit )
{
- irc_usermsg( irc, "Command `%s' not supported by this protocol", cmd[0] );
+ irc_rootmsg( irc, "Command `%s' not supported by this protocol", cmd[0] );
}
else
{
imc_rem_block( ic, cmd[2] );
imc_add_allow( ic, cmd[2] );
- irc_usermsg( irc, "Buddy `%s' moved from block- to allow-list", cmd[2] );
+ irc_rootmsg( irc, "Buddy `%s' moved from block- to allow-list", cmd[2] );
}
}
@@ -997,7 +998,7 @@ static void cmd_yesno( irc_t *irc, char **cmd )
{
if( ( ++times >= 3 ) )
{
- irc_usermsg( irc, "%s", msg[rand()%(sizeof(msg)/sizeof(char*))] );
+ irc_rootmsg( irc, "%s", msg[rand()%(sizeof(msg)/sizeof(char*))] );
last_irc = NULL;
times = 0;
return;
@@ -1010,7 +1011,7 @@ static void cmd_yesno( irc_t *irc, char **cmd )
times = 0;
}
- irc_usermsg( irc, "Did I ask you something?" );
+ irc_rootmsg( irc, "Did I ask you something?" );
return;
}
@@ -1020,7 +1021,7 @@ static void cmd_yesno( irc_t *irc, char **cmd )
{
if( sscanf( cmd[1], "%d", &numq ) != 1 )
{
- irc_usermsg( irc, "Invalid query number" );
+ irc_rootmsg( irc, "Invalid query number" );
return;
}
@@ -1030,7 +1031,7 @@ static void cmd_yesno( irc_t *irc, char **cmd )
if( !q )
{
- irc_usermsg( irc, "Uhm, I never asked you something like that..." );
+ irc_rootmsg( irc, "Uhm, I never asked you something like that..." );
return;
}
}
@@ -1070,7 +1071,7 @@ static void cmd_blist( irc_t *irc, char **cmd )
else
format = "%-16.16s %-40.40s %s";
- irc_usermsg( irc, format, "Nick", "Handle/Account", "Status" );
+ irc_rootmsg( irc, format, "Nick", "Handle/Account", "Status" );
if( irc->root->last_channel &&
strcmp( set_getstr( &irc->root->last_channel->set, "type" ), "control" ) != 0 )
@@ -1092,8 +1093,8 @@ static void cmd_blist( irc_t *irc, char **cmd )
if( bu->status_msg )
g_snprintf( st, sizeof( st ) - 1, "Online (%s)", bu->status_msg );
- g_snprintf( s, sizeof( s ) - 1, "%s %s(%s)", bu->handle, bu->ic->acc->prpl->name, bu->ic->acc->user );
- irc_usermsg( irc, format, iu->nick, s, st );
+ g_snprintf( s, sizeof( s ) - 1, "%s %s", bu->handle, bu->ic->acc->tag );
+ irc_rootmsg( irc, format, iu->nick, s, st );
}
n_online ++;
@@ -1110,8 +1111,8 @@ static void cmd_blist( irc_t *irc, char **cmd )
if( away == 1 )
{
- g_snprintf( s, sizeof( s ) - 1, "%s %s(%s)", bu->handle, bu->ic->acc->prpl->name, bu->ic->acc->user );
- irc_usermsg( irc, format, iu->nick, s, irc_user_get_away( iu ) );
+ g_snprintf( s, sizeof( s ) - 1, "%s %s", bu->handle, bu->ic->acc->tag );
+ irc_rootmsg( irc, format, iu->nick, s, irc_user_get_away( iu ) );
}
n_away ++;
}
@@ -1127,13 +1128,13 @@ static void cmd_blist( irc_t *irc, char **cmd )
if( offline == 1 )
{
- g_snprintf( s, sizeof( s ) - 1, "%s %s(%s)", bu->handle, bu->ic->acc->prpl->name, bu->ic->acc->user );
- irc_usermsg( irc, format, iu->nick, s, "Offline" );
+ g_snprintf( s, sizeof( s ) - 1, "%s %s", bu->handle, bu->ic->acc->tag );
+ irc_rootmsg( irc, format, iu->nick, s, "Offline" );
}
n_offline ++;
}
- irc_usermsg( irc, "%d buddies (%d available, %d away, %d offline)", n_online + n_away + n_offline, n_online, n_away, n_offline );
+ irc_rootmsg( irc, "%d buddies (%d available, %d away, %d offline)", n_online + n_away + n_offline, n_online, n_away, n_offline );
}
static void cmd_qlist( irc_t *irc, char **cmd )
@@ -1143,17 +1144,17 @@ static void cmd_qlist( irc_t *irc, char **cmd )
if( !q )
{
- irc_usermsg( irc, "There are no pending questions." );
+ irc_rootmsg( irc, "There are no pending questions." );
return;
}
- irc_usermsg( irc, "Pending queries:" );
+ irc_rootmsg( irc, "Pending queries:" );
for( num = 0; q; q = q->next, num ++ )
if( q->ic ) /* Not necessary yet, but it might come later */
- irc_usermsg( irc, "%d, %s(%s): %s", num, q->ic->acc->prpl->name, q->ic->acc->user, q->question );
+ irc_rootmsg( irc, "%d, %s: %s", num, q->ic->acc->tag, q->question );
else
- irc_usermsg( irc, "%d, BitlBee: %s", num, q->question );
+ irc_rootmsg( irc, "%d, BitlBee: %s", num, q->question );
}
static void cmd_chat( irc_t *irc, char **cmd )
@@ -1169,12 +1170,12 @@ static void cmd_chat( irc_t *irc, char **cmd )
if( !( acc = account_get( irc->b, cmd[2] ) ) )
{
- irc_usermsg( irc, "Invalid account" );
+ irc_rootmsg( irc, "Invalid account" );
return;
}
else if( !acc->prpl->chat_join )
{
- irc_usermsg( irc, "Named chatrooms not supported on that account." );
+ irc_rootmsg( irc, "Named chatrooms not supported on that account." );
return;
}
@@ -1204,14 +1205,14 @@ static void cmd_chat( irc_t *irc, char **cmd )
set_setstr( &ic->set, "account", cmd[2] ) &&
set_setstr( &ic->set, "room", cmd[3] ) )
{
- irc_usermsg( irc, "Chatroom successfully added." );
+ irc_rootmsg( irc, "Chatroom successfully added." );
}
else
{
if( ic )
irc_channel_free( ic );
- irc_usermsg( irc, "Could not add chatroom." );
+ irc_rootmsg( irc, "Could not add chatroom." );
}
g_free( channel );
}
@@ -1226,25 +1227,25 @@ static void cmd_chat( irc_t *irc, char **cmd )
{
if( !iu->bu->ic->acc->prpl->chat_with( iu->bu->ic, iu->bu->handle ) )
{
- irc_usermsg( irc, "(Possible) failure while trying to open "
+ irc_rootmsg( irc, "(Possible) failure while trying to open "
"a groupchat with %s.", iu->nick );
}
}
else
{
- irc_usermsg( irc, "Can't open a groupchat with %s.", cmd[2] );
+ irc_rootmsg( irc, "Can't open a groupchat with %s.", cmd[2] );
}
}
else if( g_strcasecmp( cmd[1], "list" ) == 0 ||
g_strcasecmp( cmd[1], "set" ) == 0 ||
g_strcasecmp( cmd[1], "del" ) == 0 )
{
- irc_usermsg( irc, "Warning: The \002chat\002 command was mostly replaced with the \002channel\002 command." );
+ irc_rootmsg( irc, "Warning: The \002chat\002 command was mostly replaced with the \002channel\002 command." );
cmd_channel( irc, cmd );
}
else
{
- irc_usermsg( irc, "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "chat", cmd[1] );
+ irc_rootmsg( irc, "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "chat", cmd[1] );
}
}
@@ -1259,18 +1260,18 @@ static void cmd_group( irc_t *irc, char **cmd )
int n = 0;
if( strchr( irc->umode, 'b' ) )
- irc_usermsg( irc, "Group list:" );
+ irc_rootmsg( irc, "Group list:" );
for( l = irc->b->groups; l; l = l->next )
{
bee_group_t *bg = l->data;
- irc_usermsg( irc, "%d. %s", n ++, bg->name );
+ irc_rootmsg( irc, "%d. %s", n ++, bg->name );
}
- irc_usermsg( irc, "End of group list" );
+ irc_rootmsg( irc, "End of group list" );
}
else
{
- irc_usermsg( irc, "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "group", cmd[1] );
+ irc_rootmsg( irc, "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "group", cmd[1] );
}
}
@@ -1283,7 +1284,7 @@ static void cmd_transfer( irc_t *irc, char **cmd )
if( !files )
{
- irc_usermsg( irc, "No pending transfers" );
+ irc_rootmsg( irc, "No pending transfers" );
return;
}
@@ -1304,7 +1305,7 @@ static void cmd_transfer( irc_t *irc, char **cmd )
switch( subcmd ) {
case LIST:
if ( file->status == FT_STATUS_LISTENING )
- irc_usermsg( irc,
+ irc_rootmsg( irc,
"Pending file(id %d): %s (Listening...)", file->local_id, file->file_name);
else
{
@@ -1313,7 +1314,7 @@ static void cmd_transfer( irc_t *irc, char **cmd )
if ( ( file->started > 0 ) && ( file->bytes_transferred > 0 ) )
kb_per_s = file->bytes_transferred / 1024 / diff;
- irc_usermsg( irc,
+ irc_rootmsg( irc,
"Pending file(id %d): %s (%10zd/%zd kb, %d kb/s)", file->local_id, file->file_name,
file->bytes_transferred/1024, file->file_size/1024, kb_per_s);
}
@@ -1321,14 +1322,14 @@ static void cmd_transfer( irc_t *irc, char **cmd )
case REJECT:
if( file->status == FT_STATUS_LISTENING )
{
- irc_usermsg( irc, "Rejecting file transfer for %s", file->file_name );
+ irc_rootmsg( irc, "Rejecting file transfer for %s", file->file_name );
imcb_file_canceled( file->ic, file, "Denied by user" );
}
break;
case CANCEL:
if( file->local_id == fid )
{
- irc_usermsg( irc, "Canceling file transfer for %s", file->file_name );
+ irc_rootmsg( irc, "Canceling file transfer for %s", file->file_name );
imcb_file_canceled( file->ic, file, "Canceled by user" );
}
break;
@@ -1338,7 +1339,7 @@ static void cmd_transfer( irc_t *irc, char **cmd )
static void cmd_nick( irc_t *irc, char **cmd )
{
- irc_usermsg( irc, "This command is deprecated. Try: account %s set display_name", cmd[1] );
+ irc_rootmsg( irc, "This command is deprecated. Try: account %s set display_name", cmd[1] );
}
/* Maybe this should be a stand-alone command as well? */
@@ -1353,7 +1354,7 @@ static void bitlbee_whatsnew( irc_t *irc )
msg = help_get_whatsnew( &(global.help), last );
if( msg )
- irc_usermsg( irc, "%s: This seems to be your first time using this "
+ irc_rootmsg( irc, "%s: This seems to be your first time using this "
"this version of BitlBee. Here's a list of new "
"features you may like to know about:\n\n%s\n",
irc->user->nick, msg );
diff --git a/set.c b/set.c
index 17befba9..b35be708 100644
--- a/set.c
+++ b/set.c
@@ -111,6 +111,14 @@ int set_getbool( set_t **head, const char *key )
return bool2int( s );
}
+int set_isvisible( set_t *set )
+{
+ /* the default value is not stored in value, only in def */
+ return !( ( set->flags & SET_HIDDEN ) ||
+ ( ( set->flags & SET_HIDDEN_DEFAULT ) &&
+ ( set->value == NULL ) ) );
+}
+
int set_setstr( set_t **head, const char *key, char *value )
{
set_t *s = set_find( head, key );
diff --git a/set.h b/set.h
index 8f3028c4..f4f56f88 100644
--- a/set.h
+++ b/set.h
@@ -48,6 +48,7 @@ typedef enum
SET_NULL_OK = 0x0100,
SET_HIDDEN = 0x0200,
SET_PASSWORD = 0x0400,
+ SET_HIDDEN_DEFAULT = 0x0800,
} set_flags_t;
typedef struct set
@@ -97,6 +98,9 @@ int set_setint( set_t **head, const char *key, int value );
void set_del( set_t **head, const char *key );
int set_reset( set_t **head, const char *key );
+/* returns true if a setting shall be shown to the user */
+int set_isvisible( set_t *set );
+
/* Two very useful generic evaluators. */
char *set_eval_int( set_t *set, char *value );
char *set_eval_bool( set_t *set, char *value );
diff --git a/storage_xml.c b/storage_xml.c
index 042dcaae..af77190e 100644
--- a/storage_xml.c
+++ b/storage_xml.c
@@ -268,7 +268,7 @@ static void xml_start_element( GMarkupParseContext *ctx, const gchar *element_na
else
{
xd->unknown_tag ++;
- irc_usermsg( irc, "Warning: Unknown XML tag found in configuration file (%s). "
+ irc_rootmsg( irc, "Warning: Unknown XML tag found in configuration file (%s). "
"This may happen when downgrading BitlBee versions. "
"This tag will be skipped and the information will be lost "
"once you save your settings.", element_name );
@@ -396,7 +396,7 @@ static storage_status_t xml_load_real( irc_t *irc, const char *my_nick, const ch
else
{
if( gerr && irc )
- irc_usermsg( irc, "Error from XML-parser: %s", gerr->message );
+ irc_rootmsg( irc, "Error from XML-parser: %s", gerr->message );
g_clear_error( &gerr );
return STORAGE_OTHER_ERROR;
@@ -472,7 +472,7 @@ static storage_status_t xml_save( irc_t *irc, int overwrite )
strcat( path, ".XXXXXX" );
if( ( fd = mkstemp( path ) ) < 0 )
{
- irc_usermsg( irc, "Error while opening configuration file." );
+ irc_rootmsg( irc, "Error while opening configuration file." );
return STORAGE_OTHER_ERROR;
}
@@ -569,7 +569,7 @@ static storage_status_t xml_save( irc_t *irc, int overwrite )
path2 = g_strndup( path, strlen( path ) - 7 );
if( rename( path, path2 ) != 0 )
{
- irc_usermsg( irc, "Error while renaming temporary configuration file." );
+ irc_rootmsg( irc, "Error while renaming temporary configuration file." );
g_free( path2 );
unlink( path );
@@ -584,7 +584,7 @@ static storage_status_t xml_save( irc_t *irc, int overwrite )
write_error:
g_free( pass_buf );
- irc_usermsg( irc, "Write error. Disk full?" );
+ irc_rootmsg( irc, "Write error. Disk full?" );
close( fd );
return STORAGE_OTHER_ERROR;