diff options
author | Jan Palus <jpalus@fastmail.com> | 2021-02-01 12:05:11 +0300 |
---|---|---|
committer | Jan Palus <jpalus@fastmail.com> | 2021-02-01 12:05:11 +0300 |
commit | 8aed865de90b0ad574f34bb6a10add778eb0e45c (patch) | |
tree | 232c108a90cfe0d46ac13c0f31bfaeab27e608fc | |
parent | f23849142a7120802ebccea71a214da6945abc98 (diff) |
Unify line endings to LF
different files used different convention -- some used LF, some used
CRLF and some used mix of LF and CRLF:
$ git ls-files | ag -v '(png|dll)$' | xargs file -b | sort | uniq -c
4 ASCII text
3 ASCII text, with CRLF line terminators
1 ASCII text, with very long lines
15 C source, ASCII text
2 C source, ASCII text, with CRLF, LF line terminators
6 C source, ASCII text, with CRLF line terminators
1 C source, ASCII text, with very long lines, with CRLF, LF line terminators
1 C source, ASCII text, with very long lines, with CRLF line terminators
1 makefile script, ASCII text
1 makefile script, ASCII text, with very long lines
1 makefile script, ASCII text, with very long lines, with CRLF line terminators
1 UTF-8 Unicode text
-rw-r--r-- | README.md | 6 | ||||
-rw-r--r-- | skypeweb/Makefile | 294 | ||||
-rw-r--r-- | skypeweb/glibcompat.h | 40 | ||||
-rw-r--r-- | skypeweb/gpl3.txt | 1346 | ||||
-rw-r--r-- | skypeweb/libskypeweb.c | 2052 | ||||
-rw-r--r-- | skypeweb/purple2compat/http.c | 4 | ||||
-rw-r--r-- | skypeweb/purple2compat/internal.h | 34 | ||||
-rw-r--r-- | skypeweb/purple2compat/purple-socket.c | 4 | ||||
-rw-r--r-- | skypeweb/skypeweb_contacts.c | 3178 | ||||
-rw-r--r-- | skypeweb/skypeweb_login.c | 1210 | ||||
-rw-r--r-- | skypeweb/skypeweb_messages.c | 3518 | ||||
-rw-r--r-- | skypeweb/skypeweb_util.c | 562 | ||||
-rw-r--r-- | skypeweb/skypeweb_util.h | 70 | ||||
-rw-r--r-- | skypeweb/theme | 1454 |
14 files changed, 6886 insertions, 6886 deletions
@@ -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 <user>: 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 <user>: 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 [<new topic>]: 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 <user> [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 <user> <MASTER | USER | ADMIN>: 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 <user>: 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 <user>: 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 [<new topic>]: 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 <user> [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 <user> <MASTER | USER | ADMIN>: 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) + |