Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/EionRobb/skype4pidgin.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEion Robb <eion@robbmob.com>2021-02-02 00:51:19 +0300
committerGitHub <noreply@github.com>2021-02-02 00:51:19 +0300
commit42df93659e50fac89f2bc39433051d94b65a1340 (patch)
tree232c108a90cfe0d46ac13c0f31bfaeab27e608fc
parentf23849142a7120802ebccea71a214da6945abc98 (diff)
parent8aed865de90b0ad574f34bb6a10add778eb0e45c (diff)
Merge pull request #674 from jpalus/master
Unify line endings to LF
-rw-r--r--README.md6
-rw-r--r--skypeweb/Makefile294
-rw-r--r--skypeweb/glibcompat.h40
-rw-r--r--skypeweb/gpl3.txt1346
-rw-r--r--skypeweb/libskypeweb.c2052
-rw-r--r--skypeweb/purple2compat/http.c4
-rw-r--r--skypeweb/purple2compat/internal.h34
-rw-r--r--skypeweb/purple2compat/purple-socket.c4
-rw-r--r--skypeweb/skypeweb_contacts.c3178
-rw-r--r--skypeweb/skypeweb_login.c1210
-rw-r--r--skypeweb/skypeweb_messages.c3518
-rw-r--r--skypeweb/skypeweb_util.c562
-rw-r--r--skypeweb/skypeweb_util.h70
-rw-r--r--skypeweb/theme1454
14 files changed, 6886 insertions, 6886 deletions
diff --git a/README.md b/README.md
index bb699b3..30ba71d 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,3 @@
-If you're reading this, you probably want the newer SkypeWeb plugin from [here](https://github.com/EionRobb/skype4pidgin/tree/master/skypeweb).
-
-
+If you're reading this, you probably want the newer SkypeWeb plugin from [here](https://github.com/EionRobb/skype4pidgin/tree/master/skypeweb).
+
+
diff --git a/skypeweb/Makefile b/skypeweb/Makefile
index 594e3e1..577f3d0 100644
--- a/skypeweb/Makefile
+++ b/skypeweb/Makefile
@@ -1,147 +1,147 @@
-
-PIDGIN_TREE_TOP ?= ../../pidgin-2.10.11
-PIDGIN3_TREE_TOP ?= ../../pidgin-main
-LIBPURPLE_DIR ?= $(PIDGIN_TREE_TOP)/libpurple
-WIN32_DEV_TOP ?= $(PIDGIN_TREE_TOP)/../win32-dev
-
-WIN32_CC ?= $(WIN32_DEV_TOP)/mingw-4.7.2/bin/gcc
-
-PKG_CONFIG ?= pkg-config
-DIR_PERM = 0755
-LIB_PERM = 0755
-FILE_PERM = 0644
-MAKENSIS ?= makensis
-
-CFLAGS ?= -O2 -g -pipe
-LDFLAGS ?=
-
-# Do some nasty OS and purple version detection
-ifeq ($(OS),Windows_NT)
- #only defined on 64-bit windows
- PROGFILES32 = ${ProgramFiles(x86)}
- ifndef PROGFILES32
- PROGFILES32 = $(PROGRAMFILES)
- endif
- SKYPEWEB_TARGET = libskypeweb.dll
- SKYPEWEB_DEST = "$(PROGFILES32)/Pidgin/plugins"
- SKYPEWEB_ICONS_DEST = "$(PROGFILES32)/Pidgin/pixmaps/pidgin/protocols"
- SKYPEWEB_THEME_DEST = "$(PROGFILES32)/Pidgin/pixmaps/pidgin/emotes/skype"
- MAKENSIS = "$(PROGFILES32)/NSIS/makensis.exe"
-else
-
- UNAME_S := $(shell uname -s)
-
- #.. There are special flags we need for OSX
- ifeq ($(UNAME_S), Darwin)
- #
- #.. /opt/local/include and subdirs are included here to ensure this compiles
- # for folks using Macports. I believe Homebrew uses /usr/local/include
- # so things should "just work". You *must* make sure your packages are
- # all up to date or you will most likely get compilation errors.
- #
- INCLUDES = -I/opt/local/include -lz $(OS)
-
- CC = gcc
- else
- INCLUDES =
- CC ?= gcc
- endif
-
- ifeq ($(shell $(PKG_CONFIG) --exists purple-3 2>/dev/null && echo "true"),)
- ifeq ($(shell $(PKG_CONFIG) --exists purple 2>/dev/null && echo "true"),)
- SKYPEWEB_TARGET = FAILNOPURPLE
- SKYPEWEB_DEST =
- SKYPEWEB_ICONS_DEST =
- SKYPEWEB_THEME_DEST =
- else
- SKYPEWEB_TARGET = libskypeweb.so
- SKYPEWEB_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=plugindir purple`
- SKYPEWEB_ICONS_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=datadir purple`/pixmaps/pidgin/protocols
- SKYPEWEB_THEME_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=datadir purple`/pixmaps/pidgin/emotes/skype
- endif
- else
- SKYPEWEB_TARGET = libskypeweb3.so
- SKYPEWEB_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=plugindir purple-3`
- SKYPEWEB_ICONS_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=datadir purple-3`/pixmaps/pidgin/protocols
- SKYPEWEB_THEME_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=datadir purple-3`/pixmaps/pidgin/emotes/skype
- endif
-endif
-
-WIN32_CFLAGS = -I$(WIN32_DEV_TOP)/glib-2.28.8/include -I$(WIN32_DEV_TOP)/glib-2.28.8/include/glib-2.0 -I$(WIN32_DEV_TOP)/glib-2.28.8/lib/glib-2.0/include -I$(WIN32_DEV_TOP)/json-glib-0.14/include/json-glib-1.0 -DENABLE_NLS -DPACKAGE_VERSION='"$(PLUGIN_VERSION)"' -Wall -Wextra -Werror -Wno-deprecated-declarations -Wno-unused-parameter -fno-strict-aliasing -Wformat -Wno-sign-compare
-WIN32_LDFLAGS = -L$(WIN32_DEV_TOP)/glib-2.28.8/lib -L$(PROTOBUF_C_DIR)/bin -L$(WIN32_DEV_TOP)/json-glib-0.14/lib -lpurple -lintl -lglib-2.0 -lgobject-2.0 -ljson-glib-1.0 -g -ggdb -static-libgcc -lz
-WIN32_PIDGIN2_CFLAGS = -I$(PIDGIN_TREE_TOP)/libpurple -I$(PIDGIN_TREE_TOP) $(WIN32_CFLAGS)
-WIN32_PIDGIN3_CFLAGS = -I$(PIDGIN3_TREE_TOP)/libpurple -I$(PIDGIN3_TREE_TOP) -I$(WIN32_DEV_TOP)/gplugin-dev/gplugin $(WIN32_CFLAGS)
-WIN32_PIDGIN2_LDFLAGS = -L$(PIDGIN_TREE_TOP)/libpurple $(WIN32_LDFLAGS)
-WIN32_PIDGIN3_LDFLAGS = -L$(PIDGIN3_TREE_TOP)/libpurple -L$(WIN32_DEV_TOP)/gplugin-dev/gplugin $(WIN32_LDFLAGS) -lgplugin
-
-C_FILES = \
- skypeweb_connection.c \
- skypeweb_contacts.c \
- skypeweb_login.c \
- skypeweb_messages.c \
- skypeweb_util.c \
- libskypeweb.c
-PURPLE_COMPAT_FILES := purple2compat/http.c purple2compat/purple-socket.c
-PURPLE_C_FILES := libskypeweb.c $(C_FILES)
-
-
-
-.PHONY: all install FAILNOPURPLE clean translations
-
-all: $(SKYPEWEB_TARGET)
-
-libskypeweb.so: $(PURPLE_C_FILES) $(PURPLE_COMPAT_FILES)
- $(CC) -fPIC $(CFLAGS) -shared -o $@ $^ $(LDFLAGS) $(PROTOBUF_OPTS) `$(PKG_CONFIG) purple glib-2.0 json-glib-1.0 zlib --libs --cflags` $(INCLUDES) -Ipurple2compat -g -ggdb
-
-libskypeweb3.so: $(PURPLE_C_FILES)
- $(CC) -fPIC $(CFLAGS) -shared -o $@ $^ $(LDFLAGS) $(PROTOBUF_OPTS) `$(PKG_CONFIG) purple-3 glib-2.0 json-glib-1.0 zlib --libs --cflags` $(INCLUDES) -g -ggdb
-
-libskypeweb.dll: $(PURPLE_C_FILES) $(PURPLE_COMPAT_FILES)
- $(WIN32_CC) -shared -o $@ $^ $(WIN32_PIDGIN2_CFLAGS) $(WIN32_PIDGIN2_LDFLAGS) -Ipurple2compat
-
-libskypeweb3.dll: $(PURPLE_C_FILES)
- $(WIN32_CC) -shared -o $@ $^ $(WIN32_PIDGIN3_CFLAGS) $(WIN32_PIDGIN3_LDFLAGS)
-
-install: $(SKYPEWEB_TARGET) install-icons install-theme
- mkdir -m $(DIR_PERM) -p $(SKYPEWEB_DEST)
- install -m $(LIB_PERM) -p $(SKYPEWEB_TARGET) $(SKYPEWEB_DEST)
-
-install-icons: icons/16/skype.png icons/22/skype.png icons/48/skype.png icons/16/skypeout.png icons/22/skypeout.png icons/48/skypeout.png
- mkdir -m $(DIR_PERM) -p $(SKYPEWEB_ICONS_DEST)/16
- mkdir -m $(DIR_PERM) -p $(SKYPEWEB_ICONS_DEST)/22
- mkdir -m $(DIR_PERM) -p $(SKYPEWEB_ICONS_DEST)/48
- install -m $(FILE_PERM) -p icons/16/skype.png $(SKYPEWEB_ICONS_DEST)/16/skype.png
- install -m $(FILE_PERM) -p icons/22/skype.png $(SKYPEWEB_ICONS_DEST)/22/skype.png
- install -m $(FILE_PERM) -p icons/48/skype.png $(SKYPEWEB_ICONS_DEST)/48/skype.png
- install -m $(FILE_PERM) -p icons/16/skypeout.png $(SKYPEWEB_ICONS_DEST)/16/skypeout.png
- install -m $(FILE_PERM) -p icons/22/skypeout.png $(SKYPEWEB_ICONS_DEST)/22/skypeout.png
- install -m $(FILE_PERM) -p icons/48/skypeout.png $(SKYPEWEB_ICONS_DEST)/48/skypeout.png
-
-installer: pidgin-skypeweb.nsi libskypeweb.dll
- $(MAKENSIS) "/DPIDGIN_VARIANT"="Pidgin" "/DPRODUCT_NAME"="pidgin-skypeweb" "/DINSTALLER_NAME"="pidgin-skypeweb-installer" "/DJSON_GLIB_DLL"="libjson-glib-1.0.dll" pidgin-skypeweb.nsi
-
-install-theme: theme
- mkdir -m $(DIR_PERM) -p $(SKYPEWEB_THEME_DEST)
- install -m $(FILE_PERM) -p theme $(SKYPEWEB_THEME_DEST)/theme
-
-translations: po/skypeweb.pot
-
-po/skypeweb.pot:
- xgettext $^ -k_ --no-location -o $@
-
-po/%.po: po/skypeweb.pot
- msgmerge $@ po/skypeweb.pot > tmp-$*
- mv -f tmp-$* $@
-
-po/%.mo: po/%.po
- msgfmt -o $@ $^
-
-%-locale-install: po/%.mo
- install -D -m $(FILE_PERM) -p po/$(*F).mo $(LOCALEDIR)/$(*F)/LC_MESSAGES/purple-discord.mo
-
-FAILNOPURPLE:
- echo "You need libpurple development headers installed to be able to compile this plugin"
-
-clean:
- rm -f $(SKYPEWEB_TARGET)
-
+
+PIDGIN_TREE_TOP ?= ../../pidgin-2.10.11
+PIDGIN3_TREE_TOP ?= ../../pidgin-main
+LIBPURPLE_DIR ?= $(PIDGIN_TREE_TOP)/libpurple
+WIN32_DEV_TOP ?= $(PIDGIN_TREE_TOP)/../win32-dev
+
+WIN32_CC ?= $(WIN32_DEV_TOP)/mingw-4.7.2/bin/gcc
+
+PKG_CONFIG ?= pkg-config
+DIR_PERM = 0755
+LIB_PERM = 0755
+FILE_PERM = 0644
+MAKENSIS ?= makensis
+
+CFLAGS ?= -O2 -g -pipe
+LDFLAGS ?=
+
+# Do some nasty OS and purple version detection
+ifeq ($(OS),Windows_NT)
+ #only defined on 64-bit windows
+ PROGFILES32 = ${ProgramFiles(x86)}
+ ifndef PROGFILES32
+ PROGFILES32 = $(PROGRAMFILES)
+ endif
+ SKYPEWEB_TARGET = libskypeweb.dll
+ SKYPEWEB_DEST = "$(PROGFILES32)/Pidgin/plugins"
+ SKYPEWEB_ICONS_DEST = "$(PROGFILES32)/Pidgin/pixmaps/pidgin/protocols"
+ SKYPEWEB_THEME_DEST = "$(PROGFILES32)/Pidgin/pixmaps/pidgin/emotes/skype"
+ MAKENSIS = "$(PROGFILES32)/NSIS/makensis.exe"
+else
+
+ UNAME_S := $(shell uname -s)
+
+ #.. There are special flags we need for OSX
+ ifeq ($(UNAME_S), Darwin)
+ #
+ #.. /opt/local/include and subdirs are included here to ensure this compiles
+ # for folks using Macports. I believe Homebrew uses /usr/local/include
+ # so things should "just work". You *must* make sure your packages are
+ # all up to date or you will most likely get compilation errors.
+ #
+ INCLUDES = -I/opt/local/include -lz $(OS)
+
+ CC = gcc
+ else
+ INCLUDES =
+ CC ?= gcc
+ endif
+
+ ifeq ($(shell $(PKG_CONFIG) --exists purple-3 2>/dev/null && echo "true"),)
+ ifeq ($(shell $(PKG_CONFIG) --exists purple 2>/dev/null && echo "true"),)
+ SKYPEWEB_TARGET = FAILNOPURPLE
+ SKYPEWEB_DEST =
+ SKYPEWEB_ICONS_DEST =
+ SKYPEWEB_THEME_DEST =
+ else
+ SKYPEWEB_TARGET = libskypeweb.so
+ SKYPEWEB_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=plugindir purple`
+ SKYPEWEB_ICONS_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=datadir purple`/pixmaps/pidgin/protocols
+ SKYPEWEB_THEME_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=datadir purple`/pixmaps/pidgin/emotes/skype
+ endif
+ else
+ SKYPEWEB_TARGET = libskypeweb3.so
+ SKYPEWEB_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=plugindir purple-3`
+ SKYPEWEB_ICONS_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=datadir purple-3`/pixmaps/pidgin/protocols
+ SKYPEWEB_THEME_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=datadir purple-3`/pixmaps/pidgin/emotes/skype
+ endif
+endif
+
+WIN32_CFLAGS = -I$(WIN32_DEV_TOP)/glib-2.28.8/include -I$(WIN32_DEV_TOP)/glib-2.28.8/include/glib-2.0 -I$(WIN32_DEV_TOP)/glib-2.28.8/lib/glib-2.0/include -I$(WIN32_DEV_TOP)/json-glib-0.14/include/json-glib-1.0 -DENABLE_NLS -DPACKAGE_VERSION='"$(PLUGIN_VERSION)"' -Wall -Wextra -Werror -Wno-deprecated-declarations -Wno-unused-parameter -fno-strict-aliasing -Wformat -Wno-sign-compare
+WIN32_LDFLAGS = -L$(WIN32_DEV_TOP)/glib-2.28.8/lib -L$(PROTOBUF_C_DIR)/bin -L$(WIN32_DEV_TOP)/json-glib-0.14/lib -lpurple -lintl -lglib-2.0 -lgobject-2.0 -ljson-glib-1.0 -g -ggdb -static-libgcc -lz
+WIN32_PIDGIN2_CFLAGS = -I$(PIDGIN_TREE_TOP)/libpurple -I$(PIDGIN_TREE_TOP) $(WIN32_CFLAGS)
+WIN32_PIDGIN3_CFLAGS = -I$(PIDGIN3_TREE_TOP)/libpurple -I$(PIDGIN3_TREE_TOP) -I$(WIN32_DEV_TOP)/gplugin-dev/gplugin $(WIN32_CFLAGS)
+WIN32_PIDGIN2_LDFLAGS = -L$(PIDGIN_TREE_TOP)/libpurple $(WIN32_LDFLAGS)
+WIN32_PIDGIN3_LDFLAGS = -L$(PIDGIN3_TREE_TOP)/libpurple -L$(WIN32_DEV_TOP)/gplugin-dev/gplugin $(WIN32_LDFLAGS) -lgplugin
+
+C_FILES = \
+ skypeweb_connection.c \
+ skypeweb_contacts.c \
+ skypeweb_login.c \
+ skypeweb_messages.c \
+ skypeweb_util.c \
+ libskypeweb.c
+PURPLE_COMPAT_FILES := purple2compat/http.c purple2compat/purple-socket.c
+PURPLE_C_FILES := libskypeweb.c $(C_FILES)
+
+
+
+.PHONY: all install FAILNOPURPLE clean translations
+
+all: $(SKYPEWEB_TARGET)
+
+libskypeweb.so: $(PURPLE_C_FILES) $(PURPLE_COMPAT_FILES)
+ $(CC) -fPIC $(CFLAGS) -shared -o $@ $^ $(LDFLAGS) $(PROTOBUF_OPTS) `$(PKG_CONFIG) purple glib-2.0 json-glib-1.0 zlib --libs --cflags` $(INCLUDES) -Ipurple2compat -g -ggdb
+
+libskypeweb3.so: $(PURPLE_C_FILES)
+ $(CC) -fPIC $(CFLAGS) -shared -o $@ $^ $(LDFLAGS) $(PROTOBUF_OPTS) `$(PKG_CONFIG) purple-3 glib-2.0 json-glib-1.0 zlib --libs --cflags` $(INCLUDES) -g -ggdb
+
+libskypeweb.dll: $(PURPLE_C_FILES) $(PURPLE_COMPAT_FILES)
+ $(WIN32_CC) -shared -o $@ $^ $(WIN32_PIDGIN2_CFLAGS) $(WIN32_PIDGIN2_LDFLAGS) -Ipurple2compat
+
+libskypeweb3.dll: $(PURPLE_C_FILES)
+ $(WIN32_CC) -shared -o $@ $^ $(WIN32_PIDGIN3_CFLAGS) $(WIN32_PIDGIN3_LDFLAGS)
+
+install: $(SKYPEWEB_TARGET) install-icons install-theme
+ mkdir -m $(DIR_PERM) -p $(SKYPEWEB_DEST)
+ install -m $(LIB_PERM) -p $(SKYPEWEB_TARGET) $(SKYPEWEB_DEST)
+
+install-icons: icons/16/skype.png icons/22/skype.png icons/48/skype.png icons/16/skypeout.png icons/22/skypeout.png icons/48/skypeout.png
+ mkdir -m $(DIR_PERM) -p $(SKYPEWEB_ICONS_DEST)/16
+ mkdir -m $(DIR_PERM) -p $(SKYPEWEB_ICONS_DEST)/22
+ mkdir -m $(DIR_PERM) -p $(SKYPEWEB_ICONS_DEST)/48
+ install -m $(FILE_PERM) -p icons/16/skype.png $(SKYPEWEB_ICONS_DEST)/16/skype.png
+ install -m $(FILE_PERM) -p icons/22/skype.png $(SKYPEWEB_ICONS_DEST)/22/skype.png
+ install -m $(FILE_PERM) -p icons/48/skype.png $(SKYPEWEB_ICONS_DEST)/48/skype.png
+ install -m $(FILE_PERM) -p icons/16/skypeout.png $(SKYPEWEB_ICONS_DEST)/16/skypeout.png
+ install -m $(FILE_PERM) -p icons/22/skypeout.png $(SKYPEWEB_ICONS_DEST)/22/skypeout.png
+ install -m $(FILE_PERM) -p icons/48/skypeout.png $(SKYPEWEB_ICONS_DEST)/48/skypeout.png
+
+installer: pidgin-skypeweb.nsi libskypeweb.dll
+ $(MAKENSIS) "/DPIDGIN_VARIANT"="Pidgin" "/DPRODUCT_NAME"="pidgin-skypeweb" "/DINSTALLER_NAME"="pidgin-skypeweb-installer" "/DJSON_GLIB_DLL"="libjson-glib-1.0.dll" pidgin-skypeweb.nsi
+
+install-theme: theme
+ mkdir -m $(DIR_PERM) -p $(SKYPEWEB_THEME_DEST)
+ install -m $(FILE_PERM) -p theme $(SKYPEWEB_THEME_DEST)/theme
+
+translations: po/skypeweb.pot
+
+po/skypeweb.pot:
+ xgettext $^ -k_ --no-location -o $@
+
+po/%.po: po/skypeweb.pot
+ msgmerge $@ po/skypeweb.pot > tmp-$*
+ mv -f tmp-$* $@
+
+po/%.mo: po/%.po
+ msgfmt -o $@ $^
+
+%-locale-install: po/%.mo
+ install -D -m $(FILE_PERM) -p po/$(*F).mo $(LOCALEDIR)/$(*F)/LC_MESSAGES/purple-discord.mo
+
+FAILNOPURPLE:
+ echo "You need libpurple development headers installed to be able to compile this plugin"
+
+clean:
+ rm -f $(SKYPEWEB_TARGET)
+
diff --git a/skypeweb/glibcompat.h b/skypeweb/glibcompat.h
index 35cb10a..95d731a 100644
--- a/skypeweb/glibcompat.h
+++ b/skypeweb/glibcompat.h
@@ -1,21 +1,21 @@
-#ifndef _GLIBCOMPAT_H_
-#define _GLIBCOMPAT_H_
-
-#if !GLIB_CHECK_VERSION(2, 32, 0)
-#define g_hash_table_contains(hash_table, key) g_hash_table_lookup_extended(hash_table, key, NULL, NULL)
-#endif /* 2.32.0 */
-
-
-#if !GLIB_CHECK_VERSION(2, 28, 0)
-gint64
-g_get_real_time()
-{
- GTimeVal val;
-
- g_get_current_time (&val);
-
- return (((gint64) val.tv_sec) * 1000000) + val.tv_usec;
-}
-#endif /* 2.28.0 */
-
+#ifndef _GLIBCOMPAT_H_
+#define _GLIBCOMPAT_H_
+
+#if !GLIB_CHECK_VERSION(2, 32, 0)
+#define g_hash_table_contains(hash_table, key) g_hash_table_lookup_extended(hash_table, key, NULL, NULL)
+#endif /* 2.32.0 */
+
+
+#if !GLIB_CHECK_VERSION(2, 28, 0)
+gint64
+g_get_real_time()
+{
+ GTimeVal val;
+
+ g_get_current_time (&val);
+
+ return (((gint64) val.tv_sec) * 1000000) + val.tv_usec;
+}
+#endif /* 2.28.0 */
+
#endif /*_GLIBCOMPAT_H_*/ \ No newline at end of file
diff --git a/skypeweb/gpl3.txt b/skypeweb/gpl3.txt
index bb781ef..deea74a 100644
--- a/skypeweb/gpl3.txt
+++ b/skypeweb/gpl3.txt
@@ -1,674 +1,674 @@
- GNU GENERAL PUBLIC LICENSE
- Version 3, 29 June 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The GNU General Public License is a free, copyleft license for
-software and other kinds of works.
-
- The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works. By contrast,
-the GNU General Public License is intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users. We, the Free Software Foundation, use the
-GNU General Public License for most of our software; it applies also to
-any other work released this way by its authors. You can apply it to
-your programs, too.
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
- To protect your rights, we need to prevent others from denying you
-these rights or asking you to surrender the rights. Therefore, you have
-certain responsibilities if you distribute copies of the software, or if
-you modify it: responsibilities to respect the freedom of others.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must pass on to the recipients the same
-freedoms that you received. You must make sure that they, too, receive
-or can get the source code. And you must show them these terms so they
-know their rights.
-
- Developers that use the GNU GPL protect your rights with two steps:
-(1) assert copyright on the software, and (2) offer you this License
-giving you legal permission to copy, distribute and/or modify it.
-
- For the developers' and authors' protection, the GPL clearly explains
-that there is no warranty for this free software. For both users' and
-authors' sake, the GPL requires that modified versions be marked as
-changed, so that their problems will not be attributed erroneously to
-authors of previous versions.
-
- Some devices are designed to deny users access to install or run
-modified versions of the software inside them, although the manufacturer
-can do so. This is fundamentally incompatible with the aim of
-protecting users' freedom to change the software. The systematic
-pattern of such abuse occurs in the area of products for individuals to
-use, which is precisely where it is most unacceptable. Therefore, we
-have designed this version of the GPL to prohibit the practice for those
-products. If such problems arise substantially in other domains, we
-stand ready to extend this provision to those domains in future versions
-of the GPL, as needed to protect the freedom of users.
-
- Finally, every program is threatened constantly by software patents.
-States should not allow patents to restrict development and use of
-software on general-purpose computers, but in those that do, we wish to
-avoid the special danger that patents applied to a free program could
-make it effectively proprietary. To prevent this, the GPL assures that
-patents cannot be used to render the program non-free.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- TERMS AND CONDITIONS
-
- 0. Definitions.
-
- "This License" refers to version 3 of the GNU General Public License.
-
- "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
- "The Program" refers to any copyrightable work licensed under this
-License. Each licensee is addressed as "you". "Licensees" and
-"recipients" may be individuals or organizations.
-
- To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy. The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
- A "covered work" means either the unmodified Program or a work based
-on the Program.
-
- To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy. Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
- To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies. Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
- An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License. If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
- 1. Source Code.
-
- The "source code" for a work means the preferred form of the work
-for making modifications to it. "Object code" means any non-source
-form of a work.
-
- A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
- The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form. A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
- The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities. However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work. For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
- The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
- The Corresponding Source for a work in source code form is that
-same work.
-
- 2. Basic Permissions.
-
- All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met. This License explicitly affirms your unlimited
-permission to run the unmodified Program. The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work. This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
- You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force. You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright. Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
- Conveying under any other circumstances is permitted solely under
-the conditions stated below. Sublicensing is not allowed; section 10
-makes it unnecessary.
-
- 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
- No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
- When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
- 4. Conveying Verbatim Copies.
-
- You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
- You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
- 5. Conveying Modified Source Versions.
-
- You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
- a) The work must carry prominent notices stating that you modified
- it, and giving a relevant date.
-
- b) The work must carry prominent notices stating that it is
- released under this License and any conditions added under section
- 7. This requirement modifies the requirement in section 4 to
- "keep intact all notices".
-
- c) You must license the entire work, as a whole, under this
- License to anyone who comes into possession of a copy. This
- License will therefore apply, along with any applicable section 7
- additional terms, to the whole of the work, and all its parts,
- regardless of how they are packaged. This License gives no
- permission to license the work in any other way, but it does not
- invalidate such permission if you have separately received it.
-
- d) If the work has interactive user interfaces, each must display
- Appropriate Legal Notices; however, if the Program has interactive
- interfaces that do not display Appropriate Legal Notices, your
- work need not make them do so.
-
- A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit. Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
- 6. Conveying Non-Source Forms.
-
- You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
- a) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by the
- Corresponding Source fixed on a durable physical medium
- customarily used for software interchange.
-
- b) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by a
- written offer, valid for at least three years and valid for as
- long as you offer spare parts or customer support for that product
- model, to give anyone who possesses the object code either (1) a
- copy of the Corresponding Source for all the software in the
- product that is covered by this License, on a durable physical
- medium customarily used for software interchange, for a price no
- more than your reasonable cost of physically performing this
- conveying of source, or (2) access to copy the
- Corresponding Source from a network server at no charge.
-
- c) Convey individual copies of the object code with a copy of the
- written offer to provide the Corresponding Source. This
- alternative is allowed only occasionally and noncommercially, and
- only if you received the object code with such an offer, in accord
- with subsection 6b.
-
- d) Convey the object code by offering access from a designated
- place (gratis or for a charge), and offer equivalent access to the
- Corresponding Source in the same way through the same place at no
- further charge. You need not require recipients to copy the
- Corresponding Source along with the object code. If the place to
- copy the object code is a network server, the Corresponding Source
- may be on a different server (operated by you or a third party)
- that supports equivalent copying facilities, provided you maintain
- clear directions next to the object code saying where to find the
- Corresponding Source. Regardless of what server hosts the
- Corresponding Source, you remain obligated to ensure that it is
- available for as long as needed to satisfy these requirements.
-
- e) Convey the object code using peer-to-peer transmission, provided
- you inform other peers where the object code and Corresponding
- Source of the work are being offered to the general public at no
- charge under subsection 6d.
-
- A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
- A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling. In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage. For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product. A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
- "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source. The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
- If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information. But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
- The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed. Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
- Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
- 7. Additional Terms.
-
- "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law. If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
- When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it. (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.) You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
- Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
- a) Disclaiming warranty or limiting liability differently from the
- terms of sections 15 and 16 of this License; or
-
- b) Requiring preservation of specified reasonable legal notices or
- author attributions in that material or in the Appropriate Legal
- Notices displayed by works containing it; or
-
- c) Prohibiting misrepresentation of the origin of that material, or
- requiring that modified versions of such material be marked in
- reasonable ways as different from the original version; or
-
- d) Limiting the use for publicity purposes of names of licensors or
- authors of the material; or
-
- e) Declining to grant rights under trademark law for use of some
- trade names, trademarks, or service marks; or
-
- f) Requiring indemnification of licensors and authors of that
- material by anyone who conveys the material (or modified versions of
- it) with contractual assumptions of liability to the recipient, for
- any liability that these contractual assumptions directly impose on
- those licensors and authors.
-
- All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10. If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term. If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
- If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
- Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
- 8. Termination.
-
- You may not propagate or modify a covered work except as expressly
-provided under this License. Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
- However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
- Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
- Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License. If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
- 9. Acceptance Not Required for Having Copies.
-
- You are not required to accept this License in order to receive or
-run a copy of the Program. Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance. However,
-nothing other than this License grants you permission to propagate or
-modify any covered work. These actions infringe copyright if you do
-not accept this License. Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
- 10. Automatic Licensing of Downstream Recipients.
-
- Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License. You are not responsible
-for enforcing compliance by third parties with this License.
-
- An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations. If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
- You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License. For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
- 11. Patents.
-
- A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based. The
-work thus licensed is called the contributor's "contributor version".
-
- A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version. For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
- Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
- In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement). To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
- If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients. "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
- If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
- A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License. You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
- Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
- 12. No Surrender of Others' Freedom.
-
- If conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all. For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
- 13. Use with the GNU Affero General Public License.
-
- Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU Affero General Public License into a single
-combined work, and to convey the resulting work. The terms of this
-License will continue to apply to the part which is the covered work,
-but the special requirements of the GNU Affero General Public License,
-section 13, concerning interaction through a network will apply to the
-combination as such.
-
- 14. Revised Versions of this License.
-
- The Free Software Foundation may publish revised and/or new versions of
-the GNU General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
- Each version is given a distinguishing version number. If the
-Program specifies that a certain numbered version of the GNU General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation. If the Program does not specify a version number of the
-GNU General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
- If the Program specifies that a proxy can decide which future
-versions of the GNU General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
- Later license versions may give you additional or different
-permissions. However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
- 15. Disclaimer of Warranty.
-
- THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
- 16. Limitation of Liability.
-
- IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
- 17. Interpretation of Sections 15 and 16.
-
- If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
- To do so, attach the following notices to the program. It is safest
-to attach them to the start of each source file to most effectively
-state the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
- <one line to give the program's name and a brief idea of what it does.>
- Copyright (C) <year> <name of author>
-
- 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 3 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, see <http://www.gnu.org/licenses/>.
-
-Also add information on how to contact you by electronic and paper mail.
-
- If the program does terminal interaction, make it output a short
-notice like this when it starts in an interactive mode:
-
- {{ project }} Copyright (C) {{ year }} {{ organization }}
- This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, your program's commands
-might be different; for a GUI interface, you would use an "about box".
-
- You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU GPL, see
-<http://www.gnu.org/licenses/>.
-
- The GNU General Public License does not permit incorporating your program
-into proprietary programs. If your program is a subroutine library, you
-may consider it more useful to permit linking proprietary applications with
-the library. If this is what you want to do, use the GNU Lesser General
-Public License instead of this License. But first, please read
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ {{ project }} Copyright (C) {{ year }} {{ organization }}
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>. \ No newline at end of file
diff --git a/skypeweb/libskypeweb.c b/skypeweb/libskypeweb.c
index ace9c1b..04ad7ac 100644
--- a/skypeweb/libskypeweb.c
+++ b/skypeweb/libskypeweb.c
@@ -1,1026 +1,1026 @@
-/*
- * SkypeWeb Plugin for libpurple/Pidgin
- * Copyright (c) 2014-2020 Eion Robb
- *
- * 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 3 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, see <http://www.gnu.org/licenses/>.
- */
-
-#include "libskypeweb.h"
-#include "skypeweb_connection.h"
-#include "skypeweb_contacts.h"
-#include "skypeweb_login.h"
-#include "skypeweb_messages.h"
-#include "skypeweb_util.h"
-
-void
-skypeweb_do_all_the_things(SkypeWebAccount *sa)
-{
- skypeweb_get_vdms_token(sa);
-
- if (!sa->username) {
- skypeweb_get_self_details(sa);
- } else
- if (sa->registration_token) {
- skypeweb_get_self_details(sa);
-
- if (sa->authcheck_timeout)
- g_source_remove(sa->authcheck_timeout);
- skypeweb_check_authrequests(sa);
- sa->authcheck_timeout = g_timeout_add_seconds(120, (GSourceFunc)skypeweb_check_authrequests, sa);
- purple_connection_set_state(sa->pc, PURPLE_CONNECTION_CONNECTED);
-
- skypeweb_get_friend_list(sa);
- skypeweb_poll(sa);
-
- skype_web_get_offline_history(sa);
-
- skypeweb_set_status(sa->account, purple_account_get_active_status(sa->account));
- } else {
- //Too soon!
- skypeweb_get_registration_token(sa);
- }
-
-
-}
-
-
-/******************************************************************************/
-/* PRPL functions */
-/******************************************************************************/
-
-static const char *
-skypeweb_list_icon(PurpleAccount *account, PurpleBuddy *buddy)
-{
-
- if (buddy != NULL) {
- const gchar *buddy_name = purple_buddy_get_name(buddy);
- if (buddy_name && SKYPEWEB_BUDDY_IS_MSN(buddy_name)) {
- return "msn";
- }
- }
- return "skype";
-}
-
-static gchar *
-skypeweb_status_text(PurpleBuddy *buddy)
-{
- SkypeWebBuddy *sbuddy = purple_buddy_get_protocol_data(buddy);
-
- if (sbuddy && sbuddy->mood && *(sbuddy->mood))
- {
- gchar *stripped = purple_markup_strip_html(sbuddy->mood);
- gchar *escaped = g_markup_printf_escaped("%s", stripped);
-
- g_free(stripped);
-
- return escaped;
- }
-
- return NULL;
-}
-
-void
-skypeweb_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
-{
- SkypeWebBuddy *sbuddy = purple_buddy_get_protocol_data(buddy);
-
- if (sbuddy)
- {
- PurplePresence *presence;
- PurpleStatus *status;
- SkypeWebBuddy *sbuddy = purple_buddy_get_protocol_data(buddy);
-
- presence = purple_buddy_get_presence(buddy);
- status = purple_presence_get_active_status(presence);
- purple_notify_user_info_add_pair_html(user_info, _("Status"), purple_status_get_name(status));
- if (sbuddy->mood && *sbuddy->mood) {
- gchar *stripped = purple_markup_strip_html(sbuddy->mood);
- gchar *escaped = g_markup_printf_escaped("%s", stripped);
-
- purple_notify_user_info_add_pair_html(user_info, _("Message"), escaped);
-
- g_free(stripped);
- g_free(escaped);
- }
-
- if (sbuddy->display_name && *sbuddy->display_name) {
- gchar *escaped = g_markup_printf_escaped("%s", sbuddy->display_name);
- purple_notify_user_info_add_pair_html(user_info, "Alias", escaped);
- g_free(escaped);
- }
- if (sbuddy->fullname && *sbuddy->fullname) {
- gchar *escaped = g_markup_printf_escaped("%s", sbuddy->fullname);
- purple_notify_user_info_add_pair_html(user_info, "Full Name", escaped);
- g_free(escaped);
- }
- }
-}
-
-const gchar *
-skypeweb_list_emblem(PurpleBuddy *buddy)
-{
- if (buddy != NULL) {
- //SkypeWebBuddy *sbuddy = purple_buddy_get_protocol_data(buddy);
- const gchar *buddy_name = purple_buddy_get_name(buddy);
-
- if (buddy_name && SKYPEWEB_BUDDY_IS_BOT(buddy_name)) {
- return "bot";
- }
- }
- return NULL;
-}
-
-GList *
-skypeweb_status_types(PurpleAccount *account)
-{
- GList *types = NULL;
- PurpleStatusType *status;
-
- status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, NULL, NULL, FALSE, FALSE, FALSE);
- types = g_list_append(types, status);
-
- status = purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE, SKYPEWEB_STATUS_ONLINE, _("Online"), TRUE, TRUE, FALSE, "message", "Mood", purple_value_new(PURPLE_TYPE_STRING), NULL);
- types = g_list_append(types, status);
- status = purple_status_type_new_with_attrs(PURPLE_STATUS_AWAY, SKYPEWEB_STATUS_AWAY, _("Away"), TRUE, TRUE, FALSE, "message", "Mood", purple_value_new(PURPLE_TYPE_STRING), NULL);
- types = g_list_append(types, status);
-// status = purple_status_type_new_with_attrs(PURPLE_STATUS_EXTENDED_AWAY, SKYPEWEB_STATUS_AWAY, _("Not Available"), TRUE, TRUE, FALSE, "message", "Mood", purple_value_new(PURPLE_TYPE_STRING), NULL);
-// types = g_list_append(types, status);
- status = purple_status_type_new_with_attrs(PURPLE_STATUS_UNAVAILABLE, SKYPEWEB_STATUS_BUSY, _("Do Not Disturb"), TRUE, TRUE, FALSE, "message", "Mood", purple_value_new(PURPLE_TYPE_STRING), NULL);
- types = g_list_append(types, status);
- status = purple_status_type_new_with_attrs(PURPLE_STATUS_INVISIBLE, SKYPEWEB_STATUS_HIDDEN, _("Invisible"), TRUE, TRUE, FALSE, "message", "Mood", purple_value_new(PURPLE_TYPE_STRING), NULL);
- types = g_list_append(types, status);
- status = purple_status_type_new_with_attrs(PURPLE_STATUS_OFFLINE, SKYPEWEB_STATUS_OFFLINE, _("Offline"), TRUE, TRUE, FALSE, "message", "Mood", purple_value_new(PURPLE_TYPE_STRING), NULL);
- types = g_list_append(types, status);
-
- return types;
-}
-
-
-static GList *
-skypeweb_chat_info(PurpleConnection *gc)
-{
- GList *m = NULL;
- PurpleProtocolChatEntry *pce;
-
- pce = g_new0(PurpleProtocolChatEntry, 1);
- pce->label = _("Skype Name");
- pce->identifier = "chatname";
- pce->required = TRUE;
- m = g_list_append(m, pce);
-
- /*pce = g_new0(PurpleProtocolChatEntry, 1);
- pce->label = _("Password");
- pce->identifier = "password";
- pce->required = FALSE;
- m = g_list_append(m, pce);*/
-
- return m;
-}
-
-static GHashTable *
-skypeweb_chat_info_defaults(PurpleConnection *gc, const char *chatname)
-{
- GHashTable *defaults;
- defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
- if (chatname != NULL)
- {
- g_hash_table_insert(defaults, "chatname", g_strdup(chatname));
- }
- return defaults;
-}
-
-static gchar *
-skypeweb_get_chat_name(GHashTable *data)
-{
- gchar *temp;
-
- if (data == NULL)
- return NULL;
-
- temp = g_hash_table_lookup(data, "chatname");
-
- if (temp == NULL)
- return NULL;
-
- return g_strdup(temp);
-}
-
-
-static void
-skypeweb_join_chat(PurpleConnection *pc, GHashTable *data)
-{
- SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
- gchar *chatname;
- gchar *post;
- GString *url;
- PurpleChatConversation *chatconv;
-
- chatname = (gchar *)g_hash_table_lookup(data, "chatname");
- if (chatname == NULL)
- {
- return;
- }
-
- chatconv = purple_conversations_find_chat_with_account(chatname, sa->account);
- if (chatconv != NULL && !purple_chat_conversation_has_left(chatconv)) {
- purple_conversation_present(PURPLE_CONVERSATION(chatconv));
- return;
- }
-
- url = g_string_new("/v1/threads/");
- g_string_append_printf(url, "%s", purple_url_encode(chatname));
- g_string_append(url, "/members/");
- g_string_append_printf(url, "8:%s", purple_url_encode(sa->username));
-
- /* Specifying the role does not seem to be required and often result in a users role being
- * downgraded from admin to user
- * post = "{\"role\":\"User\"}"; */
- post = "{}";
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, url->str, post, NULL, NULL, TRUE);
-
- g_string_free(url, TRUE);
-
- skypeweb_get_conversation_history(sa, chatname);
- skypeweb_get_thread_users(sa, chatname);
-
- chatconv = purple_serv_got_joined_chat(pc, g_str_hash(chatname), chatname);
- purple_conversation_set_data(PURPLE_CONVERSATION(chatconv), "chatname", g_strdup(chatname));
-
- purple_conversation_present(PURPLE_CONVERSATION(chatconv));
-}
-
-void
-skypeweb_buddy_free(PurpleBuddy *buddy)
-{
- SkypeWebBuddy *sbuddy = purple_buddy_get_protocol_data(buddy);
- if (sbuddy != NULL)
- {
- purple_buddy_set_protocol_data(buddy, NULL);
-
- g_free(sbuddy->skypename);
- g_free(sbuddy->fullname);
- g_free(sbuddy->display_name);
- g_free(sbuddy->avatar_url);
- g_free(sbuddy->mood);
-
- g_free(sbuddy);
- }
-}
-
-void
-skypeweb_fake_group_buddy(PurpleConnection *pc, const char *who, const char *old_group, const char *new_group)
-{
- // Do nothing to stop the remove+add behaviour
-}
-void
-skypeweb_fake_group_rename(PurpleConnection *pc, const char *old_name, PurpleGroup *group, GList *moved_buddies)
-{
- // Do nothing to stop the remove+add behaviour
-}
-
-static GList *
-skypeweb_node_menu(PurpleBlistNode *node)
-{
- GList *m = NULL;
- PurpleMenuAction *act;
- PurpleBuddy *buddy;
- SkypeWebAccount *sa = NULL;
-
- if(PURPLE_IS_BUDDY(node))
- {
- buddy = PURPLE_BUDDY(node);
- if (purple_buddy_get_protocol_data(buddy)) {
- SkypeWebBuddy *sbuddy = purple_buddy_get_protocol_data(buddy);
- sa = sbuddy->sa;
- }
- if (sa == NULL) {
- PurpleConnection *pc = purple_account_get_connection(purple_buddy_get_account(buddy));
- sa = purple_connection_get_protocol_data(pc);
- }
-
- if (sa != NULL) {
- act = purple_menu_action_new(_("Initiate _Chat"),
- PURPLE_CALLBACK(skypeweb_initiate_chat_from_node),
- sa, NULL);
- m = g_list_append(m, act);
- }
- }
-
- return m;
-}
-
-static gulong conversation_updated_signal = 0;
-static gulong chat_conversation_typing_signal = 0;
-
-static void
-skypeweb_login(PurpleAccount *account)
-{
- PurpleConnection *pc = purple_account_get_connection(account);
- SkypeWebAccount *sa = g_new0(SkypeWebAccount, 1);
- PurpleConnectionFlags flags;
-
- purple_connection_set_protocol_data(pc, sa);
-
- flags = purple_connection_get_flags(pc);
- flags |= PURPLE_CONNECTION_FLAG_HTML | PURPLE_CONNECTION_FLAG_NO_BGCOLOR | PURPLE_CONNECTION_FLAG_NO_FONTSIZE;
- purple_connection_set_flags(pc, flags);
-
- if (!SKYPEWEB_BUDDY_IS_MSN(purple_account_get_username(account))) {
- sa->username = g_ascii_strdown(purple_account_get_username(account), -1);
- }
- sa->account = account;
- sa->pc = pc;
- sa->cookie_jar = purple_http_cookie_jar_new();
- sa->sent_messages_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
- sa->messages_host = g_strdup(SKYPEWEB_DEFAULT_MESSAGES_HOST);
- sa->keepalive_pool = purple_http_keepalive_pool_new();
- purple_http_keepalive_pool_set_limit_per_host(sa->keepalive_pool, SKYPEWEB_MAX_CONNECTIONS);
- sa->conns = purple_http_connection_set_new();
-
- if (purple_account_get_bool(account, "alt-login", FALSE)) {
- skypeweb_begin_soapy_login(sa);
- } else {
- if (purple_account_get_string(account, "refresh-token", NULL) && purple_account_get_remember_password(account)) {
- skypeweb_refresh_token_login(sa);
- } else {
- skypeweb_begin_oauth_login(sa);
- }
- }
-
- if (!conversation_updated_signal) {
- conversation_updated_signal = purple_signal_connect(purple_conversations_get_handle(), "conversation-updated", purple_connection_get_protocol(pc), PURPLE_CALLBACK(skypeweb_mark_conv_seen), NULL);
- }
- if (!chat_conversation_typing_signal) {
- chat_conversation_typing_signal = purple_signal_connect(purple_conversations_get_handle(), "chat-conversation-typing", purple_connection_get_protocol(pc), PURPLE_CALLBACK(skypeweb_conv_send_typing), NULL);
- }
-}
-
-static void
-skypeweb_close(PurpleConnection *pc)
-{
- SkypeWebAccount *sa;
- GSList *buddies;
-
- g_return_if_fail(pc != NULL);
-#if !PURPLE_VERSION_CHECK(3, 0, 0)
- purple_connection_set_state(pc, PURPLE_CONNECTION_DISCONNECTING);
-#endif
-
- sa = purple_connection_get_protocol_data(pc);
- g_return_if_fail(sa != NULL);
-
- g_source_remove(sa->authcheck_timeout);
- g_source_remove(sa->poll_timeout);
- g_source_remove(sa->watchdog_timeout);
-
- skypeweb_logout(sa);
-
- purple_debug_info("skypeweb", "destroying incomplete connections\n");
-
- purple_http_connection_set_destroy(sa->conns);
- purple_http_conn_cancel_all(pc);
- purple_http_keepalive_pool_unref(sa->keepalive_pool);
- purple_http_cookie_jar_unref(sa->cookie_jar);
-
- buddies = purple_blist_find_buddies(sa->account, NULL);
- while (buddies != NULL) {
- PurpleBuddy *buddy = buddies->data;
- skypeweb_buddy_free(buddy);
- purple_buddy_set_protocol_data(buddy, NULL);
- buddies = g_slist_delete_link(buddies, buddies);
- }
-
- g_hash_table_destroy(sa->sent_messages_hash);
-
- g_free(sa->vdms_token);
- g_free(sa->messages_host);
- g_free(sa->skype_token);
- g_free(sa->registration_token);
- g_free(sa->endpoint);
- g_free(sa->primary_member_name);
- g_free(sa->self_display_name);
- g_free(sa->username);
- g_free(sa);
-}
-
-gboolean
-skypeweb_offline_message(const PurpleBuddy *buddy)
-{
- return TRUE;
-}
-
-static PurpleCmdRet
-skypeweb_cmd_list(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
-{
- purple_roomlist_show_with_account(purple_conversation_get_account(conv));
-
- return PURPLE_CMD_RET_OK;
-}
-
-static PurpleCmdRet
-skypeweb_cmd_leave(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
-{
- PurpleConnection *pc = NULL;
- int id = -1;
- SkypeWebAccount *sa;
-
- pc = purple_conversation_get_connection(conv);
- id = purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv));
-
- if (pc == NULL || id == -1)
- return PURPLE_CMD_RET_FAILED;
-
- sa = purple_connection_get_protocol_data(pc);
- if (sa == NULL)
- return PURPLE_CMD_RET_FAILED;
-
- skypeweb_chat_kick(pc, id, sa->username);
-
- return PURPLE_CMD_RET_OK;
-}
-
-static PurpleCmdRet
-skypeweb_cmd_kick(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
-{
- PurpleConnection *pc = NULL;
- int id = -1;
-
- pc = purple_conversation_get_connection(conv);
- id = purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv));
-
- if (pc == NULL || id == -1)
- return PURPLE_CMD_RET_FAILED;
-
- skypeweb_chat_kick(pc, id, args[0]);
-
- return PURPLE_CMD_RET_OK;
-}
-
-static PurpleCmdRet
-skypeweb_cmd_invite(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
-{
- PurpleConnection *pc = NULL;
- int id = -1;
-
- pc = purple_conversation_get_connection(conv);
- id = purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv));
-
- if (pc == NULL || id == -1)
- return PURPLE_CMD_RET_FAILED;
-
- skypeweb_chat_invite(pc, id, NULL, args[0]);
-
- return PURPLE_CMD_RET_OK;
-}
-
-static PurpleCmdRet
-skypeweb_cmd_topic(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
-{
- PurpleConnection *pc = NULL;
- PurpleChatConversation *chat;
- int id = -1;
-
- pc = purple_conversation_get_connection(conv);
- chat = PURPLE_CHAT_CONVERSATION(conv);
- id = purple_chat_conversation_get_id(chat);
-
- if (pc == NULL || id == -1)
- return PURPLE_CMD_RET_FAILED;
-
- if (!args || !args[0]) {
- gchar *buf;
- const gchar *topic = purple_chat_conversation_get_topic(chat);
-
- if (topic) {
- gchar *tmp, *tmp2;
- tmp = g_markup_escape_text(topic, -1);
- tmp2 = purple_markup_linkify(tmp);
- buf = g_strdup_printf(_("current topic is: %s"), tmp2);
- g_free(tmp);
- g_free(tmp2);
- } else {
- buf = g_strdup(_("No topic is set"));
- }
-
- purple_conversation_write_system_message(conv, buf, PURPLE_MESSAGE_NO_LOG);
-
- g_free(buf);
- return PURPLE_CMD_RET_OK;
- }
-
- skypeweb_chat_set_topic(pc, id, args[0]);
-
- return PURPLE_CMD_RET_OK;
-}
-
-/******************************************************************************/
-/* Plugin functions */
-/******************************************************************************/
-
-static gboolean
-skypeweb_uri_handler(const char *proto, const char *cmd, GHashTable *params)
-{
- PurpleAccount *account;
- PurpleConnection *pc;
-
- if (!g_str_equal(proto, "skype"))
- return FALSE;
-
- /*skype uri's:
-
- skype: //does nothing
- skype:{buddyname} //open im with {buddyname}
- skype:{buddynames}?chat //open multi-user chat with {buddynames}
- skype:?chat&blob={blob id} //open public multi-user chat with the blob id of {blob id}
- skype:?chat&id={chat id} //open multi-user chat with the id of {chat id}
- skype:{buddyname}?add //add user to buddy list
- skype:{buddyname}?userinfo //get buddy's info
-
- skype:{buddynames}?call //call {buddynames}
- skype:{buddyname}?voicemail //send a voice mail message
- skype:{buddyname}?sendfile //send a file
- */
-
- account = find_acct(SKYPEWEB_PLUGIN_ID, g_hash_table_lookup(params, "account"));
- pc = purple_account_get_connection(account);
-
- if (g_hash_table_lookup(params, "chat")) {
- if (cmd && *cmd) {
- //there'll be a bunch of usernames, seperated by semi-colon
- if (strchr(cmd, ';')) {
- gchar **users = g_strsplit_set(cmd, ";", -1);
- skypeweb_initiate_chat(purple_connection_get_protocol_data(pc), users[0]);
- //TODO the other users
- g_strfreev(users);
- } else {
- PurpleIMConversation *imconv;
- imconv = purple_conversations_find_im_with_account(cmd, account);
- if (!imconv) {
- imconv = purple_im_conversation_new(account, cmd);
- }
- purple_conversation_present(PURPLE_CONVERSATION(imconv));
- }
- } else {
- //probably a public multi-user chat?
- GHashTable *chatinfo = NULL;
- if (g_hash_table_lookup(params, "id")) {
- chatinfo = skypeweb_chat_info_defaults(pc, g_hash_table_lookup(params, "id"));
- } else if (g_hash_table_lookup(params, "blob")) {
- chatinfo = skypeweb_chat_info_defaults(pc, g_hash_table_lookup(params, "blob"));
- }
-
- if (chatinfo != NULL) {
- skypeweb_join_chat(pc, chatinfo);
- g_hash_table_destroy(chatinfo);
- }
- }
- } else if (g_hash_table_lookup(params, "add")) {
- purple_blist_request_add_buddy(account, cmd, "Skype", g_hash_table_lookup(params, "displayname"));
- return TRUE;
- } else if (g_hash_table_lookup(params, "call")) {
-
- } else if (g_hash_table_lookup(params, "userinfo")) {
- skypeweb_get_info(pc, cmd);
- return TRUE;
- } else if (g_hash_table_lookup(params, "voicemail")) {
-
- } else if (g_hash_table_lookup(params, "sendfile")) {
-
- } else if (strlen(cmd)) {
- //supposed to be the same as call?
- }
-
- //we don't know how to handle this
- return FALSE;
-}
-
-#if PURPLE_VERSION_CHECK(3, 0, 0)
- typedef struct _SkypeWebProtocol
- {
- PurpleProtocol parent;
- } SkypeWebProtocol;
-
- typedef struct _SkypeWebProtocolClass
- {
- PurpleProtocolClass parent_class;
- } SkypeWebProtocolClass;
-
- G_MODULE_EXPORT GType skypeweb_protocol_get_type(void);
- #define SKYPEWEB_TYPE_PROTOCOL (skypeweb_protocol_get_type())
- #define SKYPEWEB_PROTOCOL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SKYPEWEB_TYPE_PROTOCOL, SkypeWebProtocol))
- #define SKYPEWEB_PROTOCOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SKYPEWEB_TYPE_PROTOCOL, SkypeWebProtocolClass))
- #define SKYPEWEB_IS_PROTOCOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SKYPEWEB_TYPE_PROTOCOL))
- #define SKYPEWEB_IS_PROTOCOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SKYPEWEB_TYPE_PROTOCOL))
- #define SKYPEWEB_PROTOCOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SKYPEWEB_TYPE_PROTOCOL, SkypeWebProtocolClass))
-
- static PurpleProtocol *skypeweb_protocol;
-#else
-
-// Normally set in core.c in purple3
-void _purple_socket_init(void);
-void _purple_socket_uninit(void);
-
-#endif
-
-
-static gboolean
-plugin_load(PurplePlugin *plugin
-#if PURPLE_VERSION_CHECK(3, 0, 0)
-, GError **error
-#endif
-)
-{
-
-#if !PURPLE_VERSION_CHECK(3, 0, 0)
- _purple_socket_init();
- purple_http_init();
-#endif
-
-
- //leave
- purple_cmd_register("leave", "", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
- PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
- SKYPEWEB_PLUGIN_ID, skypeweb_cmd_leave,
- _("leave: Leave the group chat"), NULL);
- //kick
- purple_cmd_register("kick", "s", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
- PURPLE_CMD_FLAG_PROTOCOL_ONLY,
- SKYPEWEB_PLUGIN_ID, skypeweb_cmd_kick,
- _("kick &lt;user&gt;: Kick a user from the group chat."),
- NULL);
- //add
- purple_cmd_register("add", "s", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
- PURPLE_CMD_FLAG_PROTOCOL_ONLY,
- SKYPEWEB_PLUGIN_ID, skypeweb_cmd_invite,
- _("add &lt;user&gt;: Add a user to the group chat."),
- NULL);
- //topic
- purple_cmd_register("topic", "s", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
- PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
- SKYPEWEB_PLUGIN_ID, skypeweb_cmd_topic,
- _("topic [&lt;new topic&gt;]: View or change the topic"),
- NULL);
- /*
- //call, as in call person
- //kickban
- purple_cmd_register("kickban", "s", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
- PURPLE_CMD_FLAG_PROTOCOL_ONLY,
- SKYPEWEB_PLUGIN_ID, skypeweb_cmd_kickban,
- _("kickban &lt;user&gt; [room]: Kick and ban a user from the room."),
- NULL);
- //setrole
- purple_cmd_register("setrole", "ss", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
- PURPLE_CMD_FLAG_PROTOCOL_ONLY,
- SKYPEWEB_PLUGIN_ID, skypeweb_cmd_setrole,
- _("setrole &lt;user&gt; &lt;MASTER | USER | ADMIN&gt;: Change the role of a user."),
- NULL);
- */
-
- purple_cmd_register("list", "", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
- PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_IM,
- SKYPEWEB_PLUGIN_ID, skypeweb_cmd_list,
- _("list: Display a list of multi-chat group chats you are in."),
- NULL);
-
- purple_signal_connect(purple_get_core(), "uri-handler", plugin, PURPLE_CALLBACK(skypeweb_uri_handler), NULL);
-
- return TRUE;
-}
-
-static gboolean
-plugin_unload(PurplePlugin *plugin
-#if PURPLE_VERSION_CHECK(3, 0, 0)
-, GError **error
-#endif
-)
-{
-#if !PURPLE_VERSION_CHECK(3, 0, 0)
- _purple_socket_uninit();
- purple_http_uninit();
-#endif
- purple_signals_disconnect_by_handle(plugin);
-
- return TRUE;
-}
-
-static GList *
-skypeweb_actions(
-#if !PURPLE_VERSION_CHECK(3, 0, 0)
-PurplePlugin *plugin, gpointer context
-#else
-PurpleConnection *pc
-#endif
-)
-{
- GList *m = NULL;
- PurpleProtocolAction *act;
-
- act = purple_protocol_action_new(_("Search for friends..."), skypeweb_search_users);
- m = g_list_append(m, act);
-
- act = purple_protocol_action_new(_("People you might know..."), skypeweb_contact_suggestions);
- m = g_list_append(m, act);
-
- return m;
-}
-
-#if !PURPLE_VERSION_CHECK(2, 8, 0)
-# define OPT_PROTO_INVITE_MESSAGE 0x00000800
-#endif
-
-#if !PURPLE_VERSION_CHECK(3, 0, 0)
-static void
-plugin_init(PurplePlugin *plugin)
-{
- PurplePluginInfo *info = g_new0(PurplePluginInfo, 1);
- PurplePluginProtocolInfo *prpl_info = g_new0(PurplePluginProtocolInfo, 1);
-#endif
-
-#if PURPLE_VERSION_CHECK(3, 0, 0)
-static void
-skypeweb_protocol_init(PurpleProtocol *prpl_info)
-{
- PurpleProtocol *info = prpl_info;
-#endif
- PurpleAccountOption *typing_type1, *typing_type2, *alt_login;
- PurpleBuddyIconSpec icon_spec = {"jpeg", 0, 0, 96, 96, 0, PURPLE_ICON_SCALE_DISPLAY};
-
- //PurpleProtocol
- info->id = SKYPEWEB_PLUGIN_ID;
- info->name = "Skype (HTTP)";
- prpl_info->options = OPT_PROTO_CHAT_TOPIC | OPT_PROTO_INVITE_MESSAGE /*| OPT_PROTO_IM_IMAGE*/;
- typing_type1 = purple_account_option_bool_new(N_("Show 'Typing' status as system message in chat window."), "show-typing-as-text", FALSE);
- typing_type2 = purple_account_option_bool_new(N_("Show 'Typing' status with 'Voice' icon near buddy name."), "show-typing-as-icon", FALSE);
- alt_login = purple_account_option_bool_new(N_("Use alternative login method"), "alt-login", TRUE);
-
-#if !PURPLE_VERSION_CHECK(3, 0, 0)
- prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, typing_type1);
- prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, typing_type2);
- prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, alt_login);
- prpl_info->icon_spec = icon_spec;
-#else
- prpl_info->account_options = g_list_append(prpl_info->account_options, typing_type1);
- prpl_info->account_options = g_list_append(prpl_info->account_options, typing_type2);
- prpl_info->account_options = g_list_append(prpl_info->account_options, alt_login);
- prpl_info->icon_spec = &icon_spec;
-#endif
-
-#if PURPLE_VERSION_CHECK(3, 0, 0)
-}
-
-static void
-skypeweb_protocol_class_init(PurpleProtocolClass *prpl_info)
-{
-#endif
- //PurpleProtocolClass
- prpl_info->login = skypeweb_login;
- prpl_info->close = skypeweb_close;
- prpl_info->status_types = skypeweb_status_types;
- prpl_info->list_icon = skypeweb_list_icon;
-#if PURPLE_VERSION_CHECK(3, 0, 0)
-}
-
-static void
-skypeweb_protocol_client_iface_init(PurpleProtocolClientIface *prpl_info)
-{
- PurpleProtocolClientIface *info = prpl_info;
-#endif
-
- //PurpleProtocolClientIface
-#if !PURPLE_VERSION_CHECK(3, 0, 0)
- info->actions = skypeweb_actions;
-#else
- info->get_actions = skypeweb_actions;
-#endif
- prpl_info->list_emblem = skypeweb_list_emblem;
- prpl_info->status_text = skypeweb_status_text;
- prpl_info->tooltip_text = skypeweb_tooltip_text;
- prpl_info->blist_node_menu = skypeweb_node_menu;
- prpl_info->buddy_free = skypeweb_buddy_free;
- prpl_info->normalize = purple_normalize_nocase;
- prpl_info->offline_message = skypeweb_offline_message;
- prpl_info->get_account_text_table = NULL; // skypeweb_get_account_text_table;
-#if PURPLE_VERSION_CHECK(3, 0, 0)
-}
-
-static void
-skypeweb_protocol_server_iface_init(PurpleProtocolServerIface *prpl_info)
-{
-#endif
-
- //PurpleProtocolServerIface
- prpl_info->get_info = skypeweb_get_info;
- prpl_info->set_status = skypeweb_set_status;
- prpl_info->set_idle = skypeweb_set_idle;
-#if !PURPLE_VERSION_CHECK(3, 0, 0)
- prpl_info->add_buddy = skypeweb_add_buddy;
-#else
- prpl_info->add_buddy = skypeweb_add_buddy_with_invite;
-#endif
- prpl_info->remove_buddy = skypeweb_buddy_remove;
- prpl_info->group_buddy = skypeweb_fake_group_buddy;
- prpl_info->rename_group = skypeweb_fake_group_rename;
-#if PURPLE_VERSION_CHECK(3, 0, 0)
-}
-
-static void
-skypeweb_protocol_im_iface_init(PurpleProtocolIMIface *prpl_info)
-{
-#endif
-
- //PurpleProtocolIMIface
-#if !PURPLE_VERSION_CHECK(3, 0, 0)
- prpl_info->send_im = skypeweb_send_im;
-#else
- prpl_info->send = skypeweb_send_im;
-#endif
- prpl_info->send_typing = skypeweb_send_typing;
-#if PURPLE_VERSION_CHECK(3, 0, 0)
-}
-
-static void
-skypeweb_protocol_chat_iface_init(PurpleProtocolChatIface *prpl_info)
-{
-#endif
-
- //PurpleProtocolChatIface
-#if !PURPLE_VERSION_CHECK(3, 0, 0)
- prpl_info->chat_info = skypeweb_chat_info;
- prpl_info->chat_info_defaults = skypeweb_chat_info_defaults;
- prpl_info->join_chat = skypeweb_join_chat;
- prpl_info->get_chat_name = skypeweb_get_chat_name;
- prpl_info->chat_invite = skypeweb_chat_invite;
- prpl_info->chat_leave = NULL; //skypeweb_chat_fake_leave;
- prpl_info->chat_send = skypeweb_chat_send;
- prpl_info->set_chat_topic = skypeweb_chat_set_topic;
-#else
- prpl_info->info = skypeweb_chat_info;
- prpl_info->info_defaults = skypeweb_chat_info_defaults;
- prpl_info->join = skypeweb_join_chat;
- prpl_info->get_name = skypeweb_get_chat_name;
- prpl_info->invite = skypeweb_chat_invite;
- prpl_info->leave = NULL; //skypeweb_chat_fake_leave;
- prpl_info->send = skypeweb_chat_send;
- prpl_info->set_topic = skypeweb_chat_set_topic;
-#endif
-#if PURPLE_VERSION_CHECK(3, 0, 0)
-}
-
-static void
-skypeweb_protocol_privacy_iface_init(PurpleProtocolPrivacyIface *prpl_info)
-{
-#endif
-
- //PurpleProtocolPrivacyIface
- prpl_info->add_deny = skypeweb_buddy_block;
- prpl_info->rem_deny = skypeweb_buddy_unblock;
-#if PURPLE_VERSION_CHECK(3, 0, 0)
-}
-
-static void
-skypeweb_protocol_xfer_iface_init(PurpleProtocolXferInterface *prpl_info)
-{
-#endif
-
- //PurpleProtocolXferInterface
- prpl_info->new_xfer = skypeweb_new_xfer;
- prpl_info->send_file = skypeweb_send_file;
-#if !PURPLE_VERSION_CHECK(3, 0, 0)
- prpl_info->can_receive_file = skypeweb_can_receive_file;
-#else
- prpl_info->can_receive = skypeweb_can_receive_file;
-#endif
-
-#if PURPLE_VERSION_CHECK(3, 0, 0)
-}
-
-static void
-skypeweb_protocol_roomlist_iface_init(PurpleProtocolRoomlistIface *prpl_info)
-{
-#endif
-
- //PurpleProtocolRoomlistIface
-#if !PURPLE_VERSION_CHECK(3, 0, 0)
- prpl_info->roomlist_get_list = skypeweb_roomlist_get_list;
-#else
- prpl_info->get_list = skypeweb_roomlist_get_list;
-#endif
-
-#if !PURPLE_VERSION_CHECK(3, 0, 0)
- // Plugin info
- info->magic = PURPLE_PLUGIN_MAGIC;
- info->major_version = 2;
- info->minor_version = MIN(PURPLE_MINOR_VERSION, 8);
- info->type = PURPLE_PLUGIN_PROTOCOL;
- info->priority = PURPLE_PRIORITY_DEFAULT;
- info->version = SKYPEWEB_PLUGIN_VERSION;
- info->summary = N_("Skype for Web Protocol Plugin");
- info->description = N_("Skype for Web Protocol Plugin");
- info->author = "Eion Robb <eionrobb@gmail.com>";
- info->homepage = "http://github.com/EionRobb/skype4pidgin";
- info->load = plugin_load;
- info->unload = plugin_unload;
- info->extra_info = prpl_info;
-
- // Protocol info
- #if PURPLE_MINOR_VERSION >= 5
- prpl_info->struct_size = sizeof(PurplePluginProtocolInfo);
- #endif
- #if PURPLE_MINOR_VERSION >= 8
- prpl_info->add_buddy_with_invite = skypeweb_add_buddy_with_invite;
- #endif
-
- plugin->info = info;
-#endif
-
-}
-
-#if PURPLE_VERSION_CHECK(3, 0, 0)
-
-
-PURPLE_DEFINE_TYPE_EXTENDED(
- SkypeWebProtocol, skypeweb_protocol, PURPLE_TYPE_PROTOCOL, 0,
-
- PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CLIENT_IFACE,
- skypeweb_protocol_client_iface_init)
-
- PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_SERVER_IFACE,
- skypeweb_protocol_server_iface_init)
-
- PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_IM_IFACE,
- skypeweb_protocol_im_iface_init)
-
- PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CHAT_IFACE,
- skypeweb_protocol_chat_iface_init)
-
- PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_PRIVACY_IFACE,
- skypeweb_protocol_privacy_iface_init)
-
- PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_ROOMLIST_IFACE,
- skypeweb_protocol_roomlist_iface_init)
-
- PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_XFER,
- skypeweb_protocol_xfer_iface_init)
-);
-
-static gboolean
-libpurple3_plugin_load(PurplePlugin *plugin, GError **error)
-{
- skypeweb_protocol_register_type(plugin);
- skypeweb_protocol = purple_protocols_add(SKYPEWEB_TYPE_PROTOCOL, error);
- if (!skypeweb_protocol)
- return FALSE;
-
- return plugin_load(plugin, error);
-}
-
-static gboolean
-libpurple3_plugin_unload(PurplePlugin *plugin, GError **error)
-{
- if (!plugin_unload(plugin, error))
- return FALSE;
-
- if (!purple_protocols_remove(skypeweb_protocol, error))
- return FALSE;
-
- return TRUE;
-}
-
-static PurplePluginInfo *
-plugin_query(GError **error)
-{
- return purple_plugin_info_new(
- "id", SKYPEWEB_PLUGIN_ID,
- "name", "SkypeWeb Protocol",
- "version", SKYPEWEB_PLUGIN_VERSION,
- "category", N_("Protocol"),
- "summary", N_("SkypeWeb Protocol Plugin"),
- "description", N_("SkypeWeb Protocol Plugin"),
- "website", "http://github.com/EionRobb/skype4pidgin",
- "abi-version", PURPLE_ABI_VERSION,
- "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL |
- PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD,
- NULL
- );
-}
-
-
-PURPLE_PLUGIN_INIT(skypeweb, plugin_query, libpurple3_plugin_load, libpurple3_plugin_unload);
-#else
-
-static PurplePluginInfo aLovelyBunchOfCoconuts;
-PURPLE_INIT_PLUGIN(skypeweb, plugin_init, aLovelyBunchOfCoconuts);
-#endif
-
+/*
+ * SkypeWeb Plugin for libpurple/Pidgin
+ * Copyright (c) 2014-2020 Eion Robb
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "libskypeweb.h"
+#include "skypeweb_connection.h"
+#include "skypeweb_contacts.h"
+#include "skypeweb_login.h"
+#include "skypeweb_messages.h"
+#include "skypeweb_util.h"
+
+void
+skypeweb_do_all_the_things(SkypeWebAccount *sa)
+{
+ skypeweb_get_vdms_token(sa);
+
+ if (!sa->username) {
+ skypeweb_get_self_details(sa);
+ } else
+ if (sa->registration_token) {
+ skypeweb_get_self_details(sa);
+
+ if (sa->authcheck_timeout)
+ g_source_remove(sa->authcheck_timeout);
+ skypeweb_check_authrequests(sa);
+ sa->authcheck_timeout = g_timeout_add_seconds(120, (GSourceFunc)skypeweb_check_authrequests, sa);
+ purple_connection_set_state(sa->pc, PURPLE_CONNECTION_CONNECTED);
+
+ skypeweb_get_friend_list(sa);
+ skypeweb_poll(sa);
+
+ skype_web_get_offline_history(sa);
+
+ skypeweb_set_status(sa->account, purple_account_get_active_status(sa->account));
+ } else {
+ //Too soon!
+ skypeweb_get_registration_token(sa);
+ }
+
+
+}
+
+
+/******************************************************************************/
+/* PRPL functions */
+/******************************************************************************/
+
+static const char *
+skypeweb_list_icon(PurpleAccount *account, PurpleBuddy *buddy)
+{
+
+ if (buddy != NULL) {
+ const gchar *buddy_name = purple_buddy_get_name(buddy);
+ if (buddy_name && SKYPEWEB_BUDDY_IS_MSN(buddy_name)) {
+ return "msn";
+ }
+ }
+ return "skype";
+}
+
+static gchar *
+skypeweb_status_text(PurpleBuddy *buddy)
+{
+ SkypeWebBuddy *sbuddy = purple_buddy_get_protocol_data(buddy);
+
+ if (sbuddy && sbuddy->mood && *(sbuddy->mood))
+ {
+ gchar *stripped = purple_markup_strip_html(sbuddy->mood);
+ gchar *escaped = g_markup_printf_escaped("%s", stripped);
+
+ g_free(stripped);
+
+ return escaped;
+ }
+
+ return NULL;
+}
+
+void
+skypeweb_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
+{
+ SkypeWebBuddy *sbuddy = purple_buddy_get_protocol_data(buddy);
+
+ if (sbuddy)
+ {
+ PurplePresence *presence;
+ PurpleStatus *status;
+ SkypeWebBuddy *sbuddy = purple_buddy_get_protocol_data(buddy);
+
+ presence = purple_buddy_get_presence(buddy);
+ status = purple_presence_get_active_status(presence);
+ purple_notify_user_info_add_pair_html(user_info, _("Status"), purple_status_get_name(status));
+ if (sbuddy->mood && *sbuddy->mood) {
+ gchar *stripped = purple_markup_strip_html(sbuddy->mood);
+ gchar *escaped = g_markup_printf_escaped("%s", stripped);
+
+ purple_notify_user_info_add_pair_html(user_info, _("Message"), escaped);
+
+ g_free(stripped);
+ g_free(escaped);
+ }
+
+ if (sbuddy->display_name && *sbuddy->display_name) {
+ gchar *escaped = g_markup_printf_escaped("%s", sbuddy->display_name);
+ purple_notify_user_info_add_pair_html(user_info, "Alias", escaped);
+ g_free(escaped);
+ }
+ if (sbuddy->fullname && *sbuddy->fullname) {
+ gchar *escaped = g_markup_printf_escaped("%s", sbuddy->fullname);
+ purple_notify_user_info_add_pair_html(user_info, "Full Name", escaped);
+ g_free(escaped);
+ }
+ }
+}
+
+const gchar *
+skypeweb_list_emblem(PurpleBuddy *buddy)
+{
+ if (buddy != NULL) {
+ //SkypeWebBuddy *sbuddy = purple_buddy_get_protocol_data(buddy);
+ const gchar *buddy_name = purple_buddy_get_name(buddy);
+
+ if (buddy_name && SKYPEWEB_BUDDY_IS_BOT(buddy_name)) {
+ return "bot";
+ }
+ }
+ return NULL;
+}
+
+GList *
+skypeweb_status_types(PurpleAccount *account)
+{
+ GList *types = NULL;
+ PurpleStatusType *status;
+
+ status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, NULL, NULL, FALSE, FALSE, FALSE);
+ types = g_list_append(types, status);
+
+ status = purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE, SKYPEWEB_STATUS_ONLINE, _("Online"), TRUE, TRUE, FALSE, "message", "Mood", purple_value_new(PURPLE_TYPE_STRING), NULL);
+ types = g_list_append(types, status);
+ status = purple_status_type_new_with_attrs(PURPLE_STATUS_AWAY, SKYPEWEB_STATUS_AWAY, _("Away"), TRUE, TRUE, FALSE, "message", "Mood", purple_value_new(PURPLE_TYPE_STRING), NULL);
+ types = g_list_append(types, status);
+// status = purple_status_type_new_with_attrs(PURPLE_STATUS_EXTENDED_AWAY, SKYPEWEB_STATUS_AWAY, _("Not Available"), TRUE, TRUE, FALSE, "message", "Mood", purple_value_new(PURPLE_TYPE_STRING), NULL);
+// types = g_list_append(types, status);
+ status = purple_status_type_new_with_attrs(PURPLE_STATUS_UNAVAILABLE, SKYPEWEB_STATUS_BUSY, _("Do Not Disturb"), TRUE, TRUE, FALSE, "message", "Mood", purple_value_new(PURPLE_TYPE_STRING), NULL);
+ types = g_list_append(types, status);
+ status = purple_status_type_new_with_attrs(PURPLE_STATUS_INVISIBLE, SKYPEWEB_STATUS_HIDDEN, _("Invisible"), TRUE, TRUE, FALSE, "message", "Mood", purple_value_new(PURPLE_TYPE_STRING), NULL);
+ types = g_list_append(types, status);
+ status = purple_status_type_new_with_attrs(PURPLE_STATUS_OFFLINE, SKYPEWEB_STATUS_OFFLINE, _("Offline"), TRUE, TRUE, FALSE, "message", "Mood", purple_value_new(PURPLE_TYPE_STRING), NULL);
+ types = g_list_append(types, status);
+
+ return types;
+}
+
+
+static GList *
+skypeweb_chat_info(PurpleConnection *gc)
+{
+ GList *m = NULL;
+ PurpleProtocolChatEntry *pce;
+
+ pce = g_new0(PurpleProtocolChatEntry, 1);
+ pce->label = _("Skype Name");
+ pce->identifier = "chatname";
+ pce->required = TRUE;
+ m = g_list_append(m, pce);
+
+ /*pce = g_new0(PurpleProtocolChatEntry, 1);
+ pce->label = _("Password");
+ pce->identifier = "password";
+ pce->required = FALSE;
+ m = g_list_append(m, pce);*/
+
+ return m;
+}
+
+static GHashTable *
+skypeweb_chat_info_defaults(PurpleConnection *gc, const char *chatname)
+{
+ GHashTable *defaults;
+ defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
+ if (chatname != NULL)
+ {
+ g_hash_table_insert(defaults, "chatname", g_strdup(chatname));
+ }
+ return defaults;
+}
+
+static gchar *
+skypeweb_get_chat_name(GHashTable *data)
+{
+ gchar *temp;
+
+ if (data == NULL)
+ return NULL;
+
+ temp = g_hash_table_lookup(data, "chatname");
+
+ if (temp == NULL)
+ return NULL;
+
+ return g_strdup(temp);
+}
+
+
+static void
+skypeweb_join_chat(PurpleConnection *pc, GHashTable *data)
+{
+ SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
+ gchar *chatname;
+ gchar *post;
+ GString *url;
+ PurpleChatConversation *chatconv;
+
+ chatname = (gchar *)g_hash_table_lookup(data, "chatname");
+ if (chatname == NULL)
+ {
+ return;
+ }
+
+ chatconv = purple_conversations_find_chat_with_account(chatname, sa->account);
+ if (chatconv != NULL && !purple_chat_conversation_has_left(chatconv)) {
+ purple_conversation_present(PURPLE_CONVERSATION(chatconv));
+ return;
+ }
+
+ url = g_string_new("/v1/threads/");
+ g_string_append_printf(url, "%s", purple_url_encode(chatname));
+ g_string_append(url, "/members/");
+ g_string_append_printf(url, "8:%s", purple_url_encode(sa->username));
+
+ /* Specifying the role does not seem to be required and often result in a users role being
+ * downgraded from admin to user
+ * post = "{\"role\":\"User\"}"; */
+ post = "{}";
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, url->str, post, NULL, NULL, TRUE);
+
+ g_string_free(url, TRUE);
+
+ skypeweb_get_conversation_history(sa, chatname);
+ skypeweb_get_thread_users(sa, chatname);
+
+ chatconv = purple_serv_got_joined_chat(pc, g_str_hash(chatname), chatname);
+ purple_conversation_set_data(PURPLE_CONVERSATION(chatconv), "chatname", g_strdup(chatname));
+
+ purple_conversation_present(PURPLE_CONVERSATION(chatconv));
+}
+
+void
+skypeweb_buddy_free(PurpleBuddy *buddy)
+{
+ SkypeWebBuddy *sbuddy = purple_buddy_get_protocol_data(buddy);
+ if (sbuddy != NULL)
+ {
+ purple_buddy_set_protocol_data(buddy, NULL);
+
+ g_free(sbuddy->skypename);
+ g_free(sbuddy->fullname);
+ g_free(sbuddy->display_name);
+ g_free(sbuddy->avatar_url);
+ g_free(sbuddy->mood);
+
+ g_free(sbuddy);
+ }
+}
+
+void
+skypeweb_fake_group_buddy(PurpleConnection *pc, const char *who, const char *old_group, const char *new_group)
+{
+ // Do nothing to stop the remove+add behaviour
+}
+void
+skypeweb_fake_group_rename(PurpleConnection *pc, const char *old_name, PurpleGroup *group, GList *moved_buddies)
+{
+ // Do nothing to stop the remove+add behaviour
+}
+
+static GList *
+skypeweb_node_menu(PurpleBlistNode *node)
+{
+ GList *m = NULL;
+ PurpleMenuAction *act;
+ PurpleBuddy *buddy;
+ SkypeWebAccount *sa = NULL;
+
+ if(PURPLE_IS_BUDDY(node))
+ {
+ buddy = PURPLE_BUDDY(node);
+ if (purple_buddy_get_protocol_data(buddy)) {
+ SkypeWebBuddy *sbuddy = purple_buddy_get_protocol_data(buddy);
+ sa = sbuddy->sa;
+ }
+ if (sa == NULL) {
+ PurpleConnection *pc = purple_account_get_connection(purple_buddy_get_account(buddy));
+ sa = purple_connection_get_protocol_data(pc);
+ }
+
+ if (sa != NULL) {
+ act = purple_menu_action_new(_("Initiate _Chat"),
+ PURPLE_CALLBACK(skypeweb_initiate_chat_from_node),
+ sa, NULL);
+ m = g_list_append(m, act);
+ }
+ }
+
+ return m;
+}
+
+static gulong conversation_updated_signal = 0;
+static gulong chat_conversation_typing_signal = 0;
+
+static void
+skypeweb_login(PurpleAccount *account)
+{
+ PurpleConnection *pc = purple_account_get_connection(account);
+ SkypeWebAccount *sa = g_new0(SkypeWebAccount, 1);
+ PurpleConnectionFlags flags;
+
+ purple_connection_set_protocol_data(pc, sa);
+
+ flags = purple_connection_get_flags(pc);
+ flags |= PURPLE_CONNECTION_FLAG_HTML | PURPLE_CONNECTION_FLAG_NO_BGCOLOR | PURPLE_CONNECTION_FLAG_NO_FONTSIZE;
+ purple_connection_set_flags(pc, flags);
+
+ if (!SKYPEWEB_BUDDY_IS_MSN(purple_account_get_username(account))) {
+ sa->username = g_ascii_strdown(purple_account_get_username(account), -1);
+ }
+ sa->account = account;
+ sa->pc = pc;
+ sa->cookie_jar = purple_http_cookie_jar_new();
+ sa->sent_messages_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+ sa->messages_host = g_strdup(SKYPEWEB_DEFAULT_MESSAGES_HOST);
+ sa->keepalive_pool = purple_http_keepalive_pool_new();
+ purple_http_keepalive_pool_set_limit_per_host(sa->keepalive_pool, SKYPEWEB_MAX_CONNECTIONS);
+ sa->conns = purple_http_connection_set_new();
+
+ if (purple_account_get_bool(account, "alt-login", FALSE)) {
+ skypeweb_begin_soapy_login(sa);
+ } else {
+ if (purple_account_get_string(account, "refresh-token", NULL) && purple_account_get_remember_password(account)) {
+ skypeweb_refresh_token_login(sa);
+ } else {
+ skypeweb_begin_oauth_login(sa);
+ }
+ }
+
+ if (!conversation_updated_signal) {
+ conversation_updated_signal = purple_signal_connect(purple_conversations_get_handle(), "conversation-updated", purple_connection_get_protocol(pc), PURPLE_CALLBACK(skypeweb_mark_conv_seen), NULL);
+ }
+ if (!chat_conversation_typing_signal) {
+ chat_conversation_typing_signal = purple_signal_connect(purple_conversations_get_handle(), "chat-conversation-typing", purple_connection_get_protocol(pc), PURPLE_CALLBACK(skypeweb_conv_send_typing), NULL);
+ }
+}
+
+static void
+skypeweb_close(PurpleConnection *pc)
+{
+ SkypeWebAccount *sa;
+ GSList *buddies;
+
+ g_return_if_fail(pc != NULL);
+#if !PURPLE_VERSION_CHECK(3, 0, 0)
+ purple_connection_set_state(pc, PURPLE_CONNECTION_DISCONNECTING);
+#endif
+
+ sa = purple_connection_get_protocol_data(pc);
+ g_return_if_fail(sa != NULL);
+
+ g_source_remove(sa->authcheck_timeout);
+ g_source_remove(sa->poll_timeout);
+ g_source_remove(sa->watchdog_timeout);
+
+ skypeweb_logout(sa);
+
+ purple_debug_info("skypeweb", "destroying incomplete connections\n");
+
+ purple_http_connection_set_destroy(sa->conns);
+ purple_http_conn_cancel_all(pc);
+ purple_http_keepalive_pool_unref(sa->keepalive_pool);
+ purple_http_cookie_jar_unref(sa->cookie_jar);
+
+ buddies = purple_blist_find_buddies(sa->account, NULL);
+ while (buddies != NULL) {
+ PurpleBuddy *buddy = buddies->data;
+ skypeweb_buddy_free(buddy);
+ purple_buddy_set_protocol_data(buddy, NULL);
+ buddies = g_slist_delete_link(buddies, buddies);
+ }
+
+ g_hash_table_destroy(sa->sent_messages_hash);
+
+ g_free(sa->vdms_token);
+ g_free(sa->messages_host);
+ g_free(sa->skype_token);
+ g_free(sa->registration_token);
+ g_free(sa->endpoint);
+ g_free(sa->primary_member_name);
+ g_free(sa->self_display_name);
+ g_free(sa->username);
+ g_free(sa);
+}
+
+gboolean
+skypeweb_offline_message(const PurpleBuddy *buddy)
+{
+ return TRUE;
+}
+
+static PurpleCmdRet
+skypeweb_cmd_list(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
+{
+ purple_roomlist_show_with_account(purple_conversation_get_account(conv));
+
+ return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet
+skypeweb_cmd_leave(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
+{
+ PurpleConnection *pc = NULL;
+ int id = -1;
+ SkypeWebAccount *sa;
+
+ pc = purple_conversation_get_connection(conv);
+ id = purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv));
+
+ if (pc == NULL || id == -1)
+ return PURPLE_CMD_RET_FAILED;
+
+ sa = purple_connection_get_protocol_data(pc);
+ if (sa == NULL)
+ return PURPLE_CMD_RET_FAILED;
+
+ skypeweb_chat_kick(pc, id, sa->username);
+
+ return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet
+skypeweb_cmd_kick(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
+{
+ PurpleConnection *pc = NULL;
+ int id = -1;
+
+ pc = purple_conversation_get_connection(conv);
+ id = purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv));
+
+ if (pc == NULL || id == -1)
+ return PURPLE_CMD_RET_FAILED;
+
+ skypeweb_chat_kick(pc, id, args[0]);
+
+ return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet
+skypeweb_cmd_invite(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
+{
+ PurpleConnection *pc = NULL;
+ int id = -1;
+
+ pc = purple_conversation_get_connection(conv);
+ id = purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(conv));
+
+ if (pc == NULL || id == -1)
+ return PURPLE_CMD_RET_FAILED;
+
+ skypeweb_chat_invite(pc, id, NULL, args[0]);
+
+ return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet
+skypeweb_cmd_topic(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
+{
+ PurpleConnection *pc = NULL;
+ PurpleChatConversation *chat;
+ int id = -1;
+
+ pc = purple_conversation_get_connection(conv);
+ chat = PURPLE_CHAT_CONVERSATION(conv);
+ id = purple_chat_conversation_get_id(chat);
+
+ if (pc == NULL || id == -1)
+ return PURPLE_CMD_RET_FAILED;
+
+ if (!args || !args[0]) {
+ gchar *buf;
+ const gchar *topic = purple_chat_conversation_get_topic(chat);
+
+ if (topic) {
+ gchar *tmp, *tmp2;
+ tmp = g_markup_escape_text(topic, -1);
+ tmp2 = purple_markup_linkify(tmp);
+ buf = g_strdup_printf(_("current topic is: %s"), tmp2);
+ g_free(tmp);
+ g_free(tmp2);
+ } else {
+ buf = g_strdup(_("No topic is set"));
+ }
+
+ purple_conversation_write_system_message(conv, buf, PURPLE_MESSAGE_NO_LOG);
+
+ g_free(buf);
+ return PURPLE_CMD_RET_OK;
+ }
+
+ skypeweb_chat_set_topic(pc, id, args[0]);
+
+ return PURPLE_CMD_RET_OK;
+}
+
+/******************************************************************************/
+/* Plugin functions */
+/******************************************************************************/
+
+static gboolean
+skypeweb_uri_handler(const char *proto, const char *cmd, GHashTable *params)
+{
+ PurpleAccount *account;
+ PurpleConnection *pc;
+
+ if (!g_str_equal(proto, "skype"))
+ return FALSE;
+
+ /*skype uri's:
+
+ skype: //does nothing
+ skype:{buddyname} //open im with {buddyname}
+ skype:{buddynames}?chat //open multi-user chat with {buddynames}
+ skype:?chat&blob={blob id} //open public multi-user chat with the blob id of {blob id}
+ skype:?chat&id={chat id} //open multi-user chat with the id of {chat id}
+ skype:{buddyname}?add //add user to buddy list
+ skype:{buddyname}?userinfo //get buddy's info
+
+ skype:{buddynames}?call //call {buddynames}
+ skype:{buddyname}?voicemail //send a voice mail message
+ skype:{buddyname}?sendfile //send a file
+ */
+
+ account = find_acct(SKYPEWEB_PLUGIN_ID, g_hash_table_lookup(params, "account"));
+ pc = purple_account_get_connection(account);
+
+ if (g_hash_table_lookup(params, "chat")) {
+ if (cmd && *cmd) {
+ //there'll be a bunch of usernames, seperated by semi-colon
+ if (strchr(cmd, ';')) {
+ gchar **users = g_strsplit_set(cmd, ";", -1);
+ skypeweb_initiate_chat(purple_connection_get_protocol_data(pc), users[0]);
+ //TODO the other users
+ g_strfreev(users);
+ } else {
+ PurpleIMConversation *imconv;
+ imconv = purple_conversations_find_im_with_account(cmd, account);
+ if (!imconv) {
+ imconv = purple_im_conversation_new(account, cmd);
+ }
+ purple_conversation_present(PURPLE_CONVERSATION(imconv));
+ }
+ } else {
+ //probably a public multi-user chat?
+ GHashTable *chatinfo = NULL;
+ if (g_hash_table_lookup(params, "id")) {
+ chatinfo = skypeweb_chat_info_defaults(pc, g_hash_table_lookup(params, "id"));
+ } else if (g_hash_table_lookup(params, "blob")) {
+ chatinfo = skypeweb_chat_info_defaults(pc, g_hash_table_lookup(params, "blob"));
+ }
+
+ if (chatinfo != NULL) {
+ skypeweb_join_chat(pc, chatinfo);
+ g_hash_table_destroy(chatinfo);
+ }
+ }
+ } else if (g_hash_table_lookup(params, "add")) {
+ purple_blist_request_add_buddy(account, cmd, "Skype", g_hash_table_lookup(params, "displayname"));
+ return TRUE;
+ } else if (g_hash_table_lookup(params, "call")) {
+
+ } else if (g_hash_table_lookup(params, "userinfo")) {
+ skypeweb_get_info(pc, cmd);
+ return TRUE;
+ } else if (g_hash_table_lookup(params, "voicemail")) {
+
+ } else if (g_hash_table_lookup(params, "sendfile")) {
+
+ } else if (strlen(cmd)) {
+ //supposed to be the same as call?
+ }
+
+ //we don't know how to handle this
+ return FALSE;
+}
+
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+ typedef struct _SkypeWebProtocol
+ {
+ PurpleProtocol parent;
+ } SkypeWebProtocol;
+
+ typedef struct _SkypeWebProtocolClass
+ {
+ PurpleProtocolClass parent_class;
+ } SkypeWebProtocolClass;
+
+ G_MODULE_EXPORT GType skypeweb_protocol_get_type(void);
+ #define SKYPEWEB_TYPE_PROTOCOL (skypeweb_protocol_get_type())
+ #define SKYPEWEB_PROTOCOL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SKYPEWEB_TYPE_PROTOCOL, SkypeWebProtocol))
+ #define SKYPEWEB_PROTOCOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SKYPEWEB_TYPE_PROTOCOL, SkypeWebProtocolClass))
+ #define SKYPEWEB_IS_PROTOCOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SKYPEWEB_TYPE_PROTOCOL))
+ #define SKYPEWEB_IS_PROTOCOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SKYPEWEB_TYPE_PROTOCOL))
+ #define SKYPEWEB_PROTOCOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SKYPEWEB_TYPE_PROTOCOL, SkypeWebProtocolClass))
+
+ static PurpleProtocol *skypeweb_protocol;
+#else
+
+// Normally set in core.c in purple3
+void _purple_socket_init(void);
+void _purple_socket_uninit(void);
+
+#endif
+
+
+static gboolean
+plugin_load(PurplePlugin *plugin
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+, GError **error
+#endif
+)
+{
+
+#if !PURPLE_VERSION_CHECK(3, 0, 0)
+ _purple_socket_init();
+ purple_http_init();
+#endif
+
+
+ //leave
+ purple_cmd_register("leave", "", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
+ PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
+ SKYPEWEB_PLUGIN_ID, skypeweb_cmd_leave,
+ _("leave: Leave the group chat"), NULL);
+ //kick
+ purple_cmd_register("kick", "s", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
+ PURPLE_CMD_FLAG_PROTOCOL_ONLY,
+ SKYPEWEB_PLUGIN_ID, skypeweb_cmd_kick,
+ _("kick &lt;user&gt;: Kick a user from the group chat."),
+ NULL);
+ //add
+ purple_cmd_register("add", "s", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
+ PURPLE_CMD_FLAG_PROTOCOL_ONLY,
+ SKYPEWEB_PLUGIN_ID, skypeweb_cmd_invite,
+ _("add &lt;user&gt;: Add a user to the group chat."),
+ NULL);
+ //topic
+ purple_cmd_register("topic", "s", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
+ PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
+ SKYPEWEB_PLUGIN_ID, skypeweb_cmd_topic,
+ _("topic [&lt;new topic&gt;]: View or change the topic"),
+ NULL);
+ /*
+ //call, as in call person
+ //kickban
+ purple_cmd_register("kickban", "s", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
+ PURPLE_CMD_FLAG_PROTOCOL_ONLY,
+ SKYPEWEB_PLUGIN_ID, skypeweb_cmd_kickban,
+ _("kickban &lt;user&gt; [room]: Kick and ban a user from the room."),
+ NULL);
+ //setrole
+ purple_cmd_register("setrole", "ss", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
+ PURPLE_CMD_FLAG_PROTOCOL_ONLY,
+ SKYPEWEB_PLUGIN_ID, skypeweb_cmd_setrole,
+ _("setrole &lt;user&gt; &lt;MASTER | USER | ADMIN&gt;: Change the role of a user."),
+ NULL);
+ */
+
+ purple_cmd_register("list", "", PURPLE_CMD_P_PLUGIN, PURPLE_CMD_FLAG_CHAT |
+ PURPLE_CMD_FLAG_PROTOCOL_ONLY | PURPLE_CMD_FLAG_IM,
+ SKYPEWEB_PLUGIN_ID, skypeweb_cmd_list,
+ _("list: Display a list of multi-chat group chats you are in."),
+ NULL);
+
+ purple_signal_connect(purple_get_core(), "uri-handler", plugin, PURPLE_CALLBACK(skypeweb_uri_handler), NULL);
+
+ return TRUE;
+}
+
+static gboolean
+plugin_unload(PurplePlugin *plugin
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+, GError **error
+#endif
+)
+{
+#if !PURPLE_VERSION_CHECK(3, 0, 0)
+ _purple_socket_uninit();
+ purple_http_uninit();
+#endif
+ purple_signals_disconnect_by_handle(plugin);
+
+ return TRUE;
+}
+
+static GList *
+skypeweb_actions(
+#if !PURPLE_VERSION_CHECK(3, 0, 0)
+PurplePlugin *plugin, gpointer context
+#else
+PurpleConnection *pc
+#endif
+)
+{
+ GList *m = NULL;
+ PurpleProtocolAction *act;
+
+ act = purple_protocol_action_new(_("Search for friends..."), skypeweb_search_users);
+ m = g_list_append(m, act);
+
+ act = purple_protocol_action_new(_("People you might know..."), skypeweb_contact_suggestions);
+ m = g_list_append(m, act);
+
+ return m;
+}
+
+#if !PURPLE_VERSION_CHECK(2, 8, 0)
+# define OPT_PROTO_INVITE_MESSAGE 0x00000800
+#endif
+
+#if !PURPLE_VERSION_CHECK(3, 0, 0)
+static void
+plugin_init(PurplePlugin *plugin)
+{
+ PurplePluginInfo *info = g_new0(PurplePluginInfo, 1);
+ PurplePluginProtocolInfo *prpl_info = g_new0(PurplePluginProtocolInfo, 1);
+#endif
+
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+static void
+skypeweb_protocol_init(PurpleProtocol *prpl_info)
+{
+ PurpleProtocol *info = prpl_info;
+#endif
+ PurpleAccountOption *typing_type1, *typing_type2, *alt_login;
+ PurpleBuddyIconSpec icon_spec = {"jpeg", 0, 0, 96, 96, 0, PURPLE_ICON_SCALE_DISPLAY};
+
+ //PurpleProtocol
+ info->id = SKYPEWEB_PLUGIN_ID;
+ info->name = "Skype (HTTP)";
+ prpl_info->options = OPT_PROTO_CHAT_TOPIC | OPT_PROTO_INVITE_MESSAGE /*| OPT_PROTO_IM_IMAGE*/;
+ typing_type1 = purple_account_option_bool_new(N_("Show 'Typing' status as system message in chat window."), "show-typing-as-text", FALSE);
+ typing_type2 = purple_account_option_bool_new(N_("Show 'Typing' status with 'Voice' icon near buddy name."), "show-typing-as-icon", FALSE);
+ alt_login = purple_account_option_bool_new(N_("Use alternative login method"), "alt-login", TRUE);
+
+#if !PURPLE_VERSION_CHECK(3, 0, 0)
+ prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, typing_type1);
+ prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, typing_type2);
+ prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, alt_login);
+ prpl_info->icon_spec = icon_spec;
+#else
+ prpl_info->account_options = g_list_append(prpl_info->account_options, typing_type1);
+ prpl_info->account_options = g_list_append(prpl_info->account_options, typing_type2);
+ prpl_info->account_options = g_list_append(prpl_info->account_options, alt_login);
+ prpl_info->icon_spec = &icon_spec;
+#endif
+
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+}
+
+static void
+skypeweb_protocol_class_init(PurpleProtocolClass *prpl_info)
+{
+#endif
+ //PurpleProtocolClass
+ prpl_info->login = skypeweb_login;
+ prpl_info->close = skypeweb_close;
+ prpl_info->status_types = skypeweb_status_types;
+ prpl_info->list_icon = skypeweb_list_icon;
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+}
+
+static void
+skypeweb_protocol_client_iface_init(PurpleProtocolClientIface *prpl_info)
+{
+ PurpleProtocolClientIface *info = prpl_info;
+#endif
+
+ //PurpleProtocolClientIface
+#if !PURPLE_VERSION_CHECK(3, 0, 0)
+ info->actions = skypeweb_actions;
+#else
+ info->get_actions = skypeweb_actions;
+#endif
+ prpl_info->list_emblem = skypeweb_list_emblem;
+ prpl_info->status_text = skypeweb_status_text;
+ prpl_info->tooltip_text = skypeweb_tooltip_text;
+ prpl_info->blist_node_menu = skypeweb_node_menu;
+ prpl_info->buddy_free = skypeweb_buddy_free;
+ prpl_info->normalize = purple_normalize_nocase;
+ prpl_info->offline_message = skypeweb_offline_message;
+ prpl_info->get_account_text_table = NULL; // skypeweb_get_account_text_table;
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+}
+
+static void
+skypeweb_protocol_server_iface_init(PurpleProtocolServerIface *prpl_info)
+{
+#endif
+
+ //PurpleProtocolServerIface
+ prpl_info->get_info = skypeweb_get_info;
+ prpl_info->set_status = skypeweb_set_status;
+ prpl_info->set_idle = skypeweb_set_idle;
+#if !PURPLE_VERSION_CHECK(3, 0, 0)
+ prpl_info->add_buddy = skypeweb_add_buddy;
+#else
+ prpl_info->add_buddy = skypeweb_add_buddy_with_invite;
+#endif
+ prpl_info->remove_buddy = skypeweb_buddy_remove;
+ prpl_info->group_buddy = skypeweb_fake_group_buddy;
+ prpl_info->rename_group = skypeweb_fake_group_rename;
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+}
+
+static void
+skypeweb_protocol_im_iface_init(PurpleProtocolIMIface *prpl_info)
+{
+#endif
+
+ //PurpleProtocolIMIface
+#if !PURPLE_VERSION_CHECK(3, 0, 0)
+ prpl_info->send_im = skypeweb_send_im;
+#else
+ prpl_info->send = skypeweb_send_im;
+#endif
+ prpl_info->send_typing = skypeweb_send_typing;
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+}
+
+static void
+skypeweb_protocol_chat_iface_init(PurpleProtocolChatIface *prpl_info)
+{
+#endif
+
+ //PurpleProtocolChatIface
+#if !PURPLE_VERSION_CHECK(3, 0, 0)
+ prpl_info->chat_info = skypeweb_chat_info;
+ prpl_info->chat_info_defaults = skypeweb_chat_info_defaults;
+ prpl_info->join_chat = skypeweb_join_chat;
+ prpl_info->get_chat_name = skypeweb_get_chat_name;
+ prpl_info->chat_invite = skypeweb_chat_invite;
+ prpl_info->chat_leave = NULL; //skypeweb_chat_fake_leave;
+ prpl_info->chat_send = skypeweb_chat_send;
+ prpl_info->set_chat_topic = skypeweb_chat_set_topic;
+#else
+ prpl_info->info = skypeweb_chat_info;
+ prpl_info->info_defaults = skypeweb_chat_info_defaults;
+ prpl_info->join = skypeweb_join_chat;
+ prpl_info->get_name = skypeweb_get_chat_name;
+ prpl_info->invite = skypeweb_chat_invite;
+ prpl_info->leave = NULL; //skypeweb_chat_fake_leave;
+ prpl_info->send = skypeweb_chat_send;
+ prpl_info->set_topic = skypeweb_chat_set_topic;
+#endif
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+}
+
+static void
+skypeweb_protocol_privacy_iface_init(PurpleProtocolPrivacyIface *prpl_info)
+{
+#endif
+
+ //PurpleProtocolPrivacyIface
+ prpl_info->add_deny = skypeweb_buddy_block;
+ prpl_info->rem_deny = skypeweb_buddy_unblock;
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+}
+
+static void
+skypeweb_protocol_xfer_iface_init(PurpleProtocolXferInterface *prpl_info)
+{
+#endif
+
+ //PurpleProtocolXferInterface
+ prpl_info->new_xfer = skypeweb_new_xfer;
+ prpl_info->send_file = skypeweb_send_file;
+#if !PURPLE_VERSION_CHECK(3, 0, 0)
+ prpl_info->can_receive_file = skypeweb_can_receive_file;
+#else
+ prpl_info->can_receive = skypeweb_can_receive_file;
+#endif
+
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+}
+
+static void
+skypeweb_protocol_roomlist_iface_init(PurpleProtocolRoomlistIface *prpl_info)
+{
+#endif
+
+ //PurpleProtocolRoomlistIface
+#if !PURPLE_VERSION_CHECK(3, 0, 0)
+ prpl_info->roomlist_get_list = skypeweb_roomlist_get_list;
+#else
+ prpl_info->get_list = skypeweb_roomlist_get_list;
+#endif
+
+#if !PURPLE_VERSION_CHECK(3, 0, 0)
+ // Plugin info
+ info->magic = PURPLE_PLUGIN_MAGIC;
+ info->major_version = 2;
+ info->minor_version = MIN(PURPLE_MINOR_VERSION, 8);
+ info->type = PURPLE_PLUGIN_PROTOCOL;
+ info->priority = PURPLE_PRIORITY_DEFAULT;
+ info->version = SKYPEWEB_PLUGIN_VERSION;
+ info->summary = N_("Skype for Web Protocol Plugin");
+ info->description = N_("Skype for Web Protocol Plugin");
+ info->author = "Eion Robb <eionrobb@gmail.com>";
+ info->homepage = "http://github.com/EionRobb/skype4pidgin";
+ info->load = plugin_load;
+ info->unload = plugin_unload;
+ info->extra_info = prpl_info;
+
+ // Protocol info
+ #if PURPLE_MINOR_VERSION >= 5
+ prpl_info->struct_size = sizeof(PurplePluginProtocolInfo);
+ #endif
+ #if PURPLE_MINOR_VERSION >= 8
+ prpl_info->add_buddy_with_invite = skypeweb_add_buddy_with_invite;
+ #endif
+
+ plugin->info = info;
+#endif
+
+}
+
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+
+
+PURPLE_DEFINE_TYPE_EXTENDED(
+ SkypeWebProtocol, skypeweb_protocol, PURPLE_TYPE_PROTOCOL, 0,
+
+ PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CLIENT_IFACE,
+ skypeweb_protocol_client_iface_init)
+
+ PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_SERVER_IFACE,
+ skypeweb_protocol_server_iface_init)
+
+ PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_IM_IFACE,
+ skypeweb_protocol_im_iface_init)
+
+ PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_CHAT_IFACE,
+ skypeweb_protocol_chat_iface_init)
+
+ PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_PRIVACY_IFACE,
+ skypeweb_protocol_privacy_iface_init)
+
+ PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_ROOMLIST_IFACE,
+ skypeweb_protocol_roomlist_iface_init)
+
+ PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_XFER,
+ skypeweb_protocol_xfer_iface_init)
+);
+
+static gboolean
+libpurple3_plugin_load(PurplePlugin *plugin, GError **error)
+{
+ skypeweb_protocol_register_type(plugin);
+ skypeweb_protocol = purple_protocols_add(SKYPEWEB_TYPE_PROTOCOL, error);
+ if (!skypeweb_protocol)
+ return FALSE;
+
+ return plugin_load(plugin, error);
+}
+
+static gboolean
+libpurple3_plugin_unload(PurplePlugin *plugin, GError **error)
+{
+ if (!plugin_unload(plugin, error))
+ return FALSE;
+
+ if (!purple_protocols_remove(skypeweb_protocol, error))
+ return FALSE;
+
+ return TRUE;
+}
+
+static PurplePluginInfo *
+plugin_query(GError **error)
+{
+ return purple_plugin_info_new(
+ "id", SKYPEWEB_PLUGIN_ID,
+ "name", "SkypeWeb Protocol",
+ "version", SKYPEWEB_PLUGIN_VERSION,
+ "category", N_("Protocol"),
+ "summary", N_("SkypeWeb Protocol Plugin"),
+ "description", N_("SkypeWeb Protocol Plugin"),
+ "website", "http://github.com/EionRobb/skype4pidgin",
+ "abi-version", PURPLE_ABI_VERSION,
+ "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL |
+ PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD,
+ NULL
+ );
+}
+
+
+PURPLE_PLUGIN_INIT(skypeweb, plugin_query, libpurple3_plugin_load, libpurple3_plugin_unload);
+#else
+
+static PurplePluginInfo aLovelyBunchOfCoconuts;
+PURPLE_INIT_PLUGIN(skypeweb, plugin_init, aLovelyBunchOfCoconuts);
+#endif
+
diff --git a/skypeweb/purple2compat/http.c b/skypeweb/purple2compat/http.c
index f3692ed..4da18c6 100644
--- a/skypeweb/purple2compat/http.c
+++ b/skypeweb/purple2compat/http.c
@@ -430,7 +430,7 @@ purple_http_gz_put(PurpleHttpGzStream *gzs, const gchar *buf, gsize len)
purple_debug_error("http",
"Decompression failed (%d): %s\n", gzres,
zs->msg);
- gzs->failed = TRUE;
+ gzs->failed = TRUE;
g_string_free(ret, TRUE);
return NULL;
}
@@ -3222,7 +3222,7 @@ void purple_http_uninit(void)
purple_http_re_url_host = NULL;
g_regex_unref(purple_http_re_rfc1123);
purple_http_re_rfc1123 = NULL;
-
+
if (purple_http_hc_list != NULL)
g_list_foreach(purple_http_hc_list, purple_http_foreach_conn_cancel,
NULL);
diff --git a/skypeweb/purple2compat/internal.h b/skypeweb/purple2compat/internal.h
index b59053e..ac7628f 100644
--- a/skypeweb/purple2compat/internal.h
+++ b/skypeweb/purple2compat/internal.h
@@ -1,17 +1,17 @@
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#ifdef _WIN32
-#include "win32/win32dep.h"
-#endif
-
-#include "../purplecompat.h"
-
-#ifndef N_
-# define N_(a) (a)
-#endif
-
-#ifndef _
-# define _(a) (a)
-#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#ifdef _WIN32
+#include "win32/win32dep.h"
+#endif
+
+#include "../purplecompat.h"
+
+#ifndef N_
+# define N_(a) (a)
+#endif
+
+#ifndef _
+# define _(a) (a)
+#endif
diff --git a/skypeweb/purple2compat/purple-socket.c b/skypeweb/purple2compat/purple-socket.c
index fe64f89..6134d8b 100644
--- a/skypeweb/purple2compat/purple-socket.c
+++ b/skypeweb/purple2compat/purple-socket.c
@@ -77,10 +77,10 @@ handle_remove(PurpleSocket *ps)
PurpleConnection *gc = ps->gc;
GSList *l;
- l = g_hash_table_lookup(handles, gc);
+ l = g_hash_table_lookup(handles, gc);
if (l != NULL) {
l = g_slist_remove(l, ps);
- g_hash_table_insert(handles, gc, l);
+ g_hash_table_insert(handles, gc, l);
}
}
diff --git a/skypeweb/skypeweb_contacts.c b/skypeweb/skypeweb_contacts.c
index aab23e4..ecaab9e 100644
--- a/skypeweb/skypeweb_contacts.c
+++ b/skypeweb/skypeweb_contacts.c
@@ -1,682 +1,682 @@
-/*
- * SkypeWeb Plugin for libpurple/Pidgin
- * Copyright (c) 2014-2020 Eion Robb
- *
- * 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 3 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, see <http://www.gnu.org/licenses/>.
- */
-
-
-#include "skypeweb_contacts.h"
-#include "skypeweb_connection.h"
-#include "skypeweb_messages.h"
-#include "skypeweb_util.h"
-
-#include "http.h"
-#include "xfer.h"
-#include "image-store.h"
-
-static void purple_conversation_write_img_message(
- PurpleConversation *conv, const char* who, const gchar *msg,
- PurpleMessageFlags flags, time_t ts) {
- PurpleMessage *pmsg;
-
- if (flags & PURPLE_MESSAGE_SEND) {
- pmsg = purple_message_new_outgoing(who, msg, flags);
- purple_message_set_time(pmsg, ts);
- } else {
- pmsg = purple_message_new_incoming(who, msg, flags, ts);
- }
-
- purple_conversation_write_message(conv, pmsg);
- purple_message_destroy(pmsg);
-}
-
-// Check that the conversation hasn't been closed
-static gboolean
-purple_conversation_is_valid(PurpleConversation *conv)
-{
- GList *convs = purple_conversations_get_all();
-
- return (g_list_find(convs, conv) != NULL);
-}
-
-
-typedef struct {
- PurpleXfer *xfer;
- JsonObject *info;
- gchar *from;
- gchar *url;
- gchar *id;
- SkypeWebAccount *sa;
-} SkypeWebFileTransfer;
-
-static guint active_icon_downloads = 0;
-
-static void
-skypeweb_get_icon_cb(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
-{
- PurpleHttpRequest *request = purple_http_conn_get_request(http_conn);
- PurpleBuddy *buddy = user_data;
- const gchar *url = purple_http_request_get_url(request);
- const gchar *data;
- gsize len;
-
- active_icon_downloads--;
-
- if (!buddy || !purple_http_response_is_successful(response)) {
- return;
- }
-
- data = purple_http_response_get_data(response, &len);
-
- if (!len || !*data) {
- return;
- }
-
- purple_buddy_icons_set_for_user(purple_buddy_get_account(buddy), purple_buddy_get_name(buddy), g_memdup(data, len), len, url);
-
-}
-
-static void
-skypeweb_get_icon_now(PurpleBuddy *buddy)
-{
- SkypeWebBuddy *sbuddy;
- SkypeWebAccount *sa;
- gchar *url;
-
- purple_debug_info("skypeweb", "getting new buddy icon for %s\n", purple_buddy_get_name(buddy));
-
- sbuddy = purple_buddy_get_protocol_data(buddy);
-
- if (!sbuddy || !sbuddy->sa || !sbuddy->sa->pc)
- return;
-
- if (sbuddy->avatar_url && sbuddy->avatar_url[0]) {
- url = g_strdup(sbuddy->avatar_url);
- } else {
- url = g_strdup_printf("https://avatar.skype.com/v1/avatars/%s/public?returnDefaultImage=false", purple_url_encode(purple_buddy_get_name(buddy)));
- }
-
- sa = sbuddy->sa;
-
- purple_http_get(sa->pc, skypeweb_get_icon_cb, buddy, url);
- g_free(url);
-
- active_icon_downloads++;
-}
-
-static gboolean
-skypeweb_get_icon_queuepop(gpointer data)
-{
- PurpleBuddy *buddy = data;
-
- // Only allow 4 simultaneous downloads
- if (active_icon_downloads > 4)
- return TRUE;
-
- skypeweb_get_icon_now(buddy);
- return FALSE;
-}
-
-void
-skypeweb_get_icon(PurpleBuddy *buddy)
-{
- if (!buddy) return;
-
- g_timeout_add(100, skypeweb_get_icon_queuepop, (gpointer)buddy);
-}
-
-typedef struct SkypeImgMsgContext_ {
- PurpleConversation *conv;
- time_t composetimestamp;
+/*
+ * SkypeWeb Plugin for libpurple/Pidgin
+ * Copyright (c) 2014-2020 Eion Robb
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "skypeweb_contacts.h"
+#include "skypeweb_connection.h"
+#include "skypeweb_messages.h"
+#include "skypeweb_util.h"
+
+#include "http.h"
+#include "xfer.h"
+#include "image-store.h"
+
+static void purple_conversation_write_img_message(
+ PurpleConversation *conv, const char* who, const gchar *msg,
+ PurpleMessageFlags flags, time_t ts) {
+ PurpleMessage *pmsg;
+
+ if (flags & PURPLE_MESSAGE_SEND) {
+ pmsg = purple_message_new_outgoing(who, msg, flags);
+ purple_message_set_time(pmsg, ts);
+ } else {
+ pmsg = purple_message_new_incoming(who, msg, flags, ts);
+ }
+
+ purple_conversation_write_message(conv, pmsg);
+ purple_message_destroy(pmsg);
+}
+
+// Check that the conversation hasn't been closed
+static gboolean
+purple_conversation_is_valid(PurpleConversation *conv)
+{
+ GList *convs = purple_conversations_get_all();
+
+ return (g_list_find(convs, conv) != NULL);
+}
+
+
+typedef struct {
+ PurpleXfer *xfer;
+ JsonObject *info;
+ gchar *from;
+ gchar *url;
+ gchar *id;
+ SkypeWebAccount *sa;
+} SkypeWebFileTransfer;
+
+static guint active_icon_downloads = 0;
+
+static void
+skypeweb_get_icon_cb(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
+{
+ PurpleHttpRequest *request = purple_http_conn_get_request(http_conn);
+ PurpleBuddy *buddy = user_data;
+ const gchar *url = purple_http_request_get_url(request);
+ const gchar *data;
+ gsize len;
+
+ active_icon_downloads--;
+
+ if (!buddy || !purple_http_response_is_successful(response)) {
+ return;
+ }
+
+ data = purple_http_response_get_data(response, &len);
+
+ if (!len || !*data) {
+ return;
+ }
+
+ purple_buddy_icons_set_for_user(purple_buddy_get_account(buddy), purple_buddy_get_name(buddy), g_memdup(data, len), len, url);
+
+}
+
+static void
+skypeweb_get_icon_now(PurpleBuddy *buddy)
+{
+ SkypeWebBuddy *sbuddy;
+ SkypeWebAccount *sa;
+ gchar *url;
+
+ purple_debug_info("skypeweb", "getting new buddy icon for %s\n", purple_buddy_get_name(buddy));
+
+ sbuddy = purple_buddy_get_protocol_data(buddy);
+
+ if (!sbuddy || !sbuddy->sa || !sbuddy->sa->pc)
+ return;
+
+ if (sbuddy->avatar_url && sbuddy->avatar_url[0]) {
+ url = g_strdup(sbuddy->avatar_url);
+ } else {
+ url = g_strdup_printf("https://avatar.skype.com/v1/avatars/%s/public?returnDefaultImage=false", purple_url_encode(purple_buddy_get_name(buddy)));
+ }
+
+ sa = sbuddy->sa;
+
+ purple_http_get(sa->pc, skypeweb_get_icon_cb, buddy, url);
+ g_free(url);
+
+ active_icon_downloads++;
+}
+
+static gboolean
+skypeweb_get_icon_queuepop(gpointer data)
+{
+ PurpleBuddy *buddy = data;
+
+ // Only allow 4 simultaneous downloads
+ if (active_icon_downloads > 4)
+ return TRUE;
+
+ skypeweb_get_icon_now(buddy);
+ return FALSE;
+}
+
+void
+skypeweb_get_icon(PurpleBuddy *buddy)
+{
+ if (!buddy) return;
+
+ g_timeout_add(100, skypeweb_get_icon_queuepop, (gpointer)buddy);
+}
+
+typedef struct SkypeImgMsgContext_ {
+ PurpleConversation *conv;
+ time_t composetimestamp;
gchar* from;
-} SkypeImgMsgContext;
-
-static void
-skypeweb_got_imagemessage(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
-{
- gint icon_id;
- gchar *msg_tmp;
- const gchar *url_text;
- gsize len;
- PurpleImage *image;
-
- SkypeImgMsgContext *ctx = user_data;
- PurpleConversation *conv = ctx->conv;
- time_t ts = ctx->composetimestamp;
- gchar* ctx_from = ctx->from;
- ctx->from = NULL;
- g_free(ctx);
-
- // Conversation could have been closed before we retrieved the image
- if (!purple_conversation_is_valid(conv)) {
- return;
- }
-
- url_text = purple_http_response_get_data(response, &len);
-
- if (!url_text || !len || url_text[0] == '{' || url_text[0] == '<')
- return;
-
- if (!purple_http_response_is_successful(response))
- return;
-
- image = purple_image_new_from_data(g_memdup(url_text, len), len);
- icon_id = purple_image_store_add(image);
- msg_tmp = g_strdup_printf("<img id='%d'>", icon_id);
- purple_conversation_write_img_message(conv, ctx_from, msg_tmp, PURPLE_MESSAGE_NO_LOG | PURPLE_MESSAGE_IMAGES, ts);
- g_free(msg_tmp);
- g_free(ctx_from);
-}
-
-static const char*
-skypeweb_uri_type_name(SkypeWebURIType uri_type) {
- switch (uri_type) {
- case SKYPEWEB_URI_TYPE_IMAGE:
- return "image";
- case SKYPEWEB_URI_TYPE_VIDEO:
- return "video";
- default:
- return "(unknown)";
- }
-}
-void
-skypeweb_download_uri_to_conv(SkypeWebAccount *sa, const gchar *uri, SkypeWebURIType uri_type, PurpleConversation *conv, time_t ts, const gchar* from)
-{
- gchar *url, *text;
- PurpleHttpRequest *request;
-
- switch (uri_type) {
- case SKYPEWEB_URI_TYPE_IMAGE:
- url = purple_strreplace(uri, "imgt1", "imgpsh_fullsize");
- break;
- case SKYPEWEB_URI_TYPE_VIDEO:
- url = purple_strreplace(uri, "thumbnail", "video");
- break;
- default:
- url = g_strdup(uri);
- break;
- }
- if (purple_strequal(purple_core_get_ui(), "BitlBee")) {
- // Bitlbee doesn't support images, so just plop a url to the image instead
-
- if (PURPLE_IS_IM_CONVERSATION(conv)) {
- purple_serv_got_im(sa->pc, from, url, PURPLE_MESSAGE_RECV, ts);
- } else if (PURPLE_IS_CHAT_CONVERSATION(conv)) {
- gchar *chatname = purple_conversation_get_data(conv, "chatname");
- purple_serv_got_chat_in(sa->pc, g_str_hash(chatname), from, PURPLE_MESSAGE_RECV, url, ts);
- }
- g_free(url);
-
- return;
- }
-
- request = purple_http_request_new(uri);
- purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
- purple_http_request_header_set_printf(request, "Cookie", "skypetoken_asm=%s", sa->skype_token);
- purple_http_request_header_set(request, "Accept", "image/*");
- SkypeImgMsgContext *ctx = g_new(SkypeImgMsgContext, 1);
- ctx->composetimestamp = ts;
- ctx->conv = conv;
- ctx->from = g_strdup(from);
- purple_http_request(sa->pc, request, skypeweb_got_imagemessage, ctx);
- purple_http_request_unref(request);
-
- text = g_strdup_printf("<a href=\"%s\">Click here to view full %s</a>", url, skypeweb_uri_type_name(uri_type));
- purple_conversation_write_img_message(conv, from, text, 0, ts);
-
- g_free(url);
- g_free(text);
-}
-
-void
-skypeweb_download_moji_to_conv(SkypeWebAccount *sa, const gchar *text, const gchar *url_thumbnail, PurpleConversation *conv, time_t ts, const gchar* from)
-{
- gchar *cdn_url_thumbnail;
- PurpleHttpURL *httpurl;
- PurpleHttpRequest *request;
-
- httpurl = purple_http_url_parse(url_thumbnail);
-
- cdn_url_thumbnail = g_strdup_printf("https://%s/%s", SKYPEWEB_STATIC_CDN_HOST, purple_http_url_get_path(httpurl));
-
- request = purple_http_request_new(cdn_url_thumbnail);
- purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
- purple_http_request_header_set_printf(request, "Cookie", "vdms-skype-token=%s", sa->vdms_token);
- purple_http_request_header_set(request, "Accept", "image/*");
- SkypeImgMsgContext *ctx = g_new(SkypeImgMsgContext, 1);
- ctx->composetimestamp = ts;
- ctx->conv = conv;
- ctx->from = g_strdup(from);
- purple_http_request(sa->pc, request, skypeweb_got_imagemessage, ctx);
- purple_http_request_unref(request);
-
- purple_conversation_write_img_message(conv, from, text, 0, ts);
-
- g_free(cdn_url_thumbnail);
- purple_http_url_free(httpurl);
-}
-
-static void
-skypeweb_got_vm_file(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
-{
- PurpleXfer *xfer = user_data;
- const gchar *data;
- gsize len;
-
- data = purple_http_response_get_data(response, &len);
- purple_xfer_write(xfer, (guchar *)data, len);
-}
-
-static void
-skypeweb_init_vm_download(PurpleXfer *xfer)
-{
- SkypeWebAccount *sa;
- JsonObject *file = purple_xfer_get_protocol_data(xfer);
- gint64 fileSize;
- const gchar *url;
- PurpleHttpRequest *request;
-
- fileSize = json_object_get_int_member(file, "fileSize");
- url = json_object_get_string_member(file, "url");
-
- purple_xfer_set_completed(xfer, FALSE);
- sa = purple_connection_get_protocol_data(purple_account_get_connection(purple_xfer_get_account(xfer)));
-
- request = purple_http_request_new(url);
- purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
- purple_http_request_set_max_len(request, fileSize);
- purple_http_request(sa->pc, request, skypeweb_got_vm_file, xfer);
- purple_http_request_unref(request);
-
- json_object_unref(file);
-}
-
-static void
-skypeweb_cancel_vm_download(PurpleXfer *xfer)
-{
- JsonObject *file = purple_xfer_get_protocol_data(xfer);
- json_object_unref(file);
-}
-
-static void
-skypeweb_got_vm_download_info(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
-{
- PurpleConversation *conv = user_data;
- PurpleXfer *xfer;
- JsonObject *obj, *file;
- JsonArray *files;
- gint64 fileSize;
- const gchar *url, *assetId, *status;
- gchar *filename;
-
- if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
- return;
- obj = json_node_get_object(node);
-
- files = json_object_get_array_member(obj, "files");
- file = json_array_get_object_element(files, 0);
- if (file != NULL) {
- status = json_object_get_string_member(file, "status");
- if (status && g_str_equal(status, "ok")) {
- assetId = json_object_get_string_member(obj, "assetId");
- fileSize = json_object_get_int_member(file, "fileSize");
- url = json_object_get_string_member(file, "url");
- (void) url;
-
- filename = g_strconcat(assetId, ".mp4", NULL);
-
- xfer = purple_xfer_new(sa->account, PURPLE_XFER_TYPE_RECEIVE, purple_conversation_get_name(conv));
- purple_xfer_set_size(xfer, fileSize);
- purple_xfer_set_filename(xfer, filename);
- json_object_ref(file);
- purple_xfer_set_protocol_data(xfer, file);
- purple_xfer_set_init_fnc(xfer, skypeweb_init_vm_download);
- purple_xfer_set_cancel_recv_fnc(xfer, skypeweb_cancel_vm_download);
- purple_xfer_add(xfer);
-
- g_free(filename);
- } else if (status && g_str_equal(status, "running")) {
- //skypeweb_download_video_message(sa, sid??????, conv);
- }
- }
-}
-
-static void
-skypeweb_got_vm_info(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
-{
- PurpleConversation *conv = user_data;
- JsonObject *obj, *response, *media_stream;
- const gchar *filename;
-
- if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
- return;
- obj = json_node_get_object(node);
-
- response = json_object_get_object_member(obj, "response");
- media_stream = json_object_get_object_member(response, "media_stream");
- filename = json_object_get_string_member(media_stream, "filename");
-
- if (filename != NULL) {
- // Need to keep retrying this url until it comes back with status:ok
- gchar *url = g_strdup_printf("/vod/api-create?assetId=%s&profile=mp4-vm", purple_url_encode(filename));
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, "media.vm.skype.com", url, NULL, skypeweb_got_vm_download_info, conv, TRUE);
- g_free(url);
- }
-
-}
-
-void
-skypeweb_download_video_message(SkypeWebAccount *sa, const gchar *sid, PurpleConversation *conv)
-{
- gchar *url, *username_encoded;
-
- username_encoded = g_strdup(purple_url_encode(sa->username));
- url = g_strdup_printf("/users/%s/video_mails/%s", username_encoded, purple_url_encode(sid));
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_VIDEOMAIL_HOST, url, NULL, skypeweb_got_vm_info, conv, TRUE);
-
- g_free(url);
- g_free(username_encoded);
-
-}
-
-
-static void
-skypeweb_free_xfer(PurpleXfer *xfer)
-{
- SkypeWebFileTransfer *swft;
-
- swft = purple_xfer_get_protocol_data(xfer);
- g_return_if_fail(swft != NULL);
-
- if (swft->info != NULL)
- json_object_unref(swft->info);
- g_free(swft->url);
- g_free(swft->id);
- g_free(swft->from);
- g_free(swft);
-
- purple_xfer_set_protocol_data(xfer, NULL);
-}
-
-static void
-skypeweb_got_file(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
-{
- SkypeWebFileTransfer *swft = user_data;
- PurpleXfer *xfer = swft->xfer;
- SkypeWebAccount *sa = swft->sa;
- const gchar *data;
- gsize len;
-
-
- if (!purple_http_response_is_successful(response)) {
- purple_xfer_error(purple_xfer_get_xfer_type(xfer), sa->account, swft->from, purple_http_response_get_error(response));
- purple_xfer_cancel_local(xfer);
- } else {
- data = purple_http_response_get_data(response, &len);
- purple_xfer_write_file(xfer, (guchar *)data, len);
- purple_xfer_set_completed(xfer, TRUE);
- }
-
- //cleanup
- skypeweb_free_xfer(xfer);
- purple_xfer_end(xfer);
-}
-
-static void
-skypeweb_init_file_download(PurpleXfer *xfer)
-{
- SkypeWebAccount *sa;
- SkypeWebFileTransfer *swft;
- const gchar *view_location;
- gint64 content_full_length;
- PurpleHttpRequest *request;
-
- swft = purple_xfer_get_protocol_data(xfer);
- sa = swft->sa;
-
- view_location = json_object_get_string_member(swft->info, "view_location");
- content_full_length = json_object_get_int_member(swft->info, "content_full_length");
-
- purple_xfer_start(xfer, -1, NULL, 0);
-
- request = purple_http_request_new(view_location);
- purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
- purple_http_request_header_set_printf(request, "Cookie", "skypetoken_asm=%s", sa->skype_token);
- purple_http_request_header_set(request, "Accept", "*/*");
- purple_http_request_set_max_len(request, content_full_length);
- purple_http_request(sa->pc, request, skypeweb_got_file, swft);
- purple_http_request_unref(request);
-}
-
-static void
-skypeweb_got_file_info(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
-{
- JsonObject *obj;
- PurpleXfer *xfer;
- SkypeWebFileTransfer *swft = user_data;
- SkypeWebAccount *sa = swft->sa;
- JsonParser *parser;
- JsonNode *node;
- const gchar *data;
- gsize len;
-
- data = purple_http_response_get_data(response, &len);
-
- parser = json_parser_new();
- if (!json_parser_load_from_data(parser, data, len, NULL)) {
- g_free(swft->url);
- g_free(swft->from);
- g_free(swft);
- g_object_unref(parser);
- return;
- }
-
- node = json_parser_get_root(parser);
- if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT) {
- g_free(swft->url);
- g_free(swft->from);
- g_free(swft);
- g_object_unref(parser);
- return;
- }
- obj = json_node_get_object(node);
-
- /*
- {
- "content_length": 40708,
- "content_full_length": 40708,
- "view_length": 40708,
- "content_state": "ready",
- "view_state": "ready",
- "view_location": "uri/views/original",
- "status_location": "uri/views/original/status",
- "scan": {
- "status": "passed"
- },
- "original_filename": "filename"
- } */
- purple_debug_info("skypeweb", "File info: %s\n", data);
-
- if (!json_object_has_member(obj, "content_state") || !purple_strequal(json_object_get_string_member(obj, "content_state"), "ready")) {
- skypeweb_present_uri_as_filetransfer(sa, json_object_get_string_member(obj, "status_location"), swft->from);
- g_free(swft->url);
- g_free(swft->from);
- g_free(swft);
- g_object_unref(parser);
- return;
- }
-
- json_object_ref(obj);
- swft->info = obj;
-
- xfer = purple_xfer_new(sa->account, PURPLE_XFER_TYPE_RECEIVE, swft->from);
- purple_xfer_set_size(xfer, json_object_get_int_member(obj, "content_full_length"));
- purple_xfer_set_filename(xfer, json_object_get_string_member(obj, "original_filename"));
- purple_xfer_set_init_fnc(xfer, skypeweb_init_file_download);
- purple_xfer_set_cancel_recv_fnc(xfer, skypeweb_free_xfer);
-
- swft->xfer = xfer;
- purple_xfer_set_protocol_data(xfer, swft);
-
- purple_xfer_request(xfer);
-
- g_object_unref(parser);
-}
-
-void
-skypeweb_present_uri_as_filetransfer(SkypeWebAccount *sa, const gchar *uri, const gchar *from)
-{
- SkypeWebFileTransfer *swft;
- PurpleHttpRequest *request;
-
- swft = g_new0(SkypeWebFileTransfer, 1);
- swft->sa = sa;
- swft->url = g_strdup(uri);
- swft->from = g_strdup(from);
-
- request = purple_http_request_new(uri);
- if (!g_str_has_suffix(uri, "/views/original/status")) {
- purple_http_request_set_url_printf(request, "%s%s", uri, "/views/original/status");
- }
- purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
- purple_http_request_header_set_printf(request, "Cookie", "skypetoken_asm=%s", sa->skype_token);
- purple_http_request_header_set(request, "Accept", "*/*");
- purple_http_request(sa->pc, request, skypeweb_got_file_info, swft);
- purple_http_request_unref(request);
-}
-
-static void
-got_file_send_progress(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
-{
- SkypeWebFileTransfer *swft = user_data;
- PurpleXfer *xfer = swft->xfer;
- SkypeWebAccount *sa = swft->sa;
- JsonParser *parser;
- JsonNode *node;
- JsonObject *obj;
- const gchar *data;
- gsize len;
-
- data = purple_http_response_get_data(response, &len);
-
- //{"content_length":0,"content_full_length":0,"view_length":0,"content_state":"no content","view_state":"none","view_location":"https://nus1-api.asm.skype.com/v1/objects/0-cus-d1-61121cfae8cf601944627a66afdb77ad/views/original","status_location":"https://nus1-api.asm.skype.com/v1/objects/0-cus-d1-61121cfae8cf601944627a66afdb77ad/views/original/status"}
- parser = json_parser_new();
- if (!json_parser_load_from_data(parser, data, len, NULL)) {
- //probably bad
- return;
- }
- node = json_parser_get_root(parser);
- if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT) {
- //probably bad
- return;
- }
- obj = json_node_get_object(node);
-
-
- if (json_object_has_member(obj, "status_location")) {
- g_free(swft->url);
- swft->url = g_strdup(json_object_get_string_member(obj, "status_location"));
- }
-
- if (json_object_has_member(obj, "content_state") && purple_strequal(json_object_get_string_member(obj, "content_state"), "ready")) {
- PurpleXmlNode *uriobject = purple_xmlnode_new("URIObject");
- PurpleXmlNode *title = purple_xmlnode_new_child(uriobject, "Title");
- PurpleXmlNode *description = purple_xmlnode_new_child(uriobject, "Description");
- PurpleXmlNode *anchor = purple_xmlnode_new_child(uriobject, "a");
- PurpleXmlNode *originalname = purple_xmlnode_new_child(uriobject, "OriginalName");
- PurpleXmlNode *filesize = purple_xmlnode_new_child(uriobject, "FileSize");
- gchar *message, *temp;
- //We finally did it!
- // May the pesants rejoyce
- purple_xfer_set_completed(xfer, TRUE);
-
- // Don't forget to let the other end know about it
-
- purple_xmlnode_set_attrib(uriobject, "type", "File.1");
- temp = g_strconcat("https://" SKYPEWEB_XFER_HOST "/v1/objects/", purple_url_encode(swft->id), NULL);
- purple_xmlnode_set_attrib(uriobject, "uri", temp);
- g_free(temp);
- temp = g_strconcat("https://" SKYPEWEB_XFER_HOST "/v1/objects/", purple_url_encode(swft->id), "/views/thumbnail", NULL);
- purple_xmlnode_set_attrib(uriobject, "url_thumbnail", temp);
- g_free(temp);
- purple_xmlnode_insert_data(title, purple_xfer_get_filename(xfer), -1);
- purple_xmlnode_insert_data(description, "Description: ", -1);
- temp = g_strconcat("https://login.skype.com/login/sso?go=webclient.xmm&docid=", purple_url_encode(swft->id), NULL);
- purple_xmlnode_set_attrib(anchor, "href", temp);
- purple_xmlnode_insert_data(anchor, temp, -1);
- g_free(temp);
- purple_xmlnode_set_attrib(originalname, "v", purple_xfer_get_filename(xfer));
- temp = g_strdup_printf("%" G_GSIZE_FORMAT, (gsize) purple_xfer_get_size(xfer));
- purple_xmlnode_set_attrib(filesize, "v", temp);
- g_free(temp);
-
- temp = purple_xmlnode_to_str(uriobject, NULL);
+} SkypeImgMsgContext;
+
+static void
+skypeweb_got_imagemessage(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
+{
+ gint icon_id;
+ gchar *msg_tmp;
+ const gchar *url_text;
+ gsize len;
+ PurpleImage *image;
+
+ SkypeImgMsgContext *ctx = user_data;
+ PurpleConversation *conv = ctx->conv;
+ time_t ts = ctx->composetimestamp;
+ gchar* ctx_from = ctx->from;
+ ctx->from = NULL;
+ g_free(ctx);
+
+ // Conversation could have been closed before we retrieved the image
+ if (!purple_conversation_is_valid(conv)) {
+ return;
+ }
+
+ url_text = purple_http_response_get_data(response, &len);
+
+ if (!url_text || !len || url_text[0] == '{' || url_text[0] == '<')
+ return;
+
+ if (!purple_http_response_is_successful(response))
+ return;
+
+ image = purple_image_new_from_data(g_memdup(url_text, len), len);
+ icon_id = purple_image_store_add(image);
+ msg_tmp = g_strdup_printf("<img id='%d'>", icon_id);
+ purple_conversation_write_img_message(conv, ctx_from, msg_tmp, PURPLE_MESSAGE_NO_LOG | PURPLE_MESSAGE_IMAGES, ts);
+ g_free(msg_tmp);
+ g_free(ctx_from);
+}
+
+static const char*
+skypeweb_uri_type_name(SkypeWebURIType uri_type) {
+ switch (uri_type) {
+ case SKYPEWEB_URI_TYPE_IMAGE:
+ return "image";
+ case SKYPEWEB_URI_TYPE_VIDEO:
+ return "video";
+ default:
+ return "(unknown)";
+ }
+}
+void
+skypeweb_download_uri_to_conv(SkypeWebAccount *sa, const gchar *uri, SkypeWebURIType uri_type, PurpleConversation *conv, time_t ts, const gchar* from)
+{
+ gchar *url, *text;
+ PurpleHttpRequest *request;
+
+ switch (uri_type) {
+ case SKYPEWEB_URI_TYPE_IMAGE:
+ url = purple_strreplace(uri, "imgt1", "imgpsh_fullsize");
+ break;
+ case SKYPEWEB_URI_TYPE_VIDEO:
+ url = purple_strreplace(uri, "thumbnail", "video");
+ break;
+ default:
+ url = g_strdup(uri);
+ break;
+ }
+ if (purple_strequal(purple_core_get_ui(), "BitlBee")) {
+ // Bitlbee doesn't support images, so just plop a url to the image instead
+
+ if (PURPLE_IS_IM_CONVERSATION(conv)) {
+ purple_serv_got_im(sa->pc, from, url, PURPLE_MESSAGE_RECV, ts);
+ } else if (PURPLE_IS_CHAT_CONVERSATION(conv)) {
+ gchar *chatname = purple_conversation_get_data(conv, "chatname");
+ purple_serv_got_chat_in(sa->pc, g_str_hash(chatname), from, PURPLE_MESSAGE_RECV, url, ts);
+ }
+ g_free(url);
+
+ return;
+ }
+
+ request = purple_http_request_new(uri);
+ purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
+ purple_http_request_header_set_printf(request, "Cookie", "skypetoken_asm=%s", sa->skype_token);
+ purple_http_request_header_set(request, "Accept", "image/*");
+ SkypeImgMsgContext *ctx = g_new(SkypeImgMsgContext, 1);
+ ctx->composetimestamp = ts;
+ ctx->conv = conv;
+ ctx->from = g_strdup(from);
+ purple_http_request(sa->pc, request, skypeweb_got_imagemessage, ctx);
+ purple_http_request_unref(request);
+
+ text = g_strdup_printf("<a href=\"%s\">Click here to view full %s</a>", url, skypeweb_uri_type_name(uri_type));
+ purple_conversation_write_img_message(conv, from, text, 0, ts);
+
+ g_free(url);
+ g_free(text);
+}
+
+void
+skypeweb_download_moji_to_conv(SkypeWebAccount *sa, const gchar *text, const gchar *url_thumbnail, PurpleConversation *conv, time_t ts, const gchar* from)
+{
+ gchar *cdn_url_thumbnail;
+ PurpleHttpURL *httpurl;
+ PurpleHttpRequest *request;
+
+ httpurl = purple_http_url_parse(url_thumbnail);
+
+ cdn_url_thumbnail = g_strdup_printf("https://%s/%s", SKYPEWEB_STATIC_CDN_HOST, purple_http_url_get_path(httpurl));
+
+ request = purple_http_request_new(cdn_url_thumbnail);
+ purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
+ purple_http_request_header_set_printf(request, "Cookie", "vdms-skype-token=%s", sa->vdms_token);
+ purple_http_request_header_set(request, "Accept", "image/*");
+ SkypeImgMsgContext *ctx = g_new(SkypeImgMsgContext, 1);
+ ctx->composetimestamp = ts;
+ ctx->conv = conv;
+ ctx->from = g_strdup(from);
+ purple_http_request(sa->pc, request, skypeweb_got_imagemessage, ctx);
+ purple_http_request_unref(request);
+
+ purple_conversation_write_img_message(conv, from, text, 0, ts);
+
+ g_free(cdn_url_thumbnail);
+ purple_http_url_free(httpurl);
+}
+
+static void
+skypeweb_got_vm_file(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
+{
+ PurpleXfer *xfer = user_data;
+ const gchar *data;
+ gsize len;
+
+ data = purple_http_response_get_data(response, &len);
+ purple_xfer_write(xfer, (guchar *)data, len);
+}
+
+static void
+skypeweb_init_vm_download(PurpleXfer *xfer)
+{
+ SkypeWebAccount *sa;
+ JsonObject *file = purple_xfer_get_protocol_data(xfer);
+ gint64 fileSize;
+ const gchar *url;
+ PurpleHttpRequest *request;
+
+ fileSize = json_object_get_int_member(file, "fileSize");
+ url = json_object_get_string_member(file, "url");
+
+ purple_xfer_set_completed(xfer, FALSE);
+ sa = purple_connection_get_protocol_data(purple_account_get_connection(purple_xfer_get_account(xfer)));
+
+ request = purple_http_request_new(url);
+ purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
+ purple_http_request_set_max_len(request, fileSize);
+ purple_http_request(sa->pc, request, skypeweb_got_vm_file, xfer);
+ purple_http_request_unref(request);
+
+ json_object_unref(file);
+}
+
+static void
+skypeweb_cancel_vm_download(PurpleXfer *xfer)
+{
+ JsonObject *file = purple_xfer_get_protocol_data(xfer);
+ json_object_unref(file);
+}
+
+static void
+skypeweb_got_vm_download_info(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ PurpleConversation *conv = user_data;
+ PurpleXfer *xfer;
+ JsonObject *obj, *file;
+ JsonArray *files;
+ gint64 fileSize;
+ const gchar *url, *assetId, *status;
+ gchar *filename;
+
+ if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
+ return;
+ obj = json_node_get_object(node);
+
+ files = json_object_get_array_member(obj, "files");
+ file = json_array_get_object_element(files, 0);
+ if (file != NULL) {
+ status = json_object_get_string_member(file, "status");
+ if (status && g_str_equal(status, "ok")) {
+ assetId = json_object_get_string_member(obj, "assetId");
+ fileSize = json_object_get_int_member(file, "fileSize");
+ url = json_object_get_string_member(file, "url");
+ (void) url;
+
+ filename = g_strconcat(assetId, ".mp4", NULL);
+
+ xfer = purple_xfer_new(sa->account, PURPLE_XFER_TYPE_RECEIVE, purple_conversation_get_name(conv));
+ purple_xfer_set_size(xfer, fileSize);
+ purple_xfer_set_filename(xfer, filename);
+ json_object_ref(file);
+ purple_xfer_set_protocol_data(xfer, file);
+ purple_xfer_set_init_fnc(xfer, skypeweb_init_vm_download);
+ purple_xfer_set_cancel_recv_fnc(xfer, skypeweb_cancel_vm_download);
+ purple_xfer_add(xfer);
+
+ g_free(filename);
+ } else if (status && g_str_equal(status, "running")) {
+ //skypeweb_download_video_message(sa, sid??????, conv);
+ }
+ }
+}
+
+static void
+skypeweb_got_vm_info(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ PurpleConversation *conv = user_data;
+ JsonObject *obj, *response, *media_stream;
+ const gchar *filename;
+
+ if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
+ return;
+ obj = json_node_get_object(node);
+
+ response = json_object_get_object_member(obj, "response");
+ media_stream = json_object_get_object_member(response, "media_stream");
+ filename = json_object_get_string_member(media_stream, "filename");
+
+ if (filename != NULL) {
+ // Need to keep retrying this url until it comes back with status:ok
+ gchar *url = g_strdup_printf("/vod/api-create?assetId=%s&profile=mp4-vm", purple_url_encode(filename));
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, "media.vm.skype.com", url, NULL, skypeweb_got_vm_download_info, conv, TRUE);
+ g_free(url);
+ }
+
+}
+
+void
+skypeweb_download_video_message(SkypeWebAccount *sa, const gchar *sid, PurpleConversation *conv)
+{
+ gchar *url, *username_encoded;
+
+ username_encoded = g_strdup(purple_url_encode(sa->username));
+ url = g_strdup_printf("/users/%s/video_mails/%s", username_encoded, purple_url_encode(sid));
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_VIDEOMAIL_HOST, url, NULL, skypeweb_got_vm_info, conv, TRUE);
+
+ g_free(url);
+ g_free(username_encoded);
+
+}
+
+
+static void
+skypeweb_free_xfer(PurpleXfer *xfer)
+{
+ SkypeWebFileTransfer *swft;
+
+ swft = purple_xfer_get_protocol_data(xfer);
+ g_return_if_fail(swft != NULL);
+
+ if (swft->info != NULL)
+ json_object_unref(swft->info);
+ g_free(swft->url);
+ g_free(swft->id);
+ g_free(swft->from);
+ g_free(swft);
+
+ purple_xfer_set_protocol_data(xfer, NULL);
+}
+
+static void
+skypeweb_got_file(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
+{
+ SkypeWebFileTransfer *swft = user_data;
+ PurpleXfer *xfer = swft->xfer;
+ SkypeWebAccount *sa = swft->sa;
+ const gchar *data;
+ gsize len;
+
+
+ if (!purple_http_response_is_successful(response)) {
+ purple_xfer_error(purple_xfer_get_xfer_type(xfer), sa->account, swft->from, purple_http_response_get_error(response));
+ purple_xfer_cancel_local(xfer);
+ } else {
+ data = purple_http_response_get_data(response, &len);
+ purple_xfer_write_file(xfer, (guchar *)data, len);
+ purple_xfer_set_completed(xfer, TRUE);
+ }
+
+ //cleanup
+ skypeweb_free_xfer(xfer);
+ purple_xfer_end(xfer);
+}
+
+static void
+skypeweb_init_file_download(PurpleXfer *xfer)
+{
+ SkypeWebAccount *sa;
+ SkypeWebFileTransfer *swft;
+ const gchar *view_location;
+ gint64 content_full_length;
+ PurpleHttpRequest *request;
+
+ swft = purple_xfer_get_protocol_data(xfer);
+ sa = swft->sa;
+
+ view_location = json_object_get_string_member(swft->info, "view_location");
+ content_full_length = json_object_get_int_member(swft->info, "content_full_length");
+
+ purple_xfer_start(xfer, -1, NULL, 0);
+
+ request = purple_http_request_new(view_location);
+ purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
+ purple_http_request_header_set_printf(request, "Cookie", "skypetoken_asm=%s", sa->skype_token);
+ purple_http_request_header_set(request, "Accept", "*/*");
+ purple_http_request_set_max_len(request, content_full_length);
+ purple_http_request(sa->pc, request, skypeweb_got_file, swft);
+ purple_http_request_unref(request);
+}
+
+static void
+skypeweb_got_file_info(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
+{
+ JsonObject *obj;
+ PurpleXfer *xfer;
+ SkypeWebFileTransfer *swft = user_data;
+ SkypeWebAccount *sa = swft->sa;
+ JsonParser *parser;
+ JsonNode *node;
+ const gchar *data;
+ gsize len;
+
+ data = purple_http_response_get_data(response, &len);
+
+ parser = json_parser_new();
+ if (!json_parser_load_from_data(parser, data, len, NULL)) {
+ g_free(swft->url);
+ g_free(swft->from);
+ g_free(swft);
+ g_object_unref(parser);
+ return;
+ }
+
+ node = json_parser_get_root(parser);
+ if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT) {
+ g_free(swft->url);
+ g_free(swft->from);
+ g_free(swft);
+ g_object_unref(parser);
+ return;
+ }
+ obj = json_node_get_object(node);
+
+ /*
+ {
+ "content_length": 40708,
+ "content_full_length": 40708,
+ "view_length": 40708,
+ "content_state": "ready",
+ "view_state": "ready",
+ "view_location": "uri/views/original",
+ "status_location": "uri/views/original/status",
+ "scan": {
+ "status": "passed"
+ },
+ "original_filename": "filename"
+ } */
+ purple_debug_info("skypeweb", "File info: %s\n", data);
+
+ if (!json_object_has_member(obj, "content_state") || !purple_strequal(json_object_get_string_member(obj, "content_state"), "ready")) {
+ skypeweb_present_uri_as_filetransfer(sa, json_object_get_string_member(obj, "status_location"), swft->from);
+ g_free(swft->url);
+ g_free(swft->from);
+ g_free(swft);
+ g_object_unref(parser);
+ return;
+ }
+
+ json_object_ref(obj);
+ swft->info = obj;
+
+ xfer = purple_xfer_new(sa->account, PURPLE_XFER_TYPE_RECEIVE, swft->from);
+ purple_xfer_set_size(xfer, json_object_get_int_member(obj, "content_full_length"));
+ purple_xfer_set_filename(xfer, json_object_get_string_member(obj, "original_filename"));
+ purple_xfer_set_init_fnc(xfer, skypeweb_init_file_download);
+ purple_xfer_set_cancel_recv_fnc(xfer, skypeweb_free_xfer);
+
+ swft->xfer = xfer;
+ purple_xfer_set_protocol_data(xfer, swft);
+
+ purple_xfer_request(xfer);
+
+ g_object_unref(parser);
+}
+
+void
+skypeweb_present_uri_as_filetransfer(SkypeWebAccount *sa, const gchar *uri, const gchar *from)
+{
+ SkypeWebFileTransfer *swft;
+ PurpleHttpRequest *request;
+
+ swft = g_new0(SkypeWebFileTransfer, 1);
+ swft->sa = sa;
+ swft->url = g_strdup(uri);
+ swft->from = g_strdup(from);
+
+ request = purple_http_request_new(uri);
+ if (!g_str_has_suffix(uri, "/views/original/status")) {
+ purple_http_request_set_url_printf(request, "%s%s", uri, "/views/original/status");
+ }
+ purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
+ purple_http_request_header_set_printf(request, "Cookie", "skypetoken_asm=%s", sa->skype_token);
+ purple_http_request_header_set(request, "Accept", "*/*");
+ purple_http_request(sa->pc, request, skypeweb_got_file_info, swft);
+ purple_http_request_unref(request);
+}
+
+static void
+got_file_send_progress(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
+{
+ SkypeWebFileTransfer *swft = user_data;
+ PurpleXfer *xfer = swft->xfer;
+ SkypeWebAccount *sa = swft->sa;
+ JsonParser *parser;
+ JsonNode *node;
+ JsonObject *obj;
+ const gchar *data;
+ gsize len;
+
+ data = purple_http_response_get_data(response, &len);
+
+ //{"content_length":0,"content_full_length":0,"view_length":0,"content_state":"no content","view_state":"none","view_location":"https://nus1-api.asm.skype.com/v1/objects/0-cus-d1-61121cfae8cf601944627a66afdb77ad/views/original","status_location":"https://nus1-api.asm.skype.com/v1/objects/0-cus-d1-61121cfae8cf601944627a66afdb77ad/views/original/status"}
+ parser = json_parser_new();
+ if (!json_parser_load_from_data(parser, data, len, NULL)) {
+ //probably bad
+ return;
+ }
+ node = json_parser_get_root(parser);
+ if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT) {
+ //probably bad
+ return;
+ }
+ obj = json_node_get_object(node);
+
+
+ if (json_object_has_member(obj, "status_location")) {
+ g_free(swft->url);
+ swft->url = g_strdup(json_object_get_string_member(obj, "status_location"));
+ }
+
+ if (json_object_has_member(obj, "content_state") && purple_strequal(json_object_get_string_member(obj, "content_state"), "ready")) {
+ PurpleXmlNode *uriobject = purple_xmlnode_new("URIObject");
+ PurpleXmlNode *title = purple_xmlnode_new_child(uriobject, "Title");
+ PurpleXmlNode *description = purple_xmlnode_new_child(uriobject, "Description");
+ PurpleXmlNode *anchor = purple_xmlnode_new_child(uriobject, "a");
+ PurpleXmlNode *originalname = purple_xmlnode_new_child(uriobject, "OriginalName");
+ PurpleXmlNode *filesize = purple_xmlnode_new_child(uriobject, "FileSize");
+ gchar *message, *temp;
+ //We finally did it!
+ // May the pesants rejoyce
+ purple_xfer_set_completed(xfer, TRUE);
+
+ // Don't forget to let the other end know about it
+
+ purple_xmlnode_set_attrib(uriobject, "type", "File.1");
+ temp = g_strconcat("https://" SKYPEWEB_XFER_HOST "/v1/objects/", purple_url_encode(swft->id), NULL);
+ purple_xmlnode_set_attrib(uriobject, "uri", temp);
+ g_free(temp);
+ temp = g_strconcat("https://" SKYPEWEB_XFER_HOST "/v1/objects/", purple_url_encode(swft->id), "/views/thumbnail", NULL);
+ purple_xmlnode_set_attrib(uriobject, "url_thumbnail", temp);
+ g_free(temp);
+ purple_xmlnode_insert_data(title, purple_xfer_get_filename(xfer), -1);
+ purple_xmlnode_insert_data(description, "Description: ", -1);
+ temp = g_strconcat("https://login.skype.com/login/sso?go=webclient.xmm&docid=", purple_url_encode(swft->id), NULL);
+ purple_xmlnode_set_attrib(anchor, "href", temp);
+ purple_xmlnode_insert_data(anchor, temp, -1);
+ g_free(temp);
+ purple_xmlnode_set_attrib(originalname, "v", purple_xfer_get_filename(xfer));
+ temp = g_strdup_printf("%" G_GSIZE_FORMAT, (gsize) purple_xfer_get_size(xfer));
+ purple_xmlnode_set_attrib(filesize, "v", temp);
+ g_free(temp);
+
+ temp = purple_xmlnode_to_str(uriobject, NULL);
message = purple_strreplace(temp, "'", "\"");
g_free(temp);
-#if PURPLE_VERSION_CHECK(3, 0, 0)
- PurpleMessage *msg = purple_message_new_outgoing(swft->from, message, PURPLE_MESSAGE_SEND);
- skypeweb_send_im(sa->pc, msg);
- purple_message_destroy(msg);
-#else
- skypeweb_send_im(sa->pc, swft->from, message, PURPLE_MESSAGE_SEND);
-#endif
- g_free(message);
-
- skypeweb_free_xfer(xfer);
- purple_xfer_unref(xfer);
-
- purple_xmlnode_free(uriobject);
- g_object_unref(parser);
- return;
- }
-
-
- g_object_unref(parser);
-
- // probably good
-}
-
-static gboolean
-poll_file_send_progress(gpointer user_data)
-{
- SkypeWebFileTransfer *swft = user_data;
- SkypeWebAccount *sa = swft->sa;
- PurpleHttpRequest *request;
-
- request = purple_http_request_new(swft->url);
- purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
- purple_http_request_header_set_printf(request, "Cookie", "skypetoken_asm=%s", sa->skype_token);
- purple_http_request_header_set(request, "Accept", "*/*");
- purple_http_request(sa->pc, request, got_file_send_progress, swft);
- purple_http_request_unref(request);
-
- return FALSE;
-}
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+ PurpleMessage *msg = purple_message_new_outgoing(swft->from, message, PURPLE_MESSAGE_SEND);
+ skypeweb_send_im(sa->pc, msg);
+ purple_message_destroy(msg);
+#else
+ skypeweb_send_im(sa->pc, swft->from, message, PURPLE_MESSAGE_SEND);
+#endif
+ g_free(message);
+
+ skypeweb_free_xfer(xfer);
+ purple_xfer_unref(xfer);
+
+ purple_xmlnode_free(uriobject);
+ g_object_unref(parser);
+ return;
+ }
+
+
+ g_object_unref(parser);
+
+ // probably good
+}
+
+static gboolean
+poll_file_send_progress(gpointer user_data)
+{
+ SkypeWebFileTransfer *swft = user_data;
+ SkypeWebAccount *sa = swft->sa;
+ PurpleHttpRequest *request;
+
+ request = purple_http_request_new(swft->url);
+ purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
+ purple_http_request_header_set_printf(request, "Cookie", "skypetoken_asm=%s", sa->skype_token);
+ purple_http_request_header_set(request, "Accept", "*/*");
+ purple_http_request(sa->pc, request, got_file_send_progress, swft);
+ purple_http_request_unref(request);
+
+ return FALSE;
+}
static void
skypeweb_xfer_send_contents_reader(PurpleHttpConnection *con, gchar *buf, size_t offset, size_t len, gpointer user_data, PurpleHttpContentReaderCb cb)
{
SkypeWebFileTransfer *swft = user_data;
PurpleXfer *xfer = swft->xfer;
- gsize read;
+ gsize read;
- purple_debug_info("skypeweb", "Asked %" G_GSIZE_FORMAT " bytes from offset %" G_GSIZE_FORMAT "\n", len, offset);
+ purple_debug_info("skypeweb", "Asked %" G_GSIZE_FORMAT " bytes from offset %" G_GSIZE_FORMAT "\n", len, offset);
purple_xfer_set_bytes_sent(xfer, offset);
read = purple_xfer_read_file(xfer, (guchar *)buf, len);
- purple_debug_info("skypeweb", "Read %" G_GSIZE_FORMAT " bytes\n", read);
+ purple_debug_info("skypeweb", "Read %" G_GSIZE_FORMAT " bytes\n", read);
cb(con, TRUE, read != len, read);
}
@@ -690,7 +690,7 @@ skypeweb_xfer_send_done(PurpleHttpConnection *conn, PurpleHttpResponse *resp, gp
int code = purple_http_response_get_code(resp);
purple_debug_info("skypeweb", "Finished [%d]: %s\n", code, error);
purple_debug_info("skypeweb", "Server message: %s\n", data);
- g_timeout_add_seconds(1, poll_file_send_progress, user_data);
+ g_timeout_add_seconds(1, poll_file_send_progress, user_data);
}
static void
@@ -725,925 +725,925 @@ skypeweb_xfer_send_begin(gpointer user_data)
purple_http_request_unref(request);
}
-static void
-skypeweb_got_object_for_file(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
-{
- SkypeWebFileTransfer *swft = user_data;
- PurpleXfer *xfer = swft->xfer;
- JsonParser *parser;
- JsonNode *node;
- JsonObject *obj;
- const gchar *data;
- gsize len;
-
- data = purple_http_response_get_data(response, &len);
-
- //Get back {"id": "0-cus-d3-deadbeefdeadbeef012345678"}
- parser = json_parser_new();
- if (!json_parser_load_from_data(parser, data, len, NULL)) {
- g_free(swft->from);
- g_free(swft);
- g_object_unref(parser);
- return;
- }
- node = json_parser_get_root(parser);
- if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT) {
- g_free(swft->from);
- g_free(swft);
- g_object_unref(parser);
- purple_xfer_cancel_local(xfer);
- return;
- }
- obj = json_node_get_object(node);
-
- if (!json_object_has_member(obj, "id")) {
- g_free(swft->from);
- g_free(swft);
- g_object_unref(parser);
- purple_xfer_cancel_local(xfer);
- return;
- }
-
- swft->id = g_strdup(json_object_get_string_member(obj, "id"));
- swft->url = g_strconcat("https://" SKYPEWEB_XFER_HOST "/v1/objects/", purple_url_encode(swft->id), "/views/original/status", NULL);
-
- g_object_unref(parser);
-
- //Send the data
-
- //can't use fetch_url_request because it doesn't handle binary data
+static void
+skypeweb_got_object_for_file(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
+{
+ SkypeWebFileTransfer *swft = user_data;
+ PurpleXfer *xfer = swft->xfer;
+ JsonParser *parser;
+ JsonNode *node;
+ JsonObject *obj;
+ const gchar *data;
+ gsize len;
+
+ data = purple_http_response_get_data(response, &len);
+
+ //Get back {"id": "0-cus-d3-deadbeefdeadbeef012345678"}
+ parser = json_parser_new();
+ if (!json_parser_load_from_data(parser, data, len, NULL)) {
+ g_free(swft->from);
+ g_free(swft);
+ g_object_unref(parser);
+ return;
+ }
+ node = json_parser_get_root(parser);
+ if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT) {
+ g_free(swft->from);
+ g_free(swft);
+ g_object_unref(parser);
+ purple_xfer_cancel_local(xfer);
+ return;
+ }
+ obj = json_node_get_object(node);
+
+ if (!json_object_has_member(obj, "id")) {
+ g_free(swft->from);
+ g_free(swft);
+ g_object_unref(parser);
+ purple_xfer_cancel_local(xfer);
+ return;
+ }
+
+ swft->id = g_strdup(json_object_get_string_member(obj, "id"));
+ swft->url = g_strconcat("https://" SKYPEWEB_XFER_HOST "/v1/objects/", purple_url_encode(swft->id), "/views/original/status", NULL);
+
+ g_object_unref(parser);
+
+ //Send the data
+
+ //can't use fetch_url_request because it doesn't handle binary data
skypeweb_xfer_send_begin(user_data);
-
-}
-
-static void
-skypeweb_xfer_send_init(PurpleXfer *xfer)
-{
- PurpleConnection *pc = purple_account_get_connection(purple_xfer_get_account(xfer));
- SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
- gchar *basename = g_path_get_basename(purple_xfer_get_local_filename(xfer));
- gchar *id, *post;
- SkypeWebFileTransfer *swft = purple_xfer_get_protocol_data(xfer);
- JsonObject *obj = json_object_new();
- JsonObject *permissions = json_object_new();
- JsonArray *userpermissions = json_array_new();
- PurpleHttpRequest *request;
-
- purple_xfer_set_filename(xfer, basename);
- purple_xfer_ref(xfer);
-
- json_object_set_string_member(obj, "type", "sharing/file");
- json_object_set_string_member(obj, "filename", basename);
-
- id = g_strconcat(skypeweb_user_url_prefix(swft->from), swft->from, NULL);
- json_array_add_string_element(userpermissions, "read");
- json_object_set_array_member(permissions, id, userpermissions);
- json_object_set_object_member(obj, "permissions", permissions);
-
- post = skypeweb_jsonobj_to_string(obj);
- //POST to api.asm.skype.com /v1/objects
- //{"type":"sharing/file","permissions":{"8:eionrobb":["read"]},"filename":"GiantLobsterMoose.txt"}
-
- request = purple_http_request_new("https://" SKYPEWEB_XFER_HOST "/v1/objects");
- purple_http_request_set_method(request, "POST");
- purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
- purple_http_request_header_set_printf(request, "Authorization", "skype_token %s", sa->skype_token); //slightly different to normal!
- purple_http_request_header_set(request, "Content-Type", "application/json");
- purple_http_request_header_set(request, "X-Client-Version", SKYPEWEB_CLIENTINFO_VERSION);
- purple_http_request_set_contents(request, post, -1);
- purple_http_request(sa->pc, request, skypeweb_got_object_for_file, swft);
- purple_http_request_unref(request);
-
- g_free(post);
- json_object_unref(obj);
- g_free(id);
- g_free(basename);
-}
-
-PurpleXfer *
-skypeweb_new_xfer(
-#if PURPLE_VERSION_CHECK(3, 0, 0)
-PurpleProtocolXfer *prplxfer,
-#endif
-PurpleConnection *pc, const char *who)
-{
- SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
- PurpleXfer *xfer;
- SkypeWebFileTransfer *swft;
-
- xfer = purple_xfer_new(sa->account, PURPLE_XFER_TYPE_SEND, who);
-
- swft = g_new0(SkypeWebFileTransfer, 1);
- swft->sa = sa;
- swft->from = g_strdup(who);
- swft->xfer = xfer;
- purple_xfer_set_protocol_data(xfer, swft);
-
- purple_xfer_set_init_fnc(xfer, skypeweb_xfer_send_init);
- //purple_xfer_set_write_fnc(xfer, skypeweb_xfer_send_write);
- //purple_xfer_set_end_fnc(xfer, skypeweb_xfer_send_end);
- purple_xfer_set_request_denied_fnc(xfer, skypeweb_free_xfer);
- purple_xfer_set_cancel_send_fnc(xfer, skypeweb_free_xfer);
-
- return xfer;
-}
-
-void
-skypeweb_send_file(
-#if PURPLE_VERSION_CHECK(3, 0, 0)
-PurpleProtocolXfer *prplxfer,
-#endif
-PurpleConnection *pc, const gchar *who, const gchar *filename)
-{
- PurpleXfer *xfer = skypeweb_new_xfer(
-#if PURPLE_VERSION_CHECK(3, 0, 0)
- prplxfer,
-#endif
- pc, who);
-
- if (filename && *filename)
- purple_xfer_request_accepted(xfer, filename);
- else
- purple_xfer_request(xfer);
-}
-
-gboolean
-skypeweb_can_receive_file(
-#if PURPLE_VERSION_CHECK(3, 0, 0)
-PurpleProtocolXfer *prplxfer,
-#endif
-PurpleConnection *pc, const gchar *who)
-{
- if (!who || g_str_equal(who, purple_account_get_username(purple_connection_get_account(pc))))
- return FALSE;
-
- return TRUE;
-}
-
-
-static void
-skypeweb_got_self_details(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
-{
- JsonObject *userobj;
- const gchar *old_alias;
- const gchar *displayname = NULL;
- const gchar *username;
-
- if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
- return;
- userobj = json_node_get_object(node);
-
- username = json_object_get_string_member(userobj, "username");
- g_free(sa->username); sa->username = g_strdup(username);
- purple_connection_set_display_name(sa->pc, sa->username);
-
- old_alias = purple_account_get_private_alias(sa->account);
- if (!old_alias || !*old_alias) {
- if (json_object_has_member(userobj, "displayname"))
- displayname = json_object_get_string_member(userobj, "displayname");
- if (!displayname || purple_strequal(displayname, username))
- displayname = json_object_get_string_member(userobj, "firstname");
-
- if (displayname)
- purple_account_set_private_alias(sa->account, displayname);
- }
-
- if (!PURPLE_CONNECTION_IS_CONNECTED(sa->pc)) {
- skypeweb_do_all_the_things(sa);
- }
-
- skypeweb_gather_self_properties(sa);
-}
-
-
-void
-skypeweb_get_self_details(SkypeWebAccount *sa)
-{
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, "/users/self/profile", NULL, skypeweb_got_self_details, NULL, TRUE);
-}
-
-
-
-
-
-
-
-
-
-void
-skypeweb_search_results_add_buddy(PurpleConnection *pc, GList *row, void *user_data)
-{
- PurpleAccount *account = purple_connection_get_account(pc);
-
- if (!purple_blist_find_buddy(account, g_list_nth_data(row, 0)))
- purple_blist_request_add_buddy(account, g_list_nth_data(row, 0), "Skype", g_list_nth_data(row, 1));
-}
-
-void
-skypeweb_received_contacts(SkypeWebAccount *sa, PurpleXmlNode *contacts)
-{
- PurpleNotifySearchResults *results;
- PurpleNotifySearchColumn *column;
-
- PurpleXmlNode *contact;
-
- results = purple_notify_searchresults_new();
- if (results == NULL) {
- return;
- }
-
- /* columns: Friend ID, Name */
- column = purple_notify_searchresults_column_new(_("Skype Name"));
- purple_notify_searchresults_column_add(results, column);
- column = purple_notify_searchresults_column_new(_("Display Name"));
- purple_notify_searchresults_column_add(results, column);
-
-
- purple_notify_searchresults_button_add(results,
- PURPLE_NOTIFY_BUTTON_ADD,
- skypeweb_search_results_add_buddy);
-
- for(contact = purple_xmlnode_get_child(contacts, "c"); contact;
- contact = purple_xmlnode_get_next_twin(contact))
- {
- GList *row = NULL;
-
- gchar *contact_id = g_strdup(purple_xmlnode_get_attrib(contact, "s"));
- gchar *contact_name = g_strdup(purple_xmlnode_get_attrib(contact, "f"));
-
- row = g_list_append(row, contact_id);
- row = g_list_append(row, contact_name);
-
- purple_notify_searchresults_row_add(results, row);
- }
-
- purple_notify_searchresults(sa->pc, _("Received contacts"), NULL, NULL, results, NULL, NULL);
-}
-
-static PurpleNotifySearchResults*
-create_search_results(JsonNode *node, gint *olength)
-{
- PurpleNotifySearchColumn *column;
- gint index, length;
- JsonObject *response = NULL;
- JsonArray *resultsarray = NULL;
-
- response = json_node_get_object(node);
- resultsarray = json_object_get_array_member(response, "results");
- length = json_array_get_length(resultsarray);
-
- PurpleNotifySearchResults *results = purple_notify_searchresults_new();
- if (results == NULL || length == 0)
- {
- if (olength)
- {
- *olength = 0;
- }
- return NULL;
- }
-
- /* columns: Friend ID, Name, Network */
- column = purple_notify_searchresults_column_new(_("Skype Name"));
- purple_notify_searchresults_column_add(results, column);
- column = purple_notify_searchresults_column_new(_("Display Name"));
- purple_notify_searchresults_column_add(results, column);
- column = purple_notify_searchresults_column_new(_("City"));
- purple_notify_searchresults_column_add(results, column);
- column = purple_notify_searchresults_column_new(_("Country"));
- purple_notify_searchresults_column_add(results, column);
-
- purple_notify_searchresults_button_add(results,
- PURPLE_NOTIFY_BUTTON_ADD,
- skypeweb_search_results_add_buddy);
-
- for(index = 0; index < length; index++)
- {
- JsonObject *result = json_array_get_object_element(resultsarray, index);
- JsonObject *skypecontact = json_object_get_object_member(result, "nodeProfileData");
-
- /* the row in the search results table */
- /* prepend to it backwards then reverse to speed up adds */
- GList *row = NULL;
-
-#define add_skypecontact_row(value) (\
- row = g_list_prepend(row, \
- !json_object_has_member(skypecontact, (value)) ? NULL : \
- g_strdup(json_object_get_string_member(skypecontact, (value)))\
- ) \
-)
- add_skypecontact_row("skypeId");
- add_skypecontact_row("name");
- add_skypecontact_row("city");
- add_skypecontact_row("country");
-
- row = g_list_reverse(row);
-
- purple_notify_searchresults_row_add(results, row);
- }
-
- if (olength)
- {
- *olength = length;
- }
- return results;
-}
-
-static void
-skypeweb_search_users_text_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
-{
- gint length;
- gchar *search_term = user_data;
- PurpleNotifySearchResults *results = create_search_results(node, &length);
-
- if (results == NULL || length == 0)
- {
- gchar *primary_text = g_strdup_printf("Your search for the user \"%s\" returned no results", search_term);
- purple_notify_warning(sa->pc, _("No users found"), primary_text, "", purple_request_cpar_from_connection(sa->pc));
- g_free(primary_text);
- g_free(search_term);
- return;
- }
-
- purple_notify_searchresults(sa->pc, NULL, search_term, NULL, results, NULL, NULL);
-}
-
-static void
-skypeweb_contact_suggestions_received_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
-{
- gint length;
- PurpleNotifySearchResults *results = create_search_results(node, &length);
-
- if (results == NULL || length == 0)
- {
- purple_notify_warning(sa->pc, _("No results"), _("There are no contact suggestions available for you"), "", purple_request_cpar_from_connection(sa->pc));
- return;
- }
-
- purple_notify_searchresults(sa->pc, _("Contact suggestions"), NULL, NULL, results, NULL, NULL);
-}
-
-void
-skypeweb_search_users_text(gpointer user_data, const gchar *text)
-{
- SkypeWebAccount *sa = user_data;
- GString *url = g_string_new("/search/v1.1/namesearch/swx/?");
-
- g_string_append_printf(url, "searchstring=%s&", purple_url_encode(text));
- g_string_append(url, "requestId=1&");
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_GRAPH_HOST, url->str, NULL, skypeweb_search_users_text_cb, g_strdup(text), FALSE);
-
- g_string_free(url, TRUE);
-}
-
-void
-skypeweb_search_users(PurpleProtocolAction *action)
-{
- PurpleConnection *pc = purple_protocol_action_get_connection(action);
- SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
-
- purple_request_input(pc, "Search for Skype Friends",
- "Search for Skype Friends",
- NULL,
- NULL, FALSE, FALSE, NULL,
- _("_Search"), G_CALLBACK(skypeweb_search_users_text),
- _("_Cancel"), NULL,
- purple_request_cpar_from_connection(pc),
- sa);
-
-}
-
-void
-skypeweb_contact_suggestions(PurpleProtocolAction *action)
-{
- PurpleConnection *pc = purple_protocol_action_get_connection(action);
- SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
-
- GString *url = g_string_new("/v1.1/recommend?requestId=1&locale=en-US&count=20");
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_DEFAULT_CONTACT_SUGGESTIONS_HOST, url->str, NULL, skypeweb_contact_suggestions_received_cb, 0, FALSE);
-
- g_string_free(url, TRUE);
-}
-
-static void
-skypeweb_got_friend_profiles(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
-{
- JsonArray *contacts;
- PurpleBuddy *buddy;
- SkypeWebBuddy *sbuddy;
- gint index, length;
-
- if (node == NULL || json_node_get_node_type(node) != JSON_NODE_ARRAY)
- return;
- contacts = json_node_get_array(node);
- length = json_array_get_length(contacts);
-
- for(index = 0; index < length; index++)
- {
- JsonObject *contact = json_array_get_object_element(contacts, index);
-
- const gchar *username = json_object_get_string_member(contact, "username");
- const gchar *new_avatar;
-
- buddy = purple_blist_find_buddy(sa->account, username);
- if (!buddy)
- continue;
- sbuddy = purple_buddy_get_protocol_data(buddy);
- if (sbuddy == NULL) {
- sbuddy = g_new0(SkypeWebBuddy, 1);
- purple_buddy_set_protocol_data(buddy, sbuddy);
- sbuddy->skypename = g_strdup(username);
- sbuddy->sa = sa;
- }
-
- g_free(sbuddy->display_name); sbuddy->display_name = g_strdup(json_object_get_string_member(contact, "displayname"));
- purple_serv_got_alias(sa->pc, username, sbuddy->display_name);
- if (json_object_has_member(contact, "lastname")) {
- gchar *fullname = g_strconcat(json_object_get_string_member(contact, "firstname"), " ", json_object_get_string_member(contact, "lastname"), NULL);
-
- purple_buddy_set_server_alias(buddy, fullname);
-
- g_free(fullname);
- } else {
- purple_buddy_set_server_alias(buddy, json_object_get_string_member(contact, "firstname"));
- }
-
- new_avatar = json_object_get_string_member(contact, "avatarUrl");
- if (new_avatar && *new_avatar && (!sbuddy->avatar_url || !g_str_equal(sbuddy->avatar_url, new_avatar))) {
- g_free(sbuddy->avatar_url);
- sbuddy->avatar_url = g_strdup(new_avatar);
- skypeweb_get_icon(buddy);
- }
-
- g_free(sbuddy->mood); sbuddy->mood = g_strdup(json_object_get_string_member(contact, "mood"));
- }
-}
-
-void
-skypeweb_get_friend_profiles(SkypeWebAccount *sa, GSList *contacts)
-{
- const gchar *profiles_url = "/users/self/contacts/profiles";
- GString *postdata;
- GSList *cur = contacts;
-
- if (contacts == NULL)
- return;
-
- postdata = g_string_new("");
-
- do {
- g_string_append_printf(postdata, "&contacts[]=%s", purple_url_encode(cur->data));
- } while((cur = g_slist_next(cur)));
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, profiles_url, postdata->str, skypeweb_got_friend_profiles, NULL, TRUE);
-
- g_string_free(postdata, TRUE);
-}
-
-
-static void
-skypeweb_got_info(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
-{
- gchar *username = user_data;
- PurpleNotifyUserInfo *user_info;
- JsonObject *userobj;
- PurpleBuddy *buddy;
- SkypeWebBuddy *sbuddy;
-
- if (node == NULL)
- return;
- if (json_node_get_node_type(node) == JSON_NODE_ARRAY)
- node = json_array_get_element(json_node_get_array(node), 0);
- if (json_node_get_node_type(node) != JSON_NODE_OBJECT)
- return;
- userobj = json_node_get_object(node);
-
- user_info = purple_notify_user_info_new();
-
-#define _SKYPE_USER_INFO(prop, key) if (prop && json_object_has_member(userobj, (prop)) && !json_object_get_null_member(userobj, (prop))) \
- purple_notify_user_info_add_pair_html(user_info, _(key), json_object_get_string_member(userobj, (prop)));
-
- _SKYPE_USER_INFO("firstname", "First Name");
- _SKYPE_USER_INFO("lastname", "Last Name");
- _SKYPE_USER_INFO("birthday", "Birthday");
- //_SKYPE_USER_INFO("gender", "Gender");
- if (json_object_has_member(userobj, "gender") && !json_object_get_null_member(userobj, "gender")) {
- const gchar *gender_output = _("Unknown");
-
- // Can be presented as either a string of a number or as a number argh
- if (json_node_get_value_type(json_object_get_member(userobj, "gender")) == G_TYPE_STRING) {
- const gchar *gender = json_object_get_string_member(userobj, "gender");
- if (gender && *gender == '1') {
- gender_output = _("Male");
- } else if (gender && *gender == '2') {
- gender_output = _("Female");
- }
- } else {
- gint64 gender = json_object_get_int_member(userobj, "gender");
- if (gender == 1) {
- gender_output = _("Male");
- } else if (gender == 2) {
- gender_output = _("Female");
- }
- }
-
- purple_notify_user_info_add_pair_html(user_info, _("Gender"), gender_output);
- }
- _SKYPE_USER_INFO("language", "Language");
- _SKYPE_USER_INFO("country", "Country");
- _SKYPE_USER_INFO("province", "Province");
- _SKYPE_USER_INFO("city", "City");
- _SKYPE_USER_INFO("homepage", "Homepage");
- _SKYPE_USER_INFO("about", "About");
- _SKYPE_USER_INFO("jobtitle", "Job Title");
- _SKYPE_USER_INFO("phoneMobile", "Phone - Mobile");
- _SKYPE_USER_INFO("phoneHome", "Phone - Home");
- _SKYPE_USER_INFO("phoneOffice", "Phone - Office");
- //_SKYPE_USER_INFO("mood", "Mood");
- //_SKYPE_USER_INFO("richMood", "Mood");
- //_SKYPE_USER_INFO("avatarUrl", "Avatar");
-
- buddy = purple_blist_find_buddy(sa->account, username);
- if (buddy) {
- sbuddy = purple_buddy_get_protocol_data(buddy);
- if (sbuddy == NULL) {
- sbuddy = g_new0(SkypeWebBuddy, 1);
- purple_buddy_set_protocol_data(buddy, sbuddy);
- sbuddy->skypename = g_strdup(username);
- sbuddy->sa = sa;
- }
-
- //At the moment, "mood" field is present but always null via this call. Do not clear it.
- if (json_object_has_member(userobj, ("mood")) && !json_object_get_null_member(userobj, ("mood"))) {
- g_free(sbuddy->mood); sbuddy->mood = g_strdup(json_object_get_string_member(userobj, "mood"));
- }
- }
-
- purple_notify_userinfo(sa->pc, username, user_info, NULL, NULL);
-
- g_free(username);
-}
-
-void
-skypeweb_get_info(PurpleConnection *pc, const gchar *username)
-{
- SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
- gchar *post;
- const gchar *url = "/users/batch/profiles";
- JsonObject *obj;
- JsonArray *usernames_array;
-
- obj = json_object_new();
- usernames_array = json_array_new();
-
- json_array_add_string_element(usernames_array, username);
- json_object_set_array_member(obj, "usernames", usernames_array);
- post = skypeweb_jsonobj_to_string(obj);
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, url, post, skypeweb_got_info, g_strdup(username), TRUE);
-
- g_free(post);
- json_object_unref(obj);
-}
-
-void
-skypeweb_get_friend_profile(SkypeWebAccount *sa, const gchar *who)
-{
- GSList *contacts = NULL;
- gchar *whodup;
-
- g_return_if_fail(sa && who && *who);
-
- whodup = g_strdup(who);
- contacts = g_slist_prepend(contacts, whodup);
-
- skypeweb_get_friend_profiles(sa, contacts);
-
- g_free(contacts);
- g_free(whodup);
-}
-
-static void
-skypeweb_get_friend_list_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
-{
- JsonObject *obj;
- JsonArray *contacts;
- PurpleGroup *group = NULL;
- GSList *users_to_fetch = NULL;
- guint index, length;
-
- obj = json_node_get_object(node);
- contacts = json_object_get_array_member(obj, "contacts");
- length = json_array_get_length(contacts);
-
- for(index = 0; index < length; index++)
- {
- JsonObject *contact = json_array_get_object_element(contacts, index);
- JsonObject *profile = json_object_get_object_member(contact, "profile");
- const gchar *mri = json_object_get_string_member(contact, "mri");
- const gchar *display_name = json_object_get_string_member(contact, "display_name");
- const gchar *avatar_url = NULL;
- gboolean authorized = json_object_get_boolean_member(contact, "authorized");
- gboolean blocked = json_object_get_boolean_member(contact, "blocked");
-
- const gchar *mood = json_object_get_string_member(profile, "mood");
- JsonObject *name = json_object_get_object_member(profile, "name");
- const gchar *firstname = json_object_get_string_member(name, "first");
- const gchar *surname = NULL;
-
- PurpleBuddy *buddy;
- const gchar *id;
-
- if (json_object_has_member(contact, "suggested") && json_object_get_boolean_member(contact, "suggested") && !authorized) {
- // suggested buddies wtf? some kind of advertising?
- continue;
- }
-
- id = skypeweb_strip_user_prefix(mri);
-
- buddy = purple_blist_find_buddy(sa->account, id);
- if (!buddy)
- {
- if (!group)
- {
- group = purple_blist_find_group("Skype");
- if (!group)
- {
- group = purple_group_new("Skype");
- purple_blist_add_group(group, NULL);
- }
- }
- buddy = purple_buddy_new(sa->account, id, display_name);
- purple_blist_add_buddy(buddy, NULL, group, NULL);
- }
-
- if (name && json_object_has_member(name, "surname"))
- surname = json_object_get_string_member(name, "surname");
-
- // try to free the sbuddy here. no-op if it's not set before, otherwise prevents a leak.
- skypeweb_buddy_free(buddy);
-
- SkypeWebBuddy *sbuddy = g_new0(SkypeWebBuddy, 1);
- sbuddy->skypename = g_strdup(id);
- sbuddy->sa = sa;
- sbuddy->fullname = g_strconcat(firstname, (surname ? " " : NULL), surname, NULL);
- sbuddy->display_name = g_strdup(display_name);
- sbuddy->authorized = authorized;
- sbuddy->blocked = blocked;
- sbuddy->avatar_url = g_strdup(purple_buddy_icons_get_checksum_for_user(buddy));
- sbuddy->mood = g_strdup(mood);
-
- sbuddy->buddy = buddy;
- purple_buddy_set_protocol_data(buddy, sbuddy);
-
- if (!purple_strequal(purple_buddy_get_local_alias(buddy), sbuddy->display_name)) {
- purple_serv_got_alias(sa->pc, id, sbuddy->display_name);
- }
- if (!purple_strequal(purple_buddy_get_server_alias(buddy), sbuddy->fullname)) {
- purple_buddy_set_server_alias(buddy, sbuddy->fullname);
- }
-
- if (json_object_has_member(profile, "avatar_url")) {
- avatar_url = json_object_get_string_member(profile, "avatar_url");
- if (avatar_url && *avatar_url && (!sbuddy->avatar_url || !g_str_equal(sbuddy->avatar_url, avatar_url))) {
- g_free(sbuddy->avatar_url);
- sbuddy->avatar_url = g_strdup(avatar_url);
- skypeweb_get_icon(buddy);
- }
- }
-
- if (blocked == TRUE) {
- purple_account_privacy_deny_add(sa->account, id, TRUE);
- } else {
- users_to_fetch = g_slist_prepend(users_to_fetch, sbuddy->skypename);
- }
-
- if (purple_strequal(skypeweb_strip_user_prefix(id), sa->primary_member_name)) {
- g_free(sa->self_display_name);
- sa->self_display_name = g_strdup(display_name);
- }
- }
-
- if (users_to_fetch)
- {
- //skypeweb_get_friend_profiles(sa, users_to_fetch);
- skypeweb_subscribe_to_contact_status(sa, users_to_fetch);
- g_slist_free(users_to_fetch);
- }
-}
-
-void
-skypeweb_get_friend_list(SkypeWebAccount *sa)
-{
- const gchar *url = "/contacts/v2/users/SELF?delta=&reason=default";
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, url, NULL, skypeweb_get_friend_list_cb, NULL, TRUE);
-}
-
-
-
-void
-skypeweb_auth_accept_cb(
-#if PURPLE_VERSION_CHECK(3, 0, 0)
- const gchar *who,
-#endif
- gpointer sender)
-{
- PurpleBuddy *buddy = sender;
- SkypeWebAccount *sa;
- gchar *url = NULL;
- GSList *users_to_fetch;
- gchar *buddy_name;
-
- sa = purple_connection_get_protocol_data(purple_account_get_connection(purple_buddy_get_account(buddy)));
- buddy_name = g_strdup(purple_buddy_get_name(buddy));
-
- url = g_strdup_printf("/contacts/v2/users/SELF/invites/%s%s/accept", skypeweb_user_url_prefix(buddy_name), purple_url_encode(buddy_name));
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, url, NULL, NULL, NULL, TRUE);
- g_free(url);
-
- // Subscribe to status/message updates
- users_to_fetch = g_slist_prepend(NULL, buddy_name);
- skypeweb_subscribe_to_contact_status(sa, users_to_fetch);
- g_slist_free(users_to_fetch);
- g_free(buddy_name);
-}
-
-void
-skypeweb_auth_reject_cb(
-#if PURPLE_VERSION_CHECK(3, 0, 0)
- const gchar *who,
-#endif
- gpointer sender)
-{
- PurpleBuddy *buddy = sender;
- SkypeWebAccount *sa;
- gchar *url = NULL;
- gchar *buddy_name;
-
- sa = purple_connection_get_protocol_data(purple_account_get_connection(purple_buddy_get_account(buddy)));
- buddy_name = g_strdup(purple_buddy_get_name(buddy));
-
- url = g_strdup_printf("/contacts/v2/users/SELF/invites/%s%s/decline", skypeweb_user_url_prefix(buddy_name), purple_url_encode(buddy_name));
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, url, NULL, NULL, NULL, TRUE);
-
- g_free(url);
- g_free(buddy_name);
-}
-
-static void
-skypeweb_got_authrequests(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
-{
- JsonObject *requests;
- JsonArray *invite_list;
- guint index, length;
- time_t latest_timestamp = 0;
-
- requests = json_node_get_object(node);
- invite_list = json_object_get_array_member(requests, "invite_list");
- length = json_array_get_length(invite_list);
- for(index = 0; index < length; index++)
- {
- JsonObject *invite = json_array_get_object_element(invite_list, index);
- JsonArray *invites = json_object_get_array_member(invite, "invites");
- const gchar *event_time_iso = json_object_get_string_member(json_array_get_object_element(invites, 0), "time");
- time_t event_timestamp = purple_str_to_time(event_time_iso, TRUE, NULL, NULL, NULL);
- const gchar *sender = json_object_get_string_member(invite, "mri");
- const gchar *greeting = json_object_get_string_member(invite, "greeting");
- if (!greeting)
- greeting = json_object_get_string_member(json_array_get_object_element(invites, 0), "message");
- const gchar *displayname = json_object_get_string_member(invite, "displayname");
-
- latest_timestamp = MAX(latest_timestamp, event_timestamp);
- if (sa->last_authrequest && event_timestamp <= sa->last_authrequest)
- continue;
-
- if (sender == NULL)
- continue;
- sender = skypeweb_strip_user_prefix(sender);
-
- purple_account_request_authorization(
- sa->account, sender, NULL,
- displayname, greeting, FALSE,
- skypeweb_auth_accept_cb, skypeweb_auth_reject_cb, purple_buddy_new(sa->account, sender, displayname));
-
- }
-
- sa->last_authrequest = latest_timestamp;
-}
-
-gboolean
-skypeweb_check_authrequests(SkypeWebAccount *sa)
-{
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, "/contacts/v2/users/SELF/invites", NULL, skypeweb_got_authrequests, NULL, TRUE);
- return TRUE;
-}
-
-
-void
-skypeweb_add_buddy_with_invite(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group, const char *message)
-{
- SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
- gchar *postdata;
- const gchar *url = "/contacts/v2/users/SELF/contacts";
- GSList *users_to_fetch;
- JsonObject *obj;
- gchar *buddy_name, *mri;
-
- //https://contacts.skype.com/contacts/v2/users/SELF/contacts
- // POST {"mri":"2:eionrobb@dequis.onmicrosoft.com","greeting":"Hi, eionrobb@dequis.onmicrosoft.com, I'd like to add you as a contact."}
-
- buddy_name = g_strdup(purple_buddy_get_name(buddy));
- mri = g_strconcat(skypeweb_user_url_prefix(buddy_name), buddy_name, NULL);
-
- obj = json_object_new();
-
- json_object_set_string_member(obj, "mri", mri);
- json_object_set_string_member(obj, "greeting", message ? message : _("Please authorize me so I can add you to my buddy list."));
- postdata = skypeweb_jsonobj_to_string(obj);
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, url, postdata, NULL, NULL, TRUE);
-
- g_free(mri);
- g_free(postdata);
- json_object_unref(obj);
-
- // Subscribe to status/message updates
- users_to_fetch = g_slist_prepend(NULL, buddy_name);
- skypeweb_subscribe_to_contact_status(sa, users_to_fetch);
- g_slist_free(users_to_fetch);
-
- g_free(buddy_name);
-}
-
-void
-skypeweb_add_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group)
-{
- skypeweb_add_buddy_with_invite(pc, buddy, group, NULL);
-}
-
-void
-skypeweb_buddy_remove(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group)
-{
- SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
- gchar *url;
- const gchar *buddy_name = purple_buddy_get_name(buddy);
-
- url = g_strdup_printf("/contacts/v2/users/SELF/contacts/%s%s", skypeweb_user_url_prefix(buddy_name), purple_url_encode(buddy_name));
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_DELETE | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, url, NULL, NULL, NULL, TRUE);
-
- g_free(url);
-
- skypeweb_unsubscribe_from_contact_status(sa, buddy_name);
-}
-
-void
-skypeweb_buddy_block(PurpleConnection *pc, const char *name)
-{
- SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
- gchar *url;
- const gchar *postdata;
-
- url = g_strdup_printf("/contacts/v2/users/SELF/contacts/blocklist/%s%s", skypeweb_user_url_prefix(name), purple_url_encode(name));
- postdata = "{\"report_abuse\":\"false\",\"ui_version\":\"skype.com\"}";
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, url, postdata, NULL, NULL, TRUE);
-
- g_free(url);
-}
-
-void
-skypeweb_buddy_unblock(PurpleConnection *pc, const char *name)
-{
- SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
- gchar *url;
-
- url = g_strdup_printf("/contacts/v2/users/SELF/contacts/blocklist/%s%s", skypeweb_user_url_prefix(name), purple_url_encode(name));
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_DELETE | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, url, NULL, NULL, NULL, TRUE);
-
- g_free(url);
-}
-
-
-void
-skypeweb_set_mood_message(SkypeWebAccount *sa, const gchar *mood)
-{
- JsonObject *obj, *payload;
- gchar *post;
-
- obj = json_object_new();
- payload = json_object_new();
-
- json_object_set_string_member(payload, "mood", mood ? mood : "");
- json_object_set_object_member(obj, "payload", payload);
- post = skypeweb_jsonobj_to_string(obj);
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, "/users/self/profile/partial", post, NULL, NULL, TRUE);
-
- g_free(post);
- json_object_unref(obj);
-}
+
+}
+
+static void
+skypeweb_xfer_send_init(PurpleXfer *xfer)
+{
+ PurpleConnection *pc = purple_account_get_connection(purple_xfer_get_account(xfer));
+ SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
+ gchar *basename = g_path_get_basename(purple_xfer_get_local_filename(xfer));
+ gchar *id, *post;
+ SkypeWebFileTransfer *swft = purple_xfer_get_protocol_data(xfer);
+ JsonObject *obj = json_object_new();
+ JsonObject *permissions = json_object_new();
+ JsonArray *userpermissions = json_array_new();
+ PurpleHttpRequest *request;
+
+ purple_xfer_set_filename(xfer, basename);
+ purple_xfer_ref(xfer);
+
+ json_object_set_string_member(obj, "type", "sharing/file");
+ json_object_set_string_member(obj, "filename", basename);
+
+ id = g_strconcat(skypeweb_user_url_prefix(swft->from), swft->from, NULL);
+ json_array_add_string_element(userpermissions, "read");
+ json_object_set_array_member(permissions, id, userpermissions);
+ json_object_set_object_member(obj, "permissions", permissions);
+
+ post = skypeweb_jsonobj_to_string(obj);
+ //POST to api.asm.skype.com /v1/objects
+ //{"type":"sharing/file","permissions":{"8:eionrobb":["read"]},"filename":"GiantLobsterMoose.txt"}
+
+ request = purple_http_request_new("https://" SKYPEWEB_XFER_HOST "/v1/objects");
+ purple_http_request_set_method(request, "POST");
+ purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
+ purple_http_request_header_set_printf(request, "Authorization", "skype_token %s", sa->skype_token); //slightly different to normal!
+ purple_http_request_header_set(request, "Content-Type", "application/json");
+ purple_http_request_header_set(request, "X-Client-Version", SKYPEWEB_CLIENTINFO_VERSION);
+ purple_http_request_set_contents(request, post, -1);
+ purple_http_request(sa->pc, request, skypeweb_got_object_for_file, swft);
+ purple_http_request_unref(request);
+
+ g_free(post);
+ json_object_unref(obj);
+ g_free(id);
+ g_free(basename);
+}
+
+PurpleXfer *
+skypeweb_new_xfer(
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+PurpleProtocolXfer *prplxfer,
+#endif
+PurpleConnection *pc, const char *who)
+{
+ SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
+ PurpleXfer *xfer;
+ SkypeWebFileTransfer *swft;
+
+ xfer = purple_xfer_new(sa->account, PURPLE_XFER_TYPE_SEND, who);
+
+ swft = g_new0(SkypeWebFileTransfer, 1);
+ swft->sa = sa;
+ swft->from = g_strdup(who);
+ swft->xfer = xfer;
+ purple_xfer_set_protocol_data(xfer, swft);
+
+ purple_xfer_set_init_fnc(xfer, skypeweb_xfer_send_init);
+ //purple_xfer_set_write_fnc(xfer, skypeweb_xfer_send_write);
+ //purple_xfer_set_end_fnc(xfer, skypeweb_xfer_send_end);
+ purple_xfer_set_request_denied_fnc(xfer, skypeweb_free_xfer);
+ purple_xfer_set_cancel_send_fnc(xfer, skypeweb_free_xfer);
+
+ return xfer;
+}
+
+void
+skypeweb_send_file(
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+PurpleProtocolXfer *prplxfer,
+#endif
+PurpleConnection *pc, const gchar *who, const gchar *filename)
+{
+ PurpleXfer *xfer = skypeweb_new_xfer(
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+ prplxfer,
+#endif
+ pc, who);
+
+ if (filename && *filename)
+ purple_xfer_request_accepted(xfer, filename);
+ else
+ purple_xfer_request(xfer);
+}
+
+gboolean
+skypeweb_can_receive_file(
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+PurpleProtocolXfer *prplxfer,
+#endif
+PurpleConnection *pc, const gchar *who)
+{
+ if (!who || g_str_equal(who, purple_account_get_username(purple_connection_get_account(pc))))
+ return FALSE;
+
+ return TRUE;
+}
+
+
+static void
+skypeweb_got_self_details(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ JsonObject *userobj;
+ const gchar *old_alias;
+ const gchar *displayname = NULL;
+ const gchar *username;
+
+ if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
+ return;
+ userobj = json_node_get_object(node);
+
+ username = json_object_get_string_member(userobj, "username");
+ g_free(sa->username); sa->username = g_strdup(username);
+ purple_connection_set_display_name(sa->pc, sa->username);
+
+ old_alias = purple_account_get_private_alias(sa->account);
+ if (!old_alias || !*old_alias) {
+ if (json_object_has_member(userobj, "displayname"))
+ displayname = json_object_get_string_member(userobj, "displayname");
+ if (!displayname || purple_strequal(displayname, username))
+ displayname = json_object_get_string_member(userobj, "firstname");
+
+ if (displayname)
+ purple_account_set_private_alias(sa->account, displayname);
+ }
+
+ if (!PURPLE_CONNECTION_IS_CONNECTED(sa->pc)) {
+ skypeweb_do_all_the_things(sa);
+ }
+
+ skypeweb_gather_self_properties(sa);
+}
+
+
+void
+skypeweb_get_self_details(SkypeWebAccount *sa)
+{
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, "/users/self/profile", NULL, skypeweb_got_self_details, NULL, TRUE);
+}
+
+
+
+
+
+
+
+
+
+void
+skypeweb_search_results_add_buddy(PurpleConnection *pc, GList *row, void *user_data)
+{
+ PurpleAccount *account = purple_connection_get_account(pc);
+
+ if (!purple_blist_find_buddy(account, g_list_nth_data(row, 0)))
+ purple_blist_request_add_buddy(account, g_list_nth_data(row, 0), "Skype", g_list_nth_data(row, 1));
+}
+
+void
+skypeweb_received_contacts(SkypeWebAccount *sa, PurpleXmlNode *contacts)
+{
+ PurpleNotifySearchResults *results;
+ PurpleNotifySearchColumn *column;
+
+ PurpleXmlNode *contact;
+
+ results = purple_notify_searchresults_new();
+ if (results == NULL) {
+ return;
+ }
+
+ /* columns: Friend ID, Name */
+ column = purple_notify_searchresults_column_new(_("Skype Name"));
+ purple_notify_searchresults_column_add(results, column);
+ column = purple_notify_searchresults_column_new(_("Display Name"));
+ purple_notify_searchresults_column_add(results, column);
+
+
+ purple_notify_searchresults_button_add(results,
+ PURPLE_NOTIFY_BUTTON_ADD,
+ skypeweb_search_results_add_buddy);
+
+ for(contact = purple_xmlnode_get_child(contacts, "c"); contact;
+ contact = purple_xmlnode_get_next_twin(contact))
+ {
+ GList *row = NULL;
+
+ gchar *contact_id = g_strdup(purple_xmlnode_get_attrib(contact, "s"));
+ gchar *contact_name = g_strdup(purple_xmlnode_get_attrib(contact, "f"));
+
+ row = g_list_append(row, contact_id);
+ row = g_list_append(row, contact_name);
+
+ purple_notify_searchresults_row_add(results, row);
+ }
+
+ purple_notify_searchresults(sa->pc, _("Received contacts"), NULL, NULL, results, NULL, NULL);
+}
+
+static PurpleNotifySearchResults*
+create_search_results(JsonNode *node, gint *olength)
+{
+ PurpleNotifySearchColumn *column;
+ gint index, length;
+ JsonObject *response = NULL;
+ JsonArray *resultsarray = NULL;
+
+ response = json_node_get_object(node);
+ resultsarray = json_object_get_array_member(response, "results");
+ length = json_array_get_length(resultsarray);
+
+ PurpleNotifySearchResults *results = purple_notify_searchresults_new();
+ if (results == NULL || length == 0)
+ {
+ if (olength)
+ {
+ *olength = 0;
+ }
+ return NULL;
+ }
+
+ /* columns: Friend ID, Name, Network */
+ column = purple_notify_searchresults_column_new(_("Skype Name"));
+ purple_notify_searchresults_column_add(results, column);
+ column = purple_notify_searchresults_column_new(_("Display Name"));
+ purple_notify_searchresults_column_add(results, column);
+ column = purple_notify_searchresults_column_new(_("City"));
+ purple_notify_searchresults_column_add(results, column);
+ column = purple_notify_searchresults_column_new(_("Country"));
+ purple_notify_searchresults_column_add(results, column);
+
+ purple_notify_searchresults_button_add(results,
+ PURPLE_NOTIFY_BUTTON_ADD,
+ skypeweb_search_results_add_buddy);
+
+ for(index = 0; index < length; index++)
+ {
+ JsonObject *result = json_array_get_object_element(resultsarray, index);
+ JsonObject *skypecontact = json_object_get_object_member(result, "nodeProfileData");
+
+ /* the row in the search results table */
+ /* prepend to it backwards then reverse to speed up adds */
+ GList *row = NULL;
+
+#define add_skypecontact_row(value) (\
+ row = g_list_prepend(row, \
+ !json_object_has_member(skypecontact, (value)) ? NULL : \
+ g_strdup(json_object_get_string_member(skypecontact, (value)))\
+ ) \
+)
+ add_skypecontact_row("skypeId");
+ add_skypecontact_row("name");
+ add_skypecontact_row("city");
+ add_skypecontact_row("country");
+
+ row = g_list_reverse(row);
+
+ purple_notify_searchresults_row_add(results, row);
+ }
+
+ if (olength)
+ {
+ *olength = length;
+ }
+ return results;
+}
+
+static void
+skypeweb_search_users_text_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ gint length;
+ gchar *search_term = user_data;
+ PurpleNotifySearchResults *results = create_search_results(node, &length);
+
+ if (results == NULL || length == 0)
+ {
+ gchar *primary_text = g_strdup_printf("Your search for the user \"%s\" returned no results", search_term);
+ purple_notify_warning(sa->pc, _("No users found"), primary_text, "", purple_request_cpar_from_connection(sa->pc));
+ g_free(primary_text);
+ g_free(search_term);
+ return;
+ }
+
+ purple_notify_searchresults(sa->pc, NULL, search_term, NULL, results, NULL, NULL);
+}
+
+static void
+skypeweb_contact_suggestions_received_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ gint length;
+ PurpleNotifySearchResults *results = create_search_results(node, &length);
+
+ if (results == NULL || length == 0)
+ {
+ purple_notify_warning(sa->pc, _("No results"), _("There are no contact suggestions available for you"), "", purple_request_cpar_from_connection(sa->pc));
+ return;
+ }
+
+ purple_notify_searchresults(sa->pc, _("Contact suggestions"), NULL, NULL, results, NULL, NULL);
+}
+
+void
+skypeweb_search_users_text(gpointer user_data, const gchar *text)
+{
+ SkypeWebAccount *sa = user_data;
+ GString *url = g_string_new("/search/v1.1/namesearch/swx/?");
+
+ g_string_append_printf(url, "searchstring=%s&", purple_url_encode(text));
+ g_string_append(url, "requestId=1&");
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_GRAPH_HOST, url->str, NULL, skypeweb_search_users_text_cb, g_strdup(text), FALSE);
+
+ g_string_free(url, TRUE);
+}
+
+void
+skypeweb_search_users(PurpleProtocolAction *action)
+{
+ PurpleConnection *pc = purple_protocol_action_get_connection(action);
+ SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
+
+ purple_request_input(pc, "Search for Skype Friends",
+ "Search for Skype Friends",
+ NULL,
+ NULL, FALSE, FALSE, NULL,
+ _("_Search"), G_CALLBACK(skypeweb_search_users_text),
+ _("_Cancel"), NULL,
+ purple_request_cpar_from_connection(pc),
+ sa);
+
+}
+
+void
+skypeweb_contact_suggestions(PurpleProtocolAction *action)
+{
+ PurpleConnection *pc = purple_protocol_action_get_connection(action);
+ SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
+
+ GString *url = g_string_new("/v1.1/recommend?requestId=1&locale=en-US&count=20");
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_DEFAULT_CONTACT_SUGGESTIONS_HOST, url->str, NULL, skypeweb_contact_suggestions_received_cb, 0, FALSE);
+
+ g_string_free(url, TRUE);
+}
+
+static void
+skypeweb_got_friend_profiles(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ JsonArray *contacts;
+ PurpleBuddy *buddy;
+ SkypeWebBuddy *sbuddy;
+ gint index, length;
+
+ if (node == NULL || json_node_get_node_type(node) != JSON_NODE_ARRAY)
+ return;
+ contacts = json_node_get_array(node);
+ length = json_array_get_length(contacts);
+
+ for(index = 0; index < length; index++)
+ {
+ JsonObject *contact = json_array_get_object_element(contacts, index);
+
+ const gchar *username = json_object_get_string_member(contact, "username");
+ const gchar *new_avatar;
+
+ buddy = purple_blist_find_buddy(sa->account, username);
+ if (!buddy)
+ continue;
+ sbuddy = purple_buddy_get_protocol_data(buddy);
+ if (sbuddy == NULL) {
+ sbuddy = g_new0(SkypeWebBuddy, 1);
+ purple_buddy_set_protocol_data(buddy, sbuddy);
+ sbuddy->skypename = g_strdup(username);
+ sbuddy->sa = sa;
+ }
+
+ g_free(sbuddy->display_name); sbuddy->display_name = g_strdup(json_object_get_string_member(contact, "displayname"));
+ purple_serv_got_alias(sa->pc, username, sbuddy->display_name);
+ if (json_object_has_member(contact, "lastname")) {
+ gchar *fullname = g_strconcat(json_object_get_string_member(contact, "firstname"), " ", json_object_get_string_member(contact, "lastname"), NULL);
+
+ purple_buddy_set_server_alias(buddy, fullname);
+
+ g_free(fullname);
+ } else {
+ purple_buddy_set_server_alias(buddy, json_object_get_string_member(contact, "firstname"));
+ }
+
+ new_avatar = json_object_get_string_member(contact, "avatarUrl");
+ if (new_avatar && *new_avatar && (!sbuddy->avatar_url || !g_str_equal(sbuddy->avatar_url, new_avatar))) {
+ g_free(sbuddy->avatar_url);
+ sbuddy->avatar_url = g_strdup(new_avatar);
+ skypeweb_get_icon(buddy);
+ }
+
+ g_free(sbuddy->mood); sbuddy->mood = g_strdup(json_object_get_string_member(contact, "mood"));
+ }
+}
+
+void
+skypeweb_get_friend_profiles(SkypeWebAccount *sa, GSList *contacts)
+{
+ const gchar *profiles_url = "/users/self/contacts/profiles";
+ GString *postdata;
+ GSList *cur = contacts;
+
+ if (contacts == NULL)
+ return;
+
+ postdata = g_string_new("");
+
+ do {
+ g_string_append_printf(postdata, "&contacts[]=%s", purple_url_encode(cur->data));
+ } while((cur = g_slist_next(cur)));
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, profiles_url, postdata->str, skypeweb_got_friend_profiles, NULL, TRUE);
+
+ g_string_free(postdata, TRUE);
+}
+
+
+static void
+skypeweb_got_info(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ gchar *username = user_data;
+ PurpleNotifyUserInfo *user_info;
+ JsonObject *userobj;
+ PurpleBuddy *buddy;
+ SkypeWebBuddy *sbuddy;
+
+ if (node == NULL)
+ return;
+ if (json_node_get_node_type(node) == JSON_NODE_ARRAY)
+ node = json_array_get_element(json_node_get_array(node), 0);
+ if (json_node_get_node_type(node) != JSON_NODE_OBJECT)
+ return;
+ userobj = json_node_get_object(node);
+
+ user_info = purple_notify_user_info_new();
+
+#define _SKYPE_USER_INFO(prop, key) if (prop && json_object_has_member(userobj, (prop)) && !json_object_get_null_member(userobj, (prop))) \
+ purple_notify_user_info_add_pair_html(user_info, _(key), json_object_get_string_member(userobj, (prop)));
+
+ _SKYPE_USER_INFO("firstname", "First Name");
+ _SKYPE_USER_INFO("lastname", "Last Name");
+ _SKYPE_USER_INFO("birthday", "Birthday");
+ //_SKYPE_USER_INFO("gender", "Gender");
+ if (json_object_has_member(userobj, "gender") && !json_object_get_null_member(userobj, "gender")) {
+ const gchar *gender_output = _("Unknown");
+
+ // Can be presented as either a string of a number or as a number argh
+ if (json_node_get_value_type(json_object_get_member(userobj, "gender")) == G_TYPE_STRING) {
+ const gchar *gender = json_object_get_string_member(userobj, "gender");
+ if (gender && *gender == '1') {
+ gender_output = _("Male");
+ } else if (gender && *gender == '2') {
+ gender_output = _("Female");
+ }
+ } else {
+ gint64 gender = json_object_get_int_member(userobj, "gender");
+ if (gender == 1) {
+ gender_output = _("Male");
+ } else if (gender == 2) {
+ gender_output = _("Female");
+ }
+ }
+
+ purple_notify_user_info_add_pair_html(user_info, _("Gender"), gender_output);
+ }
+ _SKYPE_USER_INFO("language", "Language");
+ _SKYPE_USER_INFO("country", "Country");
+ _SKYPE_USER_INFO("province", "Province");
+ _SKYPE_USER_INFO("city", "City");
+ _SKYPE_USER_INFO("homepage", "Homepage");
+ _SKYPE_USER_INFO("about", "About");
+ _SKYPE_USER_INFO("jobtitle", "Job Title");
+ _SKYPE_USER_INFO("phoneMobile", "Phone - Mobile");
+ _SKYPE_USER_INFO("phoneHome", "Phone - Home");
+ _SKYPE_USER_INFO("phoneOffice", "Phone - Office");
+ //_SKYPE_USER_INFO("mood", "Mood");
+ //_SKYPE_USER_INFO("richMood", "Mood");
+ //_SKYPE_USER_INFO("avatarUrl", "Avatar");
+
+ buddy = purple_blist_find_buddy(sa->account, username);
+ if (buddy) {
+ sbuddy = purple_buddy_get_protocol_data(buddy);
+ if (sbuddy == NULL) {
+ sbuddy = g_new0(SkypeWebBuddy, 1);
+ purple_buddy_set_protocol_data(buddy, sbuddy);
+ sbuddy->skypename = g_strdup(username);
+ sbuddy->sa = sa;
+ }
+
+ //At the moment, "mood" field is present but always null via this call. Do not clear it.
+ if (json_object_has_member(userobj, ("mood")) && !json_object_get_null_member(userobj, ("mood"))) {
+ g_free(sbuddy->mood); sbuddy->mood = g_strdup(json_object_get_string_member(userobj, "mood"));
+ }
+ }
+
+ purple_notify_userinfo(sa->pc, username, user_info, NULL, NULL);
+
+ g_free(username);
+}
+
+void
+skypeweb_get_info(PurpleConnection *pc, const gchar *username)
+{
+ SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
+ gchar *post;
+ const gchar *url = "/users/batch/profiles";
+ JsonObject *obj;
+ JsonArray *usernames_array;
+
+ obj = json_object_new();
+ usernames_array = json_array_new();
+
+ json_array_add_string_element(usernames_array, username);
+ json_object_set_array_member(obj, "usernames", usernames_array);
+ post = skypeweb_jsonobj_to_string(obj);
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, url, post, skypeweb_got_info, g_strdup(username), TRUE);
+
+ g_free(post);
+ json_object_unref(obj);
+}
+
+void
+skypeweb_get_friend_profile(SkypeWebAccount *sa, const gchar *who)
+{
+ GSList *contacts = NULL;
+ gchar *whodup;
+
+ g_return_if_fail(sa && who && *who);
+
+ whodup = g_strdup(who);
+ contacts = g_slist_prepend(contacts, whodup);
+
+ skypeweb_get_friend_profiles(sa, contacts);
+
+ g_free(contacts);
+ g_free(whodup);
+}
+
+static void
+skypeweb_get_friend_list_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ JsonObject *obj;
+ JsonArray *contacts;
+ PurpleGroup *group = NULL;
+ GSList *users_to_fetch = NULL;
+ guint index, length;
+
+ obj = json_node_get_object(node);
+ contacts = json_object_get_array_member(obj, "contacts");
+ length = json_array_get_length(contacts);
+
+ for(index = 0; index < length; index++)
+ {
+ JsonObject *contact = json_array_get_object_element(contacts, index);
+ JsonObject *profile = json_object_get_object_member(contact, "profile");
+ const gchar *mri = json_object_get_string_member(contact, "mri");
+ const gchar *display_name = json_object_get_string_member(contact, "display_name");
+ const gchar *avatar_url = NULL;
+ gboolean authorized = json_object_get_boolean_member(contact, "authorized");
+ gboolean blocked = json_object_get_boolean_member(contact, "blocked");
+
+ const gchar *mood = json_object_get_string_member(profile, "mood");
+ JsonObject *name = json_object_get_object_member(profile, "name");
+ const gchar *firstname = json_object_get_string_member(name, "first");
+ const gchar *surname = NULL;
+
+ PurpleBuddy *buddy;
+ const gchar *id;
+
+ if (json_object_has_member(contact, "suggested") && json_object_get_boolean_member(contact, "suggested") && !authorized) {
+ // suggested buddies wtf? some kind of advertising?
+ continue;
+ }
+
+ id = skypeweb_strip_user_prefix(mri);
+
+ buddy = purple_blist_find_buddy(sa->account, id);
+ if (!buddy)
+ {
+ if (!group)
+ {
+ group = purple_blist_find_group("Skype");
+ if (!group)
+ {
+ group = purple_group_new("Skype");
+ purple_blist_add_group(group, NULL);
+ }
+ }
+ buddy = purple_buddy_new(sa->account, id, display_name);
+ purple_blist_add_buddy(buddy, NULL, group, NULL);
+ }
+
+ if (name && json_object_has_member(name, "surname"))
+ surname = json_object_get_string_member(name, "surname");
+
+ // try to free the sbuddy here. no-op if it's not set before, otherwise prevents a leak.
+ skypeweb_buddy_free(buddy);
+
+ SkypeWebBuddy *sbuddy = g_new0(SkypeWebBuddy, 1);
+ sbuddy->skypename = g_strdup(id);
+ sbuddy->sa = sa;
+ sbuddy->fullname = g_strconcat(firstname, (surname ? " " : NULL), surname, NULL);
+ sbuddy->display_name = g_strdup(display_name);
+ sbuddy->authorized = authorized;
+ sbuddy->blocked = blocked;
+ sbuddy->avatar_url = g_strdup(purple_buddy_icons_get_checksum_for_user(buddy));
+ sbuddy->mood = g_strdup(mood);
+
+ sbuddy->buddy = buddy;
+ purple_buddy_set_protocol_data(buddy, sbuddy);
+
+ if (!purple_strequal(purple_buddy_get_local_alias(buddy), sbuddy->display_name)) {
+ purple_serv_got_alias(sa->pc, id, sbuddy->display_name);
+ }
+ if (!purple_strequal(purple_buddy_get_server_alias(buddy), sbuddy->fullname)) {
+ purple_buddy_set_server_alias(buddy, sbuddy->fullname);
+ }
+
+ if (json_object_has_member(profile, "avatar_url")) {
+ avatar_url = json_object_get_string_member(profile, "avatar_url");
+ if (avatar_url && *avatar_url && (!sbuddy->avatar_url || !g_str_equal(sbuddy->avatar_url, avatar_url))) {
+ g_free(sbuddy->avatar_url);
+ sbuddy->avatar_url = g_strdup(avatar_url);
+ skypeweb_get_icon(buddy);
+ }
+ }
+
+ if (blocked == TRUE) {
+ purple_account_privacy_deny_add(sa->account, id, TRUE);
+ } else {
+ users_to_fetch = g_slist_prepend(users_to_fetch, sbuddy->skypename);
+ }
+
+ if (purple_strequal(skypeweb_strip_user_prefix(id), sa->primary_member_name)) {
+ g_free(sa->self_display_name);
+ sa->self_display_name = g_strdup(display_name);
+ }
+ }
+
+ if (users_to_fetch)
+ {
+ //skypeweb_get_friend_profiles(sa, users_to_fetch);
+ skypeweb_subscribe_to_contact_status(sa, users_to_fetch);
+ g_slist_free(users_to_fetch);
+ }
+}
+
+void
+skypeweb_get_friend_list(SkypeWebAccount *sa)
+{
+ const gchar *url = "/contacts/v2/users/SELF?delta=&reason=default";
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, url, NULL, skypeweb_get_friend_list_cb, NULL, TRUE);
+}
+
+
+
+void
+skypeweb_auth_accept_cb(
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+ const gchar *who,
+#endif
+ gpointer sender)
+{
+ PurpleBuddy *buddy = sender;
+ SkypeWebAccount *sa;
+ gchar *url = NULL;
+ GSList *users_to_fetch;
+ gchar *buddy_name;
+
+ sa = purple_connection_get_protocol_data(purple_account_get_connection(purple_buddy_get_account(buddy)));
+ buddy_name = g_strdup(purple_buddy_get_name(buddy));
+
+ url = g_strdup_printf("/contacts/v2/users/SELF/invites/%s%s/accept", skypeweb_user_url_prefix(buddy_name), purple_url_encode(buddy_name));
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, url, NULL, NULL, NULL, TRUE);
+ g_free(url);
+
+ // Subscribe to status/message updates
+ users_to_fetch = g_slist_prepend(NULL, buddy_name);
+ skypeweb_subscribe_to_contact_status(sa, users_to_fetch);
+ g_slist_free(users_to_fetch);
+ g_free(buddy_name);
+}
+
+void
+skypeweb_auth_reject_cb(
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+ const gchar *who,
+#endif
+ gpointer sender)
+{
+ PurpleBuddy *buddy = sender;
+ SkypeWebAccount *sa;
+ gchar *url = NULL;
+ gchar *buddy_name;
+
+ sa = purple_connection_get_protocol_data(purple_account_get_connection(purple_buddy_get_account(buddy)));
+ buddy_name = g_strdup(purple_buddy_get_name(buddy));
+
+ url = g_strdup_printf("/contacts/v2/users/SELF/invites/%s%s/decline", skypeweb_user_url_prefix(buddy_name), purple_url_encode(buddy_name));
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, url, NULL, NULL, NULL, TRUE);
+
+ g_free(url);
+ g_free(buddy_name);
+}
+
+static void
+skypeweb_got_authrequests(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ JsonObject *requests;
+ JsonArray *invite_list;
+ guint index, length;
+ time_t latest_timestamp = 0;
+
+ requests = json_node_get_object(node);
+ invite_list = json_object_get_array_member(requests, "invite_list");
+ length = json_array_get_length(invite_list);
+ for(index = 0; index < length; index++)
+ {
+ JsonObject *invite = json_array_get_object_element(invite_list, index);
+ JsonArray *invites = json_object_get_array_member(invite, "invites");
+ const gchar *event_time_iso = json_object_get_string_member(json_array_get_object_element(invites, 0), "time");
+ time_t event_timestamp = purple_str_to_time(event_time_iso, TRUE, NULL, NULL, NULL);
+ const gchar *sender = json_object_get_string_member(invite, "mri");
+ const gchar *greeting = json_object_get_string_member(invite, "greeting");
+ if (!greeting)
+ greeting = json_object_get_string_member(json_array_get_object_element(invites, 0), "message");
+ const gchar *displayname = json_object_get_string_member(invite, "displayname");
+
+ latest_timestamp = MAX(latest_timestamp, event_timestamp);
+ if (sa->last_authrequest && event_timestamp <= sa->last_authrequest)
+ continue;
+
+ if (sender == NULL)
+ continue;
+ sender = skypeweb_strip_user_prefix(sender);
+
+ purple_account_request_authorization(
+ sa->account, sender, NULL,
+ displayname, greeting, FALSE,
+ skypeweb_auth_accept_cb, skypeweb_auth_reject_cb, purple_buddy_new(sa->account, sender, displayname));
+
+ }
+
+ sa->last_authrequest = latest_timestamp;
+}
+
+gboolean
+skypeweb_check_authrequests(SkypeWebAccount *sa)
+{
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, "/contacts/v2/users/SELF/invites", NULL, skypeweb_got_authrequests, NULL, TRUE);
+ return TRUE;
+}
+
+
+void
+skypeweb_add_buddy_with_invite(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group, const char *message)
+{
+ SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
+ gchar *postdata;
+ const gchar *url = "/contacts/v2/users/SELF/contacts";
+ GSList *users_to_fetch;
+ JsonObject *obj;
+ gchar *buddy_name, *mri;
+
+ //https://contacts.skype.com/contacts/v2/users/SELF/contacts
+ // POST {"mri":"2:eionrobb@dequis.onmicrosoft.com","greeting":"Hi, eionrobb@dequis.onmicrosoft.com, I'd like to add you as a contact."}
+
+ buddy_name = g_strdup(purple_buddy_get_name(buddy));
+ mri = g_strconcat(skypeweb_user_url_prefix(buddy_name), buddy_name, NULL);
+
+ obj = json_object_new();
+
+ json_object_set_string_member(obj, "mri", mri);
+ json_object_set_string_member(obj, "greeting", message ? message : _("Please authorize me so I can add you to my buddy list."));
+ postdata = skypeweb_jsonobj_to_string(obj);
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, url, postdata, NULL, NULL, TRUE);
+
+ g_free(mri);
+ g_free(postdata);
+ json_object_unref(obj);
+
+ // Subscribe to status/message updates
+ users_to_fetch = g_slist_prepend(NULL, buddy_name);
+ skypeweb_subscribe_to_contact_status(sa, users_to_fetch);
+ g_slist_free(users_to_fetch);
+
+ g_free(buddy_name);
+}
+
+void
+skypeweb_add_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group)
+{
+ skypeweb_add_buddy_with_invite(pc, buddy, group, NULL);
+}
+
+void
+skypeweb_buddy_remove(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group)
+{
+ SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
+ gchar *url;
+ const gchar *buddy_name = purple_buddy_get_name(buddy);
+
+ url = g_strdup_printf("/contacts/v2/users/SELF/contacts/%s%s", skypeweb_user_url_prefix(buddy_name), purple_url_encode(buddy_name));
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_DELETE | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, url, NULL, NULL, NULL, TRUE);
+
+ g_free(url);
+
+ skypeweb_unsubscribe_from_contact_status(sa, buddy_name);
+}
+
+void
+skypeweb_buddy_block(PurpleConnection *pc, const char *name)
+{
+ SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
+ gchar *url;
+ const gchar *postdata;
+
+ url = g_strdup_printf("/contacts/v2/users/SELF/contacts/blocklist/%s%s", skypeweb_user_url_prefix(name), purple_url_encode(name));
+ postdata = "{\"report_abuse\":\"false\",\"ui_version\":\"skype.com\"}";
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, url, postdata, NULL, NULL, TRUE);
+
+ g_free(url);
+}
+
+void
+skypeweb_buddy_unblock(PurpleConnection *pc, const char *name)
+{
+ SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
+ gchar *url;
+
+ url = g_strdup_printf("/contacts/v2/users/SELF/contacts/blocklist/%s%s", skypeweb_user_url_prefix(name), purple_url_encode(name));
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_DELETE | SKYPEWEB_METHOD_SSL, SKYPEWEB_NEW_CONTACTS_HOST, url, NULL, NULL, NULL, TRUE);
+
+ g_free(url);
+}
+
+
+void
+skypeweb_set_mood_message(SkypeWebAccount *sa, const gchar *mood)
+{
+ JsonObject *obj, *payload;
+ gchar *post;
+
+ obj = json_object_new();
+ payload = json_object_new();
+
+ json_object_set_string_member(payload, "mood", mood ? mood : "");
+ json_object_set_object_member(obj, "payload", payload);
+ post = skypeweb_jsonobj_to_string(obj);
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, "/users/self/profile/partial", post, NULL, NULL, TRUE);
+
+ g_free(post);
+ json_object_unref(obj);
+}
diff --git a/skypeweb/skypeweb_login.c b/skypeweb/skypeweb_login.c
index a8c9d50..6f3de50 100644
--- a/skypeweb/skypeweb_login.c
+++ b/skypeweb/skypeweb_login.c
@@ -1,605 +1,605 @@
-/*
- * SkypeWeb Plugin for libpurple/Pidgin
- * Copyright (c) 2014-2020 Eion Robb
- *
- * 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 3 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, see <http://www.gnu.org/licenses/>.
- */
-
-#include "skypeweb_login.h"
-#include "skypeweb_util.h"
-
-
-static void
-skypeweb_login_did_auth(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
-{
- gchar *refresh_token = NULL;
- SkypeWebAccount *sa = user_data;
- const gchar *data;
- gsize len;
-
- data = purple_http_response_get_data(response, &len);
-
- if (data != NULL) {
- refresh_token = skypeweb_string_get_chunk(data, len, "=\"skypetoken\" value=\"", "\"");
- } else {
- purple_connection_error(sa->pc,
- PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
- _("Failed getting Skype Token, please try logging in via browser first"));
- return;
- }
-
- if (refresh_token == NULL) {
- purple_account_set_string(sa->account, "refresh-token", NULL);
- if (g_strstr_len(data, len, "recaptcha_response_field")) {
- purple_connection_error(sa->pc,
- PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
- _("Captcha required.\nTry logging into web.skype.com and try again."));
- return;
- } else {
- purple_debug_info("skypeweb", "login response was %s\r\n", data);
- purple_connection_error(sa->pc,
- PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
- _("Failed getting Skype Token, please try logging in via browser first"));
- return;
- }
- }
-
- sa->skype_token = refresh_token;
-
- if (purple_account_get_remember_password(sa->account)) {
- purple_account_set_string(sa->account, "refresh-token", purple_http_cookie_jar_get(sa->cookie_jar, "refresh-token"));
- }
-
- skypeweb_do_all_the_things(sa);
-}
-
-static void
-skypeweb_login_got_pie(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
-{
- SkypeWebAccount *sa = user_data;
- PurpleAccount *account = sa->account;
- gchar *pie;
- gchar *etm;
- const gchar *login_url = "https://" SKYPEWEB_LOGIN_HOST "/login?client_id=578134&redirect_uri=https%3A%2F%2Fweb.skype.com";
- GString *postdata;
- struct timeval tv;
- struct timezone tz;
- gint tzhours, tzminutes;
- int tmplen;
- PurpleHttpRequest *request;
- const gchar *data;
- gsize len;
-
- if (!purple_http_response_is_successful(response)) {
- purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, purple_http_response_get_error(response));
- return;
- }
-
- data = purple_http_response_get_data(response, &len);
-
- gettimeofday(&tv, &tz);
- (void) tv;
- tzminutes = tz.tz_minuteswest;
- if (tzminutes < 0) tzminutes = -tzminutes;
- tzhours = tzminutes / 60;
- tzminutes -= tzhours * 60;
-
- pie = skypeweb_string_get_chunk(data, len, "=\"pie\" value=\"", "\"");
- if (!pie) {
- purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Failed getting PIE value, please try logging in via browser first"));
- return;
- }
-
- etm = skypeweb_string_get_chunk(data, len, "=\"etm\" value=\"", "\"");
- if (!etm) {
- purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Failed getting ETM value, please try logging in via browser first"));
- return;
- }
-
-
- postdata = g_string_new("");
- g_string_append_printf(postdata, "username=%s&", purple_url_encode(purple_account_get_username(account)));
- g_string_append_printf(postdata, "password=%s&", purple_url_encode(purple_connection_get_password(sa->pc)));
- g_string_append_printf(postdata, "timezone_field=%c|%d|%d&", (tz.tz_minuteswest < 0 ? '+' : '-'), tzhours, tzminutes);
- g_string_append_printf(postdata, "pie=%s&", purple_url_encode(pie));
- g_string_append_printf(postdata, "etm=%s&", purple_url_encode(etm));
- g_string_append_printf(postdata, "js_time=%" G_GINT64_FORMAT "&", skypeweb_get_js_time());
- g_string_append(postdata, "client_id=578134&");
- g_string_append(postdata, "redirect_uri=https://web.skype.com/");
-
- tmplen = postdata->len;
- if (postdata->len > INT_MAX) tmplen = INT_MAX;
-
- request = purple_http_request_new(login_url);
- purple_http_request_set_method(request, "POST");
- purple_http_request_set_cookie_jar(request, sa->cookie_jar);
- purple_http_request_header_set(request, "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
- purple_http_request_header_set(request, "Accept", "*/*");
- purple_http_request_header_set(request, "BehaviorOverride", "redirectAs404");
- purple_http_request_set_contents(request, postdata->str, tmplen);
- purple_http_request(sa->pc, request, skypeweb_login_did_auth, sa);
- purple_http_request_unref(request);
-
- g_string_free(postdata, TRUE);
- g_free(pie);
- g_free(etm);
-
- purple_connection_update_progress(sa->pc, _("Authenticating"), 2, 4);
-}
-
-void
-skypeweb_begin_web_login(SkypeWebAccount *sa)
-{
- const gchar *login_url = "https://" SKYPEWEB_LOGIN_HOST "/login?method=skype&client_id=578134&redirect_uri=https%3A%2F%2Fweb.skype.com";
-
- purple_http_get(sa->pc, skypeweb_login_got_pie, sa, login_url);
-
- purple_connection_set_state(sa->pc, PURPLE_CONNECTION_CONNECTING);
- purple_connection_update_progress(sa->pc, _("Connecting"), 1, 4);
-}
-
-static void
-skypeweb_login_got_t(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
-{
- SkypeWebAccount *sa = user_data;
- const gchar *login_url = "https://" SKYPEWEB_LOGIN_HOST "/login/microsoft";
- PurpleHttpRequest *request;
- GString *postdata;
- gchar *magic_t_value; // T is for tasty
- gchar *error_code;
- gchar *error_text;
- int tmplen;
- const gchar *data;
- gsize len;
-
- data = purple_http_response_get_data(response, &len);
-
- // <input type="hidden" name="t" id="t" value="...">
- error_text = skypeweb_string_get_chunk(data, len, ",sErrTxt:'", "',Am:'");
- error_code = skypeweb_string_get_chunk(data, len, ",sErrorCode:'", "',Ag:");
- magic_t_value = skypeweb_string_get_chunk(data, len, "=\"t\" value=\"", "\"");
-
- if (!magic_t_value) {
- //No Magic T???? Maybe it be the mighty 2fa-beast
-
- if (FALSE)
- /*if (g_strnstr(data, len, "Set-Cookie: LOpt=0;"))*/ {
- //XX - Would this be better retrieved with JSON decoding the "var ServerData = {...}" code?
- // <script type="text/javascript">var ServerData = {...};</script>
- gchar *session_state = skypeweb_string_get_chunk(data, len, ":'https://login.live.com/GetSessionState.srf?", "',");
- if (session_state) {
- //These two appear to have different object keys each request :(
- /*
- gchar *PPFT = skypeweb_string_get_chunk(data, len, ",sFT:'", "',");
- gchar *SLK = skypeweb_string_get_chunk(data, len, ",aB:'", "',");
- gchar *ppauth_cookie = skypeweb_string_get_chunk(data, len, "Set-Cookie: PPAuth=", ";");
- gchar *mspok_cookie = skypeweb_string_get_chunk(data, len, "Set-Cookie: MSPOK=", "; domain=");
- */
-
- //Poll https://login.live.com/GetSessionState.srv?{session_state} to retrieve GIF(!!) of 2fa status
- //1x1 size GIF means pending, 2x2 rejected, 1x2 approved
- //Then re-request the MagicT, if approved with a slightly different GET parameters
- //purpose=eOTT_OneTimePassword&PPFT={ppft}&login={email}&SLK={slk}
- return;
- }
- }
-
- if (error_text) {
- GString *new_error;
- new_error = g_string_new("");
- g_string_append_printf(new_error, "%s: ", error_code);
- g_string_append_printf(new_error, "%s", error_text);
-
- gchar *error_msg = g_string_free(new_error, FALSE);
-
- purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, error_msg);
- g_free (error_msg);
- return;
- }
-
- purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Failed getting Magic T value, please try logging in via browser first"));
-
- return;
- }
-
- // postdata: t=...&oauthPartner=999&client_id=578134&redirect_uri=https%3A%2F%2Fweb.skype.com
- postdata = g_string_new("");
- g_string_append_printf(postdata, "t=%s&", purple_url_encode(magic_t_value));
- g_string_append(postdata, "site_name=lw.skype.com&");
- g_string_append(postdata, "oauthPartner=999&");
- g_string_append(postdata, "client_id=578134&");
- g_string_append(postdata, "redirect_uri=https%3A%2F%2Fweb.skype.com");
-
- tmplen = postdata->len;
- if (postdata->len > INT_MAX) tmplen = INT_MAX;
-
- // post the t to https://login.skype.com/login/oauth?client_id=578134&redirect_uri=https%3A%2F%2Fweb.skype.com
-
- request = purple_http_request_new(login_url);
- purple_http_request_set_method(request, "POST");
- purple_http_request_set_cookie_jar(request, sa->cookie_jar);
- purple_http_request_header_set(request, "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
- purple_http_request_header_set(request, "Accept", "*/*");
- purple_http_request_header_set(request, "BehaviorOverride", "redirectAs404");
- purple_http_request_set_contents(request, postdata->str, tmplen);
- purple_http_request_set_max_redirects(request, 0);
- purple_http_request(sa->pc, request, skypeweb_login_did_auth, sa);
- purple_http_request_unref(request);
-
- g_string_free(postdata, TRUE);
- g_free(magic_t_value);
-
- purple_connection_update_progress(sa->pc, _("Verifying"), 3, 4);
-}
-
-static void
-skypeweb_login_got_opid(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
-{
- SkypeWebAccount *sa = user_data;
- const gchar *live_login_url = "https://login.live.com" "/ppsecure/post.srf?wa=wsignin1.0&wp=MBI_SSL&wreply=https%3A%2F%2Flw.skype.com%2Flogin%2Foauth%2Fproxy%3Fsite_name%3Dlw.skype.com";
- gchar *ppft;
- gchar *opid;
- GString *postdata;
- PurpleHttpRequest *request;
- int tmplen;
- const gchar *data;
- gsize len;
-
- data = purple_http_response_get_data(response, &len);
-
- ppft = skypeweb_string_get_chunk(data, len, ",sFT:'", "',");
- opid = skypeweb_string_get_chunk(data, len, "&opid=", "'");
- if (!ppft || !opid) {
- // Maybe this stage isn't needed this time: passing over the Magic T
- skypeweb_login_got_t(http_conn, response, user_data);
- } else {
- postdata = g_string_new("");
- g_string_append_printf(postdata, "opid=%s&", purple_url_encode(opid));
- g_string_append(postdata, "site_name=lw.skype.com&");
- g_string_append(postdata, "oauthPartner=999&");
- g_string_append(postdata, "client_id=578134&");
- g_string_append(postdata, "redirect_uri=https%3A%2F%2Fweb.skype.com&");
- g_string_append_printf(postdata, "PPFT=%s&", purple_url_encode(ppft));
- g_string_append(postdata, "type=28&");
-
- tmplen = postdata->len;
- if (postdata->len > INT_MAX) tmplen = INT_MAX;
-
- request = purple_http_request_new(live_login_url);
- purple_http_request_set_method(request, "POST");
- purple_http_request_set_cookie_jar(request, sa->cookie_jar);
- purple_http_request_header_set(request, "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
- purple_http_request_header_set(request, "Accept", "*/*");
- purple_http_request_set_contents(request, postdata->str, tmplen);
- purple_http_request(sa->pc, request, skypeweb_login_got_t, sa);
- purple_http_request_unref(request);
-
- g_string_free(postdata, TRUE);
- }
-
- g_free(ppft);
- g_free(opid);
-
- purple_connection_update_progress(sa->pc, _("Authenticating"), 2, 4);
-}
-
-static void
-skypeweb_login_got_ppft(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
-{
- SkypeWebAccount *sa = user_data;
- const gchar *live_login_url = "https://login.live.com" "/ppsecure/post.srf?wa=wsignin1.0&wp=MBI_SSL&wreply=https%3A%2F%2Flw.skype.com%2Flogin%2Foauth%2Fproxy%3Fsite_name%3Dlw.skype.com";
- gchar *cktst_cookie;
- gchar *ppft;
- GString *postdata;
- PurpleHttpRequest *request;
- int tmplen;
- const gchar *data;
- gsize len;
-
- data = purple_http_response_get_data(response, &len);
-
- // <input type="hidden" name="PPFT" id="i0327" value="..."/>
- ppft = skypeweb_string_get_chunk(data, len, "name=\"PPFT\" id=\"i0327\" value=\"", "\"");
- if (!ppft) {
- purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Failed getting PPFT value, please try logging in via browser first"));
- return;
- }
- // CkTst=G + timestamp e.g. G1422309314913
- cktst_cookie = g_strdup_printf("G%" G_GINT64_FORMAT, skypeweb_get_js_time());
- purple_http_cookie_jar_set(sa->cookie_jar, "CkTst", cktst_cookie);
-
- // postdata: login={username}&passwd={password}&PPFT={ppft value}
- postdata = g_string_new("");
- g_string_append_printf(postdata, "login=%s&", purple_url_encode(purple_account_get_username(sa->account)));
- g_string_append_printf(postdata, "passwd=%s&", purple_url_encode(purple_connection_get_password(sa->pc)));
- g_string_append_printf(postdata, "PPFT=%s&", purple_url_encode(ppft));
- g_string_append(postdata, "loginoptions=3&");
-
- tmplen = postdata->len;
- if (postdata->len > INT_MAX) tmplen = INT_MAX;
-
- // POST to https://login.live.com/ppsecure/post.srf?wa=wsignin1.0&wreply=https%3A%2F%2Fsecure.skype.com%2Flogin%2Foauth%2Fproxy%3Fclient_id%3D578134%26redirect_uri%3Dhttps%253A%252F%252Fweb.skype.com
-
- request = purple_http_request_new(live_login_url);
- purple_http_request_set_method(request, "POST");
- purple_http_request_set_cookie_jar(request, sa->cookie_jar);
- purple_http_request_header_set(request, "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
- purple_http_request_header_set(request, "Accept", "*/*");
- purple_http_request_set_contents(request, postdata->str, tmplen);
- purple_http_request(sa->pc, request, skypeweb_login_got_opid, sa);
- purple_http_request_unref(request);
-
- g_string_free(postdata, TRUE);
-
- g_free(cktst_cookie);
- g_free(ppft);
-
- purple_connection_update_progress(sa->pc, _("Authenticating"), 2, 4);
-}
-
-void
-skypeweb_begin_oauth_login(SkypeWebAccount *sa)
-{
- const gchar *login_url = "https://" SKYPEWEB_LOGIN_HOST "/login/oauth/microsoft?client_id=578134&redirect_uri=https%3A%2F%2Fweb.skype.com";
- PurpleHttpRequest *request;
-
- request = purple_http_request_new(login_url);
- purple_http_request_set_cookie_jar(request, sa->cookie_jar);
- purple_http_request(sa->pc, request, skypeweb_login_got_ppft, sa);
- purple_http_request_unref(request);
-
- purple_connection_set_state(sa->pc, PURPLE_CONNECTION_CONNECTING);
- purple_connection_update_progress(sa->pc, _("Connecting"), 1, 4);
-}
-
-void
-skypeweb_logout(SkypeWebAccount *sa)
-{
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_LOGIN_HOST, "/logout", NULL, NULL, NULL, TRUE);
-}
-
-
-
-void
-skypeweb_refresh_token_login(SkypeWebAccount *sa)
-{
- PurpleAccount *account = sa->account;
- const gchar *login_url = "https://" SKYPEWEB_LOGIN_HOST "/login?client_id=578134&redirect_uri=https%3A%2F%2Fweb.skype.com";
- PurpleHttpRequest *request;
-
- request = purple_http_request_new(login_url);
- purple_http_request_set_method(request, "GET");
- purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
- purple_http_request_header_set(request, "Accept", "*/*");
- purple_http_request_header_set(request, "BehaviorOverride", "redirectAs404");
- purple_http_request_header_set_printf(request, "Cookie", "refresh-token=%s", purple_account_get_string(account, "refresh-token", ""));
- purple_http_request(sa->pc, request, skypeweb_login_did_auth, sa);
- purple_http_request_unref(request);
-
- purple_connection_update_progress(sa->pc, _("Authenticating"), 2, 4);
-}
-
-
-static void
-skypeweb_login_did_got_api_skypetoken(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
-{
- SkypeWebAccount *sa = user_data;
- const gchar *data;
- gsize len;
- JsonParser *parser = NULL;
- JsonNode *node;
- JsonObject *obj;
- gchar *error = NULL;
- PurpleConnectionError error_type = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
-
- data = purple_http_response_get_data(response, &len);
-
- purple_debug_misc("skypeweb", "Full skypetoken response: %s\n", data);
-
- parser = json_parser_new();
- if (!json_parser_load_from_data(parser, data, len, NULL)) {
- goto fail;
- }
-
- node = json_parser_get_root(parser);
- if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT) {
- goto fail;
- }
- obj = json_node_get_object(node);
-
- if (!json_object_has_member(obj, "skypetoken")) {
- JsonObject *status = json_object_get_object_member(obj, "status");
- if (status) {
- //{"status":{"code":40120,"text":"Authentication failed. Bad username or password."}}
- error = g_strdup_printf(_("Login error: %s (code %" G_GINT64_FORMAT ")"),
- json_object_get_string_member(status, "text"),
- json_object_get_int_member(status, "code")
- );
- error_type = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
- }
- goto fail;
- }
-
- sa->skype_token = g_strdup(json_object_get_string_member(obj, "skypetoken"));
-
- skypeweb_do_all_the_things(sa);
-
- g_object_unref(parser);
- return;
-fail:
- if (parser) {
- g_object_unref(parser);
- }
-
- purple_connection_error(sa->pc, error_type,
- error ? error : _("Failed getting Skype Token (alt)"));
-
- g_free(error);
-}
-
-static void
-skypeweb_login_get_api_skypetoken(SkypeWebAccount *sa, const gchar *url, const gchar *username, const gchar *password)
-{
- PurpleHttpRequest *request;
- JsonObject *obj;
- gchar *postdata;
-
- obj = json_object_new();
-
- if (username) {
- json_object_set_string_member(obj, "username", username);
- json_object_set_string_member(obj, "passwordHash", password);
- } else {
- json_object_set_int_member(obj, "partner", 999);
- json_object_set_string_member(obj, "access_token", password);
- }
- json_object_set_string_member(obj, "scopes", "client");
- postdata = skypeweb_jsonobj_to_string(obj);
-
- request = purple_http_request_new(url);
- purple_http_request_set_method(request, "POST");
- purple_http_request_set_contents(request, postdata, -1);
- purple_http_request_header_set(request, "Accept", "application/json; ver=1.0");
- purple_http_request_header_set(request, "Content-Type", "application/json");
- purple_http_request(sa->pc, request, skypeweb_login_did_got_api_skypetoken, sa);
- purple_http_request_unref(request);
-
- g_free(postdata);
- json_object_unref(obj);
-}
-
-static void
-skypeweb_login_soap_got_token(SkypeWebAccount *sa, gchar *token)
-{
- const gchar *login_url = "https://edge.skype.com/rps/v1/rps/skypetoken";
-
- skypeweb_login_get_api_skypetoken(sa, login_url, NULL, token);
-}
-
-static void
-skypeweb_login_did_soap(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
-{
- SkypeWebAccount *sa = user_data;
- const gchar *data;
- gsize len;
- PurpleXmlNode *envelope, *main_node, *node, *fault;
- gchar *token;
- const char *error = NULL;
-
- data = purple_http_response_get_data(response, &len);
- envelope = purple_xmlnode_from_str(data, len);
-
- if (!data) {
- error = _("Error parsing SOAP response");
- goto fail;
- }
-
- main_node = purple_xmlnode_get_child(envelope, "Body/RequestSecurityTokenResponseCollection/RequestSecurityTokenResponse");
-
- if ((fault = purple_xmlnode_get_child(envelope, "Fault")) ||
- (main_node && (fault = purple_xmlnode_get_child(main_node, "Fault")))) {
- gchar *code, *string, *error_;
-
- code = purple_xmlnode_get_data(purple_xmlnode_get_child(fault, "faultcode"));
- string = purple_xmlnode_get_data(purple_xmlnode_get_child(fault, "faultstring"));
-
- if (purple_strequal(code, "wsse:FailedAuthentication")) {
- error_ = g_strdup_printf(_("Login error: Bad username or password (%s)"), string);
- } else {
- error_ = g_strdup_printf(_("Login error: %s - %s"), code, string);
- }
-
- purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, error_);
-
- g_free(code);
- g_free(string);
- g_free(error_);
- goto fail;
- }
-
- node = purple_xmlnode_get_child(main_node, "RequestedSecurityToken/BinarySecurityToken");
-
- if (!node) {
- error = _("Error getting BinarySecurityToken");
- goto fail;
- }
-
- token = purple_xmlnode_get_data(node);
- skypeweb_login_soap_got_token(sa, token);
- g_free(token);
-
-fail:
- if (error) {
- purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error);
- }
- purple_xmlnode_free(envelope);
- return;
-}
-
-#define SIMPLE_OBJECT_ACCESS_PROTOCOL \
-"<Envelope xmlns='http://schemas.xmlsoap.org/soap/envelope/'\n" \
-" xmlns:wsse='http://schemas.xmlsoap.org/ws/2003/06/secext'\n" \
-" xmlns:wsp='http://schemas.xmlsoap.org/ws/2002/12/policy'\n" \
-" xmlns:wsa='http://schemas.xmlsoap.org/ws/2004/03/addressing'\n" \
-" xmlns:wst='http://schemas.xmlsoap.org/ws/2004/04/trust'\n" \
-" xmlns:ps='http://schemas.microsoft.com/Passport/SoapServices/PPCRL'>\n" \
-" <Header>\n" \
-" <wsse:Security>\n" \
-" <wsse:UsernameToken Id='user'>\n" \
-" <wsse:Username>%s</wsse:Username>\n" \
-" <wsse:Password>%s</wsse:Password>\n" \
-" </wsse:UsernameToken>\n" \
-" </wsse:Security>\n" \
-" </Header>\n" \
-" <Body>\n" \
-" <ps:RequestMultipleSecurityTokens Id='RSTS'>\n" \
-" <wst:RequestSecurityToken Id='RST0'>\n" \
-" <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>\n" \
-" <wsp:AppliesTo>\n" \
-" <wsa:EndpointReference>\n" \
-" <wsa:Address>wl.skype.com</wsa:Address>\n" \
-" </wsa:EndpointReference>\n" \
-" </wsp:AppliesTo>\n" \
-" <wsse:PolicyReference URI='MBI_SSL'></wsse:PolicyReference>\n" \
-" </wst:RequestSecurityToken>\n" \
-" </ps:RequestMultipleSecurityTokens>\n" \
-" </Body>\n" \
-"</Envelope>" \
-
-void
-skypeweb_begin_soapy_login(SkypeWebAccount *sa)
-{
- PurpleAccount *account = sa->account;
- const gchar *login_url = "https://login.live.com/RST.srf";
- const gchar *template = SIMPLE_OBJECT_ACCESS_PROTOCOL;
- gchar *postdata;
- PurpleHttpRequest *request;
-
- postdata = g_markup_printf_escaped(template,
- purple_account_get_username(account),
- purple_connection_get_password(sa->pc)
- );
-
- request = purple_http_request_new(login_url);
- purple_http_request_set_method(request, "POST");
- purple_http_request_set_contents(request, postdata, -1);
- purple_http_request_header_set(request, "Accept", "*/*");
- purple_http_request_header_set(request, "Content-Type", "text/xml; charset=UTF-8");
- purple_http_request(sa->pc, request, skypeweb_login_did_soap, sa);
- purple_http_request_unref(request);
-
- purple_connection_update_progress(sa->pc, _("Authenticating"), 2, 4);
-
- g_free(postdata);
-}
+/*
+ * SkypeWeb Plugin for libpurple/Pidgin
+ * Copyright (c) 2014-2020 Eion Robb
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "skypeweb_login.h"
+#include "skypeweb_util.h"
+
+
+static void
+skypeweb_login_did_auth(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
+{
+ gchar *refresh_token = NULL;
+ SkypeWebAccount *sa = user_data;
+ const gchar *data;
+ gsize len;
+
+ data = purple_http_response_get_data(response, &len);
+
+ if (data != NULL) {
+ refresh_token = skypeweb_string_get_chunk(data, len, "=\"skypetoken\" value=\"", "\"");
+ } else {
+ purple_connection_error(sa->pc,
+ PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+ _("Failed getting Skype Token, please try logging in via browser first"));
+ return;
+ }
+
+ if (refresh_token == NULL) {
+ purple_account_set_string(sa->account, "refresh-token", NULL);
+ if (g_strstr_len(data, len, "recaptcha_response_field")) {
+ purple_connection_error(sa->pc,
+ PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
+ _("Captcha required.\nTry logging into web.skype.com and try again."));
+ return;
+ } else {
+ purple_debug_info("skypeweb", "login response was %s\r\n", data);
+ purple_connection_error(sa->pc,
+ PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
+ _("Failed getting Skype Token, please try logging in via browser first"));
+ return;
+ }
+ }
+
+ sa->skype_token = refresh_token;
+
+ if (purple_account_get_remember_password(sa->account)) {
+ purple_account_set_string(sa->account, "refresh-token", purple_http_cookie_jar_get(sa->cookie_jar, "refresh-token"));
+ }
+
+ skypeweb_do_all_the_things(sa);
+}
+
+static void
+skypeweb_login_got_pie(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
+{
+ SkypeWebAccount *sa = user_data;
+ PurpleAccount *account = sa->account;
+ gchar *pie;
+ gchar *etm;
+ const gchar *login_url = "https://" SKYPEWEB_LOGIN_HOST "/login?client_id=578134&redirect_uri=https%3A%2F%2Fweb.skype.com";
+ GString *postdata;
+ struct timeval tv;
+ struct timezone tz;
+ gint tzhours, tzminutes;
+ int tmplen;
+ PurpleHttpRequest *request;
+ const gchar *data;
+ gsize len;
+
+ if (!purple_http_response_is_successful(response)) {
+ purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, purple_http_response_get_error(response));
+ return;
+ }
+
+ data = purple_http_response_get_data(response, &len);
+
+ gettimeofday(&tv, &tz);
+ (void) tv;
+ tzminutes = tz.tz_minuteswest;
+ if (tzminutes < 0) tzminutes = -tzminutes;
+ tzhours = tzminutes / 60;
+ tzminutes -= tzhours * 60;
+
+ pie = skypeweb_string_get_chunk(data, len, "=\"pie\" value=\"", "\"");
+ if (!pie) {
+ purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Failed getting PIE value, please try logging in via browser first"));
+ return;
+ }
+
+ etm = skypeweb_string_get_chunk(data, len, "=\"etm\" value=\"", "\"");
+ if (!etm) {
+ purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Failed getting ETM value, please try logging in via browser first"));
+ return;
+ }
+
+
+ postdata = g_string_new("");
+ g_string_append_printf(postdata, "username=%s&", purple_url_encode(purple_account_get_username(account)));
+ g_string_append_printf(postdata, "password=%s&", purple_url_encode(purple_connection_get_password(sa->pc)));
+ g_string_append_printf(postdata, "timezone_field=%c|%d|%d&", (tz.tz_minuteswest < 0 ? '+' : '-'), tzhours, tzminutes);
+ g_string_append_printf(postdata, "pie=%s&", purple_url_encode(pie));
+ g_string_append_printf(postdata, "etm=%s&", purple_url_encode(etm));
+ g_string_append_printf(postdata, "js_time=%" G_GINT64_FORMAT "&", skypeweb_get_js_time());
+ g_string_append(postdata, "client_id=578134&");
+ g_string_append(postdata, "redirect_uri=https://web.skype.com/");
+
+ tmplen = postdata->len;
+ if (postdata->len > INT_MAX) tmplen = INT_MAX;
+
+ request = purple_http_request_new(login_url);
+ purple_http_request_set_method(request, "POST");
+ purple_http_request_set_cookie_jar(request, sa->cookie_jar);
+ purple_http_request_header_set(request, "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
+ purple_http_request_header_set(request, "Accept", "*/*");
+ purple_http_request_header_set(request, "BehaviorOverride", "redirectAs404");
+ purple_http_request_set_contents(request, postdata->str, tmplen);
+ purple_http_request(sa->pc, request, skypeweb_login_did_auth, sa);
+ purple_http_request_unref(request);
+
+ g_string_free(postdata, TRUE);
+ g_free(pie);
+ g_free(etm);
+
+ purple_connection_update_progress(sa->pc, _("Authenticating"), 2, 4);
+}
+
+void
+skypeweb_begin_web_login(SkypeWebAccount *sa)
+{
+ const gchar *login_url = "https://" SKYPEWEB_LOGIN_HOST "/login?method=skype&client_id=578134&redirect_uri=https%3A%2F%2Fweb.skype.com";
+
+ purple_http_get(sa->pc, skypeweb_login_got_pie, sa, login_url);
+
+ purple_connection_set_state(sa->pc, PURPLE_CONNECTION_CONNECTING);
+ purple_connection_update_progress(sa->pc, _("Connecting"), 1, 4);
+}
+
+static void
+skypeweb_login_got_t(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
+{
+ SkypeWebAccount *sa = user_data;
+ const gchar *login_url = "https://" SKYPEWEB_LOGIN_HOST "/login/microsoft";
+ PurpleHttpRequest *request;
+ GString *postdata;
+ gchar *magic_t_value; // T is for tasty
+ gchar *error_code;
+ gchar *error_text;
+ int tmplen;
+ const gchar *data;
+ gsize len;
+
+ data = purple_http_response_get_data(response, &len);
+
+ // <input type="hidden" name="t" id="t" value="...">
+ error_text = skypeweb_string_get_chunk(data, len, ",sErrTxt:'", "',Am:'");
+ error_code = skypeweb_string_get_chunk(data, len, ",sErrorCode:'", "',Ag:");
+ magic_t_value = skypeweb_string_get_chunk(data, len, "=\"t\" value=\"", "\"");
+
+ if (!magic_t_value) {
+ //No Magic T???? Maybe it be the mighty 2fa-beast
+
+ if (FALSE)
+ /*if (g_strnstr(data, len, "Set-Cookie: LOpt=0;"))*/ {
+ //XX - Would this be better retrieved with JSON decoding the "var ServerData = {...}" code?
+ // <script type="text/javascript">var ServerData = {...};</script>
+ gchar *session_state = skypeweb_string_get_chunk(data, len, ":'https://login.live.com/GetSessionState.srf?", "',");
+ if (session_state) {
+ //These two appear to have different object keys each request :(
+ /*
+ gchar *PPFT = skypeweb_string_get_chunk(data, len, ",sFT:'", "',");
+ gchar *SLK = skypeweb_string_get_chunk(data, len, ",aB:'", "',");
+ gchar *ppauth_cookie = skypeweb_string_get_chunk(data, len, "Set-Cookie: PPAuth=", ";");
+ gchar *mspok_cookie = skypeweb_string_get_chunk(data, len, "Set-Cookie: MSPOK=", "; domain=");
+ */
+
+ //Poll https://login.live.com/GetSessionState.srv?{session_state} to retrieve GIF(!!) of 2fa status
+ //1x1 size GIF means pending, 2x2 rejected, 1x2 approved
+ //Then re-request the MagicT, if approved with a slightly different GET parameters
+ //purpose=eOTT_OneTimePassword&PPFT={ppft}&login={email}&SLK={slk}
+ return;
+ }
+ }
+
+ if (error_text) {
+ GString *new_error;
+ new_error = g_string_new("");
+ g_string_append_printf(new_error, "%s: ", error_code);
+ g_string_append_printf(new_error, "%s", error_text);
+
+ gchar *error_msg = g_string_free(new_error, FALSE);
+
+ purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, error_msg);
+ g_free (error_msg);
+ return;
+ }
+
+ purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Failed getting Magic T value, please try logging in via browser first"));
+
+ return;
+ }
+
+ // postdata: t=...&oauthPartner=999&client_id=578134&redirect_uri=https%3A%2F%2Fweb.skype.com
+ postdata = g_string_new("");
+ g_string_append_printf(postdata, "t=%s&", purple_url_encode(magic_t_value));
+ g_string_append(postdata, "site_name=lw.skype.com&");
+ g_string_append(postdata, "oauthPartner=999&");
+ g_string_append(postdata, "client_id=578134&");
+ g_string_append(postdata, "redirect_uri=https%3A%2F%2Fweb.skype.com");
+
+ tmplen = postdata->len;
+ if (postdata->len > INT_MAX) tmplen = INT_MAX;
+
+ // post the t to https://login.skype.com/login/oauth?client_id=578134&redirect_uri=https%3A%2F%2Fweb.skype.com
+
+ request = purple_http_request_new(login_url);
+ purple_http_request_set_method(request, "POST");
+ purple_http_request_set_cookie_jar(request, sa->cookie_jar);
+ purple_http_request_header_set(request, "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
+ purple_http_request_header_set(request, "Accept", "*/*");
+ purple_http_request_header_set(request, "BehaviorOverride", "redirectAs404");
+ purple_http_request_set_contents(request, postdata->str, tmplen);
+ purple_http_request_set_max_redirects(request, 0);
+ purple_http_request(sa->pc, request, skypeweb_login_did_auth, sa);
+ purple_http_request_unref(request);
+
+ g_string_free(postdata, TRUE);
+ g_free(magic_t_value);
+
+ purple_connection_update_progress(sa->pc, _("Verifying"), 3, 4);
+}
+
+static void
+skypeweb_login_got_opid(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
+{
+ SkypeWebAccount *sa = user_data;
+ const gchar *live_login_url = "https://login.live.com" "/ppsecure/post.srf?wa=wsignin1.0&wp=MBI_SSL&wreply=https%3A%2F%2Flw.skype.com%2Flogin%2Foauth%2Fproxy%3Fsite_name%3Dlw.skype.com";
+ gchar *ppft;
+ gchar *opid;
+ GString *postdata;
+ PurpleHttpRequest *request;
+ int tmplen;
+ const gchar *data;
+ gsize len;
+
+ data = purple_http_response_get_data(response, &len);
+
+ ppft = skypeweb_string_get_chunk(data, len, ",sFT:'", "',");
+ opid = skypeweb_string_get_chunk(data, len, "&opid=", "'");
+ if (!ppft || !opid) {
+ // Maybe this stage isn't needed this time: passing over the Magic T
+ skypeweb_login_got_t(http_conn, response, user_data);
+ } else {
+ postdata = g_string_new("");
+ g_string_append_printf(postdata, "opid=%s&", purple_url_encode(opid));
+ g_string_append(postdata, "site_name=lw.skype.com&");
+ g_string_append(postdata, "oauthPartner=999&");
+ g_string_append(postdata, "client_id=578134&");
+ g_string_append(postdata, "redirect_uri=https%3A%2F%2Fweb.skype.com&");
+ g_string_append_printf(postdata, "PPFT=%s&", purple_url_encode(ppft));
+ g_string_append(postdata, "type=28&");
+
+ tmplen = postdata->len;
+ if (postdata->len > INT_MAX) tmplen = INT_MAX;
+
+ request = purple_http_request_new(live_login_url);
+ purple_http_request_set_method(request, "POST");
+ purple_http_request_set_cookie_jar(request, sa->cookie_jar);
+ purple_http_request_header_set(request, "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
+ purple_http_request_header_set(request, "Accept", "*/*");
+ purple_http_request_set_contents(request, postdata->str, tmplen);
+ purple_http_request(sa->pc, request, skypeweb_login_got_t, sa);
+ purple_http_request_unref(request);
+
+ g_string_free(postdata, TRUE);
+ }
+
+ g_free(ppft);
+ g_free(opid);
+
+ purple_connection_update_progress(sa->pc, _("Authenticating"), 2, 4);
+}
+
+static void
+skypeweb_login_got_ppft(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
+{
+ SkypeWebAccount *sa = user_data;
+ const gchar *live_login_url = "https://login.live.com" "/ppsecure/post.srf?wa=wsignin1.0&wp=MBI_SSL&wreply=https%3A%2F%2Flw.skype.com%2Flogin%2Foauth%2Fproxy%3Fsite_name%3Dlw.skype.com";
+ gchar *cktst_cookie;
+ gchar *ppft;
+ GString *postdata;
+ PurpleHttpRequest *request;
+ int tmplen;
+ const gchar *data;
+ gsize len;
+
+ data = purple_http_response_get_data(response, &len);
+
+ // <input type="hidden" name="PPFT" id="i0327" value="..."/>
+ ppft = skypeweb_string_get_chunk(data, len, "name=\"PPFT\" id=\"i0327\" value=\"", "\"");
+ if (!ppft) {
+ purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Failed getting PPFT value, please try logging in via browser first"));
+ return;
+ }
+ // CkTst=G + timestamp e.g. G1422309314913
+ cktst_cookie = g_strdup_printf("G%" G_GINT64_FORMAT, skypeweb_get_js_time());
+ purple_http_cookie_jar_set(sa->cookie_jar, "CkTst", cktst_cookie);
+
+ // postdata: login={username}&passwd={password}&PPFT={ppft value}
+ postdata = g_string_new("");
+ g_string_append_printf(postdata, "login=%s&", purple_url_encode(purple_account_get_username(sa->account)));
+ g_string_append_printf(postdata, "passwd=%s&", purple_url_encode(purple_connection_get_password(sa->pc)));
+ g_string_append_printf(postdata, "PPFT=%s&", purple_url_encode(ppft));
+ g_string_append(postdata, "loginoptions=3&");
+
+ tmplen = postdata->len;
+ if (postdata->len > INT_MAX) tmplen = INT_MAX;
+
+ // POST to https://login.live.com/ppsecure/post.srf?wa=wsignin1.0&wreply=https%3A%2F%2Fsecure.skype.com%2Flogin%2Foauth%2Fproxy%3Fclient_id%3D578134%26redirect_uri%3Dhttps%253A%252F%252Fweb.skype.com
+
+ request = purple_http_request_new(live_login_url);
+ purple_http_request_set_method(request, "POST");
+ purple_http_request_set_cookie_jar(request, sa->cookie_jar);
+ purple_http_request_header_set(request, "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
+ purple_http_request_header_set(request, "Accept", "*/*");
+ purple_http_request_set_contents(request, postdata->str, tmplen);
+ purple_http_request(sa->pc, request, skypeweb_login_got_opid, sa);
+ purple_http_request_unref(request);
+
+ g_string_free(postdata, TRUE);
+
+ g_free(cktst_cookie);
+ g_free(ppft);
+
+ purple_connection_update_progress(sa->pc, _("Authenticating"), 2, 4);
+}
+
+void
+skypeweb_begin_oauth_login(SkypeWebAccount *sa)
+{
+ const gchar *login_url = "https://" SKYPEWEB_LOGIN_HOST "/login/oauth/microsoft?client_id=578134&redirect_uri=https%3A%2F%2Fweb.skype.com";
+ PurpleHttpRequest *request;
+
+ request = purple_http_request_new(login_url);
+ purple_http_request_set_cookie_jar(request, sa->cookie_jar);
+ purple_http_request(sa->pc, request, skypeweb_login_got_ppft, sa);
+ purple_http_request_unref(request);
+
+ purple_connection_set_state(sa->pc, PURPLE_CONNECTION_CONNECTING);
+ purple_connection_update_progress(sa->pc, _("Connecting"), 1, 4);
+}
+
+void
+skypeweb_logout(SkypeWebAccount *sa)
+{
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_LOGIN_HOST, "/logout", NULL, NULL, NULL, TRUE);
+}
+
+
+
+void
+skypeweb_refresh_token_login(SkypeWebAccount *sa)
+{
+ PurpleAccount *account = sa->account;
+ const gchar *login_url = "https://" SKYPEWEB_LOGIN_HOST "/login?client_id=578134&redirect_uri=https%3A%2F%2Fweb.skype.com";
+ PurpleHttpRequest *request;
+
+ request = purple_http_request_new(login_url);
+ purple_http_request_set_method(request, "GET");
+ purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
+ purple_http_request_header_set(request, "Accept", "*/*");
+ purple_http_request_header_set(request, "BehaviorOverride", "redirectAs404");
+ purple_http_request_header_set_printf(request, "Cookie", "refresh-token=%s", purple_account_get_string(account, "refresh-token", ""));
+ purple_http_request(sa->pc, request, skypeweb_login_did_auth, sa);
+ purple_http_request_unref(request);
+
+ purple_connection_update_progress(sa->pc, _("Authenticating"), 2, 4);
+}
+
+
+static void
+skypeweb_login_did_got_api_skypetoken(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
+{
+ SkypeWebAccount *sa = user_data;
+ const gchar *data;
+ gsize len;
+ JsonParser *parser = NULL;
+ JsonNode *node;
+ JsonObject *obj;
+ gchar *error = NULL;
+ PurpleConnectionError error_type = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
+
+ data = purple_http_response_get_data(response, &len);
+
+ purple_debug_misc("skypeweb", "Full skypetoken response: %s\n", data);
+
+ parser = json_parser_new();
+ if (!json_parser_load_from_data(parser, data, len, NULL)) {
+ goto fail;
+ }
+
+ node = json_parser_get_root(parser);
+ if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT) {
+ goto fail;
+ }
+ obj = json_node_get_object(node);
+
+ if (!json_object_has_member(obj, "skypetoken")) {
+ JsonObject *status = json_object_get_object_member(obj, "status");
+ if (status) {
+ //{"status":{"code":40120,"text":"Authentication failed. Bad username or password."}}
+ error = g_strdup_printf(_("Login error: %s (code %" G_GINT64_FORMAT ")"),
+ json_object_get_string_member(status, "text"),
+ json_object_get_int_member(status, "code")
+ );
+ error_type = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
+ }
+ goto fail;
+ }
+
+ sa->skype_token = g_strdup(json_object_get_string_member(obj, "skypetoken"));
+
+ skypeweb_do_all_the_things(sa);
+
+ g_object_unref(parser);
+ return;
+fail:
+ if (parser) {
+ g_object_unref(parser);
+ }
+
+ purple_connection_error(sa->pc, error_type,
+ error ? error : _("Failed getting Skype Token (alt)"));
+
+ g_free(error);
+}
+
+static void
+skypeweb_login_get_api_skypetoken(SkypeWebAccount *sa, const gchar *url, const gchar *username, const gchar *password)
+{
+ PurpleHttpRequest *request;
+ JsonObject *obj;
+ gchar *postdata;
+
+ obj = json_object_new();
+
+ if (username) {
+ json_object_set_string_member(obj, "username", username);
+ json_object_set_string_member(obj, "passwordHash", password);
+ } else {
+ json_object_set_int_member(obj, "partner", 999);
+ json_object_set_string_member(obj, "access_token", password);
+ }
+ json_object_set_string_member(obj, "scopes", "client");
+ postdata = skypeweb_jsonobj_to_string(obj);
+
+ request = purple_http_request_new(url);
+ purple_http_request_set_method(request, "POST");
+ purple_http_request_set_contents(request, postdata, -1);
+ purple_http_request_header_set(request, "Accept", "application/json; ver=1.0");
+ purple_http_request_header_set(request, "Content-Type", "application/json");
+ purple_http_request(sa->pc, request, skypeweb_login_did_got_api_skypetoken, sa);
+ purple_http_request_unref(request);
+
+ g_free(postdata);
+ json_object_unref(obj);
+}
+
+static void
+skypeweb_login_soap_got_token(SkypeWebAccount *sa, gchar *token)
+{
+ const gchar *login_url = "https://edge.skype.com/rps/v1/rps/skypetoken";
+
+ skypeweb_login_get_api_skypetoken(sa, login_url, NULL, token);
+}
+
+static void
+skypeweb_login_did_soap(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
+{
+ SkypeWebAccount *sa = user_data;
+ const gchar *data;
+ gsize len;
+ PurpleXmlNode *envelope, *main_node, *node, *fault;
+ gchar *token;
+ const char *error = NULL;
+
+ data = purple_http_response_get_data(response, &len);
+ envelope = purple_xmlnode_from_str(data, len);
+
+ if (!data) {
+ error = _("Error parsing SOAP response");
+ goto fail;
+ }
+
+ main_node = purple_xmlnode_get_child(envelope, "Body/RequestSecurityTokenResponseCollection/RequestSecurityTokenResponse");
+
+ if ((fault = purple_xmlnode_get_child(envelope, "Fault")) ||
+ (main_node && (fault = purple_xmlnode_get_child(main_node, "Fault")))) {
+ gchar *code, *string, *error_;
+
+ code = purple_xmlnode_get_data(purple_xmlnode_get_child(fault, "faultcode"));
+ string = purple_xmlnode_get_data(purple_xmlnode_get_child(fault, "faultstring"));
+
+ if (purple_strequal(code, "wsse:FailedAuthentication")) {
+ error_ = g_strdup_printf(_("Login error: Bad username or password (%s)"), string);
+ } else {
+ error_ = g_strdup_printf(_("Login error: %s - %s"), code, string);
+ }
+
+ purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, error_);
+
+ g_free(code);
+ g_free(string);
+ g_free(error_);
+ goto fail;
+ }
+
+ node = purple_xmlnode_get_child(main_node, "RequestedSecurityToken/BinarySecurityToken");
+
+ if (!node) {
+ error = _("Error getting BinarySecurityToken");
+ goto fail;
+ }
+
+ token = purple_xmlnode_get_data(node);
+ skypeweb_login_soap_got_token(sa, token);
+ g_free(token);
+
+fail:
+ if (error) {
+ purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error);
+ }
+ purple_xmlnode_free(envelope);
+ return;
+}
+
+#define SIMPLE_OBJECT_ACCESS_PROTOCOL \
+"<Envelope xmlns='http://schemas.xmlsoap.org/soap/envelope/'\n" \
+" xmlns:wsse='http://schemas.xmlsoap.org/ws/2003/06/secext'\n" \
+" xmlns:wsp='http://schemas.xmlsoap.org/ws/2002/12/policy'\n" \
+" xmlns:wsa='http://schemas.xmlsoap.org/ws/2004/03/addressing'\n" \
+" xmlns:wst='http://schemas.xmlsoap.org/ws/2004/04/trust'\n" \
+" xmlns:ps='http://schemas.microsoft.com/Passport/SoapServices/PPCRL'>\n" \
+" <Header>\n" \
+" <wsse:Security>\n" \
+" <wsse:UsernameToken Id='user'>\n" \
+" <wsse:Username>%s</wsse:Username>\n" \
+" <wsse:Password>%s</wsse:Password>\n" \
+" </wsse:UsernameToken>\n" \
+" </wsse:Security>\n" \
+" </Header>\n" \
+" <Body>\n" \
+" <ps:RequestMultipleSecurityTokens Id='RSTS'>\n" \
+" <wst:RequestSecurityToken Id='RST0'>\n" \
+" <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>\n" \
+" <wsp:AppliesTo>\n" \
+" <wsa:EndpointReference>\n" \
+" <wsa:Address>wl.skype.com</wsa:Address>\n" \
+" </wsa:EndpointReference>\n" \
+" </wsp:AppliesTo>\n" \
+" <wsse:PolicyReference URI='MBI_SSL'></wsse:PolicyReference>\n" \
+" </wst:RequestSecurityToken>\n" \
+" </ps:RequestMultipleSecurityTokens>\n" \
+" </Body>\n" \
+"</Envelope>" \
+
+void
+skypeweb_begin_soapy_login(SkypeWebAccount *sa)
+{
+ PurpleAccount *account = sa->account;
+ const gchar *login_url = "https://login.live.com/RST.srf";
+ const gchar *template = SIMPLE_OBJECT_ACCESS_PROTOCOL;
+ gchar *postdata;
+ PurpleHttpRequest *request;
+
+ postdata = g_markup_printf_escaped(template,
+ purple_account_get_username(account),
+ purple_connection_get_password(sa->pc)
+ );
+
+ request = purple_http_request_new(login_url);
+ purple_http_request_set_method(request, "POST");
+ purple_http_request_set_contents(request, postdata, -1);
+ purple_http_request_header_set(request, "Accept", "*/*");
+ purple_http_request_header_set(request, "Content-Type", "text/xml; charset=UTF-8");
+ purple_http_request(sa->pc, request, skypeweb_login_did_soap, sa);
+ purple_http_request_unref(request);
+
+ purple_connection_update_progress(sa->pc, _("Authenticating"), 2, 4);
+
+ g_free(postdata);
+}
diff --git a/skypeweb/skypeweb_messages.c b/skypeweb/skypeweb_messages.c
index bf9f472..71c7b5c 100644
--- a/skypeweb/skypeweb_messages.c
+++ b/skypeweb/skypeweb_messages.c
@@ -1,1759 +1,1759 @@
-/*
- * SkypeWeb Plugin for libpurple/Pidgin
- * Copyright (c) 2014-2020 Eion Robb
- *
- * 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 3 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, see <http://www.gnu.org/licenses/>.
- */
-
-#include "skypeweb_messages.h"
-#include "skypeweb_util.h"
-#include "skypeweb_connection.h"
-#include "skypeweb_contacts.h"
-#include "skypeweb_login.h"
-
-static GString* make_last_timestamp_setting(const gchar *convname) {
- GString *rv = g_string_new(NULL);
- g_string_printf(rv, "%s_last_message_timestamp", convname);
- return rv;
-}
-
-static gboolean
-skypeweb_is_user_self(SkypeWebAccount *sa, const gchar *username) {
- if (!username || *username == 0) {
- return FALSE;
- }
-
- if (sa->username) {
- if (g_str_equal(username, sa->username)) {
- return TRUE;
- }
- }
-
- if (sa->primary_member_name) {
- if (g_str_equal(username, sa->primary_member_name)) {
- return TRUE;
- }
- }
-
- return !g_ascii_strcasecmp(username, purple_account_get_username(sa->account));
-}
-
-static gchar *
-skypeweb_meify(const gchar *message, gint skypeemoteoffset)
-{
- guint len;
- len = strlen(message);
-
- if (skypeemoteoffset <= 0 || skypeemoteoffset >= len)
- return g_strdup(message);
-
- return g_strconcat("/me ", message + skypeemoteoffset, NULL);
-}
-
-static void
-process_userpresence_resource(SkypeWebAccount *sa, JsonObject *resource)
-{
- const gchar *selfLink = json_object_get_string_member(resource, "selfLink");
- const gchar *status = json_object_get_string_member(resource, "status");
- // const gchar *capabilities = json_object_get_string_member(resource, "capabilities");
- // const gchar *lastSeenAt = json_object_get_string_member(resource, "lastSeenAt");
- const gchar *from;
- gboolean is_idle;
-
- from = skypeweb_contact_url_to_name(selfLink);
- g_return_if_fail(from);
-
- if (!purple_blist_find_buddy(sa->account, from))
- {
- PurpleGroup *group = purple_blist_find_group("Skype");
- if (!group)
- {
- group = purple_group_new("Skype");
- purple_blist_add_group(group, NULL);
- }
-
- if (skypeweb_is_user_self(sa, from)) {
- return;
- }
-
- purple_blist_add_buddy(purple_buddy_new(sa->account, from, NULL), NULL, group, NULL);
- }
-
- // if (g_str_equal(capabilities, "IsMobile")) { //"Seamless | IsMobile"
- // purple_protocol_got_user_status(sa->account, from, "mobile", NULL);
- // }
-
- is_idle = purple_strequal(status, SKYPEWEB_STATUS_IDLE);
- if (!is_idle) {
- purple_protocol_got_user_status(sa->account, from, status, NULL);
- } else {
- purple_protocol_got_user_status(sa->account, from, SKYPEWEB_STATUS_ONLINE, NULL);
- }
-
- purple_protocol_got_user_idle(sa->account, from, is_idle, 0);
-}
-
-// static gboolean
-// skypeweb_clear_typing_hack(PurpleChatUser *cb)
-// {
- // PurpleChatUserFlags cbflags;
-
- // cbflags = purple_chat_user_get_flags(cb);
- // cbflags &= ~PURPLE_CHAT_USER_TYPING & ~PURPLE_CHAT_USER_VOICE;
- // purple_chat_user_set_flags(cb, cbflags);
-
- // return FALSE;
-// }
-
-static void
-skypeweb_process_uri_message(const gchar* messagetype, SkypeWebAccount *sa, PurpleConversation* conv, const gchar* uri_content, time_t composetimestamp, const gchar* from) {
- PurpleXmlNode *blob = purple_xmlnode_from_str(uri_content, -1);
- const gchar *uri = purple_xmlnode_get_attrib(blob, "url_thumbnail");
- SkypeWebURIType uri_type;
-
- if (g_str_has_suffix(messagetype, "Media_Video")) {
- uri_type = SKYPEWEB_URI_TYPE_VIDEO;
- } else {
- uri_type = SKYPEWEB_URI_TYPE_IMAGE;
- }
-
- skypeweb_download_uri_to_conv(sa, uri, uri_type, conv, composetimestamp, from);
- purple_xmlnode_free(blob);
-}
-
-static void
-process_message_resource(SkypeWebAccount *sa, JsonObject *resource)
-{
- const gchar *clientmessageid = NULL;
- const gchar *skypeeditedid = NULL;
- const gchar *messagetype = json_object_get_string_member(resource, "messagetype");
- const gchar *from = json_object_get_string_member(resource, "from");
- const gchar *content = NULL;
- const gchar *composetime = json_object_get_string_member(resource, "composetime");
- const gchar *conversationLink = json_object_get_string_member(resource, "conversationLink");
- time_t composetimestamp = purple_str_to_time(composetime, TRUE, NULL, NULL, NULL);
- gchar **messagetype_parts;
- PurpleConversation *conv = NULL;
- gchar *convname = NULL;
-
- g_return_if_fail(messagetype != NULL);
-
- messagetype_parts = g_strsplit(messagetype, "/", -1);
-
- if (json_object_has_member(resource, "clientmessageid"))
- clientmessageid = json_object_get_string_member(resource, "clientmessageid");
-
- if (clientmessageid && *clientmessageid && g_hash_table_remove(sa->sent_messages_hash, clientmessageid)) {
- // We sent this message from here already
- g_strfreev(messagetype_parts);
- return;
- }
-
- if (json_object_has_member(resource, "skypeeditedid"))
- skypeeditedid = json_object_get_string_member(resource, "skypeeditedid");
- if (json_object_has_member(resource, "content"))
- content = json_object_get_string_member(resource, "content");
-
- if (conversationLink && strstr(conversationLink, "/19:")) {
- // This is a Thread/Group chat message
- const gchar *chatname, *topic;
- PurpleChatConversation *chatconv;
-
- chatname = skypeweb_thread_url_to_name(conversationLink);
- convname = g_strdup(chatname);
- chatconv = purple_conversations_find_chat_with_account(chatname, sa->account);
- if (!chatconv) {
- chatconv = purple_serv_got_joined_chat(sa->pc, g_str_hash(chatname), chatname);
- purple_conversation_set_data(PURPLE_CONVERSATION(chatconv), "chatname", g_strdup(chatname));
-
- if (json_object_has_member(resource, "threadtopic")) {
- topic = json_object_get_string_member(resource, "threadtopic");
- purple_chat_conversation_set_topic(chatconv, NULL, topic);
- }
-
- skypeweb_get_conversation_history(sa, chatname);
- skypeweb_get_thread_users(sa, chatname);
- }
- GString *chat_last_timestamp = make_last_timestamp_setting(convname);
- purple_account_set_int(sa->account, chat_last_timestamp->str, composetimestamp);
- g_string_free(chat_last_timestamp, TRUE);
-
- conv = PURPLE_CONVERSATION(chatconv);
-
- if (g_str_equal(messagetype, "Control/Typing")) {
- PurpleChatUserFlags cbflags;
- PurpleChatUser *cb;
-
- from = skypeweb_contact_url_to_name(from);
- if (from == NULL) {
- g_strfreev(messagetype_parts);
- g_return_if_reached();
- return;
- }
-
- // typing notification text, not personalized because of multiple "typing" events
- if (purple_account_get_bool(sa->account, "show-typing-as-text", FALSE)) {
- const gchar *message = g_strdup_printf("%s ...", N_("buddy typing"));
- const gchar *last_message = NULL;
-
- // get last message (first in GList)
- if (conv && g_list_length(purple_conversation_get_message_history(conv))) {
- PurpleMessage *last = g_list_nth_data(g_list_first(purple_conversation_get_message_history(conv)),0);
- last_message = purple_message_get_contents(last);
- }
-
- // add typing notification to chat
- if (last_message && !g_str_equal(last_message, message)) {
- PurpleMessage *msg = purple_message_new_system(message, PURPLE_MESSAGE_NO_LOG | PURPLE_MESSAGE_NOTIFY | PURPLE_MESSAGE_ACTIVE_ONLY);
- purple_message_set_time(msg, composetimestamp);
- purple_conversation_write_message(conv, msg);
- purple_message_destroy(msg);
- }
- }
-
- cb = purple_chat_conversation_find_user(chatconv, from);
- if (cb != NULL) {
- cbflags = purple_chat_user_get_flags(cb);
-
- cbflags |= PURPLE_CHAT_USER_TYPING;
-
- // typing notification icon
- if (purple_account_get_bool(sa->account, "show-typing-as-icon", FALSE)) {
- cbflags |= PURPLE_CHAT_USER_VOICE;
- }
-
- purple_chat_user_set_flags(cb, cbflags);
-
- //purple_timeout_add_seconds(7, skypeweb_clear_typing_hack, cb);
- }
-
- } else if ((g_str_equal(messagetype, "RichText") || g_str_equal(messagetype, "Text"))) {
- gchar *html;
- gint64 skypeemoteoffset = 0;
- PurpleChatUserFlags cbflags;
- PurpleChatUser *cb;
-
- if (json_object_has_member(resource, "skypeemoteoffset")) {
- skypeemoteoffset = g_ascii_strtoll(json_object_get_string_member(resource, "skypeemoteoffset"), NULL, 10);
- }
-
- from = skypeweb_contact_url_to_name(from);
- if (from == NULL) {
- g_free(messagetype_parts);
- g_return_if_reached();
- return;
- }
-
- // Remove typing notification icon w/o "show-typing-as-icon" option check.
- // Hard reset cbflags even if user changed settings while someone typing message.
-
- cb = purple_chat_conversation_find_user(chatconv, from);
- if (cb != NULL) {
- cbflags = purple_chat_user_get_flags(cb);
-
- cbflags &= ~PURPLE_CHAT_USER_TYPING & ~PURPLE_CHAT_USER_VOICE;
-
- purple_chat_user_set_flags(cb, cbflags);
- }
-
- if (content && *content) {
- if (g_str_equal(messagetype, "Text")) {
- gchar *temp = skypeweb_meify(content, skypeemoteoffset);
- html = purple_markup_escape_text(temp, -1);
- g_free(temp);
- } else {
- html = skypeweb_meify(content, skypeemoteoffset);
- }
-
- if (skypeeditedid && *skypeeditedid) {
- gchar *temp = g_strconcat(_("Edited: "), html, NULL);
- g_free(html);
- html = temp;
- }
-
- purple_serv_got_chat_in(sa->pc, g_str_hash(chatname), from, skypeweb_is_user_self(sa, from) ? PURPLE_MESSAGE_SEND : PURPLE_MESSAGE_RECV, html, composetimestamp);
-
- g_free(html);
- }
- } else if (g_str_equal(messagetype, "ThreadActivity/AddMember")) {
- PurpleXmlNode *blob = purple_xmlnode_from_str(content, -1);
- PurpleXmlNode *target;
- for(target = purple_xmlnode_get_child(blob, "target"); target;
- target = purple_xmlnode_get_next_twin(target))
- {
- gchar *user = purple_xmlnode_get_data(target);
- if (!purple_chat_conversation_find_user(chatconv, &user[2]))
- purple_chat_conversation_add_user(chatconv, &user[2], NULL, PURPLE_CHAT_USER_NONE, TRUE);
- g_free(user);
- }
- purple_xmlnode_free(blob);
- } else if (g_str_equal(messagetype, "ThreadActivity/DeleteMember")) {
- PurpleXmlNode *blob = purple_xmlnode_from_str(content, -1);
- PurpleXmlNode *target;
- for(target = purple_xmlnode_get_child(blob, "target"); target;
- target = purple_xmlnode_get_next_twin(target))
- {
- gchar *user = purple_xmlnode_get_data(target);
- if (skypeweb_is_user_self(sa, &user[2]))
- purple_chat_conversation_leave(chatconv);
- purple_chat_conversation_remove_user(chatconv, &user[2], NULL);
- g_free(user);
- }
- purple_xmlnode_free(blob);
- } else if (g_str_equal(messagetype, "ThreadActivity/TopicUpdate")) {
- PurpleXmlNode *blob = purple_xmlnode_from_str(content, -1);
- gchar *initiator = purple_xmlnode_get_data(purple_xmlnode_get_child(blob, "initiator"));
- gchar *value = purple_xmlnode_get_data(purple_xmlnode_get_child(blob, "value"));
-
- purple_chat_conversation_set_topic(chatconv, &initiator[2], value);
- purple_conversation_update(conv, PURPLE_CONVERSATION_UPDATE_TOPIC);
-
- g_free(initiator);
- g_free(value);
- purple_xmlnode_free(blob);
- } else if (g_str_equal(messagetype, "ThreadActivity/RoleUpdate")) {
- PurpleXmlNode *blob = purple_xmlnode_from_str(content, -1);
- PurpleXmlNode *target;
- PurpleChatUser *cb;
-
- for(target = purple_xmlnode_get_child(blob, "target"); target;
- target = purple_xmlnode_get_next_twin(target))
- {
- gchar *user = purple_xmlnode_get_data(purple_xmlnode_get_child(target, "id"));
- gchar *role = purple_xmlnode_get_data(purple_xmlnode_get_child(target, "role"));
- PurpleChatUserFlags cbflags = PURPLE_CHAT_USER_NONE;
-
- if (role && *role) {
- if (g_str_equal(role, "Admin") || g_str_equal(role, "admin")) {
- cbflags = PURPLE_CHAT_USER_OP;
- } else if (g_str_equal(role, "User") || g_str_equal(role, "user")) {
- //cbflags = PURPLE_CHAT_USER_VOICE;
- }
- }
- #if !PURPLE_VERSION_CHECK(3, 0, 0)
- purple_conv_chat_user_set_flags(chatconv, &user[2], cbflags);
- (void) cb;
- #else
- cb = purple_chat_conversation_find_user(chatconv, &user[2]);
- purple_chat_user_set_flags(cb, cbflags);
- #endif
- g_free(user);
- g_free(role);
- }
-
- purple_xmlnode_free(blob);
- } else if (g_str_equal(messagetype, "RichText/UriObject") || g_str_equal(messagetype, "RichText/Media_Video")) {
- from = skypeweb_contact_url_to_name(from);
- g_return_if_fail(from);
-
- skypeweb_process_uri_message(messagetype, sa, conv, content, composetimestamp, from);
- } else {
- purple_debug_warning("skypeweb", "Unhandled thread message resource messagetype '%s'\n", messagetype);
- }
-
- } else {
- gchar *convbuddyname;
- // This is a One-to-one/IM message
-
- convbuddyname = g_strdup(skypeweb_contact_url_to_name(conversationLink));
- convname = g_strconcat(skypeweb_user_url_prefix(convbuddyname), convbuddyname, NULL);
-
- from = skypeweb_contact_url_to_name(from);
- if (from == NULL) {
- g_free(convbuddyname);
- g_free(convname);
- g_return_if_reached();
- return;
- }
-
- if (g_str_equal(messagetype_parts[0], "Control")) {
- if (g_str_equal(messagetype_parts[1], "ClearTyping")) {
- purple_serv_got_typing(sa->pc, from, 7, PURPLE_IM_NOT_TYPING);
- } else if (g_str_equal(messagetype_parts[1], "Typing")) {
- purple_serv_got_typing(sa->pc, from, 7, PURPLE_IM_TYPING);
- }
- } else if ((g_str_equal(messagetype, "RichText") || g_str_equal(messagetype, "Text")) && content && *content) {
- gchar *html;
- gint64 skypeemoteoffset = 0;
- PurpleIMConversation *imconv;
-
- if (json_object_has_member(resource, "skypeemoteoffset")) {
- skypeemoteoffset = g_ascii_strtoll(json_object_get_string_member(resource, "skypeemoteoffset"), NULL, 10);
- }
-
- if (g_str_equal(messagetype, "Text")) {
- gchar *temp = skypeweb_meify(content, skypeemoteoffset);
- html = purple_markup_escape_text(temp, -1);
- g_free(temp);
- } else {
- html = skypeweb_meify(content, skypeemoteoffset);
- }
-
- if (skypeeditedid && *skypeeditedid) {
- gchar *temp = g_strconcat(_("Edited: "), html, NULL);
- g_free(html);
- html = temp;
- }
-
- if (json_object_has_member(resource, "imdisplayname")) {
- //TODO use this for an alias
- }
-
- if (skypeweb_is_user_self(sa, from)) {
- if (!g_str_has_prefix(html, "?OTR")) {
- PurpleMessage *msg;
- imconv = purple_conversations_find_im_with_account(convbuddyname, sa->account);
- if (imconv == NULL)
- {
- imconv = purple_im_conversation_new(sa->account, convbuddyname);
- }
- conv = PURPLE_CONVERSATION(imconv);
-
- msg = purple_message_new_outgoing(convbuddyname, html, PURPLE_MESSAGE_SEND);
- purple_message_set_time(msg, composetimestamp);
- purple_conversation_write_message(conv, msg);
- purple_message_destroy(msg);
- }
- } else {
- purple_serv_got_im(sa->pc, from, html, PURPLE_MESSAGE_RECV, composetimestamp);
-
- imconv = purple_conversations_find_im_with_account(from, sa->account);
- conv = PURPLE_CONVERSATION(imconv);
- }
- g_free(html);
- } else if (g_str_equal(messagetype, "RichText/UriObject") || g_str_equal(messagetype, "RichText/Media_Video")) {
- PurpleIMConversation *imconv;
-
- if (skypeweb_is_user_self(sa, from)) {
- from = convbuddyname;
- }
- if (from != NULL) {
- imconv = purple_conversations_find_im_with_account(from, sa->account);
- if (imconv == NULL)
- {
- imconv = purple_im_conversation_new(sa->account, from);
- }
-
- conv = PURPLE_CONVERSATION(imconv);
- skypeweb_process_uri_message(messagetype, sa, conv, content, composetimestamp, from);
- }
- } else if (g_str_equal(messagetype, "RichText/Media_GenericFile")) {
- PurpleXmlNode *blob = purple_xmlnode_from_str(content, -1);
- const gchar *uri = purple_xmlnode_get_attrib(blob, "uri");
-
- if (!skypeweb_is_user_self(sa, from)) {
-
- skypeweb_present_uri_as_filetransfer(sa, uri, from);
-
- from = convbuddyname;
- }
- purple_xmlnode_free(blob);
- } else if (g_str_equal(messagetype, "Event/SkypeVideoMessage")) {
- PurpleXmlNode *blob = purple_xmlnode_from_str(content, -1);
- const gchar *sid = purple_xmlnode_get_attrib(blob, "sid");
- PurpleIMConversation *imconv;
-
- if (skypeweb_is_user_self(sa, from)) {
- from = convbuddyname;
- }
- if (from != NULL) {
- imconv = purple_conversations_find_im_with_account(from, sa->account);
- if (imconv == NULL)
- {
- imconv = purple_im_conversation_new(sa->account, from);
- }
-
- conv = PURPLE_CONVERSATION(imconv);
- //skypeweb_download_video_message(sa, sid, conv); //TODO
- (void) sid;
- purple_serv_got_im(sa->pc, from, content, PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_SYSTEM, composetimestamp);
- }
- purple_xmlnode_free(blob);
- } else if (g_str_equal(messagetype, "Event/Call")) {
- PurpleXmlNode *partlist = purple_xmlnode_from_str(content, -1);
- const gchar *partlisttype = purple_xmlnode_get_attrib(partlist, "type");
- const gchar *message = NULL;
- PurpleIMConversation *imconv;
- gboolean incoming = TRUE;
-
- if (skypeweb_is_user_self(sa, from)) {
- incoming = FALSE;
- (void) incoming;
- from = convbuddyname;
- }
-
- if (from != NULL) {
- if (partlisttype) {
- imconv = purple_conversations_find_im_with_account(from, sa->account);
- if (imconv == NULL)
- {
- imconv = purple_im_conversation_new(sa->account, from);
- }
-
- conv = PURPLE_CONVERSATION(imconv);
- if (g_str_equal(partlisttype, "started")) {
- message = _("Call started");
- } else if (g_str_equal(partlisttype, "ended")) {
- PurpleXmlNode *part;
- gint duration_int = -1;
-
- for(part = purple_xmlnode_get_child(partlist, "part");
- part;
- part = purple_xmlnode_get_next_twin(part))
- {
- const gchar *identity = purple_xmlnode_get_attrib(part, "identity");
- if (identity && skypeweb_is_user_self(sa, identity)) {
- PurpleXmlNode *duration = purple_xmlnode_get_child(part, "duration");
- if (duration != NULL) {
- gchar *duration_str;
- duration_str = purple_xmlnode_get_data(duration);
- duration_int = atoi(duration_str);
- break;
- }
- }
- }
- if (duration_int < 0) {
- message = _("Call missed");
- } else {
- //TODO report how long the call was
- message = _("Call ended");
- }
- }
- }
- else {
- message = _("Unsupported call received");
- }
-
- purple_serv_got_im(sa->pc, from, message, PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_SYSTEM, composetimestamp);
- }
-
- purple_xmlnode_free(partlist);
- } else if (g_str_equal(messagetype, "Signal/Flamingo")) {
- const gchar *message = NULL;
-
- if (skypeweb_is_user_self(sa, from)) {
- from = convbuddyname;
- }
-
- if (from != NULL) {
- message = _("Unsupported call received");
-
- purple_serv_got_im(sa->pc, from, message, PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_SYSTEM, composetimestamp);
- }
- } else if (g_str_equal(messagetype, "RichText/Contacts")) {
- PurpleXmlNode *contacts = purple_xmlnode_from_str(content, -1);
- PurpleXmlNode *contact;
-
- for(contact = purple_xmlnode_get_child(contacts, "c"); contact;
- contact = purple_xmlnode_get_next_twin(contact))
- {
- const gchar *contact_id = purple_xmlnode_get_attrib(contact, "s");
- const gchar *contact_name = purple_xmlnode_get_attrib(contact, "f");
-
- gchar *message = g_strdup_printf(_("The user sent a contact: %s (%s)"), contact_id, contact_name);
-
- purple_serv_got_im(sa->pc, from, message, PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_SYSTEM, composetimestamp);
-
- g_free(message);
- }
-
- skypeweb_received_contacts(sa, contacts);
- purple_xmlnode_free(contacts);
- } else if (g_str_equal(messagetype, "RichText/Media_FlikMsg")) {
-
-
- PurpleXmlNode *blob = purple_xmlnode_from_str(content, -1);
-
- const gchar *url_thumbnail = purple_xmlnode_get_attrib(blob, "url_thumbnail");
- gchar *text = purple_markup_strip_html(content); //purple_xmlnode_get_data_unescaped doesn't work properly in this situation
-
- PurpleIMConversation *imconv;
-
- if (skypeweb_is_user_self(sa, from)) {
- from = convbuddyname;
- }
- if (from != NULL) {
- imconv = purple_conversations_find_im_with_account(from, sa->account);
- if (imconv == NULL) {
- imconv = purple_im_conversation_new(sa->account, from);
- }
-
- conv = PURPLE_CONVERSATION(imconv);
-
- skypeweb_download_moji_to_conv(sa, text, url_thumbnail, conv, composetimestamp, from);
-
- const gchar *message = _("The user sent a Moji");
-
- purple_serv_got_im(sa->pc, from, message, PURPLE_MESSAGE_NO_LOG, composetimestamp);
-
- g_free(text);
- }
-
- purple_xmlnode_free(blob);
- } else if (g_str_equal(messagetype, "RichText/Files")) {
- purple_serv_got_im(sa->pc, convbuddyname, _("The user sent files in an unsupported way"), PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_ERROR, composetimestamp);
- } else {
- purple_debug_warning("skypeweb", "Unhandled message resource messagetype '%s'\n", messagetype);
- }
-
- g_free(convbuddyname);
- }
-
- if (conv != NULL) {
- const gchar *id = json_object_get_string_member(resource, "id");
-
- g_free(purple_conversation_get_data(conv, "last_skypeweb_id"));
-
- if (purple_conversation_has_focus(conv)) {
- // Mark message as seen straight away
- gchar *post, *url;
-
- url = g_strdup_printf("/v1/users/ME/conversations/%s/properties?name=consumptionhorizon", purple_url_encode(convname));
- post = g_strdup_printf("{\"consumptionhorizon\":\"%s;%" G_GINT64_FORMAT ";%s\"}", id ? id : "", skypeweb_get_js_time(), id ? id : "");
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, url, post, NULL, NULL, TRUE);
-
- g_free(post);
- g_free(url);
-
- purple_conversation_set_data(conv, "last_skypeweb_id", NULL);
- } else {
- purple_conversation_set_data(conv, "last_skypeweb_id", g_strdup(id));
- }
- }
-
- g_free(convname);
- g_strfreev(messagetype_parts);
-
- if (composetimestamp > purple_account_get_int(sa->account, "last_message_timestamp", 0))
- purple_account_set_int(sa->account, "last_message_timestamp", composetimestamp);
-}
-
-static void
-process_conversation_resource(SkypeWebAccount *sa, JsonObject *resource)
-{
- const gchar *id = json_object_get_string_member(resource, "id");
- (void) id;
-
- JsonObject *threadProperties;
-
- if (json_object_has_member(resource, "threadProperties")) {
- threadProperties = json_object_get_object_member(resource, "threadProperties");
- }
- (void) threadProperties;
-}
-
-static void
-process_thread_resource(SkypeWebAccount *sa, JsonObject *resource)
-{
-
-}
-
-static void
-process_endpointpresence_resource(SkypeWebAccount *sa, JsonObject *resource)
-{
- JsonObject *publicInfo = json_object_get_object_member(resource, "publicInfo");
- if (publicInfo != NULL) {
- const gchar *typ_str = json_object_get_string_member(publicInfo, "typ");
- const gchar *skypeNameVersion = json_object_get_string_member(publicInfo, "skypeNameVersion");
-
- if (typ_str && *typ_str) {
- if (g_str_equal(typ_str, "website")) {
-
- } else {
- gint typ = atoi(typ_str);
- switch(typ) {
- case 17: //Android
- break;
- case 16: //iOS
- break;
- case 12: //WinRT/Metro
- break;
- case 15: //Winphone
- break;
- case 13: //OSX
- break;
- case 11: //Windows
- break;
- case 14: //Linux
- break;
- case 10: //XBox ? skypeNameVersion 11/1.8.0.1006
- break;
- case 1: //SkypeWeb
- break;
- default:
- purple_debug_warning("skypeweb", "Unknown typ %d: %s\n", typ, skypeNameVersion ? skypeNameVersion : "");
- break;
- }
- }
- }
- }
-}
-
-gboolean
-skypeweb_timeout(gpointer userdata)
-{
- SkypeWebAccount *sa = userdata;
- skypeweb_poll(sa);
-
- // If no response within 3 minutes, assume connection lost and try again
- g_source_remove(sa->watchdog_timeout);
- sa->watchdog_timeout = g_timeout_add_seconds(3 * 60, skypeweb_timeout, sa);
-
- return FALSE;
-}
-
-static void
-skypeweb_poll_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
-{
- JsonArray *messages = NULL;
- gint index, length;
- JsonObject *obj = NULL;
-
-
- if (((int)time(NULL)) > sa->vdms_expiry) {
- skypeweb_get_vdms_token(sa);
- }
-
- if (node == NULL && ((int)time(NULL)) > sa->registration_expiry) {
- skypeweb_get_registration_token(sa);
- return;
- }
-
-
- if (node != NULL && json_node_get_node_type(node) == JSON_NODE_OBJECT)
- obj = json_node_get_object(node);
-
- if (obj != NULL) {
- if (json_object_has_member(obj, "eventMessages"))
- messages = json_object_get_array_member(obj, "eventMessages");
-
- if (messages != NULL) {
- length = json_array_get_length(messages);
- for(index = length - 1; index >= 0; index--)
- {
- JsonObject *message = json_array_get_object_element(messages, index);
- const gchar *resourceType = json_object_get_string_member(message, "resourceType");
- JsonObject *resource = json_object_get_object_member(message, "resource");
-
- if (purple_strequal(resourceType, "NewMessage"))
- {
- process_message_resource(sa, resource);
- } else if (purple_strequal(resourceType, "UserPresence"))
- {
- process_userpresence_resource(sa, resource);
- } else if (purple_strequal(resourceType, "EndpointPresence"))
- {
- process_endpointpresence_resource(sa, resource);
- } else if (purple_strequal(resourceType, "ConversationUpdate"))
- {
- process_conversation_resource(sa, resource);
- } else if (purple_strequal(resourceType, "ThreadUpdate"))
- {
- process_thread_resource(sa, resource);
- }
- }
- } else if (json_object_has_member(obj, "errorCode")) {
- gint64 errorCode = json_object_get_int_member(obj, "errorCode");
-
- if (errorCode == 729) {
- // "You must create an endpoint before performing this operation"
- // Dammit, Jim; I'm a programmer, not a surgeon!
- skypeweb_get_registration_token(sa);
- return;
- } else if (errorCode == 450) {
- // "Subscription requested could not be found."
- // No more Womens Weekly? :O
- }
- }
-
- //TODO record id of highest recieved id to make sure we dont process the same id twice
- }
-
- if (!purple_connection_is_disconnecting(sa->pc)) {
- sa->poll_timeout = g_timeout_add_seconds(1, skypeweb_timeout, sa);
- }
-}
-
-void
-skypeweb_poll(SkypeWebAccount *sa)
-{
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, "/v1/users/ME/endpoints/SELF/subscriptions/0/poll", NULL, skypeweb_poll_cb, NULL, TRUE);
-}
-
-void
-skypeweb_mark_conv_seen(PurpleConversation *conv, PurpleConversationUpdateType type)
-{
- PurpleConnection *pc = purple_conversation_get_connection(conv);
- if (!PURPLE_CONNECTION_IS_CONNECTED(pc))
- return;
-
- if (!purple_strequal(purple_protocol_get_id(purple_connection_get_protocol(pc)), SKYPEWEB_PLUGIN_ID))
- return;
-
- if (type == PURPLE_CONVERSATION_UPDATE_UNSEEN) {
- gchar *last_skypeweb_id = purple_conversation_get_data(conv, "last_skypeweb_id");
-
- if (last_skypeweb_id && *last_skypeweb_id) {
- SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
- gchar *post, *url, *convname;
-
- if (PURPLE_IS_IM_CONVERSATION(conv)) {
- const gchar *buddyname = purple_conversation_get_name(conv);
- convname = g_strconcat(skypeweb_user_url_prefix(buddyname), buddyname, NULL);
- } else {
- convname = g_strdup(purple_conversation_get_data(conv, "chatname"));
- }
-
- url = g_strdup_printf("/v1/users/ME/conversations/%s/properties?name=consumptionhorizon", purple_url_encode(convname));
- post = g_strdup_printf("{\"consumptionhorizon\":\"%s;%" G_GINT64_FORMAT ";%s\"}", last_skypeweb_id, skypeweb_get_js_time(), last_skypeweb_id);
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, url, post, NULL, NULL, TRUE);
-
- g_free(convname);
- g_free(post);
- g_free(url);
-
- g_free(last_skypeweb_id);
- purple_conversation_set_data(conv, "last_skypeweb_id", NULL);
- }
- }
-}
-
-static void
-skypeweb_got_thread_users(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
-{
- PurpleChatConversation *chatconv;
- gchar *chatname = user_data;
- JsonObject *response;
- JsonArray *members;
- gint length, index;
-
- chatconv = purple_conversations_find_chat_with_account(chatname, sa->account);
- if (chatconv == NULL)
- return;
- purple_chat_conversation_clear_users(chatconv);
-
- if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
- return;
- response = json_node_get_object(node);
-
- members = json_object_get_array_member(response, "members");
- length = json_array_get_length(members);
- for(index = length - 1; index >= 0; index--)
- {
- JsonObject *member = json_array_get_object_element(members, index);
- const gchar *userLink = json_object_get_string_member(member, "userLink");
- const gchar *role = json_object_get_string_member(member, "role");
- const gchar *username = skypeweb_contact_url_to_name(userLink);
- PurpleChatUserFlags cbflags = PURPLE_CHAT_USER_NONE;
-
- if (role && *role) {
- if (g_str_equal(role, "Admin") || g_str_equal(role, "admin")) {
- cbflags = PURPLE_CHAT_USER_OP;
- } else if (g_str_equal(role, "User") || g_str_equal(role, "user")) {
- //cbflags = PURPLE_CHAT_USER_VOICE;
- }
- }
-
- if (username == NULL && json_object_has_member(member, "linkedMri")) {
- username = skypeweb_contact_url_to_name(json_object_get_string_member(member, "linkedMri"));
- }
- if (username != NULL) {
- purple_chat_conversation_add_user(chatconv, username, NULL, cbflags, FALSE);
- }
- }
-}
-
-void
-skypeweb_get_thread_users(SkypeWebAccount *sa, const gchar *convname)
-{
- gchar *url;
- url = g_strdup_printf("/v1/threads/%s?view=msnp24Equivalent", purple_url_encode(convname));
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, sa->messages_host, url, NULL, skypeweb_got_thread_users, g_strdup(convname), TRUE);
-
- g_free(url);
-}
-
-static void
-skypeweb_got_conv_history(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
-{
- gint since = GPOINTER_TO_INT(user_data);
- JsonObject *obj;
- JsonArray *messages;
- gint index, length;
-
- if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
- return;
- obj = json_node_get_object(node);
-
- messages = json_object_get_array_member(obj, "messages");
- length = json_array_get_length(messages);
- for(index = length - 1; index >= 0; index--)
- {
- JsonObject *message = json_array_get_object_element(messages, index);
- const gchar *composetime = json_object_get_string_member(message, "composetime");
- gint composetimestamp = (gint) purple_str_to_time(composetime, TRUE, NULL, NULL, NULL);
-
- if (composetimestamp > since) {
- process_message_resource(sa, message);
- }
- }
-}
-
-void
-skypeweb_get_conversation_history_since(SkypeWebAccount *sa, const gchar *convname, gint since)
-{
- gchar *url;
- url = g_strdup_printf("/v1/users/ME/conversations/%s/messages?startTime=%d000&pageSize=30&view=msnp24Equivalent&targetType=Passport|Skype|Lync|Thread|PSTN|Agent", purple_url_encode(convname), since);
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, sa->messages_host, url, NULL, skypeweb_got_conv_history, GINT_TO_POINTER(since), TRUE);
-
- g_free(url);
-}
-
-void
-skypeweb_get_conversation_history(SkypeWebAccount *sa, const gchar *convname)
-{
- GString *timestamp_key = make_last_timestamp_setting(convname);
- skypeweb_get_conversation_history_since(sa, convname, purple_account_get_int(sa->account, timestamp_key->str, 0));
- g_string_free(timestamp_key, TRUE);
-}
-
-static void
-skypeweb_got_all_convs(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
-{
- gint since = GPOINTER_TO_INT(user_data);
- JsonObject *obj;
- JsonArray *conversations;
- gint index, length;
-
- if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
- return;
- obj = json_node_get_object(node);
-
- conversations = json_object_get_array_member(obj, "conversations");
- length = json_array_get_length(conversations);
- for(index = 0; index < length; index++) {
- JsonObject *conversation = json_array_get_object_element(conversations, index);
- const gchar *id = json_object_get_string_member(conversation, "id");
- JsonObject *lastMessage = json_object_get_object_member(conversation, "lastMessage");
- if (lastMessage != NULL && json_object_has_member(lastMessage, "composetime")) {
- const gchar *composetime = json_object_get_string_member(lastMessage, "composetime");
- gint composetimestamp = (gint) purple_str_to_time(composetime, TRUE, NULL, NULL, NULL);
-
- if (composetimestamp > since) {
- skypeweb_get_conversation_history_since(sa, id, since);
- }
- }
- }
-}
-
-void
-skypeweb_get_all_conversations_since(SkypeWebAccount *sa, gint since)
-{
- gchar *url;
- url = g_strdup_printf("/v1/users/ME/conversations?startTime=%d000&pageSize=100&view=msnp24Equivalent&targetType=Passport|Skype|Lync|Thread|PSTN|Agent", since);
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, sa->messages_host, url, NULL, skypeweb_got_all_convs, GINT_TO_POINTER(since), TRUE);
-
- g_free(url);
-}
-
-void
-skype_web_get_offline_history(SkypeWebAccount *sa)
-{
- skypeweb_get_all_conversations_since(sa, purple_account_get_int(sa->account, "last_message_timestamp", ((gint) time(NULL))));
-}
-
-
-static void
-skypeweb_got_roomlist_threads(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
-{
- PurpleRoomlist *roomlist = user_data;
- JsonObject *obj;
- JsonArray *conversations;
- gint index, length;
-
- if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
- return;
- obj = json_node_get_object(node);
-
- conversations = json_object_get_array_member(obj, "conversations");
- length = json_array_get_length(conversations);
- for(index = 0; index < length; index++) {
- JsonObject *conversation = json_array_get_object_element(conversations, index);
- const gchar *id = json_object_get_string_member(conversation, "id");
- PurpleRoomlistRoom *room = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM, id, NULL);
-
- purple_roomlist_room_add_field(roomlist, room, id);
- if (json_object_has_member(conversation, "threadProperties")) {
- JsonObject *threadProperties = json_object_get_object_member(conversation, "threadProperties");
- if (threadProperties != NULL) {
- const gchar *num_members = json_object_get_string_member(threadProperties, "membercount");
- purple_roomlist_room_add_field(roomlist, room, num_members);
- const gchar *topic = json_object_get_string_member(threadProperties, "topic");
- purple_roomlist_room_add_field(roomlist, room, topic);
- }
- }
- purple_roomlist_room_add(roomlist, room);
- }
-
- purple_roomlist_set_in_progress(roomlist, FALSE);
-}
-
-PurpleRoomlist *
-skypeweb_roomlist_get_list(PurpleConnection *pc)
-{
- SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
- const gchar *url = "/v1/users/ME/conversations?startTime=0&pageSize=100&view=msnp24Equivalent&targetType=Thread";
- PurpleRoomlist *roomlist;
- GList *fields = NULL;
- PurpleRoomlistField *f;
-
- roomlist = purple_roomlist_new(sa->account);
-
- f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("ID"), "chatname", TRUE);
- fields = g_list_append(fields, f);
-
- f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Users"), "users", FALSE);
- fields = g_list_append(fields, f);
-
- f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Topic"), "topic", FALSE);
- fields = g_list_append(fields, f);
-
- purple_roomlist_set_fields(roomlist, fields);
- purple_roomlist_set_in_progress(roomlist, TRUE);
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, sa->messages_host, url, NULL, skypeweb_got_roomlist_threads, roomlist, FALSE);
-
- return roomlist;
-}
-
-void
-skypeweb_unsubscribe_from_contact_status(SkypeWebAccount *sa, const gchar *who)
-{
- const gchar *contacts_url = "/v1/users/ME/contacts";
- gchar *url;
-
- url = g_strconcat(contacts_url, "/", skypeweb_user_url_prefix(who), purple_url_encode(who), NULL);
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_DELETE | SKYPEWEB_METHOD_SSL, sa->messages_host, url, NULL, NULL, NULL, TRUE);
-
- g_free(url);
-}
-
-static void
-skypeweb_got_contact_status(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
-{
- JsonObject *obj = json_node_get_object(node);
- JsonArray *responses = json_object_get_array_member(obj, "Responses");
-
- if (responses != NULL) {
- guint length = json_array_get_length(responses);
- gint index;
- for(index = length - 1; index >= 0; index--)
- {
- JsonObject *response = json_array_get_object_element(responses, index);
- JsonObject *payload = json_object_get_object_member(response, "Payload");
- process_userpresence_resource(sa, payload);
- }
- }
-}
-
-static void
-skypeweb_lookup_contact_status(SkypeWebAccount *sa, const gchar *contact)
-{
- if (contact == NULL) {
- return;
- }
-
- // Allowed to be up to 10 at once
- gchar *url = g_strdup_printf("/v1/users/ME/contacts/ALL/presenceDocs/messagingService?cMri=%s%s", skypeweb_user_url_prefix(contact), purple_url_encode(contact));
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, sa->messages_host, url, NULL, skypeweb_got_contact_status, NULL, TRUE);
-
- g_free(url);
-}
-
-void
-skypeweb_subscribe_to_contact_status(SkypeWebAccount *sa, GSList *contacts)
-{
- const gchar *contacts_url = "/v1/users/ME/contacts";
- gchar *post;
- GSList *cur = contacts;
- JsonObject *obj;
- JsonArray *contacts_array;
- guint count = 0;
-
- if (contacts == NULL)
- return;
-
- obj = json_object_new();
- contacts_array = json_array_new();
-
- JsonArray *interested = json_array_new();
- json_array_add_string_element(interested, "/v1/users/ME/conversations/ALL/properties");
- json_array_add_string_element(interested, "/v1/users/ME/conversations/ALL/messages");
- json_array_add_string_element(interested, "/v1/users/ME/contacts/ALL");
- json_array_add_string_element(interested, "/v1/threads/ALL");
-
- do {
- JsonObject *contact;
- gchar *id;
-
- if (SKYPEWEB_BUDDY_IS_BOT(cur->data)) {
- purple_protocol_got_user_status(sa->account, cur->data, SKYPEWEB_STATUS_ONLINE, NULL);
- continue;
- }
-
- contact = json_object_new();
-
- id = g_strconcat(skypeweb_user_url_prefix(cur->data), cur->data, NULL);
- json_object_set_string_member(contact, "id", id);
- json_array_add_object_element(contacts_array, contact);
-
- if (id && id[0] == '8') {
- gchar *contact_url = g_strconcat("/v1/users/ME/contacts/", id, NULL);
- json_array_add_string_element(interested, contact_url);
- g_free(contact_url);
- }
-
- g_free(id);
-
- if (count++ >= 100) {
- // Send off the current batch and continue
- json_object_set_array_member(obj, "contacts", contacts_array);
- post = skypeweb_jsonobj_to_string(obj);
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, contacts_url, post, NULL, NULL, TRUE);
-
- g_free(post);
- json_object_unref(obj);
-
- obj = json_object_new();
- contacts_array = json_array_new();
- count = 0;
- }
- } while((cur = g_slist_next(cur)));
-
- json_object_set_array_member(obj, "contacts", contacts_array);
- post = skypeweb_jsonobj_to_string(obj);
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, contacts_url, post, NULL, NULL, TRUE);
-
- g_free(post);
- json_object_unref(obj);
-
-
- gchar *url = g_strdup_printf("/v1/users/ME/endpoints/%s/subscriptions/0?name=interestedResources", purple_url_encode(sa->endpoint));
-
- obj = json_object_new();
- json_object_set_array_member(obj, "interestedResources", interested);
-
- skypeweb_lookup_contact_status(sa, NULL);
- post = skypeweb_jsonobj_to_string(obj);
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, url, post, NULL, NULL, TRUE);
-
- g_free(url);
- g_free(post);
- json_object_unref(obj);
-}
-
-
-static void
-skypeweb_subscribe_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
-{
- skypeweb_do_all_the_things(sa);
-}
-
-static void
-skypeweb_subscribe(SkypeWebAccount *sa)
-{
- JsonObject *obj;
- JsonArray *interested;
- gchar *post;
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, "/v1/users/ME/endpoints/SELF/properties?name=supportsMessageProperties", "{\"supportsMessageProperties\":true}", NULL, NULL, TRUE);
-
- interested = json_array_new();
- json_array_add_string_element(interested, "/v1/users/ME/conversations/ALL/properties");
- json_array_add_string_element(interested, "/v1/users/ME/conversations/ALL/messages");
- json_array_add_string_element(interested, "/v1/users/ME/contacts/ALL");
- json_array_add_string_element(interested, "/v1/threads/ALL");
-
- obj = json_object_new();
- json_object_set_array_member(obj, "interestedResources", interested);
- json_object_set_string_member(obj, "template", "raw");
- json_object_set_string_member(obj, "channelType", "httpLongPoll");
-
- post = skypeweb_jsonobj_to_string(obj);
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, "/v1/users/ME/endpoints/SELF/subscriptions", post, skypeweb_subscribe_cb, NULL, TRUE);
-
- g_free(post);
- json_object_unref(obj);
-}
-
-static void
-skypeweb_got_registration_token(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
-{
- const gchar *registration_token = NULL;
- gchar *endpointId = NULL;
- gchar *expires = NULL;
- SkypeWebAccount *sa = user_data;
- gchar *new_messages_host = NULL;
- const gchar *data;
- gsize len;
-
- data = purple_http_response_get_data(response, &len);
-
- if (data == NULL) {
- if (purple_major_version == 2 && (
- purple_minor_version < 10 ||
- (purple_minor_version == 10 && purple_micro_version < 11))
- ) {
- purple_connection_error (sa->pc,
- PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
- _("Your version of libpurple is too old.\nUpgrade to 2.10.11 or newer and try again."));
- return;
- }
- }
-
- new_messages_host = skypeweb_string_get_chunk(purple_http_response_get_header(response, "Location"), -1, "https://", "/");
- if (new_messages_host != NULL && !g_str_equal(sa->messages_host, new_messages_host)) {
- g_free(sa->messages_host);
- sa->messages_host = new_messages_host;
-
- // Your princess is in another castle
- purple_debug_info("skypeweb", "Messages host has changed to %s\n", sa->messages_host);
-
- skypeweb_get_registration_token(sa);
- return;
- }
- g_free(new_messages_host);
-
- registration_token = purple_http_response_get_header(response, "Set-RegistrationToken");
-
- if (registration_token == NULL) {
- if (purple_account_get_string(sa->account, "refresh-token", NULL)) {
- skypeweb_refresh_token_login(sa);
- } else {
- purple_connection_error (sa->pc,
- PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
- _("Failed getting Registration Token"));
- }
- return;
- }
- //purple_debug_info("skypeweb", "New RegistrationToken is %s\n", registration_token);
- endpointId = skypeweb_string_get_chunk(registration_token, -1, "endpointId=", NULL);
- expires = skypeweb_string_get_chunk(registration_token, -1, "expires=", ";");
-
- g_free(sa->registration_token); sa->registration_token = g_strndup(registration_token, strchr(registration_token, ';') - registration_token);
- g_free(sa->endpoint); sa->endpoint = endpointId;
- if (expires && *expires) {
- sa->registration_expiry = atoi(expires);
- g_free(expires);
- }
-
- if (sa->endpoint) {
- gchar *url = g_strdup_printf("/v1/users/ME/endpoints/%s/presenceDocs/messagingService", purple_url_encode(sa->endpoint));
- const gchar *post = "{\"id\":\"messagingService\", \"type\":\"EndpointPresenceDoc\", \"selfLink\":\"uri\", \"privateInfo\":{\"epname\":\"skype\"}, \"publicInfo\":{\"capabilities\":\"\", \"type\":1, \"typ\":1, \"skypeNameVersion\":\"" SKYPEWEB_CLIENTINFO_VERSION "/" SKYPEWEB_CLIENTINFO_NAME "\", \"nodeInfo\":\"\", \"version\":\"" SKYPEWEB_CLIENTINFO_VERSION "\"}}";
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, url, post, NULL, NULL, TRUE);
- g_free(url);
- }
-
- skypeweb_gather_self_properties(sa);
- skypeweb_subscribe(sa);
-}
-
-static void
-skypeweb_got_vdms_token(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
-{
- const gchar *token;
- SkypeWebAccount *sa = user_data;
- JsonParser *parser = json_parser_new();
- const gchar *data;
- gsize len;
-
- data = purple_http_response_get_data(response, &len);
-
- if (json_parser_load_from_data(parser, data, len, NULL)) {
- JsonNode *root = json_parser_get_root(parser);
- JsonObject *obj = json_node_get_object(root);
-
- token = json_object_get_string_member(obj, "token");
- g_free(sa->vdms_token);
- sa->vdms_token = g_strdup(token);
- sa->vdms_expiry = (int)time(NULL) + SKYPEWEB_VDMS_TTL;
- }
-
- g_object_unref(parser);
-
-}
-
-void
-skypeweb_get_registration_token(SkypeWebAccount *sa)
-{
- gchar *messages_url;
- PurpleHttpRequest *request;
- gchar *curtime;
- gchar *response;
-
- g_free(sa->registration_token); sa->registration_token = NULL;
- g_free(sa->endpoint); sa->endpoint = NULL;
-
- curtime = g_strdup_printf("%d", (int) time(NULL));
- response = skypeweb_hmac_sha256(curtime);
-
- messages_url = g_strdup_printf("https://%s/v1/users/ME/endpoints", sa->messages_host);
-
- request = purple_http_request_new(messages_url);
- purple_http_request_set_method(request, "POST");
- purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
- purple_http_request_set_max_redirects(request, 0);
- purple_http_request_header_set(request, "Accept", "*/*");
- purple_http_request_header_set(request, "BehaviorOverride", "redirectAs404");
- purple_http_request_header_set_printf(request, "LockAndKey", "appId=" SKYPEWEB_LOCKANDKEY_APPID "; time=%s; lockAndKeyResponse=%s", curtime, response);
- purple_http_request_header_set(request, "ClientInfo", "os=Windows; osVer=8.1; proc=Win32; lcid=en-us; deviceType=1; country=n/a; clientName=" SKYPEWEB_CLIENTINFO_NAME "; clientVer=" SKYPEWEB_CLIENTINFO_VERSION);
- purple_http_request_header_set(request, "Content-Type", "application/json");
- purple_http_request_header_set_printf(request, "Authentication", "skypetoken=%s", sa->skype_token);
- purple_http_request_set_contents(request, "{\"endpointFeatures\":\"Agent\"}", -1);
- purple_http_request(sa->pc, request, skypeweb_got_registration_token, sa);
- purple_http_request_unref(request);
-
- g_free(curtime);
- g_free(response);
- g_free(messages_url);
-}
-
-void
-skypeweb_get_vdms_token(SkypeWebAccount *sa)
-{
- const gchar *messages_url = "https://" SKYPEWEB_STATIC_HOST "/pes/v1/petoken";
- PurpleHttpRequest *request;
-
- request = purple_http_request_new(messages_url);
- purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
- purple_http_request_header_set(request, "Accept", "*/*");
- purple_http_request_header_set(request, "Origin", "https://web.skype.com");
- purple_http_request_header_set_printf(request, "Authorization", "skype_token %s", sa->skype_token);
- purple_http_request_header_set(request, "Content-Type", "application/x-www-form-urlencoded");
- purple_http_request_set_contents(request, "{}", -1);
- purple_http_request(sa->pc, request, skypeweb_got_vdms_token, sa);
- purple_http_request_unref(request);
-}
-
-
-guint
-skypeweb_conv_send_typing(PurpleConversation *conv, PurpleIMTypingState state)
-{
- PurpleConnection *pc = purple_conversation_get_connection(conv);
- SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
- gchar *post, *url;
- JsonObject *obj;
-
- if (!PURPLE_CONNECTION_IS_CONNECTED(pc))
- return 0;
-
- if (!purple_strequal(purple_protocol_get_id(purple_connection_get_protocol(pc)), SKYPEWEB_PLUGIN_ID))
- return 0;
-
- url = g_strdup_printf("/v1/users/ME/conversations/%s/messages", purple_url_encode(purple_conversation_get_name(conv)));
-
- obj = json_object_new();
- json_object_set_int_member(obj, "clientmessageid", time(NULL));
- json_object_set_string_member(obj, "content", "");
- json_object_set_string_member(obj, "messagetype", state == PURPLE_IM_TYPING ? "Control/Typing" : "Control/ClearTyping");
- json_object_set_string_member(obj, "contenttype", "text");
-
- post = skypeweb_jsonobj_to_string(obj);
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, url, post, NULL, NULL, TRUE);
-
- g_free(post);
- json_object_unref(obj);
- g_free(url);
-
- return 5;
-}
-
-guint
-skypeweb_send_typing(PurpleConnection *pc, const gchar *name, PurpleIMTypingState state)
-{
- SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
- gchar *post, *url;
- JsonObject *obj;
-
- url = g_strdup_printf("/v1/users/ME/conversations/%s%s/messages", skypeweb_user_url_prefix(name), purple_url_encode(name));
-
- obj = json_object_new();
- json_object_set_int_member(obj, "clientmessageid", time(NULL));
- json_object_set_string_member(obj, "content", "");
- json_object_set_string_member(obj, "messagetype", state == PURPLE_IM_TYPING ? "Control/Typing" : "Control/ClearTyping");
- json_object_set_string_member(obj, "contenttype", "text");
-
- post = skypeweb_jsonobj_to_string(obj);
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, url, post, NULL, NULL, TRUE);
-
- g_free(post);
- json_object_unref(obj);
- g_free(url);
-
- return 5;
-}
-
-
-static void
-skypeweb_set_statusid(SkypeWebAccount *sa, const gchar *status)
-{
- gchar *post;
-
- g_return_if_fail(status);
-
- post = g_strdup_printf("{\"status\":\"%s\"}", status);
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, "/v1/users/ME/presenceDocs/messagingService", post, NULL, NULL, TRUE);
- g_free(post);
-}
-
-void
-skypeweb_set_status(PurpleAccount *account, PurpleStatus *status)
-{
- PurpleConnection *pc = purple_account_get_connection(account);
- SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
-
- skypeweb_set_statusid(sa, purple_status_get_id(status));
- skypeweb_set_mood_message(sa, purple_status_get_attr_string(status, "message"));
-}
-
-void
-skypeweb_set_idle(PurpleConnection *pc, int time)
-{
- const gchar *status_id;
- PurpleStatus *status;
-
- SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
-
- status = purple_account_get_active_status(purple_connection_get_account(pc));
- status_id = purple_status_get_id(status);
-
- /* Only go idle if active status is online */
- if (!strcmp(status_id, SKYPEWEB_STATUS_ONLINE)) {
- if (time < 30) {
- skypeweb_set_statusid(sa, SKYPEWEB_STATUS_ONLINE);
- } else {
- skypeweb_set_statusid(sa, SKYPEWEB_STATUS_IDLE);
- }
- }
-}
-
-
-static void
-skypeweb_sent_message_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
-{
- gchar *convname = user_data;
- JsonObject *obj = NULL;
-
- if (node != NULL && json_node_get_node_type(node) == JSON_NODE_OBJECT)
- obj = json_node_get_object(node);
-
- if (obj != NULL) {
- if (json_object_has_member(obj, "errorCode")) {
- PurpleChatConversation *chatconv = purple_conversations_find_chat_with_account(convname, sa->account);
- if (chatconv == NULL) {
- purple_conversation_present_error(skypeweb_strip_user_prefix(convname), sa->account, json_object_get_string_member(obj, "message"));
- } else {
- PurpleMessage *msg = purple_message_new_system(json_object_get_string_member(obj, "message"), PURPLE_MESSAGE_ERROR);
- purple_conversation_write_message(PURPLE_CONVERSATION(chatconv), msg);
- purple_message_destroy(msg);
- }
- }
- }
-
- g_free(convname);
-}
-
-static void
-skypeweb_send_message(SkypeWebAccount *sa, const gchar *convname, const gchar *message)
-{
- gchar *post, *url;
- JsonObject *obj;
- gint64 clientmessageid;
- gchar *clientmessageid_str;
- gchar *stripped;
- static GRegex *font_strip_regex = NULL;
- gchar *font_stripped;
- char *xhtml;
-
- url = g_strdup_printf("/v1/users/ME/conversations/%s/messages", purple_url_encode(convname));
-
- clientmessageid = skypeweb_get_js_time();
- clientmessageid_str = g_strdup_printf("%" G_GINT64_FORMAT "", clientmessageid);
-
- purple_markup_html_to_xhtml(message, &xhtml, NULL);
- // Some clients don't receive messages with <br>'s in them
- stripped = purple_strreplace(xhtml, "<br>", "\r\n");
- g_free(xhtml);
-
- // Pidgin has a nasty habit of sending <font size="3"> when copy-pasting text
- if (font_strip_regex == NULL) {
- font_strip_regex = g_regex_new("(<font [^>]*)size=\"[0-9]+\"([^>]*>)", G_REGEX_CASELESS | G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
- }
- font_stripped = g_regex_replace(font_strip_regex, stripped, -1, 0, "\\1\\2", 0, NULL);
- if (font_stripped != NULL) {
- g_free(stripped);
- stripped = font_stripped;
- }
-
- obj = json_object_new();
- json_object_set_string_member(obj, "clientmessageid", clientmessageid_str);
- json_object_set_string_member(obj, "content", stripped);
- if (G_UNLIKELY(g_str_has_prefix(message, "<URIObject "))) {
- json_object_set_string_member(obj, "messagetype", "RichText/Media_GenericFile"); //hax!
- } else {
- json_object_set_string_member(obj, "messagetype", "RichText");
- }
- json_object_set_string_member(obj, "contenttype", "text");
- json_object_set_string_member(obj, "imdisplayname", sa->self_display_name ? sa->self_display_name : sa->username);
-
- if (g_str_has_prefix(message, "/me ")) {
- json_object_set_string_member(obj, "skypeemoteoffset", "4"); //Why is this a string :(
- }
-
- post = skypeweb_jsonobj_to_string(obj);
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, url, post, skypeweb_sent_message_cb, g_strdup(convname), TRUE);
-
- g_free(post);
- json_object_unref(obj);
- g_free(url);
- g_free(stripped);
-
- g_hash_table_insert(sa->sent_messages_hash, clientmessageid_str, clientmessageid_str);
-}
-
-
-gint
-skypeweb_chat_send(PurpleConnection *pc, gint id,
-#if PURPLE_VERSION_CHECK(3, 0, 0)
-PurpleMessage *msg)
-{
- const gchar *message = purple_message_get_contents(msg);
-#else
-const gchar *message, PurpleMessageFlags flags)
-{
-#endif
-
- SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
-
- PurpleChatConversation *chatconv;
- const gchar* chatname;
-
- chatconv = purple_conversations_find_chat(pc, id);
- chatname = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "chatname");
- if (!chatname) {
- // Fix for a condition around the chat data and serv_got_joined_chat()
- chatname = purple_conversation_get_name(PURPLE_CONVERSATION(chatconv));
- if (!chatname)
- return -1;
- }
-
- skypeweb_send_message(sa, chatname, message);
-
- purple_serv_got_chat_in(pc, id, sa->username, PURPLE_MESSAGE_SEND, message, time(NULL));
-
- return 1;
-}
-
-gint
-skypeweb_send_im(PurpleConnection *pc,
-#if PURPLE_VERSION_CHECK(3, 0, 0)
-PurpleMessage *msg)
-{
- const gchar *who = purple_message_get_recipient(msg);
- const gchar *message = purple_message_get_contents(msg);
-#else
-const gchar *who, const gchar *message, PurpleMessageFlags flags)
-{
-#endif
-
- SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
- gchar *convname;
-
- convname = g_strconcat(skypeweb_user_url_prefix(who), who, NULL);
- skypeweb_send_message(sa, convname, message);
- g_free(convname);
-
- return 1;
-}
-
-
-void
-skypeweb_chat_invite(PurpleConnection *pc, int id, const char *message, const char *who)
-{
- SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
- PurpleChatConversation *chatconv;
- gchar *chatname;
- gchar *post;
- GString *url;
-
- chatconv = purple_conversations_find_chat(pc, id);
- chatname = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "chatname");
-
- url = g_string_new("/v1/threads/");
- g_string_append_printf(url, "%s", purple_url_encode(chatname));
- g_string_append(url, "/members/");
- g_string_append_printf(url, "%s%s", skypeweb_user_url_prefix(who), purple_url_encode(who));
-
- post = "{\"role\":\"User\"}";
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, url->str, post, NULL, NULL, TRUE);
-
- g_string_free(url, TRUE);
-}
-
-void
-skypeweb_chat_kick(PurpleConnection *pc, int id, const char *who)
-{
- SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
- PurpleChatConversation *chatconv;
- gchar *chatname;
- gchar *post;
- GString *url;
-
- chatconv = purple_conversations_find_chat(pc, id);
- chatname = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "chatname");
-
- url = g_string_new("/v1/threads/");
- g_string_append_printf(url, "%s", purple_url_encode(chatname));
- g_string_append(url, "/members/");
- g_string_append_printf(url, "%s%s", skypeweb_user_url_prefix(who), purple_url_encode(who));
-
- post = "";
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_DELETE | SKYPEWEB_METHOD_SSL, sa->messages_host, url->str, post, NULL, NULL, TRUE);
-
- g_string_free(url, TRUE);
-}
-
-void
-skypeweb_initiate_chat(SkypeWebAccount *sa, const gchar *who)
-{
- JsonObject *obj, *contact;
- JsonArray *members;
- gchar *id, *post;
-
- obj = json_object_new();
- members = json_array_new();
-
- contact = json_object_new();
- id = g_strconcat(skypeweb_user_url_prefix(who), who, NULL);
- json_object_set_string_member(contact, "id", id);
- json_object_set_string_member(contact, "role", "User");
- json_array_add_object_element(members, contact);
- g_free(id);
-
- contact = json_object_new();
- id = g_strconcat(skypeweb_user_url_prefix(sa->username), sa->username, NULL);
- json_object_set_string_member(contact, "id", id);
- json_object_set_string_member(contact, "role", "Admin");
- json_array_add_object_element(members, contact);
- g_free(id);
-
- json_object_set_array_member(obj, "members", members);
- post = skypeweb_jsonobj_to_string(obj);
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, "/v1/threads", post, NULL, NULL, TRUE);
-
- g_free(post);
- json_object_unref(obj);
-}
-
-void
-skypeweb_initiate_chat_from_node(PurpleBlistNode *node, gpointer userdata)
-{
- if(PURPLE_IS_BUDDY(node))
- {
- PurpleBuddy *buddy = (PurpleBuddy *) node;
- SkypeWebAccount *sa;
-
- if (userdata) {
- sa = userdata;
- } else {
- PurpleConnection *pc = purple_account_get_connection(purple_buddy_get_account(buddy));
- sa = purple_connection_get_protocol_data(pc);
- }
-
- skypeweb_initiate_chat(sa, purple_buddy_get_name(buddy));
- }
-}
-
-void
-skypeweb_chat_set_topic(PurpleConnection *pc, int id, const char *topic)
-{
- SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
- PurpleChatConversation *chatconv;
- JsonObject *obj;
- gchar *chatname;
- gchar *post;
- GString *url;
-
- chatconv = purple_conversations_find_chat(pc, id);
- chatname = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "chatname");
-
- url = g_string_new("/v1/threads/");
- g_string_append_printf(url, "%s", purple_url_encode(chatname));
- g_string_append(url, "/properties?name=topic");
-
- obj = json_object_new();
- json_object_set_string_member(obj, "topic", topic);
- post = skypeweb_jsonobj_to_string(obj);
-
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, url->str, post, NULL, NULL, TRUE);
-
- g_string_free(url, TRUE);
- g_free(post);
- json_object_unref(obj);
-}
-
-void
-skypeweb_get_thread_url(SkypeWebAccount *sa, const gchar *thread)
-{
- //POST https://api.scheduler.skype.com/threads
- //{"baseDomain":"https://join.skype.com/launch/","threadId":"%s"}
-
- // {"Id":"MeMxigEAAAAxOTo5NDZkMjExMGQ4YmU0ZjQzODc3NjMxNDQ3ZTgxYWNmNkB0aHJlYWQuc2t5cGU","Blob":null,"JoinUrl":"https://join.skype.com/ALXsHZ2RFQnk","ThreadId":"19:946d2110d8be4f43877631447e81acf6@thread.skype"}
-}
-
-
-static void
-skypeweb_got_self_properties(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
-{
- JsonObject *userobj;
- if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
- return;
- userobj = json_node_get_object(node);
-
- if (json_object_has_member(userobj, "primaryMemberName")) {
- g_free(sa->primary_member_name); sa->primary_member_name = g_strdup(json_object_get_string_member(userobj, "primaryMemberName"));
- }
-}
-
-void
-skypeweb_gather_self_properties(SkypeWebAccount *sa)
-{
- skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, sa->messages_host, "/v1/users/ME/properties", NULL, skypeweb_got_self_properties, NULL, TRUE);
-}
+/*
+ * SkypeWeb Plugin for libpurple/Pidgin
+ * Copyright (c) 2014-2020 Eion Robb
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "skypeweb_messages.h"
+#include "skypeweb_util.h"
+#include "skypeweb_connection.h"
+#include "skypeweb_contacts.h"
+#include "skypeweb_login.h"
+
+static GString* make_last_timestamp_setting(const gchar *convname) {
+ GString *rv = g_string_new(NULL);
+ g_string_printf(rv, "%s_last_message_timestamp", convname);
+ return rv;
+}
+
+static gboolean
+skypeweb_is_user_self(SkypeWebAccount *sa, const gchar *username) {
+ if (!username || *username == 0) {
+ return FALSE;
+ }
+
+ if (sa->username) {
+ if (g_str_equal(username, sa->username)) {
+ return TRUE;
+ }
+ }
+
+ if (sa->primary_member_name) {
+ if (g_str_equal(username, sa->primary_member_name)) {
+ return TRUE;
+ }
+ }
+
+ return !g_ascii_strcasecmp(username, purple_account_get_username(sa->account));
+}
+
+static gchar *
+skypeweb_meify(const gchar *message, gint skypeemoteoffset)
+{
+ guint len;
+ len = strlen(message);
+
+ if (skypeemoteoffset <= 0 || skypeemoteoffset >= len)
+ return g_strdup(message);
+
+ return g_strconcat("/me ", message + skypeemoteoffset, NULL);
+}
+
+static void
+process_userpresence_resource(SkypeWebAccount *sa, JsonObject *resource)
+{
+ const gchar *selfLink = json_object_get_string_member(resource, "selfLink");
+ const gchar *status = json_object_get_string_member(resource, "status");
+ // const gchar *capabilities = json_object_get_string_member(resource, "capabilities");
+ // const gchar *lastSeenAt = json_object_get_string_member(resource, "lastSeenAt");
+ const gchar *from;
+ gboolean is_idle;
+
+ from = skypeweb_contact_url_to_name(selfLink);
+ g_return_if_fail(from);
+
+ if (!purple_blist_find_buddy(sa->account, from))
+ {
+ PurpleGroup *group = purple_blist_find_group("Skype");
+ if (!group)
+ {
+ group = purple_group_new("Skype");
+ purple_blist_add_group(group, NULL);
+ }
+
+ if (skypeweb_is_user_self(sa, from)) {
+ return;
+ }
+
+ purple_blist_add_buddy(purple_buddy_new(sa->account, from, NULL), NULL, group, NULL);
+ }
+
+ // if (g_str_equal(capabilities, "IsMobile")) { //"Seamless | IsMobile"
+ // purple_protocol_got_user_status(sa->account, from, "mobile", NULL);
+ // }
+
+ is_idle = purple_strequal(status, SKYPEWEB_STATUS_IDLE);
+ if (!is_idle) {
+ purple_protocol_got_user_status(sa->account, from, status, NULL);
+ } else {
+ purple_protocol_got_user_status(sa->account, from, SKYPEWEB_STATUS_ONLINE, NULL);
+ }
+
+ purple_protocol_got_user_idle(sa->account, from, is_idle, 0);
+}
+
+// static gboolean
+// skypeweb_clear_typing_hack(PurpleChatUser *cb)
+// {
+ // PurpleChatUserFlags cbflags;
+
+ // cbflags = purple_chat_user_get_flags(cb);
+ // cbflags &= ~PURPLE_CHAT_USER_TYPING & ~PURPLE_CHAT_USER_VOICE;
+ // purple_chat_user_set_flags(cb, cbflags);
+
+ // return FALSE;
+// }
+
+static void
+skypeweb_process_uri_message(const gchar* messagetype, SkypeWebAccount *sa, PurpleConversation* conv, const gchar* uri_content, time_t composetimestamp, const gchar* from) {
+ PurpleXmlNode *blob = purple_xmlnode_from_str(uri_content, -1);
+ const gchar *uri = purple_xmlnode_get_attrib(blob, "url_thumbnail");
+ SkypeWebURIType uri_type;
+
+ if (g_str_has_suffix(messagetype, "Media_Video")) {
+ uri_type = SKYPEWEB_URI_TYPE_VIDEO;
+ } else {
+ uri_type = SKYPEWEB_URI_TYPE_IMAGE;
+ }
+
+ skypeweb_download_uri_to_conv(sa, uri, uri_type, conv, composetimestamp, from);
+ purple_xmlnode_free(blob);
+}
+
+static void
+process_message_resource(SkypeWebAccount *sa, JsonObject *resource)
+{
+ const gchar *clientmessageid = NULL;
+ const gchar *skypeeditedid = NULL;
+ const gchar *messagetype = json_object_get_string_member(resource, "messagetype");
+ const gchar *from = json_object_get_string_member(resource, "from");
+ const gchar *content = NULL;
+ const gchar *composetime = json_object_get_string_member(resource, "composetime");
+ const gchar *conversationLink = json_object_get_string_member(resource, "conversationLink");
+ time_t composetimestamp = purple_str_to_time(composetime, TRUE, NULL, NULL, NULL);
+ gchar **messagetype_parts;
+ PurpleConversation *conv = NULL;
+ gchar *convname = NULL;
+
+ g_return_if_fail(messagetype != NULL);
+
+ messagetype_parts = g_strsplit(messagetype, "/", -1);
+
+ if (json_object_has_member(resource, "clientmessageid"))
+ clientmessageid = json_object_get_string_member(resource, "clientmessageid");
+
+ if (clientmessageid && *clientmessageid && g_hash_table_remove(sa->sent_messages_hash, clientmessageid)) {
+ // We sent this message from here already
+ g_strfreev(messagetype_parts);
+ return;
+ }
+
+ if (json_object_has_member(resource, "skypeeditedid"))
+ skypeeditedid = json_object_get_string_member(resource, "skypeeditedid");
+ if (json_object_has_member(resource, "content"))
+ content = json_object_get_string_member(resource, "content");
+
+ if (conversationLink && strstr(conversationLink, "/19:")) {
+ // This is a Thread/Group chat message
+ const gchar *chatname, *topic;
+ PurpleChatConversation *chatconv;
+
+ chatname = skypeweb_thread_url_to_name(conversationLink);
+ convname = g_strdup(chatname);
+ chatconv = purple_conversations_find_chat_with_account(chatname, sa->account);
+ if (!chatconv) {
+ chatconv = purple_serv_got_joined_chat(sa->pc, g_str_hash(chatname), chatname);
+ purple_conversation_set_data(PURPLE_CONVERSATION(chatconv), "chatname", g_strdup(chatname));
+
+ if (json_object_has_member(resource, "threadtopic")) {
+ topic = json_object_get_string_member(resource, "threadtopic");
+ purple_chat_conversation_set_topic(chatconv, NULL, topic);
+ }
+
+ skypeweb_get_conversation_history(sa, chatname);
+ skypeweb_get_thread_users(sa, chatname);
+ }
+ GString *chat_last_timestamp = make_last_timestamp_setting(convname);
+ purple_account_set_int(sa->account, chat_last_timestamp->str, composetimestamp);
+ g_string_free(chat_last_timestamp, TRUE);
+
+ conv = PURPLE_CONVERSATION(chatconv);
+
+ if (g_str_equal(messagetype, "Control/Typing")) {
+ PurpleChatUserFlags cbflags;
+ PurpleChatUser *cb;
+
+ from = skypeweb_contact_url_to_name(from);
+ if (from == NULL) {
+ g_strfreev(messagetype_parts);
+ g_return_if_reached();
+ return;
+ }
+
+ // typing notification text, not personalized because of multiple "typing" events
+ if (purple_account_get_bool(sa->account, "show-typing-as-text", FALSE)) {
+ const gchar *message = g_strdup_printf("%s ...", N_("buddy typing"));
+ const gchar *last_message = NULL;
+
+ // get last message (first in GList)
+ if (conv && g_list_length(purple_conversation_get_message_history(conv))) {
+ PurpleMessage *last = g_list_nth_data(g_list_first(purple_conversation_get_message_history(conv)),0);
+ last_message = purple_message_get_contents(last);
+ }
+
+ // add typing notification to chat
+ if (last_message && !g_str_equal(last_message, message)) {
+ PurpleMessage *msg = purple_message_new_system(message, PURPLE_MESSAGE_NO_LOG | PURPLE_MESSAGE_NOTIFY | PURPLE_MESSAGE_ACTIVE_ONLY);
+ purple_message_set_time(msg, composetimestamp);
+ purple_conversation_write_message(conv, msg);
+ purple_message_destroy(msg);
+ }
+ }
+
+ cb = purple_chat_conversation_find_user(chatconv, from);
+ if (cb != NULL) {
+ cbflags = purple_chat_user_get_flags(cb);
+
+ cbflags |= PURPLE_CHAT_USER_TYPING;
+
+ // typing notification icon
+ if (purple_account_get_bool(sa->account, "show-typing-as-icon", FALSE)) {
+ cbflags |= PURPLE_CHAT_USER_VOICE;
+ }
+
+ purple_chat_user_set_flags(cb, cbflags);
+
+ //purple_timeout_add_seconds(7, skypeweb_clear_typing_hack, cb);
+ }
+
+ } else if ((g_str_equal(messagetype, "RichText") || g_str_equal(messagetype, "Text"))) {
+ gchar *html;
+ gint64 skypeemoteoffset = 0;
+ PurpleChatUserFlags cbflags;
+ PurpleChatUser *cb;
+
+ if (json_object_has_member(resource, "skypeemoteoffset")) {
+ skypeemoteoffset = g_ascii_strtoll(json_object_get_string_member(resource, "skypeemoteoffset"), NULL, 10);
+ }
+
+ from = skypeweb_contact_url_to_name(from);
+ if (from == NULL) {
+ g_free(messagetype_parts);
+ g_return_if_reached();
+ return;
+ }
+
+ // Remove typing notification icon w/o "show-typing-as-icon" option check.
+ // Hard reset cbflags even if user changed settings while someone typing message.
+
+ cb = purple_chat_conversation_find_user(chatconv, from);
+ if (cb != NULL) {
+ cbflags = purple_chat_user_get_flags(cb);
+
+ cbflags &= ~PURPLE_CHAT_USER_TYPING & ~PURPLE_CHAT_USER_VOICE;
+
+ purple_chat_user_set_flags(cb, cbflags);
+ }
+
+ if (content && *content) {
+ if (g_str_equal(messagetype, "Text")) {
+ gchar *temp = skypeweb_meify(content, skypeemoteoffset);
+ html = purple_markup_escape_text(temp, -1);
+ g_free(temp);
+ } else {
+ html = skypeweb_meify(content, skypeemoteoffset);
+ }
+
+ if (skypeeditedid && *skypeeditedid) {
+ gchar *temp = g_strconcat(_("Edited: "), html, NULL);
+ g_free(html);
+ html = temp;
+ }
+
+ purple_serv_got_chat_in(sa->pc, g_str_hash(chatname), from, skypeweb_is_user_self(sa, from) ? PURPLE_MESSAGE_SEND : PURPLE_MESSAGE_RECV, html, composetimestamp);
+
+ g_free(html);
+ }
+ } else if (g_str_equal(messagetype, "ThreadActivity/AddMember")) {
+ PurpleXmlNode *blob = purple_xmlnode_from_str(content, -1);
+ PurpleXmlNode *target;
+ for(target = purple_xmlnode_get_child(blob, "target"); target;
+ target = purple_xmlnode_get_next_twin(target))
+ {
+ gchar *user = purple_xmlnode_get_data(target);
+ if (!purple_chat_conversation_find_user(chatconv, &user[2]))
+ purple_chat_conversation_add_user(chatconv, &user[2], NULL, PURPLE_CHAT_USER_NONE, TRUE);
+ g_free(user);
+ }
+ purple_xmlnode_free(blob);
+ } else if (g_str_equal(messagetype, "ThreadActivity/DeleteMember")) {
+ PurpleXmlNode *blob = purple_xmlnode_from_str(content, -1);
+ PurpleXmlNode *target;
+ for(target = purple_xmlnode_get_child(blob, "target"); target;
+ target = purple_xmlnode_get_next_twin(target))
+ {
+ gchar *user = purple_xmlnode_get_data(target);
+ if (skypeweb_is_user_self(sa, &user[2]))
+ purple_chat_conversation_leave(chatconv);
+ purple_chat_conversation_remove_user(chatconv, &user[2], NULL);
+ g_free(user);
+ }
+ purple_xmlnode_free(blob);
+ } else if (g_str_equal(messagetype, "ThreadActivity/TopicUpdate")) {
+ PurpleXmlNode *blob = purple_xmlnode_from_str(content, -1);
+ gchar *initiator = purple_xmlnode_get_data(purple_xmlnode_get_child(blob, "initiator"));
+ gchar *value = purple_xmlnode_get_data(purple_xmlnode_get_child(blob, "value"));
+
+ purple_chat_conversation_set_topic(chatconv, &initiator[2], value);
+ purple_conversation_update(conv, PURPLE_CONVERSATION_UPDATE_TOPIC);
+
+ g_free(initiator);
+ g_free(value);
+ purple_xmlnode_free(blob);
+ } else if (g_str_equal(messagetype, "ThreadActivity/RoleUpdate")) {
+ PurpleXmlNode *blob = purple_xmlnode_from_str(content, -1);
+ PurpleXmlNode *target;
+ PurpleChatUser *cb;
+
+ for(target = purple_xmlnode_get_child(blob, "target"); target;
+ target = purple_xmlnode_get_next_twin(target))
+ {
+ gchar *user = purple_xmlnode_get_data(purple_xmlnode_get_child(target, "id"));
+ gchar *role = purple_xmlnode_get_data(purple_xmlnode_get_child(target, "role"));
+ PurpleChatUserFlags cbflags = PURPLE_CHAT_USER_NONE;
+
+ if (role && *role) {
+ if (g_str_equal(role, "Admin") || g_str_equal(role, "admin")) {
+ cbflags = PURPLE_CHAT_USER_OP;
+ } else if (g_str_equal(role, "User") || g_str_equal(role, "user")) {
+ //cbflags = PURPLE_CHAT_USER_VOICE;
+ }
+ }
+ #if !PURPLE_VERSION_CHECK(3, 0, 0)
+ purple_conv_chat_user_set_flags(chatconv, &user[2], cbflags);
+ (void) cb;
+ #else
+ cb = purple_chat_conversation_find_user(chatconv, &user[2]);
+ purple_chat_user_set_flags(cb, cbflags);
+ #endif
+ g_free(user);
+ g_free(role);
+ }
+
+ purple_xmlnode_free(blob);
+ } else if (g_str_equal(messagetype, "RichText/UriObject") || g_str_equal(messagetype, "RichText/Media_Video")) {
+ from = skypeweb_contact_url_to_name(from);
+ g_return_if_fail(from);
+
+ skypeweb_process_uri_message(messagetype, sa, conv, content, composetimestamp, from);
+ } else {
+ purple_debug_warning("skypeweb", "Unhandled thread message resource messagetype '%s'\n", messagetype);
+ }
+
+ } else {
+ gchar *convbuddyname;
+ // This is a One-to-one/IM message
+
+ convbuddyname = g_strdup(skypeweb_contact_url_to_name(conversationLink));
+ convname = g_strconcat(skypeweb_user_url_prefix(convbuddyname), convbuddyname, NULL);
+
+ from = skypeweb_contact_url_to_name(from);
+ if (from == NULL) {
+ g_free(convbuddyname);
+ g_free(convname);
+ g_return_if_reached();
+ return;
+ }
+
+ if (g_str_equal(messagetype_parts[0], "Control")) {
+ if (g_str_equal(messagetype_parts[1], "ClearTyping")) {
+ purple_serv_got_typing(sa->pc, from, 7, PURPLE_IM_NOT_TYPING);
+ } else if (g_str_equal(messagetype_parts[1], "Typing")) {
+ purple_serv_got_typing(sa->pc, from, 7, PURPLE_IM_TYPING);
+ }
+ } else if ((g_str_equal(messagetype, "RichText") || g_str_equal(messagetype, "Text")) && content && *content) {
+ gchar *html;
+ gint64 skypeemoteoffset = 0;
+ PurpleIMConversation *imconv;
+
+ if (json_object_has_member(resource, "skypeemoteoffset")) {
+ skypeemoteoffset = g_ascii_strtoll(json_object_get_string_member(resource, "skypeemoteoffset"), NULL, 10);
+ }
+
+ if (g_str_equal(messagetype, "Text")) {
+ gchar *temp = skypeweb_meify(content, skypeemoteoffset);
+ html = purple_markup_escape_text(temp, -1);
+ g_free(temp);
+ } else {
+ html = skypeweb_meify(content, skypeemoteoffset);
+ }
+
+ if (skypeeditedid && *skypeeditedid) {
+ gchar *temp = g_strconcat(_("Edited: "), html, NULL);
+ g_free(html);
+ html = temp;
+ }
+
+ if (json_object_has_member(resource, "imdisplayname")) {
+ //TODO use this for an alias
+ }
+
+ if (skypeweb_is_user_self(sa, from)) {
+ if (!g_str_has_prefix(html, "?OTR")) {
+ PurpleMessage *msg;
+ imconv = purple_conversations_find_im_with_account(convbuddyname, sa->account);
+ if (imconv == NULL)
+ {
+ imconv = purple_im_conversation_new(sa->account, convbuddyname);
+ }
+ conv = PURPLE_CONVERSATION(imconv);
+
+ msg = purple_message_new_outgoing(convbuddyname, html, PURPLE_MESSAGE_SEND);
+ purple_message_set_time(msg, composetimestamp);
+ purple_conversation_write_message(conv, msg);
+ purple_message_destroy(msg);
+ }
+ } else {
+ purple_serv_got_im(sa->pc, from, html, PURPLE_MESSAGE_RECV, composetimestamp);
+
+ imconv = purple_conversations_find_im_with_account(from, sa->account);
+ conv = PURPLE_CONVERSATION(imconv);
+ }
+ g_free(html);
+ } else if (g_str_equal(messagetype, "RichText/UriObject") || g_str_equal(messagetype, "RichText/Media_Video")) {
+ PurpleIMConversation *imconv;
+
+ if (skypeweb_is_user_self(sa, from)) {
+ from = convbuddyname;
+ }
+ if (from != NULL) {
+ imconv = purple_conversations_find_im_with_account(from, sa->account);
+ if (imconv == NULL)
+ {
+ imconv = purple_im_conversation_new(sa->account, from);
+ }
+
+ conv = PURPLE_CONVERSATION(imconv);
+ skypeweb_process_uri_message(messagetype, sa, conv, content, composetimestamp, from);
+ }
+ } else if (g_str_equal(messagetype, "RichText/Media_GenericFile")) {
+ PurpleXmlNode *blob = purple_xmlnode_from_str(content, -1);
+ const gchar *uri = purple_xmlnode_get_attrib(blob, "uri");
+
+ if (!skypeweb_is_user_self(sa, from)) {
+
+ skypeweb_present_uri_as_filetransfer(sa, uri, from);
+
+ from = convbuddyname;
+ }
+ purple_xmlnode_free(blob);
+ } else if (g_str_equal(messagetype, "Event/SkypeVideoMessage")) {
+ PurpleXmlNode *blob = purple_xmlnode_from_str(content, -1);
+ const gchar *sid = purple_xmlnode_get_attrib(blob, "sid");
+ PurpleIMConversation *imconv;
+
+ if (skypeweb_is_user_self(sa, from)) {
+ from = convbuddyname;
+ }
+ if (from != NULL) {
+ imconv = purple_conversations_find_im_with_account(from, sa->account);
+ if (imconv == NULL)
+ {
+ imconv = purple_im_conversation_new(sa->account, from);
+ }
+
+ conv = PURPLE_CONVERSATION(imconv);
+ //skypeweb_download_video_message(sa, sid, conv); //TODO
+ (void) sid;
+ purple_serv_got_im(sa->pc, from, content, PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_SYSTEM, composetimestamp);
+ }
+ purple_xmlnode_free(blob);
+ } else if (g_str_equal(messagetype, "Event/Call")) {
+ PurpleXmlNode *partlist = purple_xmlnode_from_str(content, -1);
+ const gchar *partlisttype = purple_xmlnode_get_attrib(partlist, "type");
+ const gchar *message = NULL;
+ PurpleIMConversation *imconv;
+ gboolean incoming = TRUE;
+
+ if (skypeweb_is_user_self(sa, from)) {
+ incoming = FALSE;
+ (void) incoming;
+ from = convbuddyname;
+ }
+
+ if (from != NULL) {
+ if (partlisttype) {
+ imconv = purple_conversations_find_im_with_account(from, sa->account);
+ if (imconv == NULL)
+ {
+ imconv = purple_im_conversation_new(sa->account, from);
+ }
+
+ conv = PURPLE_CONVERSATION(imconv);
+ if (g_str_equal(partlisttype, "started")) {
+ message = _("Call started");
+ } else if (g_str_equal(partlisttype, "ended")) {
+ PurpleXmlNode *part;
+ gint duration_int = -1;
+
+ for(part = purple_xmlnode_get_child(partlist, "part");
+ part;
+ part = purple_xmlnode_get_next_twin(part))
+ {
+ const gchar *identity = purple_xmlnode_get_attrib(part, "identity");
+ if (identity && skypeweb_is_user_self(sa, identity)) {
+ PurpleXmlNode *duration = purple_xmlnode_get_child(part, "duration");
+ if (duration != NULL) {
+ gchar *duration_str;
+ duration_str = purple_xmlnode_get_data(duration);
+ duration_int = atoi(duration_str);
+ break;
+ }
+ }
+ }
+ if (duration_int < 0) {
+ message = _("Call missed");
+ } else {
+ //TODO report how long the call was
+ message = _("Call ended");
+ }
+ }
+ }
+ else {
+ message = _("Unsupported call received");
+ }
+
+ purple_serv_got_im(sa->pc, from, message, PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_SYSTEM, composetimestamp);
+ }
+
+ purple_xmlnode_free(partlist);
+ } else if (g_str_equal(messagetype, "Signal/Flamingo")) {
+ const gchar *message = NULL;
+
+ if (skypeweb_is_user_self(sa, from)) {
+ from = convbuddyname;
+ }
+
+ if (from != NULL) {
+ message = _("Unsupported call received");
+
+ purple_serv_got_im(sa->pc, from, message, PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_SYSTEM, composetimestamp);
+ }
+ } else if (g_str_equal(messagetype, "RichText/Contacts")) {
+ PurpleXmlNode *contacts = purple_xmlnode_from_str(content, -1);
+ PurpleXmlNode *contact;
+
+ for(contact = purple_xmlnode_get_child(contacts, "c"); contact;
+ contact = purple_xmlnode_get_next_twin(contact))
+ {
+ const gchar *contact_id = purple_xmlnode_get_attrib(contact, "s");
+ const gchar *contact_name = purple_xmlnode_get_attrib(contact, "f");
+
+ gchar *message = g_strdup_printf(_("The user sent a contact: %s (%s)"), contact_id, contact_name);
+
+ purple_serv_got_im(sa->pc, from, message, PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_SYSTEM, composetimestamp);
+
+ g_free(message);
+ }
+
+ skypeweb_received_contacts(sa, contacts);
+ purple_xmlnode_free(contacts);
+ } else if (g_str_equal(messagetype, "RichText/Media_FlikMsg")) {
+
+
+ PurpleXmlNode *blob = purple_xmlnode_from_str(content, -1);
+
+ const gchar *url_thumbnail = purple_xmlnode_get_attrib(blob, "url_thumbnail");
+ gchar *text = purple_markup_strip_html(content); //purple_xmlnode_get_data_unescaped doesn't work properly in this situation
+
+ PurpleIMConversation *imconv;
+
+ if (skypeweb_is_user_self(sa, from)) {
+ from = convbuddyname;
+ }
+ if (from != NULL) {
+ imconv = purple_conversations_find_im_with_account(from, sa->account);
+ if (imconv == NULL) {
+ imconv = purple_im_conversation_new(sa->account, from);
+ }
+
+ conv = PURPLE_CONVERSATION(imconv);
+
+ skypeweb_download_moji_to_conv(sa, text, url_thumbnail, conv, composetimestamp, from);
+
+ const gchar *message = _("The user sent a Moji");
+
+ purple_serv_got_im(sa->pc, from, message, PURPLE_MESSAGE_NO_LOG, composetimestamp);
+
+ g_free(text);
+ }
+
+ purple_xmlnode_free(blob);
+ } else if (g_str_equal(messagetype, "RichText/Files")) {
+ purple_serv_got_im(sa->pc, convbuddyname, _("The user sent files in an unsupported way"), PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_ERROR, composetimestamp);
+ } else {
+ purple_debug_warning("skypeweb", "Unhandled message resource messagetype '%s'\n", messagetype);
+ }
+
+ g_free(convbuddyname);
+ }
+
+ if (conv != NULL) {
+ const gchar *id = json_object_get_string_member(resource, "id");
+
+ g_free(purple_conversation_get_data(conv, "last_skypeweb_id"));
+
+ if (purple_conversation_has_focus(conv)) {
+ // Mark message as seen straight away
+ gchar *post, *url;
+
+ url = g_strdup_printf("/v1/users/ME/conversations/%s/properties?name=consumptionhorizon", purple_url_encode(convname));
+ post = g_strdup_printf("{\"consumptionhorizon\":\"%s;%" G_GINT64_FORMAT ";%s\"}", id ? id : "", skypeweb_get_js_time(), id ? id : "");
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, url, post, NULL, NULL, TRUE);
+
+ g_free(post);
+ g_free(url);
+
+ purple_conversation_set_data(conv, "last_skypeweb_id", NULL);
+ } else {
+ purple_conversation_set_data(conv, "last_skypeweb_id", g_strdup(id));
+ }
+ }
+
+ g_free(convname);
+ g_strfreev(messagetype_parts);
+
+ if (composetimestamp > purple_account_get_int(sa->account, "last_message_timestamp", 0))
+ purple_account_set_int(sa->account, "last_message_timestamp", composetimestamp);
+}
+
+static void
+process_conversation_resource(SkypeWebAccount *sa, JsonObject *resource)
+{
+ const gchar *id = json_object_get_string_member(resource, "id");
+ (void) id;
+
+ JsonObject *threadProperties;
+
+ if (json_object_has_member(resource, "threadProperties")) {
+ threadProperties = json_object_get_object_member(resource, "threadProperties");
+ }
+ (void) threadProperties;
+}
+
+static void
+process_thread_resource(SkypeWebAccount *sa, JsonObject *resource)
+{
+
+}
+
+static void
+process_endpointpresence_resource(SkypeWebAccount *sa, JsonObject *resource)
+{
+ JsonObject *publicInfo = json_object_get_object_member(resource, "publicInfo");
+ if (publicInfo != NULL) {
+ const gchar *typ_str = json_object_get_string_member(publicInfo, "typ");
+ const gchar *skypeNameVersion = json_object_get_string_member(publicInfo, "skypeNameVersion");
+
+ if (typ_str && *typ_str) {
+ if (g_str_equal(typ_str, "website")) {
+
+ } else {
+ gint typ = atoi(typ_str);
+ switch(typ) {
+ case 17: //Android
+ break;
+ case 16: //iOS
+ break;
+ case 12: //WinRT/Metro
+ break;
+ case 15: //Winphone
+ break;
+ case 13: //OSX
+ break;
+ case 11: //Windows
+ break;
+ case 14: //Linux
+ break;
+ case 10: //XBox ? skypeNameVersion 11/1.8.0.1006
+ break;
+ case 1: //SkypeWeb
+ break;
+ default:
+ purple_debug_warning("skypeweb", "Unknown typ %d: %s\n", typ, skypeNameVersion ? skypeNameVersion : "");
+ break;
+ }
+ }
+ }
+ }
+}
+
+gboolean
+skypeweb_timeout(gpointer userdata)
+{
+ SkypeWebAccount *sa = userdata;
+ skypeweb_poll(sa);
+
+ // If no response within 3 minutes, assume connection lost and try again
+ g_source_remove(sa->watchdog_timeout);
+ sa->watchdog_timeout = g_timeout_add_seconds(3 * 60, skypeweb_timeout, sa);
+
+ return FALSE;
+}
+
+static void
+skypeweb_poll_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ JsonArray *messages = NULL;
+ gint index, length;
+ JsonObject *obj = NULL;
+
+
+ if (((int)time(NULL)) > sa->vdms_expiry) {
+ skypeweb_get_vdms_token(sa);
+ }
+
+ if (node == NULL && ((int)time(NULL)) > sa->registration_expiry) {
+ skypeweb_get_registration_token(sa);
+ return;
+ }
+
+
+ if (node != NULL && json_node_get_node_type(node) == JSON_NODE_OBJECT)
+ obj = json_node_get_object(node);
+
+ if (obj != NULL) {
+ if (json_object_has_member(obj, "eventMessages"))
+ messages = json_object_get_array_member(obj, "eventMessages");
+
+ if (messages != NULL) {
+ length = json_array_get_length(messages);
+ for(index = length - 1; index >= 0; index--)
+ {
+ JsonObject *message = json_array_get_object_element(messages, index);
+ const gchar *resourceType = json_object_get_string_member(message, "resourceType");
+ JsonObject *resource = json_object_get_object_member(message, "resource");
+
+ if (purple_strequal(resourceType, "NewMessage"))
+ {
+ process_message_resource(sa, resource);
+ } else if (purple_strequal(resourceType, "UserPresence"))
+ {
+ process_userpresence_resource(sa, resource);
+ } else if (purple_strequal(resourceType, "EndpointPresence"))
+ {
+ process_endpointpresence_resource(sa, resource);
+ } else if (purple_strequal(resourceType, "ConversationUpdate"))
+ {
+ process_conversation_resource(sa, resource);
+ } else if (purple_strequal(resourceType, "ThreadUpdate"))
+ {
+ process_thread_resource(sa, resource);
+ }
+ }
+ } else if (json_object_has_member(obj, "errorCode")) {
+ gint64 errorCode = json_object_get_int_member(obj, "errorCode");
+
+ if (errorCode == 729) {
+ // "You must create an endpoint before performing this operation"
+ // Dammit, Jim; I'm a programmer, not a surgeon!
+ skypeweb_get_registration_token(sa);
+ return;
+ } else if (errorCode == 450) {
+ // "Subscription requested could not be found."
+ // No more Womens Weekly? :O
+ }
+ }
+
+ //TODO record id of highest recieved id to make sure we dont process the same id twice
+ }
+
+ if (!purple_connection_is_disconnecting(sa->pc)) {
+ sa->poll_timeout = g_timeout_add_seconds(1, skypeweb_timeout, sa);
+ }
+}
+
+void
+skypeweb_poll(SkypeWebAccount *sa)
+{
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, "/v1/users/ME/endpoints/SELF/subscriptions/0/poll", NULL, skypeweb_poll_cb, NULL, TRUE);
+}
+
+void
+skypeweb_mark_conv_seen(PurpleConversation *conv, PurpleConversationUpdateType type)
+{
+ PurpleConnection *pc = purple_conversation_get_connection(conv);
+ if (!PURPLE_CONNECTION_IS_CONNECTED(pc))
+ return;
+
+ if (!purple_strequal(purple_protocol_get_id(purple_connection_get_protocol(pc)), SKYPEWEB_PLUGIN_ID))
+ return;
+
+ if (type == PURPLE_CONVERSATION_UPDATE_UNSEEN) {
+ gchar *last_skypeweb_id = purple_conversation_get_data(conv, "last_skypeweb_id");
+
+ if (last_skypeweb_id && *last_skypeweb_id) {
+ SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
+ gchar *post, *url, *convname;
+
+ if (PURPLE_IS_IM_CONVERSATION(conv)) {
+ const gchar *buddyname = purple_conversation_get_name(conv);
+ convname = g_strconcat(skypeweb_user_url_prefix(buddyname), buddyname, NULL);
+ } else {
+ convname = g_strdup(purple_conversation_get_data(conv, "chatname"));
+ }
+
+ url = g_strdup_printf("/v1/users/ME/conversations/%s/properties?name=consumptionhorizon", purple_url_encode(convname));
+ post = g_strdup_printf("{\"consumptionhorizon\":\"%s;%" G_GINT64_FORMAT ";%s\"}", last_skypeweb_id, skypeweb_get_js_time(), last_skypeweb_id);
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, url, post, NULL, NULL, TRUE);
+
+ g_free(convname);
+ g_free(post);
+ g_free(url);
+
+ g_free(last_skypeweb_id);
+ purple_conversation_set_data(conv, "last_skypeweb_id", NULL);
+ }
+ }
+}
+
+static void
+skypeweb_got_thread_users(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ PurpleChatConversation *chatconv;
+ gchar *chatname = user_data;
+ JsonObject *response;
+ JsonArray *members;
+ gint length, index;
+
+ chatconv = purple_conversations_find_chat_with_account(chatname, sa->account);
+ if (chatconv == NULL)
+ return;
+ purple_chat_conversation_clear_users(chatconv);
+
+ if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
+ return;
+ response = json_node_get_object(node);
+
+ members = json_object_get_array_member(response, "members");
+ length = json_array_get_length(members);
+ for(index = length - 1; index >= 0; index--)
+ {
+ JsonObject *member = json_array_get_object_element(members, index);
+ const gchar *userLink = json_object_get_string_member(member, "userLink");
+ const gchar *role = json_object_get_string_member(member, "role");
+ const gchar *username = skypeweb_contact_url_to_name(userLink);
+ PurpleChatUserFlags cbflags = PURPLE_CHAT_USER_NONE;
+
+ if (role && *role) {
+ if (g_str_equal(role, "Admin") || g_str_equal(role, "admin")) {
+ cbflags = PURPLE_CHAT_USER_OP;
+ } else if (g_str_equal(role, "User") || g_str_equal(role, "user")) {
+ //cbflags = PURPLE_CHAT_USER_VOICE;
+ }
+ }
+
+ if (username == NULL && json_object_has_member(member, "linkedMri")) {
+ username = skypeweb_contact_url_to_name(json_object_get_string_member(member, "linkedMri"));
+ }
+ if (username != NULL) {
+ purple_chat_conversation_add_user(chatconv, username, NULL, cbflags, FALSE);
+ }
+ }
+}
+
+void
+skypeweb_get_thread_users(SkypeWebAccount *sa, const gchar *convname)
+{
+ gchar *url;
+ url = g_strdup_printf("/v1/threads/%s?view=msnp24Equivalent", purple_url_encode(convname));
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, sa->messages_host, url, NULL, skypeweb_got_thread_users, g_strdup(convname), TRUE);
+
+ g_free(url);
+}
+
+static void
+skypeweb_got_conv_history(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ gint since = GPOINTER_TO_INT(user_data);
+ JsonObject *obj;
+ JsonArray *messages;
+ gint index, length;
+
+ if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
+ return;
+ obj = json_node_get_object(node);
+
+ messages = json_object_get_array_member(obj, "messages");
+ length = json_array_get_length(messages);
+ for(index = length - 1; index >= 0; index--)
+ {
+ JsonObject *message = json_array_get_object_element(messages, index);
+ const gchar *composetime = json_object_get_string_member(message, "composetime");
+ gint composetimestamp = (gint) purple_str_to_time(composetime, TRUE, NULL, NULL, NULL);
+
+ if (composetimestamp > since) {
+ process_message_resource(sa, message);
+ }
+ }
+}
+
+void
+skypeweb_get_conversation_history_since(SkypeWebAccount *sa, const gchar *convname, gint since)
+{
+ gchar *url;
+ url = g_strdup_printf("/v1/users/ME/conversations/%s/messages?startTime=%d000&pageSize=30&view=msnp24Equivalent&targetType=Passport|Skype|Lync|Thread|PSTN|Agent", purple_url_encode(convname), since);
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, sa->messages_host, url, NULL, skypeweb_got_conv_history, GINT_TO_POINTER(since), TRUE);
+
+ g_free(url);
+}
+
+void
+skypeweb_get_conversation_history(SkypeWebAccount *sa, const gchar *convname)
+{
+ GString *timestamp_key = make_last_timestamp_setting(convname);
+ skypeweb_get_conversation_history_since(sa, convname, purple_account_get_int(sa->account, timestamp_key->str, 0));
+ g_string_free(timestamp_key, TRUE);
+}
+
+static void
+skypeweb_got_all_convs(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ gint since = GPOINTER_TO_INT(user_data);
+ JsonObject *obj;
+ JsonArray *conversations;
+ gint index, length;
+
+ if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
+ return;
+ obj = json_node_get_object(node);
+
+ conversations = json_object_get_array_member(obj, "conversations");
+ length = json_array_get_length(conversations);
+ for(index = 0; index < length; index++) {
+ JsonObject *conversation = json_array_get_object_element(conversations, index);
+ const gchar *id = json_object_get_string_member(conversation, "id");
+ JsonObject *lastMessage = json_object_get_object_member(conversation, "lastMessage");
+ if (lastMessage != NULL && json_object_has_member(lastMessage, "composetime")) {
+ const gchar *composetime = json_object_get_string_member(lastMessage, "composetime");
+ gint composetimestamp = (gint) purple_str_to_time(composetime, TRUE, NULL, NULL, NULL);
+
+ if (composetimestamp > since) {
+ skypeweb_get_conversation_history_since(sa, id, since);
+ }
+ }
+ }
+}
+
+void
+skypeweb_get_all_conversations_since(SkypeWebAccount *sa, gint since)
+{
+ gchar *url;
+ url = g_strdup_printf("/v1/users/ME/conversations?startTime=%d000&pageSize=100&view=msnp24Equivalent&targetType=Passport|Skype|Lync|Thread|PSTN|Agent", since);
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, sa->messages_host, url, NULL, skypeweb_got_all_convs, GINT_TO_POINTER(since), TRUE);
+
+ g_free(url);
+}
+
+void
+skype_web_get_offline_history(SkypeWebAccount *sa)
+{
+ skypeweb_get_all_conversations_since(sa, purple_account_get_int(sa->account, "last_message_timestamp", ((gint) time(NULL))));
+}
+
+
+static void
+skypeweb_got_roomlist_threads(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ PurpleRoomlist *roomlist = user_data;
+ JsonObject *obj;
+ JsonArray *conversations;
+ gint index, length;
+
+ if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
+ return;
+ obj = json_node_get_object(node);
+
+ conversations = json_object_get_array_member(obj, "conversations");
+ length = json_array_get_length(conversations);
+ for(index = 0; index < length; index++) {
+ JsonObject *conversation = json_array_get_object_element(conversations, index);
+ const gchar *id = json_object_get_string_member(conversation, "id");
+ PurpleRoomlistRoom *room = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM, id, NULL);
+
+ purple_roomlist_room_add_field(roomlist, room, id);
+ if (json_object_has_member(conversation, "threadProperties")) {
+ JsonObject *threadProperties = json_object_get_object_member(conversation, "threadProperties");
+ if (threadProperties != NULL) {
+ const gchar *num_members = json_object_get_string_member(threadProperties, "membercount");
+ purple_roomlist_room_add_field(roomlist, room, num_members);
+ const gchar *topic = json_object_get_string_member(threadProperties, "topic");
+ purple_roomlist_room_add_field(roomlist, room, topic);
+ }
+ }
+ purple_roomlist_room_add(roomlist, room);
+ }
+
+ purple_roomlist_set_in_progress(roomlist, FALSE);
+}
+
+PurpleRoomlist *
+skypeweb_roomlist_get_list(PurpleConnection *pc)
+{
+ SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
+ const gchar *url = "/v1/users/ME/conversations?startTime=0&pageSize=100&view=msnp24Equivalent&targetType=Thread";
+ PurpleRoomlist *roomlist;
+ GList *fields = NULL;
+ PurpleRoomlistField *f;
+
+ roomlist = purple_roomlist_new(sa->account);
+
+ f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("ID"), "chatname", TRUE);
+ fields = g_list_append(fields, f);
+
+ f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Users"), "users", FALSE);
+ fields = g_list_append(fields, f);
+
+ f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, _("Topic"), "topic", FALSE);
+ fields = g_list_append(fields, f);
+
+ purple_roomlist_set_fields(roomlist, fields);
+ purple_roomlist_set_in_progress(roomlist, TRUE);
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, sa->messages_host, url, NULL, skypeweb_got_roomlist_threads, roomlist, FALSE);
+
+ return roomlist;
+}
+
+void
+skypeweb_unsubscribe_from_contact_status(SkypeWebAccount *sa, const gchar *who)
+{
+ const gchar *contacts_url = "/v1/users/ME/contacts";
+ gchar *url;
+
+ url = g_strconcat(contacts_url, "/", skypeweb_user_url_prefix(who), purple_url_encode(who), NULL);
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_DELETE | SKYPEWEB_METHOD_SSL, sa->messages_host, url, NULL, NULL, NULL, TRUE);
+
+ g_free(url);
+}
+
+static void
+skypeweb_got_contact_status(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ JsonObject *obj = json_node_get_object(node);
+ JsonArray *responses = json_object_get_array_member(obj, "Responses");
+
+ if (responses != NULL) {
+ guint length = json_array_get_length(responses);
+ gint index;
+ for(index = length - 1; index >= 0; index--)
+ {
+ JsonObject *response = json_array_get_object_element(responses, index);
+ JsonObject *payload = json_object_get_object_member(response, "Payload");
+ process_userpresence_resource(sa, payload);
+ }
+ }
+}
+
+static void
+skypeweb_lookup_contact_status(SkypeWebAccount *sa, const gchar *contact)
+{
+ if (contact == NULL) {
+ return;
+ }
+
+ // Allowed to be up to 10 at once
+ gchar *url = g_strdup_printf("/v1/users/ME/contacts/ALL/presenceDocs/messagingService?cMri=%s%s", skypeweb_user_url_prefix(contact), purple_url_encode(contact));
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, sa->messages_host, url, NULL, skypeweb_got_contact_status, NULL, TRUE);
+
+ g_free(url);
+}
+
+void
+skypeweb_subscribe_to_contact_status(SkypeWebAccount *sa, GSList *contacts)
+{
+ const gchar *contacts_url = "/v1/users/ME/contacts";
+ gchar *post;
+ GSList *cur = contacts;
+ JsonObject *obj;
+ JsonArray *contacts_array;
+ guint count = 0;
+
+ if (contacts == NULL)
+ return;
+
+ obj = json_object_new();
+ contacts_array = json_array_new();
+
+ JsonArray *interested = json_array_new();
+ json_array_add_string_element(interested, "/v1/users/ME/conversations/ALL/properties");
+ json_array_add_string_element(interested, "/v1/users/ME/conversations/ALL/messages");
+ json_array_add_string_element(interested, "/v1/users/ME/contacts/ALL");
+ json_array_add_string_element(interested, "/v1/threads/ALL");
+
+ do {
+ JsonObject *contact;
+ gchar *id;
+
+ if (SKYPEWEB_BUDDY_IS_BOT(cur->data)) {
+ purple_protocol_got_user_status(sa->account, cur->data, SKYPEWEB_STATUS_ONLINE, NULL);
+ continue;
+ }
+
+ contact = json_object_new();
+
+ id = g_strconcat(skypeweb_user_url_prefix(cur->data), cur->data, NULL);
+ json_object_set_string_member(contact, "id", id);
+ json_array_add_object_element(contacts_array, contact);
+
+ if (id && id[0] == '8') {
+ gchar *contact_url = g_strconcat("/v1/users/ME/contacts/", id, NULL);
+ json_array_add_string_element(interested, contact_url);
+ g_free(contact_url);
+ }
+
+ g_free(id);
+
+ if (count++ >= 100) {
+ // Send off the current batch and continue
+ json_object_set_array_member(obj, "contacts", contacts_array);
+ post = skypeweb_jsonobj_to_string(obj);
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, contacts_url, post, NULL, NULL, TRUE);
+
+ g_free(post);
+ json_object_unref(obj);
+
+ obj = json_object_new();
+ contacts_array = json_array_new();
+ count = 0;
+ }
+ } while((cur = g_slist_next(cur)));
+
+ json_object_set_array_member(obj, "contacts", contacts_array);
+ post = skypeweb_jsonobj_to_string(obj);
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, contacts_url, post, NULL, NULL, TRUE);
+
+ g_free(post);
+ json_object_unref(obj);
+
+
+ gchar *url = g_strdup_printf("/v1/users/ME/endpoints/%s/subscriptions/0?name=interestedResources", purple_url_encode(sa->endpoint));
+
+ obj = json_object_new();
+ json_object_set_array_member(obj, "interestedResources", interested);
+
+ skypeweb_lookup_contact_status(sa, NULL);
+ post = skypeweb_jsonobj_to_string(obj);
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, url, post, NULL, NULL, TRUE);
+
+ g_free(url);
+ g_free(post);
+ json_object_unref(obj);
+}
+
+
+static void
+skypeweb_subscribe_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ skypeweb_do_all_the_things(sa);
+}
+
+static void
+skypeweb_subscribe(SkypeWebAccount *sa)
+{
+ JsonObject *obj;
+ JsonArray *interested;
+ gchar *post;
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, "/v1/users/ME/endpoints/SELF/properties?name=supportsMessageProperties", "{\"supportsMessageProperties\":true}", NULL, NULL, TRUE);
+
+ interested = json_array_new();
+ json_array_add_string_element(interested, "/v1/users/ME/conversations/ALL/properties");
+ json_array_add_string_element(interested, "/v1/users/ME/conversations/ALL/messages");
+ json_array_add_string_element(interested, "/v1/users/ME/contacts/ALL");
+ json_array_add_string_element(interested, "/v1/threads/ALL");
+
+ obj = json_object_new();
+ json_object_set_array_member(obj, "interestedResources", interested);
+ json_object_set_string_member(obj, "template", "raw");
+ json_object_set_string_member(obj, "channelType", "httpLongPoll");
+
+ post = skypeweb_jsonobj_to_string(obj);
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, "/v1/users/ME/endpoints/SELF/subscriptions", post, skypeweb_subscribe_cb, NULL, TRUE);
+
+ g_free(post);
+ json_object_unref(obj);
+}
+
+static void
+skypeweb_got_registration_token(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
+{
+ const gchar *registration_token = NULL;
+ gchar *endpointId = NULL;
+ gchar *expires = NULL;
+ SkypeWebAccount *sa = user_data;
+ gchar *new_messages_host = NULL;
+ const gchar *data;
+ gsize len;
+
+ data = purple_http_response_get_data(response, &len);
+
+ if (data == NULL) {
+ if (purple_major_version == 2 && (
+ purple_minor_version < 10 ||
+ (purple_minor_version == 10 && purple_micro_version < 11))
+ ) {
+ purple_connection_error (sa->pc,
+ PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
+ _("Your version of libpurple is too old.\nUpgrade to 2.10.11 or newer and try again."));
+ return;
+ }
+ }
+
+ new_messages_host = skypeweb_string_get_chunk(purple_http_response_get_header(response, "Location"), -1, "https://", "/");
+ if (new_messages_host != NULL && !g_str_equal(sa->messages_host, new_messages_host)) {
+ g_free(sa->messages_host);
+ sa->messages_host = new_messages_host;
+
+ // Your princess is in another castle
+ purple_debug_info("skypeweb", "Messages host has changed to %s\n", sa->messages_host);
+
+ skypeweb_get_registration_token(sa);
+ return;
+ }
+ g_free(new_messages_host);
+
+ registration_token = purple_http_response_get_header(response, "Set-RegistrationToken");
+
+ if (registration_token == NULL) {
+ if (purple_account_get_string(sa->account, "refresh-token", NULL)) {
+ skypeweb_refresh_token_login(sa);
+ } else {
+ purple_connection_error (sa->pc,
+ PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+ _("Failed getting Registration Token"));
+ }
+ return;
+ }
+ //purple_debug_info("skypeweb", "New RegistrationToken is %s\n", registration_token);
+ endpointId = skypeweb_string_get_chunk(registration_token, -1, "endpointId=", NULL);
+ expires = skypeweb_string_get_chunk(registration_token, -1, "expires=", ";");
+
+ g_free(sa->registration_token); sa->registration_token = g_strndup(registration_token, strchr(registration_token, ';') - registration_token);
+ g_free(sa->endpoint); sa->endpoint = endpointId;
+ if (expires && *expires) {
+ sa->registration_expiry = atoi(expires);
+ g_free(expires);
+ }
+
+ if (sa->endpoint) {
+ gchar *url = g_strdup_printf("/v1/users/ME/endpoints/%s/presenceDocs/messagingService", purple_url_encode(sa->endpoint));
+ const gchar *post = "{\"id\":\"messagingService\", \"type\":\"EndpointPresenceDoc\", \"selfLink\":\"uri\", \"privateInfo\":{\"epname\":\"skype\"}, \"publicInfo\":{\"capabilities\":\"\", \"type\":1, \"typ\":1, \"skypeNameVersion\":\"" SKYPEWEB_CLIENTINFO_VERSION "/" SKYPEWEB_CLIENTINFO_NAME "\", \"nodeInfo\":\"\", \"version\":\"" SKYPEWEB_CLIENTINFO_VERSION "\"}}";
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, url, post, NULL, NULL, TRUE);
+ g_free(url);
+ }
+
+ skypeweb_gather_self_properties(sa);
+ skypeweb_subscribe(sa);
+}
+
+static void
+skypeweb_got_vdms_token(PurpleHttpConnection *http_conn, PurpleHttpResponse *response, gpointer user_data)
+{
+ const gchar *token;
+ SkypeWebAccount *sa = user_data;
+ JsonParser *parser = json_parser_new();
+ const gchar *data;
+ gsize len;
+
+ data = purple_http_response_get_data(response, &len);
+
+ if (json_parser_load_from_data(parser, data, len, NULL)) {
+ JsonNode *root = json_parser_get_root(parser);
+ JsonObject *obj = json_node_get_object(root);
+
+ token = json_object_get_string_member(obj, "token");
+ g_free(sa->vdms_token);
+ sa->vdms_token = g_strdup(token);
+ sa->vdms_expiry = (int)time(NULL) + SKYPEWEB_VDMS_TTL;
+ }
+
+ g_object_unref(parser);
+
+}
+
+void
+skypeweb_get_registration_token(SkypeWebAccount *sa)
+{
+ gchar *messages_url;
+ PurpleHttpRequest *request;
+ gchar *curtime;
+ gchar *response;
+
+ g_free(sa->registration_token); sa->registration_token = NULL;
+ g_free(sa->endpoint); sa->endpoint = NULL;
+
+ curtime = g_strdup_printf("%d", (int) time(NULL));
+ response = skypeweb_hmac_sha256(curtime);
+
+ messages_url = g_strdup_printf("https://%s/v1/users/ME/endpoints", sa->messages_host);
+
+ request = purple_http_request_new(messages_url);
+ purple_http_request_set_method(request, "POST");
+ purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
+ purple_http_request_set_max_redirects(request, 0);
+ purple_http_request_header_set(request, "Accept", "*/*");
+ purple_http_request_header_set(request, "BehaviorOverride", "redirectAs404");
+ purple_http_request_header_set_printf(request, "LockAndKey", "appId=" SKYPEWEB_LOCKANDKEY_APPID "; time=%s; lockAndKeyResponse=%s", curtime, response);
+ purple_http_request_header_set(request, "ClientInfo", "os=Windows; osVer=8.1; proc=Win32; lcid=en-us; deviceType=1; country=n/a; clientName=" SKYPEWEB_CLIENTINFO_NAME "; clientVer=" SKYPEWEB_CLIENTINFO_VERSION);
+ purple_http_request_header_set(request, "Content-Type", "application/json");
+ purple_http_request_header_set_printf(request, "Authentication", "skypetoken=%s", sa->skype_token);
+ purple_http_request_set_contents(request, "{\"endpointFeatures\":\"Agent\"}", -1);
+ purple_http_request(sa->pc, request, skypeweb_got_registration_token, sa);
+ purple_http_request_unref(request);
+
+ g_free(curtime);
+ g_free(response);
+ g_free(messages_url);
+}
+
+void
+skypeweb_get_vdms_token(SkypeWebAccount *sa)
+{
+ const gchar *messages_url = "https://" SKYPEWEB_STATIC_HOST "/pes/v1/petoken";
+ PurpleHttpRequest *request;
+
+ request = purple_http_request_new(messages_url);
+ purple_http_request_set_keepalive_pool(request, sa->keepalive_pool);
+ purple_http_request_header_set(request, "Accept", "*/*");
+ purple_http_request_header_set(request, "Origin", "https://web.skype.com");
+ purple_http_request_header_set_printf(request, "Authorization", "skype_token %s", sa->skype_token);
+ purple_http_request_header_set(request, "Content-Type", "application/x-www-form-urlencoded");
+ purple_http_request_set_contents(request, "{}", -1);
+ purple_http_request(sa->pc, request, skypeweb_got_vdms_token, sa);
+ purple_http_request_unref(request);
+}
+
+
+guint
+skypeweb_conv_send_typing(PurpleConversation *conv, PurpleIMTypingState state)
+{
+ PurpleConnection *pc = purple_conversation_get_connection(conv);
+ SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
+ gchar *post, *url;
+ JsonObject *obj;
+
+ if (!PURPLE_CONNECTION_IS_CONNECTED(pc))
+ return 0;
+
+ if (!purple_strequal(purple_protocol_get_id(purple_connection_get_protocol(pc)), SKYPEWEB_PLUGIN_ID))
+ return 0;
+
+ url = g_strdup_printf("/v1/users/ME/conversations/%s/messages", purple_url_encode(purple_conversation_get_name(conv)));
+
+ obj = json_object_new();
+ json_object_set_int_member(obj, "clientmessageid", time(NULL));
+ json_object_set_string_member(obj, "content", "");
+ json_object_set_string_member(obj, "messagetype", state == PURPLE_IM_TYPING ? "Control/Typing" : "Control/ClearTyping");
+ json_object_set_string_member(obj, "contenttype", "text");
+
+ post = skypeweb_jsonobj_to_string(obj);
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, url, post, NULL, NULL, TRUE);
+
+ g_free(post);
+ json_object_unref(obj);
+ g_free(url);
+
+ return 5;
+}
+
+guint
+skypeweb_send_typing(PurpleConnection *pc, const gchar *name, PurpleIMTypingState state)
+{
+ SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
+ gchar *post, *url;
+ JsonObject *obj;
+
+ url = g_strdup_printf("/v1/users/ME/conversations/%s%s/messages", skypeweb_user_url_prefix(name), purple_url_encode(name));
+
+ obj = json_object_new();
+ json_object_set_int_member(obj, "clientmessageid", time(NULL));
+ json_object_set_string_member(obj, "content", "");
+ json_object_set_string_member(obj, "messagetype", state == PURPLE_IM_TYPING ? "Control/Typing" : "Control/ClearTyping");
+ json_object_set_string_member(obj, "contenttype", "text");
+
+ post = skypeweb_jsonobj_to_string(obj);
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, url, post, NULL, NULL, TRUE);
+
+ g_free(post);
+ json_object_unref(obj);
+ g_free(url);
+
+ return 5;
+}
+
+
+static void
+skypeweb_set_statusid(SkypeWebAccount *sa, const gchar *status)
+{
+ gchar *post;
+
+ g_return_if_fail(status);
+
+ post = g_strdup_printf("{\"status\":\"%s\"}", status);
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, "/v1/users/ME/presenceDocs/messagingService", post, NULL, NULL, TRUE);
+ g_free(post);
+}
+
+void
+skypeweb_set_status(PurpleAccount *account, PurpleStatus *status)
+{
+ PurpleConnection *pc = purple_account_get_connection(account);
+ SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
+
+ skypeweb_set_statusid(sa, purple_status_get_id(status));
+ skypeweb_set_mood_message(sa, purple_status_get_attr_string(status, "message"));
+}
+
+void
+skypeweb_set_idle(PurpleConnection *pc, int time)
+{
+ const gchar *status_id;
+ PurpleStatus *status;
+
+ SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
+
+ status = purple_account_get_active_status(purple_connection_get_account(pc));
+ status_id = purple_status_get_id(status);
+
+ /* Only go idle if active status is online */
+ if (!strcmp(status_id, SKYPEWEB_STATUS_ONLINE)) {
+ if (time < 30) {
+ skypeweb_set_statusid(sa, SKYPEWEB_STATUS_ONLINE);
+ } else {
+ skypeweb_set_statusid(sa, SKYPEWEB_STATUS_IDLE);
+ }
+ }
+}
+
+
+static void
+skypeweb_sent_message_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ gchar *convname = user_data;
+ JsonObject *obj = NULL;
+
+ if (node != NULL && json_node_get_node_type(node) == JSON_NODE_OBJECT)
+ obj = json_node_get_object(node);
+
+ if (obj != NULL) {
+ if (json_object_has_member(obj, "errorCode")) {
+ PurpleChatConversation *chatconv = purple_conversations_find_chat_with_account(convname, sa->account);
+ if (chatconv == NULL) {
+ purple_conversation_present_error(skypeweb_strip_user_prefix(convname), sa->account, json_object_get_string_member(obj, "message"));
+ } else {
+ PurpleMessage *msg = purple_message_new_system(json_object_get_string_member(obj, "message"), PURPLE_MESSAGE_ERROR);
+ purple_conversation_write_message(PURPLE_CONVERSATION(chatconv), msg);
+ purple_message_destroy(msg);
+ }
+ }
+ }
+
+ g_free(convname);
+}
+
+static void
+skypeweb_send_message(SkypeWebAccount *sa, const gchar *convname, const gchar *message)
+{
+ gchar *post, *url;
+ JsonObject *obj;
+ gint64 clientmessageid;
+ gchar *clientmessageid_str;
+ gchar *stripped;
+ static GRegex *font_strip_regex = NULL;
+ gchar *font_stripped;
+ char *xhtml;
+
+ url = g_strdup_printf("/v1/users/ME/conversations/%s/messages", purple_url_encode(convname));
+
+ clientmessageid = skypeweb_get_js_time();
+ clientmessageid_str = g_strdup_printf("%" G_GINT64_FORMAT "", clientmessageid);
+
+ purple_markup_html_to_xhtml(message, &xhtml, NULL);
+ // Some clients don't receive messages with <br>'s in them
+ stripped = purple_strreplace(xhtml, "<br>", "\r\n");
+ g_free(xhtml);
+
+ // Pidgin has a nasty habit of sending <font size="3"> when copy-pasting text
+ if (font_strip_regex == NULL) {
+ font_strip_regex = g_regex_new("(<font [^>]*)size=\"[0-9]+\"([^>]*>)", G_REGEX_CASELESS | G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ }
+ font_stripped = g_regex_replace(font_strip_regex, stripped, -1, 0, "\\1\\2", 0, NULL);
+ if (font_stripped != NULL) {
+ g_free(stripped);
+ stripped = font_stripped;
+ }
+
+ obj = json_object_new();
+ json_object_set_string_member(obj, "clientmessageid", clientmessageid_str);
+ json_object_set_string_member(obj, "content", stripped);
+ if (G_UNLIKELY(g_str_has_prefix(message, "<URIObject "))) {
+ json_object_set_string_member(obj, "messagetype", "RichText/Media_GenericFile"); //hax!
+ } else {
+ json_object_set_string_member(obj, "messagetype", "RichText");
+ }
+ json_object_set_string_member(obj, "contenttype", "text");
+ json_object_set_string_member(obj, "imdisplayname", sa->self_display_name ? sa->self_display_name : sa->username);
+
+ if (g_str_has_prefix(message, "/me ")) {
+ json_object_set_string_member(obj, "skypeemoteoffset", "4"); //Why is this a string :(
+ }
+
+ post = skypeweb_jsonobj_to_string(obj);
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, url, post, skypeweb_sent_message_cb, g_strdup(convname), TRUE);
+
+ g_free(post);
+ json_object_unref(obj);
+ g_free(url);
+ g_free(stripped);
+
+ g_hash_table_insert(sa->sent_messages_hash, clientmessageid_str, clientmessageid_str);
+}
+
+
+gint
+skypeweb_chat_send(PurpleConnection *pc, gint id,
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+PurpleMessage *msg)
+{
+ const gchar *message = purple_message_get_contents(msg);
+#else
+const gchar *message, PurpleMessageFlags flags)
+{
+#endif
+
+ SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
+
+ PurpleChatConversation *chatconv;
+ const gchar* chatname;
+
+ chatconv = purple_conversations_find_chat(pc, id);
+ chatname = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "chatname");
+ if (!chatname) {
+ // Fix for a condition around the chat data and serv_got_joined_chat()
+ chatname = purple_conversation_get_name(PURPLE_CONVERSATION(chatconv));
+ if (!chatname)
+ return -1;
+ }
+
+ skypeweb_send_message(sa, chatname, message);
+
+ purple_serv_got_chat_in(pc, id, sa->username, PURPLE_MESSAGE_SEND, message, time(NULL));
+
+ return 1;
+}
+
+gint
+skypeweb_send_im(PurpleConnection *pc,
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+PurpleMessage *msg)
+{
+ const gchar *who = purple_message_get_recipient(msg);
+ const gchar *message = purple_message_get_contents(msg);
+#else
+const gchar *who, const gchar *message, PurpleMessageFlags flags)
+{
+#endif
+
+ SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
+ gchar *convname;
+
+ convname = g_strconcat(skypeweb_user_url_prefix(who), who, NULL);
+ skypeweb_send_message(sa, convname, message);
+ g_free(convname);
+
+ return 1;
+}
+
+
+void
+skypeweb_chat_invite(PurpleConnection *pc, int id, const char *message, const char *who)
+{
+ SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
+ PurpleChatConversation *chatconv;
+ gchar *chatname;
+ gchar *post;
+ GString *url;
+
+ chatconv = purple_conversations_find_chat(pc, id);
+ chatname = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "chatname");
+
+ url = g_string_new("/v1/threads/");
+ g_string_append_printf(url, "%s", purple_url_encode(chatname));
+ g_string_append(url, "/members/");
+ g_string_append_printf(url, "%s%s", skypeweb_user_url_prefix(who), purple_url_encode(who));
+
+ post = "{\"role\":\"User\"}";
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, url->str, post, NULL, NULL, TRUE);
+
+ g_string_free(url, TRUE);
+}
+
+void
+skypeweb_chat_kick(PurpleConnection *pc, int id, const char *who)
+{
+ SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
+ PurpleChatConversation *chatconv;
+ gchar *chatname;
+ gchar *post;
+ GString *url;
+
+ chatconv = purple_conversations_find_chat(pc, id);
+ chatname = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "chatname");
+
+ url = g_string_new("/v1/threads/");
+ g_string_append_printf(url, "%s", purple_url_encode(chatname));
+ g_string_append(url, "/members/");
+ g_string_append_printf(url, "%s%s", skypeweb_user_url_prefix(who), purple_url_encode(who));
+
+ post = "";
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_DELETE | SKYPEWEB_METHOD_SSL, sa->messages_host, url->str, post, NULL, NULL, TRUE);
+
+ g_string_free(url, TRUE);
+}
+
+void
+skypeweb_initiate_chat(SkypeWebAccount *sa, const gchar *who)
+{
+ JsonObject *obj, *contact;
+ JsonArray *members;
+ gchar *id, *post;
+
+ obj = json_object_new();
+ members = json_array_new();
+
+ contact = json_object_new();
+ id = g_strconcat(skypeweb_user_url_prefix(who), who, NULL);
+ json_object_set_string_member(contact, "id", id);
+ json_object_set_string_member(contact, "role", "User");
+ json_array_add_object_element(members, contact);
+ g_free(id);
+
+ contact = json_object_new();
+ id = g_strconcat(skypeweb_user_url_prefix(sa->username), sa->username, NULL);
+ json_object_set_string_member(contact, "id", id);
+ json_object_set_string_member(contact, "role", "Admin");
+ json_array_add_object_element(members, contact);
+ g_free(id);
+
+ json_object_set_array_member(obj, "members", members);
+ post = skypeweb_jsonobj_to_string(obj);
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, sa->messages_host, "/v1/threads", post, NULL, NULL, TRUE);
+
+ g_free(post);
+ json_object_unref(obj);
+}
+
+void
+skypeweb_initiate_chat_from_node(PurpleBlistNode *node, gpointer userdata)
+{
+ if(PURPLE_IS_BUDDY(node))
+ {
+ PurpleBuddy *buddy = (PurpleBuddy *) node;
+ SkypeWebAccount *sa;
+
+ if (userdata) {
+ sa = userdata;
+ } else {
+ PurpleConnection *pc = purple_account_get_connection(purple_buddy_get_account(buddy));
+ sa = purple_connection_get_protocol_data(pc);
+ }
+
+ skypeweb_initiate_chat(sa, purple_buddy_get_name(buddy));
+ }
+}
+
+void
+skypeweb_chat_set_topic(PurpleConnection *pc, int id, const char *topic)
+{
+ SkypeWebAccount *sa = purple_connection_get_protocol_data(pc);
+ PurpleChatConversation *chatconv;
+ JsonObject *obj;
+ gchar *chatname;
+ gchar *post;
+ GString *url;
+
+ chatconv = purple_conversations_find_chat(pc, id);
+ chatname = purple_conversation_get_data(PURPLE_CONVERSATION(chatconv), "chatname");
+
+ url = g_string_new("/v1/threads/");
+ g_string_append_printf(url, "%s", purple_url_encode(chatname));
+ g_string_append(url, "/properties?name=topic");
+
+ obj = json_object_new();
+ json_object_set_string_member(obj, "topic", topic);
+ post = skypeweb_jsonobj_to_string(obj);
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, sa->messages_host, url->str, post, NULL, NULL, TRUE);
+
+ g_string_free(url, TRUE);
+ g_free(post);
+ json_object_unref(obj);
+}
+
+void
+skypeweb_get_thread_url(SkypeWebAccount *sa, const gchar *thread)
+{
+ //POST https://api.scheduler.skype.com/threads
+ //{"baseDomain":"https://join.skype.com/launch/","threadId":"%s"}
+
+ // {"Id":"MeMxigEAAAAxOTo5NDZkMjExMGQ4YmU0ZjQzODc3NjMxNDQ3ZTgxYWNmNkB0aHJlYWQuc2t5cGU","Blob":null,"JoinUrl":"https://join.skype.com/ALXsHZ2RFQnk","ThreadId":"19:946d2110d8be4f43877631447e81acf6@thread.skype"}
+}
+
+
+static void
+skypeweb_got_self_properties(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ JsonObject *userobj;
+ if (node == NULL || json_node_get_node_type(node) != JSON_NODE_OBJECT)
+ return;
+ userobj = json_node_get_object(node);
+
+ if (json_object_has_member(userobj, "primaryMemberName")) {
+ g_free(sa->primary_member_name); sa->primary_member_name = g_strdup(json_object_get_string_member(userobj, "primaryMemberName"));
+ }
+}
+
+void
+skypeweb_gather_self_properties(SkypeWebAccount *sa)
+{
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, sa->messages_host, "/v1/users/ME/properties", NULL, skypeweb_got_self_properties, NULL, TRUE);
+}
diff --git a/skypeweb/skypeweb_util.c b/skypeweb/skypeweb_util.c
index ef306c2..a9107f3 100644
--- a/skypeweb/skypeweb_util.c
+++ b/skypeweb/skypeweb_util.c
@@ -1,282 +1,282 @@
-/*
- * SkypeWeb Plugin for libpurple/Pidgin
- * Copyright (c) 2014-2020 Eion Robb
- *
- * 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 3 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, see <http://www.gnu.org/licenses/>.
- */
-
-#include "skypeweb_util.h"
-
-gchar *
-skypeweb_string_get_chunk(const gchar *haystack, gsize len, const gchar *start, const gchar *end)
-{
- const gchar *chunk_start, *chunk_end;
- g_return_val_if_fail(haystack && start, NULL);
-
- if (len > 0) {
- chunk_start = g_strstr_len(haystack, len, start);
- } else {
- chunk_start = strstr(haystack, start);
- }
- g_return_val_if_fail(chunk_start, NULL);
- chunk_start += strlen(start);
-
- if (end != NULL) {
- if (len > 0) {
- chunk_end = g_strstr_len(chunk_start, len - (chunk_start - haystack), end);
- } else {
- chunk_end = strstr(chunk_start, end);
- }
- g_return_val_if_fail(chunk_end, NULL);
-
- return g_strndup(chunk_start, chunk_end - chunk_start);
- } else {
- return g_strdup(chunk_start);
- }
-}
-
-gchar *
-skypeweb_jsonobj_to_string(JsonObject *jsonobj)
-{
- JsonGenerator *generator;
- JsonNode *root;
- gchar *string;
-
- root = json_node_new(JSON_NODE_OBJECT);
- json_node_set_object(root, jsonobj);
-
- generator = json_generator_new();
- json_generator_set_root(generator, root);
-
- string = json_generator_to_data(generator, NULL);
-
- g_object_unref(generator);
- json_node_free(root);
-
- return string;
-}
-
-/** turn https://bay-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:eionrobb
- or https://bay-client-s.gateway.messenger.live.com/v1/users/8:eionrobb/presenceDocs/messagingService
- into eionrobb
-*/
-const gchar *
-skypeweb_contact_url_to_name(const gchar *url)
-{
- static gchar *tempname = NULL;
- const gchar *start, *end;
-
- start = g_strrstr(url, "/8:");
- if (!start) start = g_strrstr(url, "/1:");
- if (!start) start = g_strrstr(url, "/4:");
- if (start) start = start + 2;
- if (!start) start = g_strrstr(url, "/2:");
- if (!start) start = g_strrstr(url, "/28:");
- if (start) start = start + 1;
- if (!start) return NULL;
-
- if ((end = strchr(start, '/'))) {
- g_free(tempname);
- tempname = g_strndup(start, end - start);
- return tempname;
- }
-
- g_free(tempname);
- tempname = g_strdup(start);
- return tempname;
-}
-
-/** turn https://bay-client-s.gateway.messenger.live.com/v1/users/ME/conversations/19:blah@thread.skype
- into 19:blah@thread.skype
-*/
-const gchar *
-skypeweb_thread_url_to_name(const gchar *url)
-{
- static gchar *tempname = NULL;
- const gchar *start, *end;
-
- start = g_strrstr(url, "/19:");
- if (!start) return NULL;
- start = start + 1;
-
- if ((end = strchr(start, '/'))) {
- g_free(tempname);
- tempname = g_strndup(start, end - start);
- return tempname;
- }
-
- return start;
-}
-
-/** Blatantly stolen from MSN prpl, with super-secret SHA256 change! */
-#define BUFSIZE 256
-char *
-skypeweb_hmac_sha256(char *input)
-{
- GChecksum *hash;
- const guchar productKey[] = SKYPEWEB_LOCKANDKEY_SECRET;
- const guchar productID[] = SKYPEWEB_LOCKANDKEY_APPID;
- const char hexChars[] = "0123456789abcdef";
- char buf[BUFSIZE];
- unsigned char sha256Hash[32];
- gsize sha256HashLen = sizeof(sha256Hash);
- unsigned char *newHash;
- unsigned int *sha256Parts;
- unsigned int *chlStringParts;
- unsigned int newHashParts[5];
- gchar *output;
-
- long long nHigh = 0, nLow = 0;
-
- int len;
- int i;
-
- hash = g_checksum_new(G_CHECKSUM_SHA256);
- g_checksum_update(hash, (guchar *)input, strlen(input));
- g_checksum_update(hash, productKey, sizeof(productKey) - 1);
- g_checksum_get_digest(hash, (guchar *)sha256Hash, &sha256HashLen);
- g_checksum_free(hash);
-
- /* Split it into four integers */
- sha256Parts = (unsigned int *)sha256Hash;
- for (i = 0; i < 4; i++) {
- /* adjust endianess */
- sha256Parts[i] = GUINT_TO_LE(sha256Parts[i]);
-
- /* & each integer with 0x7FFFFFFF */
- /* and save one unmodified array for later */
- newHashParts[i] = sha256Parts[i];
- sha256Parts[i] &= 0x7FFFFFFF;
- }
-
- /* make a new string and pad with '0' to length that's a multiple of 8 */
- snprintf(buf, BUFSIZE - 5, "%s%s", input, productID);
- len = strlen(buf);
- if ((len % 8) != 0) {
- int fix = 8 - (len % 8);
- memset(&buf[len], '0', fix);
- buf[len + fix] = '\0';
- len += fix;
- }
-
- /* split into integers */
- chlStringParts = (unsigned int *)buf;
-
- /* this is magic */
- for (i = 0; i < (len / 4); i += 2) {
- long long temp;
-
- chlStringParts[i] = GUINT_TO_LE(chlStringParts[i]);
- chlStringParts[i + 1] = GUINT_TO_LE(chlStringParts[i + 1]);
-
- temp = (0x0E79A9C1 * (long long)chlStringParts[i]) % 0x7FFFFFFF;
- temp = (sha256Parts[0] * (temp + nLow) + sha256Parts[1]) % 0x7FFFFFFF;
- nHigh += temp;
-
- temp = ((long long)chlStringParts[i + 1] + temp) % 0x7FFFFFFF;
- nLow = (sha256Parts[2] * temp + sha256Parts[3]) % 0x7FFFFFFF;
- nHigh += nLow;
- }
- nLow = (nLow + sha256Parts[1]) % 0x7FFFFFFF;
- nHigh = (nHigh + sha256Parts[3]) % 0x7FFFFFFF;
-
- newHashParts[0] ^= nLow;
- newHashParts[1] ^= nHigh;
- newHashParts[2] ^= nLow;
- newHashParts[3] ^= nHigh;
-
- /* adjust endianness */
- for(i = 0; i < 4; i++)
- newHashParts[i] = GUINT_TO_LE(newHashParts[i]);
-
- /* make a string of the parts */
- newHash = (unsigned char *)newHashParts;
-
- /* convert to hexadecimal */
- output = g_new0(gchar, 33);
- for (i = 0; i < 16; i++)
- {
- output[i * 2] = hexChars[(newHash[i] >> 4) & 0xF];
- output[(i * 2) + 1] = hexChars[newHash[i] & 0xF];
- }
- output[32] = '\0';
-
- return output;
-}
-
-gint64
-skypeweb_get_js_time()
-{
-#if GLIB_CHECK_VERSION(2, 28, 0)
- return (g_get_real_time() / 1000);
-#else
- GTimeVal val;
-
- g_get_current_time (&val);
-
- return (((gint64) val.tv_sec) * 1000) + (val.tv_usec / 1000);
-#endif
-}
-
-/* copied from oscar.c to be libpurple 2.1 compatible */
-PurpleAccount *
-find_acct(const char *prpl, const char *acct_id)
-{
- PurpleAccount *acct = NULL;
-
- /* If we have a specific acct, use it */
- if (acct_id && *acct_id) {
- acct = purple_accounts_find(acct_id, prpl);
- if (acct && !purple_account_is_connected(acct))
- acct = NULL;
- } else { /* Otherwise find an active account for the protocol */
- GList *l = purple_accounts_get_all();
- while (l) {
- if (!strcmp(prpl, purple_account_get_protocol_id(l->data))
- && purple_account_is_connected(l->data)) {
- acct = l->data;
- break;
- }
- l = l->next;
- }
- }
-
- return acct;
-}
-
-const gchar *
-skypeweb_user_url_prefix(const gchar *who)
-{
- if(SKYPEWEB_BUDDY_IS_S4B(who) || SKYPEWEB_BUDDY_IS_BOT(who)) {
- return ""; // already has a prefix
- } else if (SKYPEWEB_BUDDY_IS_MSN(who)) {
- return "1:";
- } else if(SKYPEWEB_BUDDY_IS_PHONE(who)) {
- return "4:";
- } else {
- return "8:";
- }
-}
-
-const gchar *
-skypeweb_strip_user_prefix(const gchar *who)
-{
- if (who[1] == ':') {
- if (who[0] != '2') {
- return who + 2;
- }
- }
-
- return who;
+/*
+ * SkypeWeb Plugin for libpurple/Pidgin
+ * Copyright (c) 2014-2020 Eion Robb
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "skypeweb_util.h"
+
+gchar *
+skypeweb_string_get_chunk(const gchar *haystack, gsize len, const gchar *start, const gchar *end)
+{
+ const gchar *chunk_start, *chunk_end;
+ g_return_val_if_fail(haystack && start, NULL);
+
+ if (len > 0) {
+ chunk_start = g_strstr_len(haystack, len, start);
+ } else {
+ chunk_start = strstr(haystack, start);
+ }
+ g_return_val_if_fail(chunk_start, NULL);
+ chunk_start += strlen(start);
+
+ if (end != NULL) {
+ if (len > 0) {
+ chunk_end = g_strstr_len(chunk_start, len - (chunk_start - haystack), end);
+ } else {
+ chunk_end = strstr(chunk_start, end);
+ }
+ g_return_val_if_fail(chunk_end, NULL);
+
+ return g_strndup(chunk_start, chunk_end - chunk_start);
+ } else {
+ return g_strdup(chunk_start);
+ }
+}
+
+gchar *
+skypeweb_jsonobj_to_string(JsonObject *jsonobj)
+{
+ JsonGenerator *generator;
+ JsonNode *root;
+ gchar *string;
+
+ root = json_node_new(JSON_NODE_OBJECT);
+ json_node_set_object(root, jsonobj);
+
+ generator = json_generator_new();
+ json_generator_set_root(generator, root);
+
+ string = json_generator_to_data(generator, NULL);
+
+ g_object_unref(generator);
+ json_node_free(root);
+
+ return string;
+}
+
+/** turn https://bay-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:eionrobb
+ or https://bay-client-s.gateway.messenger.live.com/v1/users/8:eionrobb/presenceDocs/messagingService
+ into eionrobb
+*/
+const gchar *
+skypeweb_contact_url_to_name(const gchar *url)
+{
+ static gchar *tempname = NULL;
+ const gchar *start, *end;
+
+ start = g_strrstr(url, "/8:");
+ if (!start) start = g_strrstr(url, "/1:");
+ if (!start) start = g_strrstr(url, "/4:");
+ if (start) start = start + 2;
+ if (!start) start = g_strrstr(url, "/2:");
+ if (!start) start = g_strrstr(url, "/28:");
+ if (start) start = start + 1;
+ if (!start) return NULL;
+
+ if ((end = strchr(start, '/'))) {
+ g_free(tempname);
+ tempname = g_strndup(start, end - start);
+ return tempname;
+ }
+
+ g_free(tempname);
+ tempname = g_strdup(start);
+ return tempname;
+}
+
+/** turn https://bay-client-s.gateway.messenger.live.com/v1/users/ME/conversations/19:blah@thread.skype
+ into 19:blah@thread.skype
+*/
+const gchar *
+skypeweb_thread_url_to_name(const gchar *url)
+{
+ static gchar *tempname = NULL;
+ const gchar *start, *end;
+
+ start = g_strrstr(url, "/19:");
+ if (!start) return NULL;
+ start = start + 1;
+
+ if ((end = strchr(start, '/'))) {
+ g_free(tempname);
+ tempname = g_strndup(start, end - start);
+ return tempname;
+ }
+
+ return start;
+}
+
+/** Blatantly stolen from MSN prpl, with super-secret SHA256 change! */
+#define BUFSIZE 256
+char *
+skypeweb_hmac_sha256(char *input)
+{
+ GChecksum *hash;
+ const guchar productKey[] = SKYPEWEB_LOCKANDKEY_SECRET;
+ const guchar productID[] = SKYPEWEB_LOCKANDKEY_APPID;
+ const char hexChars[] = "0123456789abcdef";
+ char buf[BUFSIZE];
+ unsigned char sha256Hash[32];
+ gsize sha256HashLen = sizeof(sha256Hash);
+ unsigned char *newHash;
+ unsigned int *sha256Parts;
+ unsigned int *chlStringParts;
+ unsigned int newHashParts[5];
+ gchar *output;
+
+ long long nHigh = 0, nLow = 0;
+
+ int len;
+ int i;
+
+ hash = g_checksum_new(G_CHECKSUM_SHA256);
+ g_checksum_update(hash, (guchar *)input, strlen(input));
+ g_checksum_update(hash, productKey, sizeof(productKey) - 1);
+ g_checksum_get_digest(hash, (guchar *)sha256Hash, &sha256HashLen);
+ g_checksum_free(hash);
+
+ /* Split it into four integers */
+ sha256Parts = (unsigned int *)sha256Hash;
+ for (i = 0; i < 4; i++) {
+ /* adjust endianess */
+ sha256Parts[i] = GUINT_TO_LE(sha256Parts[i]);
+
+ /* & each integer with 0x7FFFFFFF */
+ /* and save one unmodified array for later */
+ newHashParts[i] = sha256Parts[i];
+ sha256Parts[i] &= 0x7FFFFFFF;
+ }
+
+ /* make a new string and pad with '0' to length that's a multiple of 8 */
+ snprintf(buf, BUFSIZE - 5, "%s%s", input, productID);
+ len = strlen(buf);
+ if ((len % 8) != 0) {
+ int fix = 8 - (len % 8);
+ memset(&buf[len], '0', fix);
+ buf[len + fix] = '\0';
+ len += fix;
+ }
+
+ /* split into integers */
+ chlStringParts = (unsigned int *)buf;
+
+ /* this is magic */
+ for (i = 0; i < (len / 4); i += 2) {
+ long long temp;
+
+ chlStringParts[i] = GUINT_TO_LE(chlStringParts[i]);
+ chlStringParts[i + 1] = GUINT_TO_LE(chlStringParts[i + 1]);
+
+ temp = (0x0E79A9C1 * (long long)chlStringParts[i]) % 0x7FFFFFFF;
+ temp = (sha256Parts[0] * (temp + nLow) + sha256Parts[1]) % 0x7FFFFFFF;
+ nHigh += temp;
+
+ temp = ((long long)chlStringParts[i + 1] + temp) % 0x7FFFFFFF;
+ nLow = (sha256Parts[2] * temp + sha256Parts[3]) % 0x7FFFFFFF;
+ nHigh += nLow;
+ }
+ nLow = (nLow + sha256Parts[1]) % 0x7FFFFFFF;
+ nHigh = (nHigh + sha256Parts[3]) % 0x7FFFFFFF;
+
+ newHashParts[0] ^= nLow;
+ newHashParts[1] ^= nHigh;
+ newHashParts[2] ^= nLow;
+ newHashParts[3] ^= nHigh;
+
+ /* adjust endianness */
+ for(i = 0; i < 4; i++)
+ newHashParts[i] = GUINT_TO_LE(newHashParts[i]);
+
+ /* make a string of the parts */
+ newHash = (unsigned char *)newHashParts;
+
+ /* convert to hexadecimal */
+ output = g_new0(gchar, 33);
+ for (i = 0; i < 16; i++)
+ {
+ output[i * 2] = hexChars[(newHash[i] >> 4) & 0xF];
+ output[(i * 2) + 1] = hexChars[newHash[i] & 0xF];
+ }
+ output[32] = '\0';
+
+ return output;
+}
+
+gint64
+skypeweb_get_js_time()
+{
+#if GLIB_CHECK_VERSION(2, 28, 0)
+ return (g_get_real_time() / 1000);
+#else
+ GTimeVal val;
+
+ g_get_current_time (&val);
+
+ return (((gint64) val.tv_sec) * 1000) + (val.tv_usec / 1000);
+#endif
+}
+
+/* copied from oscar.c to be libpurple 2.1 compatible */
+PurpleAccount *
+find_acct(const char *prpl, const char *acct_id)
+{
+ PurpleAccount *acct = NULL;
+
+ /* If we have a specific acct, use it */
+ if (acct_id && *acct_id) {
+ acct = purple_accounts_find(acct_id, prpl);
+ if (acct && !purple_account_is_connected(acct))
+ acct = NULL;
+ } else { /* Otherwise find an active account for the protocol */
+ GList *l = purple_accounts_get_all();
+ while (l) {
+ if (!strcmp(prpl, purple_account_get_protocol_id(l->data))
+ && purple_account_is_connected(l->data)) {
+ acct = l->data;
+ break;
+ }
+ l = l->next;
+ }
+ }
+
+ return acct;
+}
+
+const gchar *
+skypeweb_user_url_prefix(const gchar *who)
+{
+ if(SKYPEWEB_BUDDY_IS_S4B(who) || SKYPEWEB_BUDDY_IS_BOT(who)) {
+ return ""; // already has a prefix
+ } else if (SKYPEWEB_BUDDY_IS_MSN(who)) {
+ return "1:";
+ } else if(SKYPEWEB_BUDDY_IS_PHONE(who)) {
+ return "4:";
+ } else {
+ return "8:";
+ }
+}
+
+const gchar *
+skypeweb_strip_user_prefix(const gchar *who)
+{
+ if (who[1] == ':') {
+ if (who[0] != '2') {
+ return who + 2;
+ }
+ }
+
+ return who;
} \ No newline at end of file
diff --git a/skypeweb/skypeweb_util.h b/skypeweb/skypeweb_util.h
index 625c3e7..5d8ad24 100644
--- a/skypeweb/skypeweb_util.h
+++ b/skypeweb/skypeweb_util.h
@@ -1,35 +1,35 @@
-/*
- * SkypeWeb Plugin for libpurple/Pidgin
- * Copyright (c) 2014-2020 Eion Robb
- *
- * 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 3 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, see <http://www.gnu.org/licenses/>.
- */
-
-#include "libskypeweb.h"
-
-gchar *skypeweb_string_get_chunk(const gchar *haystack, gsize len, const gchar *start, const gchar *end);
-
-gchar *skypeweb_jsonobj_to_string(JsonObject *jsonobj);
-
-const gchar *skypeweb_contact_url_to_name(const gchar *url);
-const gchar *skypeweb_thread_url_to_name(const gchar *url);
-
-gchar *skypeweb_hmac_sha256(gchar *input);
-
-gint64 skypeweb_get_js_time();
-
-PurpleAccount *find_acct(const char *prpl, const char *acct_id);
-
-const gchar *skypeweb_user_url_prefix(const gchar *who);
-const gchar *skypeweb_strip_user_prefix(const gchar *who);
+/*
+ * SkypeWeb Plugin for libpurple/Pidgin
+ * Copyright (c) 2014-2020 Eion Robb
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "libskypeweb.h"
+
+gchar *skypeweb_string_get_chunk(const gchar *haystack, gsize len, const gchar *start, const gchar *end);
+
+gchar *skypeweb_jsonobj_to_string(JsonObject *jsonobj);
+
+const gchar *skypeweb_contact_url_to_name(const gchar *url);
+const gchar *skypeweb_thread_url_to_name(const gchar *url);
+
+gchar *skypeweb_hmac_sha256(gchar *input);
+
+gint64 skypeweb_get_js_time();
+
+PurpleAccount *find_acct(const char *prpl, const char *acct_id);
+
+const gchar *skypeweb_user_url_prefix(const gchar *who);
+const gchar *skypeweb_strip_user_prefix(const gchar *who);
diff --git a/skypeweb/theme b/skypeweb/theme
index ee1195c..4fb4b5b 100644
--- a/skypeweb/theme
+++ b/skypeweb/theme
@@ -1,727 +1,727 @@
-#put this theme in its own emotes directory
-#eg /usr/share/pixmaps/pidgin/emotes/skype
-#or C:\Program Files\Pidgin\pixmaps\pidgin\emotes\skype
-
-Name=Default-with-Skype
-Description=Pidgin smileys including Skype
-Icon=../../protocols/22/skype.png
-Author=Hylke Bons
-
-# Default smileys
-[default]
-../default/happy.png :) :-)
-../default/excited.png :-D :-d :D :d
-../default/sad.png :-( :(
-../default/wink.png ;-) ;)
-../default/tongue.png :P :p :-P :-p
-../default/shocked.png =-O =-o
-../default/kiss.png :-*
-../default/glasses-cool.png 8-)
-../default/embarrassed.png :-[
-../default/crying.png :'( :'-(
-../default/thinking.png :-/ :-\\
-../default/angel.png O:-) o:-)
-../default/shut-mouth.png :-X
-../default/moneymouth.png :-$
-../default/foot-in-mouth.png :-!
-../default/shout.png >:o >:O
-! ../default/skywalker.png C:-) c:-) C:) c:)
-! ../default/monkey.png :-(|) :(|) 8-|)
-! ../default/cyclops.png O-) o-)
-
-
-[XMPP]
-# Following XEP-0038 + GTalk + our default set, in default set order
-# The GTalk strings come from ticket #3307.
-../default/happy.png :) :-) =)
-../default/excited.png :-D :-d :D :d =D =d
-../default/sad.png :-( :(
-../default/wink.png ;-) ;) ;^)
-../default/tongue.png :P :p :-P :-p
-../default/shocked.png =-O =-o :-O :-o
-../default/kiss.png :kiss: :-*
-../default/glasses-cool.png 8-) B-)
-../default/embarrassed.png :-[
-../default/crying.png :'-( :'(
-../default/thinking.png :-/ :-\\
-../default/angel.png O:-) o:-)
-../default/shut-mouth.png :-X
-../default/moneymouth.png :-$
-../default/foot-in-mouth.png :-!
-../default/shout.png >:o >:O
-
-# Following XEP-0038 + GTalk
-../default/angry.png >:-( >:( X-( x-(
-../default/good.png :yes:
-../default/bad.png :no:
-../default/stop.png :wait:
-../default/rose.png @->-- :rose:
-../default/phone.png :telephone:
-../default/mail.png :email:
-../default/lamp.png :jabber:
-../default/cake.png :cake:
-../default/in_love.png :heart: :love: <3
-../default/love-over.png :brokenheart:
-../default/musical-note.png :music:
-../default/beer.png :beer:
-../default/coffee.png :coffee:
-../default/coins.png :money:
-../default/moon.png :moon:
-../default/sun.png :sun:
-../default/star.png :star:
-
-# Others
-../default/neutral.png :| :-|
-../default/victory.png \\m/
-
-# Hidden icons from the default set.
-! ../default/skywalker.png C:-) c:-) C:) c:)
-! ../default/monkey.png :-(|) :(|) 8-|)
-! ../default/cyclops.png O-) o-)
-
-
-# Following AIM 6.1
-[AIM]
-../default/happy.png :-) :)
-../default/wink.png ;-) ;)
-../default/sad.png :-( :(
-../default/tongue.png :-P :p :P :-p
-../default/shocked.png =-O =-o
-../default/kiss.png :-*
-../default/shout.png >:o
-../default/excited.png :-D :D
-../default/moneymouth.png :-$
-../default/foot-in-mouth.png :-!
-../default/embarrassed.png :-[
-../default/angel.png O:-)
-../default/thinking.png :-\\ :-/
-../default/crying.png :'(
-../default/shut-mouth.png :-X
-../default/glasses-cool.png 8-)
-! ../default/skywalker.png C:-) c:-) C:) c:)
-! ../default/monkey.png :-(|) :(|) 8-|)
-! ../default/cyclops.png O-) o-)
-
-
-# Following Windows Live Messenger 8.1
-[MSN]
-../default/happy.png :) :-)
-../default/excited.png :D :d :-D :-d
-../default/wink.png ;) ;-)
-../default/shocked.png :-O :-o :O :o
-../default/tongue.png :-P :P :-p :p
-../default/glasses-cool.png (H) (h)
-../default/angry.png :@ :-@
-../default/embarrassed.png :$ :-$
-../default/confused.png :S :s :-S :-s
-../default/sad.png :( :-(
-../default/crying.png :'(
-../default/neutral.png :| :-|
-../default/devil.png (6)
-../default/angel.png (A) (a)
-../default/in_love.png (L) (l)
-../default/love-over.png (U) (u)
-../default/msn.png (M) (m)
-../default/cat.png (@)
-../default/dog.png (&)
-../default/moon.png (S)
-../default/star.png (*)
-../default/film.png (~)
-../default/musical-note.png (8)
-../default/mail.png (E) (e)
-../default/rose.png (F) (f)
-../default/rose-dead.png (W) (w)
-../default/clock.png (O) (o)
-../default/kiss.png (K) (k)
-../default/present.png (G) (g)
-../default/cake.png (^)
-../default/camera.png (P) (p)
-../default/lamp.png (I) (i)
-../default/coffee.png (C) (c)
-../default/phone.png (T) (t)
-../default/hug-left.png ({)
-../default/hug-right.png (})
-../default/beer.png (B) (b)
-../default/drink.png (D) (d)
-../default/boy.png (Z) (z)
-../default/girl.png (X) (x)
-../default/good.png (Y) (y)
-../default/bad.png (N) (n)
-../default/vampire.png :[ :-[
-../default/goat.png (nah)
-../default/sun.png (#)
-../default/rainbow.png (R) (r)
-../default/quiet.png :-#
-../default/teeth.png 8o|
-../default/glasses-nerdy.png 8-|
-../default/sarcastic.png ^o)
-../default/secret.png :-*
-../default/sick.png +o(
-../default/snail.png (sn)
-../default/turtle.png (tu)
-../default/plate.png (pl)
-../default/bowl.png (||)
-../default/pizza.png (pi)
-../default/soccerball.png (so)
-../default/car.png (au)
-../default/airplane.png (ap)
-../default/umbrella.png (um)
-../default/island.png (ip)
-../default/computer.png (co)
-../default/mobile.png (mp)
-../default/brb.png (brb)
-../default/rain.png (st)
-../default/highfive.png (h5)
-../default/coins.png (mo)
-../default/sheep.png (bah)
-../default/dont-know.png :^)
-../default/thinking.png *-)
-../default/thunder.png (li)
-../default/party.png <:o)
-../default/eyeroll.png 8-)
-../default/sleepy.png |-)
-../default/bunny.png ('.')
-! ../default/skywalker.png C:-) c:-) C:) c:)
-! ../default/monkey.png :-(|) :(|) 8-|)
-! ../default/cyclops.png O-) o-)
-
-# Hidden MSN emotes
-../default/cigarette.png (ci) (CI)
-../default/handcuffs.png (%)
-../default/console.png (xx) (XX)
-../default/fingers-crossed.png (yn) (YN)
-
-
-# Following QQ 2006
-[QQ]
-../default/shocked.png /:O /jy /surprised
-../default/curl-lip.png /:~ /pz /curl_lip
-../default/desire.png /:* /se /desire
-../default/dazed.png /:| /dazed
-../default/party.png /8-) /dy /revel
-../default/crying.png /:< /ll /cry
-../default/bashful.png /:$ /hx /bashful
-../default/shut-mouth.png /:X /bz /shut_mouth
-../default/sleeping.png /:Z /shui /sleep
-../default/weep.png /:'( /dk /weep
-../default/embarrassed.png /:-| /gg /embarassed
-../default/pissed-off.png /:@ /fn /pissed_off
-../default/act-up.png /:P /tp /act_up
-../default/excited.png /:D /cy /toothy_smile
-../default/happy.png /:) /wx /small_smile
-../default/sad.png /:( /ng /sad
-../default/glasses-cool.png /:+ /kuk /cool
-../default/doctor.png /:# /feid /SARS
-../default/silly.png /:Q /zk /crazy
-../default/sick.png /:T /tu /vomit
-../default/snicker.png /;p /tx /titter
-../default/cute.png /;-D /ka /cute
-../default/disdain.png /;d /by /disdain
-../default/arrogant.png /;o /am /arrogant
-../default/starving.png /:g /jie /starving
-../default/sleepy.png /|-) /kun /sleepy
-../default/terror.png /:! /jk /terror
-../default/hot.png /:L /sweat
-../default/smirk.png /:> /hanx /smirk
-../default/soldier.png /:; /db /soldier
-../default/struggle.png /;f /fendou /struggle
-../default/curse.png /:-S /zhm /curse
-../default/question.png /? /yiw /question
-../default/quiet.png /;x /xu /shh
-../default/hypnotized.png /;@ /yun /dizzy
-../default/excruciating.png /:8 /zhem /excrutiating
-../default/freaked-out.png /;! /shuai /freaked_out
-../default/skeleton.png /!!! /kl /skeleton
-../default/hammer.png /xx /qiao /hammer
-../default/bye.png /bye /zj /bye
-../default/go-away.png /go /shan /go
-../default/afraid.png /shake /fad /shake
-../default/amorous.png /love /aiq /love
-../default/jump.png /jump /tiao /jump
-../default/search.png /find /zhao /search
-../default/lashes.png /& /mm /beautiful_eyebrows
-../default/pig.png /pig /zt /pig
-../default/cat.png /cat /mm /cat
-../default/dog.png /dog /xg /dog
-../default/hug-left.png /hug /yb /hug
-../default/coins.png /$ /qianc /money
-../default/lamp.png /! /dp /lightbulb
-../default/bowl.png /cup /bei /cup
-../default/cake.png /cake /dg /cake
-../default/thunder.png /li /shd /lightning
-../default/bomb.png /bome /zhd /bomb
-../default/knife.png /kn /dao /knife
-../default/soccerball.png /footb /zq /soccer
-../default/musical-note.png /music /yy /music
-../default/poop.png /shit /bb /shit
-../default/coffee.png /coffee /kf /coffee
-../default/hungry.png /eat /fan /eat
-../default/pill.png /pill /yw /pill
-../default/rose.png /rose /mg /rose
-../default/wilt.png /fade /dx /wilt
-../default/kiss.png /kiss /wen /kiss
-../default/in_love.png /heart /xin /heart
-../default/love-over.png /break /xs /broken_heart
-../default/meeting.png /meeting /hy /meeting
-../default/present.png /gift /lw /gift
-../default/phone.png /phone /dh /phone
-../default/clock.png /time /sj /time
-../default/mail.png /email /yj /email
-../default/tv.png /TV /ds /TV
-../default/sun.png /sun /ty /sun
-../default/moon.png /moon /yl /moon
-../default/good.png /strong /qiang /thumbs_up
-../default/bad.png /weak /ruo /thumbs_down
-../default/handshake.png /share /ws /handshake
-../default/victory.png /v /shl /victory
-../default/beauty.png /<J> /mn /beauty
-../default/qq.png /<QQ> /qz /qq
-../default/blowkiss.png /<L> /fw /blow_kiss
-../default/angry.png /<O> /oh /angry
-../default/liquor.png /<B> /bj /baijiu
-../default/can.png /<U> /qsh /soda
-../default/watermelon.png /<W> /xigua /watermelon
-../default/rain.png /<!!> /xy /rain
-../default/cloudy.png /<~> /duoy /cloudy
-../default/snowman.png /<Z> /xr /snowman
-../default/star.png /<*> /xixing /star
-../default/girl.png /<00> /nv /woman
-../default/boy.png /<11> /nan /man
-! ../default/skywalker.png C:-) c:-) C:) c:)
-! ../default/monkey.png :-(|) :(|) 8-|)
-! ../default/cyclops.png O-) o-)
-
-
-# Following ICQ 6.0
-[ICQ]
-../default/happy.png :-) :)
-../default/neutral.png :-$
-../default/sad.png :-( :(
-../default/shocked.png =-O
-../default/wink.png ;-) ;)
-../default/tongue.png :-P :P :-p :p
-../default/music.png [:-}
-../default/laugh.png *JOKINGLY*
-../default/sleeping.png *TIRED*
-../default/crying.png :'( :'-(
-../default/sick.png :-!
-../default/kissed.png *KISSED*
-../default/stop.png *STOP*
-../default/kiss.png :-{} :-*
-../default/kissing.png *KISSING*
-../default/victory.png *YAHOO*
-../default/silly.png %)
-../default/embarrassed.png :-[
-../default/devil.png ]:->
-../default/angel.png O:-)
-../default/rose.png @}->--
-../default/shut-mouth.png :-X :X :-x :x
-../default/bomb.png @=
-../default/thinking.png :-\\ :-/
-../default/good.png *THUMBS\ UP*
-../default/shout.png >:o >:O :-@
-../default/beer.png *DRINK*
-../default/excited.png :-D :D
-../default/glasses-cool.png 8-)
-../default/amorous.png *IN\ LOVE*
-! ../default/skywalker.png C:-) c:-) C:) c:)
-! ../default/monkey.png :-(|) :(|) 8-|)
-! ../default/cyclops.png O-) o-)
-
-
-# Following Yahoo! Messenger 8.1
-[Yahoo]
-../default/happy.png :) :-)
-../default/question.png :-/ :-\\
-../default/shocked.png :-O :O :-o :o
-../default/devil.png >:)
-../default/angel.png O:-) o:-) 0:-)
-../default/sick.png :-&
-../default/sleepy.png (:|
-../default/hypnotized.png @-)
-../default/on-the-phone.png :)]
-../default/sad.png :( :-(
-../default/amorous.png :x :-x :X :-X
-../default/angry.png X-( x-( X( x(
-../default/crying.png :((
-../default/glasses-nerdy.png :-B :-b
-../default/quiet.png :-$
-../default/drool.png =P~ =p~
-../default/lying.png :^O :^o
-../default/call-me.png :-c
-../default/wink.png ;) ;-)
-../default/embarrassed.png :">
-../default/mean.png :-> :>
-../default/laugh.png :)) :-))
-../default/bye.png =;
-../default/arrogant.png [-(
-../default/thinking.png :-?
-../default/waiting.png :-w :-W
-../default/at-wits-end.png ~x( ~X(
-../default/excited.png :D :-D :d :-d
-../default/tongue.png :-P :P :-p :p
-../default/glasses-cool.png B-) b-)
-../default/neutral.png :| :-|
-../default/sleeping.png I-) i-) |-)
-../default/clown.png :o) :O)
-../default/doh.png #-o #-O
-../default/weep.png :-<
-../default/go-away.png :-h
-../default/lashes.png ;;)
-../default/kiss.png :-* :*
-../default/confused.png :-S :-s
-../default/sarcastic.png /:)
-../default/eyeroll.png 8-|
-../default/silly.png 8-}
-../default/clap.png =D> =d>
-../default/mad-tongue.png >:P >:p
-../default/time-out.png :-t :-T
-../default/hug-left.png >:D< >:d<
-../default/love-over.png =((
-../default/hot.png #:-S #:-s
-../default/rotfl.png =)) :-j :-J
-../default/loser.png L-) l-)
-../default/party.png <:-P <:-p
-../default/nervous.png :-SS :-Ss :-sS :-ss
-../default/cowboy.png <):)
-../default/desire.png 8->
-! ../default/skywalker.png C:-) c:-) C:) c:)
-! ../default/monkey.png :-(|) :(|) 8-|)
-! ../default/cyclops.png O-) o-)
-
-# Hidden Yahoo emotes
-../default/alien.png =:) >-)
-../default/beat-up.png b-( B-(
-../default/chicken.png ~:>
-../default/coffee.png ~o) ~O)
-../default/cow.png 3:-O 3:-o
-../default/dance.png \\:D/ \\:d/
-../default/rose.png @};-
-../default/dont-know.png :-L :-l
-../default/skeleton.png 8-X 8-x
-../default/lamp.png *-:)
-../default/monkey.png :(|)
-../default/coins.png $-)
-../default/peace.png :)>-
-../default/pig.png :@)
-../default/pray.png [-o< [-O<
-../default/pumpkin.png (~~)
-../default/shame.png [-X [-x
-../default/flag.png **==
-../default/clover.png %%-
-../default/musical-note.png :-"
-../default/giggle.png ;))
-../default/worship.png ^:)^
-../default/star.png (*)
-../default/waving.png >:/
-../default/talktohand.png :-@
-
-# Only available after activating the Yahoo! Fighter IMVironment
-../default/male-fighter1.png o-> O->
-../default/male-fighter2.png o=> O=>
-../default/female-fighter.png o-+ O-+
-../default/yin-yang.png (%)
-
-# Following Yahoo! Messenger 8.1
-[Yahoo JAPAN]
-../default/happy.png :) :-)
-../default/question.png :-/ :-\\
-../default/shocked.png :-O :O :-o :o
-../default/devil.png >:)
-../default/angel.png O:-) o:-) 0:-)
-../default/sick.png :-&
-../default/sleepy.png (:|
-../default/hypnotized.png @-)
-../default/on-the-phone.png :)]
-../default/sad.png :( :-(
-../default/amorous.png :x :-x :X :-X
-../default/angry.png X-( x-( X( x(
-../default/crying.png :((
-../default/glasses-nerdy.png :-B :-b
-../default/quiet.png :-$
-../default/drool.png =P~ =p~
-../default/lying.png :^O :^o
-../default/call-me.png :-c
-../default/wink.png ;) ;-)
-../default/embarrassed.png :">
-../default/mean.png :-> :>
-../default/laugh.png :)) :-))
-../default/bye.png =;
-../default/arrogant.png [-(
-../default/thinking.png :-?
-../default/waiting.png :-w :-W
-../default/at-wits-end.png ~x( ~X(
-../default/excited.png :D :-D :d :-d
-../default/tongue.png :-P :P :-p :p
-../default/glasses-cool.png B-) b-)
-../default/neutral.png :| :-|
-../default/sleeping.png I-) i-) |-)
-../default/clown.png :o) :O)
-../default/doh.png #-o #-O
-../default/weep.png :-<
-../default/go-away.png :-h
-../default/lashes.png ;;)
-../default/kiss.png :-* :*
-../default/confused.png :-S :-s
-../default/sarcastic.png /:)
-../default/eyeroll.png 8-|
-../default/silly.png 8-}
-../default/clap.png =D> =d>
-../default/mad-tongue.png >:P >:p
-../default/time-out.png :-t :-T
-../default/hug-left.png >:D< >:d<
-../default/love-over.png =((
-../default/hot.png #:-S #:-s
-../default/rotfl.png =)) :-j :-J
-../default/loser.png L-) l-)
-../default/party.png <:-P <:-p
-../default/nervous.png :-SS :-Ss :-sS :-ss
-../default/cowboy.png <):)
-../default/desire.png 8->
-! ../default/skywalker.png C:-) c:-) C:) c:)
-! ../default/monkey.png :-(|) :(|) 8-|)
-! ../default/cyclops.png O-) o-)
-
-# Hidden Yahoo emotes
-../default/alien.png =:) >-)
-../default/beat-up.png b-( B-(
-../default/chicken.png ~:>
-../default/coffee.png ~o) ~O)
-../default/cow.png 3:-O 3:-o
-../default/dance.png \\:D/ \\:d/
-../default/rose.png @};-
-../default/dont-know.png :-L :-l
-../default/skeleton.png 8-X 8-x
-../default/lamp.png *-:)
-../default/monkey.png :(|)
-../default/coins.png $-)
-../default/peace.png :)>-
-../default/pig.png :@)
-../default/pray.png [-o< [-O<
-../default/pumpkin.png (~~)
-../default/shame.png [-X [-x
-../default/flag.png **==
-../default/clover.png %%-
-../default/musical-note.png :-"
-../default/giggle.png ;))
-../default/worship.png ^:)^
-../default/star.png (*)
-../default/waving.png >:/
-../default/talktohand.png :-@
-
-# Only available after activating the Yahoo! Fighter IMVironment
-../default/male-fighter1.png o-> O->
-../default/male-fighter2.png o=> O=>
-../default/female-fighter.png o-+ O-+
-../default/yin-yang.png (%)
-
-
-# Following MySpaceIM Beta 1.0.697.0
-[MySpaceIM]
-../default/excited.png :D :-D
-../default/devil.png }:)
-../default/confused.png :Z
-../default/glasses-nerdy.png B)
-../default/bulgy-eyes.png %)
-../default/freaked-out.png :E
-../default/happy.png :) :-)
-../default/amorous.png :X
-../default/laugh.png :))
-../default/mohawk.png -:
-../default/mad-tongue.png X(
-../default/messed.png X)
-../default/glasses-nerdy.png Q)
-../default/doh.png :G
-../default/pirate.png P)
-../default/shocked.png :O
-../default/sidefrown.png :{
-../default/sinister.png :B
-../default/smirk.png :,
-../default/neutral.png :|
-../default/tongue.png :P :p
-../default/pissed-off.png B|
-../default/wink.png ;-) ;)
-../default/sad.png :[
-../default/kiss.png :x
-! ../default/skywalker.png C:-) c:-) C:) c:)
-! ../default/monkey.png :-(|) :(|) 8-|)
-! ../default/cyclops.png O-) o-)
-
-
-# MXit standard emoticons
-[MXit]
-../default/happy.png :-) :)
-../default/sad.png :-( :(
-../default/wink.png ;-) ;)
-../default/excited.png :-D :D :-> :>
-../default/neutral.png :-| :|
-../default/shocked.png :-O :O
-../default/tongue.png :-P :P
-../default/embarrassed.png :-$ :$
-../default/glasses-cool.png 8-)
-../default/in_love.png (H)
-../default/rose.png (F)
-### Added in v3.0
-../default/boy.png (m)
-../default/girl.png (f)
-../default/star.png (*)
-../default/chilli.png (c)
-../default/kiss.png (x)
-../default/lamp.png (i)
-../default/pissed-off.png :e :-e
-../default/shut-mouth.png :-x :x
-../default/thunder.png (z)
-../default/coffee.png (U)
-../default/mrgreen.png (G)
-### Added in v5.0
-../default/sick.png :o(
-../default/excruciating.png :-{ :{
-../default/amorous.png :-} :}
-../default/eyeroll.png 8-o 8o
-../default/crying.png :'(
-../default/thinking.png :-? :?
-../default/drool.png :-~ :~
-../default/sleeping.png :-z :z
-../default/lying.png :L)
-../default/glasses-nerdy.png 8-| 8|
-../default/pirate.png P-)
-### Added in v5.9.7
-../default/bored.png :-[ :[
-../default/cold.png :-< :<
-../default/confused.png :-, :,
-../default/hungry.png :-C :C
-../default/stressed.png :-s :s
-
-[Skype (HTTP)]
-../default/happy.png :) :-) :=) (smile)
-../default/sad.png :( :-( :=( (sad) :< :-<
-../default/excited.png :D :-D :=D :d :-d :=d (laugh) :> :-> (lol) (LOL)
-../default/glasses-cool.png 8-) 8=) B-) B=) (cool)
-../default/shocked.png :O :-O :=O :o :-o :=o (surprised)
-../default/wink.png ;) ;-) ;=) (wink)
-../default/crying.png ;( ;-( ;=( (cry) :'(
-../default/hot.png (:| (sweat)
-../default/neutral.png :| :-| :=| (speechless)
-../default/kiss.png :* :-* :=* (kiss) (xo) (K) (k)
-../default/tongue.png :P :-P :=P :p :-p :=p (tongueout)
-../default/embarrassed.png :$ :-$ :=$ :"> (blush)
-../default/dont-know.png :^) (wonder)
-../default/sleeping.png |-) I-) I=) (snooze)
-../default/neutral.png |-( |( |=( (dull)
-../default/amorous.png :] :-] (love) (inlove)
-../default/mean.png ]:) >:) (grin)
-../default/fingers-crossed.png (fingers) (fingerscrossed) (crossedfingers) (yn)
-../default/sleepy.png (yawn)
-../default/sick.png :& :-& :=& +o( (puke)
-../default/doh.png (doh)
-../default/angry.png :@ :-@ :=@ x( x-( X( X-( x=( X=( ;@ ;-@ (angry)
-../default/go-away.png (wm) (wasntme)
-../default/party.png <O) <o) <:o) (party)
-../default/confused.png :S :s :-s :-S :=s :=S (worry)
-../default/snicker.png (mm) (mmm) (mmmm)
-../default/glasses-nerdy.png 8-| B-| 8| B| 8=| B=| (nerd)
-../default/quiet.png :x :-x :X :-X :# :-# :=x :=X :=#
-../default/bye.png (hi) (wave) (bye) (BYE) (Bye) (Hi) (HI)
-../default/doh.png (facepalm) (fail)
-../default/devil.png (6) (devil)
-../default/angel.png (a) (A) (angel)
-../default/desire.png (v) (V) (envy)
-../default/stop.png (wait)
-../default/hug-left.png (hug) (bear)
-../default/beauty.png (makeup) (kate)
-../default/giggle.png (chuckle) (giggle)
-../default/clap.png (clap)
-../default/thinking.png :-? :? :=? *-) (think)
-../default/worship.png (bow)
-../default/rotfl.png (rotfl) (rofl)
-../default/hot.png (phew) (whew)
-../default/curl-lip.png (happy)
-../default/smirk.png (smirk)
-../default/happy.png (nod)
-../default/wilt.png (shake)
-../default/waiting.png (waiting) (forever) (impatience)
-../default/ghost.png (emo)
-../default/good.png (y) (Y) (ok) (OK) (oK) (Ok) (yes)
-../default/bad.png (n) (N) (no)
-../default/handshake.png (handshake)
-../default/highfive.png (highfive) (hifive) (h5)
-../default/in_love.png (h) (H) (l) (L) <3 (heart)
-../default/talktohand.png (lalala) (lalalala) (lala) (notlistening)
-../default/bunny.png (heidy) (squirrel)
-../default/rose.png (F) (f) (flower)
-../default/rain.png (st) (St) (ST) (london) (rain)
-../default/sun.png (#) (sun)
-../default/snail.png (tumbleweed)
-../default/musical-note.png (8) (music)
-../default/terror.png (bandit)
-../default/afraid.png (tmi)
-../default/coffee.png (coffee) (c) (C)
-../default/pizza.png (pi) (pizza)
-../default/coins.png (cash) (mo) ($)
-../default/waving.png (flex) (muscle)
-../default/cake.png (^) (cake)
-../default/beer.png (beer) (b) (B) (bricklayers)
-../default/drink.png (d) (D) (drink)
-../default/dance.png (dance) \\o/ \\:D/ \\:d/
-../default/male-fighter2.png (ninja) (J) (j)
-../default/star.png (*) (star)
-
-# Hidden/Secret emoticons
-! ../default/at-wits-end.png (headbang) (banghead)
-! ../default/curse.png (swear)
-! ../default/bulgy-eyes.png (drunk)
-! ../default/hypnotized.png (fubar)
-! ../small/bathing.png (hrv) (poolparty)
-! ../default/mohawk.png (rock)
-! ../default/cigarette.png (smoking) (smoke) (ci)
-! ../default/turtle.png (bug)
-! ../default/dog.png (toivo)
-! ../default/computer.png (@H) (@h) (wfh)
-! ../default/camera.png (zilmer) (priidu)
-! ../default/mail.png (e) (E) (m) (mail)
-! ../default/clock.png (o) (O) (time) (clock) (0)
-! ../default/film.png (~) (film) (movie)
-! ../default/mobile.png (mp) (ph) (phone)
-! ../default/secret.png (talk)
-! ../default/call-me.png (call) (t) (T)
-! ../default/beat-up.png (punch) *| *-|
-! ../../protocols/22/skype.png (skype) (ss)
-! ../default/love-over.png (u) (U) (brokenheart)
-! ../default/moon.png (mooning)
-! ../default/cowboy.png (oliver)
-! ../default/soccerball.png (bartlett) (football) (soccer) (so)
-! ../default/goat.png (malthe)
-! ../default/snowman.png (tauri)
-! ../default/talktohand.png (talktothehand)
-! ../default/lamp.png (idea) :i :I *-:)
-! ../default/sheep.png (sheep) (bah)
-! ../default/cat.png (cat) :3 (@) (meow)
-! ../default/car.png (bike) (bicycle) (sander)
-! ../default/dog.png (dog) :o3 (&)
-# MSN Emotes?
-! ../default/camera.png (camera) (p) (P)
-! ../default/phone.png (canyoutalk) (!!)
-! ../default/car.png (car) (au)
-! ../default/computer.png (computer) (co) (pc)
-! ../default/handcuffs.png (confidential) (qt) (QT) (Qt)
-! ../default/girl.png (woman) (x) (X) (female) (girl)
-! ../default/console.png (games) (ply) (PLY) (play) (xbox)
-! ../default/present.png (gift) (g) (G)
-! ../default/clover.png (goodluck) (gl) (GL)
-! ../default/brb.png (gottarun) (gtr) (GTR) (run)
-! ../default/clock.png (holdon) (w8) (W8)
-! ../default/island.png (island) (ip)
-#! ../default/calendar.png (letsmeet) (s+) (S+) (calendar)
-! ../default/boy.png (man) (z) (Z) (male) (boy)
-! ../default/plane.png (plane) (ap) (airplane) (aeroplane) (aircraft) (jet)
-! ../default/rainbow.png (rainbow) (r) (R)
-! ../default/snail.png (snail) (sn) (SN)
-! ../default/stop.png (stop) (!)
-! ../default/umbrella.png (umbrella) (um)
-! ../default/excruciating.png (whatsgoingon) (!!?) (wtf)
-
-! ../../protocols/22/myspace.png (myspace)
-! ../default/flag.png (flag:US)
-! ../default/lashes.png (hollest)
-! ../default/loser.png (finger)
-
+#put this theme in its own emotes directory
+#eg /usr/share/pixmaps/pidgin/emotes/skype
+#or C:\Program Files\Pidgin\pixmaps\pidgin\emotes\skype
+
+Name=Default-with-Skype
+Description=Pidgin smileys including Skype
+Icon=../../protocols/22/skype.png
+Author=Hylke Bons
+
+# Default smileys
+[default]
+../default/happy.png :) :-)
+../default/excited.png :-D :-d :D :d
+../default/sad.png :-( :(
+../default/wink.png ;-) ;)
+../default/tongue.png :P :p :-P :-p
+../default/shocked.png =-O =-o
+../default/kiss.png :-*
+../default/glasses-cool.png 8-)
+../default/embarrassed.png :-[
+../default/crying.png :'( :'-(
+../default/thinking.png :-/ :-\\
+../default/angel.png O:-) o:-)
+../default/shut-mouth.png :-X
+../default/moneymouth.png :-$
+../default/foot-in-mouth.png :-!
+../default/shout.png >:o >:O
+! ../default/skywalker.png C:-) c:-) C:) c:)
+! ../default/monkey.png :-(|) :(|) 8-|)
+! ../default/cyclops.png O-) o-)
+
+
+[XMPP]
+# Following XEP-0038 + GTalk + our default set, in default set order
+# The GTalk strings come from ticket #3307.
+../default/happy.png :) :-) =)
+../default/excited.png :-D :-d :D :d =D =d
+../default/sad.png :-( :(
+../default/wink.png ;-) ;) ;^)
+../default/tongue.png :P :p :-P :-p
+../default/shocked.png =-O =-o :-O :-o
+../default/kiss.png :kiss: :-*
+../default/glasses-cool.png 8-) B-)
+../default/embarrassed.png :-[
+../default/crying.png :'-( :'(
+../default/thinking.png :-/ :-\\
+../default/angel.png O:-) o:-)
+../default/shut-mouth.png :-X
+../default/moneymouth.png :-$
+../default/foot-in-mouth.png :-!
+../default/shout.png >:o >:O
+
+# Following XEP-0038 + GTalk
+../default/angry.png >:-( >:( X-( x-(
+../default/good.png :yes:
+../default/bad.png :no:
+../default/stop.png :wait:
+../default/rose.png @->-- :rose:
+../default/phone.png :telephone:
+../default/mail.png :email:
+../default/lamp.png :jabber:
+../default/cake.png :cake:
+../default/in_love.png :heart: :love: <3
+../default/love-over.png :brokenheart:
+../default/musical-note.png :music:
+../default/beer.png :beer:
+../default/coffee.png :coffee:
+../default/coins.png :money:
+../default/moon.png :moon:
+../default/sun.png :sun:
+../default/star.png :star:
+
+# Others
+../default/neutral.png :| :-|
+../default/victory.png \\m/
+
+# Hidden icons from the default set.
+! ../default/skywalker.png C:-) c:-) C:) c:)
+! ../default/monkey.png :-(|) :(|) 8-|)
+! ../default/cyclops.png O-) o-)
+
+
+# Following AIM 6.1
+[AIM]
+../default/happy.png :-) :)
+../default/wink.png ;-) ;)
+../default/sad.png :-( :(
+../default/tongue.png :-P :p :P :-p
+../default/shocked.png =-O =-o
+../default/kiss.png :-*
+../default/shout.png >:o
+../default/excited.png :-D :D
+../default/moneymouth.png :-$
+../default/foot-in-mouth.png :-!
+../default/embarrassed.png :-[
+../default/angel.png O:-)
+../default/thinking.png :-\\ :-/
+../default/crying.png :'(
+../default/shut-mouth.png :-X
+../default/glasses-cool.png 8-)
+! ../default/skywalker.png C:-) c:-) C:) c:)
+! ../default/monkey.png :-(|) :(|) 8-|)
+! ../default/cyclops.png O-) o-)
+
+
+# Following Windows Live Messenger 8.1
+[MSN]
+../default/happy.png :) :-)
+../default/excited.png :D :d :-D :-d
+../default/wink.png ;) ;-)
+../default/shocked.png :-O :-o :O :o
+../default/tongue.png :-P :P :-p :p
+../default/glasses-cool.png (H) (h)
+../default/angry.png :@ :-@
+../default/embarrassed.png :$ :-$
+../default/confused.png :S :s :-S :-s
+../default/sad.png :( :-(
+../default/crying.png :'(
+../default/neutral.png :| :-|
+../default/devil.png (6)
+../default/angel.png (A) (a)
+../default/in_love.png (L) (l)
+../default/love-over.png (U) (u)
+../default/msn.png (M) (m)
+../default/cat.png (@)
+../default/dog.png (&)
+../default/moon.png (S)
+../default/star.png (*)
+../default/film.png (~)
+../default/musical-note.png (8)
+../default/mail.png (E) (e)
+../default/rose.png (F) (f)
+../default/rose-dead.png (W) (w)
+../default/clock.png (O) (o)
+../default/kiss.png (K) (k)
+../default/present.png (G) (g)
+../default/cake.png (^)
+../default/camera.png (P) (p)
+../default/lamp.png (I) (i)
+../default/coffee.png (C) (c)
+../default/phone.png (T) (t)
+../default/hug-left.png ({)
+../default/hug-right.png (})
+../default/beer.png (B) (b)
+../default/drink.png (D) (d)
+../default/boy.png (Z) (z)
+../default/girl.png (X) (x)
+../default/good.png (Y) (y)
+../default/bad.png (N) (n)
+../default/vampire.png :[ :-[
+../default/goat.png (nah)
+../default/sun.png (#)
+../default/rainbow.png (R) (r)
+../default/quiet.png :-#
+../default/teeth.png 8o|
+../default/glasses-nerdy.png 8-|
+../default/sarcastic.png ^o)
+../default/secret.png :-*
+../default/sick.png +o(
+../default/snail.png (sn)
+../default/turtle.png (tu)
+../default/plate.png (pl)
+../default/bowl.png (||)
+../default/pizza.png (pi)
+../default/soccerball.png (so)
+../default/car.png (au)
+../default/airplane.png (ap)
+../default/umbrella.png (um)
+../default/island.png (ip)
+../default/computer.png (co)
+../default/mobile.png (mp)
+../default/brb.png (brb)
+../default/rain.png (st)
+../default/highfive.png (h5)
+../default/coins.png (mo)
+../default/sheep.png (bah)
+../default/dont-know.png :^)
+../default/thinking.png *-)
+../default/thunder.png (li)
+../default/party.png <:o)
+../default/eyeroll.png 8-)
+../default/sleepy.png |-)
+../default/bunny.png ('.')
+! ../default/skywalker.png C:-) c:-) C:) c:)
+! ../default/monkey.png :-(|) :(|) 8-|)
+! ../default/cyclops.png O-) o-)
+
+# Hidden MSN emotes
+../default/cigarette.png (ci) (CI)
+../default/handcuffs.png (%)
+../default/console.png (xx) (XX)
+../default/fingers-crossed.png (yn) (YN)
+
+
+# Following QQ 2006
+[QQ]
+../default/shocked.png /:O /jy /surprised
+../default/curl-lip.png /:~ /pz /curl_lip
+../default/desire.png /:* /se /desire
+../default/dazed.png /:| /dazed
+../default/party.png /8-) /dy /revel
+../default/crying.png /:< /ll /cry
+../default/bashful.png /:$ /hx /bashful
+../default/shut-mouth.png /:X /bz /shut_mouth
+../default/sleeping.png /:Z /shui /sleep
+../default/weep.png /:'( /dk /weep
+../default/embarrassed.png /:-| /gg /embarassed
+../default/pissed-off.png /:@ /fn /pissed_off
+../default/act-up.png /:P /tp /act_up
+../default/excited.png /:D /cy /toothy_smile
+../default/happy.png /:) /wx /small_smile
+../default/sad.png /:( /ng /sad
+../default/glasses-cool.png /:+ /kuk /cool
+../default/doctor.png /:# /feid /SARS
+../default/silly.png /:Q /zk /crazy
+../default/sick.png /:T /tu /vomit
+../default/snicker.png /;p /tx /titter
+../default/cute.png /;-D /ka /cute
+../default/disdain.png /;d /by /disdain
+../default/arrogant.png /;o /am /arrogant
+../default/starving.png /:g /jie /starving
+../default/sleepy.png /|-) /kun /sleepy
+../default/terror.png /:! /jk /terror
+../default/hot.png /:L /sweat
+../default/smirk.png /:> /hanx /smirk
+../default/soldier.png /:; /db /soldier
+../default/struggle.png /;f /fendou /struggle
+../default/curse.png /:-S /zhm /curse
+../default/question.png /? /yiw /question
+../default/quiet.png /;x /xu /shh
+../default/hypnotized.png /;@ /yun /dizzy
+../default/excruciating.png /:8 /zhem /excrutiating
+../default/freaked-out.png /;! /shuai /freaked_out
+../default/skeleton.png /!!! /kl /skeleton
+../default/hammer.png /xx /qiao /hammer
+../default/bye.png /bye /zj /bye
+../default/go-away.png /go /shan /go
+../default/afraid.png /shake /fad /shake
+../default/amorous.png /love /aiq /love
+../default/jump.png /jump /tiao /jump
+../default/search.png /find /zhao /search
+../default/lashes.png /& /mm /beautiful_eyebrows
+../default/pig.png /pig /zt /pig
+../default/cat.png /cat /mm /cat
+../default/dog.png /dog /xg /dog
+../default/hug-left.png /hug /yb /hug
+../default/coins.png /$ /qianc /money
+../default/lamp.png /! /dp /lightbulb
+../default/bowl.png /cup /bei /cup
+../default/cake.png /cake /dg /cake
+../default/thunder.png /li /shd /lightning
+../default/bomb.png /bome /zhd /bomb
+../default/knife.png /kn /dao /knife
+../default/soccerball.png /footb /zq /soccer
+../default/musical-note.png /music /yy /music
+../default/poop.png /shit /bb /shit
+../default/coffee.png /coffee /kf /coffee
+../default/hungry.png /eat /fan /eat
+../default/pill.png /pill /yw /pill
+../default/rose.png /rose /mg /rose
+../default/wilt.png /fade /dx /wilt
+../default/kiss.png /kiss /wen /kiss
+../default/in_love.png /heart /xin /heart
+../default/love-over.png /break /xs /broken_heart
+../default/meeting.png /meeting /hy /meeting
+../default/present.png /gift /lw /gift
+../default/phone.png /phone /dh /phone
+../default/clock.png /time /sj /time
+../default/mail.png /email /yj /email
+../default/tv.png /TV /ds /TV
+../default/sun.png /sun /ty /sun
+../default/moon.png /moon /yl /moon
+../default/good.png /strong /qiang /thumbs_up
+../default/bad.png /weak /ruo /thumbs_down
+../default/handshake.png /share /ws /handshake
+../default/victory.png /v /shl /victory
+../default/beauty.png /<J> /mn /beauty
+../default/qq.png /<QQ> /qz /qq
+../default/blowkiss.png /<L> /fw /blow_kiss
+../default/angry.png /<O> /oh /angry
+../default/liquor.png /<B> /bj /baijiu
+../default/can.png /<U> /qsh /soda
+../default/watermelon.png /<W> /xigua /watermelon
+../default/rain.png /<!!> /xy /rain
+../default/cloudy.png /<~> /duoy /cloudy
+../default/snowman.png /<Z> /xr /snowman
+../default/star.png /<*> /xixing /star
+../default/girl.png /<00> /nv /woman
+../default/boy.png /<11> /nan /man
+! ../default/skywalker.png C:-) c:-) C:) c:)
+! ../default/monkey.png :-(|) :(|) 8-|)
+! ../default/cyclops.png O-) o-)
+
+
+# Following ICQ 6.0
+[ICQ]
+../default/happy.png :-) :)
+../default/neutral.png :-$
+../default/sad.png :-( :(
+../default/shocked.png =-O
+../default/wink.png ;-) ;)
+../default/tongue.png :-P :P :-p :p
+../default/music.png [:-}
+../default/laugh.png *JOKINGLY*
+../default/sleeping.png *TIRED*
+../default/crying.png :'( :'-(
+../default/sick.png :-!
+../default/kissed.png *KISSED*
+../default/stop.png *STOP*
+../default/kiss.png :-{} :-*
+../default/kissing.png *KISSING*
+../default/victory.png *YAHOO*
+../default/silly.png %)
+../default/embarrassed.png :-[
+../default/devil.png ]:->
+../default/angel.png O:-)
+../default/rose.png @}->--
+../default/shut-mouth.png :-X :X :-x :x
+../default/bomb.png @=
+../default/thinking.png :-\\ :-/
+../default/good.png *THUMBS\ UP*
+../default/shout.png >:o >:O :-@
+../default/beer.png *DRINK*
+../default/excited.png :-D :D
+../default/glasses-cool.png 8-)
+../default/amorous.png *IN\ LOVE*
+! ../default/skywalker.png C:-) c:-) C:) c:)
+! ../default/monkey.png :-(|) :(|) 8-|)
+! ../default/cyclops.png O-) o-)
+
+
+# Following Yahoo! Messenger 8.1
+[Yahoo]
+../default/happy.png :) :-)
+../default/question.png :-/ :-\\
+../default/shocked.png :-O :O :-o :o
+../default/devil.png >:)
+../default/angel.png O:-) o:-) 0:-)
+../default/sick.png :-&
+../default/sleepy.png (:|
+../default/hypnotized.png @-)
+../default/on-the-phone.png :)]
+../default/sad.png :( :-(
+../default/amorous.png :x :-x :X :-X
+../default/angry.png X-( x-( X( x(
+../default/crying.png :((
+../default/glasses-nerdy.png :-B :-b
+../default/quiet.png :-$
+../default/drool.png =P~ =p~
+../default/lying.png :^O :^o
+../default/call-me.png :-c
+../default/wink.png ;) ;-)
+../default/embarrassed.png :">
+../default/mean.png :-> :>
+../default/laugh.png :)) :-))
+../default/bye.png =;
+../default/arrogant.png [-(
+../default/thinking.png :-?
+../default/waiting.png :-w :-W
+../default/at-wits-end.png ~x( ~X(
+../default/excited.png :D :-D :d :-d
+../default/tongue.png :-P :P :-p :p
+../default/glasses-cool.png B-) b-)
+../default/neutral.png :| :-|
+../default/sleeping.png I-) i-) |-)
+../default/clown.png :o) :O)
+../default/doh.png #-o #-O
+../default/weep.png :-<
+../default/go-away.png :-h
+../default/lashes.png ;;)
+../default/kiss.png :-* :*
+../default/confused.png :-S :-s
+../default/sarcastic.png /:)
+../default/eyeroll.png 8-|
+../default/silly.png 8-}
+../default/clap.png =D> =d>
+../default/mad-tongue.png >:P >:p
+../default/time-out.png :-t :-T
+../default/hug-left.png >:D< >:d<
+../default/love-over.png =((
+../default/hot.png #:-S #:-s
+../default/rotfl.png =)) :-j :-J
+../default/loser.png L-) l-)
+../default/party.png <:-P <:-p
+../default/nervous.png :-SS :-Ss :-sS :-ss
+../default/cowboy.png <):)
+../default/desire.png 8->
+! ../default/skywalker.png C:-) c:-) C:) c:)
+! ../default/monkey.png :-(|) :(|) 8-|)
+! ../default/cyclops.png O-) o-)
+
+# Hidden Yahoo emotes
+../default/alien.png =:) >-)
+../default/beat-up.png b-( B-(
+../default/chicken.png ~:>
+../default/coffee.png ~o) ~O)
+../default/cow.png 3:-O 3:-o
+../default/dance.png \\:D/ \\:d/
+../default/rose.png @};-
+../default/dont-know.png :-L :-l
+../default/skeleton.png 8-X 8-x
+../default/lamp.png *-:)
+../default/monkey.png :(|)
+../default/coins.png $-)
+../default/peace.png :)>-
+../default/pig.png :@)
+../default/pray.png [-o< [-O<
+../default/pumpkin.png (~~)
+../default/shame.png [-X [-x
+../default/flag.png **==
+../default/clover.png %%-
+../default/musical-note.png :-"
+../default/giggle.png ;))
+../default/worship.png ^:)^
+../default/star.png (*)
+../default/waving.png >:/
+../default/talktohand.png :-@
+
+# Only available after activating the Yahoo! Fighter IMVironment
+../default/male-fighter1.png o-> O->
+../default/male-fighter2.png o=> O=>
+../default/female-fighter.png o-+ O-+
+../default/yin-yang.png (%)
+
+# Following Yahoo! Messenger 8.1
+[Yahoo JAPAN]
+../default/happy.png :) :-)
+../default/question.png :-/ :-\\
+../default/shocked.png :-O :O :-o :o
+../default/devil.png >:)
+../default/angel.png O:-) o:-) 0:-)
+../default/sick.png :-&
+../default/sleepy.png (:|
+../default/hypnotized.png @-)
+../default/on-the-phone.png :)]
+../default/sad.png :( :-(
+../default/amorous.png :x :-x :X :-X
+../default/angry.png X-( x-( X( x(
+../default/crying.png :((
+../default/glasses-nerdy.png :-B :-b
+../default/quiet.png :-$
+../default/drool.png =P~ =p~
+../default/lying.png :^O :^o
+../default/call-me.png :-c
+../default/wink.png ;) ;-)
+../default/embarrassed.png :">
+../default/mean.png :-> :>
+../default/laugh.png :)) :-))
+../default/bye.png =;
+../default/arrogant.png [-(
+../default/thinking.png :-?
+../default/waiting.png :-w :-W
+../default/at-wits-end.png ~x( ~X(
+../default/excited.png :D :-D :d :-d
+../default/tongue.png :-P :P :-p :p
+../default/glasses-cool.png B-) b-)
+../default/neutral.png :| :-|
+../default/sleeping.png I-) i-) |-)
+../default/clown.png :o) :O)
+../default/doh.png #-o #-O
+../default/weep.png :-<
+../default/go-away.png :-h
+../default/lashes.png ;;)
+../default/kiss.png :-* :*
+../default/confused.png :-S :-s
+../default/sarcastic.png /:)
+../default/eyeroll.png 8-|
+../default/silly.png 8-}
+../default/clap.png =D> =d>
+../default/mad-tongue.png >:P >:p
+../default/time-out.png :-t :-T
+../default/hug-left.png >:D< >:d<
+../default/love-over.png =((
+../default/hot.png #:-S #:-s
+../default/rotfl.png =)) :-j :-J
+../default/loser.png L-) l-)
+../default/party.png <:-P <:-p
+../default/nervous.png :-SS :-Ss :-sS :-ss
+../default/cowboy.png <):)
+../default/desire.png 8->
+! ../default/skywalker.png C:-) c:-) C:) c:)
+! ../default/monkey.png :-(|) :(|) 8-|)
+! ../default/cyclops.png O-) o-)
+
+# Hidden Yahoo emotes
+../default/alien.png =:) >-)
+../default/beat-up.png b-( B-(
+../default/chicken.png ~:>
+../default/coffee.png ~o) ~O)
+../default/cow.png 3:-O 3:-o
+../default/dance.png \\:D/ \\:d/
+../default/rose.png @};-
+../default/dont-know.png :-L :-l
+../default/skeleton.png 8-X 8-x
+../default/lamp.png *-:)
+../default/monkey.png :(|)
+../default/coins.png $-)
+../default/peace.png :)>-
+../default/pig.png :@)
+../default/pray.png [-o< [-O<
+../default/pumpkin.png (~~)
+../default/shame.png [-X [-x
+../default/flag.png **==
+../default/clover.png %%-
+../default/musical-note.png :-"
+../default/giggle.png ;))
+../default/worship.png ^:)^
+../default/star.png (*)
+../default/waving.png >:/
+../default/talktohand.png :-@
+
+# Only available after activating the Yahoo! Fighter IMVironment
+../default/male-fighter1.png o-> O->
+../default/male-fighter2.png o=> O=>
+../default/female-fighter.png o-+ O-+
+../default/yin-yang.png (%)
+
+
+# Following MySpaceIM Beta 1.0.697.0
+[MySpaceIM]
+../default/excited.png :D :-D
+../default/devil.png }:)
+../default/confused.png :Z
+../default/glasses-nerdy.png B)
+../default/bulgy-eyes.png %)
+../default/freaked-out.png :E
+../default/happy.png :) :-)
+../default/amorous.png :X
+../default/laugh.png :))
+../default/mohawk.png -:
+../default/mad-tongue.png X(
+../default/messed.png X)
+../default/glasses-nerdy.png Q)
+../default/doh.png :G
+../default/pirate.png P)
+../default/shocked.png :O
+../default/sidefrown.png :{
+../default/sinister.png :B
+../default/smirk.png :,
+../default/neutral.png :|
+../default/tongue.png :P :p
+../default/pissed-off.png B|
+../default/wink.png ;-) ;)
+../default/sad.png :[
+../default/kiss.png :x
+! ../default/skywalker.png C:-) c:-) C:) c:)
+! ../default/monkey.png :-(|) :(|) 8-|)
+! ../default/cyclops.png O-) o-)
+
+
+# MXit standard emoticons
+[MXit]
+../default/happy.png :-) :)
+../default/sad.png :-( :(
+../default/wink.png ;-) ;)
+../default/excited.png :-D :D :-> :>
+../default/neutral.png :-| :|
+../default/shocked.png :-O :O
+../default/tongue.png :-P :P
+../default/embarrassed.png :-$ :$
+../default/glasses-cool.png 8-)
+../default/in_love.png (H)
+../default/rose.png (F)
+### Added in v3.0
+../default/boy.png (m)
+../default/girl.png (f)
+../default/star.png (*)
+../default/chilli.png (c)
+../default/kiss.png (x)
+../default/lamp.png (i)
+../default/pissed-off.png :e :-e
+../default/shut-mouth.png :-x :x
+../default/thunder.png (z)
+../default/coffee.png (U)
+../default/mrgreen.png (G)
+### Added in v5.0
+../default/sick.png :o(
+../default/excruciating.png :-{ :{
+../default/amorous.png :-} :}
+../default/eyeroll.png 8-o 8o
+../default/crying.png :'(
+../default/thinking.png :-? :?
+../default/drool.png :-~ :~
+../default/sleeping.png :-z :z
+../default/lying.png :L)
+../default/glasses-nerdy.png 8-| 8|
+../default/pirate.png P-)
+### Added in v5.9.7
+../default/bored.png :-[ :[
+../default/cold.png :-< :<
+../default/confused.png :-, :,
+../default/hungry.png :-C :C
+../default/stressed.png :-s :s
+
+[Skype (HTTP)]
+../default/happy.png :) :-) :=) (smile)
+../default/sad.png :( :-( :=( (sad) :< :-<
+../default/excited.png :D :-D :=D :d :-d :=d (laugh) :> :-> (lol) (LOL)
+../default/glasses-cool.png 8-) 8=) B-) B=) (cool)
+../default/shocked.png :O :-O :=O :o :-o :=o (surprised)
+../default/wink.png ;) ;-) ;=) (wink)
+../default/crying.png ;( ;-( ;=( (cry) :'(
+../default/hot.png (:| (sweat)
+../default/neutral.png :| :-| :=| (speechless)
+../default/kiss.png :* :-* :=* (kiss) (xo) (K) (k)
+../default/tongue.png :P :-P :=P :p :-p :=p (tongueout)
+../default/embarrassed.png :$ :-$ :=$ :"> (blush)
+../default/dont-know.png :^) (wonder)
+../default/sleeping.png |-) I-) I=) (snooze)
+../default/neutral.png |-( |( |=( (dull)
+../default/amorous.png :] :-] (love) (inlove)
+../default/mean.png ]:) >:) (grin)
+../default/fingers-crossed.png (fingers) (fingerscrossed) (crossedfingers) (yn)
+../default/sleepy.png (yawn)
+../default/sick.png :& :-& :=& +o( (puke)
+../default/doh.png (doh)
+../default/angry.png :@ :-@ :=@ x( x-( X( X-( x=( X=( ;@ ;-@ (angry)
+../default/go-away.png (wm) (wasntme)
+../default/party.png <O) <o) <:o) (party)
+../default/confused.png :S :s :-s :-S :=s :=S (worry)
+../default/snicker.png (mm) (mmm) (mmmm)
+../default/glasses-nerdy.png 8-| B-| 8| B| 8=| B=| (nerd)
+../default/quiet.png :x :-x :X :-X :# :-# :=x :=X :=#
+../default/bye.png (hi) (wave) (bye) (BYE) (Bye) (Hi) (HI)
+../default/doh.png (facepalm) (fail)
+../default/devil.png (6) (devil)
+../default/angel.png (a) (A) (angel)
+../default/desire.png (v) (V) (envy)
+../default/stop.png (wait)
+../default/hug-left.png (hug) (bear)
+../default/beauty.png (makeup) (kate)
+../default/giggle.png (chuckle) (giggle)
+../default/clap.png (clap)
+../default/thinking.png :-? :? :=? *-) (think)
+../default/worship.png (bow)
+../default/rotfl.png (rotfl) (rofl)
+../default/hot.png (phew) (whew)
+../default/curl-lip.png (happy)
+../default/smirk.png (smirk)
+../default/happy.png (nod)
+../default/wilt.png (shake)
+../default/waiting.png (waiting) (forever) (impatience)
+../default/ghost.png (emo)
+../default/good.png (y) (Y) (ok) (OK) (oK) (Ok) (yes)
+../default/bad.png (n) (N) (no)
+../default/handshake.png (handshake)
+../default/highfive.png (highfive) (hifive) (h5)
+../default/in_love.png (h) (H) (l) (L) <3 (heart)
+../default/talktohand.png (lalala) (lalalala) (lala) (notlistening)
+../default/bunny.png (heidy) (squirrel)
+../default/rose.png (F) (f) (flower)
+../default/rain.png (st) (St) (ST) (london) (rain)
+../default/sun.png (#) (sun)
+../default/snail.png (tumbleweed)
+../default/musical-note.png (8) (music)
+../default/terror.png (bandit)
+../default/afraid.png (tmi)
+../default/coffee.png (coffee) (c) (C)
+../default/pizza.png (pi) (pizza)
+../default/coins.png (cash) (mo) ($)
+../default/waving.png (flex) (muscle)
+../default/cake.png (^) (cake)
+../default/beer.png (beer) (b) (B) (bricklayers)
+../default/drink.png (d) (D) (drink)
+../default/dance.png (dance) \\o/ \\:D/ \\:d/
+../default/male-fighter2.png (ninja) (J) (j)
+../default/star.png (*) (star)
+
+# Hidden/Secret emoticons
+! ../default/at-wits-end.png (headbang) (banghead)
+! ../default/curse.png (swear)
+! ../default/bulgy-eyes.png (drunk)
+! ../default/hypnotized.png (fubar)
+! ../small/bathing.png (hrv) (poolparty)
+! ../default/mohawk.png (rock)
+! ../default/cigarette.png (smoking) (smoke) (ci)
+! ../default/turtle.png (bug)
+! ../default/dog.png (toivo)
+! ../default/computer.png (@H) (@h) (wfh)
+! ../default/camera.png (zilmer) (priidu)
+! ../default/mail.png (e) (E) (m) (mail)
+! ../default/clock.png (o) (O) (time) (clock) (0)
+! ../default/film.png (~) (film) (movie)
+! ../default/mobile.png (mp) (ph) (phone)
+! ../default/secret.png (talk)
+! ../default/call-me.png (call) (t) (T)
+! ../default/beat-up.png (punch) *| *-|
+! ../../protocols/22/skype.png (skype) (ss)
+! ../default/love-over.png (u) (U) (brokenheart)
+! ../default/moon.png (mooning)
+! ../default/cowboy.png (oliver)
+! ../default/soccerball.png (bartlett) (football) (soccer) (so)
+! ../default/goat.png (malthe)
+! ../default/snowman.png (tauri)
+! ../default/talktohand.png (talktothehand)
+! ../default/lamp.png (idea) :i :I *-:)
+! ../default/sheep.png (sheep) (bah)
+! ../default/cat.png (cat) :3 (@) (meow)
+! ../default/car.png (bike) (bicycle) (sander)
+! ../default/dog.png (dog) :o3 (&)
+# MSN Emotes?
+! ../default/camera.png (camera) (p) (P)
+! ../default/phone.png (canyoutalk) (!!)
+! ../default/car.png (car) (au)
+! ../default/computer.png (computer) (co) (pc)
+! ../default/handcuffs.png (confidential) (qt) (QT) (Qt)
+! ../default/girl.png (woman) (x) (X) (female) (girl)
+! ../default/console.png (games) (ply) (PLY) (play) (xbox)
+! ../default/present.png (gift) (g) (G)
+! ../default/clover.png (goodluck) (gl) (GL)
+! ../default/brb.png (gottarun) (gtr) (GTR) (run)
+! ../default/clock.png (holdon) (w8) (W8)
+! ../default/island.png (island) (ip)
+#! ../default/calendar.png (letsmeet) (s+) (S+) (calendar)
+! ../default/boy.png (man) (z) (Z) (male) (boy)
+! ../default/plane.png (plane) (ap) (airplane) (aeroplane) (aircraft) (jet)
+! ../default/rainbow.png (rainbow) (r) (R)
+! ../default/snail.png (snail) (sn) (SN)
+! ../default/stop.png (stop) (!)
+! ../default/umbrella.png (umbrella) (um)
+! ../default/excruciating.png (whatsgoingon) (!!?) (wtf)
+
+! ../../protocols/22/myspace.png (myspace)
+! ../default/flag.png (flag:US)
+! ../default/lashes.png (hollest)
+! ../default/loser.png (finger)
+