summaryrefslogtreecommitdiff
path: root/protocols
diff options
context:
space:
mode:
Diffstat (limited to 'protocols')
-rw-r--r--protocols/Discord/discord.vcxproj6
-rw-r--r--protocols/Discord/proto_discord/res/Away.ico (renamed from protocols/Discord/proto_discord/res/Idle.ico)bin4150 -> 4150 bytes
-rw-r--r--protocols/Discord/proto_discord/res/Proto_Discord.rc1
-rw-r--r--protocols/Discord/res/discord.rc26
-rw-r--r--protocols/Discord/src/avatars.cpp210
-rw-r--r--protocols/Discord/src/connection.cpp37
-rw-r--r--protocols/Discord/src/dispatch.cpp221
-rw-r--r--protocols/Discord/src/gateway.cpp193
-rw-r--r--protocols/Discord/src/http.cpp17
-rw-r--r--protocols/Discord/src/main.cpp4
-rw-r--r--protocols/Discord/src/options.cpp14
-rw-r--r--protocols/Discord/src/proto.cpp150
-rw-r--r--protocols/Discord/src/proto.h80
-rw-r--r--protocols/Discord/src/resource.h13
-rw-r--r--protocols/Discord/src/server.cpp91
-rw-r--r--protocols/Discord/src/stdafx.h6
-rw-r--r--protocols/Discord/src/utils.cpp32
-rw-r--r--protocols/Discord/src/version.h2
-rw-r--r--protocols/FacebookRM/src/json.cpp4
-rw-r--r--protocols/GTalkExt/src/avatar.cpp6
-rw-r--r--protocols/IcqOscarJ/src/icq_http.cpp5
-rw-r--r--protocols/IcqOscarJ/src/icq_http.h2
-rw-r--r--protocols/JabberG/src/jabber_iqid.cpp2
-rw-r--r--protocols/MSN/src/msn.cpp2
-rw-r--r--protocols/MSN/src/msn_http.cpp5
-rw-r--r--protocols/MSN/src/msn_misc.cpp2
-rw-r--r--protocols/MSN/src/msn_proto.cpp2
-rw-r--r--protocols/MSN/src/msn_svcs.cpp2
-rw-r--r--protocols/MSN/src/msn_ws.cpp8
-rw-r--r--protocols/VKontakte/src/version.h2
-rw-r--r--protocols/VKontakte/src/vk.h2
-rw-r--r--protocols/VKontakte/src/vk_messages.cpp4
-rw-r--r--protocols/VKontakte/src/vk_thread.cpp16
-rw-r--r--protocols/WhatsApp/src/WASocketConnection.cpp14
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
index c2830ed132..2a43c57cd8 100644
--- a/protocols/Discord/proto_discord/res/Idle.ico
+++ b/protocols/Discord/proto_discord/res/Away.ico
Binary files differ
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 &param)
return pReq;
}
+AsyncHttpRequest* operator<<(AsyncHttpRequest *pReq, const INT64_PARAM &param)
+{
+ 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 &param)
{
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 &param);
+JSONNode& operator<<(JSONNode &json, const BOOL_PARAM &param);
JSONNode& operator<<(JSONNode &json, const CHAR_PARAM &param);
JSONNode& operator<<(JSONNode &json, const WCHAR_PARAM &param);
@@ -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 &param)
return json;
}
+JSONNode& operator<<(JSONNode &json, const BOOL_PARAM &param)
+{
+ json.push_back(JSONNode(param.szName, param.bValue));
+ return json;
+}
+
JSONNode& operator<<(JSONNode &json, const CHAR_PARAM &param)
{
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);
}