summaryrefslogtreecommitdiff
path: root/protocols/Discord
diff options
context:
space:
mode:
authorGeorge Hazan <ghazan@miranda.im>2017-01-20 23:19:29 +0300
committerGeorge Hazan <ghazan@miranda.im>2017-01-20 23:19:29 +0300
commit4b3ab972d3109f4014746fc8b1ea5b443697861c (patch)
tree7b5eb03f1b7c3a66fce857ffd30b5774e2cd268e /protocols/Discord
parentbe6e15b71fcf555aad8bfd9054ca66ec71b91408 (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.cpp103
-rw-r--r--protocols/Discord/src/gateway.cpp167
-rw-r--r--protocols/Discord/src/proto.h7
-rw-r--r--protocols/Discord/src/stdafx.h1
-rw-r--r--protocols/Discord/src/utils.cpp11
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 &param)