diff options
-rw-r--r-- | protocols/Icq10/src/http.cpp | 72 | ||||
-rw-r--r-- | protocols/Icq10/src/http.h | 25 | ||||
-rw-r--r-- | protocols/Icq10/src/proto.cpp | 4 | ||||
-rw-r--r-- | protocols/Icq10/src/proto.h | 31 | ||||
-rw-r--r-- | protocols/Icq10/src/server.cpp | 211 | ||||
-rw-r--r-- | protocols/Icq10/src/stdafx.h | 2 | ||||
-rw-r--r-- | protocols/Icq10/src/utils.cpp | 66 |
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; +} |