diff options
author | George Hazan <ghazan@miranda.im> | 2017-01-20 23:19:29 +0300 |
---|---|---|
committer | George Hazan <ghazan@miranda.im> | 2017-01-20 23:19:29 +0300 |
commit | 4b3ab972d3109f4014746fc8b1ea5b443697861c (patch) | |
tree | 7b5eb03f1b7c3a66fce857ffd30b5774e2cd268e /protocols/Discord | |
parent | be6e15b71fcf555aad8bfd9054ca66ec71b91408 (diff) |
- valid (but totally undocumented) way of reading guild users;
- fix for decoding several replies from the single packet;
Diffstat (limited to 'protocols/Discord')
-rw-r--r-- | protocols/Discord/src/dispatch.cpp | 103 | ||||
-rw-r--r-- | protocols/Discord/src/gateway.cpp | 167 | ||||
-rw-r--r-- | protocols/Discord/src/proto.h | 7 | ||||
-rw-r--r-- | protocols/Discord/src/stdafx.h | 1 | ||||
-rw-r--r-- | protocols/Discord/src/utils.cpp | 11 |
5 files changed, 197 insertions, 92 deletions
diff --git a/protocols/Discord/src/dispatch.cpp b/protocols/Discord/src/dispatch.cpp index 0011f3becb..102a933aa4 100644 --- a/protocols/Discord/src/dispatch.cpp +++ b/protocols/Discord/src/dispatch.cpp @@ -26,8 +26,10 @@ struct CDiscordCommand } static handlers[] = // these structures must me sorted alphabetically { - { L"CHANNEL_CREATE", &CDiscordProto::OnChannelCreated }, - { L"CHANNEL_DELETE", &CDiscordProto::OnChannelDeleted }, + { L"CHANNEL_CREATE", &CDiscordProto::OnCommandChannelCreated }, + { L"CHANNEL_DELETE", &CDiscordProto::OnCommandChannelDeleted }, + + { L"GUILD_SYNC", &CDiscordProto::OnCommandGuildSync }, { L"MESSAGE_ACK", &CDiscordProto::OnCommandMessageAck }, { L"MESSAGE_CREATE", &CDiscordProto::OnCommandMessage }, @@ -60,7 +62,7 @@ GatewayHandlerFunc CDiscordProto::GetHandler(const wchar_t *pwszCommand) ////////////////////////////////////////////////////////////////////////////////////// // channel operations -void CDiscordProto::OnChannelCreated(const JSONNode &pRoot) +void CDiscordProto::OnCommandChannelCreated(const JSONNode &pRoot) { CDiscordUser *pUser = PrepareUser(pRoot["user"]); if (pUser != NULL) { @@ -69,7 +71,7 @@ void CDiscordProto::OnChannelCreated(const JSONNode &pRoot) } } -void CDiscordProto::OnChannelDeleted(const JSONNode &pRoot) +void CDiscordProto::OnCommandChannelDeleted(const JSONNode &pRoot) { CDiscordUser *pUser = FindUserByChannel(pRoot["channel_id"]); if (pUser != NULL) { @@ -103,6 +105,69 @@ void CDiscordProto::OnCommandFriendRemoved(const JSONNode &pRoot) ////////////////////////////////////////////////////////////////////////////////////// // reading a new message +void CDiscordProto::OnCommandGuildSync(const JSONNode &pRoot) +{ + struct Presence + { + Presence(SnowFlake _id, int status) : + userid(_id), + iStatus(status) + {} + + SnowFlake userid; + int iStatus; + + static int compare(const Presence *p1, const Presence *p2) + { return p1->userid - p2->userid; + } + }; + + OBJLIST<Presence> arPresences(1, &Presence::compare); + const JSONNode &pStatuses = pRoot["presences"]; + for (auto it = pStatuses.begin(); it != pStatuses.end(); ++it) { + const JSONNode &s = *it; + + int iStatus = StrToStatus(s["status"].as_mstring()); + if (iStatus) + arPresences.insert(new Presence(_wtoi64(s["user"]["id"].as_mstring()), iStatus)); + } + + SnowFlake guildId = _wtoi64(pRoot["id"].as_mstring()); + + const JSONNode &pMembers = pRoot["members"]; + for (auto it = pMembers.begin(); it != pMembers.end(); ++it) { + const JSONNode &m = *it; + + for (int i = 0; i < arUsers.getCount(); i++) { + CDiscordUser &pUser = arUsers[i]; + if (pUser.guildId != guildId) + continue; + + GCDEST gcd = { m_szModuleName, pUser.wszUsername, GC_EVENT_JOIN }; + GCEVENT gce = { &gcd }; + + CMStringW wszUsername = m["user"]["username"].as_mstring() + L"#" + m["user"]["discriminator"].as_mstring(); + CMStringW wszUserId = m["user"]["id"].as_mstring(); + SnowFlake userid = _wtoi64(wszUserId); + gce.bIsMe = (userid == m_ownId); + gce.ptszUID = wszUserId; + gce.ptszNick = wszUsername; + Chat_Event(&gce); + + int flags = GC_SSE_ONLYLISTED; + Presence *p = arPresences.find((Presence*)&userid); + if (p && (p->iStatus == ID_STATUS_ONLINE || p->iStatus == ID_STATUS_NA || p->iStatus == ID_STATUS_DND)) + flags += GC_SSE_ONLINE; + else + flags += GC_SSE_OFFLINE; + Chat_SetStatusEx(m_szModuleName, pUser.wszUsername, flags, wszUserId); + } + } +} + +////////////////////////////////////////////////////////////////////////////////////// +// reading a new message + void CDiscordProto::OnCommandMessage(const JSONNode &pRoot) { PROTORECVEVENT recv = {}; @@ -182,17 +247,7 @@ void CDiscordProto::OnCommandPresence(const JSONNode &pRoot) 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; - + int iStatus = StrToStatus(pRoot["status"].as_mstring()); if (iStatus != 0) setWord(pUser->hContact, "Status", iStatus); @@ -228,13 +283,14 @@ void CDiscordProto::OnCommandReady(const JSONNode &pRoot) for (auto it = guilds.begin(); it != guilds.end(); ++it) { const JSONNode &p = *it; + SnowFlake guildId = _wtoi64(p["id"].as_mstring()); + GatewaySendGuildInfo(guildId); CMStringW wszGuildName = p["name"].as_mstring(); GCSessionInfoBase *si = Chat_NewSession(GCW_SERVER, m_szModuleName, wszGuildName, wszGuildName); Chat_Control(m_szModuleName, wszGuildName, WINDOW_HIDDEN); Chat_Control(m_szModuleName, wszGuildName, SESSION_ONLINE); - const JSONNode &members = p["members"]; const JSONNode &channels = p["channels"]; for (auto itc = channels.begin(); itc != channels.end(); ++itc) { const JSONNode &pch = *itc; @@ -259,22 +315,11 @@ void CDiscordProto::OnCommandReady(const JSONNode &pRoot) pUser->channelId = channelId; arUsers.insert(pUser); } + pUser->wszUsername = wszChannelId; + pUser->guildId = guildId; pUser->lastMessageId = _wtoi64(pch["last_message_id"].as_mstring()); setId(pUser->hContact, DB_KEY_CHANNELID, channelId); - - GCDEST gcd = { m_szModuleName, wszChannelId, GC_EVENT_JOIN }; - GCEVENT gce = { &gcd }; - for (auto itu = members.begin(); itu != members.end(); ++itu) { - const JSONNode &pu = *itu; - - CMStringW username = pu["user"]["username"].as_mstring() + L"#" + pu["user"]["discriminator"].as_mstring(); - CMStringW userid = pu["user"]["id"].as_mstring(); - gce.bIsMe = _wtoi64(userid) == m_ownId; - gce.ptszUID = userid; - gce.ptszNick = username; - Chat_Event(&gce); - } } } diff --git a/protocols/Discord/src/gateway.cpp b/protocols/Discord/src/gateway.cpp index d17ccf4952..694e30982c 100644 --- a/protocols/Discord/src/gateway.cpp +++ b/protocols/Discord/src/gateway.cpp @@ -17,6 +17,53 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. #include "stdafx.h" +struct WSHeader +{ + WSHeader() + { + memset(this, 0, sizeof(*this)); + } + + bool init(BYTE *buf, int bufSize) + { + bIsFinal = (buf[0] & 0x80) != 0; + bIsMasked = (buf[1] & 0x80) != 0; + opCode = buf[0] & 0x0F; + firstByte = buf[1] & 0x7F; + headerSize = 2 + (firstByte == 0x7E ? 2 : 0) + (firstByte == 0x7F ? 8 : 0) + (bIsMasked ? 4 : 0); + if (bufSize < headerSize) + return false; + + payloadSize = 0; + switch (firstByte) { + case 0x7F: + payloadSize += ((uint64_t)buf[2]) << 56; + payloadSize += ((uint64_t)buf[3]) << 48; + payloadSize += ((uint64_t)buf[4]) << 40; + payloadSize += ((uint64_t)buf[5]) << 32; + payloadSize += ((uint64_t)buf[6]) << 24; + payloadSize += ((uint64_t)buf[7]) << 16; + payloadSize += ((uint64_t)buf[8]) << 8; + payloadSize += ((uint64_t)buf[9]); + break; + + case 0x7E: + payloadSize += ((uint64_t)buf[2]) << 8; + payloadSize += ((uint64_t)buf[3]); + break; + + default: + payloadSize = firstByte; + } + return true; + } + + bool bIsFinal, bIsMasked; + int opCode, firstByte; + int headerSize; + uint64_t payloadSize; +}; + ////////////////////////////////////////////////////////////////////////////////////// // sends a piece of JSON to a server via a websocket, masked @@ -152,7 +199,7 @@ void CDiscordProto::GatewayThreadWorker() break; 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, MSG_NODUMP); if (bufSize == 0) { debugLogA("Gateway connection gracefully closed"); break; @@ -167,58 +214,29 @@ void CDiscordProto::GatewayThreadWorker() } offset = 0; - bool bIsFinal = (buf[0] & 0x80) != 0; - bool bIsMasked = (buf[1] & 0x80) != 0; - int opCode = buf[0] & 0x0F, firstByte = buf[1] & 0x7F; - int headerSize = 2 + (firstByte == 0x7E ? 2 : 0) + (firstByte == 0x7F ? 8 : 0) + (bIsMasked ? 4 : 0); - if (bufSize < headerSize) { + WSHeader hdr; + if (!hdr.init(buf, bufSize)) { offset = bufSize; continue; } - int dataShift; - uint64_t payloadSize = 0; - switch (firstByte) { - case 0x7F: - payloadSize += ((uint64_t)buf[2]) << 56; - payloadSize += ((uint64_t)buf[3]) << 48; - payloadSize += ((uint64_t)buf[4]) << 40; - payloadSize += ((uint64_t)buf[5]) << 32; - payloadSize += ((uint64_t)buf[6]) << 24; - payloadSize += ((uint64_t)buf[7]) << 16; - payloadSize += ((uint64_t)buf[8]) << 8; - payloadSize += ((uint64_t)buf[9]); - dataShift = 10; - break; - - case 0x7E: - payloadSize += ((uint64_t)buf[2]) << 8; - payloadSize += ((uint64_t)buf[3]); - dataShift = 4; - break; - - default: - payloadSize = firstByte; - dataShift = 2; - } - - debugLogA("Got packet: buffer = %d, opcode = %d, headerSize = %d, final = %d, masked = %d", bufSize, opCode, headerSize, bIsFinal, bIsMasked); + debugLogA("Got packet: buffer = %d, opcode = %d, headerSize = %d, final = %d, masked = %d", bufSize, hdr.opCode, hdr.headerSize, hdr.bIsFinal, hdr.bIsMasked); // we have some additional data, not only opcode - if (bufSize > headerSize) { - if (bIsFinal && payloadSize < _countof(buf)) { // it fits, no need to reallocate a buffer + if (bufSize > hdr.headerSize) { + if (hdr.bIsFinal && hdr.payloadSize < _countof(buf)) { // it fits, no need to reallocate a buffer bDataBufAllocated = false; - dataBuf = (char*)buf + headerSize; - dataBufSize = bufSize - headerSize; + dataBuf = (char*)buf + hdr.headerSize; + dataBufSize = bufSize - hdr.headerSize; } else { bDataBufAllocated = true; - size_t newSize = dataBufSize + payloadSize; - size_t currPacketSize = bufSize - headerSize; + size_t newSize = dataBufSize + hdr.payloadSize; + size_t currPacketSize = bufSize - hdr.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); + memcpy(dataBuf + dataBufSize, buf + hdr.headerSize, currPacketSize); + while (currPacketSize < hdr.payloadSize) { + int result = Netlib_Recv(m_hGatewayConnection, dataBuf + dataBufSize + currPacketSize, int(hdr.payloadSize - currPacketSize), MSG_NODUMP); if (result == 0) { debugLogA("Gateway connection gracefully closed"); break; @@ -236,30 +254,47 @@ void CDiscordProto::GatewayThreadWorker() dataBuf[dataBufSize] = 0; } - switch (opCode){ - case 0: // text packet - case 1: // binary packet - case 2: // continuation - if (bIsFinal) { - // process a packet here - JSONNode root = JSONNode::parse(dataBuf); - if (root) - GatewayProcess(root); + // read all payloads from the current buffer, one by one + int iOffset = 0; + while (true) { + switch (hdr.opCode) { + case 0: // text packet + case 1: // binary packet + case 2: // continuation + if (hdr.bIsFinal) { + // process a packet here + char c = dataBuf[iOffset + hdr.payloadSize + 1]; dataBuf[iOffset + hdr.payloadSize + 1] = 0; + debugLogA("JSON received:\n%s", dataBuf + iOffset); + dataBuf[iOffset + hdr.payloadSize + 1] = c; + + JSONNode root = JSONNode::parse(dataBuf + iOffset); + if (root) + GatewayProcess(root); + } + break; + + case 8: // close + debugLogA("server required to exit"); + bExit = true; + break; + + case 9: // ping + debugLogA("ping received"); + Netlib_Send(m_hGatewayConnection, (char*)buf + hdr.headerSize, bufSize - hdr.headerSize, 0); + break; } - break; - case 8: // close - debugLogA("server required to exit", dataBufSize); - bExit = true; - break; + if (iOffset + hdr.payloadSize >= dataBufSize) + break; - case 9: // ping - debugLogA("ping received", dataBufSize); - Netlib_Send(m_hGatewayConnection, (char*)buf + headerSize, bufSize - headerSize, 0); - break; + iOffset += hdr.payloadSize; + if (!hdr.init((BYTE*)dataBuf + iOffset, (int)dataBufSize - iOffset)) + break; + + iOffset += hdr.headerSize; } - if (bIsFinal) { + if (hdr.bIsFinal) { if (bDataBufAllocated) mir_free(dataBuf); dataBuf = NULL; @@ -340,3 +375,13 @@ void CDiscordProto::GatewaySendIdentify() root << INT_PARAM("op", 2) << payload; GatewaySend(root); } + +void CDiscordProto::GatewaySendGuildInfo(SnowFlake id) +{ + JSONNode payload(JSON_ARRAY); payload.set_name("d"); + payload << INT64_PARAM("", id); + + JSONNode root; + root << INT_PARAM("op", 12) << payload; + GatewaySend(root); +} diff --git a/protocols/Discord/src/proto.h b/protocols/Discord/src/proto.h index 1b4e65d9b1..f4ec8055bc 100644 --- a/protocols/Discord/src/proto.h +++ b/protocols/Discord/src/proto.h @@ -95,6 +95,7 @@ struct CDiscordUser : public MZeroedObject SnowFlake id; MCONTACT hContact; + SnowFlake guildId; SnowFlake channelId; SnowFlake lastMessageId; bool bIsPrivate; @@ -156,6 +157,7 @@ class CDiscordProto : public PROTO<CDiscordProto> void GatewaySendHeartbeat(void); void GatewaySendIdentify(void); + void GatewaySendGuildInfo(SnowFlake id); GatewayHandlerFunc GetHandler(const wchar_t*); @@ -235,8 +237,9 @@ public: int __cdecl GroupchatMenuHook(WPARAM, LPARAM); // dispatch commands - void OnChannelCreated(const JSONNode&); - void OnChannelDeleted(const JSONNode&); + void OnCommandChannelCreated(const JSONNode&); + void OnCommandChannelDeleted(const JSONNode&); + void OnCommandGuildSync(const JSONNode&); void OnCommandFriendAdded(const JSONNode&); void OnCommandFriendRemoved(const JSONNode&); void OnCommandMessage(const JSONNode&); diff --git a/protocols/Discord/src/stdafx.h b/protocols/Discord/src/stdafx.h index 42bb766230..6857b6f9f4 100644 --- a/protocols/Discord/src/stdafx.h +++ b/protocols/Discord/src/stdafx.h @@ -63,5 +63,6 @@ extern HWND g_hwndHeartbeat; #define DB_KEY_GROUP "GroupName" #define DB_KEYVAL_GROUP L"Discord" +int StrToStatus(const CMStringW &str); 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 e63df2f826..38b4af4bdd 100644 --- a/protocols/Discord/src/utils.cpp +++ b/protocols/Discord/src/utils.cpp @@ -24,6 +24,17 @@ void CDiscordProto::SetAllContactStatuses(int status) setWord(hContact, "Status", (WORD)status); } +int StrToStatus(const CMStringW &str) +{ + if (str == L"idle") + return ID_STATUS_IDLE; + if (str == L"online") + return ID_STATUS_ONLINE; + if (str == L"offline") + return ID_STATUS_OFFLINE; + return 0; +} + ///////////////////////////////////////////////////////////////////////////////////////// JSONNode& operator<<(JSONNode &json, const INT_PARAM ¶m) |