diff options
author | George Hazan <ghazan@miranda.im> | 2019-10-08 20:37:59 +0300 |
---|---|---|
committer | George Hazan <ghazan@miranda.im> | 2019-10-08 20:37:59 +0300 |
commit | 6f8955cdc2e8bcdeaa15a06d0f1badf13d14cb5c (patch) | |
tree | 40bf8fd1196e87d66b412d07adfbca24195ba1f6 /protocols/WhatsAppWeb/src | |
parent | bb3b89fd5d857d396050d02bc185d765760de6ff (diff) |
first version of WA that connects
Diffstat (limited to 'protocols/WhatsAppWeb/src')
-rw-r--r-- | protocols/WhatsAppWeb/src/proto.cpp | 7 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/proto.h | 61 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/server.cpp | 254 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/stdafx.h | 1 |
4 files changed, 291 insertions, 32 deletions
diff --git a/protocols/WhatsAppWeb/src/proto.cpp b/protocols/WhatsAppWeb/src/proto.cpp index a29e50d06f..3f2f903f0a 100644 --- a/protocols/WhatsAppWeb/src/proto.cpp +++ b/protocols/WhatsAppWeb/src/proto.cpp @@ -19,7 +19,8 @@ struct SearchParam WhatsAppProto::WhatsAppProto(const char *proto_name, const wchar_t *username) : PROTO<WhatsAppProto>(proto_name, username), - m_tszDefaultGroup(getWStringA(DBKEY_DEF_GROUP)) + m_tszDefaultGroup(getWStringA(DBKEY_DEF_GROUP)), + m_arPacketQueue(10, NumericKeySortT) { db_set_resident(m_szModuleName, "StatusMsg"); @@ -135,13 +136,13 @@ int WhatsAppProto::SetStatus(int new_status) m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); } - else if (m_pConn == nullptr && !IsStatusConnecting(m_iStatus)) { + else if (m_hServerConn == nullptr && !IsStatusConnecting(m_iStatus)) { m_iStatus = ID_STATUS_CONNECTING; ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); ForkThread(&WhatsAppProto::ServerThread); } - else if (m_pConn != nullptr) { + else if (m_hServerConn != nullptr) { if (m_iDesiredStatus == ID_STATUS_ONLINE) { // m_pConn->sendAvailableForChat(); m_iStatus = ID_STATUS_ONLINE; diff --git a/protocols/WhatsAppWeb/src/proto.h b/protocols/WhatsAppWeb/src/proto.h index 610313ab0e..ce09908fcd 100644 --- a/protocols/WhatsAppWeb/src/proto.h +++ b/protocols/WhatsAppWeb/src/proto.h @@ -22,23 +22,52 @@ struct WAChatInfo MCONTACT hContact; }; -struct WAConnection : public MZeroedObject +class WhatsAppProto; +typedef void (WhatsAppProto:: *WA_PKT_HANDLER)(const JSONNode &node); + +struct WARequest { - EVP_PKEY *m_pKeys; // private & public keys + int pktId; + time_t issued; + WA_PKT_HANDLER pHandler; }; class WhatsAppProto : public PROTO<WhatsAppProto> { + bool m_bTerminated, m_bOnline; ptrW m_tszDefaultGroup; - CMStringA m_szJid, m_szClientId; + CMStringA m_szJid, m_szClientId, m_szClientToken; CMStringW m_tszAvatarFolder; - WAConnection *m_pConn; + EVP_PKEY *m_pKeys; // private & public keys + + bool ShowQrCode(const CMStringA &ref); + + /// Network //////////////////////////////////////////////////////////////////////////// + + int m_iPktNumber; + time_t m_iLoginTime; + HNETLIBCONN m_hServerConn; - bool ShowQrCode(void); + mir_cs m_csPacketQueue; + OBJLIST<WARequest> m_arPacketQueue; - /// Avatars ////////////////////////////////////////////////////////////////////////// + int WSSend(const CMStringA &str, WA_PKT_HANDLER = nullptr); + + void OnLoggedIn(void); + void OnLoggedOut(void); + void ProcessPacket(const JSONNode &node); + void RestoreSession(void); + bool ServerThreadWorker(void); + void StartSession(void); + void ShutdownSession(void); + + /// Request handlers /////////////////////////////////////////////////////////////////// + + void OnStartSession(const JSONNode &node); + + /// Avatars //////////////////////////////////////////////////////////////////////////// CMStringW GetAvatarFileName(MCONTACT hContact); INT_PTR __cdecl GetAvatarInfo(WPARAM, LPARAM); @@ -51,7 +80,7 @@ public: ~WhatsAppProto(); inline bool isOnline() const - { return false; + { return m_bOnline; } inline bool isOffline() const @@ -62,7 +91,7 @@ public: { return (m_iStatus == ID_STATUS_INVISIBLE); } - // PROTO_INTERFACE /////////////////////////////////////////////////////////////////// + // PROTO_INTERFACE ///////////////////////////////////////////////////////////////////// MCONTACT AddToList(int flags, PROTOSEARCHRESULT *psr) override; INT_PTR GetCaps(int type, MCONTACT hContact = NULL) override; @@ -71,31 +100,23 @@ public: int SetStatus(int iNewStatus) override; int UserIsTyping(MCONTACT hContact, int type) override; - // Services ////////////////////////////////////////////////////////////////////////// + // Services //////////////////////////////////////////////////////////////////////////// INT_PTR __cdecl SvcCreateAccMgrUI(WPARAM, LPARAM); - // Events //////////////////////////////////////////////////////////////////////////// + // Events ////////////////////////////////////////////////////////////////////////////// int __cdecl OnOptionsInit(WPARAM, LPARAM); int __cdecl OnUserInfo(WPARAM, LPARAM); int __cdecl OnBuildStatusMenu(WPARAM, LPARAM); - // Worker Threads //////////////////////////////////////////////////////////////////// - - void __cdecl stayConnectedLoop(void*); - void __cdecl sentinelLoop(void*); - - // Processing Threads //////////////////////////////////////////////////////////////// + // Processing Threads ////////////////////////////////////////////////////////////////// - void __cdecl ProcessBuddyList(void*); void __cdecl SearchAckThread(void*); void __cdecl ServerThread(void*); - // Contacts handling ///////////////////////////////////////////////////////////////// + // Contacts handling /////////////////////////////////////////////////////////////////// - void SetAllContactStatuses(int status, bool reset_client = false); - void UpdateStatusMsg(MCONTACT hContact); void RequestFriendship(MCONTACT hContact); }; diff --git a/protocols/WhatsAppWeb/src/server.cpp b/protocols/WhatsAppWeb/src/server.cpp index 8b94cb675c..8e9419ad47 100644 --- a/protocols/WhatsAppWeb/src/server.cpp +++ b/protocols/WhatsAppWeb/src/server.cpp @@ -97,7 +97,7 @@ static int random_func(uint8_t *pData, size_t size, void *) return 0; } -bool WhatsAppProto::ShowQrCode() +bool WhatsAppProto::ShowQrCode(const CMStringA &ref) { CMStringA szPubKey(getMStringA(DBKEY_PUBKEY)); if (szPubKey.IsEmpty()) { @@ -135,17 +135,253 @@ bool WhatsAppProto::ShowQrCode() return true; } -///////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// +// sends a piece of JSON to a server via a websocket, masked + +int WhatsAppProto::WSSend(const CMStringA &str, WA_PKT_HANDLER pHandler) +{ + if (m_hServerConn == nullptr) + return -1; + + int pktId = ++m_iPktNumber; + + CMStringA buf; + buf.Format("%d.--%d,", (int)m_iLoginTime, pktId); + if (!str.IsEmpty()) { + buf.AppendChar(','); + buf += str; + } + + if (pHandler != nullptr) { + auto *pReq = new WARequest; + pReq->issued = time(0); + pReq->pHandler = pHandler; + pReq->pktId = pktId; + + mir_cslock lck(m_csPacketQueue); + m_arPacketQueue.insert(pReq); + } + + WebSocket_Send(m_hServerConn, buf.c_str(), buf.GetLength()); + return pktId; +} + +void WhatsAppProto::OnLoggedIn() +{ + debugLogA("CDiscordProto::OnLoggedIn"); + m_bOnline = true; + // SetServerStatus(m_iDesiredStatus); +} + +void WhatsAppProto::OnLoggedOut(void) +{ + debugLogA("CDiscordProto::OnLoggedOut"); + m_bOnline = false; + m_bTerminated = true; + m_iPktNumber = 0; + + ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, ID_STATUS_OFFLINE); + m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; + + setAllContactStatuses(ID_STATUS_OFFLINE, false); +} + +void WhatsAppProto::RestoreSession() +{ +} + +void WhatsAppProto::ShutdownSession() +{ + if (m_bTerminated) + return; + + debugLogA("WhatsAppProto::ShutdownSession"); + + // shutdown all resources + if (m_hServerConn) + Netlib_Shutdown(m_hServerConn); + + OnLoggedOut(); +} + +void WhatsAppProto::StartSession() +{ + WORD v[4]; + Miranda_GetFileVersion(&v); + + CMStringA payload(FORMAT, "[\"admin\",\"init\",[%d,%d,%d],[\"Windows\",\"Miranda\",\"10\"],%s,true]", + v[1], v[2], v[3], m_szClientId.c_str()); + WSSend(payload, &WhatsAppProto::OnStartSession); +} + +void WhatsAppProto::OnStartSession(const JSONNode &root) +{ + int status = root["status"].as_int(); + if (status != 200) { + debugLogA("Session start failed with error %d", status); + ShutdownSession(); + return; + } + + CMStringA ref = root["ref"].as_mstring(); + ShowQrCode(ref); +} + +////////////////////////////////////////////////////////////////////////////////////// +// gateway worker thread void WhatsAppProto::ServerThread(void *) { - m_pConn = new WAConnection(); + while (ServerThreadWorker()) + ; + ShutdownSession(); +} + +bool WhatsAppProto::ServerThreadWorker() +{ + NETLIBHTTPHEADER hdrs[] = + { + { "Origin", "https://web.whatsapp.com" }, + { "Sec-WebSocket-Key", "k/hwJLznKpk3p2hxyYGzWA==" }, + { 0, 0 } + }; + + m_hServerConn = WebSocket_Connect(m_hNetlibUser, "web.whatsapp.com/ws", hdrs); + if (m_hServerConn == nullptr) { + debugLogA("Server connection failed, exiting"); + return false; + } + + debugLogA("Server connection succeeded"); + + m_szClientToken = getMStringA(DBKEY_CLIENT_SECRET); + if (m_szClientToken.IsEmpty()) + StartSession(); + else + RestoreSession(); + + bool bExit = false; + int offset = 0; + MBinBuffer netbuf; + + while (!bExit) { + if (m_bTerminated) + break; - ptrA szClientToken(getStringA(DBKEY_CLIENT_SECRET)); - if (szClientToken == nullptr) { - if (!ShowQrCode()) { - delete m_pConn; - return; + unsigned char buf[2048]; + int bufSize = Netlib_Recv(m_hServerConn, (char *)buf + offset, _countof(buf) - offset, MSG_NODUMP); + if (bufSize == 0) { + debugLogA("Gateway connection gracefully closed"); + bExit = !m_bTerminated; + break; + } + if (bufSize < 0) { + debugLogA("Gateway connection error, exiting"); + break; + } + + WSHeader hdr; + if (!WebSocket_InitHeader(hdr, buf, bufSize)) { + offset += bufSize; + continue; + } + offset = 0; + + debugLogA("Got packet: buffer = %d, opcode = %d, headerSize = %d, final = %d, masked = %d", bufSize, hdr.opCode, hdr.headerSize, hdr.bIsFinal, hdr.bIsMasked); + + // we have some additional data, not only opcode + if ((size_t)bufSize > hdr.headerSize) { + size_t currPacketSize = bufSize - hdr.headerSize; + netbuf.append(buf, bufSize); + while (currPacketSize < hdr.payloadSize) { + int result = Netlib_Recv(m_hServerConn, (char *)buf, _countof(buf), MSG_NODUMP); + if (result == 0) { + debugLogA("Gateway connection gracefully closed"); + bExit = !m_bTerminated; + break; + } + if (result < 0) { + debugLogA("Gateway connection error, exiting"); + break; + } + currPacketSize += result; + netbuf.append(buf, result); + } + } + + // read all payloads from the current buffer, one by one + size_t prevSize = 0; + while (true) { + switch (hdr.opCode) { + case 0: // text packet + case 1: // binary packet + case 2: // continuation + if (hdr.bIsFinal) { + // process a packet here + CMStringA szJson(netbuf.data() + hdr.headerSize, (int)hdr.payloadSize); + debugLogA("JSON received:\n%s", szJson.c_str()); + + int pos = szJson.Find(','); + if (pos != -1) { + CMStringA prefix = szJson.Left(pos); + szJson.Delete(0, pos); + + JSONNode root = JSONNode::parse(szJson); + if (root) { + int sessId, pktId; + if (sscanf(szJson, "%d--.%d,", &sessId, &pktId) == 2) { + auto *pReq = m_arPacketQueue.find((WARequest *)&pktId); + if (pReq != nullptr) { + (this->*pReq->pHandler)(root); + } + } + else ProcessPacket(root); + } + } + } + break; + + case 8: // close + debugLogA("server required to exit"); + bExit = true; // simply reconnect, don't exit + break; + + case 9: // ping + debugLogA("ping received"); + Netlib_Send(m_hServerConn, (char *)buf + hdr.headerSize, bufSize - int(hdr.headerSize), 0); + break; + } + + if (hdr.bIsFinal) + netbuf.remove(hdr.headerSize + hdr.payloadSize); + + if (netbuf.length() == 0) + break; + + // if we have not enough data for header, continue reading + if (!WebSocket_InitHeader(hdr, netbuf.data(), netbuf.length())) + break; + + // if we have not enough data for data, continue reading + if (hdr.headerSize + hdr.payloadSize > netbuf.length()) + break; + + debugLogA("Got inner packet: buffer = %d, opcode = %d, headerSize = %d, payloadSize = %d, final = %d, masked = %d", netbuf.length(), hdr.opCode, hdr.headerSize, hdr.payloadSize, hdr.bIsFinal, hdr.bIsMasked); + if (prevSize == netbuf.length()) { + netbuf.remove(prevSize); + debugLogA("dropping current packet, exiting"); + break; + } + + prevSize = netbuf.length(); } } -}
\ No newline at end of file + + Netlib_CloseHandle(m_hServerConn); + m_hServerConn = nullptr; + return bExit; +} + +void WhatsAppProto::ProcessPacket(const JSONNode &root) +{ +} diff --git a/protocols/WhatsAppWeb/src/stdafx.h b/protocols/WhatsAppWeb/src/stdafx.h index ee6c6a1156..171e384674 100644 --- a/protocols/WhatsAppWeb/src/stdafx.h +++ b/protocols/WhatsAppWeb/src/stdafx.h @@ -21,6 +21,7 @@ Copyright © 2019 George Hazan #include <m_history.h> #include <m_imgsrvc.h> #include <m_ignore.h> +#include <m_json.h> #include <m_langpack.h> #include <m_message.h> #include <m_netlib.h> |