From ab7e0b08fa8c31cf1d468ab4b3c660e2b1836811 Mon Sep 17 00:00:00 2001 From: Fishbone Date: Sun, 2 Jun 2013 16:19:21 +0000 Subject: Added WhatsApp-protocol git-svn-id: http://svn.miranda-ng.org/main/trunk@4861 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- protocols/WhatsApp/WhatsApp_10.vcxproj | 149 ++ protocols/WhatsApp/WhatsApp_10.vcxproj.filters | 184 +++ .../proto_whatsapp/Proto_WhatsApp_10.vcxproj | 94 ++ .../Proto_WhatsApp_10.vcxproj.filters | 17 + protocols/WhatsApp/proto_whatsapp/res/Away.ico | Bin 0 -> 5430 bytes .../WhatsApp/proto_whatsapp/res/Invisible.ico | Bin 0 -> 5430 bytes protocols/WhatsApp/proto_whatsapp/res/Offline.ico | Bin 0 -> 5430 bytes protocols/WhatsApp/proto_whatsapp/res/Online.ico | Bin 0 -> 5430 bytes .../WhatsApp/proto_whatsapp/res/Proto_WhatsApp.rc | Bin 0 -> 3638 bytes protocols/WhatsApp/proto_whatsapp/src/resource.h | Bin 0 -> 1282 bytes protocols/WhatsApp/res/add-group.ico | Bin 0 -> 1150 bytes protocols/WhatsApp/res/add-user-to-group.ico | Bin 0 -> 1150 bytes protocols/WhatsApp/res/change-group-subject.ico | Bin 0 -> 1150 bytes protocols/WhatsApp/res/leave-group.ico | Bin 0 -> 1150 bytes protocols/WhatsApp/res/remove-user-from-group.ico | Bin 0 -> 1150 bytes protocols/WhatsApp/res/version.rc | 38 + protocols/WhatsApp/res/whatsapp.ico | Bin 0 -> 1150 bytes protocols/WhatsApp/res/whatsapp.rc | Bin 0 -> 7498 bytes protocols/WhatsApp/src/WASocketConnection.cpp | 138 ++ protocols/WhatsApp/src/WASocketConnection.h | 41 + .../WhatsApp/src/WhatsAPI++/BinTreeNodeReader.cpp | 349 ++++ .../WhatsApp/src/WhatsAPI++/BinTreeNodeReader.h | 76 + .../WhatsApp/src/WhatsAPI++/BinTreeNodeWriter.cpp | 267 ++++ .../WhatsApp/src/WhatsAPI++/BinTreeNodeWriter.h | 71 + protocols/WhatsApp/src/WhatsAPI++/ByteArray.cpp | 155 ++ protocols/WhatsApp/src/WhatsAPI++/ByteArray.h | 55 + protocols/WhatsApp/src/WhatsAPI++/FMessage.cpp | 128 ++ protocols/WhatsApp/src/WhatsAPI++/FMessage.h | 88 + protocols/WhatsApp/src/WhatsAPI++/IMutex.h | 14 + .../WhatsApp/src/WhatsAPI++/ISocketConnection.h | 25 + protocols/WhatsApp/src/WhatsAPI++/LICENSE | 340 ++++ .../WhatsApp/src/WhatsAPI++/ProtocolTreeNode.cpp | 135 ++ .../WhatsApp/src/WhatsAPI++/ProtocolTreeNode.h | 41 + protocols/WhatsApp/src/WhatsAPI++/README.md | 30 + protocols/WhatsApp/src/WhatsAPI++/WAConnection.cpp | 1684 ++++++++++++++++++++ protocols/WhatsApp/src/WhatsAPI++/WAConnection.h | 464 ++++++ protocols/WhatsApp/src/WhatsAPI++/WAException.h | 40 + protocols/WhatsApp/src/WhatsAPI++/WALogin.cpp | 342 ++++ protocols/WhatsApp/src/WhatsAPI++/WALogin.h | 75 + protocols/WhatsApp/src/WhatsAPI++/base64.cpp | 103 ++ protocols/WhatsApp/src/WhatsAPI++/base64.h | 16 + protocols/WhatsApp/src/WhatsAPI++/stdafx.cpp | 8 + protocols/WhatsApp/src/WhatsAPI++/stdafx.h | 14 + protocols/WhatsApp/src/WhatsAPI++/targetver.h | 8 + protocols/WhatsApp/src/WhatsAPI++/utilities.cpp | 463 ++++++ protocols/WhatsApp/src/WhatsAPI++/utilities.h | 85 + protocols/WhatsApp/src/cJSON.cpp | 515 ++++++ protocols/WhatsApp/src/cJSON.h | 127 ++ protocols/WhatsApp/src/chat.cpp | 77 + protocols/WhatsApp/src/common.h | 96 ++ protocols/WhatsApp/src/connection.cpp | 230 +++ protocols/WhatsApp/src/constants.h | 62 + protocols/WhatsApp/src/contacts.cpp | 754 +++++++++ protocols/WhatsApp/src/db.h | 47 + protocols/WhatsApp/src/definitions.h | 18 + protocols/WhatsApp/src/dialogs.cpp | 179 +++ protocols/WhatsApp/src/dialogs.h | 7 + protocols/WhatsApp/src/entities.h | 41 + protocols/WhatsApp/src/main.cpp | 126 ++ protocols/WhatsApp/src/messages.cpp | 200 +++ protocols/WhatsApp/src/proto.cpp | 446 ++++++ protocols/WhatsApp/src/proto.h | 216 +++ protocols/WhatsApp/src/resource.h | Bin 0 -> 2544 bytes protocols/WhatsApp/src/theme.cpp | 297 ++++ protocols/WhatsApp/src/theme.h | 19 + protocols/WhatsApp/src/utils.cpp | 106 ++ protocols/WhatsApp/src/utils.h | 111 ++ protocols/WhatsApp/src/version.h | 16 + 68 files changed, 9427 insertions(+) create mode 100644 protocols/WhatsApp/WhatsApp_10.vcxproj create mode 100644 protocols/WhatsApp/WhatsApp_10.vcxproj.filters create mode 100644 protocols/WhatsApp/proto_whatsapp/Proto_WhatsApp_10.vcxproj create mode 100644 protocols/WhatsApp/proto_whatsapp/Proto_WhatsApp_10.vcxproj.filters create mode 100644 protocols/WhatsApp/proto_whatsapp/res/Away.ico create mode 100644 protocols/WhatsApp/proto_whatsapp/res/Invisible.ico create mode 100644 protocols/WhatsApp/proto_whatsapp/res/Offline.ico create mode 100644 protocols/WhatsApp/proto_whatsapp/res/Online.ico create mode 100644 protocols/WhatsApp/proto_whatsapp/res/Proto_WhatsApp.rc create mode 100644 protocols/WhatsApp/proto_whatsapp/src/resource.h create mode 100644 protocols/WhatsApp/res/add-group.ico create mode 100644 protocols/WhatsApp/res/add-user-to-group.ico create mode 100644 protocols/WhatsApp/res/change-group-subject.ico create mode 100644 protocols/WhatsApp/res/leave-group.ico create mode 100644 protocols/WhatsApp/res/remove-user-from-group.ico create mode 100644 protocols/WhatsApp/res/version.rc create mode 100644 protocols/WhatsApp/res/whatsapp.ico create mode 100644 protocols/WhatsApp/res/whatsapp.rc create mode 100644 protocols/WhatsApp/src/WASocketConnection.cpp create mode 100644 protocols/WhatsApp/src/WASocketConnection.h create mode 100644 protocols/WhatsApp/src/WhatsAPI++/BinTreeNodeReader.cpp create mode 100644 protocols/WhatsApp/src/WhatsAPI++/BinTreeNodeReader.h create mode 100644 protocols/WhatsApp/src/WhatsAPI++/BinTreeNodeWriter.cpp create mode 100644 protocols/WhatsApp/src/WhatsAPI++/BinTreeNodeWriter.h create mode 100644 protocols/WhatsApp/src/WhatsAPI++/ByteArray.cpp create mode 100644 protocols/WhatsApp/src/WhatsAPI++/ByteArray.h create mode 100644 protocols/WhatsApp/src/WhatsAPI++/FMessage.cpp create mode 100644 protocols/WhatsApp/src/WhatsAPI++/FMessage.h create mode 100644 protocols/WhatsApp/src/WhatsAPI++/IMutex.h create mode 100644 protocols/WhatsApp/src/WhatsAPI++/ISocketConnection.h create mode 100644 protocols/WhatsApp/src/WhatsAPI++/LICENSE create mode 100644 protocols/WhatsApp/src/WhatsAPI++/ProtocolTreeNode.cpp create mode 100644 protocols/WhatsApp/src/WhatsAPI++/ProtocolTreeNode.h create mode 100644 protocols/WhatsApp/src/WhatsAPI++/README.md create mode 100644 protocols/WhatsApp/src/WhatsAPI++/WAConnection.cpp create mode 100644 protocols/WhatsApp/src/WhatsAPI++/WAConnection.h create mode 100644 protocols/WhatsApp/src/WhatsAPI++/WAException.h create mode 100644 protocols/WhatsApp/src/WhatsAPI++/WALogin.cpp create mode 100644 protocols/WhatsApp/src/WhatsAPI++/WALogin.h create mode 100644 protocols/WhatsApp/src/WhatsAPI++/base64.cpp create mode 100644 protocols/WhatsApp/src/WhatsAPI++/base64.h create mode 100644 protocols/WhatsApp/src/WhatsAPI++/stdafx.cpp create mode 100644 protocols/WhatsApp/src/WhatsAPI++/stdafx.h create mode 100644 protocols/WhatsApp/src/WhatsAPI++/targetver.h create mode 100644 protocols/WhatsApp/src/WhatsAPI++/utilities.cpp create mode 100644 protocols/WhatsApp/src/WhatsAPI++/utilities.h create mode 100644 protocols/WhatsApp/src/cJSON.cpp create mode 100644 protocols/WhatsApp/src/cJSON.h create mode 100644 protocols/WhatsApp/src/chat.cpp create mode 100644 protocols/WhatsApp/src/common.h create mode 100644 protocols/WhatsApp/src/connection.cpp create mode 100644 protocols/WhatsApp/src/constants.h create mode 100644 protocols/WhatsApp/src/contacts.cpp create mode 100644 protocols/WhatsApp/src/db.h create mode 100644 protocols/WhatsApp/src/definitions.h create mode 100644 protocols/WhatsApp/src/dialogs.cpp create mode 100644 protocols/WhatsApp/src/dialogs.h create mode 100644 protocols/WhatsApp/src/entities.h create mode 100644 protocols/WhatsApp/src/main.cpp create mode 100644 protocols/WhatsApp/src/messages.cpp create mode 100644 protocols/WhatsApp/src/proto.cpp create mode 100644 protocols/WhatsApp/src/proto.h create mode 100644 protocols/WhatsApp/src/resource.h create mode 100644 protocols/WhatsApp/src/theme.cpp create mode 100644 protocols/WhatsApp/src/theme.h create mode 100644 protocols/WhatsApp/src/utils.cpp create mode 100644 protocols/WhatsApp/src/utils.h create mode 100644 protocols/WhatsApp/src/version.h (limited to 'protocols/WhatsApp') diff --git a/protocols/WhatsApp/WhatsApp_10.vcxproj b/protocols/WhatsApp/WhatsApp_10.vcxproj new file mode 100644 index 0000000000..2d7c6756f0 --- /dev/null +++ b/protocols/WhatsApp/WhatsApp_10.vcxproj @@ -0,0 +1,149 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {13E796AD-BEA4-4213-A1B8-E18E2397E544} + Win32Proj + WhatsApp + WhatsApp + + + + DynamicLibrary + true + Unicode + + + DynamicLibrary + false + true + Unicode + + + + + + + + + + + + + true + $(SolutionDir)$(Configuration)\Plugins\ + $(SolutionDir)$(Configuration)\Obj\$(ProjectName)\ + + + false + $(SolutionDir)$(Configuration)\Plugins\ + $(SolutionDir)$(Configuration)\Obj\$(ProjectName)\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;WHATSAPP_EXPORTS;%(PreprocessorDefinitions) + ../../include;../../plugins/ExternalAPI;C:\Daten\Projekte\2013\dev\WhatsNG\meta\openssl-1.0.1e\inc32;%(AdditionalIncludeDirectories) + + + Windows + true + $(OutDir)WhatsApp.dll + $(ProfileDir)..\..\bin10\lib;C:\Daten\Projekte\2013\dev\WhatsNG\meta\openssl-1.0.1e\out32 + ws2_32.lib;libeay32.lib;ssleay32.lib;mir_core.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(IntDir)$(TargetName).lib + + + + + Level3 + Use + MaxSpeed + true + false + WIN32;NDEBUG;_WINDOWS;_USRDLL;WHATSAPP_EXPORTS;%(PreprocessorDefinitions) + ../../include;../../plugins/ExternalAPI;C:\Daten\Projekte\2013\dev\WhatsNG\meta\openssl-1.0.1e\inc32;%(AdditionalIncludeDirectories) + Size + + + Windows + true + true + true + $(OutDir)WhatsApp.dll + $(ProfileDir)..\..\bin10\lib;C:\Daten\Projekte\2013\dev\WhatsNG\meta\openssl-1.0.1e\out32 + ws2_32.lib;libeay32.lib;ssleay32.lib;mir_core.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(IntDir)$(TargetName).lib + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/protocols/WhatsApp/WhatsApp_10.vcxproj.filters b/protocols/WhatsApp/WhatsApp_10.vcxproj.filters new file mode 100644 index 0000000000..f797d62e8c --- /dev/null +++ b/protocols/WhatsApp/WhatsApp_10.vcxproj.filters @@ -0,0 +1,184 @@ + + + + + {5474a2fc-7d36-4e0d-af1a-702c1c615e0f} + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {12ef3068-b7c1-4b48-a868-98a846fad1cd} + + + {e4575657-22c5-4cad-a1ab-15b422682e8f} + h;hpp;hxx;hm;inl;inc;xsd + + + {294e7046-0757-4b99-8855-c8d2864c6303} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + Miranda Plugin\Source Files + + + Miranda Plugin\Source Files + + + Miranda Plugin\Source Files + + + Miranda Plugin\Source Files + + + Miranda Plugin\Source Files + + + Miranda Plugin\Source Files + + + Miranda Plugin\Source Files + + + Miranda Plugin\Source Files + + + Miranda Plugin\Source Files + + + Miranda Plugin\Source Files + + + Miranda Plugin\Source Files + + + WhatsAPI++\Source Files + + + WhatsAPI++\Source Files + + + WhatsAPI++\Source Files + + + WhatsAPI++\Source Files + + + WhatsAPI++\Source Files + + + WhatsAPI++\Source Files + + + WhatsAPI++\Source Files + + + WhatsAPI++\Source Files + + + WhatsAPI++\Source Files + + + WhatsAPI++\Source Files + + + + + Miranda Plugin\Header Files + + + Miranda Plugin\Header Files + + + Miranda Plugin\Header Files + + + Miranda Plugin\Header Files + + + Miranda Plugin\Header Files + + + Miranda Plugin\Header Files + + + Miranda Plugin\Header Files + + + Miranda Plugin\Header Files + + + Miranda Plugin\Header Files + + + Miranda Plugin\Header Files + + + Miranda Plugin\Header Files + + + Miranda Plugin\Header Files + + + WhatsAPI++\Header Files + + + WhatsAPI++\Header Files + + + WhatsAPI++\Header Files + + + WhatsAPI++\Header Files + + + WhatsAPI++\Header Files + + + WhatsAPI++\Header Files + + + WhatsAPI++\Header Files + + + WhatsAPI++\Header Files + + + WhatsAPI++\Header Files + + + WhatsAPI++\Header Files + + + WhatsAPI++\Header Files + + + WhatsAPI++\Header Files + + + WhatsAPI++\Header Files + + + WhatsAPI++\Header Files + + + + + Resources + + + Resources + + + \ No newline at end of file diff --git a/protocols/WhatsApp/proto_whatsapp/Proto_WhatsApp_10.vcxproj b/protocols/WhatsApp/proto_whatsapp/Proto_WhatsApp_10.vcxproj new file mode 100644 index 0000000000..f5e536da5c --- /dev/null +++ b/protocols/WhatsApp/proto_whatsapp/Proto_WhatsApp_10.vcxproj @@ -0,0 +1,94 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {64A2B403-90AF-4CF8-BC69-4E8D33872D64} + Win32Proj + Proto_WhatsApp + Proto_WhatsApp + + + + DynamicLibrary + true + Unicode + + + DynamicLibrary + false + true + Unicode + + + + + + + + + + + + + true + $(SolutionDir)$(Configuration)\Icons\ + $(SolutionDir)$(Configuration)\Obj\$(ProjectName)\ + true + + + false + $(SolutionDir)$(Configuration)\Icons\ + $(SolutionDir)$(Configuration)\Obj\$(ProjectName)\ + true + + + + + + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;PROTO_WHATSAPP_EXPORTS;%(PreprocessorDefinitions) + + + Windows + true + $(SolutionDir)\lib + true + false + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;PROTO_WHATSAPP_EXPORTS;%(PreprocessorDefinitions) + + + Windows + true + true + true + $(SolutionDir)\lib + true + false + + + + + + + + \ No newline at end of file diff --git a/protocols/WhatsApp/proto_whatsapp/Proto_WhatsApp_10.vcxproj.filters b/protocols/WhatsApp/proto_whatsapp/Proto_WhatsApp_10.vcxproj.filters new file mode 100644 index 0000000000..cd2635f365 --- /dev/null +++ b/protocols/WhatsApp/proto_whatsapp/Proto_WhatsApp_10.vcxproj.filters @@ -0,0 +1,17 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + \ No newline at end of file diff --git a/protocols/WhatsApp/proto_whatsapp/res/Away.ico b/protocols/WhatsApp/proto_whatsapp/res/Away.ico new file mode 100644 index 0000000000..26308a1897 Binary files /dev/null and b/protocols/WhatsApp/proto_whatsapp/res/Away.ico differ diff --git a/protocols/WhatsApp/proto_whatsapp/res/Invisible.ico b/protocols/WhatsApp/proto_whatsapp/res/Invisible.ico new file mode 100644 index 0000000000..1d20baa09b Binary files /dev/null and b/protocols/WhatsApp/proto_whatsapp/res/Invisible.ico differ diff --git a/protocols/WhatsApp/proto_whatsapp/res/Offline.ico b/protocols/WhatsApp/proto_whatsapp/res/Offline.ico new file mode 100644 index 0000000000..ca4d972eaf Binary files /dev/null and b/protocols/WhatsApp/proto_whatsapp/res/Offline.ico differ diff --git a/protocols/WhatsApp/proto_whatsapp/res/Online.ico b/protocols/WhatsApp/proto_whatsapp/res/Online.ico new file mode 100644 index 0000000000..4c26cad778 Binary files /dev/null and b/protocols/WhatsApp/proto_whatsapp/res/Online.ico differ diff --git a/protocols/WhatsApp/proto_whatsapp/res/Proto_WhatsApp.rc b/protocols/WhatsApp/proto_whatsapp/res/Proto_WhatsApp.rc new file mode 100644 index 0000000000..798b72124e Binary files /dev/null and b/protocols/WhatsApp/proto_whatsapp/res/Proto_WhatsApp.rc differ diff --git a/protocols/WhatsApp/proto_whatsapp/src/resource.h b/protocols/WhatsApp/proto_whatsapp/src/resource.h new file mode 100644 index 0000000000..3512640a59 Binary files /dev/null and b/protocols/WhatsApp/proto_whatsapp/src/resource.h differ diff --git a/protocols/WhatsApp/res/add-group.ico b/protocols/WhatsApp/res/add-group.ico new file mode 100644 index 0000000000..65d0d1f41b Binary files /dev/null and b/protocols/WhatsApp/res/add-group.ico differ diff --git a/protocols/WhatsApp/res/add-user-to-group.ico b/protocols/WhatsApp/res/add-user-to-group.ico new file mode 100644 index 0000000000..d99febc5a5 Binary files /dev/null and b/protocols/WhatsApp/res/add-user-to-group.ico differ diff --git a/protocols/WhatsApp/res/change-group-subject.ico b/protocols/WhatsApp/res/change-group-subject.ico new file mode 100644 index 0000000000..6a50c7581e Binary files /dev/null and b/protocols/WhatsApp/res/change-group-subject.ico differ diff --git a/protocols/WhatsApp/res/leave-group.ico b/protocols/WhatsApp/res/leave-group.ico new file mode 100644 index 0000000000..1e7ea0d9db Binary files /dev/null and b/protocols/WhatsApp/res/leave-group.ico differ diff --git a/protocols/WhatsApp/res/remove-user-from-group.ico b/protocols/WhatsApp/res/remove-user-from-group.ico new file mode 100644 index 0000000000..d9a30dbda1 Binary files /dev/null and b/protocols/WhatsApp/res/remove-user-from-group.ico differ diff --git a/protocols/WhatsApp/res/version.rc b/protocols/WhatsApp/res/version.rc new file mode 100644 index 0000000000..5bfbab4754 --- /dev/null +++ b/protocols/WhatsApp/res/version.rc @@ -0,0 +1,38 @@ +// Microsoft Visual C++ generated resource script. +// +#ifdef APSTUDIO_INVOKED +#error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + +#include "afxres.h" +#include "..\src\version.h" + +VS_VERSION_INFO VERSIONINFO + FILEVERSION __FILEVERSION_STRING + PRODUCTVERSION __FILEVERSION_STRING + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x0L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "000004b0" + BEGIN + VALUE "FileDescription", __DESCRIPTION + VALUE "InternalName", __PLUGIN_NAME + VALUE "LegalCopyright", __COPYRIGHT + VALUE "OriginalFilename", __FILENAME + VALUE "ProductName", __PLUGIN_NAME + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0, 1200 + END +END diff --git a/protocols/WhatsApp/res/whatsapp.ico b/protocols/WhatsApp/res/whatsapp.ico new file mode 100644 index 0000000000..2cdd32009a Binary files /dev/null and b/protocols/WhatsApp/res/whatsapp.ico differ diff --git a/protocols/WhatsApp/res/whatsapp.rc b/protocols/WhatsApp/res/whatsapp.rc new file mode 100644 index 0000000000..486310794c Binary files /dev/null and b/protocols/WhatsApp/res/whatsapp.rc differ diff --git a/protocols/WhatsApp/src/WASocketConnection.cpp b/protocols/WhatsApp/src/WASocketConnection.cpp new file mode 100644 index 0000000000..f4e6ef8ae4 --- /dev/null +++ b/protocols/WhatsApp/src/WASocketConnection.cpp @@ -0,0 +1,138 @@ +#include "WASocketConnection.h" + + +HANDLE WASocketConnection::hNetlibUser = NULL; + +void WASocketConnection::initNetwork(HANDLE hNetlibUser) throw (WAException) { + WASocketConnection::hNetlibUser = hNetlibUser; +} + +void WASocketConnection::quitNetwork() { +} + +WASocketConnection::WASocketConnection(const std::string& dir, int port) throw (WAException) +{ + NETLIBOPENCONNECTION noc = {sizeof(noc)}; + noc.szHost = dir.c_str(); + noc.wPort = port; + noc.flags = NLOCF_V2; // | NLOCF_SSL; + this->hConn = (HANDLE) CallService(MS_NETLIB_OPENCONNECTION, reinterpret_cast(this->hNetlibUser), + reinterpret_cast(&noc)); + if (this->hConn == NULL) + { + throw WAException(getLastErrorMsg(), WAException::SOCKET_EX, WAException::SOCKET_EX_OPEN); + } + + this->connected = true; +} + +void WASocketConnection::write(int i) { + char buffer; + buffer = (char) i; + + NETLIBBUFFER nlb; + nlb.buf = &buffer; + nlb.len = 1; + nlb.flags = MSG_NOHTTPGATEWAYWRAP | MSG_NODUMP; + + int result = CallService(MS_NETLIB_SEND, reinterpret_cast(this->hConn), reinterpret_cast(&nlb)); + if (result < 1) { + throw WAException(getLastErrorMsg(), WAException::SOCKET_EX, WAException::SOCKET_EX_SEND); + } +} + +void WASocketConnection::makeNonBlock() { + //if (fcntl(socket->channel, F_SETFL, O_NONBLOCK) == -1) // #TODO !? + throw WAException("Error setting socket nonblocking!", WAException::SOCKET_EX, WAException::SOCKET_EX_OPEN); +} + +int WASocketConnection::waitForRead() { + // #TODO Is this called at all? + return 0; + + fd_set rfds; + struct timeval tv; + struct timeval* tvp; + int fd = 0; + + FD_ZERO(&rfds); + // _LOGDATA("preparando select"); + //fd = (this->socket)->channel; //#!? + // _LOGDATA("socket %d", fd); + FD_SET(fd, &rfds); + tv.tv_sec = 600; //ApplicationData::SELECT_TIMEOUT; + tv.tv_usec = 0; // 5000000; + tvp = &tv; + //if (ApplicationData::SELECT_TIMEOUT == -1) #TODO + // tvp = NULL; + + int retval = select(/*fd + 1*/ 0, &rfds, NULL, NULL, tvp); + if (!FD_ISSET(fd, &rfds)) + retval = 0; + + return retval; +} + +void WASocketConnection::flush() {} + +void WASocketConnection::write(const std::vector& bytes, int offset, int length) +{ + NETLIBBUFFER nlb; + std::string tmpBuf = std::string(bytes.begin(), bytes.end()); + nlb.buf = (char*) &(tmpBuf.c_str()[offset]); + nlb.len = length; + nlb.flags = 0; //MSG_NOHTTPGATEWAYWRAP | MSG_NODUMP; + + int result = CallService(MS_NETLIB_SEND, reinterpret_cast(this->hConn), + reinterpret_cast(&nlb)); + if (result < length) { + throw WAException(getLastErrorMsg(), WAException::SOCKET_EX, WAException::SOCKET_EX_SEND); + } +} + +void WASocketConnection::write(const std::vector& bytes, int length) +{ + this->write(bytes, 0, length); +} + +unsigned char WASocketConnection::read() { + char c; + + SetLastError(0); + int result; + //do { + result = Netlib_Recv(this->hConn, &c, 1, 0 /*MSG_NOHTTPGATEWAYWRAP | MSG_NODUMP*/); + //} while (WSAGetLastError() == EINTR); + if (result <= 0) { + throw WAException(getLastErrorMsg(), WAException::SOCKET_EX, WAException::SOCKET_EX_RECV); + } + return c; +} + +int WASocketConnection::read(std::vector& b, int off, int length) { + if (off < 0 || length < 0) { + throw new WAException("Out of bounds", WAException::SOCKET_EX, WAException::SOCKET_EX_RECV); + } + char* buffer = new char[length]; + int result = Netlib_Recv(this->hConn, buffer, length, MSG_NOHTTPGATEWAYWRAP | MSG_NODUMP); + + if (result <= 0) { + throw WAException(getLastErrorMsg(), WAException::SOCKET_EX, WAException::SOCKET_EX_RECV); + } + + for (int i = 0; i < result; i++) + b[off + i] = buffer[i]; + + delete[] buffer; + + return result; +} + +void WASocketConnection::forceShutdown() { + Netlib_Shutdown(this->hConn); +} + +WASocketConnection::~WASocketConnection() { + this->forceShutdown(); + //SDLNet_TCP_Close(this->socket); +} \ No newline at end of file diff --git a/protocols/WhatsApp/src/WASocketConnection.h b/protocols/WhatsApp/src/WASocketConnection.h new file mode 100644 index 0000000000..f2d46415bc --- /dev/null +++ b/protocols/WhatsApp/src/WASocketConnection.h @@ -0,0 +1,41 @@ +#if !defined(WASOCKETCONNECTION_H) +#define WASOCKETCONNECTION_H + +#include "common.h" +#include "WhatsAPI++/ISocketConnection.h" +#include +#include "WhatsAPI++/WAException.h" +#include "WhatsAPI++/WALogin.h" +#include "WhatsAPI++/base64.h" +#include + +class WASocketConnection : public ISocketConnection +{ +public: + static HANDLE hNetlibUser; + +private: + int readSize; + int maxBufRead; + bool connected; + + HANDLE hConn; + +public: + WASocketConnection(const std::string& dir, int port) throw (WAException); + void write(int i); + unsigned char read(); + void flush(); + void write(const std::vector& b, int length); + void write(const std::vector& bytes, int offset, int length); + int read(std::vector& b, int off, int length); + void makeNonBlock(); + int waitForRead(); + void forceShutdown(); + + virtual ~WASocketConnection(); + static void initNetwork(HANDLE hNetlibUser) throw (WAException); + static void quitNetwork(); +}; + +#endif // WASOCKETCONNECTION_H \ No newline at end of file diff --git a/protocols/WhatsApp/src/WhatsAPI++/BinTreeNodeReader.cpp b/protocols/WhatsApp/src/WhatsAPI++/BinTreeNodeReader.cpp new file mode 100644 index 0000000000..7155334c7f --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/BinTreeNodeReader.cpp @@ -0,0 +1,349 @@ +/* + * BinTreeNodeReader.cpp + * + * Created on: 26/06/2012 + * Author: Antonio + */ +#include "stdafx.h" +#include "BinTreeNodeReader.h" +#include "WAException.h" +#include "ProtocolTreeNode.h" +#include "utilities.h" +#include + + + +BinTreeNodeReader::BinTreeNodeReader(WAConnection* conn, ISocketConnection* connection, const char** dictionary, const int dictionarysize) { + this->conn = conn; + this->rawIn = connection; + this->tokenMap = dictionary; + this->tokenmapsize = dictionarysize; + this->readSize = 1; + this->in = NULL; + this->buf = new std::vector(BUFFER_SIZE); +} + +BinTreeNodeReader::~BinTreeNodeReader() { + if (this->buf != NULL) + delete this->buf; + if (this->in != NULL) + delete this->in; +} + +ProtocolTreeNode* BinTreeNodeReader::nextTreeInternal() { + int b = this->in->read(); + int size = readListSize(b); + b = this->in->read(); + if (b == 2) + return NULL; + + std::string* tag = this->readStringAsString(b); + + if ((size == 0) || (tag == NULL)) + throw WAException("nextTree sees 0 list or null tag", WAException::CORRUPT_STREAM_EX, -1); + int attribCount = (size - 2 + size % 2) / 2; + std::map* attribs = readAttributes(attribCount); + if (size % 2 == 1) { + ProtocolTreeNode* ret = new ProtocolTreeNode(*tag, attribs); + delete tag; + return ret; + } + b = this->in->read(); + if (isListTag(b)) { + ProtocolTreeNode* ret = new ProtocolTreeNode(*tag, attribs, NULL, readList(b)); + delete tag; + return ret; + } + + ReadData* obj = this->readString(b); + std::vector* data; + if (obj->type == STRING) { + std::string* s = (std::string*) obj->data; + data = new std::vector(s->begin(), s->end()); + delete s; + } else { + data = (std::vector*) obj->data; + } + + ProtocolTreeNode* ret = new ProtocolTreeNode(*tag, attribs, data); + delete obj; + delete tag; + return ret; +} + +bool BinTreeNodeReader::isListTag(int b) { + return (b == 248) || (b == 0) || (b == 249); +} + +void BinTreeNodeReader::decodeStream(int flags, int offset, int length) { + if ((flags & 8) != 0) { + if (length < 4) { + throw WAException("invalid length" + length, WAException::CORRUPT_STREAM_EX, 0); + } + offset += 4; + length -= 4; + this->conn->inputKey->decodeMessage(&(*this->buf)[0], offset - 4, offset, length); + } + if (this->in != NULL) + delete this->in; + this->in = new ByteArrayInputStream(this->buf, offset, length); +} + +std::map* BinTreeNodeReader::readAttributes(int attribCount) { + std::map* attribs = new std::map(); + for (int i = 0; i < attribCount; i++) { + std::string* key = readStringAsString(); + std::string* value = readStringAsString(); + (*attribs)[*key] = *value; + delete key; + delete value; + } + return attribs; +} + +std::vector* BinTreeNodeReader::readList(int token) { + int size = readListSize(token); + std::vector* list = new std::vector(size); + for (int i = 0; i < size; i++) { + (*list)[i] = nextTreeInternal(); + } + + return list; +} + +int BinTreeNodeReader::readListSize(int token) { + int size; + if (token == 0) { + size = 0; + } + else { + size = 0; + if (token == 248) { + size = readInt8(this->in); + } + else + { + size = 0; + if (token == 249) + size = readInt16(this->in); + else + throw new WAException("invalid list size in readListSize: token " + token, WAException::CORRUPT_STREAM_EX, 0); + } + } + + return size; +} + +std::vector* BinTreeNodeReader::readList() { + return readList(this->in->read()); +} + +ReadData* BinTreeNodeReader::readString() { + return readString(this->in->read()); +} + +ReadData* BinTreeNodeReader::readString(int token) { + if (token == -1) { + throw WAException("-1 token in readString", WAException::CORRUPT_STREAM_EX, -1); + } + + ReadData* ret = new ReadData(); + + if ((token > 4) && (token < 245)) { + ret->type = STRING; + ret->data = new std::string(getToken(token)); + return ret; + } + + switch(token) { + case 0: + return NULL; + case 252: { + int size8 = readInt8(this->in); + std::vector* buf8 = new std::vector(size8); + fillArray(*buf8, size8, this->in); + // std::string* ret = new std::string(buf8->begin(), buf8->end()); + // delete buf8; + ret->type = ARRAY; + ret->data = buf8; + return ret; + } + case 253: { + int size24 = readInt24(this->in); + std::vector* buf24 = new std::vector(size24); + fillArray(*buf24, size24, this->in); + // std::string* ret = new std::string(buf24->begin(), buf24->end()); + // delete buf24; + ret->type = ARRAY; + ret->data = buf24; + + return ret; + } + case 254: { + token = (unsigned char) this->in->read(); + ret->type = STRING; + ret->data = new std::string(getToken(245 + token)); + return ret; + } + case 250: { + std::string* user = readStringAsString(); + std::string* server = readStringAsString(); + if ((user != NULL) && (server != NULL)) { + std::string* result = new std::string(*user + "@" + *server); + delete user; + delete server; + ret->type = STRING; + ret->data = result; + return ret; + } + if (server != NULL) { + ret->type = STRING; + ret->data = server; + return ret; + } + throw WAException("readString couldn't reconstruct jid", WAException::CORRUPT_STREAM_EX, -1); + } + } + throw WAException("readString couldn't match token" + (int) token, WAException::CORRUPT_STREAM_EX, -1); +} + +std::string* BinTreeNodeReader::objectAsString(ReadData* o) { + if (o->type == STRING) { + return (std::string*) o->data; + } + + if (o->type == ARRAY) { + std::vector* v = (std::vector*) o->data; + std::string* ret = new std::string(v->begin(), v->end()); + delete v; + return ret; + } + + return NULL; +} + +std::string* BinTreeNodeReader::readStringAsString() { + ReadData* o = this->readString(); + std::string* ret = this->objectAsString(o); + delete o; + return ret; +} + +std::string* BinTreeNodeReader::readStringAsString(int token) { + ReadData* o = this->readString(token); + std::string* ret = this->objectAsString(o); + delete o; + return ret; +} + + +void BinTreeNodeReader::fillArray(std::vector& buff, int len, ByteArrayInputStream* in) { + int count = 0; + while (count < len) { + count += in->read(buff, count, len - count); + } +} + +void BinTreeNodeReader::fillArray(std::vector& buff, int len, ISocketConnection* in) { + int count = 0; + while (count < len) { + count += in->read(buff, count, len - count); + } +} + + +std::string BinTreeNodeReader::getToken(int token) { + std::string ret; + + if ((token >= 0) && (token < this->tokenmapsize)) + ret = std::string(this->tokenMap[token]); + if (ret.empty()) { + throw WAException("invalid token/length in getToken", WAException::CORRUPT_STREAM_EX, 0); + } + return ret; +} + + +void BinTreeNodeReader::getTopLevelStream() { + int stanzaSize; + int flags; + int byte = readInt8(this->rawIn); + flags = byte >> 4; + int size0 = byte & 15; + int size1 = readInt8(this->rawIn); + int size2 = readInt8(this->rawIn); + + stanzaSize = (size0 << 16) + (size1 << 8) + size2; + + if (this->buf->size() < (size_t) stanzaSize) { + int newsize = std::max((int) (this->buf->size() * 3 / 2), stanzaSize); + delete this->buf; + this->buf = new std::vector(newsize); + } + fillArray(*this->buf, stanzaSize, this->rawIn); + + this->decodeStream(flags, 0, stanzaSize); +} + +int BinTreeNodeReader::readInt8(ByteArrayInputStream* in) { + return in->read(); +} + +int BinTreeNodeReader::readInt16(ByteArrayInputStream* in) { + int intTop = in->read(); + int intBot = in->read(); + int value = (intTop << 8) + intBot; + return value; +} + +int BinTreeNodeReader::readInt24(ByteArrayInputStream* in) { + int int1 = in->read(); + int int2 = in->read(); + int int3 = in->read(); + int value = (int1 << 16) + (int2 << 8) + int3; + + return value; +} + +ProtocolTreeNode* BinTreeNodeReader::nextTree() { + this->getTopLevelStream(); + return nextTreeInternal(); +} + +void BinTreeNodeReader::streamStart() { + this->getTopLevelStream(); + + int tag = this->in->read(); + int size = readListSize(tag); + tag = this->in->read(); + if (tag != 1) { + throw WAException("expecting STREAM_START in streamStart", WAException::CORRUPT_STREAM_EX, 0); + } + int attribCount = (size - 2 + size % 2) / 2; + + std::map* attributes = readAttributes(attribCount); + delete attributes; +} + +int BinTreeNodeReader::readInt8(ISocketConnection* in) { + return in->read(); +} + +int BinTreeNodeReader::readInt16(ISocketConnection* in) { + int intTop = in->read(); + int intBot = in->read(); + int value = (intTop << 8) + intBot; + return value; +} + +int BinTreeNodeReader::readInt24(ISocketConnection* in) { + int int1 = in->read(); + int int2 = in->read(); + int int3 = in->read(); + int value = (int1 << 16) + (int2 << 8) + int3; + + return value; +} + + + diff --git a/protocols/WhatsApp/src/WhatsAPI++/BinTreeNodeReader.h b/protocols/WhatsApp/src/WhatsAPI++/BinTreeNodeReader.h new file mode 100644 index 0000000000..61b70b218b --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/BinTreeNodeReader.h @@ -0,0 +1,76 @@ +/* + * BinTreeNodeReader.h + * + * Created on: 26/06/2012 + * Author: Antonio + */ +#ifndef BINTREENODEREADER_H_ +#define BINTREENODEREADER_H_ + +#include "ProtocolTreeNode.h" +#include "ISocketConnection.h" +#include "ByteArray.h" +#include "WAConnection.h" +#include +#include +#include + +#define BUFFER_SIZE 512 + +class WAConnection; + +enum ReadType {STRING, ARRAY}; + +class ReadData { +public: + ReadData() {}; + virtual ~ReadData() {}; + + ReadType type; + void * data; +}; + +class BinTreeNodeReader { +private: + const char** tokenMap; + int tokenmapsize; + ISocketConnection *rawIn; + ByteArrayInputStream* in; + std::vector* buf; + int readSize; + WAConnection* conn; + + ProtocolTreeNode* nextTreeInternal(); + bool isListTag(int b); + void decodeStream(int flags, int offset, int length); + std::map* readAttributes(int attribCount); + std::vector* readList(int token); + int readListSize(int token); + std::vector* readList(); + ReadData* readString(); + ReadData* readString(int token); + static void fillArray(std::vector& buff, int len, ByteArrayInputStream* in); + static void fillArray(std::vector& buff, int len, ISocketConnection* in); + std::string* objectAsString(ReadData* o); + std::string* readStringAsString(); + std::string* readStringAsString(int token); + std::string getToken(int token); + void getTopLevelStream(); + static int readInt8(ByteArrayInputStream* in); + static int readInt8(ISocketConnection* in); + static int readInt16(ByteArrayInputStream* in); + static int readInt16(ISocketConnection* in); + static int readInt24(ByteArrayInputStream* in); + static int readInt24(ISocketConnection* in); + + +public: + BinTreeNodeReader(WAConnection* conn, ISocketConnection* connection, const char** dictionary, const int dictionarysize); + virtual ~BinTreeNodeReader(); + ProtocolTreeNode* nextTree(); + void streamStart(); + +}; + +#endif /* BINTREENODEREADER_H_ */ + diff --git a/protocols/WhatsApp/src/WhatsAPI++/BinTreeNodeWriter.cpp b/protocols/WhatsApp/src/WhatsAPI++/BinTreeNodeWriter.cpp new file mode 100644 index 0000000000..dd35f6a8e7 --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/BinTreeNodeWriter.cpp @@ -0,0 +1,267 @@ +/* + * BinTreeNodeWriter.cpp + * + * Created on: 26/06/2012 + * Author: Antonio + */ +#include "stdafx.h" +#include "BinTreeNodeWriter.h" +#include +#include "utilities.h" + +BinTreeNodeWriter::BinTreeNodeWriter(WAConnection* conn, ISocketConnection* connection, + const char** dictionary, const int dictionarysize, IMutex* mutex) { + this->mutex = mutex; + this->conn = conn; + this->out = new ByteArrayOutputStream(2048); + this->realOut = connection; + for (int i = 0; i < dictionarysize; i++) { + std::string token(dictionary[i]); + if (token.compare("") != 0) + this->tokenMap[token] = i; + } + this->dataBegin = 0; +} + +void BinTreeNodeWriter::writeDummyHeader() +{ + int num = 3; + this->dataBegin = this->out->getPosition(); + int num2 = this->dataBegin + num; + this->out->setLength(num2); + this->out->setPosition(num2); +} + + +void BinTreeNodeWriter::processBuffer() +{ + bool flag = this->conn->outputKey != NULL; + unsigned int num = 0u; + if (flag) + { + long num2 = this->out->getLength() + 4L; + this->out->setLength(num2); + this->out->setPosition(num2); + num |= 1u; + } + long num3 = this->out->getLength() - 3L - (long) this->dataBegin; + if (num3 >= 1048576L) + { + throw WAException("Buffer too large: " + num3, WAException::CORRUPT_STREAM_EX, 0); + } + + std::vector* buffer = this->out->getBuffer(); + if (flag) + { + int num4 = (int)num3 - 4; + this->conn->outputKey->encodeMessage(buffer->data(), this->dataBegin + 3 + num4, this->dataBegin + 3, num4); + } + (*buffer)[this->dataBegin] = (unsigned char)((unsigned long)((unsigned long)num << 4) | (unsigned long)((num3 & 16711680L) >> 16)); + (*buffer)[this->dataBegin + 1] = (unsigned char)((num3 & 65280L) >> 8); + (*buffer)[this->dataBegin + 2] = (unsigned char)(num3 & 255L); +} + +void BinTreeNodeWriter::streamStart(std::string domain, std::string resource) { + this->mutex->lock(); + try { + this->out->setPosition(0); + this->out->setLength(0); + this->out->write(87); + this->out->write(65); + this->out->write(1); + this->out->write(2); + + std::map attributes; + attributes["to"] = domain; + attributes["resource"] = resource; + this->writeDummyHeader(); + this->writeListStart(attributes.size() * 2 + 1); + this->out->write(1); + this->writeAttributes(&attributes); + this->processBuffer(); + this->flushBuffer(true, 0); + } catch (exception& ex) { + this->mutex->unlock(); + throw ex; + } + this->mutex->unlock(); +} + +void BinTreeNodeWriter::writeListStart(int i) { + if (i == 0) { + this->out->write(0); + } else if (i < 256) { + this->out->write(248); + writeInt8(i); + } else { + this->out->write(249); + writeInt16(i); + } +} + +void BinTreeNodeWriter::writeInt8(int v) { + this->out->write(v & 0xFF); +} + +void BinTreeNodeWriter::writeInt16(int v, ISocketConnection* o) { + o->write((v & 0xFF00) >> 8); + o->write((v & 0xFF) >> 0); +} + +void BinTreeNodeWriter::writeInt16(int v) { + writeInt16(v, this->out); +} + +void BinTreeNodeWriter::writeInt16(int v, ByteArrayOutputStream* o) { + o->write((v & 0xFF00) >> 8); + o->write((v & 0xFF) >> 0); +} + +void BinTreeNodeWriter::writeAttributes(std::map* attributes) { + if (attributes != NULL) { + std::map::iterator ii; + for (ii = attributes->begin(); ii != attributes->end(); ii++) { + writeString(ii->first); + writeString(ii->second); + } + } +} + +void BinTreeNodeWriter::writeString(const std::string& tag) { + std::map::iterator it = this->tokenMap.find(tag); + if (it != this->tokenMap.end()) { + writeToken(it->second); + } else { + unsigned int atIndex = tag.find('@'); + if (atIndex == 0 || atIndex == string::npos) { + writeBytes((unsigned char*) tag.data(), tag.length()); + } else { + std::string server = tag.substr(atIndex + 1); + std::string user = tag.substr(0, atIndex); + writeJid(&user, server); + } + } +} + +void BinTreeNodeWriter::writeJid(std::string* user, const std::string& server) { + this->out->write(250); + if (user != NULL && !user->empty()) { + writeString(*user); + } else { + writeToken(0); + } + writeString(server); + +} + +void BinTreeNodeWriter::writeToken(int intValue) { + if (intValue < 245) + this->out->write(intValue); + else if (intValue <= 500) { + this->out->write(254); + this->out->write(intValue - 245); + } +} + +void BinTreeNodeWriter::writeBytes(unsigned char* bytes, int length) { + if (length >= 256) { + this->out->write(253); + writeInt24(length); + } else { + this->out->write(252); + writeInt8(length); + } + this->out->write(bytes, length); +} + +void BinTreeNodeWriter::writeInt24(int v) { + this->out->write((v & 0xFF0000) >> 16); + this->out->write((v & 0xFF00) >> 8); + this->out->write(v & 0xFF); +} + +void BinTreeNodeWriter::writeInternal(ProtocolTreeNode* node) { + writeListStart( + 1 + (node->attributes == NULL ? 0 : node->attributes->size() * 2) + + (node->children == NULL ? 0 : 1) + + (node->data == NULL ? 0 : 1)); + writeString(node->tag); + writeAttributes(node->attributes); + if (node->data != NULL) { + writeBytes((unsigned char*) node->data->data(), node->data->size()); + } + if (node->children != NULL && !node->children->empty()) { + writeListStart(node->children->size()); + for (size_t a = 0; a < node->children->size(); a++) { + writeInternal((*node->children)[a]); + } + } +} + +void BinTreeNodeWriter::flushBuffer(bool flushNetwork) +{ + this->flushBuffer(flushNetwork, this->dataBegin); +} + +void BinTreeNodeWriter::flushBuffer(bool flushNetwork, int startingOffset) { + try { + this->processBuffer(); + } catch (WAException& ex) { + this->out->setPosition(0); + this->out->setLength(0); + throw ex; + } + + // _LOGDATA("buffer size %d, buffer position %d, dataBegin %d", this->out->getLength(), this->out->getPosition(), this->dataBegin); + + std::vector buffer(this->out->getBuffer()->begin(), this->out->getBuffer()->end()); + int num = (int)(this->out->getLength() - (long)startingOffset); + if (flushNetwork && ((long)this->out->getCapacity() - this->out->getLength() < 3L || this->out->getLength() > 4096L)) + { + delete this->out; + this->out = new ByteArrayOutputStream(4096); + } + + if (flushNetwork) + this->realOut->write(buffer, startingOffset, num); +} + +void BinTreeNodeWriter::streamEnd() { + this->mutex->lock(); + try { + writeListStart(1); + this->out->write(2); + flushBuffer(true); + } catch (exception& ex) { + this->mutex->unlock(); + throw ex; + } + this->mutex->unlock(); +} + +void BinTreeNodeWriter::write(ProtocolTreeNode* node) { + write(node, true); +} + +void BinTreeNodeWriter::write(ProtocolTreeNode* node, bool needsFlush) { + this->mutex->lock(); + try { + this->writeDummyHeader(); + if (node == NULL) + this->out->write(0); + else { + writeInternal(node); + } + flushBuffer(needsFlush); + } catch (exception& ex) { + this->mutex->unlock(); + throw WAException(ex.what()); + } + this->mutex->unlock(); +} + +BinTreeNodeWriter::~BinTreeNodeWriter() { + if (this->out != NULL) + delete this->out; +} + diff --git a/protocols/WhatsApp/src/WhatsAPI++/BinTreeNodeWriter.h b/protocols/WhatsApp/src/WhatsAPI++/BinTreeNodeWriter.h new file mode 100644 index 0000000000..20faaea4e8 --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/BinTreeNodeWriter.h @@ -0,0 +1,71 @@ +/* + * BinTreeNodeWriter.h + * + * Created on: 26/06/2012 + * Author: Antonio + */ + + +#ifndef BINTREENODEWRITER_H_ +#define BINTREENODEWRITER_H_ + +#include +#include "ProtocolTreeNode.h" +#include "ISocketConnection.h" +#include "ByteArray.h" +#include "IMutex.h" +#include "WAConnection.h" + +using namespace std; + +#define STREAM_START 1 +#define STREAM_END 2 +#define LIST_EMPTY 0 +#define LIST_8 248 +#define LIST_16 249 +#define JID_PAIR 250 +#define BINARY_8 252 +#define BINARY_24 253 +#define TOKEN_8 254 + +#include + +class WAConnection; + +class BinTreeNodeWriter { +private: + WAConnection* conn; + map tokenMap; + ISocketConnection *realOut; + ByteArrayOutputStream *out; + IMutex* mutex; + int dataBegin; + + void writeListStart(int i); + void writeInt8(int v); + void writeInt16(int v, ISocketConnection* out); + void writeInt16(int v, ByteArrayOutputStream* out); + void writeInt16(int v); + void writeAttributes(std::map* attributes); + void writeString(const std::string& tag); + void writeJid(std::string* user, const std::string& server); + void writeToken(int intValue); + void writeBytes(unsigned char* bytes, int length); + void writeInt24(int v); + void writeInternal(ProtocolTreeNode* node); + void writeDummyHeader(); + void processBuffer(); + +public: + BinTreeNodeWriter(WAConnection* conn, ISocketConnection* connection, const char** dictionary, const int dictionarysize, IMutex* mutex); + void streamStart(std::string domain, std::string resource); + void flushBuffer(bool flushNetwork); + void flushBuffer(bool flushNetwork, int startingOffset); + void streamEnd(); + void write(ProtocolTreeNode* node); + void write(ProtocolTreeNode* node, bool needsFlush); + + virtual ~BinTreeNodeWriter(); +}; + +#endif /* BINTREENODEWRITER_H_ */ diff --git a/protocols/WhatsApp/src/WhatsAPI++/ByteArray.cpp b/protocols/WhatsApp/src/WhatsAPI++/ByteArray.cpp new file mode 100644 index 0000000000..44354a4e3d --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/ByteArray.cpp @@ -0,0 +1,155 @@ +/* + * ByteArray.cpp + * + * Created on: 26/06/2012 + * Author: Antonio + */ +#include "stdafx.h" +#include "ByteArray.h" +#include "WAException.h" +#include +#include +#include "utilities.h" + +ByteArrayOutputStream::ByteArrayOutputStream(int size) { + this->buf = new std::vector(); + this->buf->reserve(size); + this->position = 0; +} + +void ByteArrayOutputStream::setLength(size_t length) { + this->buf->resize(length); +} + +size_t ByteArrayOutputStream::getLength() { + return this->buf->size(); +} + +size_t ByteArrayOutputStream::getCapacity() { + return this->buf->capacity(); +} + +size_t ByteArrayOutputStream::getPosition() { + return this->position; +} + +void ByteArrayOutputStream::setPosition(size_t count) { + this->position = count; +} + + +std::vector* ByteArrayOutputStream::toByteArray() { + std::vector* array = new std::vector(this->buf->size()); + for (size_t i = 0; i < this->buf->size(); i++) + (*array)[i] = (*this->buf)[i]; + return array; +} + +std::vector* ByteArrayOutputStream::getBuffer() { + return this->buf; +} + +void ByteArrayOutputStream::write(int i) { + if (this->position == this->buf->size()) + this->buf->push_back((unsigned char) i); + else + (*this->buf)[this->position] = (unsigned char) i; + this->position = this->position + 1; +} + +void ByteArrayOutputStream::write(unsigned char* b, size_t len) { + if (len == 0) + return; + + for (size_t i = 0; i < len; i++) + write(b[i]); +} + +void ByteArrayOutputStream::write(const std::string& s) { + for (size_t i = 0; i < s.size(); i++) + write((unsigned char) s[i]); +} + + +ByteArrayOutputStream::~ByteArrayOutputStream() { + delete this->buf; +} + + +ByteArrayInputStream::ByteArrayInputStream(std::vector* buf, size_t off, size_t length ) { + this->buf = buf; + this->pos = off; + this->count = std::min(off + length, buf->size()); +} + +ByteArrayInputStream::ByteArrayInputStream(std::vector* buf) { + this->buf = buf; + this->pos = 0; + this->count = buf->size(); +} + +int ByteArrayInputStream::read() { + return (pos < count) ? ((*this->buf)[pos++]) : -1; +} + +int ByteArrayInputStream::read(std::vector& b, size_t off, size_t len) { + if (len > (b.size() - off)) { + throw new WAException("Index out of bounds"); + } else if (len == 0) { + return 0; + } + + int c = read(); + if (c == -1) { + return -1; + } + b[off] = (unsigned char) c; + + size_t i = 1; + try { + for (; i < len ; i++) { + c = read(); + if (c == -1) { + break; + } + b[off + i] = (unsigned char) c; + } + } catch (std::exception& ee) { + } + return i; +} + +ByteArrayInputStream::~ByteArrayInputStream() { +} + +void ByteArrayInputStream::print() { + std::cout << "["; + for (size_t i = 0; i < this->count; i++) { + std::cout << (*this->buf)[i] << " "; + } + std::cout << std::endl; + for (size_t i = 0; i < this->count; i++) { + std::cout << (int) ((signed char) (*this->buf)[i]) << " "; + } + std::cout << "]" << std::endl; +} + +void ByteArrayOutputStream::print() { + _LOGDATA("["); + + std::string chars(this->buf->begin(), this->buf->end()); + _LOGDATA("%s ", chars.c_str()); + + std::string numbers = ""; + for (size_t i = 0; i < this->buf->size(); i++) { + numbers += Utilities::intToStr((int) ((signed char) (*this->buf)[i])) + " "; + } + _LOGDATA("%s", numbers.c_str()); + _LOGDATA("]"); +} + + + + + + diff --git a/protocols/WhatsApp/src/WhatsAPI++/ByteArray.h b/protocols/WhatsApp/src/WhatsAPI++/ByteArray.h new file mode 100644 index 0000000000..57ac503020 --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/ByteArray.h @@ -0,0 +1,55 @@ +/* + * ByteArray.h + * + * Created on: 26/06/2012 + * Author: Antonio + */ + + + +#ifndef BYTEARRAY_H_ +#define BYTEARRAY_H_ + +#include +#include + +class ByteArrayOutputStream { +protected: + std::vector* buf; + size_t position; + +public: + ByteArrayOutputStream(int size = 32); + std::vector* toByteArray(); + std::vector* getBuffer(); + size_t getPosition(); + void setPosition(size_t count); + void write(int i); + void write(unsigned char* c, size_t length); + void write(const std::string& s); + void print(); + void setLength(size_t length); + size_t getLength(); + size_t getCapacity(); + + virtual ~ByteArrayOutputStream(); +}; + +class ByteArrayInputStream { +protected: + std::vector* buf; + size_t pos; + size_t mark; + size_t count; + +public: + ByteArrayInputStream(std::vector* buf, size_t off, size_t length ); + ByteArrayInputStream(std::vector* buf); + int read(); + int read(std::vector& b, size_t off, size_t length); + void print(); + + virtual ~ByteArrayInputStream(); +}; + +#endif /* BYTEARRAY_H_ */ diff --git a/protocols/WhatsApp/src/WhatsAPI++/FMessage.cpp b/protocols/WhatsApp/src/WhatsAPI++/FMessage.cpp new file mode 100644 index 0000000000..e50d409ea9 --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/FMessage.cpp @@ -0,0 +1,128 @@ +/* + * FMessage.cpp + * + * Created on: 02/07/2012 + * Author: Antonio + */ +#include "stdafx.h" +#include +#include +#include +#include "FMessage.h" +#include "utilities.h" + +//SDL_mutex* FMessage::generating_lock = SDL_CreateMutex(); +IMutex* FMessage::generating_lock = NULL; +int FMessage::generating_id = 0; +std::string FMessage::generating_header = Utilities::intToStr(static_cast (time(NULL))).append("-"); + + +FMessage::FMessage() { + this->key = NULL; + this->timestamp = 0; + this->media_wa_type = 0; + this->longitude = 0; + this->latitude = 0; + this->media_duration_seconds = 0; + this->media_size = 0; + this->media_name = ""; + this->media_url = ""; + this->data = ""; +} + +FMessage::FMessage(const std::string& remote_jid, bool from_me, const std::string& data) { + Key* local_key; + FMessage::generating_lock->lock(); + FMessage::generating_id++; + local_key = new Key(remote_jid, from_me, generating_header + Utilities::intToStr(generating_id)); + this->key = local_key; + FMessage::generating_lock->unlock(); + this->data = data; + this->timestamp = time(NULL); + this->media_wa_type = 0; + this->longitude = 0; + this->latitude = 0; + this->media_duration_seconds = 0; + this->media_size = 0; + this->media_name = ""; + this->media_url = ""; +} + +std::string FMessage::nextKeyIdNumber() { + int id = 0; + FMessage::generating_lock->lock(); + id = (FMessage::generating_id++); + FMessage::generating_lock->unlock(); + return generating_header + (Utilities::intToStr(id)); +} + +FMessage::FMessage(Key* key) { + this->key = key; + this->timestamp = 0; + this->media_wa_type = 0; + this->longitude = 0; + this->latitude = 0; + this->media_duration_seconds = 0; + this->media_size = 0; + this->media_name = ""; + this->media_url = ""; +} + +std::string FMessage::getMessage_WA_Type_StrValue(unsigned char type) { + switch (type) { + case FMessage::WA_TYPE_UNDEFINED: + return ""; + case FMessage::WA_TYPE_SYSTEM: + return "system"; + case FMessage::WA_TYPE_AUDIO: + return "audio"; + case FMessage::WA_TYPE_CONTACT: + return "vcard"; + case FMessage::WA_TYPE_IMAGE: + return "image"; + case FMessage::WA_TYPE_LOCATION: + return "location"; + case FMessage::WA_TYPE_VIDEO: + return "video"; + } + + return ""; +} + +FMessage::~FMessage() { + if (this->key != NULL) + delete key; +} + +Key::Key(const std::string& remote_jid, bool from_me, const std::string& id) { + this->remote_jid = remote_jid; + this->from_me = from_me; + this->id = id; +} + +std::string Key::toString() { + return "Key[id=" + id + ", from_me=" + (from_me ? "true":"false") + ", remote_jid=" + remote_jid + "]"; +} + + +unsigned char FMessage::getMessage_WA_Type(std::string* type) { + if ((type == NULL) || (type->length() == 0)) + return WA_TYPE_UNDEFINED; + std::string typeLower = *type; + std::transform(typeLower.begin(), typeLower.end(), typeLower.begin(), ::tolower); + if (typeLower.compare("system") == 0) + return WA_TYPE_SYSTEM; + if (typeLower.compare("image") == 0) + return WA_TYPE_IMAGE; + if (typeLower.compare("audio") == 0) + return WA_TYPE_AUDIO; + if (typeLower.compare("video") == 0) + return WA_TYPE_VIDEO; + if (typeLower.compare("vcard") == 0) + return WA_TYPE_CONTACT; + if (typeLower.compare("location") == 0) + return WA_TYPE_LOCATION; + + return WA_TYPE_UNDEFINED; +} + diff --git a/protocols/WhatsApp/src/WhatsAPI++/FMessage.h b/protocols/WhatsApp/src/WhatsAPI++/FMessage.h new file mode 100644 index 0000000000..63ae64451a --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/FMessage.h @@ -0,0 +1,88 @@ +/* + * FMessage.h + * + * Created on: 02/07/2012 + * Author: Antonio + */ + + + +#ifndef FMESSAGE_H_ +#define FMESSAGE_H_ + +#include +//#include +#include +#include "IMutex.h" + +class Key { +public: + std::string remote_jid; + bool from_me; + std::string id; + + Key(const std::string& remote_jid, bool from_me, const std::string& id); + std::string toString(); + +}; + +class FMessage { +private: + static int generating_id; + static std::string generating_header; + + +public: + static IMutex* generating_lock; // #WORKAROUND + + Key* key; + unsigned char media_wa_type; + std::string data; + long long timestamp; + std::string remote_resource; + bool wants_receipt; + unsigned char status; + std::string notifyname; + bool offline; + std::string media_url; + std::string media_name; + long long media_size; + int media_duration_seconds; + double latitude; + double longitude; + + static const unsigned char WA_TYPE_UNDEFINED = 0; + static const unsigned char WA_TYPE_IMAGE = 1; + static const unsigned char WA_TYPE_AUDIO = 2; + static const unsigned char WA_TYPE_VIDEO = 3; + static const unsigned char WA_TYPE_CONTACT = 4; + static const unsigned char WA_TYPE_LOCATION = 5; + static const unsigned char WA_TYPE_SYSTEM = 7; + + static const int STATUS_UNSENT = 0; + static const int STATUS_UPLOADING = 1; + static const int STATUS_UPLOADED = 2; + static const int STATUS_SENT_BY_CLIENT = 3; + static const int STATUS_RECEIVED_BY_SERVER = 4; + static const int STATUS_RECEIVED_BY_TARGET = 5; + static const int STATUS_NEVER_SEND = 6; + static const int STATUS_SERVER_BOUNCE = 7; + + static const int STATUS_USER_ADDED = 191; + static const int STATUS_USER_REMOVED = 192; + static const int STATUS_SUBJECT_CHANGED = 193; + static const int STATUS_PICTURE_CHANGED_SET = 194; + static const int STATUS_PICTURE_CHANGED_DELETE = 195; + + + static std::string getMessage_WA_Type_StrValue(unsigned char type); + static std::string nextKeyIdNumber(); + static unsigned char getMessage_WA_Type(std::string* typeString); + + FMessage(); + FMessage(const std::string& remote_jid, bool from_me = true, const std::string& data = ""); + FMessage(Key* key); + virtual ~FMessage(); +}; + +#endif /* FMESSAGE_H_ */ diff --git a/protocols/WhatsApp/src/WhatsAPI++/IMutex.h b/protocols/WhatsApp/src/WhatsAPI++/IMutex.h new file mode 100644 index 0000000000..56a5492ac9 --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/IMutex.h @@ -0,0 +1,14 @@ +#if !defined(IMUTEX_H) +#define IMUTEX_H + +class IMutex +{ +public: + IMutex() {} + virtual ~IMutex() {} + + virtual void lock() = 0; + virtual void unlock() = 0; +}; + +#endif \ No newline at end of file diff --git a/protocols/WhatsApp/src/WhatsAPI++/ISocketConnection.h b/protocols/WhatsApp/src/WhatsAPI++/ISocketConnection.h new file mode 100644 index 0000000000..a2d82efc64 --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/ISocketConnection.h @@ -0,0 +1,25 @@ +#ifndef ISOCKETCONNECTION_H_ +#define ISOCKETCONNECTION_H_ + +#include + +class ISocketConnection { + +public: + ISocketConnection() {} + virtual void write(int i) = 0; + virtual unsigned char read() = 0; + virtual void flush() = 0; + virtual void write(const std::vector& b, int length) = 0; + virtual void write(const std::vector& bytes, int offset, int length) = 0; + virtual int read(std::vector& b, int off, int length) = 0; + virtual void makeNonBlock() = 0; + virtual int waitForRead() = 0; + virtual void forceShutdown() = 0; + + virtual ~ISocketConnection() {} + //static void initNetwork(); + //static void quitNetwork(); +}; + +#endif /* ISOCKETCONNECTION_H_ */ diff --git a/protocols/WhatsApp/src/WhatsAPI++/LICENSE b/protocols/WhatsApp/src/WhatsAPI++/LICENSE new file mode 100644 index 0000000000..cccee9ef01 --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/LICENSE @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey 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 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This 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 Library General +Public License instead of this License. diff --git a/protocols/WhatsApp/src/WhatsAPI++/ProtocolTreeNode.cpp b/protocols/WhatsApp/src/WhatsAPI++/ProtocolTreeNode.cpp new file mode 100644 index 0000000000..0ed60edbb8 --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/ProtocolTreeNode.cpp @@ -0,0 +1,135 @@ +/* + * ProtocolTreeNode.cpp + * + * Created on: 26/06/2012 + * Author: Antonio + */ +#include "stdafx.h" +#include "WAException.h" +#include "ProtocolTreeNode.h" + +ProtocolTreeNode::ProtocolTreeNode(const string& tag, map *attributes, vector* data, vector *children) { + this->tag = tag; + this->data = data; + this->attributes = attributes; + this->children = children; +} + +ProtocolTreeNode::ProtocolTreeNode(const string& tag, map *attributes, ProtocolTreeNode* child) { + this->tag = tag; + this->data = NULL; + this->attributes = attributes; + this->children = new std::vector(1); + (*this->children)[0] = child; +} + +ProtocolTreeNode::~ProtocolTreeNode() { + if (this->attributes != NULL) + delete this->attributes; + if (this->children != NULL) { + for (size_t i = 0; i < this->children->size(); i++) + if (this->children->at(i) != NULL) + delete this->children->at(i); + delete this->children; + } + if (this->data != NULL) + delete data; +} + + +string ProtocolTreeNode::toString() { + string out; + out += "<" + this->tag; + if (this->attributes != NULL) { + map::iterator ii; + for (ii = attributes->begin(); ii != attributes->end(); ii++) + out += "" + ii->first + "=\"" + ii->second + "\""; + } + out += ">\n"; + std::string* data = getDataAsString(); + out += (this->data != NULL? *data:""); + delete data; + + if (this->children != NULL) { + vector::iterator ii; + + for (ii = children->begin(); ii != children->end(); ii++) + out += (*ii)->toString(); + } + + out += "tag + ">\n"; + + return out; +} + +ProtocolTreeNode* ProtocolTreeNode::getChild(const string& id) { + if (this->children == NULL || this->children->size() == 0) + return NULL; + + for (std::size_t i = 0; i < this->children->size(); i++) + if (id.compare((*children)[i]->tag) == 0) + return (*children)[i]; + + return NULL; +} + +ProtocolTreeNode* ProtocolTreeNode::getChild(size_t id) { + if (this->children == NULL || this->children->size() == 0) + return NULL; + + if (children->size() > id) + return (*children)[id]; + + return NULL; +} + +string* ProtocolTreeNode::getAttributeValue(const string& attribute) { + if (this->attributes == NULL) + return NULL; + + map::iterator it = attributes->find(attribute); + if (it == attributes->end()) + return NULL; + + return &it->second; +} + +vector* ProtocolTreeNode::getAllChildren() { + vector* ret = new vector(); + + if (this->children == NULL) + return ret; + + return this->children; +} + +std::string* ProtocolTreeNode::getDataAsString() { + if (this->data == NULL) + return NULL; + return new std::string(this->data->begin(), this->data->end()); +} + +vector* ProtocolTreeNode::getAllChildren(const string& tag) { + vector* ret = new vector(); + + if (this->children == NULL) + return ret; + + for (size_t i = 0; i < this->children->size(); i++) + if (tag.compare((*children)[i]->tag) == 0) + ret->push_back((*children)[i]); + + return ret; +} + + +bool ProtocolTreeNode::tagEquals(ProtocolTreeNode *node, const string& tag) { + return (node != NULL && node->tag.compare(tag) == 0); +} + +void ProtocolTreeNode::require(ProtocolTreeNode *node, const string& tag) { + if (!tagEquals(node, tag)) + throw WAException("failed require. node:" + node->toString() + "tag: " + tag, WAException::CORRUPT_STREAM_EX, 0); +} + + diff --git a/protocols/WhatsApp/src/WhatsAPI++/ProtocolTreeNode.h b/protocols/WhatsApp/src/WhatsAPI++/ProtocolTreeNode.h new file mode 100644 index 0000000000..3156c8b7e7 --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/ProtocolTreeNode.h @@ -0,0 +1,41 @@ +/* +* ProtocolTreeNode.h +* +* Created on: 26/06/2012 +* Author: Antonio +*/ + +#if !defined(PROTOCOLNODE_H) +#define PROTOCOLNODE_H + +#include +#include +#include + +using namespace std; + +class ProtocolTreeNode { +public: + vector* data; + string tag; + map *attributes; + vector *children; + + ProtocolTreeNode(const string& tag, map *attributes, ProtocolTreeNode* child); + ProtocolTreeNode(const string& tag, map *attributes, vector* data = NULL, vector *children = NULL); + string toString(); + ProtocolTreeNode* getChild(const string& id); + ProtocolTreeNode* getChild(size_t id); + string* getAttributeValue(const string& attribute); + + vector* getAllChildren(); + vector* getAllChildren(const string& tag); + std::string* getDataAsString(); + + static bool tagEquals(ProtocolTreeNode *node, const string& tag); + static void require(ProtocolTreeNode *node, const string& tag); + + virtual ~ProtocolTreeNode(); +}; + +#endif /* PROTOCOLNODE_H_ */ \ No newline at end of file diff --git a/protocols/WhatsApp/src/WhatsAPI++/README.md b/protocols/WhatsApp/src/WhatsAPI++/README.md new file mode 100644 index 0000000000..69e374a6ef --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/README.md @@ -0,0 +1,30 @@ +This library can be used for creating WhatsApp-clients and is based on the MojoWhatsup-project written by Antonio Morales. The original source code has been modified by Uli Hecht. + +------------------------------------------------------------------------------------------------- +ORIGINAL "README.MD" BELOW +------------------------------------------------------------------------------------------------- + +MojoWhatsup +=========== + +MojoWhatsup is an IM application for Webos that allows you to chat with your Whatsapp friends + +MojoWhatsup has been developed as a Webos hybrid app, i.e. a javascript webos app (based in Mojo framework) +and a plugin (mojowhatsup_service_plugin) in C++. The plugin supports all Whatsapp communication API. + +Contact: Antonio Morales + +License: + + Copyright (c) 2012, Antonio Morales + + MojoWhatsup is free software: you can redistribute it and/or modify it under the terms + of the GNU General Public License as published by the Free Software Foundation, + either version 2 of the License, or (at your option) any later version. + + MojoWhatsup 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 MojoWhatsup. + If not, see http://www.gnu.org/licenses/. diff --git a/protocols/WhatsApp/src/WhatsAPI++/WAConnection.cpp b/protocols/WhatsApp/src/WhatsAPI++/WAConnection.cpp new file mode 100644 index 0000000000..da5837f682 --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/WAConnection.cpp @@ -0,0 +1,1684 @@ +/* + * WAConnection.cpp + * + * Created on: 26/06/2012 + * Author: Antonio + */ +#include "stdafx.h" +#include "WAConnection.h" +#include "ProtocolTreeNode.h" +#include +#include +#include "utilities.h" +#include "base64.h" + +const char* WAConnection::dictionary[] = { + "", + "", + "", + "", + "", + "account", + "ack", + "action", + "active", + "add", + "after", + "ib", + "all", + "allow", + "apple", + "audio", + "auth", + "author", + "available", + "bad-protocol", + "bad-request", + "before", + "Bell.caf", + "body", + "Boing.caf", + "cancel", + "category", + "challenge", + "chat", + "clean", + "code", + "composing", + "config", + "conflict", + "contacts", + "count", + "create", + "creation", + "default", + "delay", + "delete", + "delivered", + "deny", + "digest", + "DIGEST-MD5-1", + "DIGEST-MD5-2", + "dirty", + "elapsed", + "broadcast", + "enable", + "encoding", + "duplicate", + "error", + "event", + "expiration", + "expired", + "fail", + "failure", + "false", + "favorites", + "feature", + "features", + "field", + "first", + "free", + "from", + "g.us", + "get", + "Glass.caf", + "google", + "group", + "groups", + "g_notify", + "g_sound", + "Harp.caf", + "http://etherx.jabber.org/streams", + "http://jabber.org/protocol/chatstates", + "id", + "image", + "img", + "inactive", + "index", + "internal-server-error", + "invalid-mechanism", + "ip", + "iq", + "item", + "item-not-found", + "user-not-found", + "jabber:iq:last", + "jabber:iq:privacy", + "jabber:x:delay", + "jabber:x:event", + "jid", + "jid-malformed", + "kind", + "last", + "latitude", + "lc", + "leave", + "leave-all", + "lg", + "list", + "location", + "longitude", + "max", + "max_groups", + "max_participants", + "max_subject", + "mechanism", + "media", + "message", + "message_acks", + "method", + "microsoft", + "missing", + "modify", + "mute", + "name", + "nokia", + "none", + "not-acceptable", + "not-allowed", + "not-authorized", + "notification", + "notify", + "off", + "offline", + "order", + "owner", + "owning", + "paid", + "participant", + "participants", + "participating", + "password", + "paused", + "picture", + "pin", + "ping", + "platform", + "pop_mean_time", + "pop_plus_minus", + "port", + "presence", + "preview", + "probe", + "proceed", + "prop", + "props", + "p_o", + "p_t", + "query", + "raw", + "reason", + "receipt", + "receipt_acks", + "received", + "registration", + "relay", + "remote-server-timeout", + "remove", + "Replaced by new connection", + "request", + "required", + "resource", + "resource-constraint", + "response", + "result", + "retry", + "rim", + "s.whatsapp.net", + "s.us", + "seconds", + "server", + "server-error", + "service-unavailable", + "set", + "show", + "sid", + "silent", + "sound", + "stamp", + "unsubscribe", + "stat", + "status", + "stream:error", + "stream:features", + "subject", + "subscribe", + "success", + "sync", + "system-shutdown", + "s_o", + "s_t", + "t", + "text", + "timeout", + "TimePassing.caf", + "timestamp", + "to", + "Tri-tone.caf", + "true", + "type", + "unavailable", + "uri", + "url", + "urn:ietf:params:xml:ns:xmpp-sasl", + "urn:ietf:params:xml:ns:xmpp-stanzas", + "urn:ietf:params:xml:ns:xmpp-streams", + "urn:xmpp:delay", + "urn:xmpp:ping", + "urn:xmpp:receipts", + "urn:xmpp:whatsapp", + "urn:xmpp:whatsapp:account", + "urn:xmpp:whatsapp:dirty", + "urn:xmpp:whatsapp:mms", + "urn:xmpp:whatsapp:push", + "user", + "username", + "value", + "vcard", + "version", + "video", + "w", + "w:g", + "w:p", + "w:p:r", + "w:profile:picture", + "wait", + "x", + "xml-not-well-formed", + "xmlns", + "xmlns:stream", + "Xylophone.caf", + "1", + "WAUTH-1" +}; + + +//const char* WAConnection::dictionary[] = { +// "", +// "", +// "", +// "", +// "", +// "1", +// "1.0", +// "ack", +// "action", +// "active", +// "add", +// "all", +// "allow", +// "apple", +// "audio", +// "auth", +// "author", +// "available", +// "bad-request", +// "base64", +// "Bell.caf", +// "bind", +// "body", +// "Boing.caf", +// "cancel", +// "category", +// "challenge", +// "chat", +// "clean", +// "code", +// "composing", +// "config", +// "conflict", +// "contacts", +// "create", +// "creation", +// "default", +// "delay", +// "delete", +// "delivered", +// "deny", +// "DIGEST-MD5", +// "DIGEST-MD5-1", +// "dirty", +// "en", +// "enable", +// "encoding", +// "error", +// "expiration", +// "expired", +// "failure", +// "false", +// "favorites", +// "feature", +// "field", +// "free", +// "from", +// "g.us", +// "get", +// "Glass.caf", +// "google", +// "group", +// "groups", +// "g_sound", +// "Harp.caf", +// "http://etherx.jabber.org/streams", +// "http://jabber.org/protocol/chatstates", +// "id", +// "image", +// "img", +// "inactive", +// "internal-server-error", +// "iq", +// "item", +// "item-not-found", +// "jabber:client", +// "jabber:iq:last", +// "jabber:iq:privacy", +// "jabber:x:delay", +// "jabber:x:event", +// "jid", +// "jid-malformed", +// "kind", +// "leave", +// "leave-all", +// "list", +// "location", +// "max_groups", +// "max_participants", +// "max_subject", +// "mechanism", +// "mechanisms", +// "media", +// "message", +// "message_acks", +// "missing", +// "modify", +// "name", +// "not-acceptable", +// "not-allowed", +// "not-authorized", +// "notify", +// "Offline Storage", +// "order", +// "owner", +// "owning", +// "paid", +// "participant", +// "participants", +// "participating", +// "particpants", +// "paused", +// "picture", +// "ping", +// "PLAIN", +// "platform", +// "presence", +// "preview", +// "probe", +// "prop", +// "props", +// "p_o", +// "p_t", +// "query", +// "raw", +// "receipt", +// "receipt_acks", +// "received", +// "relay", +// "remove", +// "Replaced by new connection", +// "request", +// "resource", +// "resource-constraint", +// "response", +// "result", +// "retry", +// "rim", +// "s.whatsapp.net", +// "seconds", +// "server", +// "session", +// "set", +// "show", +// "sid", +// "sound", +// "stamp", +// "starttls", +// "status", +// "stream:error", +// "stream:features", +// "subject", +// "subscribe", +// "success", +// "system-shutdown", +// "s_o", +// "s_t", +// "t", +// "TimePassing.caf", +// "timestamp", +// "to", +// "Tri-tone.caf", +// "type", +// "unavailable", +// "uri", +// "url", +// "urn:ietf:params:xml:ns:xmpp-bind", +// "urn:ietf:params:xml:ns:xmpp-sasl", +// "urn:ietf:params:xml:ns:xmpp-session", +// "urn:ietf:params:xml:ns:xmpp-stanzas", +// "urn:ietf:params:xml:ns:xmpp-streams", +// "urn:xmpp:delay", +// "urn:xmpp:ping", +// "urn:xmpp:receipts", +// "urn:xmpp:whatsapp", +// "urn:xmpp:whatsapp:dirty", +// "urn:xmpp:whatsapp:mms", +// "urn:xmpp:whatsapp:push", +// "value", +// "vcard", +// "version", +// "video", +// "w", +// "w:g", +// "w:p:r", +// "wait", +// "x", +// "xml-not-well-formed", +// "xml:lang", +// "xmlns", +// "xmlns:stream", +// "Xylophone.caf", +// "account", +// "digest", +// "g_notify", +// "method", +// "password", +// "registration", +// "stat", +// "text", +// "user", +// "username", +// "event", +// "latitude", +// "longitude", +// "true", +// "after", +// "before", +// "broadcast", +// "count", +// "features", +// "first", +// "index", +// "invalid-mechanism", +// "l$dict", +// "max", +// "offline", +// "proceed", +// "required", +// "sync", +// "elapsed", +// "ip", +// "microsoft", +// "mute", +// "nokia", +// "off", +// "pin", +// "pop_mean_time", +// "pop_plus_minus", +// "port", +// "reason", +// "server-error", +// "silent", +// "timeout", +// "lc", +// "lg", +// "bad-protocol", +// "none", +// "remote-server-timeout", +// "service-unavailable", +// "w:p", +// "w:profile:picture", +// "notification", +// "", +// "", +// "", +// "", +// "", +// "XXX" +//}; + +WAConnection::WAConnection(IMutex* mutex, WAListener* event_handler, WAGroupListener* group_event_handler) { + this->init(event_handler, group_event_handler, mutex); +} + +void WAConnection::init(WAListener* event_handler, WAGroupListener* group_event_handler, IMutex* mutex) { + this->login = NULL; + this->event_handler = event_handler; + this->group_event_handler = group_event_handler; + this->inputKey = NULL; + this->outputKey = NULL; + this->in = NULL; + this->out = NULL; + + this->msg_id = 0; + this->state = 0; // 0 disconnected 1 connecting 2 connected + this->retry = true; + + this->iqid = 0; + this->verbose = true; + this->lastTreeRead = 0; + this->expire_date = 0L; + this->account_kind = -1; + this->mutex = mutex; +} + +void WAConnection::setLogin(WALogin* login) { + this->login = login; + + if (login->expire_date != 0L) { + this->expire_date = login->expire_date; + } + if (login->account_kind != -1) { + this->account_kind = login->account_kind; + } + + this->jid = this->login->user + "@" + this->login->domain; + this->fromm = this->login->user + "@" + this->login->domain + "/" + this->login->resource; + + this->in = login->inn; + this->out = login->out; +} + +WALogin* WAConnection::getLogin() { + return this->login; +} + +void WAConnection::sendMessageWithMedia(FMessage* message) throw (WAException) { + _LOGDATA("Send message with media %s %d", message->media_name.c_str(), message->media_size); + _LOGDATA("media-url:%s", message->media_url.c_str()); + if (message->media_wa_type == FMessage::WA_TYPE_SYSTEM) + throw new WAException("Cannot send system message over the network"); + std::map* attribs = new std::map(); + (*attribs)["xmlns"] = "urn:xmpp:whatsapp:mms"; + (*attribs)["type"] = FMessage::getMessage_WA_Type_StrValue(message->media_wa_type); + + if (message->media_wa_type == FMessage::WA_TYPE_LOCATION) { + (*attribs)["latitude"] = Utilities::doubleToStr(message->latitude); + (*attribs)["longitude"] = Utilities::doubleToStr(message->longitude);; + } else { + if (message->media_wa_type != FMessage::WA_TYPE_CONTACT && !message->media_name.empty() && !message->media_url.empty() && message->media_size > 0L) { + (*attribs)["file"] = message->media_name; + (*attribs)["size"] = Utilities::intToStr(message->media_size); + (*attribs)["url"] = message->media_url; + } else { + (*attribs)["file"] = message->media_name; + (*attribs)["size"] = Utilities::intToStr(message->media_size); + (*attribs)["url"] = message->media_url; + (*attribs)["seconds"] = Utilities::intToStr(message->media_duration_seconds); + } + } + + ProtocolTreeNode* mediaNode; + if (message->media_wa_type == FMessage::WA_TYPE_CONTACT && !message->media_name.empty()) { + std::map* attribs2 = new std::map(); + (*attribs2)["name"] = message->media_name; + ProtocolTreeNode* vcardNode = new ProtocolTreeNode("vcard", attribs2, new std::vector(message->data.begin(), message->data.end())); + mediaNode = new ProtocolTreeNode("media", attribs, vcardNode); + } else { + (*attribs)["encoding"] = "text"; + mediaNode = new ProtocolTreeNode("media", attribs, new std::vector(message->data.begin(), message->data.end()), NULL); + } + + ProtocolTreeNode* root = WAConnection::getMessageNode(message, mediaNode); + this->out->write(root); + delete root; +} + +void WAConnection::sendMessageWithBody(FMessage* message) throw (WAException) { + ProtocolTreeNode* bodyNode = new ProtocolTreeNode("body", NULL, new std::vector(message->data.begin(), message->data.end())); + ProtocolTreeNode* root = WAConnection::getMessageNode(message, bodyNode); + this->out->write(root); + delete root; +} + +ProtocolTreeNode* WAConnection::getMessageNode(FMessage* message, ProtocolTreeNode* child) { + ProtocolTreeNode* requestNode = NULL; + ProtocolTreeNode* serverNode = new ProtocolTreeNode("server", NULL); + std::map* attrib = new std::map(); + (*attrib)["xmlns"] = "jabber:x:event"; + std::vector* children = new std::vector(1); + (*children)[0] = serverNode; + ProtocolTreeNode* xNode = new ProtocolTreeNode("x", attrib, NULL, children); + int childCount = (requestNode == NULL? 0 : 1) + 2; + std::vector* messageChildren = new std::vector(childCount); + int i = 0; + if (requestNode != NULL) { + (*messageChildren)[i] = requestNode; + i++; + } + (*messageChildren)[i] = xNode; + i++; + (*messageChildren)[i] = child; + i++; + + std::map* attrib2 = new std::map(); + (*attrib2)["to"] = message->key->remote_jid; + (*attrib2)["type"] = "chat"; + (*attrib2)["id"] = message->key->id; + + return new ProtocolTreeNode("message", attrib2, NULL, messageChildren); +} + +void WAConnection::sendMessage(FMessage* message) throw(WAException) { + if (message->media_wa_type != 0) + sendMessageWithMedia(message); + else + sendMessageWithBody(message); +} + + +WAConnection::~WAConnection() { + if (this->inputKey != NULL) + delete this->inputKey; + if (this->outputKey != NULL) + delete this->outputKey; + std::map::iterator it; + for (it = this->pending_server_requests.begin(); it != this->pending_server_requests.end(); it++) { + delete it->second; + } +} + + +void WAConnection::setVerboseId(bool b) { + this->verbose = b; +} + +void WAConnection::sendAvailableForChat() throw(WAException) { + std::map* attribs = new std::map(); + (*attribs)["name"] = this->login->push_name; + ProtocolTreeNode *presenceNode = new ProtocolTreeNode("presence", attribs); + this->out->write(presenceNode); + delete presenceNode; +} + +bool WAConnection::read() throw(WAException) { + ProtocolTreeNode* node; + try { + node = this->in->nextTree(); + this->lastTreeRead = time(NULL); + } catch (exception& ex) { + throw WAException(ex.what(), WAException::CORRUPT_STREAM_EX, 0); + } + + if (node == NULL) { + return false; + } + + if (ProtocolTreeNode::tagEquals(node, "iq")) { + std::string* type = node->getAttributeValue("type"); + std::string* id = node->getAttributeValue("id"); + std::string* from = node->getAttributeValue("from"); + std::string f; + if (from == NULL) + f = ""; + else + f = *from; + + if (type == NULL) + throw WAException("missing 'type' attribute in iq stanza", WAException::CORRUPT_STREAM_EX, 0); + + if (type->compare("result") == 0) { + if (id == NULL) + throw WAException("missing 'id' attribute in iq stanza", WAException::CORRUPT_STREAM_EX, 0); + + std::map::iterator it = this->pending_server_requests.find(*id); + if (it!= this->pending_server_requests.end()) { + it->second->parse(node, f); + delete it->second; + this->pending_server_requests.erase(*id); + } else if (id->compare(0, this->login->user.size(), this->login->user) == 0) { + ProtocolTreeNode* accountNode = node->getChild(0); + ProtocolTreeNode::require(accountNode, "account"); + std::string* kind = accountNode->getAttributeValue("kind"); + if ((kind != NULL) && (kind->compare("paid") == 0)) { + this->account_kind = 1; + } else if ((kind != NULL) && (kind->compare("free") == 0)) { + this->account_kind = 0; + } else + this->account_kind = -1; + std::string* expiration = accountNode->getAttributeValue("expiration"); + if (expiration == NULL) { + throw WAException("no expiration"); + } + this->expire_date = atol(expiration->c_str()); + if (this->expire_date == 0) + throw WAException("invalid expire date: " + *expiration); + if (this->event_handler != NULL) + this->event_handler->onAccountChange(this->account_kind, this->expire_date); + } + } else if (type->compare("error") == 0) { + std::map::iterator it = this->pending_server_requests.find(*id); + if (it!= this->pending_server_requests.end()) { + it->second->error(node); + delete it->second; + this->pending_server_requests.erase(*id); + } + } else if (type->compare("get") == 0) { + ProtocolTreeNode* childNode = node->getChild(0); + if (ProtocolTreeNode::tagEquals(childNode, "ping")) { + if (this->event_handler != NULL) + this->event_handler->onPing(*id); + } else if (ProtocolTreeNode::tagEquals(childNode, "query") && (from != NULL)? false : // (childNode->getAttributeValue("xmlns") != NULL) && ((*childNode->getAttributeValue("xmlns")).compare("http://jabber.org/protocol/disco#info") == 0) : + (ProtocolTreeNode::tagEquals(childNode, "relay")) && (from != NULL)) { + std::string* pin = childNode->getAttributeValue("pin"); + std::string* timeoutString = childNode->getAttributeValue("timeout"); + int timeoutSeconds; + timeoutSeconds = timeoutString == NULL? 0 : atoi(timeoutString->c_str()); + if (pin != NULL) + if (this->event_handler != NULL) + this->event_handler->onRelayRequest(*pin, timeoutSeconds, *id); + } + } else if (type->compare("set") == 0) { + ProtocolTreeNode* childNode = node->getChild(0); + if (ProtocolTreeNode::tagEquals(childNode, "query")) { + std::string* xmlns = childNode->getAttributeValue("xmlns"); + if ((xmlns != NULL) && (xmlns->compare("jabber:iq:roster") == 0)) { + std::vector* itemNodes = childNode->getAllChildren("item"); + std::string ask = ""; + for (size_t i = 0; i < itemNodes->size(); i++) { + ProtocolTreeNode* itemNode = (*itemNodes)[i]; + std::string* jid = itemNode->getAttributeValue("jid"); + std::string* subscription = itemNode->getAttributeValue("subscription"); + ask = *itemNode->getAttributeValue("ask"); + } + delete itemNodes; + } + } + } else + throw WAException("unknown iq type attribute: " + *type, WAException::CORRUPT_STREAM_EX, 0); + } else if (ProtocolTreeNode::tagEquals(node, "presence")) { + std::string* xmlns = node->getAttributeValue("xmlns"); + std::string* from = node->getAttributeValue("from"); + if (((xmlns == NULL) || (xmlns->compare("urn:xmpp") == 0)) && (from != NULL)) { + std::string* type = node->getAttributeValue("type"); + if ((type != NULL) && (type->compare("unavailable") == 0)) { + if (this->event_handler != NULL) + this->event_handler->onAvailable(*from, false); + } else if ((type == NULL) || (type->compare("available") == 0)) { + if (this->event_handler != NULL) + this->event_handler->onAvailable(*from, true); + } + } else if ((xmlns->compare("w") == 0) && (from != NULL)) { + std::string* add = node->getAttributeValue("add"); + std::string* remove = node->getAttributeValue("remove"); + std::string* status = node->getAttributeValue("status"); + if (add != NULL) { + if (this->group_event_handler != NULL) + this->group_event_handler->onGroupAddUser(*from, *add); + } else if (remove != NULL) { + if (this->group_event_handler != NULL) + this->group_event_handler->onGroupRemoveUser(*from, *remove); + } else if ((status != NULL) && (status->compare("dirty") == 0)) { + std::map* categories = parseCategories(node); + if (this->event_handler != NULL) + this->event_handler->onDirty(*categories); + delete categories; + } + } + } else if (ProtocolTreeNode::tagEquals(node, "message")) { + parseMessageInitialTagAlreadyChecked(node); + } + + delete node; + return true; +} + +void WAConnection::sendNop() throw(WAException) { + this->out->write(NULL); +} + +void WAConnection::sendPing() throw(WAException) { + std::string id = makeId("ping_"); + this->pending_server_requests[id] = new IqResultPingHandler(this); + + std::map* attribs1 = new std::map(); + (*attribs1)["xmlns"] = "w:p"; + ProtocolTreeNode* pingNode = new ProtocolTreeNode("ping", attribs1); + + std::map* attribs2 = new std::map(); + (*attribs2)["id"] = id; + (*attribs2)["type"] = "get"; + ProtocolTreeNode* iqNode = new ProtocolTreeNode("iq", attribs2, pingNode); + + this->out->write(iqNode); + delete iqNode; +} + +void WAConnection::sendPong(const std::string& id) throw(WAException) { + std::map* attribs = new std::map(); + (*attribs)["type"] = "result"; + (*attribs)["to"] = this->login->domain; + (*attribs)["id"] = id; + ProtocolTreeNode *iqNode = new ProtocolTreeNode("iq", attribs); + this->out->write(iqNode); + delete iqNode; +} + +void WAConnection::sendComposing(const std::string& to) throw(WAException) { + std::map* attribs1 = new std::map(); + (*attribs1)["xmlns"] = "http://jabber.org/protocol/chatstates"; + ProtocolTreeNode* composingNode = new ProtocolTreeNode("composing", attribs1); + + std::map* attribs2 = new std::map(); + (*attribs2)["to"] = to; + (*attribs2)["type"] = "chat"; + ProtocolTreeNode* messageNode = new ProtocolTreeNode("message", attribs2, composingNode); + + this->out->write(messageNode); + + delete messageNode; +} + + +void WAConnection::sendActive() throw(WAException) { + std::map* attribs = new std::map(); + (*attribs)["type"] = "active"; + ProtocolTreeNode* presenceNode = new ProtocolTreeNode("presence", attribs); + + this->out->write(presenceNode); + + delete presenceNode; +} + +void WAConnection::sendInactive() throw(WAException) { + std::map* attribs = new std::map(); + (*attribs)["type"] = "inactive"; + ProtocolTreeNode* presenceNode = new ProtocolTreeNode("presence", attribs); + + this->out->write(presenceNode); + + delete presenceNode; +} + +void WAConnection::sendPaused(const std::string& to) throw(WAException) { + std::map* attribs1 = new std::map(); + (*attribs1)["xmlns"] = "http://jabber.org/protocol/chatstates"; + ProtocolTreeNode* pausedNode = new ProtocolTreeNode("paused", attribs1); + + std::map* attribs2 = new std::map(); + (*attribs2)["to"] = to; + (*attribs2)["type"] = "chat"; + ProtocolTreeNode* messageNode = new ProtocolTreeNode("message", attribs2, pausedNode); + + this->out->write(messageNode); + + delete messageNode; +} + +void WAConnection::sendSubjectReceived(const std::string& to, const std::string& id)throw(WAException) { + std::map* attribs1 = new std::map(); + (*attribs1)["xmlns"] = "urn:xmpp:receipts"; + ProtocolTreeNode* receivedNode = new ProtocolTreeNode("received", attribs1); + + ProtocolTreeNode* messageNode = getSubjectMessage(to, id, receivedNode); + + this->out->write(messageNode); + + delete messageNode; +} + +ProtocolTreeNode* WAConnection::getSubjectMessage(const std::string& to, const std::string& id, ProtocolTreeNode* child) throw (WAException) { + std::map* attribs1 = new std::map(); + (*attribs1)["to"] = to; + (*attribs1)["type"] = "subject"; + (*attribs1)["id"] = id; + ProtocolTreeNode* messageNode = new ProtocolTreeNode("message", attribs1, child); + + return messageNode; +} + +void WAConnection::sendMessageReceived(FMessage* message) throw(WAException) { + std::map* attribs1 = new std::map(); + (*attribs1)["xmlns"] = "urn:xmpp:receipts"; + ProtocolTreeNode* receivedNode = new ProtocolTreeNode("received", attribs1); + + std::map* attribs2 = new std::map(); + (*attribs2)["to"] = message->key->remote_jid; + (*attribs2)["type"] = "chat"; + (*attribs2)["id"] = message->key->id; + + ProtocolTreeNode* messageNode = new ProtocolTreeNode("message", attribs2, receivedNode); + + this->out->write(messageNode); + delete messageNode; +} + +void WAConnection::sendDeliveredReceiptAck(const std::string& to, + const std::string& id) throw(WAException) { + ProtocolTreeNode *root = getReceiptAck(to, id, "delivered"); + this->out->write(root); + delete root; +} + +void WAConnection::sendVisibleReceiptAck(const std::string& to, const std::string& id) throw (WAException) { + ProtocolTreeNode *root = getReceiptAck(to, id, "visible"); + this->out->write(root); + delete root; +} + +void WAConnection::sendPresenceSubscriptionRequest(const std::string& to) throw(WAException) { + std::map* attribs1 = new std::map(); + (*attribs1)["type"] = "subscribe"; + (*attribs1)["to"] = to; + ProtocolTreeNode* presenceNode = new ProtocolTreeNode("presence", attribs1); + this->out->write(presenceNode); + delete presenceNode; +} + +void WAConnection::sendClientConfig(const std::string& sound, const std::string& pushID, bool preview, const std::string& platform) throw(WAException) { + std::map* attribs1 = new std::map(); + (*attribs1)["xmlns"] ="urn:xmpp:whatsapp:push"; + (*attribs1)["sound"] =sound; + (*attribs1)["id"] = pushID; + (*attribs1)["preview"] = preview ? "1" : "0"; + (*attribs1)["platform"] = platform; + ProtocolTreeNode* configNode = new ProtocolTreeNode("config", attribs1); + + std::string id = makeId("config_"); + + this->pending_server_requests[id] = new IqSendClientConfigHandler(this); + + std::map* attribs2 = new std::map(); + (*attribs2)["id"] = id; + (*attribs2)["type"] = "set"; + (*attribs2)["to"] = this->login->domain; + + ProtocolTreeNode* iqNode = new ProtocolTreeNode("iq", attribs2, configNode); + + this->out->write(iqNode); + delete iqNode; + +} + +void WAConnection::sendClientConfig(const std::string& pushID, bool preview, const std::string& platform, bool defaultSettings, bool groupSettings, const std::vector& groups) throw(WAException) { + std::map* attribs1 = new std::map(); + (*attribs1)["xmlns"] ="urn:xmpp:whatsapp:push"; + (*attribs1)["id"] = pushID; + (*attribs1)["lg"] = "en"; + (*attribs1)["lc"] = "US"; + (*attribs1)["clear"] = "0"; + (*attribs1)["preview"] = preview ? "1" : "0"; + (*attribs1)["platform"] = platform; + (*attribs1)["default"] = defaultSettings? "1": "0"; + (*attribs1)["groups"] = groupSettings? "1" : "0"; + ProtocolTreeNode* configNode = new ProtocolTreeNode("config", attribs1, NULL, this->processGroupSettings(groups)); + std::string id = makeId("config_"); + + std::map* attribs2 = new std::map(); + (*attribs2)["id"] = id; + (*attribs2)["type"] = "set"; + (*attribs2)["to"] = this->login->domain; + + ProtocolTreeNode* iqNode = new ProtocolTreeNode("iq", attribs2, configNode); + this->out->write(iqNode); + delete iqNode; +} + +std::vector* WAConnection::processGroupSettings(const std::vector& groups) { + std::vector* result = new std::vector(groups.size()); + if (!groups.empty()) { + time_t now = time(NULL); + for (int i = 0; i < groups.size(); i++) { + std::map* attribs = new std::map(); + (*attribs)["jid"] = groups[i].jid; + (*attribs)["notify"] = (groups[i].enabled? "1": "0"); + (*attribs)["mute"] = Utilities::intToStr(groups[i].muteExpiry > now? (groups[i].muteExpiry - now): 0); + _LOGDATA("mute group %s, %s", (*attribs)["jid"].c_str(), (*attribs)["mute"].c_str()); + + (*result)[i] = new ProtocolTreeNode("item", attribs); + } + } + + return result; +} + +std::string WAConnection::makeId(const std::string& prefix) { + this->iqid++; + std::string id; + if (this->verbose) + id = prefix + Utilities::intToStr(this->iqid); + else + id = Utilities::itoa(this->iqid, 16); + + return id; +} + + +ProtocolTreeNode* WAConnection::getReceiptAck(const std::string& to, const std::string& id, const std::string& receiptType) throw(WAException) { + std::map* attribs1 = new std::map(); + (*attribs1)["xmlns"] = "urn:xmpp:receipts"; + (*attribs1)["type"] = receiptType; + ProtocolTreeNode* ackNode = new ProtocolTreeNode("ack", attribs1); + + std::map* attribs2 = new std::map(); + (*attribs2)["to"] = to; + (*attribs2)["type"] = "chat"; + (*attribs2)["id"] = id; + ProtocolTreeNode* messageNode = new ProtocolTreeNode("message", attribs2, ackNode); + + return messageNode; +} + +std::map* WAConnection::parseCategories(ProtocolTreeNode* dirtyNode) throw (WAException) { + std::map* categories = new std::map(); + if (dirtyNode->children != NULL) { + for (size_t i = 0; i < dirtyNode->children->size(); i++) { + ProtocolTreeNode* childNode = (*dirtyNode->children)[i]; + if (ProtocolTreeNode::tagEquals(childNode, "category")) { + std::string* categoryName = childNode->getAttributeValue("name"); + std::string* timestamp = childNode->getAttributeValue("timestamp"); + (*categories)[*categoryName] = *timestamp; + } + } + } + + return categories; +} + +void WAConnection::parseMessageInitialTagAlreadyChecked(ProtocolTreeNode* messageNode) throw (WAException){ + std::string* id = messageNode->getAttributeValue("id"); + std::string* attribute_t = messageNode->getAttributeValue("t"); + std::string* from = messageNode->getAttributeValue("from"); + std::string* authoraux = messageNode->getAttributeValue("author"); + std::string author = ""; + + if (authoraux != NULL) + author = *authoraux; + + std::string* typeAttribute = messageNode->getAttributeValue("type"); + if (typeAttribute != NULL) { + if (typeAttribute->compare("error") == 0) { + int errorCode = 0; + std::vector* errorNodes = messageNode->getAllChildren("error"); + for (size_t i = 0; i < errorNodes->size(); i++) { + ProtocolTreeNode *errorNode = (*errorNodes)[i]; + std::string* codeString = errorNode->getAttributeValue("code"); + errorCode = atoi(codeString->c_str()); + } + + Key* key = new Key(*from, true, *id); + FMessage* message = new FMessage(key); + message->status = FMessage::STATUS_SERVER_BOUNCE; + + if (this->event_handler != NULL) + this->event_handler->onMessageError(message, errorCode); + delete errorNodes; + delete message; + } else if (typeAttribute->compare("subject") == 0) { + bool receiptRequested = false; + std::vector* requestNodes = messageNode->getAllChildren("request"); + for (size_t i = 0; i < requestNodes->size(); i++) { + ProtocolTreeNode *requestNode = (*requestNodes)[i]; + if ((requestNode->getAttributeValue("xmlns") != NULL) && (*requestNode->getAttributeValue("xmlns")).compare("urn:xmpp:receipts") == 0) + receiptRequested = true; + } + delete requestNodes; + + ProtocolTreeNode* bodyNode = messageNode->getChild("body"); + std::string* newSubject = bodyNode == NULL? NULL : bodyNode->getDataAsString(); + if ((newSubject != NULL) && (this->group_event_handler != NULL)) + this->group_event_handler->onGroupNewSubject(*from, author, *newSubject, atoi(attribute_t->c_str())); + if (newSubject != NULL) + delete newSubject; + if (receiptRequested) + sendSubjectReceived(*from, *id); + } else if (typeAttribute->compare("chat") == 0) { + FMessage* fmessage = new FMessage(); + fmessage->wants_receipt = false; + bool duplicate = false; + std::vector myVector(0); + std::vector* messageChildren = messageNode->children == NULL? &myVector: messageNode->getAllChildren(); + for (size_t i = 0; i < messageChildren->size(); i++) { + ProtocolTreeNode* childNode = (*messageChildren)[i]; + if (ProtocolTreeNode::tagEquals(childNode, "composing")) { + if (this->event_handler != NULL) + this->event_handler->onIsTyping(*from, true); + } else if (ProtocolTreeNode::tagEquals(childNode, "paused")) { + if (this->event_handler != NULL) + this->event_handler->onIsTyping(*from, false); + } else if (ProtocolTreeNode::tagEquals(childNode, "body")) { + std::string* message = childNode->getDataAsString(); + Key* key = new Key(*from, false, *id); + fmessage->key = key; + fmessage->remote_resource = author; + fmessage->data = *message; + fmessage->status = FMessage::STATUS_UNSENT; + if (message != NULL) + delete message; + } else if (ProtocolTreeNode::tagEquals(childNode, "media") && (id != NULL)) { + fmessage->media_wa_type = FMessage::getMessage_WA_Type(childNode->getAttributeValue("type")); + fmessage->media_url = (childNode->getAttributeValue("url") == NULL? "": *childNode->getAttributeValue("url")); + fmessage->media_name = (childNode->getAttributeValue("file") == NULL? "": *childNode->getAttributeValue("file")); + + if (childNode->getAttributeValue("size") != NULL) + fmessage->media_size = Utilities::parseLongLong(*childNode->getAttributeValue("size")); + else + fmessage->media_size = 0; + + if (childNode->getAttributeValue("seconds") != NULL) + fmessage->media_duration_seconds = atoi(childNode->getAttributeValue("seconds")->c_str()); + else + fmessage->media_duration_seconds = 0; + + if (fmessage->media_wa_type == FMessage::WA_TYPE_LOCATION) { + std::string* latitudeString = childNode->getAttributeValue("latitude"); + std::string* longitudeString = childNode->getAttributeValue("longitude"); + if (latitudeString == NULL || longitudeString == NULL) + throw WAException("location message missing lat or long attribute", WAException::CORRUPT_STREAM_EX, 0); + + double latitude = atof(latitudeString->c_str()); + double longitude = atof(longitudeString->c_str()); + fmessage->latitude = latitude; + fmessage->longitude = longitude; + } + + if (fmessage->media_wa_type == FMessage::WA_TYPE_CONTACT) { + ProtocolTreeNode* contactChildNode = childNode->getChild(0); + if (contactChildNode != NULL) { + fmessage->media_name = (contactChildNode->getAttributeValue("name") == NULL? "": *contactChildNode->getAttributeValue("name")); + std::string* data = contactChildNode->getDataAsString(); + fmessage->data = (data == NULL? "": *data); + if (data != NULL) + delete data; + } + } else { + std::string* encoding = childNode->getAttributeValue("encoding"); + std::string* data; + if ((encoding == NULL) || ((encoding != NULL) && (encoding->compare("text") == 0))) { + data = childNode->getDataAsString(); + } else { + _LOGDATA("Media data encoding type '%s'", (encoding == NULL? "text":encoding->c_str())); + data = (childNode->data == NULL? NULL : new std::string(base64_encode(childNode->data->data(), childNode->data->size()))); + } + fmessage->data = (data == NULL? "": *data); + if (data != NULL) + delete data; + } + + Key* key = new Key(*from, false, *id); + fmessage->key = key; + fmessage->remote_resource = author; + } else if (!ProtocolTreeNode::tagEquals(childNode, "active")) { + if (ProtocolTreeNode::tagEquals(childNode, "request")) { + fmessage->wants_receipt = true; + } else if (ProtocolTreeNode::tagEquals(childNode, "notify")) { + fmessage->notifyname = (childNode->getAttributeValue("name") == NULL)? "": *childNode->getAttributeValue("name"); + } else if (ProtocolTreeNode::tagEquals(childNode, "x")) { + std::string* xmlns = childNode->getAttributeValue("xmlns"); + if ((xmlns != NULL) && (xmlns->compare("jabber:x:event") == 0) && (id != NULL)) { + Key* key = new Key(*from, true, *id); + FMessage* message = new FMessage(key); + message->status = FMessage::STATUS_RECEIVED_BY_SERVER; + if (this->event_handler != NULL) + this->event_handler->onMessageStatusUpdate(message); + delete message; + } +// else if ((xmlns != NULL) && xmlns->compare("jabber:x:delay") == 0) { +// std::string* stamp_str = childNode->getAttributeValue("stamp"); +// if (stamp_str != NULL) { +// time_t stamp = Utilities::parseBBDate(*stamp_str); +// if (stamp != 0) { +// fmessage->timestamp = (long long) stamp; +// fmessage->offline = true; +// } +// } +// } + } else if (ProtocolTreeNode::tagEquals(childNode, "received")) { + Key* key = new Key(*from, true, *id); + FMessage* message = new FMessage(key); + message->status = FMessage::STATUS_RECEIVED_BY_TARGET; + if (this->event_handler != NULL) + this->event_handler->onMessageStatusUpdate(message); + delete message; + if (this->supportsReceiptAcks()) { + std::string* receipt_type = childNode->getAttributeValue("type"); + if ((receipt_type == NULL) || (receipt_type->compare("delivered") == 0)) + sendDeliveredReceiptAck(*from, *id); + else if (receipt_type->compare("visible") == 0) + sendVisibleReceiptAck(*from, *id); + } + } else if (ProtocolTreeNode::tagEquals(childNode, "offline")) { + if (attribute_t != NULL) { + fmessage->timestamp = atoi(attribute_t->c_str()); + } + fmessage->offline = true; + } + } + } + + if (fmessage->timestamp == 0) { + fmessage->timestamp = time(NULL); + fmessage->offline = false; + } + + if (fmessage->key != NULL && this->event_handler != NULL) + this->event_handler->onMessageForMe(fmessage, duplicate); + + delete fmessage; + } else if (typeAttribute->compare("notification") == 0) { + _LOGDATA("Notification node %s", messageNode->toString().c_str()); + bool flag = false; + std::vector myVector(0); + std::vector* children = messageNode->children == NULL? &myVector: messageNode->getAllChildren(); + for (int i = 0; i < children->size(); i++) { + ProtocolTreeNode* child = (*children)[i]; + if (ProtocolTreeNode::tagEquals(child, "notification")) { + std::string* type = child->getAttributeValue("type"); + if ((type != NULL) && (type->compare("picture") == 0) && (this->event_handler != NULL)) { + std::vector myVector2(0); + std::vector* children2 = child->children == NULL? &myVector2: child->getAllChildren(); + for (int j = 0; j < children2->size(); j++) { + ProtocolTreeNode* child2 = (*children2)[j]; + if (ProtocolTreeNode::tagEquals(child2, "set")) { + std::string* id = child2->getAttributeValue("id"); + std::string* author = child2->getAttributeValue("author"); + if (id != NULL) { + this->event_handler->onPictureChanged(*from, ((author == NULL)?"":*author), true); + } + } else if (ProtocolTreeNode::tagEquals(child2, "delete")) { + std::string* author = child2->getAttributeValue("author"); + this->event_handler->onPictureChanged(*from, ((author == NULL)?"":*author), false); + } + } + } + } else if (ProtocolTreeNode::tagEquals(child, "request")) { + flag = true; + } + } + if (flag) { + this->sendNotificationReceived(*from, *id); + } + } + } +} + +bool WAConnection::supportsReceiptAcks() { + return (this->login != NULL) && (this->login->supports_receipt_acks); +} + +void WAConnection::sendNotificationReceived(const std::string& jid, const std::string& id) throw(WAException) { + std::map* attribs1 = new std::map(); + (*attribs1)["xmlns"] = "urn:xmpp:receipts"; + ProtocolTreeNode* child = new ProtocolTreeNode("received", attribs1); + + std::map* attribs2 = new std::map(); + (*attribs2)["id"] = id; + (*attribs2)["type"] = "notification"; + (*attribs2)["to"] = jid; + ProtocolTreeNode* node = new ProtocolTreeNode("message", attribs2, child); + + this->out->write(node); + delete node; +} + +void WAConnection::sendClose() throw(WAException) { + std::map* attribs1 = new std::map(); + (*attribs1)["type"] = "unavailable"; + ProtocolTreeNode* presenceNode = new ProtocolTreeNode("presence", attribs1); + this->out->write(presenceNode); + delete presenceNode; + this->out->streamEnd(); +} + +void WAConnection::sendGetPrivacyList() throw (WAException) { + std::string id = makeId("privacylist_"); + this->pending_server_requests[id] = new IqResultPrivayListHandler(this); + + std::map* attribs1 = new std::map(); + (*attribs1)["name"] = "default"; + ProtocolTreeNode* listNode = new ProtocolTreeNode("list", attribs1); + + std::map* attribs2 = new std::map(); + (*attribs2)["xmlns"] = "jabber:iq:privacy"; + ProtocolTreeNode* queryNode = new ProtocolTreeNode("query", attribs2, listNode); + + std::map* attribs3 = new std::map(); + (*attribs3)["id"] = id; + (*attribs3)["type"] = "get"; + ProtocolTreeNode* iqNode = new ProtocolTreeNode("iq", attribs3, queryNode); + + this->out->write(iqNode); + delete iqNode; +} + +void WAConnection::sendGetServerProperties() throw (WAException) { + std::string id = makeId("get_server_properties_"); + this->pending_server_requests[id] = new IqResultServerPropertiesHandler(this); + + std::map* attribs1 = new std::map(); + (*attribs1)["xmlns"] = "w:g"; + (*attribs1)["type"] = "props"; + ProtocolTreeNode* listNode = new ProtocolTreeNode("list", attribs1); + + std::map* attribs2 = new std::map(); + (*attribs2)["id"] = id; + (*attribs2)["type"] = "get"; + (*attribs2)["to"] = "g.us"; + ProtocolTreeNode* iqNode = new ProtocolTreeNode("iq", attribs2, listNode); + + this->out->write(iqNode); + delete iqNode; +} + +void WAConnection::sendGetGroups() throw (WAException) { + this->mutex->lock(); + std::string id = makeId("get_groups_"); + this->pending_server_requests[id] = new IqResultGetGroupsHandler(this, "participating"); + + sendGetGroups(id, "participating"); + this->mutex->unlock(); +} + +void WAConnection::sendGetOwningGroups() throw (WAException) { + this->mutex->lock(); + std::string id = makeId("get_owning_groups_"); + this->pending_server_requests[id] = new IqResultGetGroupsHandler(this, "owning"); + + sendGetGroups(id, "owning"); + this->mutex->unlock(); +} + +void WAConnection::sendGetGroups(const std::string& id, const std::string& type) throw (WAException) { + std::map* attribs1 = new std::map(); + (*attribs1)["xmlns"] = "w:g"; + (*attribs1)["type"] = type; + ProtocolTreeNode* listNode = new ProtocolTreeNode("list", attribs1); + + std::map* attribs2 = new std::map(); + (*attribs2)["id"] = id; + (*attribs2)["type"] = "get"; + (*attribs2)["to"] = "g.us"; + ProtocolTreeNode* iqNode = new ProtocolTreeNode("iq", attribs2, listNode); + + this->out->write(iqNode); + delete iqNode; +} + +void WAConnection::readGroupList(ProtocolTreeNode* node, std::vector& groups) throw (WAException) { + std::vector* nodes = node->getAllChildren("group"); + for (size_t i = 0; i < nodes->size(); i++) { + ProtocolTreeNode* groupNode = (*nodes)[i]; + std::string* gid = groupNode->getAttributeValue("id"); + std::string gjid = gidToGjid(*gid); + std::string* owner = groupNode->getAttributeValue("owner"); + std::string* subject = groupNode->getAttributeValue("subject"); + std::string* subject_t = groupNode->getAttributeValue("s_t"); + std::string* subject_owner = groupNode->getAttributeValue("s_o"); + std::string* creation = groupNode->getAttributeValue("creation"); + if (this->group_event_handler != NULL) + this->group_event_handler->onGroupInfoFromList(gjid, *owner, *subject, *subject_owner, atoi(subject_t->c_str()), atoi(creation->c_str())); + groups.push_back(gjid); + } + delete nodes; +} + +std::string WAConnection::gidToGjid(const std::string& gid) { + return gid + "@g.us"; +} + + +void WAConnection::sendQueryLastOnline(const std::string& jid) throw (WAException) { + std::string id = makeId("last_"); + this->pending_server_requests[id] = new IqResultQueryLastOnlineHandler(this); + + std::map* attribs1 = new std::map(); + (*attribs1)["xmlns"] = "jabber:iq:last"; + ProtocolTreeNode* queryNode = new ProtocolTreeNode("query", attribs1); + + std::map* attribs2 = new std::map(); + (*attribs2)["id"] = id; + (*attribs2)["type"] = "get"; + (*attribs2)["to"] = jid; + ProtocolTreeNode* iqNode = new ProtocolTreeNode("iq", attribs2, queryNode); + + this->out->write(iqNode); + delete iqNode; +} + +void WAConnection::sendGetGroupInfo(const std::string& gjid) throw (WAException) { + std::string id = makeId("get_g_info_"); + this->pending_server_requests[id] = new IqResultGetGroupInfoHandler(this); + + std::map* attribs1 = new std::map(); + (*attribs1)["xmlns"] = "w:g"; + ProtocolTreeNode* queryNode = new ProtocolTreeNode("query", attribs1); + + std::map* attribs2 = new std::map(); + (*attribs2)["id"] = id; + (*attribs2)["type"] = "get"; + (*attribs2)["to"] = gjid; + ProtocolTreeNode* iqNode = new ProtocolTreeNode("iq", attribs2, queryNode); + + this->out->write(iqNode); + delete iqNode; +} + +void WAConnection::sendGetParticipants(const std::string& gjid) throw (WAException) { + std::string id = makeId("get_participants_"); + this->pending_server_requests[id] = new IqResultGetGroupParticipantsHandler(this); + + std::map* attribs1 = new std::map(); + (*attribs1)["xmlns"] = "w:g"; + ProtocolTreeNode* listNode = new ProtocolTreeNode("list", attribs1); + + std::map* attribs2 = new std::map(); + (*attribs2)["id"] = id; + (*attribs2)["type"] = "get"; + (*attribs2)["to"] = gjid; + ProtocolTreeNode* iqNode = new ProtocolTreeNode("iq", attribs2, listNode); + + this->out->write(iqNode); + delete iqNode; +} + +void WAConnection::readAttributeList(ProtocolTreeNode* node, std::vector& vector, const std::string& tag, const std::string& attribute) throw (WAException) { + std::vector* nodes = node->getAllChildren(tag); + for (size_t i = 0; i < nodes->size(); i++) { + ProtocolTreeNode* tagNode = (*nodes)[i]; + std::string* value = tagNode->getAttributeValue(attribute); + vector.push_back(*value); + } + delete nodes; +} + +void WAConnection::sendCreateGroupChat(const std::string& subject) throw (WAException){ + _LOGDATA("sending create group: %s", subject.c_str()); + std::string id = makeId("create_group_"); + this->pending_server_requests[id] = new IqResultCreateGroupChatHandler(this); + + std::map* attribs1 = new std::map(); + (*attribs1)["xmlns"] = "w:g"; + (*attribs1)["action"] = "create"; + (*attribs1)["subject"] = subject; + ProtocolTreeNode* groupNode = new ProtocolTreeNode("group", attribs1); + + std::map* attribs2 = new std::map(); + (*attribs2)["id"] = id; + (*attribs2)["type"] = "set"; + (*attribs2)["to"] = "g.us"; + ProtocolTreeNode* iqNode = new ProtocolTreeNode("iq", attribs2, groupNode); + + this->out->write(iqNode); + delete iqNode; +} + +void WAConnection::sendEndGroupChat(const std::string& gjid) throw (WAException){ + std::string id = makeId("remove_group_"); + + std::map* attribs1 = new std::map(); + (*attribs1)["xmlns"] = "w:g"; + (*attribs1)["action"] = "delete"; + ProtocolTreeNode* groupNode = new ProtocolTreeNode("group", attribs1); + + std::map* attribs2 = new std::map(); + (*attribs2)["id"] = id; + (*attribs2)["type"] = "set"; + (*attribs2)["to"] = gjid ; + ProtocolTreeNode* iqNode = new ProtocolTreeNode("iq", attribs2, groupNode); + + this->out->write(iqNode); + delete iqNode; +} + +void WAConnection::sendClearDirty(const std::string& category) throw (WAException) { + std::string id = makeId("clean_dirty_"); + this->pending_server_requests[id] = new IqResultClearDirtyHandler(this); + + std::map* attribs1 = new std::map(); + (*attribs1)["name"] = category; + ProtocolTreeNode* categoryNode = new ProtocolTreeNode("category", attribs1); + + std::map* attribs2 = new std::map(); + (*attribs2)["xmlns"] = "urn:xmpp:whatsapp:dirty"; + ProtocolTreeNode* cleanNode = new ProtocolTreeNode("clean", attribs2, categoryNode); + + std::map* attribs3 = new std::map(); + (*attribs3)["id"] = id; + (*attribs3)["type"] = "set"; + (*attribs3)["to"] = "s.whatsapp.net"; + ProtocolTreeNode* iqNode = new ProtocolTreeNode("iq", attribs3, cleanNode); + + this->out->write(iqNode); + delete iqNode; +} + +void WAConnection::sendLeaveGroup(const std::string& gjid) throw (WAException) { + std::string id = makeId("leave_group_"); + + std::map* attribs1 = new std::map(); + (*attribs1)["id"] = gjid; + ProtocolTreeNode* groupNode = new ProtocolTreeNode("group", attribs1); + + std::map* attribs2 = new std::map(); + (*attribs2)["xmlns"] = "w:g"; + ProtocolTreeNode* leaveNode = new ProtocolTreeNode("leave", attribs2, groupNode); + + std::map* attribs3 = new std::map(); + (*attribs3)["id"] = id; + (*attribs3)["type"] = "set"; + (*attribs3)["to"] = "g.us"; + ProtocolTreeNode* iqNode = new ProtocolTreeNode("iq", attribs3, leaveNode); + + this->out->write(iqNode); + delete iqNode; +} + +void WAConnection::sendAddParticipants(const std::string& gjid, const std::vector& participants) throw (WAException) { + std::string id = makeId("add_group_participants_"); + this->sendVerbParticipants(gjid, participants, id, "add"); +} + +void WAConnection::sendRemoveParticipants(const std::string& gjid, const std::vector& participants) throw (WAException) { + std::string id = makeId("remove_group_participants_"); + this->sendVerbParticipants(gjid, participants, id, "remove"); +} + +void WAConnection::sendVerbParticipants(const std::string& gjid, const std::vector& participants, const std::string& id, const std::string& inner_tag) throw (WAException) { + size_t size = participants.size(); + std::vector* children = new std::vector(size); + for (int i = 0; i < size; i++) { + std::map* attribs1 = new std::map(); + (*attribs1)["jid"] = participants[i]; + (*children)[i] = new ProtocolTreeNode("participant", attribs1); + } + + std::map* attribs2 = new std::map(); + (*attribs2)["xmlns"] = "w:g"; + ProtocolTreeNode* innerNode = new ProtocolTreeNode(inner_tag, attribs2, NULL, children); + + std::map* attribs3 = new std::map(); + (*attribs3)["id"] = id; + (*attribs3)["type"] = "set"; + (*attribs3)["to"] = gjid; + ProtocolTreeNode* iqNode = new ProtocolTreeNode("iq", attribs3, innerNode); + + this->out->write(iqNode); + delete iqNode; +} + +void WAConnection::sendSetNewSubject(const std::string& gjid, const std::string& subject) throw (WAException) { + std::string id = this->makeId("set_group_subject_"); + + std::map* attribs1 = new std::map(); + (*attribs1)["xmlns"] = "w:g"; + (*attribs1)["value"] = subject; + ProtocolTreeNode* subjectNode = new ProtocolTreeNode("subject", attribs1); + + std::map* attribs2 = new std::map(); + (*attribs2)["id"] = id; + (*attribs2)["type"] = "set"; + (*attribs2)["to"] = gjid; + ProtocolTreeNode* iqNode = new ProtocolTreeNode("iq", attribs2, subjectNode); + + this->out->write(iqNode); + delete iqNode; +} + +std::string WAConnection::removeResourceFromJid(const std::string& jid) { + size_t slashidx = jid.find('/'); + if (slashidx == std::string::npos) + return jid; + + return jid.substr(0, slashidx + 1); +} + +void WAConnection::sendStatusUpdate(std::string& status) throw (WAException) { + std::string id = this->makeId(Utilities::intToStr(time(NULL))); + FMessage* message = new FMessage(new Key("s.us", true, id)); + ProtocolTreeNode* body = new ProtocolTreeNode("body", NULL, new std::vector(status.begin(), status.end()), NULL); + ProtocolTreeNode* messageNode = getMessageNode(message, body); + this->out->write(messageNode); + delete messageNode; + delete message; +} + + + +void WAConnection::sendSetPicture(const std::string& jid, std::vector* data) throw (WAException) { + std::string id = this->makeId("set_photo_"); + this->pending_server_requests[id] = new IqResultSetPhotoHandler(this, jid); + + std::map* attribs1 = new std::map(); + (*attribs1)["xmlns"] = "w:profile:picture"; + // (*attribs1)["type"] = "image"; + ProtocolTreeNode* listNode = new ProtocolTreeNode("picture", attribs1, data, NULL); + + std::map* attribs2 = new std::map(); + (*attribs2)["id"] = id; + (*attribs2)["type"] = "set"; + (*attribs2)["to"] = jid; + + ProtocolTreeNode* iqNode = new ProtocolTreeNode("iq", attribs2, listNode); + this->out->write(iqNode); + delete iqNode; +} + +void WAConnection::sendGetPicture(const std::string& jid, const std::string& type, const std::string& oldId, const std::string& newId) throw (WAException) { + std::string id = makeId("get_picture_"); + this->pending_server_requests[id] = new IqResultGetPhotoHandler(this, jid, oldId, newId); + + std::map* attribs1 = new std::map(); + (*attribs1)["xmlns"] = "w:profile:picture"; + (*attribs1)["type"] = type; + ProtocolTreeNode* listNode = new ProtocolTreeNode("picture", attribs1); + + std::map* attribs2 = new std::map(); + (*attribs2)["id"] = id; + (*attribs2)["to"] = jid; + (*attribs2)["type"] = "get"; + ProtocolTreeNode* iqNode = new ProtocolTreeNode("iq", attribs2, listNode); + + this->out->write(iqNode); + delete iqNode; +} + +void WAConnection::sendGetPictureIds(const std::vector& jids) throw (WAException) { + std::string id = makeId("get_picture_ids_"); + this->pending_server_requests[id] = new IqResultGetPictureIdsHandler(this); + + std::vector* children = new std::vector(); + for (int i = 0; i < jids.size(); i++) { + std::map* attribs = new std::map(); + (*attribs)["jid"] = jids[i]; + ProtocolTreeNode* child = new ProtocolTreeNode("user", attribs); + children->push_back(child); + } + + std::map* attribs1 = new std::map(); + (*attribs1)["xmlns"] = "w:profile:picture"; + ProtocolTreeNode* queryNode = new ProtocolTreeNode("list", attribs1, NULL, children); + + std::map* attribs2 = new std::map(); + (*attribs2)["id"] = id; + (*attribs2)["type"] = "get"; + ProtocolTreeNode* iqNode = new ProtocolTreeNode("iq", attribs2, queryNode); + + this->out->write(iqNode); + delete iqNode; +} + +void WAConnection::sendDeleteAccount() throw (WAException) { + std::string id = makeId("del_acct_"); + this->pending_server_requests[id] = new IqResultSendDeleteAccount(this); + + std::map* attribs1 = new std::map(); + (*attribs1)["xmlns"] = "urn:xmpp:whatsapp:account"; + ProtocolTreeNode* node1 = new ProtocolTreeNode("remove", attribs1); + + std::map* attribs2 = new std::map(); + (*attribs2)["id"] = id; + (*attribs2)["type"] = "get"; + (*attribs2)["to"] = "s.whatsapp.net"; + + ProtocolTreeNode* node2 = new ProtocolTreeNode("iq", attribs2, node1); + this->out->write(node2); + delete node2; +} diff --git a/protocols/WhatsApp/src/WhatsAPI++/WAConnection.h b/protocols/WhatsApp/src/WhatsAPI++/WAConnection.h new file mode 100644 index 0000000000..b692924f46 --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/WAConnection.h @@ -0,0 +1,464 @@ +/* + * WAConnection.h + * + * Created on: 26/06/2012 + * Author: Antonio + */ + + + + +#ifndef WACONNECTION_H_ +#define WACONNECTION_H_ + +#include +#include +#include +#include "WAException.h" +#include "FMessage.h" +#include "WALogin.h" +#include "utilities.h" +#include "BinTreeNodeReader.h" +#include "BinTreeNodeWriter.h" + +class WALogin; +class KeyStream; +class BinTreeNodeReader; + +class WAListener { +public: + virtual void onMessageForMe(FMessage* paramFMessage, bool paramBoolean) throw (WAException)=0; + virtual void onMessageStatusUpdate(FMessage* paramFMessage)=0; + virtual void onMessageError(FMessage* message, int paramInt)=0; + virtual void onPing(const std::string& paramString) throw (WAException)=0; + virtual void onPingResponseReceived()=0; + virtual void onAvailable(const std::string& paramString, bool paramBoolean)=0; + virtual void onClientConfigReceived(const std::string& paramString)=0; + virtual void onLastSeen(const std::string& paramString1, int paramInt, std::string* paramString2)=0; + virtual void onIsTyping(const std::string& paramString, bool paramBoolean)=0; + virtual void onAccountChange(int paramInt, long paramLong)=0; + virtual void onPrivacyBlockListAdd(const std::string& paramString)=0; + virtual void onPrivacyBlockListClear()=0; + virtual void onDirty(const std::map& paramHashtable)=0; + virtual void onDirtyResponse(int paramHashtable)=0; + virtual void onRelayRequest(const std::string& paramString1, int paramInt, const std::string& paramString2)=0; + virtual void onSendGetPictureIds(std::map* ids)=0; + virtual void onSendGetPicture(const std::string& jid, const std::vector& data, const std::string& oldId, const std::string& newId)=0; + virtual void onPictureChanged(const std::string& from, const std::string& author, bool set)=0; + virtual void onDeleteAccount(bool result)=0; +}; + +class WAGroupListener { +public: + virtual void onGroupAddUser(const std::string& paramString1, const std::string& paramString2)=0; + virtual void onGroupRemoveUser(const std::string& paramString1, const std::string& paramString2)=0; + virtual void onGroupNewSubject(const std::string& from, const std::string& author, const std::string& newSubject, int paramInt)=0; + virtual void onServerProperties(std::map* nameValueMap)=0; + virtual void onGroupCreated(const std::string& paramString1, const std::string& paramString2)=0; + virtual void onGroupInfo(const std::string& paramString1, const std::string& paramString2, const std::string& paramString3, const std::string& paramString4, int paramInt1, int paramInt2)=0; + virtual void onGroupInfoFromList(const std::string& paramString1, const std::string& paramString2, const std::string& paramString3, const std::string& paramString4, int paramInt1, int paramInt2)=0; + virtual void onOwningGroups(const std::vector& paramVector)=0; + virtual void onSetSubject(const std::string& paramString)=0; + virtual void onAddGroupParticipants(const std::string& paramString, const std::vector& paramVector, int paramHashtable)=0; + virtual void onRemoveGroupParticipants(const std::string& paramString, const std::vector& paramVector, int paramHashtable)=0; + virtual void onGetParticipants(const std::string& gjid, const std::vector& participants)=0; + virtual void onParticipatingGroups(const std::vector& paramVector)=0; + virtual void onLeaveGroup(const std::string& paramString)=0; +}; + + + +class MessageStore { +public: + MessageStore(); + + virtual FMessage* get(Key* key); + + virtual ~MessageStore(); +}; + + +class GroupSetting { +public: + std::string jid; + bool enabled; + time_t muteExpiry; + + GroupSetting() { + enabled = true; + jid = ""; + muteExpiry = 0; + } +}; + +class WAConnection { + + class IqResultHandler { + protected: + WAConnection* con; + public: + IqResultHandler(WAConnection* con) {this->con = con;} + virtual void parse(ProtocolTreeNode* paramProtocolTreeNode, const std::string& paramString) throw (WAException)=0; + void error(ProtocolTreeNode* node, int code) { + _LOGDATA("WAConnection: error node %s: code = %d", node->getAttributeValue("id")->c_str(), code); + } + void error(ProtocolTreeNode* node) throw (WAException) { + std::vector* nodes = node->getAllChildren("error"); + for (size_t i = 0; i < nodes->size(); i++) { + ProtocolTreeNode* errorNode = (*nodes)[i]; + if (errorNode != NULL) { + std::string* errorCodeString = errorNode->getAttributeValue("code"); + if (errorCodeString != NULL) { + int errorCode = atoi(errorCodeString->c_str()); + error(node, errorCode); + } + } + } + delete nodes; + } + + virtual ~IqResultHandler() {} + + }; + + class IqResultPingHandler: public IqResultHandler { + public: + IqResultPingHandler(WAConnection* con):IqResultHandler(con) {} + virtual void parse(ProtocolTreeNode* node, const std::string& from) throw (WAException) { + if (this->con->event_handler != NULL) + this->con->event_handler->onPingResponseReceived(); + } + + void error(ProtocolTreeNode* node) throw (WAException) { + if (this->con->event_handler != NULL) + this->con->event_handler->onPingResponseReceived(); + } + }; + + class IqResultGetGroupsHandler: public IqResultHandler { + private: + std::string type; + public: + IqResultGetGroupsHandler(WAConnection* con, const std::string& type ):IqResultHandler(con) {this->type = type;} + virtual void parse(ProtocolTreeNode* node, const std::string& from) throw (WAException) { + std::vector groups; + this->con->readGroupList(node, groups); + if (this->con->group_event_handler != NULL) { + if (this->type.compare("participating") == 0) + this->con->group_event_handler->onParticipatingGroups(groups); + else if (this->type.compare("owning") == 0) + this->con->group_event_handler->onOwningGroups(groups); + } + } + }; + + class IqResultServerPropertiesHandler: public IqResultHandler { + public: + IqResultServerPropertiesHandler(WAConnection* con):IqResultHandler(con) {} + virtual void parse(ProtocolTreeNode* node, const std::string& from) throw (WAException) { + std::vector* nodes = node->getAllChildren("prop"); + std::map nameValueMap; + for (size_t i = 0; i < nodes->size();i++) { + ProtocolTreeNode* propNode = (*nodes)[i]; + std::string* nameAttr = propNode->getAttributeValue("name"); + std::string* valueAttr = propNode->getAttributeValue("value"); + nameValueMap[*nameAttr] = *valueAttr; + } + delete nodes; + if (this->con->group_event_handler != NULL) + this->con->group_event_handler->onServerProperties(&nameValueMap); + } + }; + + class IqResultPrivayListHandler: public IqResultHandler { + public: + IqResultPrivayListHandler(WAConnection* con):IqResultHandler(con) {} + virtual void parse(ProtocolTreeNode* node, const std::string& from) throw (WAException) { + ProtocolTreeNode* queryNode = node->getChild(0); + ProtocolTreeNode::require(queryNode, "query"); + ProtocolTreeNode* listNode = queryNode->getChild(0); + ProtocolTreeNode::require(listNode, "list"); + if (this->con->event_handler != NULL) + this->con->event_handler->onPrivacyBlockListClear(); + if (listNode->children != NULL) { + for (size_t i = 0; i < listNode->children->size(); i++) { + ProtocolTreeNode* itemNode = (*listNode->children)[i]; + ProtocolTreeNode::require(itemNode, "item"); + if (itemNode->getAttributeValue("type")->compare("jid") == 0) { + std::string* jid = itemNode->getAttributeValue("value"); + if (jid != NULL && this->con->event_handler != NULL) + this->con->event_handler->onPrivacyBlockListAdd(*jid); + } + } + } + } + }; + + class IqResultGetGroupInfoHandler: public IqResultHandler { + public: + IqResultGetGroupInfoHandler(WAConnection* con):IqResultHandler(con) {} + virtual void parse(ProtocolTreeNode* node, const std::string& from) throw (WAException) { + ProtocolTreeNode* groupNode = node->getChild(0); + ProtocolTreeNode::require(groupNode, "group"); + // std::string* gid = groupNode->getAttributeValue("id"); + std::string* owner = groupNode->getAttributeValue("owner"); + std::string* subject = groupNode->getAttributeValue("subject"); + std::string* subject_t = groupNode->getAttributeValue("s_t"); + std::string* subject_owner = groupNode->getAttributeValue("s_o"); + std::string* creation = groupNode->getAttributeValue("creation"); + if (this->con->group_event_handler != NULL) + this->con->group_event_handler->onGroupInfo(from, *owner, *subject, *subject_owner, atoi(subject_t->c_str()), atoi(creation->c_str())); + } + }; + + class IqResultGetGroupParticipantsHandler: public IqResultHandler { + public: + IqResultGetGroupParticipantsHandler(WAConnection* con):IqResultHandler(con) {} + virtual void parse(ProtocolTreeNode* node, const std::string& from) throw (WAException) { + std::vector participants; + this->con->readAttributeList(node, participants, "participant", "jid"); + if (this->con->group_event_handler != NULL) + this->con->group_event_handler->onGetParticipants(from, participants); + } + }; + + class IqResultCreateGroupChatHandler: public IqResultHandler { + public: + IqResultCreateGroupChatHandler(WAConnection* con):IqResultHandler(con) {} + virtual void parse(ProtocolTreeNode* node, const std::string& from) throw (WAException) { + ProtocolTreeNode* groupNode = node->getChild(0); + ProtocolTreeNode::require(groupNode, "group"); + std::string* groupId = groupNode->getAttributeValue("id"); + if (groupId != NULL && con->group_event_handler != NULL) + this->con->group_event_handler->onGroupCreated(from, *groupId); + } + }; + + class IqResultQueryLastOnlineHandler: public IqResultHandler { + public: + IqResultQueryLastOnlineHandler(WAConnection* con):IqResultHandler(con) {} + virtual void parse(ProtocolTreeNode* node, const std::string& from) throw (WAException) { + ProtocolTreeNode* firstChild = node->getChild(0); + ProtocolTreeNode::require(firstChild, "query"); + std::string* seconds = firstChild->getAttributeValue("seconds"); + std::string* status = NULL; + status = firstChild->getDataAsString(); + if (seconds != NULL && !from.empty()) { + if (this->con->event_handler != NULL) + this->con->event_handler->onLastSeen(from, atoi(seconds->c_str()), status); + } + delete status; + } + }; + + class IqResultGetPhotoHandler: public IqResultHandler { + private: + std::string jid; + std::string oldId; + std::string newId; + public: + IqResultGetPhotoHandler(WAConnection* con, const std::string& jid, const std::string& oldId, const std::string& newId):IqResultHandler(con) { + this->jid = jid; + this->oldId = oldId; + this->newId = newId; + } + virtual void parse(ProtocolTreeNode* node, const std::string& from) throw (WAException) { + std::string* attributeValue = node->getAttributeValue("type"); + + if ((attributeValue != NULL) && (attributeValue->compare("result") == 0) && (this->con->event_handler != NULL)) { + std::vector* children = node->getAllChildren("picture"); + for (int i = 0; i < children->size(); i++) { + ProtocolTreeNode* current = (*children)[i]; + std::string* id = current->getAttributeValue("id"); + if ((id != NULL) && (current->data != NULL) && (current->data->size() > 0)) { + if (current->data != NULL) { + this->con->event_handler->onSendGetPicture(this->jid, *current->data, this->oldId, this->newId); + } + break; + } + } + delete children; + } + } + void error(ProtocolTreeNode* node) throw (WAException) { + if (this->con->event_handler != NULL) { + std::vector v; + this->con->event_handler->onSendGetPicture("error", v, "", ""); + } + } + }; + + class IqResultSetPhotoHandler: public IqResultHandler { + private: + std::string jid; + public: + IqResultSetPhotoHandler(WAConnection* con, const std::string& jid):IqResultHandler(con) {this->jid = jid;} + virtual void parse(ProtocolTreeNode* node, const std::string& from) throw (WAException) { + if (this->con->event_handler != NULL) { + std::string* photoId = NULL; + ProtocolTreeNode* child = node->getChild("picture"); + if (child != NULL) { + this->con->event_handler->onPictureChanged(this->jid, "", true); + } else { + this->con->event_handler->onPictureChanged(this->jid, "", false); + } + } + } + }; + + + class IqResultGetPictureIdsHandler: public IqResultHandler { + public: + IqResultGetPictureIdsHandler(WAConnection* con):IqResultHandler(con) {} + virtual void parse(ProtocolTreeNode* node, const std::string& from) throw (WAException) { + // _LOGDATA("onGetPhotoIds %s", node->toString().c_str()); + ProtocolTreeNode* groupNode = node->getChild("list"); + std::vector* children = groupNode->getAllChildren("user"); + std::map ids; + for (int i = 0; i < children->size(); i++) { + std::string* jid = (*children)[i]->getAttributeValue("jid"); + std::string* id = (*children)[i]->getAttributeValue("id"); + if (jid != NULL) { + ids[*jid] = (id == NULL? "": *id); + } + } + delete children; + + if (this->con->event_handler != NULL) { + this->con->event_handler->onSendGetPictureIds(&ids); + } + } + }; + + class IqResultSendDeleteAccount: public IqResultHandler { + public: + IqResultSendDeleteAccount(WAConnection* con):IqResultHandler(con) {} + virtual void parse(ProtocolTreeNode* node, const std::string& from) throw (WAException) { + if (this->con->event_handler != NULL) { + this->con->event_handler->onDeleteAccount(true); + } + } + + void error(ProtocolTreeNode* node) throw (WAException) { + if (this->con->event_handler != NULL) { + this->con->event_handler->onDeleteAccount(false); + } + } + }; + + class IqResultClearDirtyHandler: public IqResultHandler { + public: + IqResultClearDirtyHandler(WAConnection* con):IqResultHandler(con) {} + virtual void parse(ProtocolTreeNode* node, const std::string& from) throw (WAException) { + } + }; + + class IqSendClientConfigHandler: public IqResultHandler { + public: + IqSendClientConfigHandler(WAConnection* con):IqResultHandler(con) {} + virtual void parse(ProtocolTreeNode* node, const std::string& from) throw (WAException) { + _LOGDATA("Clientconfig response %s", node->toString().c_str()); + } + + void error(ProtocolTreeNode* node) throw (WAException) { + _LOGDATA("Clientconfig response error %s", node->toString().c_str()); + } + }; + + + private: + WALogin* login; + BinTreeNodeReader* in; + BinTreeNodeWriter* out; + WAListener* event_handler; + WAGroupListener* group_event_handler; + bool verbose; + int iqid; + std::map pending_server_requests; + IMutex* mutex; + + // std:: message_store; + + void init(WAListener* event_handler, WAGroupListener* group_event_handler, IMutex* mutex); + void sendMessageWithMedia(FMessage* message) throw(WAException); + void sendMessageWithBody(FMessage* message) throw(WAException); + std::map* parseCategories(ProtocolTreeNode* node) throw(WAException); + void parseMessageInitialTagAlreadyChecked(ProtocolTreeNode* node) throw(WAException); + ProtocolTreeNode* getReceiptAck(const std::string& to, const std::string& id, const std::string& receiptType) throw(WAException); + std::string makeId(const std::string& prefix); + void sendGetGroups(const std::string& id, const std::string& type) throw (WAException); + void readGroupList(ProtocolTreeNode* node, std::vector& groups) throw (WAException); + std::string gidToGjid(const std::string& gid); + void readAttributeList(ProtocolTreeNode* node, std::vector& vector, const std::string& tag, const std::string& attribute) throw (WAException); + void sendVerbParticipants(const std::string& gjid, const std::vector& participants, const std::string& id, const std::string& inner_tag) throw (WAException); + bool supportsReceiptAcks(); + static ProtocolTreeNode* getMessageNode(FMessage* message, ProtocolTreeNode* node); + static ProtocolTreeNode* getSubjectMessage(const std::string& to, const std::string& id, ProtocolTreeNode* child) throw (WAException); + std::vector* processGroupSettings(const std::vector& gruops); + + public: + WAConnection(IMutex* mutex, WAListener* event_handler = NULL, WAGroupListener* group_event_handler = NULL); + virtual ~WAConnection(); + std::string jid; + std::string fromm; + int msg_id; + int state; + bool retry; + time_t expire_date; + int account_kind; + time_t lastTreeRead; + static const int DICTIONARY_LEN = 237; + static const char* dictionary[]; + static MessageStore* message_store; + KeyStream* inputKey; + KeyStream* outputKey; + + + static std::string removeResourceFromJid(const std::string& jid); + + WALogin* getLogin(); + void setLogin(WALogin* login); + void setVerboseId(bool b); + void sendMessage(FMessage* message) throw(WAException); + void sendAvailableForChat() throw(WAException); + bool read() throw(WAException); + void sendNop() throw(WAException); + void sendPing() throw(WAException); + void sendQueryLastOnline(const std::string& jid) throw (WAException); + void sendPong(const std::string& id) throw(WAException); + void sendComposing(const std::string& to) throw(WAException); + void sendActive() throw(WAException); + void sendInactive() throw(WAException); + void sendPaused(const std::string& to) throw(WAException); + void sendSubjectReceived(const std::string& to, const std::string& id) throw(WAException); + void sendMessageReceived(FMessage* message) throw(WAException); + void sendDeliveredReceiptAck(const std::string& to, const std::string& id) throw(WAException); + void sendVisibleReceiptAck(const std::string& to, const std::string& id) throw (WAException); + void sendPresenceSubscriptionRequest(const std::string& to) throw (WAException); + void sendClientConfig(const std::string& sound, const std::string& pushID, bool preview, const std::string& platform) throw(WAException); + void sendClientConfig(const std::string& pushID, bool preview, const std::string& platform, bool defaultSettings, bool groupSettings, const std::vector& groups) throw(WAException); + void sendClose() throw (WAException); + void sendAvailable() throw (WAException); // U.H. + void sendGetPrivacyList() throw (WAException); + void sendGetServerProperties() throw (WAException); + void sendGetGroups() throw (WAException); + void sendGetOwningGroups() throw (WAException); + void sendCreateGroupChat(const std::string& subject) throw (WAException); + void sendEndGroupChat(const std::string& gjid) throw (WAException); + void sendGetGroupInfo(const std::string& gjid) throw (WAException); + void sendGetParticipants(const std::string& gjid) throw (WAException); + void sendClearDirty(const std::string& category) throw (WAException); + void sendLeaveGroup(const std::string& gjid) throw (WAException); + void sendAddParticipants(const std::string& gjid, const std::vector& participants) throw (WAException); + void sendRemoveParticipants(const std::string& gjid, const std::vector& participants) throw (WAException); + void sendSetNewSubject(const std::string& gjid, const std::string& subject) throw (WAException); + void sendStatusUpdate(std::string& status) throw (WAException); + void sendGetPicture(const std::string& jid, const std::string& type, const std::string& oldId, const std::string& newId) throw (WAException); + void sendGetPictureIds(const std::vector& jids) throw (WAException); + void sendSetPicture(const std::string& jid, std::vector* data) throw (WAException); + void sendNotificationReceived(const std::string& from, const std::string& id) throw(WAException); + void sendDeleteAccount() throw(WAException); +}; + + +#endif /* WACONNECTION_H_ */ diff --git a/protocols/WhatsApp/src/WhatsAPI++/WAException.h b/protocols/WhatsApp/src/WhatsAPI++/WAException.h new file mode 100644 index 0000000000..47d5cdfaf7 --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/WAException.h @@ -0,0 +1,40 @@ +/* + * WAException.h + * + * Created on: 27/06/2012 + * Author: Antonio + */ + + + +#ifndef WAEXCEPTION_H_ +#define WAEXCEPTION_H_ + +#include +#include + +class WAException: public std::runtime_error { +public: + int type; + int subtype; + time_t expire_date; // in seconds + + static const int LOGIN_FAILURE_EX = 1; + static const int LOGIN_FAILURE_EX_TYPE_PASSWORD = 0; + static const int LOGIN_FAILURE_EX_TYPE_EXPIRED = 1; + + static const int CORRUPT_STREAM_EX = 2; + + static const int SOCKET_EX = 3; + static const int SOCKET_EX_RESOLVE_HOST = 0; + static const int SOCKET_EX_OPEN = 1; + static const int SOCKET_EX_INIT = 2; + static const int SOCKET_EX_SEND = 3; + static const int SOCKET_EX_RECV = 4; + + WAException(const std::string& err): runtime_error(err) {this->type = 0; this->subtype = 0; this->expire_date = 0;}; + WAException(const std::string& err, int type, int subtype): runtime_error(err), type(type), subtype(subtype), expire_date(0) {}; + WAException(const std::string& err, int type, int subtype, time_t expireDate): runtime_error(err), type(type), subtype(subtype), expire_date(expireDate) {}; +}; + +#endif /* WAEXCEPTION_H_ */ diff --git a/protocols/WhatsApp/src/WhatsAPI++/WALogin.cpp b/protocols/WhatsApp/src/WhatsAPI++/WALogin.cpp new file mode 100644 index 0000000000..3836a94a3c --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/WALogin.cpp @@ -0,0 +1,342 @@ +/* + * WALogin.cpp + * + * Created on: 26/06/2012 + * Author: Antonio + */ + +#include "stdafx.h" +#include "WALogin.h" +#include "ByteArray.h" +//#include "ApplicationData.h" +#include "ProtocolTreeNode.h" +#include "WAException.h" +#include "base64.h" +#include +#include +#include +#include +#include +#include + + +using namespace Utilities; + +const std::string WALogin::NONCE_KEY = "nonce=\""; + +WALogin::WALogin(WAConnection* connection, BinTreeNodeReader *reader, BinTreeNodeWriter *writer, const std::string& domain, const std::string& user, const std::string& resource, const std::string& password, const std::string& push_name) { + this->connection = connection; + this->inn = reader; + this->out = writer; + this->domain = domain; + this->user = user; + this->resource = resource; + this->password = password; + this->push_name = push_name; + this->supports_receipt_acks = false; + this->account_kind = -1; + this->expire_date = 0L; + this->outputKey = NULL; +} + +std::vector* WALogin::login(const std::vector& authBlob) { + this->out->streamStart(this->domain, this->resource); + + _LOGDATA("sent stream start"); + + sendFeatures(); + + _LOGDATA("sent features"); + + sendAuth(authBlob); + + _LOGDATA("send auth, auth blob size %d", authBlob.size()); + + this->inn->streamStart(); + + _LOGDATA("read stream start"); + + return this->readFeaturesUntilChallengeOrSuccess(); +} + +BinTreeNodeReader* WALogin::getTreeNodeReader() { + return this->inn; +} + +BinTreeNodeWriter* WALogin::getTreeNodeWriter() { + return this->out; +} + +std::string WALogin::getResponse(const std::string& challenge) { + unsigned char md5_buffer[/*MD5_DIGEST_SIZE*/ MD5_DIGEST_LENGTH /*#WORKAROUND*/]; + + size_t i = challenge.find(WALogin::NONCE_KEY); + i += WALogin::NONCE_KEY.length(); + + size_t j = challenge.find('"', i); + std::string nonce = challenge.substr(i,j-i); + + std::string cnonce = str(absLong(randLong()), 36); + _LOGDATA("cnonce = %s", cnonce.c_str()); + std::string nc = "00000001"; + std::string cinfo(this->user + ":" + this->domain + ":" + this->password); + + _LOGDATA("cinfo = %s", cinfo.c_str()); + + ByteArrayOutputStream bos; + _LOGDATA((char*) md5digest((unsigned char*) cinfo.data(), cinfo.length(), md5_buffer), MD5_DIGEST_SIZE); + bos.write(md5digest((unsigned char*) cinfo.data(), cinfo.length(), md5_buffer), MD5_DIGEST_SIZE); + bos.write(58); + bos.write(nonce); + bos.write(58); + bos.write(cnonce); + // bos.print(); + + std::string digest_uri = "xmpp/" + this->domain; + std::vector* A1 = bos.toByteArray(); + std::string A2 = "AUTHENTICATE:" + digest_uri; + std::string KD((char*) bytesToHex(md5digest(&A1->front(), A1->size(), md5_buffer), MD5_DIGEST_SIZE), MD5_DIGEST_SIZE * 2); + KD += + ":" + nonce + ":" + nc + ":" + cnonce + ":auth:" + std::string((char*) bytesToHex(md5digest((unsigned char*) A2.data(), A2.size(), md5_buffer), MD5_DIGEST_SIZE), MD5_DIGEST_SIZE*2); + + _LOGDATA("KD = %s", KD.c_str()); + + std::string response((char*) bytesToHex(md5digest((unsigned char*) KD.data(), KD.size(), md5_buffer), MD5_DIGEST_SIZE), MD5_DIGEST_SIZE*2); + + _LOGDATA("response = %s", response.c_str()); + + std::string bigger_response; + bigger_response.append("realm=\""); + bigger_response.append(this->domain); + bigger_response.append("\",response="); + bigger_response.append(response); + bigger_response.append(",nonce=\""); + bigger_response.append(nonce); + bigger_response.append("\",digest-uri=\""); + bigger_response.append(digest_uri); + bigger_response.append("\",cnonce=\""); + bigger_response.append(cnonce); + bigger_response.append("\",qop=auth"); + bigger_response.append(",username=\""); + bigger_response.append(this->user); + bigger_response.append("\",nc="); + bigger_response.append(nc); + + _LOGDATA("biggerresponse = %s", bigger_response.c_str()); + + delete A1; + + return bigger_response; +} + +void WALogin::sendResponse(const std::vector& challengeData) { + std::vector* authBlob = this->getAuthBlob(challengeData); + + // std::string response = this->getResponse(challengeData); + std::map *attributes = new std::map(); + (*attributes)["xmlns"] = "urn:ietf:params:xml:ns:xmpp-sasl"; + ProtocolTreeNode node("response", attributes, authBlob); + + this->out->write(&node); +} + +void WALogin::sendFeatures() { + ProtocolTreeNode* child = new ProtocolTreeNode("receipt_acks", NULL); + std::vector* children = new std::vector(); + children->push_back(child); + + std::map* attributes = new std::map(); + (*attributes)["type"] = "all"; + ProtocolTreeNode* pictureChild = new ProtocolTreeNode("w:profile:picture", attributes); + children->push_back(pictureChild); + + // children->push_back(new ProtocolTreeNode("status", NULL)); + + ProtocolTreeNode node("stream:features", NULL, NULL, children); + this->out->write(&node, true); +} + +void WALogin::sendAuth(const std::vector& existingChallenge) { + std::vector* data = NULL; + if (!existingChallenge.empty()) { + data = this->getAuthBlob(existingChallenge); + } + + std::map* attributes = new std::map(); + (*attributes)["xmlns"] = "urn:ietf:params:xml:ns:xmpp-sasl"; + (*attributes)["mechanism"] = "WAUTH-1"; + (*attributes)["user"] = this->user; + + ProtocolTreeNode node("auth", attributes, data, NULL); + this->out->write(&node, true); +} + +std::vector* WALogin::getAuthBlob(const std::vector& nonce) { + unsigned char out[20]; + KeyStream::keyFromPasswordAndNonce(this->password, nonce, out); + + if (this->connection->inputKey != NULL) + delete this->connection->inputKey; + this->connection->inputKey = new KeyStream(out, 20); + + if (this->outputKey != NULL) + delete this->outputKey; + + this->outputKey = new KeyStream(out, 20); + std::vector* list = new std::vector(0); + for (int i = 0; i < 4; i++) { + list->push_back(0); + } + list->insert(list->end(), this->user.begin(), this->user.end()); + list->insert(list->end(), nonce.begin(), nonce.end()); + time_t now; + std::string time = Utilities::intToStr(difftime(mktime(gmtime(&now)), 0)); + list->insert(list->end(), time.begin(), time.end()); + + this->outputKey->encodeMessage(&((*list)[0]), 0, 4, list->size() - 4); + return list; +} + +std::vector* WALogin::readFeaturesUntilChallengeOrSuccess() { + ProtocolTreeNode* root; + while ((root = this->inn->nextTree()) != NULL) { + if (ProtocolTreeNode::tagEquals(root, "stream:features")) { + this->supports_receipt_acks = root->getChild("receipt_acks") != NULL; + delete root; + continue; + } + if (ProtocolTreeNode::tagEquals(root, "challenge")) { + // base64_decode(*root->data); + // _LOGDATA("Challenge data %s (%d)", root->data->c_str(), root->data->length()); + std::vector challengedata(root->data->begin(), root->data->end()); + delete root; + this->sendResponse(challengedata); + _LOGDATA("Send response"); + std::vector data = this->readSuccess(); + _LOGDATA("Read success"); + return new std::vector(data.begin(), data.end()); + } + if (ProtocolTreeNode::tagEquals(root, "success")) { + // base64_decode(*root->data); + std::vector* ret = new std::vector(root->data->begin(), root->data->end()); + this->parseSuccessNode(root); + delete root; + return ret; + } + } + throw WAException("fell out of loop in readFeaturesAndChallenge", WAException::CORRUPT_STREAM_EX, 0); +} + +void WALogin::parseSuccessNode(ProtocolTreeNode* node) { + std::string* expiration = node->getAttributeValue("expiration"); + if (expiration != NULL) { + this->expire_date = atol(expiration->c_str()); + if (this->expire_date == 0) + throw WAException("invalid expire date: " + *expiration); + } + + + std::string* kind = node->getAttributeValue("kind"); + if (kind != NULL && kind->compare("paid") == 0) + this->account_kind = 1; + else if (kind != NULL && kind->compare("free") == 0) + this->account_kind = 0; + else + this->account_kind = -1; + + if (this->connection->outputKey != NULL) + delete this->connection->outputKey; + this->connection->outputKey = this->outputKey; +} + +std::vector WALogin::readSuccess() { + ProtocolTreeNode* node = this->inn->nextTree(); + + if (ProtocolTreeNode::tagEquals(node, "failure")) { + delete node; + throw WAException("Login failure", WAException::LOGIN_FAILURE_EX, WAException::LOGIN_FAILURE_EX_TYPE_PASSWORD); + } + + ProtocolTreeNode::require(node, "success"); + this->parseSuccessNode(node); + + std::string* status = node->getAttributeValue("status"); + if (status != NULL && status->compare("expired") == 0) { + delete node; + throw WAException("Account expired on" + std::string(ctime(&this->expire_date)), WAException::LOGIN_FAILURE_EX, WAException::LOGIN_FAILURE_EX_TYPE_EXPIRED, this->expire_date); + } + if (status != NULL && status->compare("active") == 0) { + if (node->getAttributeValue("expiration") == NULL) { + delete node; + throw WAException("active account with no expiration"); + } + } else + this->account_kind = -1; + + std::vector data = *node->data; + delete node; + return data; +} + +WALogin::~WALogin() { +} + +KeyStream::KeyStream(unsigned char* key, size_t keyLength) { + unsigned char drop[256]; + + this->key = new unsigned char[keyLength]; + memcpy(this->key, key, keyLength); + this->keyLength = keyLength; + + RC4_set_key(&this->rc4, this->keyLength, this->key); + RC4(&this->rc4, 256, drop, drop); + HMAC_CTX_init(&this->hmac); +} + +KeyStream::~KeyStream() { + delete [] this->key; + HMAC_CTX_cleanup(&this->hmac); +} + +void KeyStream::keyFromPasswordAndNonce(const std::string& pass, const std::vector& nonce, unsigned char *out) { + PKCS5_PBKDF2_HMAC_SHA1(pass.data(), pass.size(), nonce.data(), nonce.size(), 16, 20, out); +} + +void KeyStream::decodeMessage(unsigned char* buffer, int macOffset, int offset, const int length) { + unsigned char digest[20]; + this->hmacsha1(buffer + offset, length, digest); + + for (int i = 0; i < 4; i++) { + if (buffer[macOffset + i] != digest[i]) { + throw WAException("invalid MAC", WAException::CORRUPT_STREAM_EX, 0); + } + } + unsigned char* out = new unsigned char[length]; + RC4(&this->rc4, length, buffer + offset, out); + memcpy(buffer + offset, out, length); + delete[] out; +} + +void KeyStream::encodeMessage(unsigned char* buffer, int macOffset, int offset, const int length) { + unsigned char* out = new unsigned char[length]; + RC4(&this->rc4, length, buffer + offset, out); + memcpy(buffer + offset, out, length); + delete[] out; + + unsigned char digest[20]; + this->hmacsha1(buffer + offset, length, digest); + + memcpy(buffer + macOffset, digest, 4); +} + +void KeyStream::hmacsha1(unsigned char* text, int textLength, unsigned char *out) { + // CHMAC_SHA1 hmac; + + // hmac.HMAC_SHA1(text, textLength, this->key, this->keyLength, out); + + unsigned int mdLength; + HMAC(EVP_sha1(), this->key, this->keyLength, text, textLength, out, &mdLength); +} + + + + diff --git a/protocols/WhatsApp/src/WhatsAPI++/WALogin.h b/protocols/WhatsApp/src/WhatsAPI++/WALogin.h new file mode 100644 index 0000000000..92054abf54 --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/WALogin.h @@ -0,0 +1,75 @@ +/* + * WALogin.h + * + * Created on: 26/06/2012 + * Author: Antonio + */ + +#ifndef WALOGIN_H_ +#define WALOGIN_H_ + +#include "BinTreeNodeReader.h" +#include "BinTreeNodeWriter.h" +#include "WAConnection.h" +#include +#include +#include + +class WAConnection; +class BinTreeNodeReader; +class BinTreeNodeWriter; + +class KeyStream { +private: + RC4_KEY rc4; + HMAC_CTX hmac; + unsigned char* key; + int keyLength; + + void hmacsha1(unsigned char* text, int textLength, unsigned char *out); + +public: + KeyStream(unsigned char* key, size_t keyLegnth); + virtual ~KeyStream(); + + static void keyFromPasswordAndNonce(const std::string& pass, const std::vector& nonce, unsigned char *out); + void decodeMessage(unsigned char* buffer, int macOffset, int offset, const int length); + void encodeMessage(unsigned char* buffer, int macOffset, int offset, const int length); +}; + + +class WALogin { +private: + static const std::string NONCE_KEY; + KeyStream* outputKey; + WAConnection* connection; + + std::vector* getAuthBlob(const std::vector& nonce); + void sendResponse(const std::vector& challengeData); + void sendFeatures(); + void sendAuth(const std::vector& nonce); + std::vector* readFeaturesUntilChallengeOrSuccess(); + void parseSuccessNode(ProtocolTreeNode* node); + std::vector readSuccess(); + std::string getResponse(const std::string& challenge); + +public: + std::string user; + std::string domain; + std::string password; + std::string resource; + std::string push_name; + bool supports_receipt_acks; + time_t expire_date; + int account_kind; + BinTreeNodeReader* inn; + BinTreeNodeWriter* out; + + WALogin(WAConnection* connection, BinTreeNodeReader *reader, BinTreeNodeWriter *writer, const std::string& domain, const std::string& user, const std::string& resource, const std::string& password, const std::string& push_name); + std::vector* login(const std::vector& blobLength); + BinTreeNodeReader *getTreeNodeReader(); + BinTreeNodeWriter *getTreeNodeWriter(); + virtual ~WALogin(); +}; + +#endif /* WALOGIN_H_ */ diff --git a/protocols/WhatsApp/src/WhatsAPI++/base64.cpp b/protocols/WhatsApp/src/WhatsAPI++/base64.cpp new file mode 100644 index 0000000000..842ca4aed3 --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/base64.cpp @@ -0,0 +1,103 @@ +/* + * Base64.cpp + * + * Created on: 26/06/2012 + * Author: Antonio + */ +#include "stdafx.h" +#include "base64.h" +#include + +static const std::string base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + + +static inline bool is_base64(unsigned char c) { + return (isalnum(c) || (c == '+') || (c == '/')); +} + +std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { + std::string ret; + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for(i = 0; (i <4) ; i++) + ret += base64_chars[char_array_4[i]]; + i = 0; + } + } + + if (i) + { + for(j = i; j < 3; j++) + char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (j = 0; (j < i + 1); j++) + ret += base64_chars[char_array_4[j]]; + + while((i++ < 3)) + ret += '='; + + } + + return ret; + +} + +std::string base64_decode(std::string const& encoded_string) { + int in_len = encoded_string.size(); + int i = 0; + int j = 0; + int in_ = 0; + unsigned char char_array_4[4], char_array_3[3]; + std::string ret; + + while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { + char_array_4[i++] = encoded_string[in_]; in_++; + if (i ==4) { + for (i = 0; i <4; i++) + char_array_4[i] = base64_chars.find(char_array_4[i]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (i = 0; (i < 3); i++) + ret += char_array_3[i]; + i = 0; + } + } + + if (i) { + for (j = i; j <4; j++) + char_array_4[j] = 0; + + for (j = 0; j <4; j++) + char_array_4[j] = base64_chars.find(char_array_4[j]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; + } + + return ret; +} diff --git a/protocols/WhatsApp/src/WhatsAPI++/base64.h b/protocols/WhatsApp/src/WhatsAPI++/base64.h new file mode 100644 index 0000000000..2ece162b89 --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/base64.h @@ -0,0 +1,16 @@ +/* + * Base64.h + * + * Created on: 26/06/2012 + * Author: Antonio + */ + +#ifndef BASE64_H_ +#define BASE64_H_ + +#include + +std::string base64_encode(unsigned char const* , unsigned int len); +std::string base64_decode(std::string const& s); + +#endif /* BASE64_H_ */ diff --git a/protocols/WhatsApp/src/WhatsAPI++/stdafx.cpp b/protocols/WhatsApp/src/WhatsAPI++/stdafx.cpp new file mode 100644 index 0000000000..8d5b5b5857 --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : Quelldatei, die nur die Standard-Includes einbindet. +// WhatsAPI++.pch ist der vorkompilierte Header. +// stdafx.obj enthält die vorkompilierten Typinformationen. + +#include "stdafx.h" + +// TODO: Auf zusätzliche Header verweisen, die in STDAFX.H +// und nicht in dieser Datei erforderlich sind. diff --git a/protocols/WhatsApp/src/WhatsAPI++/stdafx.h b/protocols/WhatsApp/src/WhatsAPI++/stdafx.h new file mode 100644 index 0000000000..9649140359 --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/stdafx.h @@ -0,0 +1,14 @@ +// stdafx.h : Includedatei für Standardsystem-Includedateien +// oder häufig verwendete projektspezifische Includedateien, +// die nur in unregelmäßigen Abständen geändert werden. +// + +#pragma once + +#include "targetver.h" + +#define WIN32_LEAN_AND_MEAN // Selten verwendete Teile der Windows-Header nicht einbinden. + + + +// TODO: Hier auf zusätzliche Header, die das Programm erfordert, verweisen. diff --git a/protocols/WhatsApp/src/WhatsAPI++/targetver.h b/protocols/WhatsApp/src/WhatsAPI++/targetver.h new file mode 100644 index 0000000000..497706ee83 --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Durch Einbeziehen von"SDKDDKVer.h" wird die höchste verfügbare Windows-Plattform definiert. + +// Wenn Sie die Anwendung für eine frühere Windows-Plattform erstellen möchten, schließen Sie "WinSDKVer.h" ein, und +// legen Sie das _WIN32_WINNT-Makro auf die zu unterstützende Plattform fest, bevor Sie "SDKDDKVer.h" einschließen. + +#include diff --git a/protocols/WhatsApp/src/WhatsAPI++/utilities.cpp b/protocols/WhatsApp/src/WhatsAPI++/utilities.cpp new file mode 100644 index 0000000000..97b437452b --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/utilities.cpp @@ -0,0 +1,463 @@ +#include "stdafx.h" +#include "utilities.h" +//#include "ApplicationData.h" +#include +#include +#include +#include +#include +#include +#include "WAException.h" +#include +#include +#include +#include + +namespace Utilities{ + +const int MD5_DIGEST_SIZE = MD5_DIGEST_LENGTH; + +const static char digits[] = { + '0' , '1' , '2' , '3' , '4' , '5' , + '6' , '7' , '8' , '9' , 'a' , 'b' , + 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , + 'i' , 'j' , 'k' , 'l' , 'm' , 'n' , + 'o' , 'p' , 'q' , 'r' , 's' , 't' , + 'u' , 'v' , 'w' , 'x' , 'y' , 'z' +}; + +void configureLogging(const char* ident) { +#ifndef _LOGWIN32 + openlog(ident, 0, LOG_USER); +#endif +} + +void closeLog() { +#ifndef _LOGWIN32 + closelog(); +#endif +} + +std::string getCountryCode(){ + return "34"; +} + +std::string reverseString(const std::string& str) +{ + return std::string(str.rbegin(), str.rend()); +} + + + +std::string itoa(int value, unsigned int base) { + + const char digitMap[] = "0123456789abcdef"; + + std::string buf; + // Guard: + if (base == 0 || base > 16) { + + // Error: may add more trace/log output here + return buf; + } + // Take care of negative int: + std::string sign; + int _value = value; + // Check for case when input is zero: + if (_value == 0) return "0"; + if (value < 0) { + _value = -value; + sign = "-"; + } + + // Translating number to string with base: + + for (int i = 30; _value && i ; --i) { + buf = digitMap[ _value % base ] + buf; + _value /= base; + } + + return sign.append(buf); +} + + +unsigned char* md5digest(unsigned char *bytes, int length, unsigned char* buffer) { + MD5(bytes, length, buffer); + return buffer; +} + + +std::string processIdentity(const std::string& id){ + std::string buffer_str = reverseString(id); + unsigned char buffer[MD5_DIGEST_LENGTH]; + + MD5((unsigned char*) buffer_str.data(), buffer_str.length(), buffer); + buffer_str.clear(); + + for(int i =0; i< MD5_DIGEST_LENGTH; i++){ + int tmp = buffer[i]+128; + int f = tmp & 0xff; + + buffer_str=buffer_str.append(itoa(f,16)); + } + + return buffer_str; +} + +void debug(const std::string& msg) { +#ifdef _LOGWIN32 + cout << "DEBUG: " << msg << endl; +#else + syslog(LOG_ERR, msg.c_str()); +#endif +} + +std::string str(int64_t i, int radix ) { + if (radix < 2 || radix > 36) + throw WAException("radix must be in 2..36"); + char buf[65]; + int charPos = 64; + bool negative = (i < 0); + + if (!negative) { + i = -i; + } + + while (i <= -radix) { + buf[charPos--] = digits[(int)(-(i % radix))]; + i = i / radix; + } + buf[charPos] = digits[(int)(-i)]; + + if (negative) { + buf[--charPos] = '-'; + } + + std::string aux(buf, 65); + + return std::string(aux, charPos, (65 - charPos)); +} + +int64_t randLong() { + std::srand(time(NULL)); + int64_t r = (int64_t) ((char) (std::rand() % 256)); + + for (int i = 0; i < 7 ; i++) + r = (r << 8) + ((char) (std::rand() % 256)); + + return r; +} + +int64_t absLong(int64_t num) { + return (num >= 0? num: -num); +} + + +std::string intToStr(int i) { + std::stringstream convert; + convert << i; + return convert.str(); +} + +std::string doubleToStr(double d) { + std::stringstream convert; + convert << d; + return convert.str(); +} + +time_t parseBBDate(const string& s) { + _LOGDATA("parse DATE %s", s.c_str()); + if (s.length() < 17) + return time(NULL); + + struct tm timeinfo; + timeinfo.tm_year = atoi(s.substr(0, 4).c_str()) - 1900; + timeinfo.tm_mon = atoi(s.substr(4, 2).c_str()) - 1; + timeinfo.tm_mday = atoi(s.substr(6,2).c_str()); + timeinfo.tm_hour = atoi(s.substr(9,2).c_str()); + timeinfo.tm_min = atoi(s.substr(12,2).c_str()); + timeinfo.tm_sec = atoi(s.substr(15,2).c_str()); + + //return timegm(&timeinfo); + return mktime(&timeinfo); +} + +void logData(const char *format, ...) { + va_list args; + va_start(args, format); +#ifdef _LOGWIN32 + std::string formatLine = std::string(format).append("\n"); + vprintf(formatLine.c_str(), args); fflush(stdout); +#else + vsyslog(LOG_ERR, format, args); +#endif + +} + +long long parseLongLong(const std::string& str) { + std::stringstream sstr(str); + long long val; + sstr >> val; + + return val; +} + +unsigned char* bytesToHex(unsigned char* bytes, int length) { + unsigned char* ret = new unsigned char[length*2]; + int i = 0; + for (int c = 0; c < length; c++) { + int ub = bytes[c]; + + if (ub < 0) + ub += 256; + ret[i] = forDigit(ub >> 4); + i++; + ret[i] = forDigit(ub % 16); + i++; + } + + return ret; +} + +unsigned char forDigit(int b) { + if (b < 10) + return (unsigned char) (48 + b); + return (unsigned char) (97 + b - 10); +} + +string md5String(const string& data) { + unsigned char md5_buffer[Utilities::MD5_DIGEST_SIZE + 1]; + md5_buffer[Utilities::MD5_DIGEST_SIZE] = '\0'; + md5digest((unsigned char*) data.c_str(), data.length(), md5_buffer); + std::string result((char*) Utilities::bytesToHex(md5_buffer, Utilities::MD5_DIGEST_SIZE), Utilities::MD5_DIGEST_SIZE*2); + + return result; +} + +bool saveStringToFile(const string& data, const string& filePath) { + std::ofstream out(filePath.c_str()); + if (out.fail()) return false; + out << data; + if (out.fail()) return false; + out.close(); + if (out.fail()) return false; + return true; +} + +bool saveBytesToFile(const std::vector& data, const string& filePath) { + std::fstream out(filePath.c_str(), ios::out | ios::binary); + if (out.fail()) return false; + out.write((const char*) &data[0], data.size()); + if (out.fail()) return false; + out.close(); + if (out.fail()) return false; + return true; +} + + +bool saveBytesToFile(const string& data, const string& filePath) { + std::fstream out(filePath.c_str(), ios::out | ios::binary); + if (out.fail()) return false; + out.write(data.c_str(), data.length()); + if (out.fail()) return false; + out.close(); + if (out.fail()) return false; + return true; +} + +vector* loadFileToBytes(const string& path) { + vector* bytes = NULL; + std::ifstream in(path.c_str(), ios::in | ios::binary | ios::ate); + long size = in.tellg(); + if (in.fail()) return NULL; + + in.seekg(0, ios::beg); + char *buffer = new char[size]; + in.read(buffer, size); + bytes = new vector(buffer, buffer + size); + delete [] buffer; + in.close(); + if (in.fail()) return NULL; + + return bytes; +} + +bool fileExists(const std::string& path) { + std::ifstream in(path.c_str()); + return in; +} + + +string removeWaDomainFromJid(const string& jid) { + string result = jid; + + int index = jid.find("@s.whatsapp.net"); + if (index != string::npos) { + result.replace(index, 15, ""); + return result; + } + + index = jid.find("@g.us"); + if (index != string::npos) { + result.replace(index, 5, ""); + return result; + } + + return jid; +} + +string getNameFromPath(const std::string& path) { + int i = path.rfind('/'); + if (i == string::npos) + i = 0; + else + i = i + 1; + return path.substr(i); +} + +vector* getChallengeData(const std::string& challengeFile) { + return loadFileToBytes(challengeFile); +} + +bool saveChallengeData(const std::vector& data, const std::string& challengeFile) { + return saveBytesToFile(data, challengeFile); +} + +std::string utf8_to_utf16(const std::string& utf8) +{ + std::vector unicode; + size_t i = 0; + while (i < utf8.size()) + { + unsigned long uni; + size_t todo; + bool error = false; + unsigned char ch = utf8[i++]; + if (ch <= 0x7F) + { + uni = ch; + todo = 0; + } + else if (ch <= 0xBF) + { + throw std::logic_error("not a UTF-8 string"); + } + else if (ch <= 0xDF) + { + uni = ch&0x1F; + todo = 1; + } + else if (ch <= 0xEF) + { + uni = ch&0x0F; + todo = 2; + } + else if (ch <= 0xF7) + { + uni = ch&0x07; + todo = 3; + } + else + { + throw std::logic_error("not a UTF-8 string"); + } + for (size_t j = 0; j < todo; ++j) + { + if (i == utf8.size()) + throw std::logic_error("not a UTF-8 string"); + unsigned char ch = utf8[i++]; + if (ch < 0x80 || ch > 0xBF) + throw std::logic_error("not a UTF-8 string"); + uni <<= 6; + uni += ch & 0x3F; + } + if (uni >= 0xD800 && uni <= 0xDFFF) + throw std::logic_error("not a UTF-8 string"); + if (uni > 0x10FFFF) + throw std::logic_error("not a UTF-8 string"); + unicode.push_back(uni); + } + std::string utf16; + for (size_t i = 0; i < unicode.size(); ++i) + { + unsigned long uni = unicode[i]; + if (uni <= 0x7F) { + utf16 += (char) uni; + } + else + if (uni <= 0xFFFF) + { + stringstream value; + value << std::setw(4) << std::setfill('0') << Utilities::itoa(uni, 16).c_str(); + utf16 += "\\u" + value.str(); + } + else + { + stringstream value1, value2; + uni -= 0x10000; + value1 << std::setw(4) << std::setfill('0') << Utilities::itoa(((uni >> 10) + 0xD800), 16); + utf16 += "\\u" + value1.str(); + + value2 << std::setw(4) << std::setfill('0') << Utilities::itoa(((uni & 0x3FF) + 0xDC00), 16); + utf16 += "\\u" + value2.str(); + } + } + return utf16; +} + +std::string string_format(const char* fmt, va_list ap) +{ + int size = 100; + std::string str; + while (1) { + str.resize(size); + //va_start(ap, fmt); + int n = vsnprintf((char *)str.c_str(), size, fmt, ap); + //va_end(ap); + if (n > -1 && n < size) { + str.resize(n); + return str; + } + if (n > -1) + size = n + 1; + else + size *= 2; + } + return str; +} + +std::string string_format(const std::string fmt, va_list ap) +{ + return string_format(fmt.c_str(), ap); + /* + int size = 100; + std::string str; + //va_list ap; + while (1) { + str.resize(size); + //va_start(ap, fmt); + int n = vsnprintf((char *)str.c_str(), size, fmt.c_str(), ap); + //va_end(ap); + if (n > -1 && n < size) { + str.resize(n); + return str; + } + if (n > -1) + size = n + 1; + else + size *= 2; + } + return str; + */ +} + +std::string string_format(const std::string fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + std::string ret = string_format(fmt, ap); + va_end(ap); + return ret; +} + +} diff --git a/protocols/WhatsApp/src/WhatsAPI++/utilities.h b/protocols/WhatsApp/src/WhatsAPI++/utilities.h new file mode 100644 index 0000000000..48db82b145 --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/utilities.h @@ -0,0 +1,85 @@ +/*************************************************************************** +** +** Copyright (c) 2012, Tarek Galal +** +** This file is part of Wazapp, an IM application for Meego Harmattan +** platform that allows communication with Whatsapp users. +** +** Wazapp is free software: you can redistribute it and/or modify it under +** the terms of the GNU General Public License as published by the +** Free Software Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** Wazapp 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 Wazapp. If not, see http://www.gnu.org/licenses/. +** +****************************************************************************/ + +#ifndef WA_UTILITIES +#define WA_UTILITIES + +#include +#include +#include +#include +#include +#include + +#define _LOGWIN32 // #TODO +#ifndef _LOGWIN32 +#include +#endif + +#ifdef _DEBUG + #define _DEBUGENABLED true +#else + #define _DEBUGENABLED false +#endif + +#define _LOGDATA(format, ...) if (_DEBUGENABLED) Utilities::logData(format, ##__VA_ARGS__) + +using namespace std; +namespace Utilities{ + extern void configureLogging(const char* ident); + extern void closeLog(); + extern const int MD5_DIGEST_SIZE; + extern string getCountryCode(); + extern string getMcc(); + extern string getMnc(); + extern string reverseString(const string& str); + extern unsigned char* md5digest(unsigned char *bytes, int length, unsigned char* buffer); + extern string processIdentity(const std::string& password); + extern int64_t randLong(); + extern int64_t absLong(int64_t num); + extern string str(int64_t number, int radix); + extern std::string itoa(int value, unsigned int base); + extern std::string intToStr(int i); + extern std::string doubleToStr(double d); + extern long long parseLongLong(const std::string& str); + extern time_t parseBBDate(const string& s); + extern void logData(const char *msg, ...); + extern long long getCurrentTimeMillis(); + extern unsigned char* bytesToHex(unsigned char* bytes, int length); + extern unsigned char forDigit(int b); + extern string md5String(const string& data); + extern bool saveStringToFile(const string& data, const string& filePath); + extern bool saveBytesToFile(const string& data, const string& filePath); + extern bool saveBytesToFile(const std::vector& data, const string& filePath); + extern string removeWaDomainFromJid(const string& jid); + extern string getNameFromPath(const std::string& path); + extern vector* loadFileToBytes(const string& path); + extern bool fileExists(const std::string& path); + extern std::vector* getChallengeData(const std::string& file); + extern bool saveChallengeData(const std::vector& data, const std::string& file); + extern std::string utf8_to_utf16(const std::string& utf8); + extern std::string string_format(const std::string fmt, ...); + extern std::string string_format(const std::string fmt, va_list ap); + extern std::string string_format(const char* fmt, va_list ap); +} +#endif + diff --git a/protocols/WhatsApp/src/cJSON.cpp b/protocols/WhatsApp/src/cJSON.cpp new file mode 100644 index 0000000000..a1f326bbe7 --- /dev/null +++ b/protocols/WhatsApp/src/cJSON.cpp @@ -0,0 +1,515 @@ +/* + Copyright (c) 2009 Dave Gamble + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +#include +//#include +#include +#include +#include +#include +#include +#include +#include "cJSON.h" + +static const char *ep; + +const char *cJSON_GetErrorPtr() {return ep;} + +static int cJSON_strcasecmp(const char *s1,const char *s2) +{ + if (!s1) return (s1==s2)?0:1;if (!s2) return 1; + for(; tolower(*s1) == tolower(*s2); ++s1, ++s2) if(*s1 == 0) return 0; + return tolower(*(const unsigned char *)s1) - tolower(*(const unsigned char *)s2); +} + +static void *(*cJSON_malloc)(size_t sz) = malloc; +static void (*cJSON_free)(void *ptr) = free; + +static char* cJSON_strdup(const char* str) +{ + size_t len; + char* copy; + + len = strlen(str) + 1; + if (!(copy = (char*)cJSON_malloc(len))) return 0; + memcpy(copy,str,len); + return copy; +} + +void cJSON_InitHooks(cJSON_Hooks* hooks) +{ + if (!hooks) { /* Reset hooks */ + cJSON_malloc = malloc; + cJSON_free = free; + return; + } + + cJSON_malloc = (hooks->malloc_fn)?hooks->malloc_fn:malloc; + cJSON_free = (hooks->free_fn)?hooks->free_fn:free; +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item() +{ + cJSON* node = (cJSON*)cJSON_malloc(sizeof(cJSON)); + if (node) memset(node,0,sizeof(cJSON)); + return node; +} + +/* Delete a cJSON structure. */ +void cJSON_Delete(cJSON *c) +{ + cJSON *next; + while (c) + { + next=c->next; + if (!(c->type&cJSON_IsReference) && c->child) cJSON_Delete(c->child); + if (!(c->type&cJSON_IsReference) && c->valuestring) cJSON_free(c->valuestring); + if (c->string) cJSON_free(c->string); + cJSON_free(c); + c=next; + } +} + +/* Parse the input text to generate a number, and populate the result into item. */ +static const char *parse_number(cJSON *item,const char *num) +{ + double n=0,sign=1,scale=0;int subscale=0,signsubscale=1; + + /* Could use sscanf for this? */ + if (*num=='-') sign=-1,num++; /* Has sign? */ + if (*num=='0') num++; /* is zero */ + if (*num>='1' && *num<='9') do n=(n*10.0)+(*num++ -'0'); while (*num>='0' && *num<='9'); /* Number? */ + if (*num=='.' && num[1]>='0' && num[1]<='9') {num++; do n=(n*10.0)+(*num++ -'0'),scale--; while (*num>='0' && *num<='9');} /* Fractional part? */ + if (*num=='e' || *num=='E') /* Exponent? */ + { num++;if (*num=='+') num++; else if (*num=='-') signsubscale=-1,num++; /* With sign? */ + while (*num>='0' && *num<='9') subscale=(subscale*10)+(*num++ - '0'); /* Number? */ + } + + n=sign*n*pow(10.0,(scale+subscale*signsubscale)); /* number = +/- number.fraction * 10^+/- exponent */ + + item->valuedouble=n; + item->valueint=(int)n; + item->type=cJSON_Number; + return num; +} + +/* Render the number nicely from the given item into a string. */ +static char *print_number(cJSON *item) +{ + char *str; + double d=item->valuedouble; + if (fabs(((double)item->valueint)-d)<=DBL_EPSILON && d<=INT_MAX && d>=INT_MIN) + { + str=(char*)cJSON_malloc(21); /* 2^64+1 can be represented in 21 chars. */ + if (str) sprintf(str,"%d",item->valueint); + } + else + { + str=(char*)cJSON_malloc(64); /* This is a nice tradeoff. */ + if (str) + { + if (fabs(floor(d)-d)<=DBL_EPSILON) sprintf(str,"%.0f",d); + else if (fabs(d)<1.0e-6 || fabs(d)>1.0e9) sprintf(str,"%e",d); + else sprintf(str,"%f",d); + } + } + return str; +} + +/* Parse the input text into an unescaped cstring, and populate item. */ +static const unsigned char firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; +static const char *parse_string(cJSON *item,const char *str) +{ + const char *ptr=str+1;char *ptr2;char *out;int len=0;unsigned uc,uc2; + if (*str!='\"') {ep=str;return 0;} /* not a string! */ + + while (*ptr!='\"' && *ptr && ++len) if (*ptr++ == '\\') ptr++; /* Skip escaped quotes. */ + + out=(char*)cJSON_malloc(len+1); /* This is how long we need for the string, roughly. */ + if (!out) return 0; + + ptr=str+1;ptr2=out; + while (*ptr!='\"' && *ptr) + { + if (*ptr!='\\') *ptr2++=*ptr++; + else + { + ptr++; + switch (*ptr) + { + case 'b': *ptr2++='\b'; break; + case 'f': *ptr2++='\f'; break; + case 'n': *ptr2++='\n'; break; + case 'r': *ptr2++='\r'; break; + case 't': *ptr2++='\t'; break; + case 'u': /* transcode utf16 to utf8. */ + sscanf(ptr+1,"%4x",&uc);ptr+=4; /* get the unicode char. */ + + if ((uc>=0xDC00 && uc<=0xDFFF) || uc==0) break; // check for invalid. + + if (uc>=0xD800 && uc<=0xDBFF) // UTF16 surrogate pairs. + { + if (ptr[1]!='\\' || ptr[2]!='u') break; // missing second-half of surrogate. + sscanf(ptr+3,"%4x",&uc2);ptr+=6; + if (uc2<0xDC00 || uc2>0xDFFF) break; // invalid second-half of surrogate. + uc=0x10000 | ((uc&0x3FF)<<10) | (uc2&0x3FF); + } + + len=4;if (uc<0x80) len=1;else if (uc<0x800) len=2;else if (uc<0x10000) len=3; ptr2+=len; + + switch (len) { + case 4: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; + case 3: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; + case 2: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; + case 1: *--ptr2 =(uc | firstByteMark[len]); + } + ptr2+=len; + break; + default: *ptr2++=*ptr; break; + } + ptr++; + } + } + *ptr2=0; + if (*ptr=='\"') ptr++; + item->valuestring=out; + item->type=cJSON_String; + return ptr; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static char *print_string_ptr(const char *str) +{ + const char *ptr;char *ptr2,*out;int len=0;unsigned char token; + + if (!str) return cJSON_strdup(""); + ptr=str;while ((token=*ptr) && ++len) {if (strchr("\"\\\b\f\n\r\t",token)) len++; else if (token<32) len+=5;ptr++;} + + out=(char*)cJSON_malloc(len+3); + if (!out) return 0; + + ptr2=out;ptr=str; + *ptr2++='\"'; + while (*ptr) + { + if ((unsigned char)*ptr>31 && *ptr!='\"' && *ptr!='\\') *ptr2++=*ptr++; + else + { + *ptr2++='\\'; + switch (token=*ptr++) + { + case '\\': break; // *ptr2++='\\'; break; + case '\"': *ptr2++='\"'; break; + case '\b': *ptr2++='b'; break; + case '\f': *ptr2++='f'; break; + case '\n': *ptr2++='n'; break; + case '\r': *ptr2++='r'; break; + case '\t': *ptr2++='t'; break; + default: sprintf(ptr2,"u%04x",token);ptr2+=5; break; /* escape and print */ + } + } + } + *ptr2++='\"';*ptr2++=0; + return out; +} +/* Invote print_string_ptr (which is useful) on an item. */ +static char *print_string(cJSON *item) {return print_string_ptr(item->valuestring);} + +/* Predeclare these prototypes. */ +static const char *parse_value(cJSON *item,const char *value); +static char *print_value(cJSON *item,int depth,int fmt); +static const char *parse_array(cJSON *item,const char *value); +static char *print_array(cJSON *item,int depth,int fmt); +static const char *parse_object(cJSON *item,const char *value); +static char *print_object(cJSON *item,int depth,int fmt); + +/* Utility to jump whitespace and cr/lf */ +static const char *skip(const char *in) {while (in && *in && (unsigned char)*in<=32) in++; return in;} + +/* Parse an object - create a new root, and populate. */ +cJSON *cJSON_Parse(const char *value) +{ + cJSON *c=cJSON_New_Item(); + ep=0; + if (!c) return 0; /* memory fail */ + + if (!parse_value(c,skip(value))) {cJSON_Delete(c);return 0;} + return c; +} + +/* Render a cJSON item/entity/structure to text. */ +char *cJSON_Print(cJSON *item) {return print_value(item,0,1);} +char *cJSON_PrintUnformatted(cJSON *item) {return print_value(item,0,0);} + +/* Parser core - when encountering text, process appropriately. */ +static const char *parse_value(cJSON *item,const char *value) +{ + if (!value) return 0; /* Fail on null. */ + if (!strncmp(value,"null",4)) { item->type=cJSON_NULL; return value+4; } + if (!strncmp(value,"false",5)) { item->type=cJSON_False; return value+5; } + if (!strncmp(value,"true",4)) { item->type=cJSON_True; item->valueint=1; return value+4; } + if (*value=='\"') { return parse_string(item,value); } + if (*value=='-' || (*value>='0' && *value<='9')) { return parse_number(item,value); } + if (*value=='[') { return parse_array(item,value); } + if (*value=='{') { return parse_object(item,value); } + + ep=value;return 0; /* failure. */ +} + +/* Render a value to text. */ +static char *print_value(cJSON *item,int depth,int fmt) +{ + char *out=0; + if (!item) return 0; + switch ((item->type)&255) + { + case cJSON_NULL: out=cJSON_strdup("null"); break; + case cJSON_False: out=cJSON_strdup("false");break; + case cJSON_True: out=cJSON_strdup("true"); break; + case cJSON_Number: out=print_number(item);break; + case cJSON_String: out=print_string(item);break; + case cJSON_Array: out=print_array(item,depth,fmt);break; + case cJSON_Object: out=print_object(item,depth,fmt);break; + } + return out; +} + +/* Build an array from input text. */ +static const char *parse_array(cJSON *item,const char *value) +{ + cJSON *child; + if (*value!='[') {ep=value;return 0;} /* not an array! */ + + item->type=cJSON_Array; + value=skip(value+1); + if (*value==']') return value+1; /* empty array. */ + + item->child=child=cJSON_New_Item(); + if (!item->child) return 0; /* memory fail */ + value=skip(parse_value(child,skip(value))); /* skip any spacing, get the value. */ + if (!value) return 0; + + while (*value==',') + { + cJSON *new_item; + if (!(new_item=cJSON_New_Item())) return 0; /* memory fail */ + child->next=new_item;new_item->prev=child;child=new_item; + value=skip(parse_value(child,skip(value+1))); + if (!value) return 0; /* memory fail */ + } + + if (*value==']') return value+1; /* end of array */ + ep=value;return 0; /* malformed. */ +} + +/* Render an array to text */ +static char *print_array(cJSON *item,int depth,int fmt) +{ + char **entries; + char *out=0,*ptr,*ret;int len=5; + cJSON *child=item->child; + int numentries=0,i=0,fail=0; + + /* How many entries in the array? */ + while (child) numentries++,child=child->next; + /* Allocate an array to hold the values for each */ + entries=(char**)cJSON_malloc(numentries*sizeof(char*)); + if (!entries) return 0; + memset(entries,0,numentries*sizeof(char*)); + /* Retrieve all the results: */ + child=item->child; + while (child && !fail) + { + ret=print_value(child,depth+1,fmt); + entries[i++]=ret; + if (ret) len+=strlen(ret)+2+(fmt?1:0); else fail=1; + child=child->next; + } + + /* If we didn't fail, try to malloc the output string */ + if (!fail) out=(char*)cJSON_malloc(len); + /* If that fails, we fail. */ + if (!out) fail=1; + + /* Handle failure. */ + if (fail) + { + for (i=0;itype=cJSON_Object; + value=skip(value+1); + if (*value=='}') return value+1; /* empty array. */ + + item->child=child=cJSON_New_Item(); + if (!item->child) return 0; + value=skip(parse_string(child,skip(value))); + if (!value) return 0; + child->string=child->valuestring;child->valuestring=0; + if (*value!=':') {ep=value;return 0;} /* fail! */ + value=skip(parse_value(child,skip(value+1))); /* skip any spacing, get the value. */ + if (!value) return 0; + + while (*value==',') + { + cJSON *new_item; + if (!(new_item=cJSON_New_Item())) return 0; /* memory fail */ + child->next=new_item;new_item->prev=child;child=new_item; + value=skip(parse_string(child,skip(value+1))); + if (!value) return 0; + child->string=child->valuestring;child->valuestring=0; + if (*value!=':') {ep=value;return 0;} /* fail! */ + value=skip(parse_value(child,skip(value+1))); /* skip any spacing, get the value. */ + if (!value) return 0; + } + + if (*value=='}') return value+1; /* end of array */ + ep=value;return 0; /* malformed. */ +} + +/* Render an object to text. */ +static char *print_object(cJSON *item,int depth,int fmt) +{ + char **entries=0,**names=0; + char *out=0,*ptr,*ret,*str;int len=7,i=0,j; + cJSON *child=item->child; + int numentries=0,fail=0; + /* Count the number of entries. */ + while (child) numentries++,child=child->next; + /* Allocate space for the names and the objects */ + entries=(char**)cJSON_malloc(numentries*sizeof(char*)); + if (!entries) return 0; + names=(char**)cJSON_malloc(numentries*sizeof(char*)); + if (!names) {cJSON_free(entries);return 0;} + memset(entries,0,sizeof(char*)*numentries); + memset(names,0,sizeof(char*)*numentries); + + /* Collect all the results into our arrays: */ + child=item->child;depth++;if (fmt) len+=depth; + while (child) + { + names[i]=str=print_string_ptr(child->string); + entries[i++]=ret=print_value(child,depth,fmt); + if (str && ret) len+=strlen(ret)+strlen(str)+2+(fmt?2+depth:0); else fail=1; + child=child->next; + } + + /* Try to allocate the output string */ + if (!fail) out=(char*)cJSON_malloc(len); + if (!out) fail=1; + + /* Handle failure */ + if (fail) + { + for (i=0;ichild;int i=0;while(c)i++,c=c->next;return i;} +cJSON *cJSON_GetArrayItem(cJSON *array,int item) {cJSON *c=array->child; while (c && item>0) item--,c=c->next; return c;} +cJSON *cJSON_GetObjectItem(cJSON *object,const char *string) {cJSON *c=object->child; while (c && cJSON_strcasecmp(c->string,string)) c=c->next; return c;} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev,cJSON *item) {prev->next=item;item->prev=prev;} +/* Utility for handling references. */ +static cJSON *create_reference(cJSON *item) {cJSON *ref=cJSON_New_Item();if (!ref) return 0;memcpy(ref,item,sizeof(cJSON));ref->string=0;ref->type|=cJSON_IsReference;ref->next=ref->prev=0;return ref;} + +/* Add item to array/object. */ +void cJSON_AddItemToArray(cJSON *array, cJSON *item) {cJSON *c=array->child;if (!item) return; if (!c) {array->child=item;} else {while (c && c->next) c=c->next; suffix_object(c,item);}} +void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item) {if (!item) return; if (item->string) cJSON_free(item->string);item->string=cJSON_strdup(string);cJSON_AddItemToArray(object,item);} +void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) {cJSON_AddItemToArray(array,create_reference(item));} +void cJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item) {cJSON_AddItemToObject(object,string,create_reference(item));} + +cJSON *cJSON_DetachItemFromArray(cJSON *array,int which) {cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) return 0; + if (c->prev) c->prev->next=c->next;if (c->next) c->next->prev=c->prev;if (c==array->child) array->child=c->next;c->prev=c->next=0;return c;} +void cJSON_DeleteItemFromArray(cJSON *array,int which) {cJSON_Delete(cJSON_DetachItemFromArray(array,which));} +cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string) {int i=0;cJSON *c=object->child;while (c && cJSON_strcasecmp(c->string,string)) i++,c=c->next;if (c) return cJSON_DetachItemFromArray(object,i);return 0;} +void cJSON_DeleteItemFromObject(cJSON *object,const char *string) {cJSON_Delete(cJSON_DetachItemFromObject(object,string));} + +/* Replace array/object items with new ones. */ +void cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem) {cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) return; + newitem->next=c->next;newitem->prev=c->prev;if (newitem->next) newitem->next->prev=newitem; + if (c==array->child) array->child=newitem; else newitem->prev->next=newitem;c->next=c->prev=0;cJSON_Delete(c);} +void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem){int i=0;cJSON *c=object->child;while(c && cJSON_strcasecmp(c->string,string))i++,c=c->next;if(c){newitem->string=cJSON_strdup(string);cJSON_ReplaceItemInArray(object,i,newitem);}} + +/* Create basic types: */ +cJSON *cJSON_CreateNull() {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_NULL;return item;} +cJSON *cJSON_CreateTrue() {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_True;return item;} +cJSON *cJSON_CreateFalse() {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_False;return item;} +cJSON *cJSON_CreateBool(int b) {cJSON *item=cJSON_New_Item();if(item)item->type=b?cJSON_True:cJSON_False;return item;} +cJSON *cJSON_CreateNumber(double num) {cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_Number;item->valuedouble=num;item->valueint=(int)num;}return item;} +cJSON *cJSON_CreateString(const char *string) {cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_String;item->valuestring=cJSON_strdup(string);}return item;} +cJSON *cJSON_CreateArray() {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_Array;return item;} +cJSON *cJSON_CreateObject() {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_Object;return item;} + +/* Create Arrays: */ +cJSON *cJSON_CreateIntArray(int *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} +cJSON *cJSON_CreateFloatArray(float *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} +cJSON *cJSON_CreateDoubleArray(double *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} +cJSON *cJSON_CreateStringArray(const char **strings,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} diff --git a/protocols/WhatsApp/src/cJSON.h b/protocols/WhatsApp/src/cJSON.h new file mode 100644 index 0000000000..128d0f17c2 --- /dev/null +++ b/protocols/WhatsApp/src/cJSON.h @@ -0,0 +1,127 @@ +/* + Copyright (c) 2009 Dave Gamble + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef cJSON__h +#define cJSON__h + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* cJSON Types: */ +#define cJSON_False 0 +#define cJSON_True 1 +#define cJSON_NULL 2 +#define cJSON_Number 3 +#define cJSON_String 4 +#define cJSON_Array 5 +#define cJSON_Object 6 + +#define cJSON_IsReference 256 + +/* The cJSON structure: */ +typedef struct cJSON { + struct cJSON *next,*prev; /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *child; /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + + int type; /* The type of the item, as above. */ + + char *valuestring; /* The item's string, if type==cJSON_String */ + int valueint; /* The item's number, if type==cJSON_Number */ + double valuedouble; /* The item's number, if type==cJSON_Number */ + + char *string; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ +} cJSON; + +typedef struct cJSON_Hooks { + void *(*malloc_fn)(size_t sz); + void (*free_fn)(void *ptr); +} cJSON_Hooks; + +/* Supply malloc, realloc and free functions to cJSON */ +extern void cJSON_InitHooks(cJSON_Hooks* hooks); + + +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. Call cJSON_Delete when finished. */ +extern cJSON *cJSON_Parse(const char *value); +/* Render a cJSON entity to text for transfer/storage. Free the char* when finished. */ +extern char *cJSON_Print(cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. Free the char* when finished. */ +extern char *cJSON_PrintUnformatted(cJSON *item); +/* Delete a cJSON entity and all subentities. */ +extern void cJSON_Delete(cJSON *c); + +/* Returns the number of items in an array (or object). */ +extern int cJSON_GetArraySize(cJSON *array); +/* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. */ +extern cJSON *cJSON_GetArrayItem(cJSON *array,int item); +/* Get item "string" from object. Case insensitive. */ +extern cJSON *cJSON_GetObjectItem(cJSON *object,const char *string); + +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +extern const char *cJSON_GetErrorPtr(); + +/* These calls create a cJSON item of the appropriate type. */ +extern cJSON *cJSON_CreateNull(); +extern cJSON *cJSON_CreateTrue(); +extern cJSON *cJSON_CreateFalse(); +extern cJSON *cJSON_CreateBool(int b); +extern cJSON *cJSON_CreateNumber(double num); +extern cJSON *cJSON_CreateString(const char *string); +extern cJSON *cJSON_CreateArray(); +extern cJSON *cJSON_CreateObject(); + +/* These utilities create an Array of count items. */ +extern cJSON *cJSON_CreateIntArray(int *numbers,int count); +extern cJSON *cJSON_CreateFloatArray(float *numbers,int count); +extern cJSON *cJSON_CreateDoubleArray(double *numbers,int count); +extern cJSON *cJSON_CreateStringArray(const char **strings,int count); + +/* Append item to the specified array/object. */ +extern void cJSON_AddItemToArray(cJSON *array, cJSON *item); +extern void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item); +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ +extern void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +extern void cJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item); + +/* Remove/Detatch items from Arrays/Objects. */ +extern cJSON *cJSON_DetachItemFromArray(cJSON *array,int which); +extern void cJSON_DeleteItemFromArray(cJSON *array,int which); +extern cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string); +extern void cJSON_DeleteItemFromObject(cJSON *object,const char *string); + +/* Update array items. */ +extern void cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem); +extern void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); + +#define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull()) +#define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue()) +#define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse()) +#define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n)) +#define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s)) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/protocols/WhatsApp/src/chat.cpp b/protocols/WhatsApp/src/chat.cpp new file mode 100644 index 0000000000..a53a5077ca --- /dev/null +++ b/protocols/WhatsApp/src/chat.cpp @@ -0,0 +1,77 @@ +#include "common.h" + +// #TODO Remove, as we are not using the chat-module for groups anymore + +int WhatsAppProto::OnJoinChat(WPARAM,LPARAM) +{ + return 0; +} + +int WhatsAppProto::OnLeaveChat(WPARAM,LPARAM) +{ + return 0; +} + +int WhatsAppProto::OnChatOutgoing(WPARAM wParam, LPARAM lParam) +{ + GCHOOK *hook = reinterpret_cast(lParam); + char *text; + char *id; + + if (strcmp(hook->pDest->pszModule,m_szModuleName)) + return 0; + + switch(hook->pDest->iType) + { + case GC_USER_MESSAGE: + { + text = mir_t2a_cp(hook->ptszText,CP_UTF8); + std::string msg = text; + + id = mir_t2a_cp(hook->pDest->ptszID,CP_UTF8); + std::string chat_id = id; + + mir_free(text); + mir_free(id); + + if (isOnline()) { + HANDLE hContact = this->ContactIDToHContact(chat_id); + if (hContact) + { + LOG("**Chat - Outgoing message: %s", text); + this->SendMsg(hContact, IS_CHAT, msg.c_str()); + + // #TODO Move to SendMsgWorker, otherwise all messages are "acknowledged" by Miranda + + GCDEST gcd = { m_szModuleName, { NULL }, GC_EVENT_MESSAGE }; + gcd.ptszID = hook->pDest->ptszID; + + GCEVENT gce = {0}; + gce.cbSize = sizeof(GCEVENT); + gce.dwFlags = GC_TCHAR | GCEF_ADDTOLOG; + gce.pDest = &gcd; + gce.ptszNick = mir_a2t(this->nick.c_str()); + gce.ptszUID = mir_a2t(this->jid.c_str()); + gce.time = time(NULL); + gce.ptszText = hook->ptszText; + gce.bIsMe = TRUE; + CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gce); + + mir_free((void*)gce.ptszUID); + mir_free((void*)gce.ptszNick); + } + } + + break; + } + + case GC_USER_LEAVE: + case GC_SESSION_TERMINATE: + { + break; + } + } + + return 0; +} + diff --git a/protocols/WhatsApp/src/common.h b/protocols/WhatsApp/src/common.h new file mode 100644 index 0000000000..1c817f9f32 --- /dev/null +++ b/protocols/WhatsApp/src/common.h @@ -0,0 +1,96 @@ +/* + +WhatsApp plugin for Miranda NG +Copyright © 2013 Uli Hecht + +*/ + +#pragma once + +//#pragma warning(push) +//#pragma warning(disable:4312) +#pragma warning(disable:4996) +#pragma warning(disable:4290) + +#define MIRANDA_VER 0x090E +#define _WIN32_WINNT 0x0500 +#define _WIN32_WINDOWS 0x0500 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +#include +#include "WhatsAPI++/WAConnection.h" + +class WhatsAppProto; + +#include "constants.h" +#include "utils.h" +#include "db.h" +#include "resource.h" +#include "dialogs.h" +#include "theme.h" +#include "definitions.h" +#include "WASocketConnection.h" +#include "proto.h" +#include "entities.h" + +#if defined _DEBUG +#include +#include +#endif + +//#pragma warning(pop) + +extern HINSTANCE g_hInstance; +extern std::string g_strUserAgent; +extern DWORD g_mirandaVersion; diff --git a/protocols/WhatsApp/src/connection.cpp b/protocols/WhatsApp/src/connection.cpp new file mode 100644 index 0000000000..12d9e95508 --- /dev/null +++ b/protocols/WhatsApp/src/connection.cpp @@ -0,0 +1,230 @@ +#include "common.h" + +void WhatsAppProto::ChangeStatus(void*) +{ + if (m_iDesiredStatus != ID_STATUS_OFFLINE && m_iStatus == ID_STATUS_OFFLINE) + { + ResetEvent(update_loop_lock_); + ForkThread(&WhatsAppProto::sentinelLoop, this); + ForkThread(&WhatsAppProto::stayConnectedLoop, this); + } + else if (m_iStatus == ID_STATUS_INVISIBLE && m_iDesiredStatus == ID_STATUS_ONLINE) + { + if (this->connection != NULL) + { + this->connection->sendAvailableForChat(); + m_iStatus = ID_STATUS_ONLINE; + ProtoBroadcastAck(m_szModuleName, 0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE) m_iStatus, ID_STATUS_INVISIBLE); + } + } + else if (m_iStatus == ID_STATUS_ONLINE && m_iDesiredStatus == ID_STATUS_INVISIBLE) + { + if (this->connection != NULL) + { + this->connection->sendClose(); + m_iStatus = ID_STATUS_INVISIBLE; + SetAllContactStatuses( ID_STATUS_OFFLINE, true ); + ProtoBroadcastAck(m_szModuleName, 0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE) m_iStatus, ID_STATUS_ONLINE); + } + } + else if (m_iDesiredStatus == ID_STATUS_OFFLINE) + { + if (this->conn != NULL) + { + SetEvent(update_loop_lock_); + this->conn->forceShutdown(); + LOG("Forced shutdown"); + } + } +} + +void WhatsAppProto::stayConnectedLoop(void*) +{ + bool error = true; + std::string cc, in, pass; + DBVARIANT dbv = {0}; + + if ( !db_get_s(NULL,m_szModuleName,WHATSAPP_KEY_CC,&dbv,DBVT_ASCIIZ)) + { + cc = dbv.pszVal; + db_free(&dbv); + error = cc.empty(); + } + if (error) + { + NotifyEvent(m_tszUserName,TranslateT("Please enter a country-code."),NULL,WHATSAPP_EVENT_CLIENT); + return; + } + + error = true; + if ( !db_get_s(NULL,m_szModuleName,WHATSAPP_KEY_LOGIN,&dbv,DBVT_ASCIIZ)) + { + in = dbv.pszVal; + db_free(&dbv); + error = in.empty(); + } + if (error) + { + NotifyEvent(m_tszUserName,TranslateT("Please enter a phone-number without country code."),NULL,WHATSAPP_EVENT_CLIENT); + return; + } + this->phoneNumber = cc + in; + this->jid = this->phoneNumber + "@s.whatsapp.net"; + + error = true; + if ( !db_get_s(NULL, m_szModuleName, WHATSAPP_KEY_NICK, &dbv, DBVT_ASCIIZ)) + { + this->nick = dbv.pszVal; + db_free(&dbv); + error = nick.empty(); + } + if (error) + { + NotifyEvent(m_tszUserName, TranslateT("Please enter a nickname."), NULL, WHATSAPP_EVENT_CLIENT); + return; + } + + error = true; + if ( !db_get_s(NULL,m_szModuleName,WHATSAPP_KEY_PASS,&dbv, DBVT_ASCIIZ)) + { + CallService(MS_DB_CRYPT_DECODESTRING,strlen(dbv.pszVal)+1, + reinterpret_cast(dbv.pszVal)); + pass = dbv.pszVal; + db_free(&dbv); + error = pass.empty(); + } + if (error) + { + NotifyEvent(m_tszUserName,TranslateT("Please enter a password."),NULL,WHATSAPP_EVENT_CLIENT); + return; + } + + // ----------------------------- + + Mutex writerMutex; + WALogin* login = NULL; + int desiredStatus; + + this->conn = NULL; + + while (true) + { + if (connection != NULL) + { + if (connection->getLogin() == NULL && login != NULL) + { + delete login; + login = NULL; + } + delete connection; + connection = NULL; + } + if (this->conn != NULL) + { + delete this->conn; + this->conn = NULL; + } + + desiredStatus = this->m_iDesiredStatus; + if (desiredStatus == ID_STATUS_OFFLINE || error) + { + LOG("Set status to offline"); + SetAllContactStatuses( ID_STATUS_OFFLINE, true ); + this->ToggleStatusMenuItems(false); + int prevStatus = this->m_iStatus; + this->m_iStatus = ID_STATUS_OFFLINE; + ProtoBroadcastAck(m_szModuleName, 0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE) m_iStatus, prevStatus); + break; + } + + LOG("Connecting..."); + this->m_iStatus = ID_STATUS_CONNECTING; + ProtoBroadcastAck(m_szModuleName, 0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE) ID_STATUS_OFFLINE, m_iStatus); + + CODE_BLOCK_TRY + + this->conn = new WASocketConnection("c.whatsapp.net", 5222); + + connection = new WAConnection(&this->connMutex, this, this); + login = new WALogin(connection, new BinTreeNodeReader(connection, conn, WAConnection::dictionary, WAConnection::DICTIONARY_LEN), + new BinTreeNodeWriter(connection, conn, WAConnection::dictionary, WAConnection::DICTIONARY_LEN, &writerMutex), + "s.whatsapp.net", this->phoneNumber, std::string(ACCOUNT_RESOURCE) +"-5222", base64_decode(pass), nick); + + std::vector* nextChallenge = login->login(*this->challenge); + delete this->challenge; + this->challenge = nextChallenge; + connection->setLogin(login); + connection->setVerboseId(true); // ? + if (desiredStatus != ID_STATUS_INVISIBLE) { + connection->sendAvailableForChat(); + } + + LOG("Set status to online"); + this->m_iStatus = desiredStatus; + ProtoBroadcastAck(m_szModuleName, 0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE )m_iStatus, ID_STATUS_CONNECTING); + this->ToggleStatusMenuItems(true); + + ForkThread(&WhatsAppProto::ProcessBuddyList, this); + + // #TODO Move out of try block. Exception is expected on disconnect + bool cont = true; + while (cont == true) + { + this->lastPongTime = time(NULL); + cont = connection->read(); + } + LOG("Exit from read-loop"); + + CODE_BLOCK_CATCH(WAException) + error = true; + CODE_BLOCK_CATCH(exception) + error = true; + CODE_BLOCK_CATCH_UNKNOWN + error = true; + CODE_BLOCK_END + } + LOG("Break out from loop"); +} + +void WhatsAppProto::sentinelLoop(void*) +{ + int delay = MAX_SILENT_INTERVAL; + int quietInterval; + while (WaitForSingleObjectEx(update_loop_lock_, delay * 1000, true) == WAIT_TIMEOUT) + { + if (this->m_iStatus != ID_STATUS_OFFLINE && this->connection != NULL && this->m_iDesiredStatus == this->m_iStatus) + { + // #TODO Quiet after pong or tree read? + quietInterval = difftime(time(NULL), this->lastPongTime); + if (quietInterval >= MAX_SILENT_INTERVAL) + { + CODE_BLOCK_TRY + LOG("send ping"); + this->lastPongTime = time(NULL); + this->connection->sendPing(); + CODE_BLOCK_CATCH(exception) + CODE_BLOCK_END + } + else + { + delay = MAX_SILENT_INTERVAL - quietInterval; + } + } + else + { + delay = MAX_SILENT_INTERVAL; + } + } + ResetEvent(update_loop_lock_); + LOG("Exiting sentinel loop"); +} + +void WhatsAppProto::onPing(const std::string& id) +{ + if (this->isOnline()) { + CODE_BLOCK_TRY + LOG("Sending pong with id %s", id.c_str()); + this->connection->sendPong(id); + CODE_BLOCK_CATCH_ALL + } +} diff --git a/protocols/WhatsApp/src/constants.h b/protocols/WhatsApp/src/constants.h new file mode 100644 index 0000000000..5ef23bb98b --- /dev/null +++ b/protocols/WhatsApp/src/constants.h @@ -0,0 +1,62 @@ +#if !defined(CONSTANTS_H) +#define CONSTANTS_H + +// Version management +#define __VERSION_DWORD PLUGIN_MAKE_VERSION(0, 0, 2, 0) +#define __PRODUCT_DWORD PLUGIN_MAKE_VERSION(0, 9, 14, 0) +#define __VERSION_STRING "0.0.2.0" +#define __PRODUCT_STRING "0.9.14.0" +#define __VERSION_VS_FILE 0,0,2, +#define __VERSION_VS_PROD 0,9,14,0 +#define __VERSION_VS_FILE_STRING "0, 0, 2, 0" +#define __VERSION_VS_PROD_STRING "0, 9, 14, 0" + +#define PRODUCT_NAME _T("WhatsApp Protocol") + +// Limits +#define WHATSAPP_GROUP_NAME_LIMIT 420 + +// Defaults +#define DEFAULT_MAP_STATUSES 0 +#define DEFAULT_SYSTRAY_NOTIFY 0 + +#define DEFAULT_EVENT_NOTIFICATIONS_ENABLE 1 +#define DEFAULT_EVENT_FEEDS_ENABLE 1 +#define DEFAULT_EVENT_OTHER_ENABLE 1 +#define DEFAULT_EVENT_CLIENT_ENABLE 1 +#define DEFAULT_EVENT_COLBACK 0x00ffffff +#define DEFAULT_EVENT_COLTEXT 0x00000000 +#define DEFAULT_EVENT_TIMEOUT_TYPE 0 +#define DEFAULT_EVENT_TIMEOUT -1 + +// #TODO Move constants below to WhatsAPI++ + +// WhatsApp +#define WHATSAPP_LOGIN_SERVER "c.whatsapp.net" +#define ACCOUNT_USER_AGENT "WhatsApp/2.8.3 iPhone_OS/5.0.1 Device/Unknown_(iPhone4,1)" +#define ACCOUNT_URL_CODEREQUEST "https://r.whatsapp.net/v1/code.php" +#define ACCOUNT_URL_CODEREQUESTV2 "https://v.whatsapp.net/v2/code" +#define ACCOUNT_URL_REGISTERREQUEST "https://r.whatsapp.net/v1/register.php" +#define ACCOUNT_URL_REGISTERREQUESTV2 "https://v.whatsapp.net/v2/register" +#define ACCOUNT_URL_UPLOADREQUEST "https://mms.whatsapp.net/client/iphone/upload.php" +#define ACCOUNT_URL_EXISTSV2 "https://v.whatsapp.net/v2/exist" + +// WhatsApp Nokia 302 S40 +#define ACCOUNT_RESOURCE "S40-2.3.53" +#define ACCOUNT_USER_AGENT_REGISTRATION "WhatsApp/2.3.53 S40Version/14.26 Device/Nokia302" +#define ACCOUNT_TOKEN_PREFIX1 "PdA2DJyKoUrwLw1Bg6EIhzh502dF9noR9uFCllGk" +#define ACCOUNT_TOKEN_PREFIX2 "1354754753509" + +#define WHATSAPP_RECV_MESSAGE 1 +#define WHATSAPP_SEND_MESSAGE 2 + +#define MAX_SILENT_INTERVAL 210 + +// Event flags +#define WHATSAPP_EVENT_CLIENT 0x10000000 // WhatsApp error or info message +#define WHATSAPP_EVENT_NOTIFICATION 0x40000000 // WhatsApp notification +#define WHATSAPP_EVENT_OTHER 0x80000000 // WhatsApp other event - friend requests/new messages + +#define IS_CHAT 1 + +#endif \ No newline at end of file diff --git a/protocols/WhatsApp/src/contacts.cpp b/protocols/WhatsApp/src/contacts.cpp new file mode 100644 index 0000000000..5f1ae75a67 --- /dev/null +++ b/protocols/WhatsApp/src/contacts.cpp @@ -0,0 +1,754 @@ +#include "common.h" + +bool WhatsAppProto::IsMyContact(HANDLE hContact, bool include_chat) +{ + const char *proto = GetContactProto(hContact); + if( proto && strcmp(m_szModuleName,proto) == 0 ) + { + if( include_chat ) + return true; + else + return db_get_b(hContact,m_szModuleName,"ChatRoom",0) == 0; + } else { + return false; + } +} + +HANDLE WhatsAppProto::AddToContactList(const std::string& jid, BYTE type, bool dont_check, const char *new_name, + bool isChatRoom, bool isHidden) +{ + HANDLE hContact; + + if (!dont_check) { + // First, check if this contact exists + hContact = ContactIDToHContact(jid); + + if( hContact ) + { + if (new_name != NULL) + { + DBVARIANT dbv; + string oldName; + if (db_get_s(hContact, m_szModuleName, WHATSAPP_KEY_PUSH_NAME, &dbv, DBVT_UTF8)) + { + oldName = jid.c_str(); + } + else + { + oldName = dbv.pszVal; + db_free(&dbv); + } + db_set_utf(hContact, m_szModuleName, WHATSAPP_KEY_PUSH_NAME, new_name); + + if (oldName.compare(string(new_name)) != 0) + { + this->NotifyEvent(oldName.c_str(), this->TranslateStr("is now known as '%s'", new_name), + hContact, WHATSAPP_EVENT_OTHER); + } + } + if (db_get_b(hContact, "CList", "Hidden", 0) > 0) + { + db_unset(hContact, "CList", "Hidden"); + } + return hContact; + } + } + + // If not, make a new contact! + hContact = (HANDLE)CallService(MS_DB_CONTACT_ADD, 0, 0); + if (hContact) + { + if (CallService(MS_PROTO_ADDTOCONTACT, (WPARAM)hContact, (LPARAM)m_szModuleName) == 0) + { + db_set_s(hContact ,m_szModuleName, "ID", jid.c_str()); + LOG("Added contact %s", jid.c_str()); + db_set_s(hContact, m_szModuleName, "MirVer", "WhatsApp"); + db_unset(hContact, "CList", "MyHandle"); + db_set_b(hContact, "CList", "NotOnList", 1); + + /* + std::string newNameStr; + if (hasNickName) + { + newNameStr = new_name; + } + + DBEVENTINFO dbei = {0}; + dbei.cbSize = sizeof(dbei); + dbei.szModule = m_szModuleName; + dbei.timestamp = time(NULL); + dbei.flags = DBEF_UTF; + dbei.eventType = EVENTTYPE_ADDED; + dbei.cbBlob = sizeof(DWORD) * 2 + newNameStr.length() + 5; + + PBYTE pCurBlob = dbei.pBlob = ( PBYTE ) mir_alloc( dbei.cbBlob ); + *(PDWORD)pCurBlob = 0; pCurBlob += sizeof(DWORD); // UID + *(PDWORD)pCurBlob = (DWORD)hContact; pCurBlob += sizeof(DWORD); // Contact Handle + strcpy((char*)pCurBlob, newNameStr.data()); pCurBlob += newNameStr.length()+1; // Nickname + *pCurBlob = '\0'; pCurBlob++; // First Name + *pCurBlob = '\0'; pCurBlob++; // Last Name + *pCurBlob = '\0'; pCurBlob++; // E-mail + *pCurBlob = '\0'; // Reason + + CallService(MS_DB_EVENT_ADD, 0, (LPARAM) &dbei); + */ + + DBVARIANT dbv; + if( !db_get_s(NULL, m_szModuleName, WHATSAPP_KEY_DEF_GROUP, &dbv, DBVT_WCHAR)) + { + db_set_ws(hContact, "CList", "Group", dbv.ptszVal); + db_free(&dbv); + } + + if (new_name != NULL) + { + db_set_utf(hContact, m_szModuleName, WHATSAPP_KEY_PUSH_NAME, new_name); + } + + if (isChatRoom) + { + db_set_b(hContact, m_szModuleName, "SimpleChatRoom", 1); + //ForkThread(&WhatsAppProto::SendGetGroupInfoWorker, this, (void*) &jid); + } + + return hContact; + } else { + CallService(MS_DB_CONTACT_DELETE, (WPARAM)hContact, 0); + } + } + + return INVALID_HANDLE_VALUE; +} + +HANDLE WhatsAppProto::ContactIDToHContact(const std::string& phoneNumber) +{ + // Cache + std::map::iterator it = this->hContactByJid.find(phoneNumber); + if (it != this->hContactByJid.end()) + { + return it->second; + } + + const char* id; + const char* idForContact = "ID"; + const char* idForChat = "ChatRoomID"; + + for(HANDLE hContact = db_find_first(); + hContact; + hContact = db_find_next(hContact)) + { + if(!IsMyContact(hContact, true)) + continue; + + id = db_get_b(hContact, m_szModuleName, "ChatRoom", 0) > 0 ? idForChat : idForContact; + + DBVARIANT dbv; + if( !db_get_s(hContact,m_szModuleName, id,&dbv, DBVT_ASCIIZ)) + { + if( strcmp(phoneNumber.c_str(),dbv.pszVal) == 0 ) + { + db_free(&dbv); + this->hContactByJid[phoneNumber] = hContact; + return hContact; + } + else + { + db_free(&dbv); + } + } + } + + return 0; +} + +void WhatsAppProto::SetAllContactStatuses(int status, bool reset_client) +{ + for (HANDLE hContact = db_find_first(); + hContact; + hContact = db_find_next(hContact)) + { + if (!IsMyContact(hContact)) + continue; + + if (reset_client) { + DBVARIANT dbv; + if (!db_get_s(hContact,m_szModuleName,"MirVer",&dbv,DBVT_WCHAR)) { + if (_tcscmp(dbv.ptszVal, _T("WhatsApp"))) + db_set_ws(hContact,m_szModuleName,"MirVer", _T("WhatsApp")); + db_free(&dbv); + } + + db_set_ws(hContact, "CList", "StatusMsg", _T("")); + } + + if (db_get_w(hContact,m_szModuleName,"Status",ID_STATUS_OFFLINE) != status) + db_set_w(hContact,m_szModuleName,"Status",status); + } +} + +void WhatsAppProto::ProcessBuddyList(void*) +{ + std::vector jids; + DBVARIANT dbv; + for (HANDLE hContact = db_find_first(); hContact; hContact = db_find_next(hContact)) { + if (!IsMyContact(hContact)) + continue; + + if (!db_get_s(hContact, m_szModuleName, WHATSAPP_KEY_ID, &dbv, DBVT_ASCIIZ)) { + std::string id(dbv.pszVal); + db_free(&dbv); + + CODE_BLOCK_TRY + if (!db_get_b(hContact, "CList", "Hidden", 0)) + { + // Do not request picture for inactive groups - this would make the group visible again + jids.push_back(id); + } + if (db_get_b(hContact, m_szModuleName, "SimpleChatRoom", 0) == 0) + { + this->connection->sendQueryLastOnline(id); + this->connection->sendPresenceSubscriptionRequest(id); + } + CODE_BLOCK_CATCH_ALL + } + } + if (jids.size() > 0) { + CODE_BLOCK_TRY + this->connection->sendGetPictureIds(jids); + CODE_BLOCK_CATCH_ALL + } + CODE_BLOCK_TRY + this->connection->sendGetGroups(); + this->connection->sendGetOwningGroups(); + CODE_BLOCK_CATCH_ALL +} + +void WhatsAppProto::SearchAckThread(void *targ) +{ + char *id = mir_utf8encodeT((TCHAR*)targ); + std::string jid(id); + jid.append("@s.whatsapp.net"); + + this->connection->sendQueryLastOnline(jid); + this->connection->sendPresenceSubscriptionRequest(jid); + + mir_free(targ); + mir_free(id); +} + +void WhatsAppProto::onAvailable(const std::string& paramString, bool paramBoolean) +{ + HANDLE hContact = this->AddToContactList(paramString, 0, false); + if (hContact != NULL) + { + if (paramBoolean) + { + /* + this->connection->sendGetPicture(paramString, "image", "old", "new"); + std::vector ids; + ids.push_back(paramString); + this->connection->sendGetPictureIds(ids); + */ + db_set_w(hContact, m_szModuleName, "Status", ID_STATUS_ONLINE); + } + else + { + db_set_w(hContact, m_szModuleName, "Status", ID_STATUS_OFFLINE); + this->UpdateStatusMsg(hContact); + } + } + + db_set_dw(hContact, this->m_szModuleName, WHATSAPP_KEY_LAST_SEEN, 0); + this->UpdateStatusMsg(hContact); +} + +void WhatsAppProto::onLastSeen(const std::string& paramString1, int paramInt, std::string* paramString2) +{ + /* + HANDLE hContact = this->ContactIDToHContact(paramString1); + if (hContact == NULL) + { + // This contact was searched + PROTOSEARCHRESULT isr = {0}; + isr.cbSize = sizeof(isr); + isr.flags = PSR_TCHAR; + isr.id = mir_a2t_cp(id.c_str(), CP_UTF8); + isr.nick = ""; + isr.firstName = ""; + isr.lastName = ""; + isr.email = ""; + ProtoBroadcastAck(m_szModuleName, NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, targ, (LPARAM)&isr); + // #TODO + } + */ + HANDLE hContact = this->AddToContactList(paramString1, 0, false); + db_set_dw(hContact, this->m_szModuleName, WHATSAPP_KEY_LAST_SEEN, paramInt); + + this->UpdateStatusMsg(hContact); +} + +void WhatsAppProto::UpdateStatusMsg(HANDLE hContact) +{ + std::wstringstream ss; + + int lastSeen = db_get_dw(hContact, m_szModuleName, WHATSAPP_KEY_LAST_SEEN, -1); + if (lastSeen != -1) + { + time_t timestamp = time(NULL) - lastSeen; + + tm* t = localtime(×tamp); + ss << _T("Last seen on ") << std::setfill(_T('0')) << std::setw(2) << (t->tm_mon + 1) << + _T("/") << std::setw(2) << t->tm_mday << _T("/") << (t->tm_year + 1900) << _T(" ") + << std::setw(2) << t->tm_hour << _T(":") << std::setw(2) << t->tm_min; + } + + int state = db_get_dw(hContact, m_szModuleName, WHATSAPP_KEY_LAST_MSG_STATE, 2); + if (state < 2 && lastSeen != -1) + ss << _T(" - "); + for (; state < 2; ++state) + ss << _T("\u2713"); + + db_set_ws(hContact, "CList", "StatusMsg", ss.str().c_str()); +} + +void WhatsAppProto::onPictureChanged(const std::string& from, const std::string& author, bool set) +{ + if (this->isOnline()) + { + vector ids; + ids.push_back(from); + this->connection->sendGetPictureIds(ids); + } +} + +void WhatsAppProto::onSendGetPicture(const std::string& jid, const std::vector& data, const std::string& oldId, const std::string& newId) +{ + HANDLE hContact = this->ContactIDToHContact(jid); + if (hContact) + { + LOG("Updating avatar for jid %s", jid.c_str()); + + // Save avatar + std::tstring filename = this->GetAvatarFolder() ; + if (_taccess(filename.c_str(), 0)) + CallService(MS_UTILS_CREATEDIRTREET, 0, (LPARAM)filename.c_str()); + filename = filename + _T("\\") + (TCHAR*) _A2T(jid.c_str()) + _T("-") + (TCHAR*) _A2T(newId.c_str()) +_T(".jpg"); + FILE *f = _tfopen(filename.c_str(), _T("wb")); + int r = fwrite(std::string(data.begin(), data.end()).c_str(), 1, data.size(), f); + fclose(f); + + PROTO_AVATAR_INFORMATIONT ai = {sizeof(ai)}; + ai.hContact = hContact; + ai.format = PA_FORMAT_JPEG; + _tcsncpy(ai.filename, filename.c_str(), SIZEOF(ai.filename)); + ai.filename[SIZEOF(ai.filename)-1] = 0; + + int ackResult; + if (r > 0) + { + db_set_s(hContact, m_szModuleName, WHATSAPP_KEY_AVATAR_ID, newId.c_str()); + ackResult = ACKRESULT_SUCCESS; + } + else + { + ackResult = ACKRESULT_FAILED; + } + ProtoBroadcastAck(m_szModuleName, ai.hContact, ACKTYPE_AVATAR, ackResult, (HANDLE)&ai, 0); + } +} + +void WhatsAppProto::onSendGetPictureIds(std::map* ids) +{ + for (std::map::iterator it = ids->begin(); it != ids->end(); ++it) + { + HANDLE hContact = this->AddToContactList(it->first); + if (hContact != NULL) + { + DBVARIANT dbv; + std::string oldId; + if (db_get_s(hContact, m_szModuleName, WHATSAPP_KEY_AVATAR_ID, &dbv, DBVT_ASCIIZ)) + { + oldId = ""; + } + else + { + oldId = dbv.pszVal; + db_free(&dbv); + } + + if (it->second.size() > 0 && it->second.compare(oldId) != 0) + { + CODE_BLOCK_TRY + this->connection->sendGetPicture(it->first, "image", oldId, it->second); + CODE_BLOCK_CATCH_ALL + } + } + } +} + +string WhatsAppProto::GetContactDisplayName(HANDLE hContact) +{ + return string((CHAR*) CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM) hContact, 0)); +} + +string WhatsAppProto::GetContactDisplayName(const string& jid) +{ + HANDLE hContact = this->ContactIDToHContact(jid); + return hContact ? this->GetContactDisplayName(hContact) : (string("+")+ Utilities::removeWaDomainFromJid(jid)); +} + +// Group contacts -------------------------- + +void WhatsAppProto::SendGetGroupInfoWorker(void* data) +{ + if (this->isOnline()) + { + this->connection->sendGetGroupInfo(*((std::string*) data)); + } +} + +void WhatsAppProto::onGroupInfo(const std::string& gjid, const std::string& ownerJid, const std::string& subject, const std::string& createrJid, int paramInt1, int paramInt2) +{ + LOG("'%s', '%s', '%s', '%s'", gjid.c_str(), ownerJid.c_str(), subject.c_str(), createrJid.c_str()); + HANDLE hContact = ContactIDToHContact(gjid); + if (!hContact) + { + LOG("Group info requested for non existing contact '%s'", gjid.c_str()); + return; + } + db_set_b(hContact, m_szModuleName, "SimpleChatRoom", ownerJid.compare(this->jid) == 0 ? 2 : 1); + if (this->isOnline()) + { + this->connection->sendGetParticipants(gjid); + } +} + +void WhatsAppProto::onGroupInfoFromList(const std::string& paramString1, const std::string& paramString2, const std::string& paramString3, const std::string& paramString4, int paramInt1, int paramInt2) +{ + // Called before onOwningGroups() or onParticipatingGroups() is called! + LOG(""); +} + +void WhatsAppProto::onGroupNewSubject(const std::string& from, const std::string& author, const std::string& newSubject, int paramInt) +{ + LOG("'%s', '%s', '%s'", from.c_str(), author.c_str(), newSubject.c_str()); + HANDLE hContact = this->AddToContactList(from, 0, false, newSubject.c_str(), true); +} + +void WhatsAppProto::onGroupAddUser(const std::string& paramString1, const std::string& paramString2) +{ + LOG("%s - user: %s", paramString1.c_str(), paramString2.c_str()); + HANDLE hContact = this->AddToContactList(paramString1); + std::string groupName(this->GetContactDisplayName(hContact)); + + if (paramString2.compare(this->jid) == 0) + { + this->NotifyEvent(groupName, this->TranslateStr("You have been added to the group"), hContact, WHATSAPP_EVENT_OTHER); + db_set_b(hContact, m_szModuleName, "IsGroupMember", 1); + } + else + { + this->NotifyEvent(groupName, this->TranslateStr("User '%s' has been added to the group", + this->GetContactDisplayName(paramString2).c_str()), hContact, WHATSAPP_EVENT_OTHER); + } + + if(this->isOnline()) + { + this->connection->sendGetGroupInfo(paramString1); + } +} + +void WhatsAppProto::onGroupRemoveUser(const std::string& paramString1, const std::string& paramString2) +{ + LOG("%s - user: %s", paramString1.c_str(), paramString2.c_str()); + HANDLE hContact = this->ContactIDToHContact(paramString1); + if (!hContact) + return; + + string groupName(this->GetContactDisplayName(hContact)); + + if (paramString2.compare(this->jid) == 0) + { + //db_set_b(hContact, "CList", "Hidden", 1); + db_set_b(hContact, m_szModuleName, "IsGroupMember", 0); + + this->NotifyEvent(groupName, this->TranslateStr("You have been removed from the group"), + hContact, WHATSAPP_EVENT_OTHER); + } + else if(this->isOnline()) + { + this->NotifyEvent(groupName, this->TranslateStr("User '%s' has been removed from the group", + this->GetContactDisplayName(paramString2).c_str()), hContact, WHATSAPP_EVENT_OTHER); + this->connection->sendGetGroupInfo(paramString1); + //this->connection->sendGetParticipants(paramString1); + } +} + +void WhatsAppProto::onLeaveGroup(const std::string& paramString) +{ + // Won't be called for unknown reasons! + LOG("%s", this->GetContactDisplayName(paramString).c_str()); + HANDLE hContact = this->ContactIDToHContact(paramString); + if (hContact) + { + //db_set_b(hContact, "CList", "Hidden", 1); + db_set_b(hContact, m_szModuleName, "IsGroupMember", 0); + } +} + +void WhatsAppProto::onGetParticipants(const std::string& gjid, const std::vector& participants) +{ + LOG("%s", this->GetContactDisplayName(gjid).c_str()); + + HANDLE hUserContact; + HANDLE hContact = this->ContactIDToHContact(gjid); + if (!hContact) + return; + + if (db_get_b(hContact, "CList", "Hidden", 0) == 1) + return; + + bool isHidden = true; + bool isOwningGroup = db_get_b(hContact, m_szModuleName, "SimpleChatRoom", 0) == 2; + + if (isOwningGroup) + { + this->isMemberByGroupContact[hContact].clear(); + } + + for (std::vector::const_iterator it = participants.begin(); it != participants.end(); ++it) + { + // Hide, if we are not member of the group + // Sometimes the group is shown shortly after hiding it again, due to other threads which stored the contact + // in a cache before it has been removed (E.g. picture-id list in processBuddyList) + if (isHidden && this->jid.compare(*it) == 0) + { + isHidden = false; + if (!isOwningGroup) + { + // Break, as we don't need to collect group-members + break; + } + } + + // #TODO Slow for big count of participants + // #TODO If a group is hidden it has been deleted from the local contact list + // => don't allow to add users anymore + if (isOwningGroup) + { + hUserContact = this->ContactIDToHContact(*it); + if (hUserContact) + { + this->isMemberByGroupContact[hContact][hUserContact] = true; + } + } + } + if (isHidden) + { + //db_set_b(hContact, "CList", "Hidden", 1); + // #TODO Check if it's possible to reach this point at all + db_set_b(hContact, m_szModuleName, "IsGroupMember", 0); + } +} + +// Menu handler +INT_PTR __cdecl WhatsAppProto::OnAddContactToGroup(WPARAM wParam, LPARAM, LPARAM lParam) +{ + string a = GetContactDisplayName((HANDLE) wParam); + string b = GetContactDisplayName((HANDLE) lParam); + LOG("Request add user %s to group %s", a.c_str(), b.c_str()); + + if (!this->isOnline()) + return NULL; + + DBVARIANT dbv; + + if (db_get_s((HANDLE) wParam, m_szModuleName, "ID", &dbv, DBVT_ASCIIZ)) + return NULL; + + std::vector participants; + participants.push_back(string(dbv.pszVal)); + db_free(&dbv); + + if (db_get_s((HANDLE) lParam, m_szModuleName, "ID", &dbv, DBVT_ASCIIZ)) + return NULL; + + this->connection->sendAddParticipants(string(dbv.pszVal), participants); + + db_free(&dbv); + return NULL; +} + +// Menu handler +INT_PTR __cdecl WhatsAppProto::OnRemoveContactFromGroup(WPARAM wParam, LPARAM, LPARAM lParam) +{ + string a = GetContactDisplayName((HANDLE) wParam); + string b = GetContactDisplayName((HANDLE) lParam); + LOG("Request remove user %s from group %s", a.c_str(), b.c_str()); + + if (!this->isOnline()) + return NULL; + + DBVARIANT dbv; + + if (db_get_s((HANDLE) lParam, m_szModuleName, "ID", &dbv, DBVT_ASCIIZ)) + return NULL; + + std::vector participants; + participants.push_back(string(dbv.pszVal)); + db_free(&dbv); + + if (db_get_s((HANDLE) wParam, m_szModuleName, "ID", &dbv, DBVT_ASCIIZ)) + return NULL; + + this->connection->sendRemoveParticipants(string(dbv.pszVal), participants); + + db_free(&dbv); + return NULL; +} + +void WhatsAppProto::onOwningGroups(const std::vector& paramVector) +{ + LOG(""); + this->HandleReceiveGroups(paramVector, true); +} + +void WhatsAppProto::onParticipatingGroups(const std::vector& paramVector) +{ + LOG(""); + this->HandleReceiveGroups(paramVector, false); +} + +void WhatsAppProto::HandleReceiveGroups(const std::vector& groups, bool isOwned) +{ + HANDLE hContact; + map isMember; // at the moment, only members of owning groups are stored + + // This could take long time if there are many new groups which aren't + // yet stored to the database. But that should be a rare case + for (std::vector::const_iterator it = groups.begin(); it != groups.end(); ++it) + { + hContact = this->AddToContactList(*it, 0, false, NULL, true); + db_set_b(hContact, m_szModuleName, "IsGroupMember", 1); + if (isOwned) + { + this->isMemberByGroupContact[hContact]; // []-operator creates entry, if it doesn't exist + db_set_b(hContact, m_szModuleName, "SimpleChatRoom", 2); + this->connection->sendGetParticipants(*it); + } + else + { + isMember[hContact] = true; + } + } + + // Mark as non-meber if group only exists locally + if (!isOwned) + { + for (hContact = db_find_first(); hContact; hContact = db_find_next(hContact)) + { + if (IsMyContact(hContact) && db_get_b(hContact, m_szModuleName, "SimpleChatRoom", 0) > 0) + { + //LOG("Set IsGroupMember to 0 for '%s'", this->GetContactDisplayName(hContact).c_str()); + db_set_b(hContact, m_szModuleName, "IsGroupMember", + isMember.find(hContact) == isMember.end() ? 0 : 1); + } + } + } +} + +void WhatsAppProto::onGroupCreated(const std::string& paramString1, const std::string& paramString2) +{ + // Must be received after onOwningGroups() :/ + LOG("%s / %s", paramString1.c_str(), paramString2.c_str()); + string jid = paramString2 +string("@")+ paramString1; + HANDLE hContact = this->AddToContactList(jid, 0, false, NULL, true); + db_set_b(hContact, m_szModuleName, "SimpleChatRoom", 2); +} + +// Menu-handler +int __cdecl WhatsAppProto::OnCreateGroup(WPARAM wParam, LPARAM lParam) +{ + LOG(""); + input_box* ib = new input_box; + ib->defaultValue = _T(""); + ib->limit = WHATSAPP_GROUP_NAME_LIMIT; + ib->proto = this; + ib->text = _T("Enter group subject"); + ib->title = _T("WhatsApp - Create Group"); + ib->thread = &WhatsAppProto::SendCreateGroupWorker; + HWND hDlg = CreateDialogParam(g_hInstance, MAKEINTRESOURCE(IDD_INPUTBOX), 0, WhatsAppInputBoxProc, + reinterpret_cast(ib)); + ShowWindow(hDlg, SW_SHOW); + return FALSE; +} + +void __cdecl WhatsAppProto::SendSetGroupNameWorker(void* data) +{ + input_box_ret* ibr(static_cast(data)); + string groupName(ibr->value); + mir_free(ibr->value); + DBVARIANT dbv; + if (!db_get_s(*((HANDLE*) ibr->userData), m_szModuleName, WHATSAPP_KEY_ID, &dbv, DBVT_ASCIIZ) + && this->isOnline()) + { + this->connection->sendSetNewSubject(dbv.pszVal, groupName); + db_free(&dbv); + } + delete ibr->userData; + delete ibr; +} + +void __cdecl WhatsAppProto::SendCreateGroupWorker(void* data) +{ + input_box_ret* ibr(static_cast(data)); + string groupName(ibr->value); + mir_free(ibr->value); + if (this->isOnline()) + { + this->connection->sendCreateGroupChat(groupName); + } +} + +int __cdecl WhatsAppProto::OnChangeGroupSubject(WPARAM wParam, LPARAM lParam) +{ + DBVARIANT dbv; + HANDLE hContact = reinterpret_cast(wParam); + input_box* ib = new input_box; + + if (db_get_s(hContact, m_szModuleName, WHATSAPP_KEY_PUSH_NAME, &dbv, DBVT_WCHAR)) + { + ib->defaultValue = _T(""); + } + else + { + ib->defaultValue = dbv.ptszVal; + db_free(&dbv); + } + ib->limit = WHATSAPP_GROUP_NAME_LIMIT; + ib->text = _T("Enter new group subject"); + ib->title = _T("WhatsApp - Change Group Subject"); + + ib->thread = &WhatsAppProto::SendSetGroupNameWorker; + ib->proto = this; + HANDLE* hContactPtr = new HANDLE(hContact); + ib->userData = (void*) hContactPtr; + + HWND hDlg = CreateDialogParam(g_hInstance, MAKEINTRESOURCE(IDD_INPUTBOX), 0, WhatsAppInputBoxProc, + reinterpret_cast(ib)); + ShowWindow(hDlg, SW_SHOW); + return 0; +} + +int __cdecl WhatsAppProto::OnLeaveGroup(WPARAM wParam, LPARAM) +{ + DBVARIANT dbv; + HANDLE hContact = reinterpret_cast(wParam); + if (this->isOnline() && !db_get_s(hContact, m_szModuleName, WHATSAPP_KEY_ID, &dbv, DBVT_ASCIIZ)) + { + db_set_b(hContact, m_szModuleName, "IsGroupMember", 0); + this->connection->sendLeaveGroup(dbv.pszVal); + db_free(&dbv); + } + return 0; +} \ No newline at end of file diff --git a/protocols/WhatsApp/src/db.h b/protocols/WhatsApp/src/db.h new file mode 100644 index 0000000000..d78d2e75d6 --- /dev/null +++ b/protocols/WhatsApp/src/db.h @@ -0,0 +1,47 @@ +// DB macros +#define getByte( setting, error ) db_get_b( NULL, m_szModuleName, setting, error ) +#define setByte( setting, value ) db_set_b( NULL, m_szModuleName, setting, value ) +#define getWord( setting, error ) db_get_w( NULL, m_szModuleName, setting, error ) +#define setWord( setting, value ) db_set_w( NULL, m_szModuleName, setting, value ) +#define getDword( setting, error ) db_get_dw( NULL, m_szModuleName, setting, error ) +#define setDword( setting, value ) db_set_dw( NULL, m_szModuleName, setting, value ) +#define getString( setting, dest ) db_get_s( NULL, m_szModuleName, setting, dest, DBVT_ASCIIZ ) +#define setString( setting, value ) db_set_s( NULL, m_szModuleName, setting, value ) +#define getTString( setting, dest ) DBGetContactSettingTString( NULL, m_szModuleName, setting, dest ) +#define setTString( setting, value ) DBWriteContactSettingTString( NULL, m_szModuleName, setting, value ) +#define getU8String( setting, dest ) DBGetContactSettingUTF8String( NULL, m_szModuleName, setting, dest ) +#define setU8String( setting, value ) DBWriteContactSettingUTF8String( NULL, m_szModuleName, setting, value ) +#define deleteSetting( setting ) DBDeleteContactSetting( NULL, m_szModuleName, setting ) + +// DB keys +#define WHATSAPP_KEY_ID "ID" +#define WHATSAPP_KEY_LOGIN "Login" +#define WHATSAPP_KEY_CC "CountryCode" +#define WHATSAPP_KEY_NICK "Nickname" +#define WHATSAPP_KEY_PASS "Password" +#define WHATSAPP_KEY_IDX "DeviceID" +#define WHATSAPP_KEY_MAP_STATUSES "MapStatuses" +#define WHATSAPP_KEY_LOGGING_ENABLE "LoggingEnable" +#define WHATSAPP_KEY_NAME "RealName" +#define WHATSAPP_KEY_PUSH_NAME "Nick" +#define WHATSAPP_KEY_LAST_SEEN "LastSeen" +#define WHATSAPP_KEY_LAST_MSG_ID_HEADER "LastMsgIdHeader" +#define WHATSAPP_KEY_LAST_MSG_ID "LastMsgId" +#define WHATSAPP_KEY_LAST_MSG_STATE "LastMsgState" +#define WHATSAPP_KEY_AVATAR_ID "AvatarId" +#define WHATSAPP_KEY_SYSTRAY_NOTIFY "UseSystrayNotify" +#define WHATSAPP_KEY_DEF_GROUP "DefaultGroup" + +#define WHATSAPP_KEY_EVENT_CLIENT_ENABLE "EventClientEnable" +#define WHATSAPP_KEY_EVENT_OTHER_ENABLE "EventOtherEnable" + +#define WHATSAPP_KEY_EVENT_CLIENT_COLBACK "PopupClientColorBack" +#define WHATSAPP_KEY_EVENT_CLIENT_COLTEXT "PopupClientColorText" +#define WHATSAPP_KEY_EVENT_CLIENT_TIMEOUT "PopupClientTimeout" +#define WHATSAPP_KEY_EVENT_CLIENT_DEFAULT "PopupClientColorDefault" + +#define WHATSAPP_KEY_EVENT_OTHER_COLBACK "PopupOtherColorBack" +#define WHATSAPP_KEY_EVENT_OTHER_COLTEXT "PopupOtherColorText" +#define WHATSAPP_KEY_EVENT_OTHER_TIMEOUT "PopupOtherTimeout" +#define WHATSAPP_KEY_EVENT_OTHER_DEFAULT "PopupOtherColorDefault" + diff --git a/protocols/WhatsApp/src/definitions.h b/protocols/WhatsApp/src/definitions.h new file mode 100644 index 0000000000..4e24139d67 --- /dev/null +++ b/protocols/WhatsApp/src/definitions.h @@ -0,0 +1,18 @@ +#if !defined(DEFINITIONS_H) +#define DEFINITIONS_H + +#define FLAG_CONTAINS(x,y) ( ( x & y ) == y ) +#define REMOVE_FLAG(x,y) ( x = ( x & ~y )) + +#define LOG(fmt, ...) Log(__FUNCTION__, fmt, ##__VA_ARGS__) +#define CODE_BLOCK_TRY try { +#define CODE_BLOCK_CATCH(ex) } catch (ex& e) { LOG("Exception: %s", e.what()); +#define CODE_BLOCK_CATCH_UNKNOWN } catch (...) { LOG("Unknown exception"); +#define CODE_BLOCK_CATCH_ALL } catch (WAException& e) { LOG("Exception: %s", e.what()); \ + } catch (exception& e) { LOG("Exception: %s", e.what()); \ + } catch (...) { LOG("Unknown exception"); } +#define CODE_BLOCK_END } + +#define NIIF_INTERN_TCHAR NIIF_INTERN_UNICODE + +#endif \ No newline at end of file diff --git a/protocols/WhatsApp/src/dialogs.cpp b/protocols/WhatsApp/src/dialogs.cpp new file mode 100644 index 0000000000..81a17351fb --- /dev/null +++ b/protocols/WhatsApp/src/dialogs.cpp @@ -0,0 +1,179 @@ +#include "common.h" + +INT_PTR CALLBACK WhatsAppAccountProc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam ) +{ + WhatsAppProto *proto; + + switch ( message ) + { + + case WM_INITDIALOG: + TranslateDialogDefault(hwnd); + + proto = reinterpret_cast(lparam); + SetWindowLongPtr(hwnd,GWLP_USERDATA,lparam); + + DBVARIANT dbv; + + if ( !db_get_s(0,proto->ModuleName(),WHATSAPP_KEY_CC,&dbv,DBVT_ASCIIZ)) + { + SetDlgItemTextA(hwnd,IDC_CC,dbv.pszVal); + db_free(&dbv); + } + + if ( !db_get_s(0,proto->ModuleName(),WHATSAPP_KEY_LOGIN,&dbv,DBVT_ASCIIZ)) + { + SetDlgItemTextA(hwnd,IDC_LOGIN,dbv.pszVal); + db_free(&dbv); + } + + if ( !db_get_s(0,proto->ModuleName(),WHATSAPP_KEY_NICK,&dbv,DBVT_ASCIIZ)) + { + SetDlgItemTextA(hwnd,IDC_NICK,dbv.pszVal); + db_free(&dbv); + } + + if ( !db_get_s(0,proto->ModuleName(),WHATSAPP_KEY_PASS,&dbv,DBVT_ASCIIZ)) + { + CallService(MS_DB_CRYPT_DECODESTRING,strlen(dbv.pszVal)+1, + reinterpret_cast(dbv.pszVal)); + SetDlgItemTextA(hwnd,IDC_PW,dbv.pszVal); + db_free(&dbv); + } + + + if (!proto->isOffline()) { + SendMessage(GetDlgItem(hwnd,IDC_CC),EM_SETREADONLY,1,0); + SendMessage(GetDlgItem(hwnd,IDC_LOGIN),EM_SETREADONLY,1,0); + SendMessage(GetDlgItem(hwnd,IDC_NICK),EM_SETREADONLY,1,0); + SendMessage(GetDlgItem(hwnd,IDC_PW),EM_SETREADONLY,1,0); + } + + return TRUE; + + case WM_COMMAND: + if (LOWORD(wparam) == IDC_BUTTON_REQUEST_CODE) + { + if (MessageBox(NULL, _T("An SMS with registration-code will be sent to your mobile phone.\nNotice that you are not able to use the real WhatsApp and this plugin simultaneousley!\nContinue?"), + PRODUCT_NAME, MB_YESNO) == IDYES) + { + proto = reinterpret_cast(GetWindowLongPtr(hwnd,GWLP_USERDATA)); + proto->RequestCode(); + } + } + + if ( HIWORD( wparam ) == EN_CHANGE && reinterpret_cast(lparam) == GetFocus()) + { + switch(LOWORD(wparam)) + { + case IDC_CC: + case IDC_LOGIN: + case IDC_NICK: + case IDC_PW: + SendMessage(GetParent(hwnd),PSM_CHANGED,0,0); + } + } + break; + + case WM_NOTIFY: + if ( reinterpret_cast(lparam)->code == PSN_APPLY ) + { + proto = reinterpret_cast(GetWindowLongPtr(hwnd,GWLP_USERDATA)); + char str[128]; + + GetDlgItemTextA(hwnd, IDC_CC, str, sizeof(str)); + db_set_s(0,proto->ModuleName(),WHATSAPP_KEY_CC,str); + + GetDlgItemTextA(hwnd, IDC_LOGIN, str, sizeof(str)); + db_set_s(0,proto->ModuleName(),WHATSAPP_KEY_LOGIN,str); + + GetDlgItemTextA(hwnd, IDC_NICK, str, sizeof(str)); + db_set_s(0,proto->ModuleName(),WHATSAPP_KEY_NICK,str); + + GetDlgItemTextA(hwnd,IDC_PW,str,sizeof(str)); + CallService(MS_DB_CRYPT_ENCODESTRING,sizeof(str),reinterpret_cast(str)); + db_set_s(0,proto->ModuleName(),WHATSAPP_KEY_PASS,str); + + return TRUE; + } + break; + + } + + return FALSE; + +} + +INT_PTR CALLBACK WhatsAppInputBoxProc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam ) +{ + input_box* ib; + + switch(message) + { + + case WM_INITDIALOG: + { + TranslateDialogDefault(hwnd); + + ib = reinterpret_cast(lparam); + SetWindowLongPtr(hwnd,GWLP_USERDATA,lparam); + SendDlgItemMessage(hwnd,IDC_VALUE,EM_LIMITTEXT,ib->limit,0); + SetDlgItemText(hwnd, IDC_VALUE, ib->defaultValue.c_str()); + SetDlgItemText(hwnd, IDC_TEXT, ib->text.c_str()); + + /* + DBVARIANT dbv = { DBVT_TCHAR }; + if (!DBGetContactSettingTString(NULL,ib->proto->m_szModuleName,WHATSAPP_KEY_PUSH_NAME,&dbv)) + { + SetWindowText( hwnd, dbv.ptszVal ); + db_free( &dbv ); + } + */ + SetWindowText(hwnd, ib->title.c_str()); + } + + EnableWindow(GetDlgItem( hwnd, IDC_OK ), FALSE); + return TRUE; + + case WM_COMMAND: + if ( LOWORD( wparam ) == IDC_VALUE && HIWORD( wparam ) == EN_CHANGE ) + { + ib = reinterpret_cast(GetWindowLongPtr(hwnd,GWLP_USERDATA)); + size_t len = SendDlgItemMessage(hwnd,IDC_VALUE,WM_GETTEXTLENGTH,0,0); + TCHAR str[4]; + _sntprintf( str, 4, TEXT( "%d" ), ib->limit - len ); + //SetDlgItemText(hwnd,IDC_CHARACTERS,str); + + EnableWindow(GetDlgItem( hwnd, IDC_OK ), len > 0); + return TRUE; + } + else if ( LOWORD( wparam ) == IDC_OK ) + { + ib = reinterpret_cast(GetWindowLongPtr(hwnd,GWLP_USERDATA)); + TCHAR* value = new TCHAR[ib->limit+1]; + + GetDlgItemText(hwnd,IDC_VALUE,value, ib->limit + 1); + ShowWindow(hwnd,SW_HIDE); + + input_box_ret* ret = new input_box_ret; + ret->userData = ib->userData; + ret->value = mir_utf8encodeT(value); + delete value; + ForkThread(ib->thread, ib->proto, ret); + EndDialog(hwnd, wparam); + delete ib; + return TRUE; + } + else if ( LOWORD( wparam ) == IDC_CANCEL ) + { + EndDialog(hwnd, wparam); + ib = reinterpret_cast(GetWindowLongPtr(hwnd,GWLP_USERDATA)); + delete ib; + return TRUE; + } + break; + + } + + return FALSE; +} \ No newline at end of file diff --git a/protocols/WhatsApp/src/dialogs.h b/protocols/WhatsApp/src/dialogs.h new file mode 100644 index 0000000000..8b34cd0854 --- /dev/null +++ b/protocols/WhatsApp/src/dialogs.h @@ -0,0 +1,7 @@ +#if !defined(DIALOGS_H) +#define DIALOGS_H + +INT_PTR CALLBACK WhatsAppAccountProc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam ); +INT_PTR CALLBACK WhatsAppInputBoxProc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam ); + +#endif \ No newline at end of file diff --git a/protocols/WhatsApp/src/entities.h b/protocols/WhatsApp/src/entities.h new file mode 100644 index 0000000000..d48fbfb573 --- /dev/null +++ b/protocols/WhatsApp/src/entities.h @@ -0,0 +1,41 @@ +#if !defined(ENTITIES_H) +#define ENTITIES_H + +struct send_direct +{ + send_direct(HANDLE hContact,const std::string &msg, HANDLE msgid, bool isChat = false) + : hContact(hContact), msg(msg), msgid(msgid) + {} + HANDLE hContact; + std::string msg; + HANDLE msgid; +}; + +struct send_typing +{ + send_typing(HANDLE hContact,const int status) : hContact(hContact), status(status) {} + HANDLE hContact; + int status; +}; + +struct input_box +{ + tstring text; + tstring title; + tstring defaultValue; + int limit; + + void (__cdecl WhatsAppProto::*thread)(void*); + WhatsAppProto* proto; + void* userData; +}; + +struct input_box_ret // has to be deleted by input-box handler +{ + void* userData; // has to be deleted by input-box handler + char* value; // mir_free() has to be called by input-box handler +}; + + + +#endif // ENTITIES_H \ No newline at end of file diff --git a/protocols/WhatsApp/src/main.cpp b/protocols/WhatsApp/src/main.cpp new file mode 100644 index 0000000000..2545bb6736 --- /dev/null +++ b/protocols/WhatsApp/src/main.cpp @@ -0,0 +1,126 @@ +#include "common.h" + +CLIST_INTERFACE* pcli; +int hLangpack; + +HINSTANCE g_hInstance; +std::string g_strUserAgent; +DWORD g_mirandaVersion; + +PLUGININFOEX pluginInfo = { + sizeof(PLUGININFOEX), + "WhatsApp Protocol", + __VERSION_DWORD, + "Provides basic support for WhatsApp. [Built: "__DATE__" "__TIME__"]", + "Uli Hecht", + "uli.hecht@gmail.com", + "© 2013 Uli Hecht", + "http://example.com", + UNICODE_AWARE, //not transient + // {4f1ff7fa-4d75-44b9-93b0-2ced2e4f9e3e} + { 0x4f1ff7fa, 0x4d75, 0x44b9, { 0x93, 0xb0, 0x2c, 0xed, 0x2e, 0x4f, 0x9e, 0x3e } } + +}; + + +///////////////////////////////////////////////////////////////////////////// +// Protocol instances +static int compare_protos(const WhatsAppProto *p1, const WhatsAppProto *p2) +{ + return _tcscmp(p1->m_tszUserName, p2->m_tszUserName); +} + +OBJLIST g_Instances(1, compare_protos); + +DWORD WINAPI DllMain(HINSTANCE hInstance,DWORD,LPVOID) +{ + g_hInstance = hInstance; + return TRUE; +} + +extern "C" __declspec(dllexport) PLUGININFOEX* MirandaPluginInfoEx(DWORD mirandaVersion) +{ + g_mirandaVersion = mirandaVersion; + return &pluginInfo; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Interface information + +extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = {MIID_PROTOCOL, MIID_LAST}; + +///////////////////////////////////////////////////////////////////////////////////////// +// Load + +static PROTO_INTERFACE* protoInit(const char *proto_name,const TCHAR *username ) +{ + WhatsAppProto *proto = new WhatsAppProto(proto_name,username); + g_Instances.insert(proto); + return proto; +} + +static int protoUninit(PROTO_INTERFACE* proto) +{ + g_Instances.remove(( WhatsAppProto* )proto); + return EXIT_SUCCESS; +} + +static HANDLE g_hEvents[1]; + +extern "C" int __declspec(dllexport) Load(void) +{ + mir_getLP(&pluginInfo); + + pcli = reinterpret_cast( CallService( + MS_CLIST_RETRIEVE_INTERFACE,0,reinterpret_cast(g_hInstance))); + + PROTOCOLDESCRIPTOR pd = { sizeof(pd) }; + pd.szName = "WhatsApp"; + pd.type = PROTOTYPE_PROTOCOL; + pd.fnInit = protoInit; + pd.fnUninit = protoUninit; + CallService(MS_PROTO_REGISTERMODULE,0,reinterpret_cast(&pd)); + + InitIcons(); + //InitContactMenus(); + + // Init native User-Agent + { + std::stringstream agent; +// DWORD mir_ver = ( DWORD )CallService( MS_SYSTEM_GETVERSION, NULL, NULL ); + agent << "MirandaNG/"; + agent << (( g_mirandaVersion >> 24) & 0xFF); + agent << "."; + agent << (( g_mirandaVersion >> 16) & 0xFF); + agent << "."; + agent << (( g_mirandaVersion >> 8) & 0xFF); + agent << "."; + agent << (( g_mirandaVersion ) & 0xFF); + #ifdef _WIN64 + agent << " WhatsApp Protocol x64/"; + #else + agent << " WhatsApp Protocol/"; + #endif + agent << __VERSION_STRING; + g_strUserAgent = agent.str( ); + } + + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Unload + +extern "C" int __declspec(dllexport) Unload(void) +{ + //UninitContactMenus(); + for(size_t i=0; iremote_resource.empty(); + + std::string* msg; + switch (paramFMessage->media_wa_type) + { + case FMessage::WA_TYPE_IMAGE: + case FMessage::WA_TYPE_AUDIO: + case FMessage::WA_TYPE_VIDEO: + msg = ¶mFMessage->media_url; + break; + default: + msg = ¶mFMessage->data; + } + + if (isChatRoom) + { + msg->insert(0, std::string("[").append(paramFMessage->notifyname).append("]: ")); + } + + HANDLE hContact = this->AddToContactList(paramFMessage->key->remote_jid, 0, false, + isChatRoom ? NULL : paramFMessage->notifyname.c_str(), isChatRoom); + + PROTORECVEVENT recv = {0}; + recv.flags = PREF_UTF; + recv.szMessage = const_cast(msg->c_str()); + recv.timestamp = paramFMessage->timestamp; //time(NULL); + ProtoChainRecvMsg(hContact, &recv); + + this->connection->sendMessageReceived(paramFMessage); +} + +int WhatsAppProto::SendMsg(HANDLE hContact, int flags, const char *msg) +{ + LOG(""); + int msgId = ++(this->msgId); + + ForkThread( &WhatsAppProto::SendMsgWorker, this, new send_direct(hContact, msg, (HANDLE) msgId, flags & IS_CHAT)); + return this->msgIdHeader + msgId; +} + +void WhatsAppProto::SendMsgWorker(void* p) +{ + LOG(""); + if (p == NULL) + return; + + DBVARIANT dbv; + send_direct *data = static_cast(p); + + if (db_get_b(data->hContact, m_szModuleName, "SimpleChatRoom", 0) > 0 && + db_get_b(data->hContact, m_szModuleName, "IsGroupMember", 0) == 0) + { + LOG("not a group member"); + ProtoBroadcastAck(m_szModuleName,data->hContact, ACKTYPE_MESSAGE, ACKRESULT_FAILED, + (HANDLE) (this->msgIdHeader + this->msgId), (LPARAM) "You cannot send messages to groups if you are not a member."); + } + else if (!db_get_s(data->hContact, m_szModuleName,"ID", &dbv, DBVT_ASCIIZ) && + this->connection != NULL) + { + try + { + db_set_dw(data->hContact, m_szModuleName, WHATSAPP_KEY_LAST_MSG_STATE, 2); + db_set_dw(data->hContact, m_szModuleName, WHATSAPP_KEY_LAST_MSG_ID_HEADER, this->msgIdHeader); + db_set_dw(data->hContact, m_szModuleName, WHATSAPP_KEY_LAST_MSG_ID, this->msgId); + + std::stringstream ss; + ss << this->msgIdHeader << "-" << this->msgId; + Key* key = new Key(Key(dbv.pszVal, true, ss.str())); // deleted by FMessage + FMessage fmsg(key); + fmsg.data = data->msg; + fmsg.timestamp = time(NULL); + + db_free(&dbv); + + this->connection->sendMessage(&fmsg); + } + catch (exception &e) + { + LOG("exception: %s", e.what()); + ProtoBroadcastAck(m_szModuleName,data->hContact,ACKTYPE_MESSAGE,ACKRESULT_FAILED, + (HANDLE) (this->msgIdHeader + this->msgId), (LPARAM) e.what()); + } + catch (...) + { + LOG("unknown exception"); + ProtoBroadcastAck(m_szModuleName,data->hContact,ACKTYPE_MESSAGE,ACKRESULT_FAILED, + (HANDLE) (this->msgIdHeader + this->msgId), (LPARAM) "Failed sending message"); + } + } + else + { + LOG("No connection"); + ProtoBroadcastAck(m_szModuleName,data->hContact,ACKTYPE_MESSAGE,ACKRESULT_FAILED, + (HANDLE) (this->msgIdHeader + this->msgId), (LPARAM) "You cannot send messages when you are offline."); + } + + delete data; +} + +void WhatsAppProto::RecvMsgWorker(void *p) +{ + if (p == NULL) + return; + + //WAConnection.cpp l1225 - message will be deleted. We cannot send the ack inside a thread! + //FMessage *fmsg = static_cast(p); + //this->connection->sendMessageReceived(fmsg); + + //delete fmsg; +} + +void WhatsAppProto::onIsTyping(const std::string& paramString, bool paramBoolean) +{ + HANDLE hContact = this->AddToContactList(paramString, 0, false); + if (hContact != NULL) + { + CallService(MS_PROTO_CONTACTISTYPING, (WPARAM) hContact, (LPARAM) + paramBoolean ? PROTOTYPE_CONTACTTYPING_INFINITE : PROTOTYPE_CONTACTTYPING_OFF); + } +} + + +int WhatsAppProto::UserIsTyping(HANDLE hContact,int type) +{ + if (hContact && isOnline()) + ForkThread(&WhatsAppProto::SendTypingWorker, this, new send_typing(hContact, type)); + + return 0; +} + +void WhatsAppProto::SendTypingWorker(void* p) +{ + if(p == NULL) + return; + + send_typing *typing = static_cast(p); + + // Don't send typing notifications to contacts which are offline + if ( db_get_w(typing->hContact,m_szModuleName,"Status", 0) == ID_STATUS_OFFLINE) + return; + + DBVARIANT dbv; + if ( !db_get_s(typing->hContact,m_szModuleName,WHATSAPP_KEY_ID,&dbv, DBVT_ASCIIZ) && + this->isOnline()) + { + if (typing->status == PROTOTYPE_SELFTYPING_ON) { + this->connection->sendComposing(dbv.pszVal); + } else { + this->connection->sendPaused(dbv.pszVal); + } + } + + delete typing; +} + +void WhatsAppProto::onMessageStatusUpdate(FMessage* fmsg) +{ + LOG(""); + + HANDLE hContact = this->ContactIDToHContact(fmsg->key->remote_jid); + if (hContact == 0) + return; + + int state = 5 - fmsg->status; + if (state != 0 && state != 1) + return; + + int header; + int id; + int delimPos = fmsg->key->id.find("-"); + + std::stringstream ss; + ss << fmsg->key->id.substr(0, delimPos); + ss >> header; + + ss.clear(); + ss << fmsg->key->id.substr(delimPos + 1); + ss >> id; + + if (state == 1) + ProtoBroadcastAck(m_szModuleName, hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE) (header + id),0); + + if (db_get_dw(hContact, m_szModuleName, WHATSAPP_KEY_LAST_MSG_ID_HEADER, 0) == header && + db_get_dw(hContact, m_szModuleName, WHATSAPP_KEY_LAST_MSG_ID, -1) == id) + { + db_set_dw(hContact, m_szModuleName, WHATSAPP_KEY_LAST_MSG_STATE, state); + this->UpdateStatusMsg(hContact); + } +} \ No newline at end of file diff --git a/protocols/WhatsApp/src/proto.cpp b/protocols/WhatsApp/src/proto.cpp new file mode 100644 index 0000000000..99a903226f --- /dev/null +++ b/protocols/WhatsApp/src/proto.cpp @@ -0,0 +1,446 @@ +#include "common.h" + +WhatsAppProto::WhatsAppProto(const char* proto_name, const TCHAR* username) +{ + ProtoConstructor(this, proto_name, username); + + this->challenge = new std::vector; + this->msgId = 0; + this->msgIdHeader = time(NULL); + + update_loop_lock_ = CreateEvent(NULL, false, false, NULL); + FMessage::generating_lock = new Mutex(); + + CreateProtoService(m_szModuleName, PS_CREATEACCMGRUI, &WhatsAppProto::SvcCreateAccMgrUI, this); + CreateProtoService(m_szModuleName, PS_JOINCHAT, &WhatsAppProto::OnJoinChat, this); + CreateProtoService(m_szModuleName, PS_LEAVECHAT, &WhatsAppProto::OnLeaveChat, this); + + HookProtoEvent(ME_CLIST_PREBUILDSTATUSMENU, &WhatsAppProto::OnBuildStatusMenu, this); + HookProtoEvent(ME_GC_EVENT, &WhatsAppProto::OnChatOutgoing, this); + + this->InitContactMenus(); + + // Create standard network connection + TCHAR descr[512]; + NETLIBUSER nlu = {sizeof(nlu)}; + nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_HTTPCONNS; + char module[512]; + mir_snprintf(module, SIZEOF(module), "%s", m_szModuleName); + nlu.szSettingsModule = module; + mir_sntprintf(descr, SIZEOF(descr), TranslateT("%s server connection"), m_tszUserName); + nlu.ptszDescriptiveName = descr; + m_hNetlibUser = (HANDLE) CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM) &nlu); + if (m_hNetlibUser == NULL) + MessageBox(NULL, TranslateT("Unable to get Netlib connection for WhatsApp"), m_tszUserName, MB_OK); + + WASocketConnection::initNetwork(m_hNetlibUser); + + TCHAR *profile = Utils_ReplaceVarsT( _T("%miranda_avatarcache%")); + def_avatar_folder_ = std::tstring(profile) + _T("\\") + m_tszUserName; + mir_free(profile); + hAvatarFolder_ = FoldersRegisterCustomPathT(m_szModuleName, "Avatars", def_avatar_folder_.c_str()); + + // Register group chat + GCREGISTER gcr = {0}; + gcr.cbSize = sizeof(GCREGISTER); + gcr.dwFlags = GC_TYPNOTIF | GC_CHANMGR | GC_TCHAR; + gcr.iMaxText = 0; + gcr.nColors = 0; + gcr.pColors = NULL; //(COLORREF*)crCols; + gcr.ptszModuleDispName = m_tszUserName; + gcr.pszModule = m_szModuleName; + CallServiceSync(MS_GC_REGISTER, 0, (LPARAM)&gcr); + + //HookProtoEvent(ME_GC_EVENT, &CMsnProto::MSN_GCEventHook); + //HookProtoEvent(ME_GC_BUILDMENU, &CMsnProto::MSN_GCMenuHook); + + SetAllContactStatuses(ID_STATUS_OFFLINE, true); +} + +WhatsAppProto::~WhatsAppProto() +{ + CloseHandle(update_loop_lock_); + + if (this->challenge != NULL) + delete this->challenge; + + ProtoDestructor(this); +} + +DWORD_PTR WhatsAppProto::GetCaps( int type, HANDLE hContact ) +{ + switch(type) + { + case PFLAGNUM_1: + { + DWORD_PTR flags = PF1_IM | PF1_CHAT | PF1_BASICSEARCH | PF1_ADDSEARCHRES; + return flags | PF1_MODEMSGRECV; // #TODO + } + case PFLAGNUM_2: + return PF2_ONLINE | PF2_INVISIBLE; + case PFLAGNUM_3: + return 0; + case PFLAGNUM_4: + return PF4_NOCUSTOMAUTH | PF4_IMSENDUTF | PF4_FORCEADDED | PF4_NOAUTHDENYREASON | PF4_IMSENDOFFLINE | PF4_NOAUTHDENYREASON | PF4_SUPPORTTYPING | PF4_AVATARS; + case PFLAGNUM_5: + return 0; + case PFLAG_MAXLENOFMESSAGE: + return 500; // #TODO + case PFLAG_UNIQUEIDTEXT: + return (DWORD_PTR) "WhatsApp ID"; + case PFLAG_UNIQUEIDSETTING: + return (DWORD_PTR) "ID"; + } + return 0; +} + +int WhatsAppProto::SetStatus( int new_status ) +{ + LOG("===== Beginning SetStatus process"); + + // Routing statuses not supported by WhatsApp + switch ( new_status ) + { + case ID_STATUS_INVISIBLE: + case ID_STATUS_OFFLINE: + m_iDesiredStatus = new_status; + break; + + /* + case ID_STATUS_CONNECTING: + m_iDesiredStatus = ID_STATUS_OFFLINE; + break; + */ + + case ID_STATUS_IDLE: + default: + m_iDesiredStatus = ID_STATUS_INVISIBLE; + if (db_get_b(NULL, m_szModuleName, WHATSAPP_KEY_MAP_STATUSES, DEFAULT_MAP_STATUSES)) + break; + case ID_STATUS_ONLINE: + case ID_STATUS_FREECHAT: + m_iDesiredStatus = ID_STATUS_ONLINE; + break; + } + + if (m_iStatus == ID_STATUS_CONNECTING) + { + LOG("===== Status is connecting, no change"); + return 0; + } + + if (m_iStatus == m_iDesiredStatus) + { + LOG("===== Statuses are same, no change"); + return 0; + } + + ForkThread( &WhatsAppProto::ChangeStatus, this ); + + return 0; +} + +HANDLE WhatsAppProto::AddToList( int flags, PROTOSEARCHRESULT* psr ) +{ + return NULL; +} + +int WhatsAppProto::AuthRequest(HANDLE hContact,const PROTOCHAR *message) +{ + return this->RequestFriendship((WPARAM)hContact, NULL); +} + +int WhatsAppProto::Authorize(HANDLE hDbEvent) +{ + return 1; +} + +HANDLE WhatsAppProto::SearchBasic( const PROTOCHAR* id ) +{ + if (isOffline()) + return 0; + + TCHAR* email = mir_tstrdup(id); + ForkThread(&WhatsAppProto::SearchAckThread, this, (void*)email); + + return email; +} + +void WhatsAppProto::RequestCode() +{ + std::string cc, number, idx; + bool error; + DBVARIANT dbv; + + if ( WASocketConnection::hNetlibUser == NULL) + { + NotifyEvent(m_tszUserName,TranslateT("Network-connection error."),NULL,WHATSAPP_EVENT_CLIENT); + return; + } + + if ( !db_get_s(NULL,m_szModuleName,WHATSAPP_KEY_IDX,&dbv,DBVT_ASCIIZ)) + { + idx = dbv.pszVal; + if (idx.empty()) + { + std::stringstream tm; + tm << time(NULL); + unsigned char* idxBuf = new unsigned char[16]; + MD5((unsigned char*) tm.str().c_str(), tm.str().length(), idxBuf); + idx = std::string((const char*) idxBuf, 16); + db_set_s(0, m_szModuleName,WHATSAPP_KEY_IDX, idx.c_str()); + delete idxBuf; + } + } + if ( !db_get_s(NULL,m_szModuleName,WHATSAPP_KEY_CC,&dbv, DBVT_ASCIIZ)) + { + cc = dbv.pszVal; + db_free(&dbv); + if (cc.empty()) + { + NotifyEvent(m_tszUserName,TranslateT("Please enter a country-code."),NULL,WHATSAPP_EVENT_CLIENT); + return; + } + } + if ( !db_get_s(NULL,m_szModuleName,WHATSAPP_KEY_LOGIN,&dbv, DBVT_ASCIIZ)) + { + number = dbv.pszVal; + db_free(&dbv); + if (number.empty()) + { + NotifyEvent(m_tszUserName,TranslateT("Please enter a phone-number without country-code."),NULL,WHATSAPP_EVENT_CLIENT); + return; + } + } + + std::string token(Utilities::md5String(std::string(ACCOUNT_TOKEN_PREFIX1) + ACCOUNT_TOKEN_PREFIX2 + number)); + + NETLIBHTTPHEADER agentHdr; + agentHdr.szName = "User-Agent"; + agentHdr.szValue = ACCOUNT_USER_AGENT_REGISTRATION; + + NETLIBHTTPHEADER acceptHdr; + acceptHdr.szName = "Accept"; + acceptHdr.szValue = "text/json"; + + NETLIBHTTPHEADER ctypeHdr; + ctypeHdr.szName = "Content-Type"; + ctypeHdr.szValue = "application/x-www-form-urlencoded"; + + NETLIBHTTPHEADER headers[] = { agentHdr, acceptHdr, ctypeHdr }; + + NETLIBHTTPREQUEST nlhr = {sizeof(NETLIBHTTPREQUEST)}; + nlhr.requestType = REQUEST_POST; + nlhr.szUrl = (char*) (std::string(ACCOUNT_URL_CODEREQUESTV2) + "?cc="+ cc + "&in="+ number + + "lc=US&lg=en&mcc=000&mnc=000&method=sms&id=" + idx + "&token="+ token).c_str(); + nlhr.headers = &headers[0]; + nlhr.headersCount = 3; + nlhr.flags = NLHRF_HTTP11 | NLHRF_GENERATEHOST | NLHRF_REMOVEHOST; + + return; + + NETLIBHTTPREQUEST* pnlhr = (NETLIBHTTPREQUEST*) CallService(MS_NETLIB_HTTPTRANSACTION, + (WPARAM) WASocketConnection::hNetlibUser, (LPARAM)&nlhr); + + // #TODO +} + +void WhatsAppProto::RegisterCode(const std::string& code) +{ +} + +////////////////////////////////////////////////////////////////////////////// +// EVENTS + +int WhatsAppProto::SvcCreateAccMgrUI(WPARAM wParam,LPARAM lParam) +{ + return (int)CreateDialogParam(g_hInstance, MAKEINTRESOURCE(IDD_WHATSAPPACCOUNT), + (HWND)lParam, WhatsAppAccountProc, (LPARAM)this ); +} + +int WhatsAppProto::RefreshBuddyList(WPARAM, LPARAM ) +{ + LOG(""); + if (!isOffline()) + { + //ForkThread( + } + return 0; +} + +int WhatsAppProto::RequestFriendship(WPARAM wParam, LPARAM lParam) +{ + if (wParam == NULL || isOffline()) + return 0; + + HANDLE hContact = reinterpret_cast(wParam); + + DBVARIANT dbv; + if ( !db_get_s(hContact,m_szModuleName,WHATSAPP_KEY_ID,&dbv, DBVT_ASCIIZ)) + { + std::string id(dbv.pszVal); + this->connection->sendQueryLastOnline(id); + this->connection->sendPresenceSubscriptionRequest(id); + db_free(&dbv); + } + + return 0; +} + +std::tstring WhatsAppProto::GetAvatarFolder() +{ + TCHAR path[MAX_PATH]; + if ( hAvatarFolder_ && FoldersGetCustomPathT(hAvatarFolder_, path, SIZEOF(path), _T("")) == 0 ) + return path; + else + return def_avatar_folder_; +} + +int WhatsAppProto::Log(const char* fn, const char *fmt,...) +{ +#if !defined(_DEBUG) + if (!getByte(WHATSAPP_KEY_LOGGING_ENABLE, 0)) + return EXIT_SUCCESS; +#endif + + va_list va; + char text[65535]; + ScopedLock s(log_lock_); + + va_start(va,fmt); + mir_vsnprintf(text,sizeof(text),fmt,va); + va_end(va); + + // Write into network log + //CallService(MS_NETLIB_LOG, (WPARAM)m_hNetlibUser, (LPARAM)text); + + // Write into log file + return utils::debug::log(m_szModuleName, std::string(fn).append(" - ").append(text)); +} + +LRESULT CALLBACK PopupDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch(message) + { + case WM_COMMAND: + { + // After a click, destroy popup + PUDeletePopup(hwnd); + } break; + + case WM_CONTEXTMENU: + PUDeletePopup(hwnd); + break; + + case UM_FREEPLUGINDATA: + { + // After close, free + TCHAR* url = (TCHAR*)PUGetPluginData(hwnd); + if (url != NULL) + mir_free(url); + } return FALSE; + + default: + break; + } + + return DefWindowProc(hwnd, message, wParam, lParam); +}; + +void WhatsAppProto::NotifyEvent(const string& title, const string& info, HANDLE contact, DWORD flags, TCHAR* url) +{ + TCHAR* rawTitle = mir_a2t_cp(title.c_str(), CP_UTF8); + TCHAR* rawInfo = mir_a2t_cp(info.c_str(), CP_UTF8); + this->NotifyEvent(rawTitle, rawInfo, contact, flags, url); + mir_free(rawTitle); + mir_free(rawInfo); +} + +void WhatsAppProto::NotifyEvent(TCHAR* title, TCHAR* info, HANDLE contact, DWORD flags, TCHAR* szUrl) +{ + int ret; int timeout; COLORREF colorBack = 0; COLORREF colorText = 0; + + switch ( flags ) + { + case WHATSAPP_EVENT_CLIENT: + if ( !getByte( WHATSAPP_KEY_EVENT_CLIENT_ENABLE, DEFAULT_EVENT_CLIENT_ENABLE )) + goto exit; + if ( !getByte( WHATSAPP_KEY_EVENT_CLIENT_DEFAULT, 0 )) + { + colorBack = getDword( WHATSAPP_KEY_EVENT_CLIENT_COLBACK, DEFAULT_EVENT_COLBACK ); + colorText = getDword( WHATSAPP_KEY_EVENT_CLIENT_COLTEXT, DEFAULT_EVENT_COLTEXT ); + } + timeout = getDword( WHATSAPP_KEY_EVENT_CLIENT_TIMEOUT, 0 ); + flags |= NIIF_WARNING; + break; + + case WHATSAPP_EVENT_OTHER: + if ( !getByte( WHATSAPP_KEY_EVENT_OTHER_ENABLE, DEFAULT_EVENT_OTHER_ENABLE )) + goto exit; + if ( !getByte( WHATSAPP_KEY_EVENT_OTHER_DEFAULT, 0 )) + { + colorBack = getDword( WHATSAPP_KEY_EVENT_OTHER_COLBACK, DEFAULT_EVENT_COLBACK ); + colorText = getDword( WHATSAPP_KEY_EVENT_OTHER_COLTEXT, DEFAULT_EVENT_COLTEXT ); + } + timeout = getDword( WHATSAPP_KEY_EVENT_OTHER_TIMEOUT, -1 ); + SkinPlaySound( "OtherEvent" ); + flags |= NIIF_INFO; + break; + } + + if ( !getByte(WHATSAPP_KEY_SYSTRAY_NOTIFY,DEFAULT_SYSTRAY_NOTIFY)) + { + if (ServiceExists(MS_POPUP_ADDPOPUP)) + { + POPUPDATAT pd; + pd.colorBack = colorBack; + pd.colorText = colorText; + pd.iSeconds = timeout; + pd.lchContact = contact; + pd.lchIcon = Skin_GetIconByHandle(m_hProtoIcon); // TODO: Icon test + pd.PluginData = szUrl; + pd.PluginWindowProc = (WNDPROC)PopupDlgProc; + lstrcpy(pd.lptzContactName, title); + lstrcpy(pd.lptzText, info); + ret = PUAddPopupT(&pd); + + if (ret == 0) + return; + } + } else { + if (ServiceExists(MS_CLIST_SYSTRAY_NOTIFY)) + { + MIRANDASYSTRAYNOTIFY err; + int niif_flags = flags; + REMOVE_FLAG( niif_flags, WHATSAPP_EVENT_CLIENT | + WHATSAPP_EVENT_NOTIFICATION | + WHATSAPP_EVENT_OTHER ); + err.szProto = m_szModuleName; + err.cbSize = sizeof(err); + err.dwInfoFlags = NIIF_INTERN_TCHAR | niif_flags; + err.tszInfoTitle = title; + err.tszInfo = info; + err.uTimeout = 1000 * timeout; + ret = CallService(MS_CLIST_SYSTRAY_NOTIFY, 0, (LPARAM) & err); + + if (ret == 0) + goto exit; + } + } + + if (FLAG_CONTAINS(flags, WHATSAPP_EVENT_CLIENT)) + MessageBox(NULL, info, title, MB_OK | MB_ICONINFORMATION); + +exit: + if (szUrl != NULL) + mir_free(szUrl); +} + +string WhatsAppProto::TranslateStr(const char* str, ...) +{ + va_list ap; + va_start(ap, str); + string ret = Utilities::string_format(Translate(str), ap); + va_end(ap); + return ret; +} \ No newline at end of file diff --git a/protocols/WhatsApp/src/proto.h b/protocols/WhatsApp/src/proto.h new file mode 100644 index 0000000000..efb44f1f9d --- /dev/null +++ b/protocols/WhatsApp/src/proto.h @@ -0,0 +1,216 @@ +#if !defined(PROTO_H) +#define PROTO_H + +class WASocketConnection; + +class WhatsAppProto : public PROTO_INTERFACE, public MZeroedObject, public WAListener, public WAGroupListener +{ +public: + WhatsAppProto( const char *proto_name, const TCHAR *username ); + ~WhatsAppProto( ); + + inline const char* ModuleName( ) const + { + return m_szModuleName; + } + + inline bool isOnline( ) + { + return ( m_iStatus != ID_STATUS_OFFLINE && m_iStatus != ID_STATUS_CONNECTING && + connection != NULL ); + } + + inline bool isOffline( ) + { + return ( m_iStatus == ID_STATUS_OFFLINE ); + } + + inline bool isInvisible( ) + { + return ( m_iStatus == ID_STATUS_INVISIBLE ); + } + + //PROTO_INTERFACE + + virtual HANDLE __cdecl AddToList( int flags, PROTOSEARCHRESULT* psr ); + virtual HANDLE __cdecl AddToListByEvent( int flags, int iContact, HANDLE hDbEvent ) { return NULL; } + + virtual int __cdecl Authorize( HANDLE hDbEvent ); + virtual int __cdecl AuthDeny( HANDLE hDbEvent, const PROTOCHAR* szReason ) { return 1; } + virtual int __cdecl AuthRecv( HANDLE hContact, PROTORECVEVENT* ) { return 1; } + virtual int __cdecl AuthRequest( HANDLE hContact, const PROTOCHAR* szMessage ); + + virtual HANDLE __cdecl ChangeInfo( int iInfoType, void* pInfoData ) { return NULL; } + + virtual HANDLE __cdecl FileAllow( HANDLE hContact, HANDLE hTransfer, const PROTOCHAR* szPath ) { return NULL; } + virtual int __cdecl FileCancel( HANDLE hContact, HANDLE hTransfer ) { return 1; } + virtual int __cdecl FileDeny( HANDLE hContact, HANDLE hTransfer, const PROTOCHAR* szReason ) { return 1; } + virtual int __cdecl FileResume( HANDLE hTransfer, int* action, const PROTOCHAR** szFilename ) { return 1; } + + virtual DWORD_PTR __cdecl GetCaps( int type, HANDLE hContact = NULL ); + virtual int __cdecl GetInfo( HANDLE hContact, int infoType ) { return 1; } + + virtual HANDLE __cdecl SearchBasic( const PROTOCHAR* id ); + virtual HANDLE __cdecl SearchByEmail( const PROTOCHAR* email ) { return NULL; } + virtual HANDLE __cdecl SearchByName( const PROTOCHAR* nick, const PROTOCHAR* firstName, const PROTOCHAR* lastName ) { return NULL; } + virtual HWND __cdecl SearchAdvanced( HWND owner ) { return NULL; } + virtual HWND __cdecl CreateExtendedSearchUI( HWND owner ) { return NULL; } + + virtual int __cdecl RecvContacts( HANDLE hContact, PROTORECVEVENT* ) { return 1; } + virtual int __cdecl RecvFile( HANDLE hContact, PROTOFILEEVENT* ) { return 1; } + virtual int __cdecl RecvMsg( HANDLE hContact, PROTORECVEVENT* ); + virtual int __cdecl RecvUrl( HANDLE hContact, PROTORECVEVENT* ) { return 1; } + + virtual int __cdecl SendContacts( HANDLE hContact, int flags, int nContacts, HANDLE* hContactsList ) { return 1; } + virtual HANDLE __cdecl SendFile( HANDLE hContact, const PROTOCHAR* szDescription, PROTOCHAR** ppszFiles ) { return NULL; } + virtual int __cdecl SendMsg( HANDLE hContact, int flags, const char* msg ); + virtual int __cdecl SendUrl( HANDLE hContact, int flags, const char* url ) { return 1; } + + virtual int __cdecl SetApparentMode( HANDLE hContact, int mode ) { return 1; } + virtual int __cdecl SetStatus( int iNewStatus ); + + virtual HANDLE __cdecl GetAwayMsg( HANDLE hContact ) { return NULL; } + virtual int __cdecl RecvAwayMsg( HANDLE hContact, int mode, PROTORECVEVENT* evt ) { return 1; } + virtual int __cdecl SendAwayMsg( HANDLE hContact, HANDLE hProcess, const char* msg ) { return 1; } + virtual int __cdecl SetAwayMsg( int iStatus, const PROTOCHAR* msg ) { return 1; } + + virtual int __cdecl UserIsTyping( HANDLE hContact, int type ); + + virtual int __cdecl OnEvent( PROTOEVENTTYPE iEventType, WPARAM wParam, LPARAM lParam ) { return 1; } + + //////////////////////// + + // Services + int __cdecl SvcCreateAccMgrUI( WPARAM, LPARAM); + int __cdecl RefreshBuddyList(WPARAM, LPARAM); + int __cdecl RequestFriendship(WPARAM, LPARAM); + int __cdecl OnJoinChat(WPARAM, LPARAM); + int __cdecl OnLeaveChat(WPARAM, LPARAM); + + // Events + int __cdecl OnBuildStatusMenu(WPARAM,LPARAM); + int __cdecl OnChatOutgoing(WPARAM,LPARAM); + int __cdecl OnCreateGroup(WPARAM,LPARAM); + int __cdecl OnPrebuildContactMenu(WPARAM,LPARAM); + + INT_PTR __cdecl OnAddContactToGroup(WPARAM, LPARAM, LPARAM); + INT_PTR __cdecl OnRemoveContactFromGroup(WPARAM, LPARAM, LPARAM); + int __cdecl OnChangeGroupSubject(WPARAM, LPARAM); + int __cdecl OnLeaveGroup(WPARAM, LPARAM); + + // Loops + bool NegotiateConnection(); + void __cdecl stayConnectedLoop(void*); + void __cdecl sentinelLoop(void*); + + // Processing Threads + void __cdecl ProcessBuddyList(void*); + void __cdecl SearchAckThread(void*); + + // Worker Threads + void __cdecl ChangeStatus(void*); + void __cdecl SendMsgWorker(void*); + void __cdecl RecvMsgWorker(void*); + void __cdecl SendTypingWorker(void*); + void __cdecl SendGetGroupInfoWorker(void*); + void __cdecl SendSetGroupNameWorker(void*); + void __cdecl SendCreateGroupWorker(void*); + + // Contacts handling + HANDLE AddToContactList(const std::string& jid, BYTE type = 0, bool dont_check = false, + const char *new_name = NULL, bool isChatRoom = false, bool isHidden = false); + bool IsMyContact(HANDLE, bool include_chat = false); + HANDLE ContactIDToHContact(const std::string&); + void SetAllContactStatuses(int status, bool reset_client = false); + void UpdateStatusMsg(HANDLE hContact); + string GetContactDisplayName(HANDLE hContact); + string GetContactDisplayName(const string& jid); + void InitContactMenus(); + void HandleReceiveGroups(const std::vector& groups, bool isOwned); + + bool IsGroupChat(HANDLE hC, bool checkIsAdmin = false) + { + return db_get_b(hC, m_szModuleName, "SimpleChatRoom", 0) > (checkIsAdmin ? 1 : 0); + } + + // Registration + void RequestCode(); + void RegisterCode(const std::string& code); + + // Helpers + std::tstring GetAvatarFolder(); + void ToggleStatusMenuItems( BOOL bEnable ); + string TranslateStr(const char* str, ...); + + // Handles, Locks + HGENMENU m_hMenuRoot; + HANDLE m_hMenuCreateGroup; + + HANDLE signon_lock_; + HANDLE log_lock_; + HANDLE update_loop_lock_; + + std::tstring def_avatar_folder_; + HANDLE hAvatarFolder_; + + HANDLE m_hNetlibUser; + WASocketConnection* conn; + WAConnection* connection; + Mutex connMutex; + int lastPongTime; + + std::vector* challenge; + int msgId; + int msgIdHeader; + string phoneNumber; + string jid; + string nick; + std::map hContactByJid; + //std::map> membersByGroupContact; + map> isMemberByGroupContact; + + // WhatsApp Events + virtual void onMessageForMe(FMessage* paramFMessage, bool paramBoolean); + virtual void onMessageStatusUpdate(FMessage* paramFMessage); + virtual void onMessageError(FMessage* message, int paramInt) { LOG(""); } + virtual void onPing(const std::string& id) throw (WAException); + virtual void onPingResponseReceived() { LOG(""); } + virtual void onAvailable(const std::string& paramString, bool paramBoolean); + virtual void onClientConfigReceived(const std::string& paramString) { LOG(""); } + virtual void onLastSeen(const std::string& paramString1, int paramInt, std::string* paramString2); + virtual void onIsTyping(const std::string& paramString, bool paramBoolean); + virtual void onAccountChange(int paramInt, long paramLong) { LOG(""); } + virtual void onPrivacyBlockListAdd(const std::string& paramString) { LOG(""); } + virtual void onPrivacyBlockListClear() { LOG(""); } + virtual void onDirty(const std::map& paramHashtable) { LOG(""); } + virtual void onDirtyResponse(int paramHashtable) { LOG(""); } + virtual void onRelayRequest(const std::string& paramString1, int paramInt, const std::string& paramString2) { LOG(""); } + virtual void onSendGetPictureIds(std::map* ids); + virtual void onSendGetPicture(const std::string& jid, const std::vector& data, const std::string& oldId, const std::string& newId); + virtual void onPictureChanged(const std::string& from, const std::string& author, bool set); + virtual void onDeleteAccount(bool result) { LOG(""); } + + virtual void onGroupAddUser(const std::string& paramString1, const std::string& paramString2); + virtual void onGroupRemoveUser(const std::string& paramString1, const std::string& paramString2); + virtual void onGroupNewSubject(const std::string& from, const std::string& author, const std::string& newSubject, int paramInt); + virtual void onServerProperties(std::map* nameValueMap) { LOG(""); } + virtual void onGroupCreated(const std::string& paramString1, const std::string& paramString2); + virtual void onGroupInfo(const std::string& paramString1, const std::string& paramString2, const std::string& paramString3, const std::string& paramString4, int paramInt1, int paramInt2); + virtual void onGroupInfoFromList(const std::string& paramString1, const std::string& paramString2, const std::string& paramString3, const std::string& paramString4, int paramInt1, int paramInt2); + virtual void onOwningGroups(const std::vector& paramVector); + virtual void onSetSubject(const std::string& paramString) { LOG(""); } + virtual void onAddGroupParticipants(const std::string& paramString, const std::vector& paramVector, int paramHashtable) { LOG(""); } + virtual void onRemoveGroupParticipants(const std::string& paramString, const std::vector& paramVector, int paramHashtable) { LOG(""); } + virtual void onGetParticipants(const std::string& gjid, const std::vector& participants); + virtual void onParticipatingGroups(const std::vector& paramVector); + virtual void onLeaveGroup(const std::string& paramString); + + // Information providing + int Log(const char* fn, const char *fmt,...); + void NotifyEvent(TCHAR* title, TCHAR* info, HANDLE contact, DWORD flags, TCHAR* url=NULL); + void NotifyEvent(const string& title, const string& info, HANDLE contact, DWORD flags, TCHAR* url=NULL); + + +}; + +#endif \ No newline at end of file diff --git a/protocols/WhatsApp/src/resource.h b/protocols/WhatsApp/src/resource.h new file mode 100644 index 0000000000..a72296428f Binary files /dev/null and b/protocols/WhatsApp/src/resource.h differ diff --git a/protocols/WhatsApp/src/theme.cpp b/protocols/WhatsApp/src/theme.cpp new file mode 100644 index 0000000000..99cfc5c63d --- /dev/null +++ b/protocols/WhatsApp/src/theme.cpp @@ -0,0 +1,297 @@ +#include "common.h" + +extern OBJLIST g_Instances; + +static IconItem icons[] = +{ + { LPGEN("WhatsApp Icon"), "whatsApp", IDI_WHATSAPP }, + { LPGEN("Add To Group"), "addContactToGroup", IDI_ADD_USER_TO_GROUP}, + { LPGEN("Create Chat Group"), "createGroup", IDI_ADD_GROUP }, + { LPGEN("Remove From Chat Group"), "removeContactFromGroup", IDI_REMOVE_USER_FROM_GROUP }, + { LPGEN("Leave And Delete Group"), "leaveAndDeleteGroup", IDI_LEAVE_GROUP }, + { LPGEN("Leave Group"), "leaveGroup", IDI_LEAVE_GROUP }, + { LPGEN("Change Group Subject"), "changeGroupSubject", IDI_CHANGE_GROUP_SUBJECT } +}; + +void InitIcons(void) +{ + Icon_Register(g_hInstance, "Protocols/WhatsApp", icons, SIZEOF(icons), "WhatsApp"); +} + +HANDLE GetIconHandle(const char* name) +{ + for(size_t i=0; i +INT_PTR GlobalService(WPARAM wParam,LPARAM lParam) +{ + WhatsAppProto *proto = GetInstanceByHContact(reinterpret_cast(wParam)); + return proto ? (proto->*Fcn)(wParam,lParam) : 0; +} + +template +INT_PTR GlobalServiceParam(WPARAM wParam,LPARAM lParam, LPARAM lParam2) +{ + WhatsAppProto *proto = GetInstanceByHContact(reinterpret_cast(wParam)); + return proto ? (proto->*Fcn)(wParam,lParam,lParam2) : 0; +} + + +static int PrebuildContactMenu(WPARAM wParam,LPARAM lParam) +{ + for (size_t i=0; i(wParam)); + return proto ? proto->OnPrebuildContactMenu(wParam,lParam) : 0; +} + +void WhatsAppProto::InitContactMenus() +{ + hHookPreBuildMenu = HookEvent(ME_CLIST_PREBUILDCONTACTMENU, PrebuildContactMenu); + + CLISTMENUITEM mi = {sizeof(mi)}; + + mi.position = -2000006100; + mi.icolibItem = GetIconHandle("leaveGroup"); + mi.pszName = GetIconDescription("leaveGroup"); + mi.pszService = "WhatsAppProto/LeaveGroup"; + g_hContactMenuSvc[CMI_LEAVE_GROUP] = + CreateServiceFunction(mi.pszService,GlobalService<&WhatsAppProto::OnLeaveGroup>); + g_hContactMenuItems[CMI_LEAVE_GROUP] = Menu_AddContactMenuItem(&mi); + + mi.position = -2000006100; + mi.icolibItem = GetIconHandle("leaveAndDeleteGroup"); + mi.pszName = GetIconDescription("leaveAndDeleteGroup"); + g_hContactMenuSvc[CMI_REMOVE_GROUP] = g_hContactMenuSvc[CMI_LEAVE_GROUP]; + g_hContactMenuItems[CMI_REMOVE_GROUP] = Menu_AddContactMenuItem(&mi); + + mi.position = -2000006099; + mi.icolibItem = GetIconHandle("changeGroupSubject"); + mi.pszName = GetIconDescription("changeGroupSubject"); + mi.pszService = "WhatsAppProto/ChangeGroupSubject"; + g_hContactMenuSvc[CMI_CHANGE_GROUP_SUBJECT] = + CreateServiceFunction(mi.pszService,GlobalService<&WhatsAppProto::OnChangeGroupSubject>); + g_hContactMenuItems[CMI_CHANGE_GROUP_SUBJECT] = Menu_AddContactMenuItem(&mi); + +} + +void EnableMenuItem(HANDLE hMenuItem, bool enable) +{ + CLISTMENUITEM clmi = { sizeof(clmi) }; + clmi.flags = CMIM_FLAGS; + if (!enable) + clmi.flags |= CMIF_HIDDEN; + + CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)hMenuItem, (LPARAM)&clmi); +} + +int WhatsAppProto::OnPrebuildContactMenu(WPARAM wParam,LPARAM lParam) +{ + HANDLE hContact = reinterpret_cast(wParam); + if (hContact) + LOG(this->GetContactDisplayName(hContact).c_str()); + else + LOG("No contact found"); + + if (g_hContactMenuItems[CMI_ADD_CONTACT_TO_GROUP] != NULL) + { + CallService("CList/RemoveContactMenuItem", (WPARAM) g_hContactMenuItems[CMI_ADD_CONTACT_TO_GROUP], (LPARAM) 0); + } + if (g_hContactMenuItems[CMI_REMOVE_CONTACT_FROM_GROUP] != NULL) + { + CallService("CList/RemoveContactMenuItem", (WPARAM) g_hContactMenuItems[CMI_REMOVE_CONTACT_FROM_GROUP], (LPARAM) 0); + } + + int chatType = db_get_b(hContact, m_szModuleName, "SimpleChatRoom", 0); + + CLISTMENUITEM mi = {sizeof(mi)}; + + if (chatType == 0) + { + mi.flags = CMIF_CHILDPOPUP; + mi.position= -2000006102; + mi.icolibItem = GetIconHandle("addContactToGroup"); + mi.pszName = GetIconDescription("addContactToGroup"); + mi.pszService = NULL; + g_hContactMenuItems[CMI_ADD_CONTACT_TO_GROUP] = Menu_AddContactMenuItem(&mi); + + if (!isOnline()) + { + mi.flags = CMIM_FLAGS | CMIF_GRAYED; + CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM) g_hContactMenuItems[CMI_ADD_CONTACT_TO_GROUP], (LPARAM) &mi); + return 0; + } + + mi.hParentMenu = (HGENMENU) g_hContactMenuItems[CMI_ADD_CONTACT_TO_GROUP]; + mi.flags = CMIF_ROOTHANDLE | CMIF_TCHAR; + + string fullSvcName; + string svcName = m_szModuleName; + svcName += "/AddContactToGroup_"; + DBVARIANT dbv; + + for (map>::iterator it = this->isMemberByGroupContact.begin(); + it != this->isMemberByGroupContact.end(); ++it) + { + map::iterator memberIt = it->second.find(hContact); + // Only, if current contact is not already member of this group + if ((memberIt == it->second.end() || memberIt->second == false) && + !db_get_s(it->first, m_szModuleName, "ID", &dbv, DBVT_ASCIIZ)) + { + fullSvcName = svcName + dbv.pszVal; + mi.pszService = (char*) fullSvcName.c_str(); + mi.ptszName = mir_a2t_cp(this->GetContactDisplayName(it->first).c_str(), CP_UTF8); + CreateServiceFunctionParam(mi.pszService, GlobalServiceParam<&WhatsAppProto::OnAddContactToGroup>, (LPARAM) it->first); + Menu_AddContactMenuItem(&mi); + db_free(&dbv); + mir_free(mi.ptszName); + } + } + } + else if (chatType == 1) + { + mi.flags = CMIM_FLAGS; + if (!isOnline() || db_get_b(hContact, m_szModuleName, "IsGroupMember", 0) == 0) + { + mi.flags |= CMIF_GRAYED; + } + CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM) g_hContactMenuItems[CMI_LEAVE_GROUP], (LPARAM) &mi); + } + else if (chatType == 2) + { + // owning chat/group + mi.flags = CMIF_CHILDPOPUP; + mi.position= -2000006102; + mi.icolibItem = GetIconHandle("removeContactFromGroup"); + mi.pszName = GetIconDescription("removeContactFromGroup"); + mi.pszService = NULL; + g_hContactMenuItems[CMI_REMOVE_CONTACT_FROM_GROUP] = Menu_AddContactMenuItem(&mi); + + if (isOnline() && db_get_b(hContact, m_szModuleName, "IsGroupMember", 0) == 1) + { + map>::iterator groupsIt = this->isMemberByGroupContact.find(hContact); + if (groupsIt == this->isMemberByGroupContact.end()) + { + LOG("Group exists only on contact list"); + } + else + { + mi.hParentMenu = (HGENMENU) g_hContactMenuItems[CMI_REMOVE_CONTACT_FROM_GROUP]; + mi.flags = CMIF_ROOTHANDLE | CMIF_TCHAR; + + string fullSvcName; + string svcName = m_szModuleName; + svcName += "/RemoveContactFromGroup_"; + DBVARIANT dbv; + + for (map::iterator it = groupsIt->second.begin(); it != groupsIt->second.end(); ++it) + { + if (!db_get_s(it->first, m_szModuleName, "ID", &dbv, DBVT_ASCIIZ)) + { + fullSvcName = svcName + dbv.pszVal; + mi.pszService = (char*) fullSvcName.c_str(); + mi.ptszName = mir_a2t_cp(this->GetContactDisplayName(it->first).c_str(), CP_UTF8); + CreateServiceFunctionParam(mi.pszService, + GlobalServiceParam<&WhatsAppProto::OnRemoveContactFromGroup>, (LPARAM) it->first); + Menu_AddContactMenuItem(&mi); + db_free(&dbv); + mir_free(mi.ptszName); + } + } + } + mi.flags = CMIM_FLAGS; + } + else + { + mi.flags = CMIM_FLAGS | CMIF_GRAYED; + CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM) g_hContactMenuItems[CMI_REMOVE_CONTACT_FROM_GROUP], (LPARAM) &mi); + } + CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM) g_hContactMenuItems[CMI_REMOVE_GROUP], (LPARAM) &mi); + CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM) g_hContactMenuItems[CMI_CHANGE_GROUP_SUBJECT], (LPARAM) &mi); + } + + return 0; +} + +int WhatsAppProto::OnBuildStatusMenu(WPARAM wParam,LPARAM lParam) +{ + LOG(""); + char text[200]; + strcpy(text,m_szModuleName); + char *tDest = text+strlen(text); + + CLISTMENUITEM mi = {sizeof(mi)}; + mi.pszService = text; + + HGENMENU hRoot = MO_GetProtoRootMenu(m_szModuleName); + if (hRoot == NULL) { + mi.popupPosition = 500085000; + mi.hParentMenu = HGENMENU_ROOT; + mi.flags = CMIF_ROOTPOPUP | CMIF_TCHAR | CMIF_KEEPUNTRANSLATED | ( this->isOnline() ? 0 : CMIF_GRAYED ); + mi.icolibItem = GetIconHandle( "whatsApp" ); + mi.ptszName = m_tszUserName; + hRoot = m_hMenuRoot = Menu_AddProtoMenuItem(&mi); + } else { + if ( m_hMenuRoot ) + CallService( MS_CLIST_REMOVEMAINMENUITEM, ( WPARAM )m_hMenuRoot, 0 ); + m_hMenuRoot = NULL; + } + + mi.flags = CMIF_CHILDPOPUP | ( this->isOnline() ? 0 : CMIF_GRAYED ); + mi.position = 201001; + + CreateProtoService(m_szModuleName, "/CreateGroup", &WhatsAppProto::OnCreateGroup, this); + strcpy(tDest, "/CreateGroup"); + mi.hParentMenu = hRoot; + mi.pszName = LPGEN("Create Group"); + mi.icolibItem = GetIconHandle("createGroup"); + m_hMenuCreateGroup = Menu_AddProtoMenuItem(&mi); + + return 0; +} + +void WhatsAppProto::ToggleStatusMenuItems( BOOL bEnable ) +{ + CLISTMENUITEM clmi = {sizeof(clmi)}; + clmi.flags = CMIM_FLAGS | (( bEnable ) ? 0 : CMIF_GRAYED); + + CallService( MS_CLIST_MODIFYMENUITEM, ( WPARAM ) m_hMenuRoot, ( LPARAM )&clmi ); + CallService( MS_CLIST_MODIFYMENUITEM, ( WPARAM ) m_hMenuCreateGroup, ( LPARAM )&clmi ); +} diff --git a/protocols/WhatsApp/src/theme.h b/protocols/WhatsApp/src/theme.h new file mode 100644 index 0000000000..c5881ca03a --- /dev/null +++ b/protocols/WhatsApp/src/theme.h @@ -0,0 +1,19 @@ +#if !defined(THEME_H) +#define THEME_H + +void InitIcons(void); +HANDLE GetIconHandle(const char *name); + +//void InitContactMenus(void); +void EnableMenuItem(HANDLE hMenuItem, bool enable); + +/* Contact menu item indexes */ +#define CMI_ADD_CONTACT_TO_GROUP 0 +#define CMI_REMOVE_CONTACT_FROM_GROUP 1 +#define CMI_LEAVE_GROUP 2 +#define CMI_REMOVE_GROUP 3 +#define CMI_CHANGE_GROUP_SUBJECT 4 + +#define CMITEMS_COUNT 5 + +#endif \ No newline at end of file diff --git a/protocols/WhatsApp/src/utils.cpp b/protocols/WhatsApp/src/utils.cpp new file mode 100644 index 0000000000..c635061994 --- /dev/null +++ b/protocols/WhatsApp/src/utils.cpp @@ -0,0 +1,106 @@ +#include "common.h" + +void UnixTimeToFileTime(time_t t, LPFILETIME pft) +{ + // Note that LONGLONG is a 64-bit value + LONGLONG ll; + + ll = Int32x32To64(t, 10000000) + 116444736000000000; + pft->dwLowDateTime = (DWORD)ll; + pft->dwHighDateTime = ll >> 32; +} + +DWORD utils::conversion::to_timestamp(std::string data) +{ + DWORD timestamp = NULL; + /* + if (!utils::conversion::from_string(timestamp, data, std::dec)) { + timestamp = static_cast(::time(NULL)); + } + */ + return timestamp; +} + +std::string utils::text::source_get_value(std::string* data, unsigned int argument_count, ...) +{ + va_list arg; + std::string ret; + std::string::size_type start = 0, end = 0; + + va_start(arg, argument_count); + + for (unsigned int i = argument_count; i > 0; i--) + { + if (i == 1) + { + end = data->find(va_arg(arg, char*), start); + if (start == std::string::npos || end == std::string::npos) + break; + ret = data->substr(start, end - start); + } else { + std::string term = va_arg(arg, char*); + start = data->find(term, start); + if (start == std::string::npos) + break; + start += term.length(); + } + } + + va_end(arg); + return ret; +} + +std::string getLastErrorMsg() +{ + // Retrieve the system error message for the last-error code + + LPVOID lpMsgBuf; + LPVOID lpDisplayBuf; + DWORD dw = WSAGetLastError(); + + FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + dw, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR) &lpMsgBuf, + 0, NULL ); + + // Display the error message and exit the process + /* + lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, + (lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR)); + StringCchPrintf((LPTSTR)lpDisplayBuf, + LocalSize(lpDisplayBuf) / sizeof(TCHAR), + TEXT("%s"), + lpMsgBuf); + */ + + std::string ret((LPSTR) lpMsgBuf); + LocalFree(lpMsgBuf); + //LocalFree(lpDisplayBuf); + + //return std::string((LPCTSTR)lpDisplayBuf); + return ret; +} + +int utils::debug::log(std::string file_name, std::string text) +{ + char szFile[MAX_PATH]; + GetModuleFileNameA(g_hInstance, szFile, SIZEOF(szFile)); + std::string path = szFile; + path = path.substr(0, path.rfind("\\")); + path = path.substr(0, path.rfind("\\") + 1); + path = path + file_name.c_str() + ".txt"; + + SYSTEMTIME time; + GetLocalTime(&time); + + std::ofstream out(path.c_str(), std::ios_base::out | std::ios_base::app | std::ios_base::ate); + out << "[" << (time.wHour < 10 ? "0" : "") << time.wHour << ":" << (time.wMinute < 10 ? "0" : "") << time.wMinute << ":" << (time.wSecond < 10 ? "0" : "") << time.wSecond << "] " << text << std::endl; + out.close(); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/protocols/WhatsApp/src/utils.h b/protocols/WhatsApp/src/utils.h new file mode 100644 index 0000000000..e6686d1764 --- /dev/null +++ b/protocols/WhatsApp/src/utils.h @@ -0,0 +1,111 @@ +#if !defined(WHATS_NG_UTILS_H) +#define WHATS_NG_UTILS_H + +#include "WhatsAPI++/IMutex.h" + +template +void CreateProtoService(const char *module,const char *service, + int (__cdecl T::*serviceProc)(WPARAM,LPARAM),T *self) +{ + char temp[MAX_PATH*2]; + + mir_snprintf(temp,sizeof(temp),"%s%s",module,service); + CreateServiceFunctionObj(temp,( MIRANDASERVICEOBJ )*(void**)&serviceProc, self ); +} + +template +void HookProtoEvent(const char* evt, int (__cdecl T::*eventProc)(WPARAM,LPARAM), T *self) +{ + ::HookEventObj(evt,(MIRANDAHOOKOBJ)*(void**)&eventProc,self); +} + +template +HANDLE ForkThreadEx(void (__cdecl T::*thread)(void*),T *self,void *data = 0) +{ + return reinterpret_cast( mir_forkthreadowner( + (pThreadFuncOwner)*(void**)&thread,self,data,0)); +} + +template +void ForkThread(void (__cdecl T::*thread)(void*),T *self,void *data = 0) +{ + CloseHandle(ForkThreadEx(thread,self,data)); +} + +class ScopedLock +{ +public: + ScopedLock(HANDLE h, int t = INFINITE) : handle_(h), timeout_(t) + { + WaitForSingleObject(handle_,timeout_); + } + ~ScopedLock() + { + if(handle_) + ReleaseMutex(handle_); + } + void Unlock() + { + ReleaseMutex(handle_); + handle_ = 0; + } +private: + HANDLE handle_; + int timeout_; +}; + +class Mutex : public IMutex +{ +private: + HANDLE handle; +public: + Mutex() : handle(NULL) {} + + virtual ~Mutex() + { + if (this->handle != NULL) + { + ReleaseMutex(this->handle); + } + } + + virtual void lock() + { + if (this->handle == NULL) + { + this->handle = CreateMutex(NULL, FALSE, NULL); + } + } + + virtual void unlock() + { + ReleaseMutex(this->handle); + this->handle = NULL; + } +}; + + +std::string getLastErrorMsg(); + +void UnixTimeToFileTime(time_t t, LPFILETIME pft); + +namespace utils +{ + namespace debug + { + int log(std::string file_name, std::string text); + }; + + namespace conversion + { + DWORD to_timestamp( std::string data ); + }; + + namespace text + { + std::string source_get_value(std::string* data, unsigned int argument_count, ...); + }; +}; + + +#endif \ No newline at end of file diff --git a/protocols/WhatsApp/src/version.h b/protocols/WhatsApp/src/version.h new file mode 100644 index 0000000000..21c99b92a6 --- /dev/null +++ b/protocols/WhatsApp/src/version.h @@ -0,0 +1,16 @@ +#define __MAJOR_VERSION 0 +#define __MINOR_VERSION 0 +#define __RELEASE_NUM 2 +#define __BUILD_NUM 0 + +#define __FILEVERSION_STRING __MAJOR_VERSION,__MINOR_VERSION,__RELEASE_NUM,__BUILD_NUM +#define __TOSTRING(x) #x +#define __VERSION_STRING __TOSTRING(__FILEVERSION_STRING) + +#define __PLUGIN_NAME "WhatsApp" +#define __FILENAME "WhatsApp.dll" +#define __DESCRIPTION "WhatsApp protocol support for Miranda NG." +#define __AUTHOR "Uli Hecht" +#define __AUTHOREMAIL "" +#define __AUTHORWEB "http://miranda-ng.org/" +#define __COPYRIGHT "© 2013 Uli Hecht" -- cgit v1.2.3