summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--protocols/Icq10/src/http.cpp72
-rw-r--r--protocols/Icq10/src/http.h25
-rw-r--r--protocols/Icq10/src/proto.cpp4
-rw-r--r--protocols/Icq10/src/proto.h31
-rw-r--r--protocols/Icq10/src/server.cpp211
-rw-r--r--protocols/Icq10/src/stdafx.h2
-rw-r--r--protocols/Icq10/src/utils.cpp66
7 files changed, 333 insertions, 78 deletions
diff --git a/protocols/Icq10/src/http.cpp b/protocols/Icq10/src/http.cpp
index a65f0bc3c6..15835d7052 100644
--- a/protocols/Icq10/src/http.cpp
+++ b/protocols/Icq10/src/http.cpp
@@ -24,7 +24,7 @@
void __cdecl CIcqProto::ServerThread(void*)
{
- m_hAPIConnection = nullptr;
+ memset(&m_ConnPool, 0, sizeof(m_ConnPool));
m_bTerminated = false;
int uin = getDword("UIN");
@@ -40,11 +40,16 @@ void __cdecl CIcqProto::ServerThread(void*)
char mirVer[100];
Miranda_GetVersionText(mirVer, _countof(mirVer));
- auto *pReq = new AsyncHttpRequest(REQUEST_POST, "https://api.login.icq.net/auth/clientLogin", &CIcqProto::OnCheckPassword);
- pReq << CHAR_PARAM("clientName", "Miranda NG") << CHAR_PARAM("clientVersion", mirVer) << CHAR_PARAM("devId", "ic1nmMjqg7Yu-0hL")
- << CHAR_PARAM("f", "json") << CHAR_PARAM("tokenType", "longTerm") << INT_PARAM("s", uin) << CHAR_PARAM("pwd", szPassword);
- pReq->flags |= NLHRF_NODUMPSEND;
- Push(pReq);
+ m_szAToken = getMStringA("AToken");
+ m_szSessionKey = getMStringA("SessionKey");
+ if (m_szAToken.IsEmpty() || m_szSessionKey.IsEmpty()) {
+ auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_POST, "https://api.login.icq.net/auth/clientLogin", &CIcqProto::OnCheckPassword);
+ pReq << CHAR_PARAM("clientName", "Miranda NG") << CHAR_PARAM("clientVersion", mirVer) << CHAR_PARAM("devId", "ic1nmMjqg7Yu-0hL")
+ << CHAR_PARAM("f", "json") << CHAR_PARAM("tokenType", "longTerm") << INT_PARAM("s", uin) << CHAR_PARAM("pwd", szPassword);
+ pReq->flags |= NLHRF_NODUMPSEND;
+ Push(pReq);
+ }
+ else StartSession();
}
while (true) {
@@ -75,19 +80,21 @@ void __cdecl CIcqProto::ServerThread(void*)
}
m_hWorkerThread = nullptr;
- if (m_hAPIConnection) {
- Netlib_CloseHandle(m_hAPIConnection);
- m_hAPIConnection = nullptr;
- }
+ for (auto &it : m_ConnPool)
+ if (it) {
+ Netlib_CloseHandle(it);
+ it = nullptr;
+ }
debugLogA("CIcqProto::WorkerThread: %s", "leaving");
}
/////////////////////////////////////////////////////////////////////////////////////////
-AsyncHttpRequest::AsyncHttpRequest(int iType, const char *szUrl, MTHttpRequestHandler pFunc)
+AsyncHttpRequest::AsyncHttpRequest(IcqConnection conn, int iType, const char *szUrl, MTHttpRequestHandler pFunc) :
+ m_conn(conn)
{
- flags = NLHRF_HTTP11 | NLHRF_SSL;
+ flags = NLHRF_HTTP11 | NLHRF_SSL | NLHRF_DUMPASTEXT;
requestType = iType;
m_szUrl = szUrl;
m_pFunc = pFunc;
@@ -117,9 +124,9 @@ void CIcqProto::ExecuteRequest(AsyncHttpRequest *pReq)
}
}
- if (pReq->m_bMainSite) {
+ if (pReq->m_conn != CONN_NONE) {
pReq->flags |= NLHRF_PERSISTENT;
- pReq->nlc = m_hAPIConnection;
+ pReq->nlc = m_ConnPool[pReq->m_conn];
}
RPC_CSTR szId;
@@ -131,18 +138,18 @@ void CIcqProto::ExecuteRequest(AsyncHttpRequest *pReq)
if (pReq->m_pFunc != nullptr)
(this->*(pReq->m_pFunc))(reply, pReq);
- if (pReq->m_bMainSite)
- m_hAPIConnection = reply->nlc;
+ if (pReq->m_conn != CONN_NONE)
+ m_ConnPool[pReq->m_conn] = pReq->nlc;
Netlib_FreeHttpRequest(reply);
}
else {
debugLogA("Request %s failed", (char*)szId);
- if (pReq->m_bMainSite) {
+ if (pReq->m_conn != CONN_NONE) {
if (IsStatusConnecting(m_iStatus))
ConnectionFailed(LOGINERR_NONETWORK);
- m_hAPIConnection = nullptr;
+ m_ConnPool[pReq->m_conn] = nullptr;
}
}
@@ -162,3 +169,32 @@ void CIcqProto::Push(MHttpRequest *p)
SetEvent(m_evRequestsQueue);
}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+JsonReply::JsonReply(NETLIBHTTPREQUEST *pReply)
+{
+ if (pReply == nullptr) {
+ m_errorCode = 500;
+ return;
+ }
+
+ m_errorCode = pReply->resultCode;
+ if (m_errorCode != 200)
+ return;
+
+ m_root = json_parse(pReply->pData);
+ if (m_root == nullptr) {
+ m_errorCode = 500;
+ return;
+ }
+
+ JSONNode &response = (*m_root)["response"];
+ m_errorCode = response["statusCode"].as_int();
+ m_data = &response["data"];
+}
+
+JsonReply::~JsonReply()
+{
+ json_delete(m_root);
+}
diff --git a/protocols/Icq10/src/http.h b/protocols/Icq10/src/http.h
index 3c5285e2ad..574ed55130 100644
--- a/protocols/Icq10/src/http.h
+++ b/protocols/Icq10/src/http.h
@@ -1,10 +1,31 @@
class CIcqProto;
+enum IcqConnection
+{
+ CONN_NONE = -1, CONN_MAIN = 0, CONN_FETCH = 1, CONN_LAST = 2
+};
+
struct AsyncHttpRequest : public MTHttpRequest<CIcqProto>
{
- bool m_bMainSite = true;
+ IcqConnection m_conn;
GUID m_reqId;
- AsyncHttpRequest(int type, const char *szUrl, MTHttpRequestHandler pFunc = nullptr);
+ AsyncHttpRequest(IcqConnection, int type, const char *szUrl, MTHttpRequestHandler pFunc = nullptr);
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+class JsonReply
+{
+ JSONNode *m_root = nullptr;
+ int m_errorCode = 0;
+ JSONNode* m_data = nullptr;
+
+public:
+ JsonReply(NETLIBHTTPREQUEST*);
+ ~JsonReply();
+
+ __forceinline JSONNode& data() const { return *m_data; }
+ __forceinline int error() const { return m_errorCode; }
};
diff --git a/protocols/Icq10/src/proto.cpp b/protocols/Icq10/src/proto.cpp
index e9bb0ce0ea..8e46820d6f 100644
--- a/protocols/Icq10/src/proto.cpp
+++ b/protocols/Icq10/src/proto.cpp
@@ -36,6 +36,7 @@
CIcqProto::CIcqProto(const char* aProtoName, const wchar_t* aUserName) :
PROTO<CIcqProto>(aProtoName, aUserName),
m_arHttpQueue(10),
+ m_arCache(20, NumericKeySortT),
m_evRequestsQueue(CreateEvent(nullptr, FALSE, FALSE, nullptr))
{
CMStringW descr(FORMAT, TranslateT("%s server connection"), m_tszUserName);
@@ -45,10 +46,13 @@ CIcqProto::CIcqProto(const char* aProtoName, const wchar_t* aUserName) :
nlu.flags = NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE;
nlu.szDescriptiveName.w = descr.GetBuffer();
m_hNetlibUser = Netlib_RegisterUser(&nlu);
+
+ InitContactCache();
}
CIcqProto::~CIcqProto()
{
+ m_arCache.destroy();
m_arHttpQueue.destroy();
::CloseHandle(m_evRequestsQueue);
}
diff --git a/protocols/Icq10/src/proto.h b/protocols/Icq10/src/proto.h
index d1e701bbe3..a3a354feb3 100644
--- a/protocols/Icq10/src/proto.h
+++ b/protocols/Icq10/src/proto.h
@@ -33,6 +33,17 @@
#include "m_system.h"
#include "m_protoint.h"
+struct IcqCacheItem
+{
+ IcqCacheItem(DWORD _uin, MCONTACT _contact) :
+ m_uin(_uin),
+ m_hContact(_contact)
+ {}
+
+ DWORD m_uin;
+ MCONTACT m_hContact;
+};
+
class CIcqProto : public PROTO<CIcqProto>
{
bool m_bOnline = false, m_bTerminated = false;
@@ -41,13 +52,20 @@ class CIcqProto : public PROTO<CIcqProto>
void OnLoggedOut(void);
void SetServerStatus(int iNewStatus);
void ShutdownSession(void);
+ void StartSession(void);
void OnCheckPassword(NETLIBHTTPREQUEST*, AsyncHttpRequest*);
+ void OnFetchEvents(NETLIBHTTPREQUEST*, AsyncHttpRequest*);
+ void OnReceiveAvatar(NETLIBHTTPREQUEST*, AsyncHttpRequest*);
void OnStartSession(NETLIBHTTPREQUEST*, AsyncHttpRequest*);
- HNETLIBCONN m_hAPIConnection;
+ void ProcessEvent(const JSONNode&);
+
+ HNETLIBCONN m_ConnPool[CONN_LAST];
CMStringA m_szSessionKey;
CMStringA m_szAToken;
+ CMStringA m_fetchBaseURL;
+ CMStringA m_aimsid;
//////////////////////////////////////////////////////////////////////////////////////
// http queue
@@ -60,6 +78,17 @@ class CIcqProto : public PROTO<CIcqProto>
void Push(MHttpRequest*);
//////////////////////////////////////////////////////////////////////////////////////
+ // cache
+
+ mir_cs m_csCache;
+ OBJLIST<IcqCacheItem> m_arCache;
+
+ void InitContactCache(void);
+ MCONTACT FindContactByUIN(DWORD);
+
+ void GetAvatarFileName(MCONTACT hContact, wchar_t *pszDest, size_t cbLen);
+
+ //////////////////////////////////////////////////////////////////////////////////////
// threads
HANDLE m_hWorkerThread;
diff --git a/protocols/Icq10/src/server.cpp b/protocols/Icq10/src/server.cpp
index 841706258a..b95ab64b14 100644
--- a/protocols/Icq10/src/server.cpp
+++ b/protocols/Icq10/src/server.cpp
@@ -65,8 +65,10 @@ void CIcqProto::ShutdownSession()
// shutdown all resources
if (m_hWorkerThread)
SetEvent(m_evRequestsQueue);
- if (m_hAPIConnection)
- Netlib_Shutdown(m_hAPIConnection);
+
+ for (auto &it : m_ConnPool)
+ if (it)
+ Netlib_Shutdown(it);
OnLoggedOut();
}
@@ -74,53 +76,11 @@ void CIcqProto::ShutdownSession()
/////////////////////////////////////////////////////////////////////////////////////////
#define CAPS "094613504c7f11d18222444553540000,094613514c7f11d18222444553540000,094613534c7f11d18222444553540000,094613544c7f11d18222444553540000,094613594c7f11d18222444553540000,0946135b4c7f11d18222444553540000,0946135a4c7f11d18222444553540000"
-#define EVENTS "myInfo,presence,buddylist,typing,dataIM,userAddedToBuddyList,service,webrtcMsg,mchat,hist,hiddenChat,diff,permitDeny,imState,notification,apps"
+#define EVENTS "myInfo,presence,buddylist,typing,dataIM,userAddedToBuddyList,webrtcMsg,mchat,hist,hiddenChat,diff,permitDeny,imState,notification,apps"
#define FIELDS "aimId,buddyIcon,bigBuddyIcon,iconId,bigIconId,largeIconId,displayId,friendly,offlineMsg,state,statusMsg,userType,phoneNumber,cellNumber,smsNumber,workNumber,otherNumber,capabilities,ssl,abPhoneNumber,moodIcon,lastName,abPhones,abContactName,lastseen,mute,livechat,official"
-void CIcqProto::OnCheckPassword(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*)
+void CIcqProto::StartSession()
{
- if (pReply->resultCode != 200 || pReply->pData == nullptr) {
- ConnectionFailed(LOGINERR_WRONGPROTOCOL);
- return;
- }
-
- JSONROOT root(pReply->pData);
- if (!root) {
- ConnectionFailed(LOGINERR_WRONGPROTOCOL);
- return;
- }
-
- BYTE hashOut[MIR_SHA256_HASH_SIZE];
- unsigned int len;
-
- JSONNode response = (*root)["response"];
- switch (response["statusCode"].as_int()) {
- case 200:
- {
- JSONNode data = response["data"];
- m_szAToken = data["token"]["a"].as_mstring();
- m_szAToken = ptrA(mir_urlDecode(m_szAToken));
- CMStringA m_szSessionSecret = data["sessionSecret"].as_mstring();
-
- ptrA szPassword(getStringA("Password"));
- HMAC(EVP_sha256(), szPassword.get(), (int)mir_strlen(szPassword), (BYTE*)m_szSessionSecret.c_str(), m_szSessionSecret.GetLength(), hashOut, &len);
- m_szSessionKey = ptrA(mir_base64_encode(hashOut, sizeof(hashOut)));
-
- CMStringA szUin = data["loginId"].as_mstring();
- if (szUin)
- setDword("UIN", atoi(szUin));
- }
- break;
-
- case 440:
- ConnectionFailed(LOGINERR_WRONGPASSWORD);
- return;
-
- default:
- ConnectionFailed(LOGINERR_WRONGPROTOCOL);
- return;
- }
-
ptrA szDeviceId(getStringA("DeviceId"));
if (szDeviceId == nullptr) {
UUID deviceId;
@@ -135,7 +95,7 @@ void CIcqProto::OnCheckPassword(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*)
int ts = time(0);
CMStringA nonce(FORMAT, "%d-2", ts);
- auto *pReq = new AsyncHttpRequest(REQUEST_POST, "https://api.icq.net/aim/startSession", &CIcqProto::OnStartSession);
+ auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_POST, "https://api.icq.net/aim/startSession", &CIcqProto::OnStartSession);
RPC_CSTR szId;
UuidToStringA(&pReq->m_reqId, &szId);
@@ -148,6 +108,8 @@ void CIcqProto::OnCheckPassword(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*)
<< INT_PARAM("rawMsg", 0) << INT_PARAM("sessionTimeout", 7776000) << INT_PARAM("ts", ts) << CHAR_PARAM("view", "online");
CMStringA hashData(FORMAT, "POST&%s&%s", ptrA(mir_urlEncode(pReq->m_szUrl)), ptrA(mir_urlEncode(pReq->m_szParam)));
+ unsigned int len;
+ BYTE hashOut[MIR_SHA256_HASH_SIZE];
HMAC(EVP_sha256(), m_szSessionKey, m_szSessionKey.GetLength(), (BYTE*)hashData.c_str(), hashData.GetLength(), hashOut, &len);
pReq << CHAR_PARAM("sig_sha256", ptrA(mir_base64_encode(hashOut, sizeof(hashOut))));
@@ -155,30 +117,165 @@ void CIcqProto::OnCheckPassword(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*)
RpcStringFreeA(&szId);
}
-void CIcqProto::OnStartSession(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*)
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CIcqProto::OnCheckPassword(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*)
{
- if (pReply->resultCode != 200 || pReply->pData == nullptr) {
- ConnectionFailed(LOGINERR_WRONGPROTOCOL);
+ JsonReply root(pReply);
+ switch (root.error()) {
+ case 200:
+ break;
+
+ case 440:
+ ConnectionFailed(LOGINERR_WRONGPASSWORD);
return;
- }
- JSONROOT root(pReply->pData);
- if (!root) {
+ default:
ConnectionFailed(LOGINERR_WRONGPROTOCOL);
return;
}
- JSONNode response = (*root)["response"];
- switch (response["statusCode"].as_int()) {
+ JSONNode &data = root.data();
+ m_szAToken = data["token"]["a"].as_mstring();
+ m_szAToken = ptrA(mir_urlDecode(m_szAToken));
+ setString("AToken", m_szAToken);
+
+ ptrA szPassword(getStringA("Password"));
+ CMStringA m_szSessionSecret = data["sessionSecret"].as_mstring();
+
+ unsigned int len;
+ BYTE hashOut[MIR_SHA256_HASH_SIZE];
+ HMAC(EVP_sha256(), szPassword.get(), (int)mir_strlen(szPassword), (BYTE*)m_szSessionSecret.c_str(), m_szSessionSecret.GetLength(), hashOut, &len);
+ m_szSessionKey = ptrA(mir_base64_encode(hashOut, sizeof(hashOut)));
+ setString("SessionKey", m_szSessionKey);
+
+ CMStringA szUin = data["loginId"].as_mstring();
+ if (szUin)
+ setDword("UIN", atoi(szUin));
+
+ StartSession();
+}
+
+void CIcqProto::OnStartSession(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*)
+{
+ JsonReply root(pReply);
+ switch (root.error()) {
case 200:
- OnLoggedIn();
break;
case 401:
ConnectionFailed(LOGINERR_WRONGPASSWORD);
- break;
+ return;
default:
ConnectionFailed(LOGINERR_WRONGPROTOCOL);
+ return;
+ }
+
+ JSONNode &data = root.data();
+ m_fetchBaseURL = data["fetchBaseURL"].as_mstring();
+ m_aimsid = data["aimsid"].as_mstring();
+
+ OnLoggedIn();
+
+ for (auto &it : data["events"])
+ ProcessEvent(it);
+
+ m_fetchBaseURL.Append("&first=1");
+ Push(new AsyncHttpRequest(CONN_FETCH, REQUEST_GET, m_fetchBaseURL, &CIcqProto::OnFetchEvents));
+}
+
+void CIcqProto::OnReceiveAvatar(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq)
+{
+ MCONTACT hContact = (MCONTACT)pReq->pUserInfo;
+
+ const wchar_t *pwszExtension;
+ PROTO_AVATAR_INFORMATION ai;
+ ai.hContact = hContact;
+ ai.format = ProtoGetBufferFormat(pReply->pData, &pwszExtension);
+ setByte(hContact, "AvatarType", ai.format);
+ GetAvatarFileName(hContact, ai.filename, _countof(ai.filename));
+
+ FILE *out = _wfopen(ai.filename, L"wb");
+ if (out != nullptr) {
+ fwrite(pReply->pData, pReply->dataLength, 1, out);
+ fclose(out);
+
+ ProtoBroadcastAck(hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, HANDLE(&ai), 0);
+ debugLogW(L"Broadcast new avatar: %s", ai.filename);
}
+ else ProtoBroadcastAck(hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, HANDLE(&ai), 0);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CIcqProto::ProcessEvent(const JSONNode &ev)
+{
+ CMStringW szType = ev["type"].as_mstring();
+ if (szType == L"buddylist") {
+ for (auto &it : ev["eventData"]["groups"]) {
+ CMStringW szGroup = it["name"].as_mstring();
+ Clist_GroupCreate(0, szGroup);
+
+ for (auto &buddy : it["buddies"]) {
+ DWORD dwUin = _wtol(buddy["aimId"].as_mstring());
+ MCONTACT hContact = FindContactByUIN(dwUin);
+ if (hContact == 0) {
+ hContact = db_add_contact();
+ Proto_AddToContact(hContact, m_szModuleName);
+ setDword(hContact, "UIN", dwUin);
+ {
+ mir_cslock l(m_csCache);
+ m_arCache.insert(new IcqCacheItem(dwUin, hContact));
+ }
+ }
+
+ CMStringW wszNick(buddy["friendly"].as_mstring());
+ if (!wszNick.IsEmpty())
+ setWString(hContact, "Nick", wszNick);
+
+ setDword(hContact, "Status", StatusFromString(buddy["state"].as_mstring()));
+
+ int lastLogin = buddy["lastseen"].as_int();
+ if (lastLogin)
+ setDword(hContact, "LoginTS", lastLogin);
+
+ CMStringW wszStatus(buddy["statusMsg"].as_mstring());
+ if (wszStatus.IsEmpty())
+ db_unset(hContact, "CList", "StatusMsg");
+ else
+ db_set_ws(hContact, "CList", "StatusMsg", wszStatus);
+
+ CMStringW wszIconId(buddy["iconId"].as_mstring());
+ CMStringW oldIconID(getMStringW(hContact, "IconId"));
+ if (wszIconId != oldIconID) {
+ setWString(hContact, "IconId", wszIconId);
+
+ CMStringA szUrl(buddy["buddyIcon"].as_mstring());
+ auto *p = new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, szUrl, &CIcqProto::OnReceiveAvatar);
+ p->pUserInfo = (void*)hContact;
+ Push(p);
+ }
+
+ db_set_ws(hContact, "CList", "Group", szGroup);
+ }
+ }
+ }
+}
+
+void CIcqProto::OnFetchEvents(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*)
+{
+ JsonReply root(pReply);
+ if (root.error() != 200) {
+ ShutdownSession();
+ return;
+ }
+
+ JSONNode &data = root.data();
+ m_fetchBaseURL = data["fetchBaseURL"].as_mstring();
+
+ for (auto &it : data["events"])
+ ProcessEvent(it);
+
+ Push(new AsyncHttpRequest(CONN_FETCH, REQUEST_GET, m_fetchBaseURL, &CIcqProto::OnFetchEvents));
}
diff --git a/protocols/Icq10/src/stdafx.h b/protocols/Icq10/src/stdafx.h
index 078ef4fe19..44f75d1099 100644
--- a/protocols/Icq10/src/stdafx.h
+++ b/protocols/Icq10/src/stdafx.h
@@ -80,3 +80,5 @@
#include "http.h"
#include "proto.h"
+
+int StatusFromString(const CMStringW&);
diff --git a/protocols/Icq10/src/utils.cpp b/protocols/Icq10/src/utils.cpp
new file mode 100644
index 0000000000..2dec6c800c
--- /dev/null
+++ b/protocols/Icq10/src/utils.cpp
@@ -0,0 +1,66 @@
+// -----------------------------------------------------------------------------
+// ICQ plugin for Miranda NG
+// -----------------------------------------------------------------------------
+// Copyright © 2018 Miranda NG team
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+// -----------------------------------------------------------------------------
+
+#include "stdafx.h"
+
+void CIcqProto::InitContactCache()
+{
+ mir_cslock l(m_csCache);
+ for (auto &it : AccContacts())
+ m_arCache.insert(new IcqCacheItem(getDword(it, "UIN"), it));
+}
+
+MCONTACT CIcqProto::FindContactByUIN(DWORD dwUin)
+{
+ mir_cslock l(m_csCache);
+ auto *p = m_arCache.find((IcqCacheItem*)&dwUin);
+ return (p) ? p->m_hContact : 0;
+}
+
+void CIcqProto::GetAvatarFileName(MCONTACT hContact, wchar_t* pszDest, size_t cbLen)
+{
+ int tPathLen = mir_snwprintf(pszDest, cbLen, L"%s\\%S", VARSW(L"%miranda_avatarcache%"), m_szModuleName);
+
+ DWORD dwAttributes = GetFileAttributes(pszDest);
+ if (dwAttributes == 0xffffffff || (dwAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
+ CreateDirectoryTreeW(pszDest);
+
+ pszDest[tPathLen++] = '\\';
+
+ CMStringW wszFileName(getMStringW(hContact, "IconId"));
+ const wchar_t* szFileType = ProtoGetAvatarExtension(getByte(hContact, "AvatarType", PA_FORMAT_PNG));
+ mir_snwprintf(pszDest + tPathLen, MAX_PATH - tPathLen, L"%s%s", wszFileName.c_str(), szFileType);
+}
+
+int StatusFromString(const CMStringW &wszStatus)
+{
+ if (wszStatus == "online")
+ return ID_STATUS_ONLINE;
+ if (wszStatus == "n/a")
+ return ID_STATUS_NA;
+ if (wszStatus == "away")
+ return ID_STATUS_AWAY;
+ if (wszStatus == "occupied")
+ return ID_STATUS_OCCUPIED;
+ if (wszStatus == "dnd")
+ return ID_STATUS_DND;
+
+ return ID_STATUS_OFFLINE;
+}