diff options
Diffstat (limited to 'protocols')
34 files changed, 1004 insertions, 177 deletions
diff --git a/protocols/Discord/discord.vcxproj b/protocols/Discord/discord.vcxproj index a360422c31..1a76deed6c 100644 --- a/protocols/Discord/discord.vcxproj +++ b/protocols/Discord/discord.vcxproj @@ -30,4 +30,10 @@ <ExceptionHandling>Sync</ExceptionHandling> </ClCompile> </ItemDefinitionGroup> + <ItemGroup> + <ProjectReference Include="..\..\libs\zlib\zlib.vcxproj"> + <Project>{e2a369cd-eda3-414f-8ad0-e732cd7ee68c}</Project> + <ReferenceOutputAssembly>false</ReferenceOutputAssembly> + </ProjectReference> + </ItemGroup> </Project>
\ No newline at end of file diff --git a/protocols/Discord/proto_discord/res/Idle.ico b/protocols/Discord/proto_discord/res/Away.ico Binary files differindex c2830ed132..2a43c57cd8 100644 --- a/protocols/Discord/proto_discord/res/Idle.ico +++ b/protocols/Discord/proto_discord/res/Away.ico diff --git a/protocols/Discord/proto_discord/res/Proto_Discord.rc b/protocols/Discord/proto_discord/res/Proto_Discord.rc index eed49abaf1..36290c99f3 100644 --- a/protocols/Discord/proto_discord/res/Proto_Discord.rc +++ b/protocols/Discord/proto_discord/res/Proto_Discord.rc @@ -54,6 +54,7 @@ END IDI_ICON1 ICON "Offline.ico" IDI_ICON2 ICON "Online.ico" IDI_ICON4 ICON "Invisible.ico" +IDI_ICON5 ICON "Away.ico" IDI_ICON6 ICON "DND.ico" #endif // Russian (Russia) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/protocols/Discord/res/discord.rc b/protocols/Discord/res/discord.rc index fc31363075..b8b5e4ae5a 100644 --- a/protocols/Discord/res/discord.rc +++ b/protocols/Discord/res/discord.rc @@ -7,7 +7,7 @@ // // Generated from the TEXTINCLUDE 2 resource. // -#include "afxres.h" +#include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS @@ -32,7 +32,7 @@ END 2 TEXTINCLUDE BEGIN - "#include ""afxres.h""\r\n" + "#include ""winres.h""\r\n" "\0" END @@ -44,6 +44,7 @@ END #endif // APSTUDIO_INVOKED + ///////////////////////////////////////////////////////////////////////////// // // Icon @@ -51,9 +52,8 @@ END // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. - -IDI_MAIN ICON "discord.ico" -IDI_OFFLINE ICON "offline.ico" +IDI_MAIN ICON "discord.ico" +IDI_OFFLINE ICON "offline.ico" ///////////////////////////////////////////////////////////////////////////// // @@ -70,12 +70,26 @@ BEGIN EDITTEXT IDC_USERNAME,84,18,123,13,ES_AUTOHSCROLL LTEXT "Password:",IDC_STATIC,17,36,61,8,0,WS_EX_RIGHT EDITTEXT IDC_PASSWORD,84,34,123,13,ES_PASSWORD | ES_AUTOHSCROLL - GROUPBOX "Contacts",IDC_STATIC,7,77,291,88 LTEXT "Default group:",IDC_STATIC,17,91,61,8,0,WS_EX_RIGHT EDITTEXT IDC_GROUP,84,89,123,13,ES_AUTOHSCROLL END +IDD_OPTIONS_ACCMGR DIALOGEX 0, 0, 200, 88 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + GROUPBOX "User details",IDC_STATIC,7,7,178,46 + LTEXT "E-mail:",IDC_STATIC,17,20,69,8,0,WS_EX_RIGHT + EDITTEXT IDC_USERNAME,92,18,86,13,ES_AUTOHSCROLL + LTEXT "Password:",IDC_STATIC,17,36,69,8,0,WS_EX_RIGHT + EDITTEXT IDC_PASSWORD,92,34,86,13,ES_PASSWORD | ES_AUTOHSCROLL + GROUPBOX "Contacts",IDC_STATIC,7,56,178,28 + LTEXT "Default group:",IDC_STATIC,17,67,69,8,0,WS_EX_RIGHT + EDITTEXT IDC_GROUP,92,65,86,13,ES_AUTOHSCROLL +END + IDD_EXTSEARCH DIALOGEX 0, 0, 114, 55 STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU EXSTYLE WS_EX_TRANSPARENT | WS_EX_CONTROLPARENT diff --git a/protocols/Discord/src/avatars.cpp b/protocols/Discord/src/avatars.cpp new file mode 100644 index 0000000000..1e0f41a565 --- /dev/null +++ b/protocols/Discord/src/avatars.cpp @@ -0,0 +1,210 @@ +/* +Copyright © 2016-17 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" + +CMStringW CDiscordProto::GetAvatarFilename(MCONTACT hContact) +{ + CMStringW wszResult(FORMAT, L"%s\\%S", VARSW(L"%miranda_avatarcache%"), m_szModuleName); + + DWORD dwAttributes = GetFileAttributes(wszResult); + if (dwAttributes == 0xffffffff || (dwAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) + CreateDirectoryTreeW(wszResult); + + wszResult.AppendChar('\\'); + + const wchar_t* szFileType = ProtoGetAvatarExtension(getByte(hContact, "AvatarType", PA_FORMAT_PNG)); + wszResult.AppendFormat(L"%lld%s", getId(hContact, DB_KEY_ID), szFileType); + return wszResult; +} + +INT_PTR CDiscordProto::GetAvatarCaps(WPARAM wParam, LPARAM lParam) +{ + int res = 0; + + switch (wParam) { + case AF_MAXSIZE: + ((POINT*)lParam)->x = ((POINT*)lParam)->y = 128; + break; + + case AF_PROPORTION: + res = PIP_NONE; + break; + + case AF_FORMATSUPPORTED: + res = lParam == PA_FORMAT_PNG || lParam == PA_FORMAT_GIF || lParam == PA_FORMAT_JPEG; + break; + + case AF_ENABLED: + res = 1; + break; + } + + return res; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CDiscordProto::OnReceiveAvatar(NETLIBHTTPREQUEST *reply, AsyncHttpRequest *pReq) +{ + PROTO_AVATAR_INFORMATION ai = { 0 }; + ai.format = PA_FORMAT_UNKNOWN; + ai.hContact = (MCONTACT)pReq->pUserInfo; + + if (reply->resultCode != 200) { +LBL_Error: + ProtoBroadcastAck(ai.hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, (HANDLE)&ai); + return; + } + + const char *pszMimeType = NULL; + for (int i = 0; i < reply->headersCount; i++) + if (!mir_strcmp(reply->headers[i].szName, "Content-Type")) { + pszMimeType = reply->headers[i].szValue; + break; + } + + if (!mir_strcmp(pszMimeType, "image/jpeg")) + ai.format = PA_FORMAT_JPEG; + else if (!mir_strcmp(pszMimeType, "image/png")) + ai.format = PA_FORMAT_PNG; + else if (!mir_strcmp(pszMimeType, "image/gif")) + ai.format = PA_FORMAT_GIF; + else if (!mir_strcmp(pszMimeType, "image/bmp")) + ai.format = PA_FORMAT_BMP; + else { + debugLogA("unknown avatar mime type: %s", pszMimeType); + goto LBL_Error; + } + + mir_wstrncpy(ai.filename, GetAvatarFilename(ai.hContact), _countof(ai.filename)); + + FILE *out = _wfopen(ai.filename, L"wb"); + if (out == NULL) { + debugLogA("cannot open avatar file %S for writing", ai.filename); + goto LBL_Error; + } + + fwrite(reply->pData, 1, reply->dataLength, out); + fclose(out); + + if (ai.hContact) + ProtoBroadcastAck(ai.hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, (HANDLE)&ai); + else + CallService(MS_AV_REPORTMYAVATARCHANGED, (WPARAM)m_szModuleName, 0); +} + +bool CDiscordProto::RetrieveAvatar(MCONTACT hContact) +{ + ptrA szAvatarHash(getStringA(hContact, DB_KEY_AVHASH)); + SnowFlake id = getId(hContact, DB_KEY_ID); + if (id == 0 || szAvatarHash == NULL) + return false; + + CMStringA szUrl(FORMAT, "https://cdn.discordapp.com/avatars/%lld/%s.jpg", id, szAvatarHash); + AsyncHttpRequest *pReq = new AsyncHttpRequest(this, REQUEST_GET, szUrl, &CDiscordProto::OnReceiveAvatar); + pReq->pUserInfo = (void*)hContact; + Push(pReq); + return true; +} + +INT_PTR CDiscordProto::GetAvatarInfo(WPARAM flags, LPARAM lParam) +{ + PROTO_AVATAR_INFORMATION *pai = (PROTO_AVATAR_INFORMATION *)lParam; + + CMStringW wszFileName(GetAvatarFilename(pai->hContact)); + if (!wszFileName.IsEmpty()) { + mir_wstrncpy(pai->filename, wszFileName, _countof(pai->filename)); + + bool bFileExist = _waccess(wszFileName, 0) == 0; + + // if we still need to load an avatar + if ((flags & GAIF_FORCE) || !bFileExist) { + if (RetrieveAvatar(pai->hContact)) + return GAIR_WAITFOR; + } + else if (bFileExist) + return GAIR_SUCCESS; + } + + return GAIR_NOAVATAR; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +INT_PTR CDiscordProto::GetMyAvatar(WPARAM wParam, LPARAM lParam) +{ + if (!wParam || !lParam) + return -3; + + wchar_t* buf = (wchar_t*)wParam; + int size = (int)lParam; + + PROTO_AVATAR_INFORMATION ai = {}; + switch (GetAvatarInfo(0, (LPARAM)&ai)) { + case GAIR_SUCCESS: + wcsncpy_s(buf, size, ai.filename, _TRUNCATE); + return 0; + + case GAIR_WAITFOR: + return -1; + } + + return -2; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +INT_PTR CDiscordProto::SetMyAvatar(WPARAM, LPARAM lParam) +{ + CMStringW wszFileName(GetAvatarFilename(NULL)); + + const wchar_t *pwszFilename = (const wchar_t*)lParam; + if (pwszFilename == NULL) { // remove my avatar file + delSetting(DB_KEY_AVHASH); + DeleteFile(wszFileName); + } + + CMStringA szPayload("data:"); + + int iFormat = ProtoGetAvatarFileFormat(pwszFilename); + switch (iFormat) { + case PA_FORMAT_BMP: szPayload.Append("image/bmp"); break; + case PA_FORMAT_GIF: szPayload.Append("image/gif"); break; + case PA_FORMAT_PNG: szPayload.Append("image/png"); break; + case PA_FORMAT_JPEG: szPayload.Append("image/jpeg"); break; + default: + debugLogA("invalid file format for avatar %S: %d", pwszFilename, iFormat); + return 1; + } + szPayload.Append(";base64,"); + FILE *in = _wfopen(pwszFilename, L"rb"); + if (in == NULL) { + debugLogA("cannot open avatar file %S for reading", pwszFilename); + return 2; + } + + int iFileLength = _filelength(_fileno(in)); + ptrA szFileContents((char*)mir_alloc(iFileLength)); + fread(szFileContents, 1, iFileLength, in); + fclose(in); + szPayload.Append(ptrA(mir_base64_encode((BYTE*)szFileContents.get(), iFileLength))); + + JSONNode root; root << CHAR_PARAM("avatar", szPayload); + Push(new AsyncHttpRequest(this, REQUEST_PATCH, "/users/@me", NULL, &root)); + return 0; +} diff --git a/protocols/Discord/src/connection.cpp b/protocols/Discord/src/connection.cpp index bc48d402e4..969e85fb05 100644 --- a/protocols/Discord/src/connection.cpp +++ b/protocols/Discord/src/connection.cpp @@ -33,8 +33,10 @@ void CDiscordProto::ExecuteRequest(AsyncHttpRequest *pReq) } } - pReq->flags |= NLHRF_PERSISTENT; - pReq->nlc = m_hAPIConnection; + if (pReq->m_bMainSite) { + pReq->flags |= NLHRF_PERSISTENT; + pReq->nlc = m_hAPIConnection; + } debugLogA("Executing request #%d:\n%s", pReq->m_iReqNum, pReq->szUrl); NETLIBHTTPREQUEST *reply = (NETLIBHTTPREQUEST*)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)m_hNetlibUser, (LPARAM)pReq); @@ -49,11 +51,11 @@ void CDiscordProto::ExecuteRequest(AsyncHttpRequest *pReq) else { debugLogA("Request %d failed", pReq->m_iReqNum); - if (IsStatusConnecting(m_iStatus)) - ConnectionFailed(LOGINERR_NONETWORK); - else - ShutdownSession(); - m_hAPIConnection = NULL; + if (pReq->m_bMainSite) { + if (IsStatusConnecting(m_iStatus)) + ConnectionFailed(LOGINERR_NONETWORK); + m_hAPIConnection = NULL; + } } delete pReq; } @@ -64,10 +66,6 @@ void CDiscordProto::OnLoggedIn() m_bOnline = true; SetServerStatus(m_iDesiredStatus); - Push(new AsyncHttpRequest(this, REQUEST_GET, "/users/@me/guilds", &CDiscordProto::OnReceiveGuilds)); - Push(new AsyncHttpRequest(this, REQUEST_GET, "/users/@me/channels", &CDiscordProto::OnReceiveChannels)); - Push(new AsyncHttpRequest(this, REQUEST_GET, "/users/@me/relationships", &CDiscordProto::OnReceiveFriends)); - if (m_szGateway.IsEmpty()) Push(new AsyncHttpRequest(this, REQUEST_GET, "/gateway", &CDiscordProto::OnReceiveGateway)); else @@ -78,10 +76,10 @@ void CDiscordProto::OnLoggedOut() { debugLogA("CDiscordProto::OnLoggedOut"); m_bOnline = false; - m_hWorkerThread = NULL; + m_bTerminated = true; + m_iGatewaySeq = 0; - if (m_hAPIConnection) - Netlib_CloseHandle(m_hAPIConnection); + KillTimer(g_hwndHeartbeat, (UINT_PTR)this); ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, ID_STATUS_OFFLINE); m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; @@ -89,10 +87,19 @@ void CDiscordProto::OnLoggedOut() void CDiscordProto::ShutdownSession() { + if (m_bTerminated) + return; + debugLogA("CDiscordProto::ShutdownSession"); - m_bTerminated = true; + + // shutdown all resources if (m_hWorkerThread) SetEvent(m_evRequestsQueue); + if (m_hGatewayConnection) + Netlib_Shutdown(m_hGatewayConnection); + if (m_hAPIConnection) + Netlib_Shutdown(m_hAPIConnection); + OnLoggedOut(); } diff --git a/protocols/Discord/src/dispatch.cpp b/protocols/Discord/src/dispatch.cpp new file mode 100644 index 0000000000..8d314e0836 --- /dev/null +++ b/protocols/Discord/src/dispatch.cpp @@ -0,0 +1,221 @@ +/* +Copyright © 2016-17 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" + +#pragma pack(4) + +struct CDiscordCommand +{ + const wchar_t *szCommandId; + GatewayHandlerFunc pFunc; +} +static handlers[] = // these structures must me sorted alphabetically +{ + { L"MESSAGE_CREATE", &CDiscordProto::OnCommandMessage }, + { L"MESSAGE_UPDATE", &CDiscordProto::OnCommandMessage }, + + { L"PRESENCE_UPDATE", &CDiscordProto::OnCommandPresence }, + + { L"READY", &CDiscordProto::OnCommandReady }, + + { L"TYPING_START", &CDiscordProto::OnCommandTyping }, + + { L"USER_UPDATE", &CDiscordProto::OnCommandUserUpdate }, +}; + +static int __cdecl pSearchFunc(const void *p1, const void *p2) +{ + return wcscmp(((CDiscordCommand*)p1)->szCommandId, ((CDiscordCommand*)p2)->szCommandId); +} + +GatewayHandlerFunc CDiscordProto::GetHandler(const wchar_t *pwszCommand) +{ + CDiscordCommand tmp = { pwszCommand, NULL }; + CDiscordCommand *p = (CDiscordCommand*)bsearch(&tmp, handlers, _countof(handlers), sizeof(handlers[0]), pSearchFunc); + return (p != NULL) ? p->pFunc : NULL; +} + +////////////////////////////////////////////////////////////////////////////////////// +// reading a new message + +void CDiscordProto::OnCommandMessage(const JSONNode &pRoot) +{ + PROTORECVEVENT recv = {}; + + CDiscordUser *pUser = PrepareUser(pRoot["author"]); + SnowFlake channelId = _wtoi64(pRoot["channel_id"].as_mstring()); + CMStringW msgId = pRoot["id"].as_mstring(); + CMStringW wszText = pRoot["content"].as_mstring(); + + // if a message has myself as an author, mark it as sent + if (pUser->id == 0) + return; + + const JSONNode &edited = pRoot["edited_timestamp"]; + if (!edited.isnull()) + wszText.AppendFormat(L" (%s %s)", TranslateT("edited at"), edited.as_mstring().c_str()); + + if (pUser->channelId != channelId) { + debugLogA("failed to process a groupchat message, exiting"); + return; + } + + ptrA buf(mir_utf8encodeW(wszText)); + recv.timestamp = (DWORD)StringToDate(pRoot["timestamp"].as_mstring()); + recv.szMessage = buf; + recv.lParam = (LPARAM)msgId.c_str(); + ProtoChainRecvMsg(pUser->hContact, &recv); + + SnowFlake lastId = getId(pUser->hContact, DB_KEY_LASTMSGID); // as stored in a database + if (lastId < _wtoi64(msgId)) + setId(pUser->hContact, DB_KEY_LASTMSGID, _wtoi64(msgId)); +} + +////////////////////////////////////////////////////////////////////////////////////// +// someone changed its status + +void CDiscordProto::OnCommandPresence(const JSONNode &pRoot) +{ + CDiscordUser *pUser = PrepareUser(pRoot["user"]); + if (pUser == NULL) + return; + + int iStatus; + CMStringW wszStatus = pRoot["status"].as_mstring(); + if (wszStatus == L"idle") + iStatus = ID_STATUS_IDLE; + else if (wszStatus == L"online") + iStatus = ID_STATUS_ONLINE; + else if (wszStatus == L"offline") + iStatus = ID_STATUS_OFFLINE; + else + iStatus = 0; + + if (iStatus != 0) + setWord(pUser->hContact, "Status", iStatus); + + CMStringW wszGame = pRoot["game"]["name"].as_mstring(); + if (!wszGame.IsEmpty()) + setWString(pUser->hContact, "XStatusMsg", wszGame); + else + delSetting(pUser->hContact, "XStatusMsg"); +} + +////////////////////////////////////////////////////////////////////////////////////// +// gateway session start + +void CALLBACK CDiscordProto::HeartbeatTimerProc(HWND, UINT, UINT_PTR id, DWORD) +{ + ((CDiscordProto*)id)->GatewaySendHeartbeat(); +} + +static void __stdcall sttStartTimer(void *param) +{ + CDiscordProto *ppro = (CDiscordProto*)param; + SetTimer(g_hwndHeartbeat, (UINT_PTR)param, ppro->getHeartbeatInterval(), &CDiscordProto::HeartbeatTimerProc); +} + +void CDiscordProto::OnCommandReady(const JSONNode &pRoot) +{ + GatewaySendHeartbeat(); + CallFunctionAsync(sttStartTimer, this); + + m_szGatewaySessionId = pRoot["session_id"].as_mstring(); + + const JSONNode &relations = pRoot["relationships"]; + for (auto it = relations.begin(); it != relations.end(); ++it) { + const JSONNode &p = *it; + + const JSONNode &user = p["user"]; + if (user) + PrepareUser(user); + } + + const JSONNode &channels = pRoot["private_channels"]; + for (auto it = channels.begin(); it != channels.end(); ++it) { + const JSONNode &p = *it; + + CDiscordUser *pUser = NULL; + const JSONNode &recipients = p["recipients"]; + for (auto it2 = recipients.begin(); it2 != recipients.end(); ++it2) + pUser = PrepareUser(*it2); + + if (pUser == NULL) + continue; + + pUser->channelId = _wtoi64(p["id"].as_mstring()); + pUser->lastMessageId = _wtoi64(p["last_message_id"].as_mstring()); + pUser->bIsPrivate = true; + + setId(pUser->hContact, DB_KEY_CHANNELID, pUser->channelId); + + SnowFlake oldMsgId = getId(pUser->hContact, DB_KEY_LASTMSGID); + if (pUser->lastMessageId > oldMsgId) + RetrieveHistory(pUser->hContact, MSG_AFTER, oldMsgId, 99); + } +} + +////////////////////////////////////////////////////////////////////////////////////// +// UTN support + +void CDiscordProto::OnCommandTyping(const JSONNode &pRoot) +{ + SnowFlake userId = _wtoi64(pRoot["user_id"].as_mstring()); + SnowFlake channelId = _wtoi64(pRoot["channel_id"].as_mstring()); + debugLogA("user typing notification: userid=%lld, channelid=%lld", userId, channelId); + + CDiscordUser *pUser = FindUser(userId); + if (pUser == NULL) { + debugLogA("user with id=%lld is not found", userId); + return; + } + + if (pUser->channelId == channelId) { + debugLogA("user is typing in his private channel"); + CallService(MS_PROTO_CONTACTISTYPING, pUser->hContact, 20); + } + else { + debugLogA("user is typing in a group channel, skipped"); + } +} + +////////////////////////////////////////////////////////////////////////////////////// +// UTN support + +void CDiscordProto::OnCommandUserUpdate(const JSONNode &pRoot) +{ + SnowFlake id = _wtoi64(pRoot["id"].as_mstring()); + + MCONTACT hContact; + if (id != m_ownId) { + CDiscordUser *pUser = FindUser(id); + if (pUser == NULL) + return; + + hContact = pUser->hContact; + } + else hContact = 0; + + // force rereading avatar + ptrW wszOldHash(getWStringA(hContact, DB_KEY_AVHASH)); + CMStringW wszNewHash(pRoot["avatar"].as_mstring()); + if (mir_wstrcmp(wszOldHash, wszNewHash)) { + setWString(hContact, DB_KEY_AVHASH, wszNewHash); + RetrieveAvatar(hContact); + } +} diff --git a/protocols/Discord/src/gateway.cpp b/protocols/Discord/src/gateway.cpp index a7352d4120..7454383878 100644 --- a/protocols/Discord/src/gateway.cpp +++ b/protocols/Discord/src/gateway.cpp @@ -17,30 +17,20 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. #include "stdafx.h" -void CDiscordProto::OnReceiveGateway(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*) -{ - if (pReply->resultCode != 200) { - ShutdownSession(); - return; - } - - JSONNode root = JSONNode::parse(pReply->pData); - if (!root) { - ShutdownSession(); - return; - } +////////////////////////////////////////////////////////////////////////////////////// +// sends a piece of JSON to a server via a websocket, masked - m_szGateway = root["url"].as_mstring(); - ForkThread(&CDiscordProto::GatewayThread, NULL); -} - -void CDiscordProto::GatewaySend(int opCode, const char *szBuf) +void CDiscordProto::GatewaySend(const JSONNode &pRoot, int opCode) { if (m_hGatewayConnection == NULL) return; + json_string szText = pRoot.write(); + BYTE header[20]; - size_t datalen, strLen = mir_strlen(szBuf); + size_t datalen; + uint64_t strLen = szText.length(); + header[0] = 0x80 + (opCode & 0x7F); if (strLen < 126) { header[1] = (strLen & 0xFF); @@ -65,15 +55,36 @@ void CDiscordProto::GatewaySend(int opCode, const char *szBuf) datalen = 10; } + union { + uLong dwMask; + Bytef arMask[4]; + }; + dwMask = crc32(rand(), (Bytef*)szText.c_str(), (uInt)szText.length()); + memcpy(header + datalen, arMask, _countof(arMask)); + datalen += _countof(arMask); + header[1] |= 0x80; + ptrA sendBuf((char*)mir_alloc(strLen + datalen)); memcpy(sendBuf, header, datalen); - if (strLen) - memcpy(sendBuf.get() + datalen, szBuf, strLen); + if (strLen) { + memcpy(sendBuf.get() + datalen, szText.c_str(), strLen); + for (size_t i = 0; i < strLen; i++) + sendBuf[i + datalen] ^= arMask[i & 3]; + } Netlib_Send(m_hGatewayConnection, sendBuf, int(strLen + datalen), 0); } +////////////////////////////////////////////////////////////////////////////////////// +// gateway worker thread + void CDiscordProto::GatewayThread(void*) { + GatewayThreadWorker(); + ShutdownSession(); +} + +void CDiscordProto::GatewayThreadWorker() +{ // connect to the gateway server if (!mir_strncmp(m_szGateway, "wss://", 6)) m_szGateway.Delete(0, 6); @@ -94,13 +105,11 @@ void CDiscordProto::GatewayThread(void*) m_hGatewayConnection = (HANDLE)CallService(MS_NETLIB_OPENCONNECTION, (WPARAM)m_hGatewayNetlibUser, (LPARAM)&conn); if (m_hGatewayConnection == NULL) { debugLogA("Gateway connection failed to connect to %s:%d, exiting", m_szGateway.c_str(), conn.wPort); - LBL_Fatal: - ShutdownSession(); return; } { CMStringA szBuf; - szBuf.AppendFormat("GET https://%s/?encoding=etf&v=6 HTTP/1.1\r\n", m_szGateway.c_str()); + szBuf.AppendFormat("GET https://%s/?encoding=json&v=6 HTTP/1.1\r\n", m_szGateway.c_str()); szBuf.AppendFormat("Host: %s\r\n", m_szGateway.c_str()); szBuf.AppendFormat("Upgrade: websocket\r\n"); szBuf.AppendFormat("Pragma: no-cache\r\n"); @@ -112,7 +121,7 @@ void CDiscordProto::GatewayThread(void*) szBuf.AppendFormat("\r\n"); if (Netlib_Send(m_hGatewayConnection, szBuf, szBuf.GetLength(), MSG_DUMPASTEXT) == SOCKET_ERROR) { debugLogA("Error establishing gateway connection to %s:%d, send failed", m_szGateway.c_str(), conn.wPort); - goto LBL_Fatal; + return; } } { @@ -120,13 +129,13 @@ void CDiscordProto::GatewayThread(void*) int bufSize = Netlib_Recv(m_hGatewayConnection, buf, _countof(buf), MSG_DUMPASTEXT); if (bufSize <= 0) { debugLogA("Error establishing gateway connection to %s:%d, read failed", m_szGateway.c_str(), conn.wPort); - goto LBL_Fatal; + return; } int status = 0; if (sscanf(buf, "HTTP/1.1 %d", &status) != 1 || status != 101) { debugLogA("Error establishing gateway connection to %s:%d, status %d", m_szGateway.c_str(), conn.wPort, status); - goto LBL_Fatal; + return; } } @@ -134,22 +143,16 @@ void CDiscordProto::GatewayThread(void*) bool bExit = false; int offset = 0; - unsigned char *dataBuf = NULL; + char *dataBuf = NULL; size_t dataBufSize = 0; + bool bDataBufAllocated = false; while (!bExit) { if (m_bTerminated) break; - NETLIBSELECT sel = {}; - sel.cbSize = sizeof(sel); - sel.dwTimeout = 1000; - sel.hReadConns[0] = m_hGatewayConnection; - if (CallService(MS_NETLIB_SELECT, 0, (LPARAM)&sel) == 0) // timeout, send a hartbeat packet - GatewaySend(3, "{ \"op\":1, \"d\":(null) }"); - unsigned char buf[2048]; - int bufSize = Netlib_Recv(m_hGatewayConnection, (char*)buf+offset, _countof(buf) - offset, 0); + int bufSize = Netlib_Recv(m_hGatewayConnection, (char*)buf + offset, _countof(buf) - offset, 0); if (bufSize == 0) { debugLogA("Gateway connection gracefully closed"); break; @@ -201,16 +204,37 @@ void CDiscordProto::GatewayThread(void*) debugLogA("Got packet: buffer = %d, opcode = %d, headerSize = %d, final = %d, masked = %d", bufSize, opCode, headerSize, bIsFinal, bIsMasked); + // we have some additional data, not only opcode if (bufSize > headerSize) { - size_t newSize = dataBufSize + bufSize - headerSize; - dataBuf = (unsigned char*)mir_realloc(dataBuf, newSize); - memcpy(dataBuf + dataBufSize, buf + headerSize, bufSize - headerSize); - dataBufSize = newSize; - debugLogA("data buffer reallocated to %d bytes", dataBufSize); - } + if (bIsFinal && payloadSize < _countof(buf)) { // it fits, no need to reallocate a buffer + bDataBufAllocated = false; + dataBuf = (char*)buf + headerSize; + dataBufSize = bufSize - headerSize; + } + else { + bDataBufAllocated = true; + size_t newSize = dataBufSize + payloadSize; + size_t currPacketSize = bufSize - headerSize; + dataBuf = (char*)mir_realloc(dataBuf, newSize+1); + memcpy(dataBuf + dataBufSize, buf + headerSize, currPacketSize); + while (currPacketSize < payloadSize) { + int result = Netlib_Recv(m_hGatewayConnection, dataBuf + dataBufSize + currPacketSize, int(payloadSize - currPacketSize), 0); + if (result == 0) { + debugLogA("Gateway connection gracefully closed"); + break; + } + if (result < 0) { + debugLogA("Gateway connection error, exiting"); + break; + } + currPacketSize += result; + } - if (dataBufSize < payloadSize) - continue; + dataBufSize = newSize; + debugLogA("data buffer reallocated to %d bytes", dataBufSize); + } + dataBuf[dataBufSize] = 0; + } switch (opCode){ case 0: // text packet @@ -218,8 +242,9 @@ void CDiscordProto::GatewayThread(void*) case 2: // continuation if (bIsFinal) { // process a packet here - mir_free(dataBuf); dataBuf = NULL; - dataBufSize = 0; + JSONNode root = JSONNode::parse(dataBuf); + if (root) + GatewayProcess(root); } break; @@ -233,9 +258,85 @@ void CDiscordProto::GatewayThread(void*) Netlib_Send(m_hGatewayConnection, (char*)buf + headerSize, bufSize - headerSize, 0); break; } + + if (bIsFinal) { + if (bDataBufAllocated) + mir_free(dataBuf); + dataBuf = NULL; + dataBufSize = 0; + } } Netlib_CloseHandle(m_hGatewayConnection); m_hGatewayConnection = NULL; - ShutdownSession(); +} + +////////////////////////////////////////////////////////////////////////////////////// +// handles server commands + +void CDiscordProto::GatewayProcess(const JSONNode &pRoot) +{ + int opCode = pRoot["op"].as_int(); + switch (opCode) { + case 0: // process incoming command + { + int iSeq = pRoot["s"].as_int(); + if (iSeq != 0) + m_iGatewaySeq = iSeq; + + CMStringW wszCommand = pRoot["t"].as_mstring(); + debugLogA("got a server command to dispatch: %S", wszCommand.c_str()); + + GatewayHandlerFunc pFunc = GetHandler(wszCommand); + if (pFunc) + (this->*pFunc)(pRoot["d"]); + } + break; + + case 10: // hello + m_iHartbeatInterval = pRoot["d"]["heartbeat_interval"].as_int(); + + GatewaySendIdentify(); + break; + + case 11: // heartbeat ack + break; + + default: + debugLogA("ACHTUNG! Unknown opcode: %d, report it to developer", opCode); + } +} + +////////////////////////////////////////////////////////////////////////////////////// +// requests to be sent to a gateway + +void CDiscordProto::GatewaySendHeartbeat() +{ + // we don't send heartbeat packets until we get logged in + if (!m_iHartbeatInterval || !m_iGatewaySeq) + return; + + JSONNode root; + root << INT_PARAM("op", 1) << INT_PARAM("d", m_iGatewaySeq); + GatewaySend(root); +} + +void CDiscordProto::GatewaySendIdentify() +{ + wchar_t wszOs[256]; + GetOSDisplayString(wszOs, _countof(wszOs)); + + char szVersion[256]; + Miranda_GetVersionText(szVersion, _countof(szVersion)); + + JSONNode props; props.set_name("properties"); + props << WCHAR_PARAM("os", wszOs) << CHAR_PARAM("browser", "Chrome") << CHAR_PARAM("device", szVersion) + << CHAR_PARAM("referrer", "http://miranda-ng.org") << CHAR_PARAM("referring_domain", "miranda-ng.org"); + + JSONNode payload; payload.set_name("d"); + payload << CHAR_PARAM("token", m_szAccessToken) << props << BOOL_PARAM("compress", false) << INT_PARAM("large_threshold", 250); + + JSONNode root; + root << INT_PARAM("op", 2) << payload; + GatewaySend(root); } diff --git a/protocols/Discord/src/http.cpp b/protocols/Discord/src/http.cpp index e5941a71a4..a9136c5552 100644 --- a/protocols/Discord/src/http.cpp +++ b/protocols/Discord/src/http.cpp @@ -43,10 +43,14 @@ AsyncHttpRequest::AsyncHttpRequest(CDiscordProto *ppro, int iRequestType, LPCSTR cbSize = sizeof(NETLIBHTTPREQUEST); if (*_url == '/') { // relative url leads to a site - m_szUrl = "https://discordapp.com/api"; + m_szUrl = "https://discordapp.com/api/v6"; m_szUrl += _url; + m_bMainSite = true; + } + else { + m_szUrl = _url; + m_bMainSite = false; } - else m_szUrl = _url; flags = NLHRF_HTTP11 | NLHRF_REDIRECT | NLHRF_SSL; if (ppro->m_szAccessToken != NULL) { @@ -98,6 +102,15 @@ AsyncHttpRequest* operator<<(AsyncHttpRequest *pReq, const INT_PARAM ¶m) return pReq; } +AsyncHttpRequest* operator<<(AsyncHttpRequest *pReq, const INT64_PARAM ¶m) +{ + CMStringA &s = pReq->m_szParam; + if (!s.IsEmpty()) + s.AppendChar('&'); + s.AppendFormat("%s=%lld", param.szName, param.iValue); + return pReq; +} + AsyncHttpRequest* operator<<(AsyncHttpRequest *pReq, const CHAR_PARAM ¶m) { CMStringA &s = pReq->m_szParam; diff --git a/protocols/Discord/src/main.cpp b/protocols/Discord/src/main.cpp index edfac36d0c..850d4a044e 100644 --- a/protocols/Discord/src/main.cpp +++ b/protocols/Discord/src/main.cpp @@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. HINSTANCE g_hInstance; int hLangpack = 0; +HWND g_hwndHeartbeat; PLUGININFOEX pluginInfo = { sizeof(PLUGININFOEX), @@ -68,6 +69,8 @@ extern "C" int __declspec(dllexport) Load(void) { mir_getLP(&pluginInfo); + g_hwndHeartbeat = CreateWindowEx(0, L"STATIC", NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL); + PROTOCOLDESCRIPTOR pd = { 0 }; pd.cbSize = sizeof(pd); pd.szName = "Discord"; @@ -83,5 +86,6 @@ extern "C" int __declspec(dllexport) Load(void) extern "C" int __declspec(dllexport) Unload(void) { + DestroyWindow(g_hwndHeartbeat); return 0; } diff --git a/protocols/Discord/src/options.cpp b/protocols/Discord/src/options.cpp index 2114e47af9..c52424ce6f 100644 --- a/protocols/Discord/src/options.cpp +++ b/protocols/Discord/src/options.cpp @@ -25,8 +25,8 @@ class CDiscardAccountOptions : public CProtoDlgBase<CDiscordProto> ptrW m_wszOldGroup; public: - CDiscardAccountOptions(CDiscordProto *ppro) : - CProtoDlgBase<CDiscordProto>(ppro, IDD_OPTIONS_ACCOUNT), + CDiscardAccountOptions(CDiscordProto *ppro, int iDlgID) : + CProtoDlgBase<CDiscordProto>(ppro, iDlgID, false), m_edGroup(this, IDC_GROUP), m_edUserName(this, IDC_USERNAME), m_edPassword(this, IDC_PASSWORD), @@ -55,6 +55,14 @@ public: ///////////////////////////////////////////////////////////////////////////////////////// +INT_PTR CDiscordProto::SvcCreateAccMgrUI(WPARAM, LPARAM hwndParent) +{ + CDiscardAccountOptions *pDlg = new CDiscardAccountOptions(this, IDD_OPTIONS_ACCMGR); + pDlg->SetParent((HWND)hwndParent); + pDlg->Create(); + return (INT_PTR)pDlg->GetHwnd(); +} + int CDiscordProto::OnOptionsInit(WPARAM wParam, LPARAM) { OPTIONSDIALOGPAGE odp = { 0 }; @@ -65,7 +73,7 @@ int CDiscordProto::OnOptionsInit(WPARAM wParam, LPARAM) odp.position = 1; odp.szTab.w = LPGENW("Account"); - odp.pDialog = new CDiscardAccountOptions(this); + odp.pDialog = new CDiscardAccountOptions(this, IDD_OPTIONS_ACCOUNT); Options_AddPage(wParam, &odp); return 0; } diff --git a/protocols/Discord/src/proto.cpp b/protocols/Discord/src/proto.cpp index f85df56fe3..ea3975a983 100644 --- a/protocols/Discord/src/proto.cpp +++ b/protocols/Discord/src/proto.cpp @@ -33,14 +33,24 @@ CDiscordProto::CDiscordProto(const char *proto_name, const wchar_t *username) : m_evRequestsQueue(CreateEvent(NULL, FALSE, FALSE, NULL)), m_wszDefaultGroup(this, DB_KEY_GROUP, DB_KEYVAL_GROUP), m_wszEmail(this, DB_KEY_EMAIL, L""), + arMarkReadQueue(1, compareUsers), arUsers(50, compareUsers) { // Services CreateProtoService(PS_GETSTATUS, &CDiscordProto::GetStatus); + CreateProtoService(PS_CREATEACCMGRUI, &CDiscordProto::SvcCreateAccMgrUI); + + CreateProtoService(PS_GETAVATARINFO, &CDiscordProto::GetAvatarInfo); + CreateProtoService(PS_GETAVATARCAPS, &CDiscordProto::GetAvatarCaps); + CreateProtoService(PS_GETMYAVATAR, &CDiscordProto::GetMyAvatar); + CreateProtoService(PS_SETMYAVATAR, &CDiscordProto::SetMyAvatar); // Events HookProtoEvent(ME_OPT_INITIALISE, &CDiscordProto::OnOptionsInit); - HookProtoEvent(ME_MSG_WINDOWEVENT, &CDiscordProto::OnSrmmEvent); + HookProtoEvent(ME_DB_EVENT_MARKED_READ, &CDiscordProto::OnDbEventRead); + + // database + db_set_resident(m_szModuleName, "XStatusMsg"); // Clist Clist_GroupCreate(NULL, m_wszDefaultGroup); @@ -91,7 +101,7 @@ DWORD_PTR CDiscordProto::GetCaps(int type, MCONTACT) return PF1_IM | PF1_MODEMSGRECV | PF1_SERVERCLIST | PF1_BASICSEARCH | PF1_EXTSEARCH | PF1_ADDSEARCHRES; case PFLAGNUM_2: case PFLAGNUM_3: - return PF2_ONLINE | PF2_HEAVYDND | PF2_INVISIBLE | PF2_IDLE; + return PF2_ONLINE | PF2_LONGAWAY | PF2_HEAVYDND | PF2_INVISIBLE; case PFLAGNUM_4: return PF4_FORCEADDED | PF4_FORCEAUTH | PF4_NOCUSTOMAUTH | PF4_NOAUTHDENYREASON | PF4_SUPPORTTYPING | PF4_SUPPORTIDLE | PF4_AVATARS | PF4_IMSENDOFFLINE; case PFLAG_UNIQUEIDTEXT: @@ -109,33 +119,34 @@ INT_PTR CDiscordProto::GetStatus(WPARAM, LPARAM) int CDiscordProto::SetStatus(int iNewStatus) { + debugLogA("CDiscordProto::SetStatus iNewStatus = %d, m_iStatus = %d, m_iDesiredStatus = %d m_hWorkerThread = %p", iNewStatus, m_iStatus, m_iDesiredStatus, m_hWorkerThread); + if (iNewStatus == m_iStatus) return 0; m_iDesiredStatus = iNewStatus; int iOldStatus = m_iStatus; - // all statuses but offline are treated as online - if (iNewStatus >= ID_STATUS_ONLINE && iNewStatus <= ID_STATUS_OUTTOLUNCH) { - m_iDesiredStatus = ID_STATUS_ONLINE; - - // if we're already connecting and they want to go online - if (IsStatusConnecting(m_iStatus)) - return 0; - - // if we're already connected, don't try to reconnect - if (m_iStatus >= ID_STATUS_ONLINE && m_iStatus <= ID_STATUS_OUTTOLUNCH) - return 0; + // go offline + if (iNewStatus == ID_STATUS_OFFLINE) { + if (m_bOnline) { + SetServerStatus(ID_STATUS_OFFLINE); + ShutdownSession(); + } + m_iStatus = m_iDesiredStatus; + SetAllContactStatuses(ID_STATUS_OFFLINE); + ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus); + } + // not logged in? come on + else if (m_hWorkerThread == NULL && !IsStatusConnecting(m_iStatus)) { m_iStatus = ID_STATUS_CONNECTING; ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus); m_hWorkerThread = ForkThreadEx(&CDiscordProto::ServerThread, NULL, NULL); } - else if (iNewStatus == ID_STATUS_OFFLINE) { - m_iStatus = m_iDesiredStatus; - SetAllContactStatuses(ID_STATUS_OFFLINE); - - ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus); + else if (m_bOnline) { + debugLogA("setting server online status to %d", iNewStatus); + SetServerStatus(iNewStatus); } return 0; @@ -258,6 +269,92 @@ MCONTACT CDiscordProto::AddToList(int flags, PROTOSEARCHRESULT *psr) return hContact; } +//////////////////////////////////////////////////////////////////////////////////////// +// RecvMsg + +int CDiscordProto::RecvMsg(MCONTACT hContact, PROTORECVEVENT *evt) +{ + T2Utf szResUtf((const wchar_t*)evt->lParam); + evt->pCustomData = (char*)szResUtf; + evt->cbCustomDataSize = (DWORD)mir_strlen(szResUtf); + Proto_RecvMessage(hContact, evt); + return 0; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// SendMsg + +void __cdecl CDiscordProto::SendMessageAckThread(void *param) +{ + Sleep(100); + ProtoBroadcastAck((MCONTACT)param, ACKTYPE_MESSAGE, ACKRESULT_FAILED, (HANDLE)1, (LPARAM)Translate("Protocol is offline or no JID")); +} + +int CDiscordProto::SendMsg(MCONTACT hContact, int /*flags*/, const char *pszSrc) +{ + if (!m_bOnline) { + ForkThread(&CDiscordProto::SendMessageAckThread, (void*)hContact); + return 1; + } + + ptrW wszText(mir_utf8decodeW(pszSrc)); + if (wszText == NULL) + return 0; + + CDiscordUser *pUser = FindUser(getId(hContact, DB_KEY_ID)); + if (pUser == NULL || pUser->channelId == NULL) + return 0; + + CMStringA szUrl(FORMAT, "/channels/%lld/messages", pUser->channelId); + JSONNode body; body << WCHAR_PARAM("content", wszText); + AsyncHttpRequest *pReq = new AsyncHttpRequest(this, REQUEST_POST, szUrl, &CDiscordProto::OnReceiveMessage, &body); + pReq->pUserInfo = (void*)hContact; + Push(pReq); + return pReq->m_iReqNum; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CDiscordProto::MarkReadTimerProc(HWND hwnd, UINT, UINT_PTR id, DWORD) +{ + CDiscordProto *ppro = (CDiscordProto*)(id - 1); + + JSONNode root; root.push_back(JSONNode("token", NULL)); + + mir_cslock lck(ppro->csMarkReadQueue); + while (ppro->arMarkReadQueue.getCount()) { + CDiscordUser *pUser = ppro->arMarkReadQueue[0]; + CMStringA szUrl(FORMAT, "/channels/%lld/messages/%lld/ack", pUser->channelId, pUser->lastMessageId); + ppro->Push(new AsyncHttpRequest(ppro, REQUEST_POST, szUrl, &CDiscordProto::OnReceiveMessageAck)); + ppro->arMarkReadQueue.remove(0); + } + KillTimer(hwnd, id); +} + +int CDiscordProto::OnDbEventRead(WPARAM, LPARAM hDbEvent) +{ + MCONTACT hContact = db_event_getContact(hDbEvent); + if (!hContact) + return 0; + + // filter out only events of my protocol + const char *szProto = GetContactProto(hContact); + if (mir_strcmp(szProto, m_szModuleName)) + return 0; + + if (m_bOnline) { + SetTimer(g_hwndHeartbeat, UINT_PTR(this) + 1, 200, &CDiscordProto::MarkReadTimerProc); + + CDiscordUser *pUser = FindUser(getId(hContact, DB_KEY_ID)); + if (pUser != NULL) { + mir_cslock lck(csMarkReadQueue); + if (arMarkReadQueue.indexOf(pUser) == -1) + arMarkReadQueue.insert(pUser); + } + } + return 0; +} + ///////////////////////////////////////////////////////////////////////////////////////// int CDiscordProto::OnModulesLoaded(WPARAM, LPARAM) @@ -279,23 +376,6 @@ int CDiscordProto::OnPreShutdown(WPARAM, LPARAM) ///////////////////////////////////////////////////////////////////////////////////////// -int CDiscordProto::OnSrmmEvent(WPARAM, LPARAM lParam) -{ - MessageWindowEventData *MWeventdata = (MessageWindowEventData*)lParam; - - if (MWeventdata->uType == MSG_WINDOW_EVT_OPENING && MWeventdata->hContact) { - SnowFlake oldid = getId(MWeventdata->hContact, DB_KEY_LASTMSGID); - if (oldid > 0) - RetrieveHistory(MWeventdata->hContact, MSG_AFTER, oldid, 99); - else - RetrieveHistory(MWeventdata->hContact, MSG_NOFILTER, 0, 99); - } - - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - int CDiscordProto::OnEvent(PROTOEVENTTYPE event, WPARAM wParam, LPARAM lParam) { switch (event) { diff --git a/protocols/Discord/src/proto.h b/protocols/Discord/src/proto.h index b822bbf323..15a6a9eeeb 100644 --- a/protocols/Discord/src/proto.h +++ b/protocols/Discord/src/proto.h @@ -3,6 +3,7 @@ typedef __int64 SnowFlake; class CDiscordProto; typedef void (CDiscordProto::*HttpCallback)(NETLIBHTTPREQUEST*, struct AsyncHttpRequest*); +typedef void (CDiscordProto::*GatewayHandlerFunc)(const JSONNode&); struct AsyncHttpRequest : public NETLIBHTTPREQUEST, public MZeroedObject { @@ -16,6 +17,7 @@ struct AsyncHttpRequest : public NETLIBHTTPREQUEST, public MZeroedObject CMStringA m_szParam; HttpCallback m_pCallback; int m_iErrorCode, m_iReqNum; + bool m_bMainSite; void *pUserInfo; }; @@ -26,6 +28,15 @@ struct PARAM {} }; +struct BOOL_PARAM : public PARAM +{ + bool bValue; + __forceinline BOOL_PARAM(LPCSTR _name, bool _value) : + PARAM(_name), bValue(_value) + {} +}; +AsyncHttpRequest* operator<<(AsyncHttpRequest*, const BOOL_PARAM&); + struct INT_PARAM : public PARAM { int iValue; @@ -35,6 +46,15 @@ struct INT_PARAM : public PARAM }; AsyncHttpRequest* operator<<(AsyncHttpRequest*, const INT_PARAM&); +struct INT64_PARAM : public PARAM +{ + SnowFlake iValue; + __forceinline INT64_PARAM(LPCSTR _name, SnowFlake _value) : + PARAM(_name), iValue(_value) + {} +}; +AsyncHttpRequest* operator<<(AsyncHttpRequest*, const INT64_PARAM&); + struct CHAR_PARAM : public PARAM { LPCSTR szValue; @@ -54,6 +74,7 @@ struct WCHAR_PARAM : public PARAM AsyncHttpRequest* operator<<(AsyncHttpRequest*, const WCHAR_PARAM&); JSONNode& operator<<(JSONNode &json, const INT_PARAM ¶m); +JSONNode& operator<<(JSONNode &json, const BOOL_PARAM ¶m); JSONNode& operator<<(JSONNode &json, const CHAR_PARAM ¶m); JSONNode& operator<<(JSONNode &json, const WCHAR_PARAM ¶m); @@ -91,6 +112,7 @@ class CDiscordProto : public PROTO<CDiscordProto> void __cdecl ServerThread(void*); void __cdecl SearchThread(void *param); + void __cdecl SendMessageAckThread(void* param); ////////////////////////////////////////////////////////////////////////////////////// // session control @@ -119,14 +141,27 @@ class CDiscordProto : public PROTO<CDiscordProto> ////////////////////////////////////////////////////////////////////////////////////// // gateway - CMStringA m_szGateway; + CMStringA + m_szGateway, // gateway url + m_szGatewaySessionId; // current session id + HANDLE m_hGatewayNetlibUser, // the separate netlib user handle for gateways m_hGatewayConnection; // gateway connection + void __cdecl GatewayThread(void*); - void GatewaySend(int opCode, const char*); + void CDiscordProto::GatewayThreadWorker(void); + + void GatewaySend(const JSONNode&, int opCode = 1); + void GatewayProcess(const JSONNode&); - void OnReceiveGateway(NETLIBHTTPREQUEST*, AsyncHttpRequest*); + void GatewaySendHeartbeat(void); + void GatewaySendIdentify(void); + + GatewayHandlerFunc GetHandler(const wchar_t*); + + int m_iHartbeatInterval; // in milliseconds + int m_iGatewaySeq; // gateway sequence number ////////////////////////////////////////////////////////////////////////////////////// // options @@ -139,9 +174,13 @@ class CDiscordProto : public PROTO<CDiscordProto> SnowFlake m_ownId; + mir_cs csMarkReadQueue; + LIST<CDiscordUser> arMarkReadQueue; + OBJLIST<CDiscordUser> arUsers; CDiscordUser* FindUser(SnowFlake id); CDiscordUser* FindUser(const wchar_t *pwszUsername, int iDiscriminator); + CDiscordUser* FindUserByChannel(SnowFlake channelId); CDiscordUser* PrepareUser(const JSONNode&); ////////////////////////////////////////////////////////////////////////////////////// @@ -168,26 +207,45 @@ public: virtual int __cdecl AuthRequest(MCONTACT hContact, const wchar_t*) override; + virtual int __cdecl RecvMsg(MCONTACT hContact, PROTORECVEVENT *evt) override; + virtual int __cdecl SendMsg(MCONTACT hContact, int flags, const char* pszSrc) override; + virtual int __cdecl SetStatus(int iNewStatus) override; virtual int __cdecl OnEvent(PROTOEVENTTYPE, WPARAM, LPARAM) override; // Services + INT_PTR __cdecl SvcCreateAccMgrUI(WPARAM, LPARAM); INT_PTR __cdecl GetStatus(WPARAM, LPARAM); + INT_PTR __cdecl GetAvatarCaps(WPARAM, LPARAM); + INT_PTR __cdecl GetAvatarInfo(WPARAM, LPARAM); + INT_PTR __cdecl GetMyAvatar(WPARAM, LPARAM); + INT_PTR __cdecl SetMyAvatar(WPARAM, LPARAM); + // Events int __cdecl OnModulesLoaded(WPARAM, LPARAM); int __cdecl OnPreShutdown(WPARAM, LPARAM); int __cdecl OnOptionsInit(WPARAM, LPARAM); - int __cdecl OnSrmmEvent(WPARAM, LPARAM); + int __cdecl OnDbEventRead(WPARAM, LPARAM); + + // dispatch commands + void OnCommandMessage(const JSONNode&); + void OnCommandPresence(const JSONNode&); + void OnCommandReady(const JSONNode&); + void OnCommandTyping(const JSONNode&); + void OnCommandUserUpdate(const JSONNode&); void OnLoggedIn(); void OnLoggedOut(); void OnReceiveAuth(NETLIBHTTPREQUEST*, AsyncHttpRequest*); - void OnReceiveToken(NETLIBHTTPREQUEST*, AsyncHttpRequest*); - void OnReceiveGuilds(NETLIBHTTPREQUEST*, AsyncHttpRequest*); void OnReceiveChannels(NETLIBHTTPREQUEST*, AsyncHttpRequest*); void OnReceiveFriends(NETLIBHTTPREQUEST*, AsyncHttpRequest*); + void OnReceiveGateway(NETLIBHTTPREQUEST*, AsyncHttpRequest*); + void OnReceiveGuilds(NETLIBHTTPREQUEST*, AsyncHttpRequest*); + void OnReceiveMessage(NETLIBHTTPREQUEST*, AsyncHttpRequest*); + void OnReceiveMessageAck(NETLIBHTTPREQUEST*, AsyncHttpRequest*); + void OnReceiveToken(NETLIBHTTPREQUEST*, AsyncHttpRequest*); void RetrieveUserInfo(MCONTACT hContact); void OnReceiveUserInfo(NETLIBHTTPREQUEST*, AsyncHttpRequest*); @@ -195,6 +253,16 @@ public: void RetrieveHistory(MCONTACT hContact, CDiscordHitoryOp iOp = MSG_NOFILTER, SnowFlake msgid = 0, int iLimit = 50); void OnReceiveHistory(NETLIBHTTPREQUEST*, AsyncHttpRequest*); + bool RetrieveAvatar(MCONTACT hContact); + void OnReceiveAvatar(NETLIBHTTPREQUEST*, AsyncHttpRequest*); + // Misc void SetServerStatus(int iStatus); + + CMStringW GetAvatarFilename(MCONTACT hContact); + + __forceinline int getHeartbeatInterval() const { return m_iHartbeatInterval; } + + static void CALLBACK HeartbeatTimerProc(HWND hwnd, UINT msg, UINT_PTR id, DWORD); + static void CALLBACK MarkReadTimerProc(HWND hwnd, UINT msg, UINT_PTR id, DWORD); }; diff --git a/protocols/Discord/src/resource.h b/protocols/Discord/src/resource.h index 840b444d17..20a3000f8c 100644 --- a/protocols/Discord/src/resource.h +++ b/protocols/Discord/src/resource.h @@ -1,17 +1,16 @@ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. -// Used by twitter.rc +// Used by w:\miranda-ng\protocols\Discord\res\discord.rc // - #define IDI_MAIN 101 #define IDI_OFFLINE 102 #define IDD_OPTIONS_ACCOUNT 103 #define IDD_EXTSEARCH 104 - -#define IDC_PASSWORD 1001 -#define IDC_USERNAME 1002 -#define IDC_GROUP 1003 -#define IDC_NICK 1004 +#define IDD_OPTIONS_ACCMGR 105 +#define IDC_PASSWORD 1001 +#define IDC_USERNAME 1002 +#define IDC_GROUP 1003 +#define IDC_NICK 1004 // Next default values for new objects // diff --git a/protocols/Discord/src/server.cpp b/protocols/Discord/src/server.cpp index a5ac8e215a..858be58c55 100644 --- a/protocols/Discord/src/server.cpp +++ b/protocols/Discord/src/server.cpp @@ -31,9 +31,9 @@ void CDiscordProto::RetrieveHistory(MCONTACT hContact, CDiscordHitoryOp iOp, Sno pReq << INT_PARAM("limit", iLimit); switch (iOp) { case MSG_AFTER: - pReq << CHAR_PARAM("after", CMStringA(FORMAT, "%lld", msgid)); break; + pReq << INT64_PARAM("after", msgid); break; case MSG_BEFORE: - pReq << CHAR_PARAM("before", CMStringA(FORMAT, "%lld", msgid)); break; + pReq << INT64_PARAM("before", msgid); break; } pReq->pUserInfo = pUser; Push(pReq); @@ -110,17 +110,46 @@ void CDiscordProto::OnReceiveUserInfo(NETLIBHTTPREQUEST *pReply, AsyncHttpReques return; } + ptrW wszOldAvatar(getWStringA(hContact, DB_KEY_AVHASH)); + m_ownId = _wtoi64(root["id"].as_mstring()); setId(hContact, DB_KEY_ID, m_ownId); setByte(hContact, DB_KEY_MFA, root["mfa_enabled"].as_bool()); setDword(hContact, DB_KEY_DISCR, root["discriminator"].as_int()); setWString(hContact, DB_KEY_NICK, root["username"].as_mstring()); - setWString(hContact, DB_KEY_AVHASH, root["avatar"].as_mstring()); setWString(hContact, DB_KEY_EMAIL, root["email"].as_mstring()); - if (hContact == NULL) + CMStringW wszNewAvatar(root["avatar"].as_mstring()); + setWString(hContact, DB_KEY_AVHASH, wszNewAvatar); + + if (hContact == NULL) { + // if avatar's hash changed, we need to request a new one + if (mir_wstrcmp(wszNewAvatar, wszOldAvatar)) + RetrieveAvatar(NULL); + OnLoggedIn(); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// finds a gateway address + +void CDiscordProto::OnReceiveGateway(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*) +{ + if (pReply->resultCode != 200) { + ShutdownSession(); + return; + } + + JSONNode root = JSONNode::parse(pReply->pData); + if (!root) { + ShutdownSession(); + return; + } + + m_szGateway = root["url"].as_mstring(); + ForkThread(&CDiscordProto::GatewayThread, NULL); } ///////////////////////////////////////////////////////////////////////////////////////// @@ -132,6 +161,17 @@ void CDiscordProto::SetServerStatus(int iStatus) if (iStatus == ID_STATUS_OFFLINE) Push(new AsyncHttpRequest(this, REQUEST_POST, "/auth/logout", NULL)); + else { + const char *pszStatus; + switch (iStatus) { + case ID_STATUS_NA: pszStatus = "idle"; break; + case ID_STATUS_DND: pszStatus = "dnd"; break; + case ID_STATUS_INVISIBLE: pszStatus = "invisible"; break; + default: pszStatus = "online"; break; + } + JSONNode root; root << CHAR_PARAM("status", pszStatus); + Push(new AsyncHttpRequest(this, REQUEST_PATCH, "/users/@me/settings", NULL, &root)); + } int iOldStatus = m_iStatus; m_iStatus = iStatus; ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus); @@ -201,6 +241,47 @@ void CDiscordProto::OnReceiveGuilds(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest* ///////////////////////////////////////////////////////////////////////////////////////// +void CDiscordProto::OnReceiveMessage(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) +{ + MCONTACT hContact = (MCONTACT)pReq->pUserInfo; + + bool bSucceeded = true; + if (pReply->resultCode != 200 && pReply->resultCode != 204) + bSucceeded = false; + + JSONNode root = JSONNode::parse(pReply->pData); + if (root) { + SnowFlake newLastId = _wtoi64(root["id"].as_mstring()); + SnowFlake oldLastId = getId(hContact, DB_KEY_LASTMSGID); // as stored in a database + if (oldLastId < newLastId) + setId(hContact, DB_KEY_LASTMSGID, newLastId); + } + + ProtoBroadcastAck(hContact, ACKTYPE_MESSAGE, bSucceeded ? ACKRESULT_SUCCESS : ACKRESULT_FAILED, (HANDLE)pReq->m_iReqNum, 0); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CDiscordProto::OnReceiveMessageAck(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*) +{ + if (pReply->resultCode != 200) + return; + + JSONNode root = JSONNode::parse(pReply->pData); + if (!root) + return; + + CMStringW wszToken(root["token"].as_mstring()); + if (!wszToken.IsEmpty()) { + JSONNode props; props.set_name("properties"); + JSONNode reply; reply << props; + reply << CHAR_PARAM("event", "ack_messages") << WCHAR_PARAM("token", root["token"].as_mstring()); + Push(new AsyncHttpRequest(this, REQUEST_POST, "/track", NULL, &reply)); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + void CDiscordProto::OnReceiveToken(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*) { if (pReply->resultCode != 200) { @@ -224,5 +305,3 @@ LBL_Error: RetrieveUserInfo(NULL); } - -///////////////////////////////////////////////////////////////////////////////////////// diff --git a/protocols/Discord/src/stdafx.h b/protocols/Discord/src/stdafx.h index 43d0077d7c..9bf1ba7cc1 100644 --- a/protocols/Discord/src/stdafx.h +++ b/protocols/Discord/src/stdafx.h @@ -9,6 +9,7 @@ #include <Shlwapi.h> #include <Wincrypt.h> #include <stdio.h> +#include <io.h> #include <direct.h> #include <time.h> @@ -37,9 +38,13 @@ #include <m_utils.h> #include <m_hotkeys.h> #include <m_json.h> +#include <m_avatars.h> #include <win2k.h> +#include "../../libs/zlib/src/zlib.h" + extern HINSTANCE g_hInstance; +extern HWND g_hwndHeartbeat; #include "version.h" #include "proto.h" @@ -58,3 +63,4 @@ extern HINSTANCE g_hInstance; #define DB_KEYVAL_GROUP L"Discord" time_t StringToDate(const CMStringW &str); +int SerialNext(void);
\ No newline at end of file diff --git a/protocols/Discord/src/utils.cpp b/protocols/Discord/src/utils.cpp index 0513e49aac..ac9eb85c11 100644 --- a/protocols/Discord/src/utils.cpp +++ b/protocols/Discord/src/utils.cpp @@ -32,6 +32,12 @@ JSONNode& operator<<(JSONNode &json, const INT_PARAM ¶m) return json; } +JSONNode& operator<<(JSONNode &json, const BOOL_PARAM ¶m) +{ + json.push_back(JSONNode(param.szName, param.bValue)); + return json; +} + JSONNode& operator<<(JSONNode &json, const CHAR_PARAM ¶m) { json.push_back(JSONNode(param.szName, param.szValue)); @@ -64,6 +70,15 @@ time_t StringToDate(const CMStringW &str) ///////////////////////////////////////////////////////////////////////////////////////// +static LONG volatile g_counter = 1; + +int SerialNext() +{ + return InterlockedIncrement(&g_counter); +} + +///////////////////////////////////////////////////////////////////////////////////////// + SnowFlake CDiscordProto::getId(const char *szSetting) { DBVARIANT dbv; @@ -100,6 +115,8 @@ void CDiscordProto::setId(MCONTACT hContact, const char *szSetting, SnowFlake iV ///////////////////////////////////////////////////////////////////////////////////////// +static CDiscordUser *g_myUser = new CDiscordUser(0); + CDiscordUser* CDiscordProto::FindUser(SnowFlake id) { return arUsers.find((CDiscordUser*)&id); @@ -116,9 +133,23 @@ CDiscordUser* CDiscordProto::FindUser(const wchar_t *pwszUsername, int iDiscrimi return NULL; } +CDiscordUser* CDiscordProto::FindUserByChannel(SnowFlake channelId) +{ + for (int i = 0; i < arUsers.getCount(); i++) { + CDiscordUser &p = arUsers[i]; + if (p.channelId == channelId) + return &p; + } + + return NULL; +} + CDiscordUser* CDiscordProto::PrepareUser(const JSONNode &user) { SnowFlake id = _wtoi64(user["id"].as_mstring()); + if (id == m_ownId) + return g_myUser; + int iDiscriminator = _wtoi(user["discriminator"].as_mstring()); CMStringW avatar = user["avatar"].as_mstring(); CMStringW username = user["username"].as_mstring(); @@ -137,6 +168,7 @@ CDiscordUser* CDiscordProto::PrepareUser(const JSONNode &user) MCONTACT hContact = db_add_contact(); Proto_AddToContact(hContact, m_szModuleName); + db_set_ws(hContact, "CList", "Group", m_wszDefaultGroup); setId(hContact, DB_KEY_ID, id); setWString(hContact, DB_KEY_NICK, username); setDword(hContact, DB_KEY_DISCR, iDiscriminator); diff --git a/protocols/Discord/src/version.h b/protocols/Discord/src/version.h index c4be71667a..283aed187b 100644 --- a/protocols/Discord/src/version.h +++ b/protocols/Discord/src/version.h @@ -1,5 +1,5 @@ #define __MAJOR_VERSION 0 -#define __MINOR_VERSION 0 +#define __MINOR_VERSION 1 #define __RELEASE_NUM 0 #define __BUILD_NUM 3 diff --git a/protocols/FacebookRM/src/json.cpp b/protocols/FacebookRM/src/json.cpp index 764ae69c5a..d577fb882c 100644 --- a/protocols/FacebookRM/src/json.cpp +++ b/protocols/FacebookRM/src/json.cpp @@ -403,7 +403,7 @@ void parseAttachments(FacebookProto *proto, std::string *message_text, const JSO } else { *message_text += T2Utf(TranslateT("User sent an unsupported attachment. Open your browser to see it.")); - proto->debugLogA("json::parseAttachments (%s) - Unsupported attachment:\n%s", legacy ? "legacy" : "not legacy", attachments_.as_string()); + proto->debugLogA("json::parseAttachments (%s) - Unsupported attachment:\n%s", legacy ? "legacy" : "not legacy", attachments_.as_string().c_str()); } } @@ -1089,7 +1089,7 @@ int facebook_json_parser::parse_unread_threads(std::string *data, std::vector< s return EXIT_FAILURE; for (auto it = unread_threads.begin(); it != unread_threads.end(); ++it) { - const JSONNode &thread_ids = (*it)["thread_ids"]; + const JSONNode &thread_ids = (*it)["thread_fbids"]; for (auto jt = thread_ids.begin(); jt != thread_ids.end(); ++jt) threads->push_back((*jt).as_string()); diff --git a/protocols/GTalkExt/src/avatar.cpp b/protocols/GTalkExt/src/avatar.cpp index 36c6f4ff6a..621df70a90 100644 --- a/protocols/GTalkExt/src/avatar.cpp +++ b/protocols/GTalkExt/src/avatar.cpp @@ -138,7 +138,7 @@ void SetAvatar(MCONTACT hContact) {
mir_cslock lck(g_csSetAvatar);
- avatarCacheEntry *ava = (avatarCacheEntry*)CallService(MS_AV_GETAVATARBITMAP, hContact, 0);
+ AVATARCACHEENTRY *ava = (AVATARCACHEENTRY*)CallService(MS_AV_GETAVATARBITMAP, hContact, 0);
if (ava && GetFileAttributes(&ava->szFilename[0]) != INVALID_FILE_ATTRIBUTES)
return;
@@ -153,7 +153,7 @@ void SetAvatar(MCONTACT hContact) return;
if (ava)
- CallService(MS_AV_SETAVATART, hContact, (LPARAM)L"");
- CallService(MS_AV_SETAVATART, hContact, (LPARAM)avaFile);
+ CallService(MS_AV_SETAVATARW, hContact, (LPARAM)L"");
+ CallService(MS_AV_SETAVATARW, hContact, (LPARAM)avaFile);
db_set_ws(hContact, SRMM_MODULE_NAME, SRMM_AVATAR_SETTING_NAME, avaFile);
}
diff --git a/protocols/IcqOscarJ/src/icq_http.cpp b/protocols/IcqOscarJ/src/icq_http.cpp index 5c5492cbc3..d9a8e5378a 100644 --- a/protocols/IcqOscarJ/src/icq_http.cpp +++ b/protocols/IcqOscarJ/src/icq_http.cpp @@ -101,7 +101,7 @@ int icq_httpGatewayBegin(HANDLE hConn, NETLIBOPENCONNECTION* nloc) -int icq_httpGatewayWrapSend(HANDLE hConn, PBYTE buf, int len, int flags, MIRANDASERVICE pfnNetlibSend)
+int icq_httpGatewayWrapSend(HANDLE hConn, PBYTE buf, int len, int flags)
{
PBYTE sendBuf = buf;
int sendLen = len;
@@ -119,8 +119,7 @@ int icq_httpGatewayWrapSend(HANDLE hConn, PBYTE buf, int len, int flags, MIRANDA write_httphdr(&packet, HTTP_PACKETTYPE_FLAP, GetGatewayIndex(hConn));
packBuffer(&packet, sendBuf, curLen);
- NETLIBBUFFER nlb={ (char*)packet.pData, packet.wLen, flags };
- curResult = pfnNetlibSend((WPARAM)hConn, (LPARAM)&nlb);
+ curResult = Netlib_Send(hConn, (char*)packet.pData, packet.wLen, flags);
SAFE_FREE((void**)&packet.pData);
diff --git a/protocols/IcqOscarJ/src/icq_http.h b/protocols/IcqOscarJ/src/icq_http.h index fb4b948770..0f2eb6a394 100644 --- a/protocols/IcqOscarJ/src/icq_http.h +++ b/protocols/IcqOscarJ/src/icq_http.h @@ -37,7 +37,7 @@ int icq_httpGatewayInit(HANDLE hConn, NETLIBOPENCONNECTION *nloc, NETLIBHTTPREQUEST *nlhr);
int icq_httpGatewayBegin(HANDLE hConn, NETLIBOPENCONNECTION *nloc);
-int icq_httpGatewayWrapSend(HANDLE hConn, PBYTE buf, int len, int flags, MIRANDASERVICE pfnNetlibSend);
+int icq_httpGatewayWrapSend(HANDLE hConn, PBYTE buf, int len, int flags);
PBYTE icq_httpGatewayUnwrapRecv(NETLIBHTTPREQUEST *nlhr, PBYTE buf, int bufLen, int *outBufLen, void *(*NetlibRealloc)(void *, size_t));
int icq_httpGatewayWalkTo(HANDLE hConn, NETLIBOPENCONNECTION* nloc);
diff --git a/protocols/JabberG/src/jabber_iqid.cpp b/protocols/JabberG/src/jabber_iqid.cpp index a72bbf3eeb..a91cd1d3d4 100644 --- a/protocols/JabberG/src/jabber_iqid.cpp +++ b/protocols/JabberG/src/jabber_iqid.cpp @@ -608,7 +608,7 @@ void CJabberProto::OnIqResultGetVcardPhoto(HXML n, MCONTACT hContact, bool &hasP debugLogA("%d bytes written", nWritten);
if (hContact == NULL) {
hasPhoto = true;
- CallService(MS_AV_SETMYAVATART, (WPARAM)m_szModuleName, (LPARAM)szAvatarFileName);
+ CallService(MS_AV_SETMYAVATARW, (WPARAM)m_szModuleName, (LPARAM)szAvatarFileName);
debugLogW(L"My picture saved to %s", szAvatarFileName);
}
diff --git a/protocols/MSN/src/msn.cpp b/protocols/MSN/src/msn.cpp index 30f52a74f2..6be1bed1dd 100644 --- a/protocols/MSN/src/msn.cpp +++ b/protocols/MSN/src/msn.cpp @@ -79,7 +79,7 @@ extern "C" BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID) // OnModulesLoaded - finalizes plugin's configuration on load
static int OnModulesLoaded(WPARAM, LPARAM)
{
- avsPresent = ServiceExists(MS_AV_SETMYAVATART) != 0;
+ avsPresent = ServiceExists(MS_AV_SETMYAVATARW) != 0;
MsnLinks_Init();
diff --git a/protocols/MSN/src/msn_http.cpp b/protocols/MSN/src/msn_http.cpp index 7ea45b5a8a..74a0f66fb7 100644 --- a/protocols/MSN/src/msn_http.cpp +++ b/protocols/MSN/src/msn_http.cpp @@ -52,7 +52,7 @@ int msn_httpGatewayInit(HANDLE hConn, NETLIBOPENCONNECTION*, NETLIBHTTPREQUEST*) // function generates the initial URL depending on a thread type
//=======================================================================================
-int msn_httpGatewayWrapSend(HANDLE hConn, PBYTE buf, int len, int flags, MIRANDASERVICE pfnNetlibSend)
+int msn_httpGatewayWrapSend(HANDLE hConn, PBYTE buf, int len, int flags)
{
ThreadData *T = FindThreadConn(hConn);
if (T != NULL) {
@@ -62,8 +62,7 @@ int msn_httpGatewayWrapSend(HANDLE hConn, PBYTE buf, int len, int flags, MIRANDA T->applyGatewayData(hConn, len == 0);
}
- NETLIBBUFFER tBuf = { (char*)buf, len, flags };
- return pfnNetlibSend((LPARAM)hConn, WPARAM(&tBuf));
+ return Netlib_Send(hConn, (char*)buf, len, flags);
}
//=======================================================================================
diff --git a/protocols/MSN/src/msn_misc.cpp b/protocols/MSN/src/msn_misc.cpp index 868fa66ba4..2f89d21fbe 100644 --- a/protocols/MSN/src/msn_misc.cpp +++ b/protocols/MSN/src/msn_misc.cpp @@ -753,7 +753,7 @@ void CMsnProto::MSN_SetServerStatus(int newStatus) if (newStatus != ID_STATUS_OFFLINE) {
DBVARIANT msnObject = { 0 };
- if (ServiceExists(MS_AV_SETMYAVATAR))
+ if (ServiceExists(MS_AV_SETMYAVATARW))
getString("PictObject", &msnObject);
// Capabilties: WLM 2009, Chunking, UUN Bootstrap
diff --git a/protocols/MSN/src/msn_proto.cpp b/protocols/MSN/src/msn_proto.cpp index ce4d563de3..1efe0b33d9 100644 --- a/protocols/MSN/src/msn_proto.cpp +++ b/protocols/MSN/src/msn_proto.cpp @@ -25,7 +25,7 @@ static const COLORREF crCols[16] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; int msn_httpGatewayInit(HANDLE hConn,NETLIBOPENCONNECTION *nloc,NETLIBHTTPREQUEST *nlhr);
int msn_httpGatewayBegin(HANDLE hConn,NETLIBOPENCONNECTION *nloc);
-int msn_httpGatewayWrapSend(HANDLE hConn,PBYTE buf,int len,int flags,MIRANDASERVICE pfnNetlibSend);
+int msn_httpGatewayWrapSend(HANDLE hConn,PBYTE buf,int len,int flags);
PBYTE msn_httpGatewayUnwrapRecv(NETLIBHTTPREQUEST *nlhr,PBYTE buf,int len,int *outBufLen,void *(*NetlibRealloc)(void*,size_t));
static int CompareLists(const MsnContact *p1, const MsnContact *p2)
diff --git a/protocols/MSN/src/msn_svcs.cpp b/protocols/MSN/src/msn_svcs.cpp index d4410c4967..a5138006b9 100644 --- a/protocols/MSN/src/msn_svcs.cpp +++ b/protocols/MSN/src/msn_svcs.cpp @@ -118,7 +118,7 @@ INT_PTR CMsnProto::GetAvatarInfo(WPARAM wParam, LPARAM lParam) }
if ((wParam & GAIF_FORCE) != 0 && pai->hContact != NULL) {
- if (avsPresent < 0) avsPresent = ServiceExists(MS_AV_SETMYAVATAR) != 0;
+ if (avsPresent < 0) avsPresent = ServiceExists(MS_AV_SETMYAVATARW) != 0;
if (!avsPresent)
return GAIR_NOAVATAR;
diff --git a/protocols/MSN/src/msn_ws.cpp b/protocols/MSN/src/msn_ws.cpp index d087f579c0..1c7dcd07e9 100644 --- a/protocols/MSN/src/msn_ws.cpp +++ b/protocols/MSN/src/msn_ws.cpp @@ -27,8 +27,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. int ThreadData::send(const char data[], size_t datalen)
{
- NETLIBBUFFER nlb = { (char*)data, (int)datalen, 0 };
-
resetTimeout();
if (proto->usingGateway && !(mType == SERVER_FILETRANS || mType == SERVER_P2P_DIRECT)) {
@@ -36,7 +34,7 @@ int ThreadData::send(const char data[], size_t datalen) CallService(MS_NETLIB_SETPOLLINGTIMEOUT, WPARAM(s), mGatewayTimeout);
}
- int rlen = CallService(MS_NETLIB_SEND, (WPARAM)s, (LPARAM)&nlb);
+ int rlen = Netlib_Send(s, data, (int)datalen);
if (rlen == SOCKET_ERROR) {
// should really also check if sendlen is the same as datalen
proto->debugLogA("Send failed: %d", WSAGetLastError());
@@ -113,8 +111,6 @@ bool ThreadData::isTimeout(void) int ThreadData::recv(char* data, size_t datalen)
{
- NETLIBBUFFER nlb = { data, (int)datalen, 0 };
-
if (!proto->usingGateway) {
resetTimeout();
NETLIBSELECT nls = { 0 };
@@ -137,7 +133,7 @@ int ThreadData::recv(char* data, size_t datalen) }
LBL_RecvAgain:
- int ret = CallService(MS_NETLIB_RECV, (WPARAM)s, (LPARAM)&nlb);
+ int ret = Netlib_Recv(s, data, (int)datalen);
if (ret == 0) {
proto->debugLogA("Connection closed gracefully");
return 0;
diff --git a/protocols/VKontakte/src/version.h b/protocols/VKontakte/src/version.h index b71271e598..7348f04c1f 100644 --- a/protocols/VKontakte/src/version.h +++ b/protocols/VKontakte/src/version.h @@ -1,7 +1,7 @@ #define __MAJOR_VERSION 0
#define __MINOR_VERSION 1
#define __RELEASE_NUM 2
-#define __BUILD_NUM 9
+#define __BUILD_NUM 10
#include <stdver.h>
diff --git a/protocols/VKontakte/src/vk.h b/protocols/VKontakte/src/vk.h index 01e92919c2..9c8b4ed64a 100644 --- a/protocols/VKontakte/src/vk.h +++ b/protocols/VKontakte/src/vk.h @@ -82,7 +82,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. #define VK_USER_DEACTIVATE_ACTION 9321
-#define VK_API_VER "5.60"
+#define VK_API_VER "5.62"
#define VER_API CHAR_PARAM("v", VK_API_VER)
#define VK_FEED_USER 2147483647L
diff --git a/protocols/VKontakte/src/vk_messages.cpp b/protocols/VKontakte/src/vk_messages.cpp index d770180521..84b8075d3f 100644 --- a/protocols/VKontakte/src/vk_messages.cpp +++ b/protocols/VKontakte/src/vk_messages.cpp @@ -142,8 +142,8 @@ int CVkProto::OnDbEventRead(WPARAM, LPARAM hDbEvent) MCONTACT hContact = db_event_getContact(hDbEvent);
if (!hContact)
return 0;
-
- CMStringA szProto(ptrA(db_get_sa(hContact, "Protocol", "p")));
+
+ CMStringA szProto(GetContactProto(hContact));
if (szProto.IsEmpty() || szProto != m_szModuleName)
return 0;
diff --git a/protocols/VKontakte/src/vk_thread.cpp b/protocols/VKontakte/src/vk_thread.cpp index 8de06350ac..e47ca75430 100644 --- a/protocols/VKontakte/src/vk_thread.cpp +++ b/protocols/VKontakte/src/vk_thread.cpp @@ -347,8 +347,7 @@ MCONTACT CVkProto::SetContactInfo(const JSONNode &jnItem, bool flag, bool self) }
int iNewStatus = (jnItem["online"].as_int() == 0) ? ID_STATUS_OFFLINE : ID_STATUS_ONLINE;
- if (getWord(hContact, "Status", ID_STATUS_OFFLINE) != iNewStatus)
- setWord(hContact, "Status", iNewStatus);
+ setWord(hContact, "Status", iNewStatus);
if (iNewStatus == ID_STATUS_ONLINE) {
db_set_dw(hContact, "BuddyExpectator", "LastSeen", (DWORD)time(NULL));
@@ -379,9 +378,7 @@ MCONTACT CVkProto::SetContactInfo(const JSONNode &jnItem, bool flag, bool self) setWString(hContact, "Phone", wszValue);
wszValue = jnItem["status"].as_mstring();
- CMStringW wszOldStatus(ptrW(db_get_wsa(hContact, hContact ? "CList" : m_szModuleName, "StatusMsg")));
- if (wszValue != wszOldStatus)
- db_set_ws(hContact, hContact ? "CList" : m_szModuleName, "StatusMsg", wszValue);
+ db_set_ws(hContact, hContact ? "CList" : m_szModuleName, "StatusMsg", wszValue);
CMStringW wszOldListeningTo(ptrW(db_get_wsa(hContact, m_szModuleName, "ListeningTo")));
const JSONNode &jnAudio = jnItem["status_audio"];
@@ -416,7 +413,7 @@ MCONTACT CVkProto::SetContactInfo(const JSONNode &jnItem, bool flag, bool self) }
// MaritalStatus
- BYTE cMaritalStatus[] = {0, 10, 11, 12, 20, 70, 50, 60};
+ BYTE cMaritalStatus[] = {0, 10, 11, 12, 20, 70, 50, 60, 80};
if (jnItem["relation"] && jnItem["relation"].as_int() < _countof(cMaritalStatus))
setByte(hContact, "MaritalStatus", cMaritalStatus[jnItem["relation"].as_int()]);
@@ -675,8 +672,7 @@ void CVkProto::OnReceiveGroupInfo(NETLIBHTTPREQUEST *reply, AsyncHttpRequest *pR if (!wszValue.IsEmpty())
setWString(hContact, "Nick", wszValue);
- if (getWord(hContact, "Status", ID_STATUS_OFFLINE) != ID_STATUS_ONLINE)
- setWord(hContact, "Status", ID_STATUS_ONLINE);
+ setWord(hContact, "Status", ID_STATUS_ONLINE);
setByte(hContact, "Auth", !bIsMember);
setByte(hContact, "friend", bIsMember);
@@ -700,9 +696,7 @@ void CVkProto::OnReceiveGroupInfo(NETLIBHTTPREQUEST *reply, AsyncHttpRequest *pR }
wszValue = jnItem["status"].as_mstring();
- CMStringW wszOldStatus(ptrW(db_get_wsa(hContact, "CList", "StatusMsg")));
- if (wszValue != wszOldStatus)
- db_set_ws(hContact, "CList", "StatusMsg", wszValue);
+ db_set_ws(hContact, "CList", "StatusMsg", wszValue);
CMStringW wszOldListeningTo(ptrW(db_get_wsa(hContact, m_szModuleName, "ListeningTo")));
const JSONNode &jnAudio = jnItem["status_audio"];
diff --git a/protocols/WhatsApp/src/WASocketConnection.cpp b/protocols/WhatsApp/src/WASocketConnection.cpp index b6ec0c0998..03ebf84ea7 100644 --- a/protocols/WhatsApp/src/WASocketConnection.cpp +++ b/protocols/WhatsApp/src/WASocketConnection.cpp @@ -30,12 +30,7 @@ void WASocketConnection::write(int i) char buffer;
buffer = (char)i;
- NETLIBBUFFER nlb;
- nlb.buf = &buffer;
- nlb.len = 1;
- nlb.flags = MSG_NOHTTPGATEWAYWRAP | MSG_NODUMP;
-
- int result = CallService(MS_NETLIB_SEND, WPARAM(this->hConn), LPARAM(&nlb));
+ int result = Netlib_Send(this->hConn, &buffer, 1, MSG_NOHTTPGATEWAYWRAP | MSG_NODUMP);
if (result < 1)
throw WAException(getLastErrorMsg(), WAException::SOCKET_EX, WAException::SOCKET_EX_SEND);
}
@@ -49,13 +44,8 @@ void WASocketConnection::flush() {} void WASocketConnection::write(const std::vector<unsigned char> &bytes, int length)
{
- NETLIBBUFFER nlb;
std::string tmpBuf = std::string(bytes.begin(), bytes.end());
- nlb.buf = (char*)&(tmpBuf.c_str()[0]);
- nlb.len = length;
- nlb.flags = MSG_NODUMP;
-
- int result = CallService(MS_NETLIB_SEND, WPARAM(hConn), LPARAM(&nlb));
+ int result = Netlib_Send(hConn, tmpBuf.c_str(), length, MSG_NODUMP);
if (result < length)
throw WAException(getLastErrorMsg(), WAException::SOCKET_EX, WAException::SOCKET_EX_SEND);
}
|