From 8aed865de90b0ad574f34bb6a10add778eb0e45c Mon Sep 17 00:00:00 2001 From: Jan Palus Date: Mon, 1 Feb 2021 10:05:11 +0100 Subject: 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 --- README.md | 6 +- skypeweb/Makefile | 294 +-- skypeweb/glibcompat.h | 40 +- skypeweb/gpl3.txt | 1346 ++++++------ skypeweb/libskypeweb.c | 2052 +++++++++---------- skypeweb/purple2compat/http.c | 4 +- skypeweb/purple2compat/internal.h | 34 +- skypeweb/purple2compat/purple-socket.c | 4 +- skypeweb/skypeweb_contacts.c | 3178 ++++++++++++++--------------- skypeweb/skypeweb_login.c | 1210 +++++------ skypeweb/skypeweb_messages.c | 3518 ++++++++++++++++---------------- skypeweb/skypeweb_util.c | 562 ++--- skypeweb/skypeweb_util.h | 70 +- skypeweb/theme | 1454 ++++++------- 14 files changed, 6886 insertions(+), 6886 deletions(-) diff --git a/README.md b/README.md index bb699b3..30ba71d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -If you're reading this, you probably want the newer SkypeWeb plugin from [here](https://github.com/EionRobb/skype4pidgin/tree/master/skypeweb). - - +If you're reading this, you probably want the newer SkypeWeb plugin from [here](https://github.com/EionRobb/skype4pidgin/tree/master/skypeweb). + + diff --git a/skypeweb/Makefile b/skypeweb/Makefile index 594e3e1..577f3d0 100644 --- a/skypeweb/Makefile +++ b/skypeweb/Makefile @@ -1,147 +1,147 @@ - -PIDGIN_TREE_TOP ?= ../../pidgin-2.10.11 -PIDGIN3_TREE_TOP ?= ../../pidgin-main -LIBPURPLE_DIR ?= $(PIDGIN_TREE_TOP)/libpurple -WIN32_DEV_TOP ?= $(PIDGIN_TREE_TOP)/../win32-dev - -WIN32_CC ?= $(WIN32_DEV_TOP)/mingw-4.7.2/bin/gcc - -PKG_CONFIG ?= pkg-config -DIR_PERM = 0755 -LIB_PERM = 0755 -FILE_PERM = 0644 -MAKENSIS ?= makensis - -CFLAGS ?= -O2 -g -pipe -LDFLAGS ?= - -# Do some nasty OS and purple version detection -ifeq ($(OS),Windows_NT) - #only defined on 64-bit windows - PROGFILES32 = ${ProgramFiles(x86)} - ifndef PROGFILES32 - PROGFILES32 = $(PROGRAMFILES) - endif - SKYPEWEB_TARGET = libskypeweb.dll - SKYPEWEB_DEST = "$(PROGFILES32)/Pidgin/plugins" - SKYPEWEB_ICONS_DEST = "$(PROGFILES32)/Pidgin/pixmaps/pidgin/protocols" - SKYPEWEB_THEME_DEST = "$(PROGFILES32)/Pidgin/pixmaps/pidgin/emotes/skype" - MAKENSIS = "$(PROGFILES32)/NSIS/makensis.exe" -else - - UNAME_S := $(shell uname -s) - - #.. There are special flags we need for OSX - ifeq ($(UNAME_S), Darwin) - # - #.. /opt/local/include and subdirs are included here to ensure this compiles - # for folks using Macports. I believe Homebrew uses /usr/local/include - # so things should "just work". You *must* make sure your packages are - # all up to date or you will most likely get compilation errors. - # - INCLUDES = -I/opt/local/include -lz $(OS) - - CC = gcc - else - INCLUDES = - CC ?= gcc - endif - - ifeq ($(shell $(PKG_CONFIG) --exists purple-3 2>/dev/null && echo "true"),) - ifeq ($(shell $(PKG_CONFIG) --exists purple 2>/dev/null && echo "true"),) - SKYPEWEB_TARGET = FAILNOPURPLE - SKYPEWEB_DEST = - SKYPEWEB_ICONS_DEST = - SKYPEWEB_THEME_DEST = - else - SKYPEWEB_TARGET = libskypeweb.so - SKYPEWEB_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=plugindir purple` - SKYPEWEB_ICONS_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=datadir purple`/pixmaps/pidgin/protocols - SKYPEWEB_THEME_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=datadir purple`/pixmaps/pidgin/emotes/skype - endif - else - SKYPEWEB_TARGET = libskypeweb3.so - SKYPEWEB_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=plugindir purple-3` - SKYPEWEB_ICONS_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=datadir purple-3`/pixmaps/pidgin/protocols - SKYPEWEB_THEME_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=datadir purple-3`/pixmaps/pidgin/emotes/skype - endif -endif - -WIN32_CFLAGS = -I$(WIN32_DEV_TOP)/glib-2.28.8/include -I$(WIN32_DEV_TOP)/glib-2.28.8/include/glib-2.0 -I$(WIN32_DEV_TOP)/glib-2.28.8/lib/glib-2.0/include -I$(WIN32_DEV_TOP)/json-glib-0.14/include/json-glib-1.0 -DENABLE_NLS -DPACKAGE_VERSION='"$(PLUGIN_VERSION)"' -Wall -Wextra -Werror -Wno-deprecated-declarations -Wno-unused-parameter -fno-strict-aliasing -Wformat -Wno-sign-compare -WIN32_LDFLAGS = -L$(WIN32_DEV_TOP)/glib-2.28.8/lib -L$(PROTOBUF_C_DIR)/bin -L$(WIN32_DEV_TOP)/json-glib-0.14/lib -lpurple -lintl -lglib-2.0 -lgobject-2.0 -ljson-glib-1.0 -g -ggdb -static-libgcc -lz -WIN32_PIDGIN2_CFLAGS = -I$(PIDGIN_TREE_TOP)/libpurple -I$(PIDGIN_TREE_TOP) $(WIN32_CFLAGS) -WIN32_PIDGIN3_CFLAGS = -I$(PIDGIN3_TREE_TOP)/libpurple -I$(PIDGIN3_TREE_TOP) -I$(WIN32_DEV_TOP)/gplugin-dev/gplugin $(WIN32_CFLAGS) -WIN32_PIDGIN2_LDFLAGS = -L$(PIDGIN_TREE_TOP)/libpurple $(WIN32_LDFLAGS) -WIN32_PIDGIN3_LDFLAGS = -L$(PIDGIN3_TREE_TOP)/libpurple -L$(WIN32_DEV_TOP)/gplugin-dev/gplugin $(WIN32_LDFLAGS) -lgplugin - -C_FILES = \ - skypeweb_connection.c \ - skypeweb_contacts.c \ - skypeweb_login.c \ - skypeweb_messages.c \ - skypeweb_util.c \ - libskypeweb.c -PURPLE_COMPAT_FILES := purple2compat/http.c purple2compat/purple-socket.c -PURPLE_C_FILES := libskypeweb.c $(C_FILES) - - - -.PHONY: all install FAILNOPURPLE clean translations - -all: $(SKYPEWEB_TARGET) - -libskypeweb.so: $(PURPLE_C_FILES) $(PURPLE_COMPAT_FILES) - $(CC) -fPIC $(CFLAGS) -shared -o $@ $^ $(LDFLAGS) $(PROTOBUF_OPTS) `$(PKG_CONFIG) purple glib-2.0 json-glib-1.0 zlib --libs --cflags` $(INCLUDES) -Ipurple2compat -g -ggdb - -libskypeweb3.so: $(PURPLE_C_FILES) - $(CC) -fPIC $(CFLAGS) -shared -o $@ $^ $(LDFLAGS) $(PROTOBUF_OPTS) `$(PKG_CONFIG) purple-3 glib-2.0 json-glib-1.0 zlib --libs --cflags` $(INCLUDES) -g -ggdb - -libskypeweb.dll: $(PURPLE_C_FILES) $(PURPLE_COMPAT_FILES) - $(WIN32_CC) -shared -o $@ $^ $(WIN32_PIDGIN2_CFLAGS) $(WIN32_PIDGIN2_LDFLAGS) -Ipurple2compat - -libskypeweb3.dll: $(PURPLE_C_FILES) - $(WIN32_CC) -shared -o $@ $^ $(WIN32_PIDGIN3_CFLAGS) $(WIN32_PIDGIN3_LDFLAGS) - -install: $(SKYPEWEB_TARGET) install-icons install-theme - mkdir -m $(DIR_PERM) -p $(SKYPEWEB_DEST) - install -m $(LIB_PERM) -p $(SKYPEWEB_TARGET) $(SKYPEWEB_DEST) - -install-icons: icons/16/skype.png icons/22/skype.png icons/48/skype.png icons/16/skypeout.png icons/22/skypeout.png icons/48/skypeout.png - mkdir -m $(DIR_PERM) -p $(SKYPEWEB_ICONS_DEST)/16 - mkdir -m $(DIR_PERM) -p $(SKYPEWEB_ICONS_DEST)/22 - mkdir -m $(DIR_PERM) -p $(SKYPEWEB_ICONS_DEST)/48 - install -m $(FILE_PERM) -p icons/16/skype.png $(SKYPEWEB_ICONS_DEST)/16/skype.png - install -m $(FILE_PERM) -p icons/22/skype.png $(SKYPEWEB_ICONS_DEST)/22/skype.png - install -m $(FILE_PERM) -p icons/48/skype.png $(SKYPEWEB_ICONS_DEST)/48/skype.png - install -m $(FILE_PERM) -p icons/16/skypeout.png $(SKYPEWEB_ICONS_DEST)/16/skypeout.png - install -m $(FILE_PERM) -p icons/22/skypeout.png $(SKYPEWEB_ICONS_DEST)/22/skypeout.png - install -m $(FILE_PERM) -p icons/48/skypeout.png $(SKYPEWEB_ICONS_DEST)/48/skypeout.png - -installer: pidgin-skypeweb.nsi libskypeweb.dll - $(MAKENSIS) "/DPIDGIN_VARIANT"="Pidgin" "/DPRODUCT_NAME"="pidgin-skypeweb" "/DINSTALLER_NAME"="pidgin-skypeweb-installer" "/DJSON_GLIB_DLL"="libjson-glib-1.0.dll" pidgin-skypeweb.nsi - -install-theme: theme - mkdir -m $(DIR_PERM) -p $(SKYPEWEB_THEME_DEST) - install -m $(FILE_PERM) -p theme $(SKYPEWEB_THEME_DEST)/theme - -translations: po/skypeweb.pot - -po/skypeweb.pot: - xgettext $^ -k_ --no-location -o $@ - -po/%.po: po/skypeweb.pot - msgmerge $@ po/skypeweb.pot > tmp-$* - mv -f tmp-$* $@ - -po/%.mo: po/%.po - msgfmt -o $@ $^ - -%-locale-install: po/%.mo - install -D -m $(FILE_PERM) -p po/$(*F).mo $(LOCALEDIR)/$(*F)/LC_MESSAGES/purple-discord.mo - -FAILNOPURPLE: - echo "You need libpurple development headers installed to be able to compile this plugin" - -clean: - rm -f $(SKYPEWEB_TARGET) - + +PIDGIN_TREE_TOP ?= ../../pidgin-2.10.11 +PIDGIN3_TREE_TOP ?= ../../pidgin-main +LIBPURPLE_DIR ?= $(PIDGIN_TREE_TOP)/libpurple +WIN32_DEV_TOP ?= $(PIDGIN_TREE_TOP)/../win32-dev + +WIN32_CC ?= $(WIN32_DEV_TOP)/mingw-4.7.2/bin/gcc + +PKG_CONFIG ?= pkg-config +DIR_PERM = 0755 +LIB_PERM = 0755 +FILE_PERM = 0644 +MAKENSIS ?= makensis + +CFLAGS ?= -O2 -g -pipe +LDFLAGS ?= + +# Do some nasty OS and purple version detection +ifeq ($(OS),Windows_NT) + #only defined on 64-bit windows + PROGFILES32 = ${ProgramFiles(x86)} + ifndef PROGFILES32 + PROGFILES32 = $(PROGRAMFILES) + endif + SKYPEWEB_TARGET = libskypeweb.dll + SKYPEWEB_DEST = "$(PROGFILES32)/Pidgin/plugins" + SKYPEWEB_ICONS_DEST = "$(PROGFILES32)/Pidgin/pixmaps/pidgin/protocols" + SKYPEWEB_THEME_DEST = "$(PROGFILES32)/Pidgin/pixmaps/pidgin/emotes/skype" + MAKENSIS = "$(PROGFILES32)/NSIS/makensis.exe" +else + + UNAME_S := $(shell uname -s) + + #.. There are special flags we need for OSX + ifeq ($(UNAME_S), Darwin) + # + #.. /opt/local/include and subdirs are included here to ensure this compiles + # for folks using Macports. I believe Homebrew uses /usr/local/include + # so things should "just work". You *must* make sure your packages are + # all up to date or you will most likely get compilation errors. + # + INCLUDES = -I/opt/local/include -lz $(OS) + + CC = gcc + else + INCLUDES = + CC ?= gcc + endif + + ifeq ($(shell $(PKG_CONFIG) --exists purple-3 2>/dev/null && echo "true"),) + ifeq ($(shell $(PKG_CONFIG) --exists purple 2>/dev/null && echo "true"),) + SKYPEWEB_TARGET = FAILNOPURPLE + SKYPEWEB_DEST = + SKYPEWEB_ICONS_DEST = + SKYPEWEB_THEME_DEST = + else + SKYPEWEB_TARGET = libskypeweb.so + SKYPEWEB_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=plugindir purple` + SKYPEWEB_ICONS_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=datadir purple`/pixmaps/pidgin/protocols + SKYPEWEB_THEME_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=datadir purple`/pixmaps/pidgin/emotes/skype + endif + else + SKYPEWEB_TARGET = libskypeweb3.so + SKYPEWEB_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=plugindir purple-3` + SKYPEWEB_ICONS_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=datadir purple-3`/pixmaps/pidgin/protocols + SKYPEWEB_THEME_DEST = $(DESTDIR)`$(PKG_CONFIG) --variable=datadir purple-3`/pixmaps/pidgin/emotes/skype + endif +endif + +WIN32_CFLAGS = -I$(WIN32_DEV_TOP)/glib-2.28.8/include -I$(WIN32_DEV_TOP)/glib-2.28.8/include/glib-2.0 -I$(WIN32_DEV_TOP)/glib-2.28.8/lib/glib-2.0/include -I$(WIN32_DEV_TOP)/json-glib-0.14/include/json-glib-1.0 -DENABLE_NLS -DPACKAGE_VERSION='"$(PLUGIN_VERSION)"' -Wall -Wextra -Werror -Wno-deprecated-declarations -Wno-unused-parameter -fno-strict-aliasing -Wformat -Wno-sign-compare +WIN32_LDFLAGS = -L$(WIN32_DEV_TOP)/glib-2.28.8/lib -L$(PROTOBUF_C_DIR)/bin -L$(WIN32_DEV_TOP)/json-glib-0.14/lib -lpurple -lintl -lglib-2.0 -lgobject-2.0 -ljson-glib-1.0 -g -ggdb -static-libgcc -lz +WIN32_PIDGIN2_CFLAGS = -I$(PIDGIN_TREE_TOP)/libpurple -I$(PIDGIN_TREE_TOP) $(WIN32_CFLAGS) +WIN32_PIDGIN3_CFLAGS = -I$(PIDGIN3_TREE_TOP)/libpurple -I$(PIDGIN3_TREE_TOP) -I$(WIN32_DEV_TOP)/gplugin-dev/gplugin $(WIN32_CFLAGS) +WIN32_PIDGIN2_LDFLAGS = -L$(PIDGIN_TREE_TOP)/libpurple $(WIN32_LDFLAGS) +WIN32_PIDGIN3_LDFLAGS = -L$(PIDGIN3_TREE_TOP)/libpurple -L$(WIN32_DEV_TOP)/gplugin-dev/gplugin $(WIN32_LDFLAGS) -lgplugin + +C_FILES = \ + skypeweb_connection.c \ + skypeweb_contacts.c \ + skypeweb_login.c \ + skypeweb_messages.c \ + skypeweb_util.c \ + libskypeweb.c +PURPLE_COMPAT_FILES := purple2compat/http.c purple2compat/purple-socket.c +PURPLE_C_FILES := libskypeweb.c $(C_FILES) + + + +.PHONY: all install FAILNOPURPLE clean translations + +all: $(SKYPEWEB_TARGET) + +libskypeweb.so: $(PURPLE_C_FILES) $(PURPLE_COMPAT_FILES) + $(CC) -fPIC $(CFLAGS) -shared -o $@ $^ $(LDFLAGS) $(PROTOBUF_OPTS) `$(PKG_CONFIG) purple glib-2.0 json-glib-1.0 zlib --libs --cflags` $(INCLUDES) -Ipurple2compat -g -ggdb + +libskypeweb3.so: $(PURPLE_C_FILES) + $(CC) -fPIC $(CFLAGS) -shared -o $@ $^ $(LDFLAGS) $(PROTOBUF_OPTS) `$(PKG_CONFIG) purple-3 glib-2.0 json-glib-1.0 zlib --libs --cflags` $(INCLUDES) -g -ggdb + +libskypeweb.dll: $(PURPLE_C_FILES) $(PURPLE_COMPAT_FILES) + $(WIN32_CC) -shared -o $@ $^ $(WIN32_PIDGIN2_CFLAGS) $(WIN32_PIDGIN2_LDFLAGS) -Ipurple2compat + +libskypeweb3.dll: $(PURPLE_C_FILES) + $(WIN32_CC) -shared -o $@ $^ $(WIN32_PIDGIN3_CFLAGS) $(WIN32_PIDGIN3_LDFLAGS) + +install: $(SKYPEWEB_TARGET) install-icons install-theme + mkdir -m $(DIR_PERM) -p $(SKYPEWEB_DEST) + install -m $(LIB_PERM) -p $(SKYPEWEB_TARGET) $(SKYPEWEB_DEST) + +install-icons: icons/16/skype.png icons/22/skype.png icons/48/skype.png icons/16/skypeout.png icons/22/skypeout.png icons/48/skypeout.png + mkdir -m $(DIR_PERM) -p $(SKYPEWEB_ICONS_DEST)/16 + mkdir -m $(DIR_PERM) -p $(SKYPEWEB_ICONS_DEST)/22 + mkdir -m $(DIR_PERM) -p $(SKYPEWEB_ICONS_DEST)/48 + install -m $(FILE_PERM) -p icons/16/skype.png $(SKYPEWEB_ICONS_DEST)/16/skype.png + install -m $(FILE_PERM) -p icons/22/skype.png $(SKYPEWEB_ICONS_DEST)/22/skype.png + install -m $(FILE_PERM) -p icons/48/skype.png $(SKYPEWEB_ICONS_DEST)/48/skype.png + install -m $(FILE_PERM) -p icons/16/skypeout.png $(SKYPEWEB_ICONS_DEST)/16/skypeout.png + install -m $(FILE_PERM) -p icons/22/skypeout.png $(SKYPEWEB_ICONS_DEST)/22/skypeout.png + install -m $(FILE_PERM) -p icons/48/skypeout.png $(SKYPEWEB_ICONS_DEST)/48/skypeout.png + +installer: pidgin-skypeweb.nsi libskypeweb.dll + $(MAKENSIS) "/DPIDGIN_VARIANT"="Pidgin" "/DPRODUCT_NAME"="pidgin-skypeweb" "/DINSTALLER_NAME"="pidgin-skypeweb-installer" "/DJSON_GLIB_DLL"="libjson-glib-1.0.dll" pidgin-skypeweb.nsi + +install-theme: theme + mkdir -m $(DIR_PERM) -p $(SKYPEWEB_THEME_DEST) + install -m $(FILE_PERM) -p theme $(SKYPEWEB_THEME_DEST)/theme + +translations: po/skypeweb.pot + +po/skypeweb.pot: + xgettext $^ -k_ --no-location -o $@ + +po/%.po: po/skypeweb.pot + msgmerge $@ po/skypeweb.pot > tmp-$* + mv -f tmp-$* $@ + +po/%.mo: po/%.po + msgfmt -o $@ $^ + +%-locale-install: po/%.mo + install -D -m $(FILE_PERM) -p po/$(*F).mo $(LOCALEDIR)/$(*F)/LC_MESSAGES/purple-discord.mo + +FAILNOPURPLE: + echo "You need libpurple development headers installed to be able to compile this plugin" + +clean: + rm -f $(SKYPEWEB_TARGET) + diff --git a/skypeweb/glibcompat.h b/skypeweb/glibcompat.h index 35cb10a..95d731a 100644 --- a/skypeweb/glibcompat.h +++ b/skypeweb/glibcompat.h @@ -1,21 +1,21 @@ -#ifndef _GLIBCOMPAT_H_ -#define _GLIBCOMPAT_H_ - -#if !GLIB_CHECK_VERSION(2, 32, 0) -#define g_hash_table_contains(hash_table, key) g_hash_table_lookup_extended(hash_table, key, NULL, NULL) -#endif /* 2.32.0 */ - - -#if !GLIB_CHECK_VERSION(2, 28, 0) -gint64 -g_get_real_time() -{ - GTimeVal val; - - g_get_current_time (&val); - - return (((gint64) val.tv_sec) * 1000000) + val.tv_usec; -} -#endif /* 2.28.0 */ - +#ifndef _GLIBCOMPAT_H_ +#define _GLIBCOMPAT_H_ + +#if !GLIB_CHECK_VERSION(2, 32, 0) +#define g_hash_table_contains(hash_table, key) g_hash_table_lookup_extended(hash_table, key, NULL, NULL) +#endif /* 2.32.0 */ + + +#if !GLIB_CHECK_VERSION(2, 28, 0) +gint64 +g_get_real_time() +{ + GTimeVal val; + + g_get_current_time (&val); + + return (((gint64) val.tv_sec) * 1000000) + val.tv_usec; +} +#endif /* 2.28.0 */ + #endif /*_GLIBCOMPAT_H_*/ \ No newline at end of file diff --git a/skypeweb/gpl3.txt b/skypeweb/gpl3.txt index bb781ef..deea74a 100644 --- a/skypeweb/gpl3.txt +++ b/skypeweb/gpl3.txt @@ -1,674 +1,674 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - 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. - - - Copyright (C) - - 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 . - -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 -. - - 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. + 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. + + + Copyright (C) + + 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 . + +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 +. + + 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 . \ 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 . - */ - -#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 "; - 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 . + */ + +#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 "; + 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 -#include -#include -#ifdef _WIN32 -#include "win32/win32dep.h" -#endif - -#include "../purplecompat.h" - -#ifndef N_ -# define N_(a) (a) -#endif - -#ifndef _ -# define _(a) (a) -#endif + +#include +#include +#include +#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 . - */ - - -#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 . + */ + + +#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("", 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("Click here to view full %s", 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("", 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("Click here to view full %s", 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 . - */ - -#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); - - // - 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? - // - 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); - - // - 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 \ -"\n" \ -"
\n" \ -" \n" \ -" \n" \ -" %s\n" \ -" %s\n" \ -" \n" \ -" \n" \ -"
\n" \ -" \n" \ -" \n" \ -" \n" \ -" http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue\n" \ -" \n" \ -" \n" \ -" wl.skype.com\n" \ -" \n" \ -" \n" \ -" \n" \ -" \n" \ -" \n" \ -" \n" \ -"
" \ - -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 . + */ + +#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); + + // + 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? + // + 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); + + // + 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 \ +"\n" \ +"
\n" \ +" \n" \ +" \n" \ +" %s\n" \ +" %s\n" \ +" \n" \ +" \n" \ +"
\n" \ +" \n" \ +" \n" \ +" \n" \ +" http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue\n" \ +" \n" \ +" \n" \ +" wl.skype.com\n" \ +" \n" \ +" \n" \ +" \n" \ +" \n" \ +" \n" \ +" \n" \ +"
" \ + +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 . - */ - -#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
's in them - stripped = purple_strreplace(xhtml, "
", "\r\n"); - g_free(xhtml); - - // Pidgin has a nasty habit of sending when copy-pasting text - if (font_strip_regex == NULL) { - font_strip_regex = g_regex_new("(]*)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, "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 . + */ + +#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
's in them + stripped = purple_strreplace(xhtml, "
", "\r\n"); + g_free(xhtml); + + // Pidgin has a nasty habit of sending when copy-pasting text + if (font_strip_regex == NULL) { + font_strip_regex = g_regex_new("(]*)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, "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 . - */ - -#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 . + */ + +#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 . - */ - -#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 . + */ + +#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 / /mn /beauty -../default/qq.png / /qz /qq -../default/blowkiss.png / /fw /blow_kiss -../default/angry.png / /oh /angry -../default/liquor.png / /bj /baijiu -../default/can.png / /qsh /soda -../default/watermelon.png / /xigua /watermelon -../default/rain.png / /xy /rain -../default/cloudy.png /<~> /duoy /cloudy -../default/snowman.png / /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