summaryrefslogtreecommitdiff
path: root/protocols/WhatsApp/src
diff options
context:
space:
mode:
authordartraiden <wowemuh@gmail.com>2023-01-14 01:30:59 +0300
committerdartraiden <wowemuh@gmail.com>2023-01-14 01:30:59 +0300
commitde40f3be3f08487937525c2ef096dad665dda61d (patch)
treeeb1205f8dca7c30b561a2776f9527072bd92eaf1 /protocols/WhatsApp/src
parentdd743899a769120ba2321230afddd6e4f1271872 (diff)
Convert sources to CR+LF
Diffstat (limited to 'protocols/WhatsApp/src')
-rw-r--r--protocols/WhatsApp/src/appsync.cpp644
-rw-r--r--protocols/WhatsApp/src/chats.cpp376
-rw-r--r--protocols/WhatsApp/src/proto.cpp616
-rw-r--r--protocols/WhatsApp/src/proto.h1016
-rw-r--r--protocols/WhatsApp/src/server.cpp836
5 files changed, 1744 insertions, 1744 deletions
diff --git a/protocols/WhatsApp/src/appsync.cpp b/protocols/WhatsApp/src/appsync.cpp
index d298f880d2..9607045131 100644
--- a/protocols/WhatsApp/src/appsync.cpp
+++ b/protocols/WhatsApp/src/appsync.cpp
@@ -1,322 +1,322 @@
-/*
-
-WhatsApp plugin for Miranda NG
-Copyright © 2019-23 George Hazan
-
-*/
-
-#include "stdafx.h"
-
-//////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::InitSync()
-{
- m_arCollections.insert(new WACollection("regular"));
- m_arCollections.insert(new WACollection("regular_high"));
- m_arCollections.insert(new WACollection("regular_low"));
- m_arCollections.insert(new WACollection("critical_block"));
- m_arCollections.insert(new WACollection("critical_unblock_low"));
-
- for (auto &it : m_arCollections) {
- CMStringW wszPath(GetTmpFileName("collection", it->szName));
- wszPath.Append(L".json");
- if (_waccess(wszPath, 0))
- continue;
-
- JSONNode root = JSONNode::parse(file2string(wszPath));
- it->version = root["version"].as_int();
-
- auto szHash = decodeBinStr(root["hash"].as_string());
- if (szHash.size() == sizeof(it->hash.hash))
- memcpy(it->hash.hash, szHash.c_str(), sizeof(it->hash.hash));
-
- for (auto &val : root["indexValueMap"])
- it->indexValueMap[decodeBinStr(val.name())] = decodeBinStr(val.as_string());
- }
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::OnServerSync(const WANode &node)
-{
- OBJLIST<WACollection> task(1);
-
- for (auto &it : node.getChildren())
- if (it->title == "collection")
- task.insert(new WACollection(it->getAttr("name"), it->getAttrInt("version")));
-
- ResyncServer(task);
- SendAck(node);
-}
-
-void WhatsAppProto::ResyncAll()
-{
- ResyncServer(m_arCollections);
-}
-
-void WhatsAppProto::ResyncServer(const OBJLIST<WACollection> &task)
-{
- WANodeIq iq(IQ::SET, "w:sync:app:state");
-
- auto *pList = iq.addChild("sync");
- for (auto &it : task) {
- auto *pCollection = m_arCollections.find(it);
- if (pCollection == nullptr)
- m_arCollections.insert(pCollection = new WACollection(it->szName, 0));
-
- if (!pCollection->version || pCollection->version < it->version) {
- auto *pNode = pList->addChild("collection");
- *pNode << CHAR_PARAM("name", it->szName) << INT_PARAM("version", pCollection->version)
- << CHAR_PARAM("return_snapshot", (!pCollection->version) ? "true" : "false");
- }
- }
-
- if (pList->getFirstChild() != nullptr)
- WSSendNode(iq, &WhatsAppProto::OnIqServerSync);
-}
-
-void WhatsAppProto::OnIqServerSync(const WANode &node)
-{
- for (auto &coll : node.getChild("sync")->getChildren()) {
- if (coll->title != "collection")
- continue;
-
- auto *pszName = coll->getAttr("name");
-
- auto *pCollection = FindCollection(pszName);
- if (pCollection == nullptr) {
- pCollection = new WACollection(pszName, 0);
- m_arCollections.insert(pCollection);
- }
-
- int dwVersion = 0;
-
- CMStringW wszSnapshotPath(GetTmpFileName("collection", pszName));
- if (auto *pSnapshot = coll->getChild("snapshot")) {
- proto::ExternalBlobReference body(pSnapshot->content);
- if (!body->directpath || !body->has_mediakey) {
- debugLogA("Invalid snapshot data, skipping");
- continue;
- }
-
- MBinBuffer buf = DownloadEncryptedFile(directPath2url(body->directpath), body->mediakey, "App State");
- if (buf.isEmpty()) {
- debugLogA("Invalid downloaded snapshot data, skipping");
- continue;
- }
-
- proto::SyncdSnapshot snapshot(unpadBuffer16(buf));
- if (!snapshot) {
- debugLogA("%s: unable to decode snapshot, skipping");
- continue;
- }
-
- dwVersion = snapshot->version->version;
- if (dwVersion > pCollection->version) {
- pCollection->hash.init();
- debugLogA("%s: applying snapshot of version %d", pCollection->szName.get(), dwVersion);
- for (int i=0; i < snapshot->n_records; i++)
- ParsePatch(pCollection, snapshot->records[i], true);
- }
- else debugLogA("%s: skipping snapshot of version %d", pCollection->szName.get(), dwVersion);
- }
-
- if (auto *pPatchList = coll->getChild("patches")) {
- for (auto &it : pPatchList->getChildren()) {
- proto::SyncdPatch patch(it->content);
- if (!patch) {
- debugLogA("%s: unable to decode patch, skipping");
- continue;
- }
-
- dwVersion = patch->version->version;
- if (dwVersion > pCollection->version) {
- debugLogA("%s: applying patch of version %d", pCollection->szName.get(), dwVersion);
- for (int i = 0; i < patch->n_mutations; i++) {
- auto &jt = *patch->mutations[i];
- ParsePatch(pCollection, jt.record, jt.operation == WA__SYNCD_MUTATION__SYNCD_OPERATION__SET);
- }
- }
- else debugLogA("%s: skipping patch of version %d", pCollection->szName.get(), dwVersion);
- }
- }
-
- JSONNode jsonRoot, jsonMap;
- for (auto &it : pCollection->indexValueMap)
- jsonMap << CHAR_PARAM(ptrA(mir_base64_encode(it.first.c_str(), it.first.size())), ptrA(mir_base64_encode(it.second.c_str(), it.second.size())));
- jsonRoot << INT_PARAM("version", dwVersion) << CHAR_PARAM("hash", ptrA(mir_base64_encode(pCollection->hash.hash, sizeof(pCollection->hash.hash))))
- << JSON_PARAM("indexValueMap", jsonMap);
-
- string2file(jsonRoot.write(), GetTmpFileName("collection", CMStringA(pszName) + ".json"));
- }
-}
-
-static uint8_t sttMutationInfo[] = "WhatsApp Mutation Keys";
-
-void WhatsAppProto::ParsePatch(WACollection *pColl, const Wa__SyncdRecord *rec, bool bSet)
-{
- int id = decodeBigEndian(rec->keyid->id);
- auto &indexBlob = rec->index->blob;
- auto &value = rec->value->blob;
-
- auto *macValue = value.data + value.len - 32;
- std::string index((char *)indexBlob.data, indexBlob.len);
-
- MBinBuffer key(getBlob(CMStringA(FORMAT, "AppSyncKey%d", id)));
- if (key.isEmpty()) {
- debugLogA("No key with id=%d to decode a patch");
- return;
- }
-
- struct
- {
- uint8_t indexKey[32];
- uint8_t encKey[32];
- uint8_t macKey[32];
- uint8_t snapshotMacKey[32];
- uint8_t patchMacKey[32];
-
- } mutationKeys;
-
- HKDF(EVP_sha256(), (BYTE *)"", 0, key.data(), key.length(), sttMutationInfo, sizeof(sttMutationInfo) - 1, (BYTE *)&mutationKeys, sizeof(mutationKeys));
-
- MBinBuffer decoded = aesDecrypt(EVP_aes_256_cbc(), mutationKeys.encKey, value.data, value.data + 16, value.len - 32);
- if (decoded.isEmpty()) {
- debugLogA("Unable to decode patch with key id=%d", id);
- return;
- }
-
- proto::SyncActionData data(unpadBuffer16(decoded));
- if (!data) {
- debugLogA("Unable to decode action data with id=%d", id);
- return;
- }
-
- JSONNode jsonRoot = JSONNode::parse((char *)data->index.data);
-
- if (bSet) {
- ApplyPatch(jsonRoot, data->value);
-
- pColl->hash.add(macValue, 32);
- pColl->indexValueMap[index] = std::string((char*)macValue, 32);
- }
- else {
- debugLogA("Removing data with index: %s", jsonRoot.write().c_str());
-
- auto &prevVal = pColl->indexValueMap.find(index);
- if (prevVal != pColl->indexValueMap.end()) {
- pColl->hash.sub(prevVal->second.c_str(), prevVal->second.size());
- pColl->indexValueMap.erase(prevVal);
- }
- }
-}
-
-void WhatsAppProto::ApplyPatch(const JSONNode &index, const Wa__SyncActionValue *data)
-{
- debugLogA("Applying patch for %s: %s", index.write().c_str(), protobuf_c_text_to_string(data).c_str());
-
- auto title = index.at((json_index_t)0).as_string();
-
- if (title == "contact" && data->contactaction) {
- auto *pUser = AddUser(index.at(1).as_string().c_str(), false);
-
- auto *pAction = data->contactaction;
- auto &fullName = pAction->fullname;
- if (fullName)
- setUString(pUser->hContact, "Nick", fullName);
-
- if (pAction->firstname) {
- CMStringA str(pAction->firstname);
- str.TrimRight();
- setUString(pUser->hContact, "FirstName", str.c_str());
- setUString(pUser->hContact, "LastName", fullName + str.GetLength() + 1);
- }
- else if (fullName != nullptr) {
- auto *p = strrchr(fullName, ' ');
- if (p != 0) {
- *p = 0;
- setUString(pUser->hContact, "FirstName", fullName);
- setUString(pUser->hContact, "LastName", p+1);
- }
- else {
- setUString(pUser->hContact, "FirstName", "");
- setUString(pUser->hContact, "LastName", fullName);
- }
- }
- }
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::ProcessHistorySync(const Wa__HistorySync *pSync)
-{
- debugLogA("Got history sync: %s", protobuf_c_text_to_string(pSync).c_str());
-
- switch (pSync->synctype) {
- case WA__HISTORY_SYNC__HISTORY_SYNC_TYPE__INITIAL_BOOTSTRAP:
- case WA__HISTORY_SYNC__HISTORY_SYNC_TYPE__RECENT:
- for (int i = 0; i < pSync->n_conversations; i++) {
- auto *pChat = pSync->conversations[i];
-
- auto *pUser = AddUser(pChat->id, false);
- for (int j = 0; j < pChat->n_messages; j++) {
- auto *pMessage = pChat->messages[j];
- if (!pMessage->message)
- continue;
-
- MEVENT hEvent = db_event_getById(m_szModuleName, pMessage->message->key->id);
- if (hEvent) {
- debugLogA("Event %s is already processed", pMessage->message->key->id);
- continue;
- }
-
- CMStringA szMessageText(GetMessageText(pMessage->message->message));
- if (!szMessageText.IsEmpty()) {
- auto *key = pMessage->message->key;
-
- PROTORECVEVENT pre = {};
- pre.timestamp = pMessage->message->messagetimestamp;
- pre.szMessage = szMessageText.GetBuffer();
- pre.szMsgId = key->id;
- pre.flags = PREF_CREATEREAD;
- if (key->fromme)
- pre.flags |= PREF_SENT;
- ProtoChainRecvMsg(pUser->hContact, &pre);
-
- if (pUser->bIsGroupChat) {
- if (pChat->name)
- setUString(pUser->hContact, "Nick", pChat->name);
-
- GCEVENT gce = {m_szModuleName, 0, GC_EVENT_MESSAGE};
- gce.dwFlags = GCEF_UTF8;
- gce.pszID.a = pUser->szId;
- gce.pszUID.a = key->participant;
- gce.bIsMe = key->fromme;
- gce.pszText.a = szMessageText.GetBuffer();
- gce.time = pMessage->message->messagetimestamp;
- Chat_Event(&gce);
- }
- }
- }
- }
-
- if (pSync->synctype == WA__HISTORY_SYNC__HISTORY_SYNC_TYPE__INITIAL_BOOTSTRAP)
- GC_RefreshMetadata();
- break;
-
- case WA__HISTORY_SYNC__HISTORY_SYNC_TYPE__PUSH_NAME:
- for (int i = 0; i < pSync->n_pushnames; i++) {
- auto *pName = pSync->pushnames[i];
- if (auto *pUser = AddUser(pName->id, false))
- setUString(pUser->hContact, "Nick", pName->pushname);
- }
- break;
-
- case WA__HISTORY_SYNC__HISTORY_SYNC_TYPE__INITIAL_STATUS_V3:
- for (int i = 0; i < pSync->n_statusv3messages; i++) {
- // TODO
- // auto *pStatus = pSync->statusv3messages[i];
- }
- break;
- }
-}
+/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+
+//////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::InitSync()
+{
+ m_arCollections.insert(new WACollection("regular"));
+ m_arCollections.insert(new WACollection("regular_high"));
+ m_arCollections.insert(new WACollection("regular_low"));
+ m_arCollections.insert(new WACollection("critical_block"));
+ m_arCollections.insert(new WACollection("critical_unblock_low"));
+
+ for (auto &it : m_arCollections) {
+ CMStringW wszPath(GetTmpFileName("collection", it->szName));
+ wszPath.Append(L".json");
+ if (_waccess(wszPath, 0))
+ continue;
+
+ JSONNode root = JSONNode::parse(file2string(wszPath));
+ it->version = root["version"].as_int();
+
+ auto szHash = decodeBinStr(root["hash"].as_string());
+ if (szHash.size() == sizeof(it->hash.hash))
+ memcpy(it->hash.hash, szHash.c_str(), sizeof(it->hash.hash));
+
+ for (auto &val : root["indexValueMap"])
+ it->indexValueMap[decodeBinStr(val.name())] = decodeBinStr(val.as_string());
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnServerSync(const WANode &node)
+{
+ OBJLIST<WACollection> task(1);
+
+ for (auto &it : node.getChildren())
+ if (it->title == "collection")
+ task.insert(new WACollection(it->getAttr("name"), it->getAttrInt("version")));
+
+ ResyncServer(task);
+ SendAck(node);
+}
+
+void WhatsAppProto::ResyncAll()
+{
+ ResyncServer(m_arCollections);
+}
+
+void WhatsAppProto::ResyncServer(const OBJLIST<WACollection> &task)
+{
+ WANodeIq iq(IQ::SET, "w:sync:app:state");
+
+ auto *pList = iq.addChild("sync");
+ for (auto &it : task) {
+ auto *pCollection = m_arCollections.find(it);
+ if (pCollection == nullptr)
+ m_arCollections.insert(pCollection = new WACollection(it->szName, 0));
+
+ if (!pCollection->version || pCollection->version < it->version) {
+ auto *pNode = pList->addChild("collection");
+ *pNode << CHAR_PARAM("name", it->szName) << INT_PARAM("version", pCollection->version)
+ << CHAR_PARAM("return_snapshot", (!pCollection->version) ? "true" : "false");
+ }
+ }
+
+ if (pList->getFirstChild() != nullptr)
+ WSSendNode(iq, &WhatsAppProto::OnIqServerSync);
+}
+
+void WhatsAppProto::OnIqServerSync(const WANode &node)
+{
+ for (auto &coll : node.getChild("sync")->getChildren()) {
+ if (coll->title != "collection")
+ continue;
+
+ auto *pszName = coll->getAttr("name");
+
+ auto *pCollection = FindCollection(pszName);
+ if (pCollection == nullptr) {
+ pCollection = new WACollection(pszName, 0);
+ m_arCollections.insert(pCollection);
+ }
+
+ int dwVersion = 0;
+
+ CMStringW wszSnapshotPath(GetTmpFileName("collection", pszName));
+ if (auto *pSnapshot = coll->getChild("snapshot")) {
+ proto::ExternalBlobReference body(pSnapshot->content);
+ if (!body->directpath || !body->has_mediakey) {
+ debugLogA("Invalid snapshot data, skipping");
+ continue;
+ }
+
+ MBinBuffer buf = DownloadEncryptedFile(directPath2url(body->directpath), body->mediakey, "App State");
+ if (buf.isEmpty()) {
+ debugLogA("Invalid downloaded snapshot data, skipping");
+ continue;
+ }
+
+ proto::SyncdSnapshot snapshot(unpadBuffer16(buf));
+ if (!snapshot) {
+ debugLogA("%s: unable to decode snapshot, skipping");
+ continue;
+ }
+
+ dwVersion = snapshot->version->version;
+ if (dwVersion > pCollection->version) {
+ pCollection->hash.init();
+ debugLogA("%s: applying snapshot of version %d", pCollection->szName.get(), dwVersion);
+ for (int i=0; i < snapshot->n_records; i++)
+ ParsePatch(pCollection, snapshot->records[i], true);
+ }
+ else debugLogA("%s: skipping snapshot of version %d", pCollection->szName.get(), dwVersion);
+ }
+
+ if (auto *pPatchList = coll->getChild("patches")) {
+ for (auto &it : pPatchList->getChildren()) {
+ proto::SyncdPatch patch(it->content);
+ if (!patch) {
+ debugLogA("%s: unable to decode patch, skipping");
+ continue;
+ }
+
+ dwVersion = patch->version->version;
+ if (dwVersion > pCollection->version) {
+ debugLogA("%s: applying patch of version %d", pCollection->szName.get(), dwVersion);
+ for (int i = 0; i < patch->n_mutations; i++) {
+ auto &jt = *patch->mutations[i];
+ ParsePatch(pCollection, jt.record, jt.operation == WA__SYNCD_MUTATION__SYNCD_OPERATION__SET);
+ }
+ }
+ else debugLogA("%s: skipping patch of version %d", pCollection->szName.get(), dwVersion);
+ }
+ }
+
+ JSONNode jsonRoot, jsonMap;
+ for (auto &it : pCollection->indexValueMap)
+ jsonMap << CHAR_PARAM(ptrA(mir_base64_encode(it.first.c_str(), it.first.size())), ptrA(mir_base64_encode(it.second.c_str(), it.second.size())));
+ jsonRoot << INT_PARAM("version", dwVersion) << CHAR_PARAM("hash", ptrA(mir_base64_encode(pCollection->hash.hash, sizeof(pCollection->hash.hash))))
+ << JSON_PARAM("indexValueMap", jsonMap);
+
+ string2file(jsonRoot.write(), GetTmpFileName("collection", CMStringA(pszName) + ".json"));
+ }
+}
+
+static uint8_t sttMutationInfo[] = "WhatsApp Mutation Keys";
+
+void WhatsAppProto::ParsePatch(WACollection *pColl, const Wa__SyncdRecord *rec, bool bSet)
+{
+ int id = decodeBigEndian(rec->keyid->id);
+ auto &indexBlob = rec->index->blob;
+ auto &value = rec->value->blob;
+
+ auto *macValue = value.data + value.len - 32;
+ std::string index((char *)indexBlob.data, indexBlob.len);
+
+ MBinBuffer key(getBlob(CMStringA(FORMAT, "AppSyncKey%d", id)));
+ if (key.isEmpty()) {
+ debugLogA("No key with id=%d to decode a patch");
+ return;
+ }
+
+ struct
+ {
+ uint8_t indexKey[32];
+ uint8_t encKey[32];
+ uint8_t macKey[32];
+ uint8_t snapshotMacKey[32];
+ uint8_t patchMacKey[32];
+
+ } mutationKeys;
+
+ HKDF(EVP_sha256(), (BYTE *)"", 0, key.data(), key.length(), sttMutationInfo, sizeof(sttMutationInfo) - 1, (BYTE *)&mutationKeys, sizeof(mutationKeys));
+
+ MBinBuffer decoded = aesDecrypt(EVP_aes_256_cbc(), mutationKeys.encKey, value.data, value.data + 16, value.len - 32);
+ if (decoded.isEmpty()) {
+ debugLogA("Unable to decode patch with key id=%d", id);
+ return;
+ }
+
+ proto::SyncActionData data(unpadBuffer16(decoded));
+ if (!data) {
+ debugLogA("Unable to decode action data with id=%d", id);
+ return;
+ }
+
+ JSONNode jsonRoot = JSONNode::parse((char *)data->index.data);
+
+ if (bSet) {
+ ApplyPatch(jsonRoot, data->value);
+
+ pColl->hash.add(macValue, 32);
+ pColl->indexValueMap[index] = std::string((char*)macValue, 32);
+ }
+ else {
+ debugLogA("Removing data with index: %s", jsonRoot.write().c_str());
+
+ auto &prevVal = pColl->indexValueMap.find(index);
+ if (prevVal != pColl->indexValueMap.end()) {
+ pColl->hash.sub(prevVal->second.c_str(), prevVal->second.size());
+ pColl->indexValueMap.erase(prevVal);
+ }
+ }
+}
+
+void WhatsAppProto::ApplyPatch(const JSONNode &index, const Wa__SyncActionValue *data)
+{
+ debugLogA("Applying patch for %s: %s", index.write().c_str(), protobuf_c_text_to_string(data).c_str());
+
+ auto title = index.at((json_index_t)0).as_string();
+
+ if (title == "contact" && data->contactaction) {
+ auto *pUser = AddUser(index.at(1).as_string().c_str(), false);
+
+ auto *pAction = data->contactaction;
+ auto &fullName = pAction->fullname;
+ if (fullName)
+ setUString(pUser->hContact, "Nick", fullName);
+
+ if (pAction->firstname) {
+ CMStringA str(pAction->firstname);
+ str.TrimRight();
+ setUString(pUser->hContact, "FirstName", str.c_str());
+ setUString(pUser->hContact, "LastName", fullName + str.GetLength() + 1);
+ }
+ else if (fullName != nullptr) {
+ auto *p = strrchr(fullName, ' ');
+ if (p != 0) {
+ *p = 0;
+ setUString(pUser->hContact, "FirstName", fullName);
+ setUString(pUser->hContact, "LastName", p+1);
+ }
+ else {
+ setUString(pUser->hContact, "FirstName", "");
+ setUString(pUser->hContact, "LastName", fullName);
+ }
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::ProcessHistorySync(const Wa__HistorySync *pSync)
+{
+ debugLogA("Got history sync: %s", protobuf_c_text_to_string(pSync).c_str());
+
+ switch (pSync->synctype) {
+ case WA__HISTORY_SYNC__HISTORY_SYNC_TYPE__INITIAL_BOOTSTRAP:
+ case WA__HISTORY_SYNC__HISTORY_SYNC_TYPE__RECENT:
+ for (int i = 0; i < pSync->n_conversations; i++) {
+ auto *pChat = pSync->conversations[i];
+
+ auto *pUser = AddUser(pChat->id, false);
+ for (int j = 0; j < pChat->n_messages; j++) {
+ auto *pMessage = pChat->messages[j];
+ if (!pMessage->message)
+ continue;
+
+ MEVENT hEvent = db_event_getById(m_szModuleName, pMessage->message->key->id);
+ if (hEvent) {
+ debugLogA("Event %s is already processed", pMessage->message->key->id);
+ continue;
+ }
+
+ CMStringA szMessageText(GetMessageText(pMessage->message->message));
+ if (!szMessageText.IsEmpty()) {
+ auto *key = pMessage->message->key;
+
+ PROTORECVEVENT pre = {};
+ pre.timestamp = pMessage->message->messagetimestamp;
+ pre.szMessage = szMessageText.GetBuffer();
+ pre.szMsgId = key->id;
+ pre.flags = PREF_CREATEREAD;
+ if (key->fromme)
+ pre.flags |= PREF_SENT;
+ ProtoChainRecvMsg(pUser->hContact, &pre);
+
+ if (pUser->bIsGroupChat) {
+ if (pChat->name)
+ setUString(pUser->hContact, "Nick", pChat->name);
+
+ GCEVENT gce = {m_szModuleName, 0, GC_EVENT_MESSAGE};
+ gce.dwFlags = GCEF_UTF8;
+ gce.pszID.a = pUser->szId;
+ gce.pszUID.a = key->participant;
+ gce.bIsMe = key->fromme;
+ gce.pszText.a = szMessageText.GetBuffer();
+ gce.time = pMessage->message->messagetimestamp;
+ Chat_Event(&gce);
+ }
+ }
+ }
+ }
+
+ if (pSync->synctype == WA__HISTORY_SYNC__HISTORY_SYNC_TYPE__INITIAL_BOOTSTRAP)
+ GC_RefreshMetadata();
+ break;
+
+ case WA__HISTORY_SYNC__HISTORY_SYNC_TYPE__PUSH_NAME:
+ for (int i = 0; i < pSync->n_pushnames; i++) {
+ auto *pName = pSync->pushnames[i];
+ if (auto *pUser = AddUser(pName->id, false))
+ setUString(pUser->hContact, "Nick", pName->pushname);
+ }
+ break;
+
+ case WA__HISTORY_SYNC__HISTORY_SYNC_TYPE__INITIAL_STATUS_V3:
+ for (int i = 0; i < pSync->n_statusv3messages; i++) {
+ // TODO
+ // auto *pStatus = pSync->statusv3messages[i];
+ }
+ break;
+ }
+}
diff --git a/protocols/WhatsApp/src/chats.cpp b/protocols/WhatsApp/src/chats.cpp
index 59f98d4548..b0423e5b20 100644
--- a/protocols/WhatsApp/src/chats.cpp
+++ b/protocols/WhatsApp/src/chats.cpp
@@ -1,188 +1,188 @@
-/*
-
-WhatsApp plugin for Miranda NG
-Copyright © 2019-23 George Hazan
-
-*/
-
-#include "stdafx.h"
-
-void WhatsAppProto::GC_RefreshMetadata()
-{
- for (auto &it : m_arUsers) {
- if (it->bIsGroupChat) {
- GC_GetAllMetadata();
- break;
- }
- }
-}
-
-void WhatsAppProto::GC_GetAllMetadata()
-{
- WANodeIq iq(IQ::GET, "w:g2", "@g.us");
- auto *pRoot = iq.addChild("participating");
- *pRoot << XCHILD("participants") << XCHILD("description");
- WSSendNode(iq, &WhatsAppProto::OnIqGcGetAllMetadata);
-}
-
-void WhatsAppProto::OnIqGcGetAllMetadata(const WANode &node)
-{
- if (auto *pGroup = node.getChild("groups"))
- for (auto &it : pGroup->getChildren())
- GC_ParseMetadata(it);
-}
-
-void WhatsAppProto::GC_ParseMetadata(const WANode *pGroup)
-{
- auto *pszId = pGroup->getAttr("id");
- if (pszId == nullptr)
- return;
-
- auto *pChatUser = AddUser(CMStringA(pszId) + "@g.us", false);
- if (pChatUser == nullptr)
- return;
-
- CMStringW wszId(Utf2T(pChatUser->szId));
-
- pChatUser->si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, wszId, getMStringW(pChatUser->hContact, "Nick"));
-
- Chat_AddGroup(pChatUser->si, TranslateT("Owner"));
- Chat_AddGroup(pChatUser->si, TranslateT("SuperAdmin"));
- Chat_AddGroup(pChatUser->si, TranslateT("Admin"));
- Chat_AddGroup(pChatUser->si, TranslateT("Participant"));
-
- CMStringA szOwner(pGroup->getAttr("creator")), szNick, szRole;
-
- for (auto &it : pGroup->getChildren()) {
- if (it->title == "description") {
- CMStringA szDescr = it->getBody();
- if (!szDescr.IsEmpty()) {
- GCEVENT gce = {m_szModuleName, 0, GC_EVENT_INFORMATION};
- gce.dwFlags = GCEF_UTF8;
- gce.pszID.a = pChatUser->szId;
- gce.pszText.a = szDescr.c_str();
- Chat_Event(&gce);
- }
- }
- else if (it->title == "member_add_mode") {
- szRole = it->getBody();
- }
- else if (it->title == "participant") {
- auto *jid = it->getAttr("jid");
-
- // if role isn't specified, use the default one
- auto *role = it->getAttr("type");
- if (role == nullptr)
- role = szRole;
-
- GCEVENT gce = {m_szModuleName, 0, GC_EVENT_JOIN};
- gce.dwFlags = GCEF_UTF8;
- gce.pszID.a = pChatUser->szId;
- gce.pszUID.a = jid;
- gce.bIsMe = (jid == m_szJid);
-
- if (jid == szOwner)
- gce.pszStatus.a = "Owner";
- else if (!mir_strcmp(role, "superadmin"))
- gce.pszStatus.a = "SuperAdmin";
- else if (!mir_strcmp(role, "adminadd"))
- gce.pszStatus.a = "Admin";
- else
- gce.pszStatus.a = "Participant";
-
- if (gce.bIsMe)
- szNick = ptrA(getUStringA(DBKEY_NICK));
- else if (auto *pUser = FindUser(jid))
- szNick = T2Utf(Clist_GetContactDisplayName(pUser->hContact)).get();
- else
- szNick = WAJid(jid).user;
-
- gce.pszNick.a = szNick;
- Chat_Event(&gce);
- }
- }
-
- if (auto *pszSubject = pGroup->getAttr("subject")) {
- time_t iSubjectTime = pGroup->getAttrInt("s_t");
- auto *pszUser = pGroup->getAttr("s_o");
- if (m_szJid == pszUser)
- szNick = ptrA(getUStringA(DBKEY_NICK));
- else if (auto *pUser = FindUser(pszUser))
- szNick = T2Utf(Clist_GetContactDisplayName(pUser->hContact)).get();
- else
- szNick = WAJid(pszUser).user;
-
- GCEVENT gce = { m_szModuleName, 0, GC_EVENT_TOPIC };
- gce.dwFlags = GCEF_UTF8;
- gce.pszID.a = pChatUser->szId;
- gce.pszUID.a = pszUser;
- gce.pszText.a = pszSubject;
- gce.time = iSubjectTime;
- Chat_Event(&gce);
-
- setUString(pChatUser->hContact, "Nick", pszSubject);
- }
-
- pChatUser->bInited = true;
- Chat_Control(m_szModuleName, wszId, m_bHideGroupchats ? WINDOW_HIDDEN : SESSION_INITDONE);
- Chat_Control(m_szModuleName, wszId, SESSION_ONLINE);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-int WhatsAppProto::GcEventHook(WPARAM, LPARAM lParam)
-{
- GCHOOK *gch = (GCHOOK*)lParam;
- if (gch == nullptr)
- return 0;
-
- if (mir_strcmpi(gch->si->pszModule, m_szModuleName))
- return 0;
-
- auto *pUser = FindUser(T2Utf(gch->si->ptszID));
- if (pUser == nullptr)
- return 0;
-
- switch (gch->iType) {
- case GC_USER_MESSAGE:
- if (gch->ptszText && mir_wstrlen(gch->ptszText) > 0) {
- rtrimw(gch->ptszText);
- Chat_UnescapeTags(gch->ptszText);
- SendTextMessage(pUser->szId, T2Utf(gch->ptszText));
- }
- break;
-
- case GC_USER_PRIVMESS:
- break;
-
- case GC_USER_LOGMENU:
- break;
-
- case GC_USER_NICKLISTMENU:
- break;
- }
-
- return 1;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-int WhatsAppProto::GcMenuHook(WPARAM, LPARAM lParam)
-{
- GCMENUITEMS* gcmi = (GCMENUITEMS*)lParam;
- if (gcmi == nullptr)
- return 0;
-
- if (mir_strcmpi(gcmi->pszModule, m_szModuleName))
- return 0;
-
- auto *pUser = FindUser(T2Utf(gcmi->pszID));
- if (pUser == nullptr)
- return 0;
-
- if (gcmi->Type == MENU_ON_LOG) {
- }
- else if (gcmi->Type == MENU_ON_NICKLIST) {
- }
- return 0;
-}
+/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+
+void WhatsAppProto::GC_RefreshMetadata()
+{
+ for (auto &it : m_arUsers) {
+ if (it->bIsGroupChat) {
+ GC_GetAllMetadata();
+ break;
+ }
+ }
+}
+
+void WhatsAppProto::GC_GetAllMetadata()
+{
+ WANodeIq iq(IQ::GET, "w:g2", "@g.us");
+ auto *pRoot = iq.addChild("participating");
+ *pRoot << XCHILD("participants") << XCHILD("description");
+ WSSendNode(iq, &WhatsAppProto::OnIqGcGetAllMetadata);
+}
+
+void WhatsAppProto::OnIqGcGetAllMetadata(const WANode &node)
+{
+ if (auto *pGroup = node.getChild("groups"))
+ for (auto &it : pGroup->getChildren())
+ GC_ParseMetadata(it);
+}
+
+void WhatsAppProto::GC_ParseMetadata(const WANode *pGroup)
+{
+ auto *pszId = pGroup->getAttr("id");
+ if (pszId == nullptr)
+ return;
+
+ auto *pChatUser = AddUser(CMStringA(pszId) + "@g.us", false);
+ if (pChatUser == nullptr)
+ return;
+
+ CMStringW wszId(Utf2T(pChatUser->szId));
+
+ pChatUser->si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, wszId, getMStringW(pChatUser->hContact, "Nick"));
+
+ Chat_AddGroup(pChatUser->si, TranslateT("Owner"));
+ Chat_AddGroup(pChatUser->si, TranslateT("SuperAdmin"));
+ Chat_AddGroup(pChatUser->si, TranslateT("Admin"));
+ Chat_AddGroup(pChatUser->si, TranslateT("Participant"));
+
+ CMStringA szOwner(pGroup->getAttr("creator")), szNick, szRole;
+
+ for (auto &it : pGroup->getChildren()) {
+ if (it->title == "description") {
+ CMStringA szDescr = it->getBody();
+ if (!szDescr.IsEmpty()) {
+ GCEVENT gce = {m_szModuleName, 0, GC_EVENT_INFORMATION};
+ gce.dwFlags = GCEF_UTF8;
+ gce.pszID.a = pChatUser->szId;
+ gce.pszText.a = szDescr.c_str();
+ Chat_Event(&gce);
+ }
+ }
+ else if (it->title == "member_add_mode") {
+ szRole = it->getBody();
+ }
+ else if (it->title == "participant") {
+ auto *jid = it->getAttr("jid");
+
+ // if role isn't specified, use the default one
+ auto *role = it->getAttr("type");
+ if (role == nullptr)
+ role = szRole;
+
+ GCEVENT gce = {m_szModuleName, 0, GC_EVENT_JOIN};
+ gce.dwFlags = GCEF_UTF8;
+ gce.pszID.a = pChatUser->szId;
+ gce.pszUID.a = jid;
+ gce.bIsMe = (jid == m_szJid);
+
+ if (jid == szOwner)
+ gce.pszStatus.a = "Owner";
+ else if (!mir_strcmp(role, "superadmin"))
+ gce.pszStatus.a = "SuperAdmin";
+ else if (!mir_strcmp(role, "adminadd"))
+ gce.pszStatus.a = "Admin";
+ else
+ gce.pszStatus.a = "Participant";
+
+ if (gce.bIsMe)
+ szNick = ptrA(getUStringA(DBKEY_NICK));
+ else if (auto *pUser = FindUser(jid))
+ szNick = T2Utf(Clist_GetContactDisplayName(pUser->hContact)).get();
+ else
+ szNick = WAJid(jid).user;
+
+ gce.pszNick.a = szNick;
+ Chat_Event(&gce);
+ }
+ }
+
+ if (auto *pszSubject = pGroup->getAttr("subject")) {
+ time_t iSubjectTime = pGroup->getAttrInt("s_t");
+ auto *pszUser = pGroup->getAttr("s_o");
+ if (m_szJid == pszUser)
+ szNick = ptrA(getUStringA(DBKEY_NICK));
+ else if (auto *pUser = FindUser(pszUser))
+ szNick = T2Utf(Clist_GetContactDisplayName(pUser->hContact)).get();
+ else
+ szNick = WAJid(pszUser).user;
+
+ GCEVENT gce = { m_szModuleName, 0, GC_EVENT_TOPIC };
+ gce.dwFlags = GCEF_UTF8;
+ gce.pszID.a = pChatUser->szId;
+ gce.pszUID.a = pszUser;
+ gce.pszText.a = pszSubject;
+ gce.time = iSubjectTime;
+ Chat_Event(&gce);
+
+ setUString(pChatUser->hContact, "Nick", pszSubject);
+ }
+
+ pChatUser->bInited = true;
+ Chat_Control(m_szModuleName, wszId, m_bHideGroupchats ? WINDOW_HIDDEN : SESSION_INITDONE);
+ Chat_Control(m_szModuleName, wszId, SESSION_ONLINE);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int WhatsAppProto::GcEventHook(WPARAM, LPARAM lParam)
+{
+ GCHOOK *gch = (GCHOOK*)lParam;
+ if (gch == nullptr)
+ return 0;
+
+ if (mir_strcmpi(gch->si->pszModule, m_szModuleName))
+ return 0;
+
+ auto *pUser = FindUser(T2Utf(gch->si->ptszID));
+ if (pUser == nullptr)
+ return 0;
+
+ switch (gch->iType) {
+ case GC_USER_MESSAGE:
+ if (gch->ptszText && mir_wstrlen(gch->ptszText) > 0) {
+ rtrimw(gch->ptszText);
+ Chat_UnescapeTags(gch->ptszText);
+ SendTextMessage(pUser->szId, T2Utf(gch->ptszText));
+ }
+ break;
+
+ case GC_USER_PRIVMESS:
+ break;
+
+ case GC_USER_LOGMENU:
+ break;
+
+ case GC_USER_NICKLISTMENU:
+ break;
+ }
+
+ return 1;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int WhatsAppProto::GcMenuHook(WPARAM, LPARAM lParam)
+{
+ GCMENUITEMS* gcmi = (GCMENUITEMS*)lParam;
+ if (gcmi == nullptr)
+ return 0;
+
+ if (mir_strcmpi(gcmi->pszModule, m_szModuleName))
+ return 0;
+
+ auto *pUser = FindUser(T2Utf(gcmi->pszID));
+ if (pUser == nullptr)
+ return 0;
+
+ if (gcmi->Type == MENU_ON_LOG) {
+ }
+ else if (gcmi->Type == MENU_ON_NICKLIST) {
+ }
+ return 0;
+}
diff --git a/protocols/WhatsApp/src/proto.cpp b/protocols/WhatsApp/src/proto.cpp
index 006f78dfc0..8fabeb098b 100644
--- a/protocols/WhatsApp/src/proto.cpp
+++ b/protocols/WhatsApp/src/proto.cpp
@@ -1,308 +1,308 @@
-/*
-
-WhatsApp plugin for Miranda NG
-Copyright © 2019-23 George Hazan
-
-*/
-
-#include "stdafx.h"
-
-struct SearchParam
-{
- SearchParam(const wchar_t *_jid, LONG _id) :
- jid(_jid), id(_id)
- {}
-
- std::wstring jid;
- LONG id;
-};
-
-static int CompareOwnMsgs(const WAOwnMessage *p1, const WAOwnMessage *p2)
-{
- return strcmp(p1->szMessageId, p2->szMessageId);
-}
-
-static int CompareUsers(const WAUser *p1, const WAUser *p2)
-{
- return strcmp(p1->szId, p2->szId);
-}
-
-static int CompareCollections(const WACollection *p1, const WACollection *p2)
-{
- return strcmp(p1->szName, p2->szName);
-}
-
-static int CompareRequests(const WARequestBase *p1, const WARequestBase *p2)
-{
- return strcmp(p1->szPacketId, p2->szPacketId);
-}
-
-WhatsAppProto::WhatsAppProto(const char *proto_name, const wchar_t *username) :
- PROTO<WhatsAppProto>(proto_name, username),
- m_impl(*this),
- m_signalStore(this, ""),
- m_szJid(getMStringA(DBKEY_ID)),
- m_tszDefaultGroup(getWStringA(DBKEY_DEF_GROUP)),
- m_arUsers(10, CompareUsers),
- m_arOwnMsgs(1, CompareOwnMsgs),
- m_arPersistent(1),
- m_arPacketQueue(10, CompareRequests),
- m_arCollections(10, CompareCollections),
-
- m_wszNick(this, "Nick"),
- m_wszDeviceName(this, "DeviceName", L"Miranda NG"),
- m_wszDefaultGroup(this, "DefaultGroup", L"WhatsApp"),
- m_bUsePopups(this, "UsePopups", true),
- m_bUseBbcodes(this, "UseBbcodes", true),
- m_bHideGroupchats(this, "HideChats", true)
-{
- db_set_resident(m_szModuleName, "StatusMsg");
-
- CreateProtoService(PS_CREATEACCMGRUI, &WhatsAppProto::SvcCreateAccMgrUI);
-
- CreateProtoService(PS_GETAVATARINFO, &WhatsAppProto::GetAvatarInfo);
- CreateProtoService(PS_GETAVATARCAPS, &WhatsAppProto::GetAvatarCaps);
- CreateProtoService(PS_GETMYAVATAR, &WhatsAppProto::GetMyAvatar);
- CreateProtoService(PS_SETMYAVATAR, &WhatsAppProto::SetMyAvatar);
-
- HookProtoEvent(ME_OPT_INITIALISE, &WhatsAppProto::OnOptionsInit);
-
- InitSync();
- InitPopups();
- InitPersistentHandlers();
-
- // Create standard network connection
- wchar_t descr[512];
- mir_snwprintf(descr, TranslateT("%s (server)"), m_tszUserName);
-
- NETLIBUSER nlu = {};
- nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE;
- nlu.szSettingsModule = m_szModuleName;
- nlu.szDescriptiveName.w = descr;
- m_hNetlibUser = Netlib_RegisterUser(&nlu);
-
- // Temporary folder
- CreateDirectoryTreeW(CMStringW(VARSW(L"%miranda_userdata%")) + L"\\" + _A2T(m_szModuleName));
-
- // Avatars folder
- m_tszAvatarFolder = CMStringW(VARSW(L"%miranda_avatarcache%")) + L"\\" + _A2T(m_szModuleName);
- DWORD dwAttributes = GetFileAttributes(m_tszAvatarFolder.c_str());
- if (dwAttributes == 0xffffffff || (dwAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
- CreateDirectoryTreeW(m_tszAvatarFolder.c_str());
-
- // default contacts group
- if (m_tszDefaultGroup == NULL)
- m_tszDefaultGroup = mir_wstrdup(L"WhatsApp");
- Clist_GroupCreate(0, m_tszDefaultGroup);
-
- // groupchat initialization
- GCREGISTER gcr = {};
- gcr.dwFlags = GC_TYPNOTIF | GC_DATABASE;
- gcr.ptszDispName = m_tszUserName;
- gcr.pszModule = m_szModuleName;
- Chat_Register(&gcr);
-
- HookProtoEvent(ME_GC_EVENT, &WhatsAppProto::GcEventHook);
- HookProtoEvent(ME_GC_BUILDMENU, &WhatsAppProto::GcMenuHook);
-}
-
-WhatsAppProto::~WhatsAppProto()
-{
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// OnErase - remove temporary folder for account
-
-const char *pszNeededItems[] = {
- "AM_BaseProto", "DefaultGroup", "DeviceName", "HideChats", "NLlog", "Nick"
-};
-
-static int sttEnumFunc(const char *szSetting, void *param)
-{
- for (auto &it : pszNeededItems)
- if (!mir_strcmp(it, szSetting))
- return 0;
-
- auto *pList = (LIST<char>*)param;
- pList->insert(mir_strdup(szSetting));
- return 0;
-}
-
-void WhatsAppProto::OnErase()
-{
- m_bUnregister = true;
- ServerThreadWorker();
-
- // remove all temporary data from database & disk folder
- LIST<char> arSettings(50);
- db_enum_settings(0, sttEnumFunc, m_szModuleName, &arSettings);
- for (auto &it : arSettings) {
- delSetting(it);
- mir_free(it);
- }
-
- DeleteDirectoryTreeW(CMStringW(VARSW(L"%miranda_userdata%")) + L"\\" + _A2T(m_szModuleName), false);
-
- m_szJid.Empty();
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// OnModulesLoaded emulator for an account
-
-void WhatsAppProto::OnModulesLoaded()
-{
- // initialize contacts cache
- if (!m_szJid.IsEmpty())
- m_arUsers.insert(new WAUser(0, m_szJid, false));
-
- for (auto &cc : AccContacts()) {
- bool bIsChat = isChatRoom(cc);
- CMStringA szId(getMStringA(cc, bIsChat ? "ChatRoomID" : DBKEY_ID));
- if (!szId.IsEmpty())
- m_arUsers.insert(new WAUser(cc, szId, bIsChat));
- }
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// PROTO_INTERFACE implementation
-
-MCONTACT WhatsAppProto::AddToList(int flags, PROTOSEARCHRESULT *psr)
-{
- if (psr->id.w == nullptr)
- return NULL;
-
- auto *pUser = AddUser(T2Utf(psr->id.w), (flags & PALF_TEMPORARY) != 0);
- db_unset(pUser->hContact, "CList", "NotOnList");
-
- return pUser->hContact;
-}
-
-INT_PTR WhatsAppProto::GetCaps(int type, MCONTACT)
-{
- switch (type) {
- case PFLAGNUM_1:
- return PF1_IM | PF1_FILE | PF1_CHAT | PF1_BASICSEARCH | PF1_ADDSEARCHRES | PF1_MODEMSGRECV;
- case PFLAGNUM_2:
- return PF2_ONLINE;
- case PFLAGNUM_3:
- return 0;
- case PFLAGNUM_4:
- return PF4_NOCUSTOMAUTH | PF4_NOAUTHDENYREASON | PF4_IMSENDOFFLINE | PF4_OFFLINEFILES | PF4_SUPPORTTYPING | PF4_AVATARS | PF4_SERVERMSGID;
- case PFLAGNUM_5:
- return 0;
- case PFLAG_UNIQUEIDTEXT:
- return (DWORD_PTR)L"WhatsApp ID";
- }
- return 0;
-}
-
-int WhatsAppProto::SetStatus(int iNewStatus)
-{
- if (m_iDesiredStatus == iNewStatus)
- return 0;
-
- if (!mir_wstrlen(m_wszNick)) {
- Popup(0, LPGENW("Connection cannot be established! You have not completed all necessary fields (Nick)."), LPGENW("Error"));
- return 0;
- }
-
- int oldStatus = m_iStatus;
-
- // Routing statuses not supported by WhatsApp
- switch (iNewStatus) {
- case ID_STATUS_OFFLINE:
- m_iDesiredStatus = iNewStatus;
- break;
-
- case ID_STATUS_ONLINE:
- case ID_STATUS_FREECHAT:
- default:
- m_iDesiredStatus = ID_STATUS_ONLINE;
- break;
- }
-
- if (m_iDesiredStatus == ID_STATUS_OFFLINE) {
- SetServerStatus(m_iDesiredStatus);
-
- if (m_hServerConn != nullptr)
- Netlib_Shutdown(m_hServerConn);
-
- m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE;
- ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, 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_hServerConn != nullptr) {
- SetServerStatus(m_iDesiredStatus);
-
- m_iStatus = m_iDesiredStatus;
- ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus);
- }
- else ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus);
-
- return 0;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-int WhatsAppProto::SendMsg(MCONTACT hContact, int, const char *pszMsg)
-{
- ptrA jid(getStringA(hContact, DBKEY_ID));
- if (jid == nullptr || pszMsg == nullptr)
- return 0;
-
- if (!isOnline()) {
- debugLogA("No connection");
- return 0;
- }
-
- return SendTextMessage(jid, pszMsg);
-}
-
-int WhatsAppProto::UserIsTyping(MCONTACT hContact, int type)
-{
- if (hContact && isOnline()) {
- ptrA jid(getStringA(hContact, DBKEY_ID));
- if (jid && isOnline()) {
- WSSendNode(
- WANode("chatstates") << CHAR_PARAM("to", jid) << XCHILD((type == PROTOTYPE_SELFTYPING_ON) ? "composing" : "paused"));
- }
- }
-
- return 0;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// contacts search
-
-void WhatsAppProto::SearchAckThread(void *targ)
-{
- Sleep(100);
-
- SearchParam *param = (SearchParam*)targ;
- PROTOSEARCHRESULT psr = {};
- psr.cbSize = sizeof(psr);
- psr.flags = PSR_UNICODE;
- psr.nick.w = psr.firstName.w = psr.lastName.w = L"";
- psr.id.w = (wchar_t*)param->jid.c_str();
-
- ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)param->id, (LPARAM)&psr);
- ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)param->id, 0);
-
- delete param;
-}
-
-HANDLE WhatsAppProto::SearchBasic(const wchar_t* id)
-{
- if (!isOnline())
- return nullptr;
-
- // fake - we always accept search
- SearchParam *param = new SearchParam(id, -1);
- ForkThread(&WhatsAppProto::SearchAckThread, param);
- return (HANDLE)param->id;
-}
+/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+
+struct SearchParam
+{
+ SearchParam(const wchar_t *_jid, LONG _id) :
+ jid(_jid), id(_id)
+ {}
+
+ std::wstring jid;
+ LONG id;
+};
+
+static int CompareOwnMsgs(const WAOwnMessage *p1, const WAOwnMessage *p2)
+{
+ return strcmp(p1->szMessageId, p2->szMessageId);
+}
+
+static int CompareUsers(const WAUser *p1, const WAUser *p2)
+{
+ return strcmp(p1->szId, p2->szId);
+}
+
+static int CompareCollections(const WACollection *p1, const WACollection *p2)
+{
+ return strcmp(p1->szName, p2->szName);
+}
+
+static int CompareRequests(const WARequestBase *p1, const WARequestBase *p2)
+{
+ return strcmp(p1->szPacketId, p2->szPacketId);
+}
+
+WhatsAppProto::WhatsAppProto(const char *proto_name, const wchar_t *username) :
+ PROTO<WhatsAppProto>(proto_name, username),
+ m_impl(*this),
+ m_signalStore(this, ""),
+ m_szJid(getMStringA(DBKEY_ID)),
+ m_tszDefaultGroup(getWStringA(DBKEY_DEF_GROUP)),
+ m_arUsers(10, CompareUsers),
+ m_arOwnMsgs(1, CompareOwnMsgs),
+ m_arPersistent(1),
+ m_arPacketQueue(10, CompareRequests),
+ m_arCollections(10, CompareCollections),
+
+ m_wszNick(this, "Nick"),
+ m_wszDeviceName(this, "DeviceName", L"Miranda NG"),
+ m_wszDefaultGroup(this, "DefaultGroup", L"WhatsApp"),
+ m_bUsePopups(this, "UsePopups", true),
+ m_bUseBbcodes(this, "UseBbcodes", true),
+ m_bHideGroupchats(this, "HideChats", true)
+{
+ db_set_resident(m_szModuleName, "StatusMsg");
+
+ CreateProtoService(PS_CREATEACCMGRUI, &WhatsAppProto::SvcCreateAccMgrUI);
+
+ CreateProtoService(PS_GETAVATARINFO, &WhatsAppProto::GetAvatarInfo);
+ CreateProtoService(PS_GETAVATARCAPS, &WhatsAppProto::GetAvatarCaps);
+ CreateProtoService(PS_GETMYAVATAR, &WhatsAppProto::GetMyAvatar);
+ CreateProtoService(PS_SETMYAVATAR, &WhatsAppProto::SetMyAvatar);
+
+ HookProtoEvent(ME_OPT_INITIALISE, &WhatsAppProto::OnOptionsInit);
+
+ InitSync();
+ InitPopups();
+ InitPersistentHandlers();
+
+ // Create standard network connection
+ wchar_t descr[512];
+ mir_snwprintf(descr, TranslateT("%s (server)"), m_tszUserName);
+
+ NETLIBUSER nlu = {};
+ nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE;
+ nlu.szSettingsModule = m_szModuleName;
+ nlu.szDescriptiveName.w = descr;
+ m_hNetlibUser = Netlib_RegisterUser(&nlu);
+
+ // Temporary folder
+ CreateDirectoryTreeW(CMStringW(VARSW(L"%miranda_userdata%")) + L"\\" + _A2T(m_szModuleName));
+
+ // Avatars folder
+ m_tszAvatarFolder = CMStringW(VARSW(L"%miranda_avatarcache%")) + L"\\" + _A2T(m_szModuleName);
+ DWORD dwAttributes = GetFileAttributes(m_tszAvatarFolder.c_str());
+ if (dwAttributes == 0xffffffff || (dwAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
+ CreateDirectoryTreeW(m_tszAvatarFolder.c_str());
+
+ // default contacts group
+ if (m_tszDefaultGroup == NULL)
+ m_tszDefaultGroup = mir_wstrdup(L"WhatsApp");
+ Clist_GroupCreate(0, m_tszDefaultGroup);
+
+ // groupchat initialization
+ GCREGISTER gcr = {};
+ gcr.dwFlags = GC_TYPNOTIF | GC_DATABASE;
+ gcr.ptszDispName = m_tszUserName;
+ gcr.pszModule = m_szModuleName;
+ Chat_Register(&gcr);
+
+ HookProtoEvent(ME_GC_EVENT, &WhatsAppProto::GcEventHook);
+ HookProtoEvent(ME_GC_BUILDMENU, &WhatsAppProto::GcMenuHook);
+}
+
+WhatsAppProto::~WhatsAppProto()
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// OnErase - remove temporary folder for account
+
+const char *pszNeededItems[] = {
+ "AM_BaseProto", "DefaultGroup", "DeviceName", "HideChats", "NLlog", "Nick"
+};
+
+static int sttEnumFunc(const char *szSetting, void *param)
+{
+ for (auto &it : pszNeededItems)
+ if (!mir_strcmp(it, szSetting))
+ return 0;
+
+ auto *pList = (LIST<char>*)param;
+ pList->insert(mir_strdup(szSetting));
+ return 0;
+}
+
+void WhatsAppProto::OnErase()
+{
+ m_bUnregister = true;
+ ServerThreadWorker();
+
+ // remove all temporary data from database & disk folder
+ LIST<char> arSettings(50);
+ db_enum_settings(0, sttEnumFunc, m_szModuleName, &arSettings);
+ for (auto &it : arSettings) {
+ delSetting(it);
+ mir_free(it);
+ }
+
+ DeleteDirectoryTreeW(CMStringW(VARSW(L"%miranda_userdata%")) + L"\\" + _A2T(m_szModuleName), false);
+
+ m_szJid.Empty();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// OnModulesLoaded emulator for an account
+
+void WhatsAppProto::OnModulesLoaded()
+{
+ // initialize contacts cache
+ if (!m_szJid.IsEmpty())
+ m_arUsers.insert(new WAUser(0, m_szJid, false));
+
+ for (auto &cc : AccContacts()) {
+ bool bIsChat = isChatRoom(cc);
+ CMStringA szId(getMStringA(cc, bIsChat ? "ChatRoomID" : DBKEY_ID));
+ if (!szId.IsEmpty())
+ m_arUsers.insert(new WAUser(cc, szId, bIsChat));
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// PROTO_INTERFACE implementation
+
+MCONTACT WhatsAppProto::AddToList(int flags, PROTOSEARCHRESULT *psr)
+{
+ if (psr->id.w == nullptr)
+ return NULL;
+
+ auto *pUser = AddUser(T2Utf(psr->id.w), (flags & PALF_TEMPORARY) != 0);
+ db_unset(pUser->hContact, "CList", "NotOnList");
+
+ return pUser->hContact;
+}
+
+INT_PTR WhatsAppProto::GetCaps(int type, MCONTACT)
+{
+ switch (type) {
+ case PFLAGNUM_1:
+ return PF1_IM | PF1_FILE | PF1_CHAT | PF1_BASICSEARCH | PF1_ADDSEARCHRES | PF1_MODEMSGRECV;
+ case PFLAGNUM_2:
+ return PF2_ONLINE;
+ case PFLAGNUM_3:
+ return 0;
+ case PFLAGNUM_4:
+ return PF4_NOCUSTOMAUTH | PF4_NOAUTHDENYREASON | PF4_IMSENDOFFLINE | PF4_OFFLINEFILES | PF4_SUPPORTTYPING | PF4_AVATARS | PF4_SERVERMSGID;
+ case PFLAGNUM_5:
+ return 0;
+ case PFLAG_UNIQUEIDTEXT:
+ return (DWORD_PTR)L"WhatsApp ID";
+ }
+ return 0;
+}
+
+int WhatsAppProto::SetStatus(int iNewStatus)
+{
+ if (m_iDesiredStatus == iNewStatus)
+ return 0;
+
+ if (!mir_wstrlen(m_wszNick)) {
+ Popup(0, LPGENW("Connection cannot be established! You have not completed all necessary fields (Nick)."), LPGENW("Error"));
+ return 0;
+ }
+
+ int oldStatus = m_iStatus;
+
+ // Routing statuses not supported by WhatsApp
+ switch (iNewStatus) {
+ case ID_STATUS_OFFLINE:
+ m_iDesiredStatus = iNewStatus;
+ break;
+
+ case ID_STATUS_ONLINE:
+ case ID_STATUS_FREECHAT:
+ default:
+ m_iDesiredStatus = ID_STATUS_ONLINE;
+ break;
+ }
+
+ if (m_iDesiredStatus == ID_STATUS_OFFLINE) {
+ SetServerStatus(m_iDesiredStatus);
+
+ if (m_hServerConn != nullptr)
+ Netlib_Shutdown(m_hServerConn);
+
+ m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE;
+ ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, 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_hServerConn != nullptr) {
+ SetServerStatus(m_iDesiredStatus);
+
+ m_iStatus = m_iDesiredStatus;
+ ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus);
+ }
+ else ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus);
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int WhatsAppProto::SendMsg(MCONTACT hContact, int, const char *pszMsg)
+{
+ ptrA jid(getStringA(hContact, DBKEY_ID));
+ if (jid == nullptr || pszMsg == nullptr)
+ return 0;
+
+ if (!isOnline()) {
+ debugLogA("No connection");
+ return 0;
+ }
+
+ return SendTextMessage(jid, pszMsg);
+}
+
+int WhatsAppProto::UserIsTyping(MCONTACT hContact, int type)
+{
+ if (hContact && isOnline()) {
+ ptrA jid(getStringA(hContact, DBKEY_ID));
+ if (jid && isOnline()) {
+ WSSendNode(
+ WANode("chatstates") << CHAR_PARAM("to", jid) << XCHILD((type == PROTOTYPE_SELFTYPING_ON) ? "composing" : "paused"));
+ }
+ }
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// contacts search
+
+void WhatsAppProto::SearchAckThread(void *targ)
+{
+ Sleep(100);
+
+ SearchParam *param = (SearchParam*)targ;
+ PROTOSEARCHRESULT psr = {};
+ psr.cbSize = sizeof(psr);
+ psr.flags = PSR_UNICODE;
+ psr.nick.w = psr.firstName.w = psr.lastName.w = L"";
+ psr.id.w = (wchar_t*)param->jid.c_str();
+
+ ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)param->id, (LPARAM)&psr);
+ ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)param->id, 0);
+
+ delete param;
+}
+
+HANDLE WhatsAppProto::SearchBasic(const wchar_t* id)
+{
+ if (!isOnline())
+ return nullptr;
+
+ // fake - we always accept search
+ SearchParam *param = new SearchParam(id, -1);
+ ForkThread(&WhatsAppProto::SearchAckThread, param);
+ return (HANDLE)param->id;
+}
diff --git a/protocols/WhatsApp/src/proto.h b/protocols/WhatsApp/src/proto.h
index dbcf57b597..8ec4593767 100644
--- a/protocols/WhatsApp/src/proto.h
+++ b/protocols/WhatsApp/src/proto.h
@@ -1,508 +1,508 @@
-/*
-
-WhatsApp plugin for Miranda NG
-Copyright © 2019-23 George Hazan
-
-*/
-
-#if !defined(PROTO_H)
-#define PROTO_H
-
-#define S_WHATSAPP_NET "@s.whatsapp.net"
-#define APP_VERSION "2.2230.15"
-#define KEY_BUNDLE_TYPE "\x05"
-
-class WhatsAppProto;
-typedef void (WhatsAppProto:: *WA_PKT_HANDLER)(const WANode &node);
-typedef void (WhatsAppProto:: *WA_PKT_HANDLER_FULL)(const WANode &node, void *pUserInfo);
-
-struct WAMSG
-{
- union {
- uint32_t dwFlags = 0;
- struct {
- bool bPrivateChat : 1;
- bool bGroupChat : 1;
- bool bDirectStatus : 1;
- bool bOtherStatus : 1;
- bool bPeerBroadcast : 1;
- bool bOtherBroadcast : 1;
- bool bOffline : 1;
- };
- };
-};
-
-struct WAMediaKeys
-{
- WAMediaKeys(const uint8_t *pKey, size_t keyLen, const char *pszMediaType);
-
- uint8_t iv[16];
- uint8_t cipherKey[32];
- uint8_t macKey[64];
-};
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// own requests
-
-struct WARequestBase
-{
- WARequestBase(const CMStringA &_1) :
- szPacketId(_1)
- {}
- virtual ~WARequestBase() {}
-
- CMStringA szPacketId;
-
- virtual void Execute(WhatsAppProto *ppro, const WANode &node) = 0;
-};
-
-class WARequestSimple : public WARequestBase
-{
- WA_PKT_HANDLER pHandler;
-
-public:
- WARequestSimple(const CMStringA &_1, WA_PKT_HANDLER _2) :
- WARequestBase(_1),
- pHandler(_2)
- {}
-
- void Execute(WhatsAppProto *ppro, const WANode &node) override
- {
- (ppro->*pHandler)(node);
- }
-};
-
-class WARequestParam : public WARequestBase
-{
- WA_PKT_HANDLER_FULL pHandler;
- void *pUserInfo;
-
-public:
- WARequestParam(const CMStringA &_1, WA_PKT_HANDLER_FULL _2, void *_3) :
- WARequestBase(_1),
- pHandler(_2),
- pUserInfo(_3)
- {}
-
- void Execute(WhatsAppProto *ppro, const WANode &node) override
- {
- (ppro->*pHandler)(node, pUserInfo);
- }
-};
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-struct WAPersistentHandler
-{
- WAPersistentHandler(const char *_1, const char *_2, const char *_3, const char *_4, WA_PKT_HANDLER _5) :
- pszTitle(_1), pszType(_2), pszXmlns(_3), pszChild(_4), pHandler(_5)
- {}
-
- const char *pszTitle, *pszType, *pszXmlns, *pszChild;
- WA_PKT_HANDLER pHandler;
-};
-
-struct WAUser
-{
- WAUser(MCONTACT _1, const char *_2, bool _3 = false) :
- hContact(_1),
- szId(mir_strdup(_2)),
- bIsGroupChat(_3),
- arDevices(1)
- {
- }
-
- ~WAUser()
- {
- mir_free(szId);
- }
-
- MCONTACT hContact;
- DWORD dwModifyTag = 0;
- char *szId;
- bool bInited = false, bIsGroupChat, bDeviceInit = false;
- SESSION_INFO *si = 0;
- OBJLIST<WAJid> arDevices;
- time_t m_timer1 = 0, m_timer2 = 0;
-};
-
-struct WAOwnMessage
-{
- WAOwnMessage(int _1, const char *_2, const char *_3) :
- pktId(_1),
- szJid(_2),
- szMessageId(_3)
- {}
-
- int pktId;
- CMStringA szJid, szMessageId;
-};
-
-struct WACollection
-{
- WACollection(const char *_1, int _2 = 0) :
- szName(mir_strdup(_1)),
- version(_2)
- {}
-
- ptrA szName;
- int version;
-
- LT_HASH hash;
- std::map<std::string, std::string> indexValueMap;
-};
-
-class WANoise
-{
- friend class WhatsAppProto;
-
- WhatsAppProto *ppro;
- uint32_t readCounter = 0, writeCounter = 0;
- bool bInitFinished = false, bSendIntro = false;
- MBinBuffer salt, encKey, decKey;
- uint8_t hash[32];
-
- struct {
- MBinBuffer priv, pub;
- } noiseKeys, ephemeral;
-
- void deriveKey(const void *pData, size_t cbLen, MBinBuffer &write, MBinBuffer &read);
- void mixIntoKey(const void *n, const void *p);
- void updateHash(const void *pData, size_t cbLen);
-
-public:
- WANoise(WhatsAppProto *_ppro);
-
- void finish();
- void init();
-
- MBinBuffer decrypt(const void *pData, size_t cbLen);
- MBinBuffer encrypt(const void *pData, size_t cbLen);
-
- size_t decodeFrame(const uint8_t *&pData, size_t &cbLen);
- MBinBuffer encodeFrame(const void *pData, size_t cbLen);
-};
-
-class MSignalSession : public MZeroedObject
-{
- friend class MSignalStore;
- signal_protocol_address address;
- session_cipher *cipher = nullptr;
-
-public:
- CMStringA szName;
- MBinBuffer sessionData;
-
- MSignalSession(const CMStringA &_1, int _2);
- ~MSignalSession();
-
- bool hasAddress(const char *name, size_t name_len) const;
-
- __forceinline session_cipher* getCipher(void) const { return cipher; }
- __forceinline int getDeviceId() const { return address.device_id; }
- CMStringA getSetting() const;
-};
-
-class MSignalStore
-{
- void init();
-
- signal_context *m_pContext;
- signal_protocol_store_context *m_pStore;
-
- void importPublicKey(ec_public_key **result, MBinBuffer &buf);
-
-public:
- PROTO_INTERFACE *pProto;
- const char *prefix;
-
- OBJLIST<MSignalSession> arSessions;
-
- struct
- {
- MBinBuffer priv, pub;
- }
- signedIdentity;
-
- struct
- {
- MBinBuffer priv, pub, signature;
- uint32_t keyid;
- }
- preKey;
-
- MSignalStore(PROTO_INTERFACE *_1, const char *_2);
- ~MSignalStore();
-
- __forceinline signal_context *CTX() const { return m_pContext; }
-
- MSignalSession* createSession(const CMStringA &szName, int deviceId);
- MSignalSession* getSession(const signal_protocol_address *address);
- void injectSession(const char *szJid, const WANode *pNode, const WANode *pKey);
-
- MBinBuffer decryptSignalProto(const CMStringA &from, const char *pszType, const MBinBuffer &encrypted);
- MBinBuffer decryptGroupSignalProto(const CMStringA &from, const CMStringA &author, const MBinBuffer &encrypted);
-
- MBinBuffer encryptSenderKey(const WAJid &to, const CMStringA &from, const MBinBuffer &buf, MBinBuffer &skmsgKey);
- MBinBuffer encryptSignalProto(const WAJid &to, const MBinBuffer &buf, int &type);
-
- MBinBuffer encodeSignedIdentity(bool);
- void generatePrekeys(int count);
-
- void logError(int code, const char *szMessage);
-
- void processSenderKeyMessage(const CMStringA &author, const Wa__Message__SenderKeyDistributionMessage *msg);
-};
-
-class WhatsAppProto : public PROTO<WhatsAppProto>
-{
- friend class WANoise;
- friend class CWhatsAppQRDlg;
- friend class COptionsDlg;
-
- class CWhatsAppProtoImpl
- {
- friend class WhatsAppProto;
- WhatsAppProto &m_proto;
-
- CTimer m_keepAlive, m_resyncApp;
- void OnKeepAlive(CTimer *)
- { m_proto.SendKeepAlive();
- }
- void OnResync(CTimer *pTimer)
- {
- pTimer->Stop();
- m_proto.ResyncAll();
- }
-
- CWhatsAppProtoImpl(WhatsAppProto &pro) :
- m_proto(pro),
- m_keepAlive(Miranda_GetSystemWindow(), UINT_PTR(this)),
- m_resyncApp(Miranda_GetSystemWindow(), UINT_PTR(this)+1)
- {
- m_keepAlive.OnEvent = Callback(this, &CWhatsAppProtoImpl::OnKeepAlive);
- m_resyncApp.OnEvent = Callback(this, &CWhatsAppProtoImpl::OnResync);
- }
- } m_impl;
-
- bool m_bTerminated, m_bRespawn, m_bUpdatedPrekeys, m_bUnregister;
- ptrW m_tszDefaultGroup;
-
- CMStringA m_szJid;
- CMStringW m_tszAvatarFolder;
-
- EVP_PKEY *m_pKeys; // private & public keys
- WANoise *m_noise;
-
- void UploadMorePrekeys();
-
- // App state management
- OBJLIST<WACollection> m_arCollections;
-
- void InitSync(void);
- void ApplyPatch(const JSONNode &index, const Wa__SyncActionValue *data);
- void ParsePatch(WACollection *pColl, const Wa__SyncdRecord *rec, bool bSet);
- void ProcessHistorySync(const Wa__HistorySync *pSync);
- void ResyncServer(const OBJLIST<WACollection> &task);
- void ResyncAll(void);
-
- __forceinline WACollection *FindCollection(const char *pszName)
- { return m_arCollections.find((WACollection *)&pszName);
- }
-
- // Contacts management /////////////////////////////////////////////////////////////////
-
- mir_cs m_csUsers;
- OBJLIST<WAUser> m_arUsers;
-
- mir_cs m_csOwnMessages;
- OBJLIST<WAOwnMessage> m_arOwnMsgs;
-
- WAUser* FindUser(const char *szId);
- WAUser* AddUser(const char *szId, bool bTemporary);
-
- // Group chats /////////////////////////////////////////////////////////////////////////
-
- void GC_RefreshMetadata();
- void GC_GetAllMetadata();
- void GC_ParseMetadata(const WANode *pGroup);
-
- int __cdecl GcEventHook(WPARAM, LPARAM);
- int __cdecl GcMenuHook(WPARAM, LPARAM);
-
- // UI //////////////////////////////////////////////////////////////////////////////////
-
- void CloseQrDialog();
- bool ShowQrCode(const CMStringA &ref);
-
- /// Network ////////////////////////////////////////////////////////////////////////////
-
- time_t m_lastRecvTime;
- HNETLIBCONN m_hServerConn;
-
- mir_cs m_csPacketQueue;
- OBJLIST<WARequestBase> m_arPacketQueue;
-
- LIST<WAPersistentHandler> m_arPersistent;
- WA_PKT_HANDLER FindPersistentHandler(const WANode &node);
-
- int m_iPacketId;
- uint16_t m_wMsgPrefix[2];
- CMStringA GenerateMessageId();
- CMStringA GetMessageText(const Wa__Message *pMessage);
- void GetMessageContent(CMStringA &txt, const char *szType, const char *szUrl, const char *szMimetype, const char *szDirectPath, const ProtobufCBinaryData &szMediaKey, const char *szCaption = nullptr);
- void ProcessMessage(WAMSG type, const Wa__WebMessageInfo &msg);
- bool CreateMsgParticipant(WANode *pParticipants, const WAJid &jid, const MBinBuffer &orig);
-
- void ProcessReceipt(MCONTACT hContact, const char *msgId, bool bRead);
-
- bool WSReadPacket(const WSHeader &hdr, MBinBuffer &buf);
- int WSSend(const ProtobufCMessage &msg);
- int WSSendNode(WANode &node);
- int WSSendNode(WANode &node, WA_PKT_HANDLER);
- int WSSendNode(WANode &node, WA_PKT_HANDLER_FULL, void *pUserInfo);
-
- MBinBuffer DownloadEncryptedFile(const char *url, const ProtobufCBinaryData &mediaKeys, const char *pszType);
- CMStringW GetTmpFileName(const char *pszClass, const char *addition);
-
- void OnLoggedIn(void);
- void OnLoggedOut(void);
- void ProcessFailure(int code);
- void ServerThreadWorker(void);
- void ShutdownSession(void);
-
- void SendAck(const WANode &node);
- void SendReceipt(const char *pszTo, const char *pszParticipant, const char *pszId, const char *pszType);
- void SendKeepAlive(void);
- int SendTextMessage(const char *jid, const char *pszMsg);
- void SendUnregister(void);
- void SendUsync(const LIST<char> &jids, void *pUserInfo);
- void SetServerStatus(int iStatus);
-
- void FinishTask(WASendTask *pTask);
- void SendTask(WASendTask *pTask);
-
- /// Popups /////////////////////////////////////////////////////////////////////////////
-
- HANDLE m_hPopupClass;
- CMOption<bool> m_bUsePopups;
-
- void InitPopups(void);
- void Popup(MCONTACT hContact, const wchar_t *szMsg, const wchar_t *szTitle);
-
- /// Request handlers ///////////////////////////////////////////////////////////////////
-
- void OnProcessHandshake(const uint8_t *pData, int cbLen);
-
- void InitPersistentHandlers();
- void OnAccountSync(const WANode &node);
- void OnIqBlockList(const WANode &node);
- void OnIqCountPrekeys(const WANode &node);
- void OnIqDoNothing(const WANode &node);
- void OnIqGcGetAllMetadata(const WANode &node);
- void OnIqGetAvatar(const WANode &node);
- void OnIqGetKeys(const WANode &node, void *pUserInfo);
- void OnIqGetUsync(const WANode &node, void *pUserInfo);
- void OnIqPairDevice(const WANode &node);
- void OnIqPairSuccess(const WANode &node);
- void OnIqResult(const WANode &node);
- void OnIqServerSync(const WANode &node);
- void OnNotifyAny(const WANode &node);
- void OnNotifyDevices(const WANode &node);
- void OnNotifyEncrypt(const WANode &node);
- void OnNotifyPicture(const WANode &node);
- void OnReceiveAck(const WANode &node);
- void OnReceiveChatState(const WANode &node);
- void OnReceiveFailure(const WANode &node);
- void OnReceiveInfo(const WANode &node);
- void OnReceiveMessage(const WANode &node);
- void OnReceiveReceipt(const WANode &node);
- void OnServerSync(const WANode &node);
- void OnStreamError(const WANode &node);
- void OnSuccess(const WANode &node);
-
- // Signal
- MSignalStore m_signalStore;
-
- // Binary packets
- void ProcessBinaryPacket(const uint8_t *pData, size_t cbLen);
-
- // unzip operations
- MBinBuffer unzip(const MBinBuffer &src);
-
- /// Avatars ////////////////////////////////////////////////////////////////////////////
- CMStringW GetAvatarFileName(MCONTACT hContact);
- void ServerFetchAvatar(const char *jid);
-
- INT_PTR __cdecl GetAvatarInfo(WPARAM, LPARAM);
- INT_PTR __cdecl GetAvatarCaps(WPARAM, LPARAM);
- INT_PTR __cdecl GetMyAvatar(WPARAM, LPARAM);
- INT_PTR __cdecl SetMyAvatar(WPARAM, LPARAM);
-
-public:
- WhatsAppProto(const char *proto_name, const wchar_t *username);
- ~WhatsAppProto();
-
- __forceinline bool isOnline() const
- { return m_hServerConn != 0;
- }
-
- __forceinline void writeStr(const char *pszSetting, const JSONNode &node)
- {
- CMStringW str(node.as_mstring());
- if (!str.IsEmpty())
- setWString(pszSetting, str);
- }
-
- class CWhatsAppQRDlg *m_pQRDlg;
-
- // PROTO_INTERFACE /////////////////////////////////////////////////////////////////////
-
- MCONTACT AddToList(int flags, PROTOSEARCHRESULT *psr) override;
- INT_PTR GetCaps(int type, MCONTACT hContact = NULL) override;
- HANDLE SearchBasic(const wchar_t* id) override;
- int SendMsg(MCONTACT hContact, int flags, const char* msg) override;
- int SetStatus(int iNewStatus) override;
- int UserIsTyping(MCONTACT hContact, int type) override;
-
- void OnErase() override;
- void OnModulesLoaded() override;
-
- // Services ////////////////////////////////////////////////////////////////////////////
-
- INT_PTR __cdecl SvcCreateAccMgrUI(WPARAM, LPARAM);
-
- // Events //////////////////////////////////////////////////////////////////////////////
-
- int __cdecl OnOptionsInit(WPARAM, LPARAM);
- int __cdecl OnBuildStatusMenu(WPARAM, LPARAM);
-
- // Options /////////////////////////////////////////////////////////////////////////////
-
- CMOption<wchar_t*> m_wszNick; // your nick name in presence
- CMOption<wchar_t*> m_wszDeviceName; // how do you see Miranda in mobile phone
- CMOption<wchar_t*> m_wszDefaultGroup; // clist group to store contacts
- CMOption<bool> m_bHideGroupchats; // do not open chat windows on creation
- CMOption<bool> m_bUseBbcodes; // use extended markup for messages
-
- // Processing Threads //////////////////////////////////////////////////////////////////
-
- void __cdecl SearchAckThread(void*);
- void __cdecl ServerThread(void*);
-};
-
-struct CMPlugin : public ACCPROTOPLUGIN<WhatsAppProto>
-{
- HNETLIBUSER hAvatarUser = nullptr;
- HNETLIBCONN hAvatarConn = nullptr;
- bool SaveFile(const char *pszUrl, PROTO_AVATAR_INFORMATION &ai);
-
- bool bHasMessageState = false;
-
- CMPlugin();
-
- int Load() override;
- int Unload() override;
-};
-
-#endif
+/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#if !defined(PROTO_H)
+#define PROTO_H
+
+#define S_WHATSAPP_NET "@s.whatsapp.net"
+#define APP_VERSION "2.2230.15"
+#define KEY_BUNDLE_TYPE "\x05"
+
+class WhatsAppProto;
+typedef void (WhatsAppProto:: *WA_PKT_HANDLER)(const WANode &node);
+typedef void (WhatsAppProto:: *WA_PKT_HANDLER_FULL)(const WANode &node, void *pUserInfo);
+
+struct WAMSG
+{
+ union {
+ uint32_t dwFlags = 0;
+ struct {
+ bool bPrivateChat : 1;
+ bool bGroupChat : 1;
+ bool bDirectStatus : 1;
+ bool bOtherStatus : 1;
+ bool bPeerBroadcast : 1;
+ bool bOtherBroadcast : 1;
+ bool bOffline : 1;
+ };
+ };
+};
+
+struct WAMediaKeys
+{
+ WAMediaKeys(const uint8_t *pKey, size_t keyLen, const char *pszMediaType);
+
+ uint8_t iv[16];
+ uint8_t cipherKey[32];
+ uint8_t macKey[64];
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// own requests
+
+struct WARequestBase
+{
+ WARequestBase(const CMStringA &_1) :
+ szPacketId(_1)
+ {}
+ virtual ~WARequestBase() {}
+
+ CMStringA szPacketId;
+
+ virtual void Execute(WhatsAppProto *ppro, const WANode &node) = 0;
+};
+
+class WARequestSimple : public WARequestBase
+{
+ WA_PKT_HANDLER pHandler;
+
+public:
+ WARequestSimple(const CMStringA &_1, WA_PKT_HANDLER _2) :
+ WARequestBase(_1),
+ pHandler(_2)
+ {}
+
+ void Execute(WhatsAppProto *ppro, const WANode &node) override
+ {
+ (ppro->*pHandler)(node);
+ }
+};
+
+class WARequestParam : public WARequestBase
+{
+ WA_PKT_HANDLER_FULL pHandler;
+ void *pUserInfo;
+
+public:
+ WARequestParam(const CMStringA &_1, WA_PKT_HANDLER_FULL _2, void *_3) :
+ WARequestBase(_1),
+ pHandler(_2),
+ pUserInfo(_3)
+ {}
+
+ void Execute(WhatsAppProto *ppro, const WANode &node) override
+ {
+ (ppro->*pHandler)(node, pUserInfo);
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+struct WAPersistentHandler
+{
+ WAPersistentHandler(const char *_1, const char *_2, const char *_3, const char *_4, WA_PKT_HANDLER _5) :
+ pszTitle(_1), pszType(_2), pszXmlns(_3), pszChild(_4), pHandler(_5)
+ {}
+
+ const char *pszTitle, *pszType, *pszXmlns, *pszChild;
+ WA_PKT_HANDLER pHandler;
+};
+
+struct WAUser
+{
+ WAUser(MCONTACT _1, const char *_2, bool _3 = false) :
+ hContact(_1),
+ szId(mir_strdup(_2)),
+ bIsGroupChat(_3),
+ arDevices(1)
+ {
+ }
+
+ ~WAUser()
+ {
+ mir_free(szId);
+ }
+
+ MCONTACT hContact;
+ DWORD dwModifyTag = 0;
+ char *szId;
+ bool bInited = false, bIsGroupChat, bDeviceInit = false;
+ SESSION_INFO *si = 0;
+ OBJLIST<WAJid> arDevices;
+ time_t m_timer1 = 0, m_timer2 = 0;
+};
+
+struct WAOwnMessage
+{
+ WAOwnMessage(int _1, const char *_2, const char *_3) :
+ pktId(_1),
+ szJid(_2),
+ szMessageId(_3)
+ {}
+
+ int pktId;
+ CMStringA szJid, szMessageId;
+};
+
+struct WACollection
+{
+ WACollection(const char *_1, int _2 = 0) :
+ szName(mir_strdup(_1)),
+ version(_2)
+ {}
+
+ ptrA szName;
+ int version;
+
+ LT_HASH hash;
+ std::map<std::string, std::string> indexValueMap;
+};
+
+class WANoise
+{
+ friend class WhatsAppProto;
+
+ WhatsAppProto *ppro;
+ uint32_t readCounter = 0, writeCounter = 0;
+ bool bInitFinished = false, bSendIntro = false;
+ MBinBuffer salt, encKey, decKey;
+ uint8_t hash[32];
+
+ struct {
+ MBinBuffer priv, pub;
+ } noiseKeys, ephemeral;
+
+ void deriveKey(const void *pData, size_t cbLen, MBinBuffer &write, MBinBuffer &read);
+ void mixIntoKey(const void *n, const void *p);
+ void updateHash(const void *pData, size_t cbLen);
+
+public:
+ WANoise(WhatsAppProto *_ppro);
+
+ void finish();
+ void init();
+
+ MBinBuffer decrypt(const void *pData, size_t cbLen);
+ MBinBuffer encrypt(const void *pData, size_t cbLen);
+
+ size_t decodeFrame(const uint8_t *&pData, size_t &cbLen);
+ MBinBuffer encodeFrame(const void *pData, size_t cbLen);
+};
+
+class MSignalSession : public MZeroedObject
+{
+ friend class MSignalStore;
+ signal_protocol_address address;
+ session_cipher *cipher = nullptr;
+
+public:
+ CMStringA szName;
+ MBinBuffer sessionData;
+
+ MSignalSession(const CMStringA &_1, int _2);
+ ~MSignalSession();
+
+ bool hasAddress(const char *name, size_t name_len) const;
+
+ __forceinline session_cipher* getCipher(void) const { return cipher; }
+ __forceinline int getDeviceId() const { return address.device_id; }
+ CMStringA getSetting() const;
+};
+
+class MSignalStore
+{
+ void init();
+
+ signal_context *m_pContext;
+ signal_protocol_store_context *m_pStore;
+
+ void importPublicKey(ec_public_key **result, MBinBuffer &buf);
+
+public:
+ PROTO_INTERFACE *pProto;
+ const char *prefix;
+
+ OBJLIST<MSignalSession> arSessions;
+
+ struct
+ {
+ MBinBuffer priv, pub;
+ }
+ signedIdentity;
+
+ struct
+ {
+ MBinBuffer priv, pub, signature;
+ uint32_t keyid;
+ }
+ preKey;
+
+ MSignalStore(PROTO_INTERFACE *_1, const char *_2);
+ ~MSignalStore();
+
+ __forceinline signal_context *CTX() const { return m_pContext; }
+
+ MSignalSession* createSession(const CMStringA &szName, int deviceId);
+ MSignalSession* getSession(const signal_protocol_address *address);
+ void injectSession(const char *szJid, const WANode *pNode, const WANode *pKey);
+
+ MBinBuffer decryptSignalProto(const CMStringA &from, const char *pszType, const MBinBuffer &encrypted);
+ MBinBuffer decryptGroupSignalProto(const CMStringA &from, const CMStringA &author, const MBinBuffer &encrypted);
+
+ MBinBuffer encryptSenderKey(const WAJid &to, const CMStringA &from, const MBinBuffer &buf, MBinBuffer &skmsgKey);
+ MBinBuffer encryptSignalProto(const WAJid &to, const MBinBuffer &buf, int &type);
+
+ MBinBuffer encodeSignedIdentity(bool);
+ void generatePrekeys(int count);
+
+ void logError(int code, const char *szMessage);
+
+ void processSenderKeyMessage(const CMStringA &author, const Wa__Message__SenderKeyDistributionMessage *msg);
+};
+
+class WhatsAppProto : public PROTO<WhatsAppProto>
+{
+ friend class WANoise;
+ friend class CWhatsAppQRDlg;
+ friend class COptionsDlg;
+
+ class CWhatsAppProtoImpl
+ {
+ friend class WhatsAppProto;
+ WhatsAppProto &m_proto;
+
+ CTimer m_keepAlive, m_resyncApp;
+ void OnKeepAlive(CTimer *)
+ { m_proto.SendKeepAlive();
+ }
+ void OnResync(CTimer *pTimer)
+ {
+ pTimer->Stop();
+ m_proto.ResyncAll();
+ }
+
+ CWhatsAppProtoImpl(WhatsAppProto &pro) :
+ m_proto(pro),
+ m_keepAlive(Miranda_GetSystemWindow(), UINT_PTR(this)),
+ m_resyncApp(Miranda_GetSystemWindow(), UINT_PTR(this)+1)
+ {
+ m_keepAlive.OnEvent = Callback(this, &CWhatsAppProtoImpl::OnKeepAlive);
+ m_resyncApp.OnEvent = Callback(this, &CWhatsAppProtoImpl::OnResync);
+ }
+ } m_impl;
+
+ bool m_bTerminated, m_bRespawn, m_bUpdatedPrekeys, m_bUnregister;
+ ptrW m_tszDefaultGroup;
+
+ CMStringA m_szJid;
+ CMStringW m_tszAvatarFolder;
+
+ EVP_PKEY *m_pKeys; // private & public keys
+ WANoise *m_noise;
+
+ void UploadMorePrekeys();
+
+ // App state management
+ OBJLIST<WACollection> m_arCollections;
+
+ void InitSync(void);
+ void ApplyPatch(const JSONNode &index, const Wa__SyncActionValue *data);
+ void ParsePatch(WACollection *pColl, const Wa__SyncdRecord *rec, bool bSet);
+ void ProcessHistorySync(const Wa__HistorySync *pSync);
+ void ResyncServer(const OBJLIST<WACollection> &task);
+ void ResyncAll(void);
+
+ __forceinline WACollection *FindCollection(const char *pszName)
+ { return m_arCollections.find((WACollection *)&pszName);
+ }
+
+ // Contacts management /////////////////////////////////////////////////////////////////
+
+ mir_cs m_csUsers;
+ OBJLIST<WAUser> m_arUsers;
+
+ mir_cs m_csOwnMessages;
+ OBJLIST<WAOwnMessage> m_arOwnMsgs;
+
+ WAUser* FindUser(const char *szId);
+ WAUser* AddUser(const char *szId, bool bTemporary);
+
+ // Group chats /////////////////////////////////////////////////////////////////////////
+
+ void GC_RefreshMetadata();
+ void GC_GetAllMetadata();
+ void GC_ParseMetadata(const WANode *pGroup);
+
+ int __cdecl GcEventHook(WPARAM, LPARAM);
+ int __cdecl GcMenuHook(WPARAM, LPARAM);
+
+ // UI //////////////////////////////////////////////////////////////////////////////////
+
+ void CloseQrDialog();
+ bool ShowQrCode(const CMStringA &ref);
+
+ /// Network ////////////////////////////////////////////////////////////////////////////
+
+ time_t m_lastRecvTime;
+ HNETLIBCONN m_hServerConn;
+
+ mir_cs m_csPacketQueue;
+ OBJLIST<WARequestBase> m_arPacketQueue;
+
+ LIST<WAPersistentHandler> m_arPersistent;
+ WA_PKT_HANDLER FindPersistentHandler(const WANode &node);
+
+ int m_iPacketId;
+ uint16_t m_wMsgPrefix[2];
+ CMStringA GenerateMessageId();
+ CMStringA GetMessageText(const Wa__Message *pMessage);
+ void GetMessageContent(CMStringA &txt, const char *szType, const char *szUrl, const char *szMimetype, const char *szDirectPath, const ProtobufCBinaryData &szMediaKey, const char *szCaption = nullptr);
+ void ProcessMessage(WAMSG type, const Wa__WebMessageInfo &msg);
+ bool CreateMsgParticipant(WANode *pParticipants, const WAJid &jid, const MBinBuffer &orig);
+
+ void ProcessReceipt(MCONTACT hContact, const char *msgId, bool bRead);
+
+ bool WSReadPacket(const WSHeader &hdr, MBinBuffer &buf);
+ int WSSend(const ProtobufCMessage &msg);
+ int WSSendNode(WANode &node);
+ int WSSendNode(WANode &node, WA_PKT_HANDLER);
+ int WSSendNode(WANode &node, WA_PKT_HANDLER_FULL, void *pUserInfo);
+
+ MBinBuffer DownloadEncryptedFile(const char *url, const ProtobufCBinaryData &mediaKeys, const char *pszType);
+ CMStringW GetTmpFileName(const char *pszClass, const char *addition);
+
+ void OnLoggedIn(void);
+ void OnLoggedOut(void);
+ void ProcessFailure(int code);
+ void ServerThreadWorker(void);
+ void ShutdownSession(void);
+
+ void SendAck(const WANode &node);
+ void SendReceipt(const char *pszTo, const char *pszParticipant, const char *pszId, const char *pszType);
+ void SendKeepAlive(void);
+ int SendTextMessage(const char *jid, const char *pszMsg);
+ void SendUnregister(void);
+ void SendUsync(const LIST<char> &jids, void *pUserInfo);
+ void SetServerStatus(int iStatus);
+
+ void FinishTask(WASendTask *pTask);
+ void SendTask(WASendTask *pTask);
+
+ /// Popups /////////////////////////////////////////////////////////////////////////////
+
+ HANDLE m_hPopupClass;
+ CMOption<bool> m_bUsePopups;
+
+ void InitPopups(void);
+ void Popup(MCONTACT hContact, const wchar_t *szMsg, const wchar_t *szTitle);
+
+ /// Request handlers ///////////////////////////////////////////////////////////////////
+
+ void OnProcessHandshake(const uint8_t *pData, int cbLen);
+
+ void InitPersistentHandlers();
+ void OnAccountSync(const WANode &node);
+ void OnIqBlockList(const WANode &node);
+ void OnIqCountPrekeys(const WANode &node);
+ void OnIqDoNothing(const WANode &node);
+ void OnIqGcGetAllMetadata(const WANode &node);
+ void OnIqGetAvatar(const WANode &node);
+ void OnIqGetKeys(const WANode &node, void *pUserInfo);
+ void OnIqGetUsync(const WANode &node, void *pUserInfo);
+ void OnIqPairDevice(const WANode &node);
+ void OnIqPairSuccess(const WANode &node);
+ void OnIqResult(const WANode &node);
+ void OnIqServerSync(const WANode &node);
+ void OnNotifyAny(const WANode &node);
+ void OnNotifyDevices(const WANode &node);
+ void OnNotifyEncrypt(const WANode &node);
+ void OnNotifyPicture(const WANode &node);
+ void OnReceiveAck(const WANode &node);
+ void OnReceiveChatState(const WANode &node);
+ void OnReceiveFailure(const WANode &node);
+ void OnReceiveInfo(const WANode &node);
+ void OnReceiveMessage(const WANode &node);
+ void OnReceiveReceipt(const WANode &node);
+ void OnServerSync(const WANode &node);
+ void OnStreamError(const WANode &node);
+ void OnSuccess(const WANode &node);
+
+ // Signal
+ MSignalStore m_signalStore;
+
+ // Binary packets
+ void ProcessBinaryPacket(const uint8_t *pData, size_t cbLen);
+
+ // unzip operations
+ MBinBuffer unzip(const MBinBuffer &src);
+
+ /// Avatars ////////////////////////////////////////////////////////////////////////////
+ CMStringW GetAvatarFileName(MCONTACT hContact);
+ void ServerFetchAvatar(const char *jid);
+
+ INT_PTR __cdecl GetAvatarInfo(WPARAM, LPARAM);
+ INT_PTR __cdecl GetAvatarCaps(WPARAM, LPARAM);
+ INT_PTR __cdecl GetMyAvatar(WPARAM, LPARAM);
+ INT_PTR __cdecl SetMyAvatar(WPARAM, LPARAM);
+
+public:
+ WhatsAppProto(const char *proto_name, const wchar_t *username);
+ ~WhatsAppProto();
+
+ __forceinline bool isOnline() const
+ { return m_hServerConn != 0;
+ }
+
+ __forceinline void writeStr(const char *pszSetting, const JSONNode &node)
+ {
+ CMStringW str(node.as_mstring());
+ if (!str.IsEmpty())
+ setWString(pszSetting, str);
+ }
+
+ class CWhatsAppQRDlg *m_pQRDlg;
+
+ // PROTO_INTERFACE /////////////////////////////////////////////////////////////////////
+
+ MCONTACT AddToList(int flags, PROTOSEARCHRESULT *psr) override;
+ INT_PTR GetCaps(int type, MCONTACT hContact = NULL) override;
+ HANDLE SearchBasic(const wchar_t* id) override;
+ int SendMsg(MCONTACT hContact, int flags, const char* msg) override;
+ int SetStatus(int iNewStatus) override;
+ int UserIsTyping(MCONTACT hContact, int type) override;
+
+ void OnErase() override;
+ void OnModulesLoaded() override;
+
+ // Services ////////////////////////////////////////////////////////////////////////////
+
+ INT_PTR __cdecl SvcCreateAccMgrUI(WPARAM, LPARAM);
+
+ // Events //////////////////////////////////////////////////////////////////////////////
+
+ int __cdecl OnOptionsInit(WPARAM, LPARAM);
+ int __cdecl OnBuildStatusMenu(WPARAM, LPARAM);
+
+ // Options /////////////////////////////////////////////////////////////////////////////
+
+ CMOption<wchar_t*> m_wszNick; // your nick name in presence
+ CMOption<wchar_t*> m_wszDeviceName; // how do you see Miranda in mobile phone
+ CMOption<wchar_t*> m_wszDefaultGroup; // clist group to store contacts
+ CMOption<bool> m_bHideGroupchats; // do not open chat windows on creation
+ CMOption<bool> m_bUseBbcodes; // use extended markup for messages
+
+ // Processing Threads //////////////////////////////////////////////////////////////////
+
+ void __cdecl SearchAckThread(void*);
+ void __cdecl ServerThread(void*);
+};
+
+struct CMPlugin : public ACCPROTOPLUGIN<WhatsAppProto>
+{
+ HNETLIBUSER hAvatarUser = nullptr;
+ HNETLIBCONN hAvatarConn = nullptr;
+ bool SaveFile(const char *pszUrl, PROTO_AVATAR_INFORMATION &ai);
+
+ bool bHasMessageState = false;
+
+ CMPlugin();
+
+ int Load() override;
+ int Unload() override;
+};
+
+#endif
diff --git a/protocols/WhatsApp/src/server.cpp b/protocols/WhatsApp/src/server.cpp
index 7512b05caf..24d64e5aa7 100644
--- a/protocols/WhatsApp/src/server.cpp
+++ b/protocols/WhatsApp/src/server.cpp
@@ -1,418 +1,418 @@
-/*
-
-WhatsApp plugin for Miranda NG
-Copyright © 2019-23 George Hazan
-
-*/
-
-#include "stdafx.h"
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// gateway worker thread
-
-void WhatsAppProto::ServerThread(void *)
-{
- do {
- m_bRespawn = m_bUnregister = false;
- ServerThreadWorker();
- }
- while (m_bRespawn);
-
- OnLoggedOut();
-}
-
-void WhatsAppProto::ServerThreadWorker()
-{
- // connect websocket
- NETLIBHTTPHEADER hdrs[] =
- {
- { "Origin", "https://web.whatsapp.com" },
- { 0, 0 }
- };
-
- NLHR_PTR pReply(WebSocket_Connect(m_hNetlibUser, "web.whatsapp.com/ws/chat", hdrs));
- if (pReply == nullptr) {
- debugLogA("Server connection failed, exiting");
- return;
- }
-
- if (pReply->resultCode != 101)
- return;
-
- delete m_noise;
- m_noise = new WANoise(this);
- m_noise->init();
-
- debugLogA("Server connection succeeded");
- m_hServerConn = pReply->nlc;
- m_lastRecvTime = time(0);
- m_iPacketId = 1;
-
- Utils_GetRandom(m_wMsgPrefix, sizeof(m_wMsgPrefix));
-
- Wa__HandshakeMessage__ClientHello client;
- client.ephemeral = {m_noise->ephemeral.pub.length(), m_noise->ephemeral.pub.data()};
- client.has_ephemeral = true;
-
- Wa__HandshakeMessage msg;
- msg.clienthello = &client;
- WSSend(msg);
-
- MBinBuffer netbuf;
-
- for (m_bTerminated = false; !m_bTerminated;) {
- unsigned char buf[2048];
- int bufSize = Netlib_Recv(m_hServerConn, (char *)buf, _countof(buf), MSG_NODUMP);
- if (bufSize == 0) {
- debugLogA("Gateway connection gracefully closed");
- break;
- }
- if (bufSize < 0) {
- debugLogA("Gateway connection error, exiting");
- break;
- }
-
- netbuf.append(buf, bufSize);
-
- WSHeader hdr;
- if (!WebSocket_InitHeader(hdr, netbuf.data(), netbuf.length()))
- continue;
-
- // we lack some data, let's read them
- if (netbuf.length() < hdr.headerSize + hdr.payloadSize)
- if (!WSReadPacket(hdr, netbuf))
- break;
-
- // debugLogA("Got 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);
- // Netlib_Dump(m_hServerConn, netbuf.data(), netbuf.length(), false, 0);
-
- m_lastRecvTime = time(0);
-
- // read all payloads from the current buffer, one by one
- while (true) {
- MBinBuffer currPacket;
- currPacket.assign(netbuf.data() + hdr.headerSize, hdr.payloadSize);
-
- switch (hdr.opCode) {
- case 1: // json packet
- debugLogA("Text packet, skipping");
- /*
- currPacket.append("", 1); // add 0 to use strchr safely
- CMStringA szJson(pos, (int)dataSize);
-
- JSONNode root = JSONNode::parse(szJson);
- if (root) {
- debugLogA("JSON received:\n%s", start);
-
- CMStringA szPrefix(start, int(pos - start - 1));
- auto *pReq = m_arPacketQueue.find((WARequest *)&szPrefix);
- if (pReq != nullptr) {
- root << CHAR_PARAM("$id$", szPrefix);
- }
- }
- }
- */
- break;
-
- case 2: // binary packet
- if (hdr.payloadSize > 32)
- ProcessBinaryPacket(currPacket.data(), hdr.payloadSize);
- break;
-
- case 8: // close
- debugLogA("server required to exit");
- m_bRespawn = m_bTerminated = true; // simply reconnect, don't exit
- break;
-
- default:
- Netlib_Dump(m_hServerConn, currPacket.data(), hdr.payloadSize, false, 0);
- }
-
- netbuf.remove(hdr.headerSize + hdr.payloadSize);
- // debugLogA("%d bytes removed from network buffer, %d bytes remain", hdr.headerSize + hdr.payloadSize, netbuf.length());
- if (netbuf.length() == 0)
- break;
-
- // if we have not enough data for header, continue reading
- if (!WebSocket_InitHeader(hdr, netbuf.data(), netbuf.length())) {
- debugLogA("not enough data for header, continue reading");
- break;
- }
-
- // if we have not enough data for data, continue reading
- if (hdr.headerSize + hdr.payloadSize > netbuf.length()) {
- debugLogA("not enough place for data (%d+%d > %d), continue reading", 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);
- }
- }
-
- debugLogA("Server connection dropped");
- Netlib_CloseHandle(m_hServerConn);
- m_hServerConn = nullptr;
-}
-
-bool WhatsAppProto::WSReadPacket(const WSHeader &hdr, MBinBuffer &res)
-{
- size_t currPacketSize = res.length() - hdr.headerSize;
-
- char buf[1024];
- while (currPacketSize < hdr.payloadSize) {
- int result = Netlib_Recv(m_hServerConn, buf, _countof(buf), MSG_NODUMP);
- if (result == 0) {
- debugLogA("Gateway connection gracefully closed");
- return false;
- }
- if (result < 0) {
- debugLogA("Gateway connection error, exiting");
- return false;
- }
-
- currPacketSize += result;
- res.append(buf, result);
- }
- return true;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// Binary data processing
-
-void WhatsAppProto::ProcessBinaryPacket(const uint8_t *pData, size_t cbDataLen)
-{
- while (size_t payloadLen = m_noise->decodeFrame(pData, cbDataLen)) {
- if (m_noise->bInitFinished) {
- MBinBuffer buf = m_noise->decrypt(pData, payloadLen);
-
- WAReader rdr(buf.data(), buf.length());
- auto b = rdr.readInt8();
- if (b & 2) {
- buf.remove(1);
- buf = unzip(buf);
- rdr = WAReader(buf.data(), buf.length());
- }
-
- if (WANode *pNode = rdr.readNode()) {
- CMStringA szText;
- pNode->print(szText);
- debugLogA("Got binary node:\n%s", szText.c_str());
-
- auto pHandler = FindPersistentHandler(*pNode);
- if (pHandler)
- (this->*pHandler)(*pNode);
- else
- debugLogA("cannot handle incoming message");
-
- delete pNode;
- }
- else {
- debugLogA("wrong or broken payload");
- Netlib_Dump(m_hServerConn, pData, cbDataLen, false, 0);
- }
- }
- else OnProcessHandshake(pData, (int)payloadLen);
-
- pData = (BYTE*)pData + payloadLen;
- cbDataLen -= payloadLen;
- }
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::ProcessFailure(int code)
-{
- switch (code) {
- case 401:
- debugLogA("Connection logged out from another device, exiting");
- Popup(0, TranslateT("This account was logged out from mobile phone, you need to link it again"), m_tszUserName);
-
- OnErase();
- break;
-
- case 408:
- debugLogA("Connection lost, exiting");
- break;
-
- case 411:
- debugLogA("Conflict between two devices, exiting");
- break;
-
- case 428:
- debugLogA("Connection forcibly closed by the server, exiting");
- break;
-
- case 440:
- debugLogA("Connection replaced from another device, exiting");
- break;
-
- case 515:
- debugLogA("Server required to restart immediately, leaving thread");
- m_bRespawn = true;
- break;
- }
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::OnLoggedIn()
-{
- debugLogA("WhatsAppProto::OnLoggedIn");
-
- if (m_bUnregister) {
- SendUnregister();
- m_bTerminated = true;
- return;
- }
-
- SetServerStatus(m_iDesiredStatus);
-
- ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, m_iDesiredStatus);
- m_iStatus = m_iDesiredStatus;
- m_bUpdatedPrekeys = false;
-
- m_impl.m_keepAlive.Start(1000);
-
- // retrieve initial info
- WANodeIq abt(IQ::GET, "abt");
- abt.addChild("props")->addAttr("protocol", "1");
- WSSendNode(abt, &WhatsAppProto::OnIqDoNothing);
-
- WSSendNode(
- WANodeIq(IQ::GET, "w") << XCHILD("props"),
- &WhatsAppProto::OnIqDoNothing);
-
- WSSendNode(
- WANodeIq(IQ::GET, "blocklist"),
- &WhatsAppProto::OnIqBlockList);
-
- WSSendNode(
- WANodeIq(IQ::GET, "privacy") << XCHILD("privacy"),
- &WhatsAppProto::OnIqDoNothing);
-
- GC_RefreshMetadata();
-}
-
-void WhatsAppProto::OnLoggedOut(void)
-{
- m_impl.m_keepAlive.Stop();
-
- debugLogA("WhatsAppProto::OnLoggedOut");
- m_bTerminated = true;
-
- ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, ID_STATUS_OFFLINE);
- m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE;
-
- setAllContactStatuses(ID_STATUS_OFFLINE, false);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// Service packets sending
-
-void WhatsAppProto::SendAck(const WANode &node)
-{
- WANode ack("ack");
- ack << CHAR_PARAM("to", node.getAttr("from")) << CHAR_PARAM("id", node.getAttr("id")) << CHAR_PARAM("class", node.title);
- if (node.title != "message")
- if (auto *param = node.getAttr("type"))
- ack << CHAR_PARAM("type", param);
- if (auto *param = node.getAttr("participant"))
- ack << CHAR_PARAM("participant", param);
- if (auto *param = node.getAttr("recipient"))
- ack << CHAR_PARAM("recipient", param);
- WSSendNode(ack);
-}
-
-void WhatsAppProto::SendKeepAlive()
-{
- time_t now = time(0);
- if (now - m_lastRecvTime > 20) {
- WSSendNode(WANodeIq(IQ::GET, "w:p") << XCHILD("ping"), &WhatsAppProto::OnIqDoNothing);
-
- m_lastRecvTime = now;
- }
-
- for (auto &it : m_arUsers) {
- if (it->m_timer1 && now - it->m_timer1 > 600) {
- it->m_timer1 = 0;
- it->m_timer2 = now;
- setWord(it->hContact, "Status", ID_STATUS_AWAY);
- }
- else if (it->m_timer2 && now - it->m_timer2 > 600) {
- it->m_timer2 = 0;
- setWord(it->hContact, "Status", ID_STATUS_OFFLINE);
- }
- }
-}
-
-void WhatsAppProto::SendReceipt(const char *pszTo, const char *pszParticipant, const char *pszId, const char *pszType)
-{
- WANode receipt("receipt");
- receipt << CHAR_PARAM("id", pszId);
-
- if (!mir_strcmp(pszType, "read") || !mir_strcmp(pszType, "read-self"))
- receipt << INT_PARAM("t", time(0));
-
- if (!mir_strcmp(pszType, "sender") && WAJid(pszTo).isUser())
- receipt << CHAR_PARAM("to", pszParticipant) << CHAR_PARAM("recipient", pszTo);
- else {
- receipt << CHAR_PARAM("to", pszTo);
- if (pszParticipant)
- receipt << CHAR_PARAM("participant", pszParticipant);
- }
-
- if (pszType)
- receipt << CHAR_PARAM("type", pszType);
- WSSendNode(receipt);
-}
-
-void WhatsAppProto::SetServerStatus(int iStatus)
-{
- if (mir_wstrlen(m_wszNick))
- WSSendNode(
- WANode("presence") << CHAR_PARAM("name", T2Utf(m_wszNick)) << CHAR_PARAM("type", (iStatus == ID_STATUS_ONLINE) ? "available" : "unavailable"),
- &WhatsAppProto::OnIqDoNothing);
-}
-
-void WhatsAppProto::SendUnregister()
-{
- WANodeIq iq(IQ::SET, "md");
- *iq.addChild("remove-companion-device") << CHAR_PARAM("jid", WAJid(m_szJid, getDword(DBKEY_DEVICE_ID)).toString()) << CHAR_PARAM("reason", "user's decision");
- WSSendNode(iq, &WhatsAppProto::OnIqDoNothing);
-
- m_bTerminated = true;
-}
-
-void WhatsAppProto::SendUsync(const LIST<char> &jids, void *pUserInfo)
-{
- WANodeIq iq(IQ::GET, "usync");
-
- auto *pNode1 = iq.addChild("usync");
- *pNode1 << CHAR_PARAM("sid", GenerateMessageId()) << CHAR_PARAM("mode", "query") << CHAR_PARAM("last", "true")
- << CHAR_PARAM("index", "0") << CHAR_PARAM("context", "message");
-
- pNode1->addChild("query")->addChild("devices")->addAttr("version", "2");
- auto *pList = pNode1->addChild("list");
- for (auto &it : jids)
- pList->addChild("user")->addAttr("jid", it);
-
- WSSendNode(iq, &WhatsAppProto::OnIqGetUsync, pUserInfo);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void WhatsAppProto::ShutdownSession()
-{
- if (m_bTerminated)
- return;
-
- debugLogA("WhatsAppProto::ShutdownSession");
-
- // shutdown all resources
- if (m_hServerConn)
- Netlib_Shutdown(m_hServerConn);
-
- OnLoggedOut();
-}
+/*
+
+WhatsApp plugin for Miranda NG
+Copyright © 2019-23 George Hazan
+
+*/
+
+#include "stdafx.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// gateway worker thread
+
+void WhatsAppProto::ServerThread(void *)
+{
+ do {
+ m_bRespawn = m_bUnregister = false;
+ ServerThreadWorker();
+ }
+ while (m_bRespawn);
+
+ OnLoggedOut();
+}
+
+void WhatsAppProto::ServerThreadWorker()
+{
+ // connect websocket
+ NETLIBHTTPHEADER hdrs[] =
+ {
+ { "Origin", "https://web.whatsapp.com" },
+ { 0, 0 }
+ };
+
+ NLHR_PTR pReply(WebSocket_Connect(m_hNetlibUser, "web.whatsapp.com/ws/chat", hdrs));
+ if (pReply == nullptr) {
+ debugLogA("Server connection failed, exiting");
+ return;
+ }
+
+ if (pReply->resultCode != 101)
+ return;
+
+ delete m_noise;
+ m_noise = new WANoise(this);
+ m_noise->init();
+
+ debugLogA("Server connection succeeded");
+ m_hServerConn = pReply->nlc;
+ m_lastRecvTime = time(0);
+ m_iPacketId = 1;
+
+ Utils_GetRandom(m_wMsgPrefix, sizeof(m_wMsgPrefix));
+
+ Wa__HandshakeMessage__ClientHello client;
+ client.ephemeral = {m_noise->ephemeral.pub.length(), m_noise->ephemeral.pub.data()};
+ client.has_ephemeral = true;
+
+ Wa__HandshakeMessage msg;
+ msg.clienthello = &client;
+ WSSend(msg);
+
+ MBinBuffer netbuf;
+
+ for (m_bTerminated = false; !m_bTerminated;) {
+ unsigned char buf[2048];
+ int bufSize = Netlib_Recv(m_hServerConn, (char *)buf, _countof(buf), MSG_NODUMP);
+ if (bufSize == 0) {
+ debugLogA("Gateway connection gracefully closed");
+ break;
+ }
+ if (bufSize < 0) {
+ debugLogA("Gateway connection error, exiting");
+ break;
+ }
+
+ netbuf.append(buf, bufSize);
+
+ WSHeader hdr;
+ if (!WebSocket_InitHeader(hdr, netbuf.data(), netbuf.length()))
+ continue;
+
+ // we lack some data, let's read them
+ if (netbuf.length() < hdr.headerSize + hdr.payloadSize)
+ if (!WSReadPacket(hdr, netbuf))
+ break;
+
+ // debugLogA("Got 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);
+ // Netlib_Dump(m_hServerConn, netbuf.data(), netbuf.length(), false, 0);
+
+ m_lastRecvTime = time(0);
+
+ // read all payloads from the current buffer, one by one
+ while (true) {
+ MBinBuffer currPacket;
+ currPacket.assign(netbuf.data() + hdr.headerSize, hdr.payloadSize);
+
+ switch (hdr.opCode) {
+ case 1: // json packet
+ debugLogA("Text packet, skipping");
+ /*
+ currPacket.append("", 1); // add 0 to use strchr safely
+ CMStringA szJson(pos, (int)dataSize);
+
+ JSONNode root = JSONNode::parse(szJson);
+ if (root) {
+ debugLogA("JSON received:\n%s", start);
+
+ CMStringA szPrefix(start, int(pos - start - 1));
+ auto *pReq = m_arPacketQueue.find((WARequest *)&szPrefix);
+ if (pReq != nullptr) {
+ root << CHAR_PARAM("$id$", szPrefix);
+ }
+ }
+ }
+ */
+ break;
+
+ case 2: // binary packet
+ if (hdr.payloadSize > 32)
+ ProcessBinaryPacket(currPacket.data(), hdr.payloadSize);
+ break;
+
+ case 8: // close
+ debugLogA("server required to exit");
+ m_bRespawn = m_bTerminated = true; // simply reconnect, don't exit
+ break;
+
+ default:
+ Netlib_Dump(m_hServerConn, currPacket.data(), hdr.payloadSize, false, 0);
+ }
+
+ netbuf.remove(hdr.headerSize + hdr.payloadSize);
+ // debugLogA("%d bytes removed from network buffer, %d bytes remain", hdr.headerSize + hdr.payloadSize, netbuf.length());
+ if (netbuf.length() == 0)
+ break;
+
+ // if we have not enough data for header, continue reading
+ if (!WebSocket_InitHeader(hdr, netbuf.data(), netbuf.length())) {
+ debugLogA("not enough data for header, continue reading");
+ break;
+ }
+
+ // if we have not enough data for data, continue reading
+ if (hdr.headerSize + hdr.payloadSize > netbuf.length()) {
+ debugLogA("not enough place for data (%d+%d > %d), continue reading", 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);
+ }
+ }
+
+ debugLogA("Server connection dropped");
+ Netlib_CloseHandle(m_hServerConn);
+ m_hServerConn = nullptr;
+}
+
+bool WhatsAppProto::WSReadPacket(const WSHeader &hdr, MBinBuffer &res)
+{
+ size_t currPacketSize = res.length() - hdr.headerSize;
+
+ char buf[1024];
+ while (currPacketSize < hdr.payloadSize) {
+ int result = Netlib_Recv(m_hServerConn, buf, _countof(buf), MSG_NODUMP);
+ if (result == 0) {
+ debugLogA("Gateway connection gracefully closed");
+ return false;
+ }
+ if (result < 0) {
+ debugLogA("Gateway connection error, exiting");
+ return false;
+ }
+
+ currPacketSize += result;
+ res.append(buf, result);
+ }
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Binary data processing
+
+void WhatsAppProto::ProcessBinaryPacket(const uint8_t *pData, size_t cbDataLen)
+{
+ while (size_t payloadLen = m_noise->decodeFrame(pData, cbDataLen)) {
+ if (m_noise->bInitFinished) {
+ MBinBuffer buf = m_noise->decrypt(pData, payloadLen);
+
+ WAReader rdr(buf.data(), buf.length());
+ auto b = rdr.readInt8();
+ if (b & 2) {
+ buf.remove(1);
+ buf = unzip(buf);
+ rdr = WAReader(buf.data(), buf.length());
+ }
+
+ if (WANode *pNode = rdr.readNode()) {
+ CMStringA szText;
+ pNode->print(szText);
+ debugLogA("Got binary node:\n%s", szText.c_str());
+
+ auto pHandler = FindPersistentHandler(*pNode);
+ if (pHandler)
+ (this->*pHandler)(*pNode);
+ else
+ debugLogA("cannot handle incoming message");
+
+ delete pNode;
+ }
+ else {
+ debugLogA("wrong or broken payload");
+ Netlib_Dump(m_hServerConn, pData, cbDataLen, false, 0);
+ }
+ }
+ else OnProcessHandshake(pData, (int)payloadLen);
+
+ pData = (BYTE*)pData + payloadLen;
+ cbDataLen -= payloadLen;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::ProcessFailure(int code)
+{
+ switch (code) {
+ case 401:
+ debugLogA("Connection logged out from another device, exiting");
+ Popup(0, TranslateT("This account was logged out from mobile phone, you need to link it again"), m_tszUserName);
+
+ OnErase();
+ break;
+
+ case 408:
+ debugLogA("Connection lost, exiting");
+ break;
+
+ case 411:
+ debugLogA("Conflict between two devices, exiting");
+ break;
+
+ case 428:
+ debugLogA("Connection forcibly closed by the server, exiting");
+ break;
+
+ case 440:
+ debugLogA("Connection replaced from another device, exiting");
+ break;
+
+ case 515:
+ debugLogA("Server required to restart immediately, leaving thread");
+ m_bRespawn = true;
+ break;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::OnLoggedIn()
+{
+ debugLogA("WhatsAppProto::OnLoggedIn");
+
+ if (m_bUnregister) {
+ SendUnregister();
+ m_bTerminated = true;
+ return;
+ }
+
+ SetServerStatus(m_iDesiredStatus);
+
+ ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, m_iDesiredStatus);
+ m_iStatus = m_iDesiredStatus;
+ m_bUpdatedPrekeys = false;
+
+ m_impl.m_keepAlive.Start(1000);
+
+ // retrieve initial info
+ WANodeIq abt(IQ::GET, "abt");
+ abt.addChild("props")->addAttr("protocol", "1");
+ WSSendNode(abt, &WhatsAppProto::OnIqDoNothing);
+
+ WSSendNode(
+ WANodeIq(IQ::GET, "w") << XCHILD("props"),
+ &WhatsAppProto::OnIqDoNothing);
+
+ WSSendNode(
+ WANodeIq(IQ::GET, "blocklist"),
+ &WhatsAppProto::OnIqBlockList);
+
+ WSSendNode(
+ WANodeIq(IQ::GET, "privacy") << XCHILD("privacy"),
+ &WhatsAppProto::OnIqDoNothing);
+
+ GC_RefreshMetadata();
+}
+
+void WhatsAppProto::OnLoggedOut(void)
+{
+ m_impl.m_keepAlive.Stop();
+
+ debugLogA("WhatsAppProto::OnLoggedOut");
+ m_bTerminated = true;
+
+ ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, ID_STATUS_OFFLINE);
+ m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE;
+
+ setAllContactStatuses(ID_STATUS_OFFLINE, false);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Service packets sending
+
+void WhatsAppProto::SendAck(const WANode &node)
+{
+ WANode ack("ack");
+ ack << CHAR_PARAM("to", node.getAttr("from")) << CHAR_PARAM("id", node.getAttr("id")) << CHAR_PARAM("class", node.title);
+ if (node.title != "message")
+ if (auto *param = node.getAttr("type"))
+ ack << CHAR_PARAM("type", param);
+ if (auto *param = node.getAttr("participant"))
+ ack << CHAR_PARAM("participant", param);
+ if (auto *param = node.getAttr("recipient"))
+ ack << CHAR_PARAM("recipient", param);
+ WSSendNode(ack);
+}
+
+void WhatsAppProto::SendKeepAlive()
+{
+ time_t now = time(0);
+ if (now - m_lastRecvTime > 20) {
+ WSSendNode(WANodeIq(IQ::GET, "w:p") << XCHILD("ping"), &WhatsAppProto::OnIqDoNothing);
+
+ m_lastRecvTime = now;
+ }
+
+ for (auto &it : m_arUsers) {
+ if (it->m_timer1 && now - it->m_timer1 > 600) {
+ it->m_timer1 = 0;
+ it->m_timer2 = now;
+ setWord(it->hContact, "Status", ID_STATUS_AWAY);
+ }
+ else if (it->m_timer2 && now - it->m_timer2 > 600) {
+ it->m_timer2 = 0;
+ setWord(it->hContact, "Status", ID_STATUS_OFFLINE);
+ }
+ }
+}
+
+void WhatsAppProto::SendReceipt(const char *pszTo, const char *pszParticipant, const char *pszId, const char *pszType)
+{
+ WANode receipt("receipt");
+ receipt << CHAR_PARAM("id", pszId);
+
+ if (!mir_strcmp(pszType, "read") || !mir_strcmp(pszType, "read-self"))
+ receipt << INT_PARAM("t", time(0));
+
+ if (!mir_strcmp(pszType, "sender") && WAJid(pszTo).isUser())
+ receipt << CHAR_PARAM("to", pszParticipant) << CHAR_PARAM("recipient", pszTo);
+ else {
+ receipt << CHAR_PARAM("to", pszTo);
+ if (pszParticipant)
+ receipt << CHAR_PARAM("participant", pszParticipant);
+ }
+
+ if (pszType)
+ receipt << CHAR_PARAM("type", pszType);
+ WSSendNode(receipt);
+}
+
+void WhatsAppProto::SetServerStatus(int iStatus)
+{
+ if (mir_wstrlen(m_wszNick))
+ WSSendNode(
+ WANode("presence") << CHAR_PARAM("name", T2Utf(m_wszNick)) << CHAR_PARAM("type", (iStatus == ID_STATUS_ONLINE) ? "available" : "unavailable"),
+ &WhatsAppProto::OnIqDoNothing);
+}
+
+void WhatsAppProto::SendUnregister()
+{
+ WANodeIq iq(IQ::SET, "md");
+ *iq.addChild("remove-companion-device") << CHAR_PARAM("jid", WAJid(m_szJid, getDword(DBKEY_DEVICE_ID)).toString()) << CHAR_PARAM("reason", "user's decision");
+ WSSendNode(iq, &WhatsAppProto::OnIqDoNothing);
+
+ m_bTerminated = true;
+}
+
+void WhatsAppProto::SendUsync(const LIST<char> &jids, void *pUserInfo)
+{
+ WANodeIq iq(IQ::GET, "usync");
+
+ auto *pNode1 = iq.addChild("usync");
+ *pNode1 << CHAR_PARAM("sid", GenerateMessageId()) << CHAR_PARAM("mode", "query") << CHAR_PARAM("last", "true")
+ << CHAR_PARAM("index", "0") << CHAR_PARAM("context", "message");
+
+ pNode1->addChild("query")->addChild("devices")->addAttr("version", "2");
+ auto *pList = pNode1->addChild("list");
+ for (auto &it : jids)
+ pList->addChild("user")->addAttr("jid", it);
+
+ WSSendNode(iq, &WhatsAppProto::OnIqGetUsync, pUserInfo);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WhatsAppProto::ShutdownSession()
+{
+ if (m_bTerminated)
+ return;
+
+ debugLogA("WhatsAppProto::ShutdownSession");
+
+ // shutdown all resources
+ if (m_hServerConn)
+ Netlib_Shutdown(m_hServerConn);
+
+ OnLoggedOut();
+}