summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--protocols/Discord/src/dispatch.cpp306
-rw-r--r--protocols/Discord/src/groupchat.cpp9
-rw-r--r--protocols/Discord/src/guilds.cpp177
-rw-r--r--protocols/Discord/src/menus.cpp34
-rw-r--r--protocols/Discord/src/proto.cpp8
-rw-r--r--protocols/Discord/src/proto.h61
-rw-r--r--protocols/Discord/src/stdafx.h1
-rw-r--r--protocols/Discord/src/version.h4
8 files changed, 389 insertions, 211 deletions
diff --git a/protocols/Discord/src/dispatch.cpp b/protocols/Discord/src/dispatch.cpp
index d960bf1cac..6a8992322b 100644
--- a/protocols/Discord/src/dispatch.cpp
+++ b/protocols/Discord/src/dispatch.cpp
@@ -76,12 +76,26 @@ GatewayHandlerFunc CDiscordProto::GetHandler(const wchar_t *pwszCommand)
void CDiscordProto::OnCommandChannelCreated(const JSONNode &pRoot)
{
- const JSONNode &members = pRoot["recipients"];
- for (auto it = members.begin(); it != members.end(); ++it) {
- CDiscordUser *pUser = PrepareUser(*it);
- pUser->lastMsg = CDiscordMessage(::getId(pRoot["last_message_id"]));
- pUser->channelId = ::getId(pRoot["id"]);
- setId(pUser->hContact, DB_KEY_CHANNELID, pUser->channelId);
+ SnowFlake guildId = ::getId(pRoot["guild_id"]);
+ if (guildId == 0) {
+ // private channel, created for a contact
+ const JSONNode &members = pRoot["recipients"];
+ for (auto it = members.begin(); it != members.end(); ++it) {
+ CDiscordUser *pUser = PrepareUser(*it);
+ pUser->lastMsg = CDiscordMessage(::getId(pRoot["last_message_id"]));
+ pUser->channelId = ::getId(pRoot["id"]);
+ setId(pUser->hContact, DB_KEY_CHANNELID, pUser->channelId);
+ }
+ }
+ else {
+ CDiscordGuild *pGuild = FindGuild(guildId);
+ if (pGuild == nullptr)
+ return;
+
+ // group channel for a guild
+ CDiscordUser *pUser = ProcessGuildChannel(pGuild, pRoot);
+ if (pUser != nullptr)
+ ApplyUsersToChannel(pGuild, *pUser);
}
}
@@ -141,205 +155,104 @@ void CDiscordProto::OnCommandFriendRemoved(const JSONNode &pRoot)
/////////////////////////////////////////////////////////////////////////////////////////
// guild synchronization
-static int sttGetPresence(const JSONNode &pStatuses, const CMStringW &wszId)
-{
- for (auto it = pStatuses.begin(); it != pStatuses.end(); ++it) {
- const JSONNode &s = *it;
-
- CMStringW wszUserId = s["user"]["id"].as_mstring();
- if (wszUserId == wszId)
- return StrToStatus(s["status"].as_mstring());
- }
-
- return 0;
-}
-
-static SnowFlake sttGetLastRead(const JSONNode &reads, const wchar_t *wszChannelId)
-{
- for (auto it = reads.begin(); it != reads.end(); ++it) {
- const JSONNode &p = *it;
-
- if (p["id"].as_mstring() == wszChannelId)
- return ::getId(p["last_message_id"]);
- }
- return 0;
-}
-
-void CDiscordProto::ProcessRole(SnowFlake guildId, const JSONNode &role)
-{
- SnowFlake id = ::getId(role["id"]);
- CDiscordRole *p = arRoles.find((CDiscordRole*)&id);
- if (p == nullptr) {
- p = new CDiscordRole();
- p->id = id;
- arRoles.insert(p);
- }
- p->guildId = guildId;
- p->color = role["color"].as_int();
- p->position = role["position"].as_int();
- p->permissions = role["permissions"].as_int();
- p->wszName = role["name"].as_mstring();
-}
-
-void CDiscordProto::ProcessGuild(const JSONNode &readState, const JSONNode &p)
-{
- SnowFlake guildId = ::getId(p["id"]), ownerId = ::getId(p["owner_id"]);
- 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);
- setId(si->hContact, DB_KEY_CHANNELID, guildId);
-
- const JSONNode &roles = p["roles"];
- for (auto itr = roles.begin(); itr != roles.end(); ++itr)
- ProcessRole(guildId, *itr);
-
- const JSONNode &channels = p["channels"];
- for (auto itc = channels.begin(); itc != channels.end(); ++itc) {
- const JSONNode &pch = *itc;
- if (pch["type"].as_int() != 0)
- continue;
-
- CMStringW wszChannelName = pch["name"].as_mstring();
- CMStringW wszChannelId = pch["id"].as_mstring();
- CMStringW wszTopic = pch["topic"].as_mstring();
- SnowFlake channelId = _wtoi64(wszChannelId);
-
- si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, wszChannelId, wszGuildName + L"#" + wszChannelName);
- setId(si->hContact, DB_KEY_OWNERID, ownerId);
- BuildStatusList(guildId, wszChannelId);
-
- Chat_Control(m_szModuleName, wszChannelId, WINDOW_HIDDEN);
- Chat_Control(m_szModuleName, wszChannelId, SESSION_ONLINE);
-
- if (!wszTopic.IsEmpty()) {
- Chat_SetStatusbarText(m_szModuleName, wszChannelId, wszTopic);
-
- GCDEST gcd = { m_szModuleName, wszChannelId, GC_EVENT_TOPIC };
- GCEVENT gce = { &gcd };
- gce.time = time(0);
- gce.ptszText = wszTopic;
- Chat_Event(&gce);
- }
-
- CDiscordUser *pUser = FindUserByChannel(channelId);
- if (pUser == NULL) {
- // missing channel - create it
- pUser = new CDiscordUser(channelId);
- pUser->bIsPrivate = false;
- pUser->hContact = si->hContact;
- pUser->id = channelId;
- pUser->channelId = channelId;
- arUsers.insert(pUser);
- }
- pUser->wszUsername = wszChannelId;
- pUser->guildId = guildId;
- pUser->lastMsg = CDiscordMessage(::getId(pch["last_message_id"]));
- pUser->lastReadId = sttGetLastRead(readState, wszChannelId);
-
- setId(pUser->hContact, DB_KEY_ID, channelId);
- setId(pUser->hContact, DB_KEY_CHANNELID, channelId);
-
- SnowFlake oldMsgId = getId(pUser->hContact, DB_KEY_LASTMSGID);
- if (oldMsgId != 0 && pUser->lastMsg.id > oldMsgId)
- RetrieveHistory(pUser->hContact, MSG_AFTER, oldMsgId, 99);
- }
-}
-
void CDiscordProto::OnCommandGuildCreated(const JSONNode &pRoot)
{
- ProcessGuild(JSONNode(), pRoot);
+ ProcessGuild(pRoot);
OnCommandGuildSync(pRoot);
}
void CDiscordProto::OnCommandGuildSync(const JSONNode &pRoot)
{
- const JSONNode &pStatuses = pRoot["presences"];
- const JSONNode &pMembers = pRoot["members"];
-
- SnowFlake guildId = ::getId(pRoot["id"]);
-
- for (int i = 0; i < arUsers.getCount(); i++) {
- CDiscordUser &pUser = arUsers[i];
- if (pUser.guildId != guildId)
- continue;
-
- SESSION_INFO *si = pci->SM_FindSession(pUser.wszUsername, m_szModuleName);
- if (si == NULL)
- continue;
+ CDiscordGuild *pGuild = FindGuild(::getId(pRoot["id"]));
+ if (pGuild == nullptr)
+ return;
- GCDEST gcd = { m_szModuleName, pUser.wszUsername, GC_EVENT_JOIN };
- GCEVENT gce = { &gcd };
- gce.time = time(0);
- gce.dwFlags = GCEF_SILENT;
+ // store all guild members
+ const JSONNode &pMembers = pRoot["members"];
+ for (auto it = pMembers.begin(); it != pMembers.end(); ++it) {
+ const JSONNode &m = *it;
+
+ CMStringW wszUserId = m["user"]["id"].as_mstring();
+ SnowFlake userId = _wtoi64(wszUserId);
+ CDiscordGuildMember *pm = pGuild->FindUser(userId);
+ if (pm == nullptr) {
+ pm = new CDiscordGuildMember(userId);
+ pGuild->arChatUsers.insert(pm);
+ }
- for (auto it = pMembers.begin(); it != pMembers.end(); ++it) {
- const JSONNode &m = *it;
+ pm->wszNick = m["nick"].as_mstring();
+ if (pm->wszNick.IsEmpty())
+ pm->wszNick = m["user"]["username"].as_mstring() + L"#" + m["user"]["discriminator"].as_mstring();
+ if (userId == pGuild->ownerId)
+ pm->wszRole = L"@owner";
+ else {
CDiscordRole *pRole = nullptr;
const JSONNode &pRoles = m["roles"];
for (auto itr = pRoles.begin(); itr != pRoles.end(); ++itr) {
SnowFlake roleId = ::getId(*itr);
- if (pRole = arRoles.find((CDiscordRole*)&roleId))
+ if (pRole = pGuild->arRoles.find((CDiscordRole*)&roleId))
break;
}
-
- CMStringW wszNick = m["nick"].as_mstring();
- CMStringW wszUsername = m["user"]["username"].as_mstring() + L"#" + m["user"]["discriminator"].as_mstring();
- CMStringW wszUserId = m["user"]["id"].as_mstring();
- SnowFlake userId = _wtoi64(wszUserId);
- if (userId == getId(pUser.hContact, DB_KEY_OWNERID))
- gce.ptszStatus = L"@owner";
- else
- gce.ptszStatus = (pRole == nullptr) ? L"@everyone" : pRole->wszName;
-
- gce.bIsMe = (userId == m_ownId);
- gce.ptszUID = wszUserId;
- gce.ptszNick = wszNick.IsEmpty() ? wszUsername : wszNick;
- Chat_Event(&gce);
-
- int flags = GC_SSE_ONLYLISTED;
- switch (sttGetPresence(pStatuses, wszUserId)) {
- case ID_STATUS_ONLINE: case ID_STATUS_NA: case ID_STATUS_DND:
- flags += GC_SSE_ONLINE;
- break;
- default:
- flags += GC_SSE_OFFLINE;
- }
- Chat_SetStatusEx(m_szModuleName, pUser.wszUsername, flags, wszUserId);
+ pm->wszRole = (pRole == nullptr) ? L"@everyone" : pRole->wszName;
}
+ pm->iStatus = ID_STATUS_OFFLINE;
+ }
+
+ // parse online statuses
+ const JSONNode &pStatuses = pRoot["presences"];
+ for (auto it = pStatuses.begin(); it != pStatuses.end(); ++it) {
+ const JSONNode &s = *it;
+ CDiscordGuildMember *gm = pGuild->FindUser(::getId(s["user"]["id"]));
+ if (gm != nullptr)
+ gm->iStatus = StrToStatus(s["status"].as_mstring());
+ }
+
+ // append users to all chat rooms
+ for (int i = 0; i < arUsers.getCount(); i++) {
+ CDiscordUser &pUser = arUsers[i];
+ if (pUser.guildId == pGuild->id)
+ ApplyUsersToChannel(pGuild, pUser);
}
}
void CDiscordProto::OnCommandGuildDeleted(const JSONNode &pRoot)
{
- SnowFlake guildId = ::getId(pRoot["id"]);
+ CDiscordGuild *pGuild = FindGuild(::getId(pRoot["id"]));
+ if (pGuild == nullptr)
+ return;
for (int i = arUsers.getCount()-1; i >= 0; i--) {
CDiscordUser &pUser = arUsers[i];
- if (pUser.guildId == guildId) {
+ if (pUser.guildId == pGuild->id) {
Chat_Terminate(m_szModuleName, pUser.wszUsername, true);
arUsers.remove(i);
}
}
Chat_Terminate(m_szModuleName, pRoot["name"].as_mstring(), true);
+
+ arGuilds.remove(pGuild);
}
+/////////////////////////////////////////////////////////////////////////////////////////
+// guild members
+
void CDiscordProto::OnCommandGuildMemberAdded(const JSONNode &pRoot)
{
}
void CDiscordProto::OnCommandGuildMemberRemoved(const JSONNode &pRoot)
{
- SnowFlake guildId = ::getId(pRoot["guild_id"]);
+ CDiscordGuild *pGuild = FindGuild(::getId(pRoot["guild_id"]));
+ if (pGuild == nullptr)
+ return;
+
CMStringW wszUserId = pRoot["user"]["id"].as_mstring();
for (int i = 0; i < arUsers.getCount(); i++) {
CDiscordUser &pUser = arUsers[i];
- if (pUser.guildId != guildId)
+ if (pUser.guildId != pGuild->id)
continue;
GCDEST gcd = { m_szModuleName, pUser.wszUsername, GC_EVENT_PART };
@@ -352,17 +265,25 @@ void CDiscordProto::OnCommandGuildMemberRemoved(const JSONNode &pRoot)
void CDiscordProto::OnCommandGuildMemberUpdated(const JSONNode &pRoot)
{
- SnowFlake guildId = ::getId(pRoot["guild_id"]);
+ CDiscordGuild *pGuild = FindGuild(::getId(pRoot["guild_id"]));
+ if (pGuild == nullptr)
+ return;
+
CMStringW wszUserId = pRoot["user"]["id"].as_mstring();
- CMStringW wszUserNick = pRoot["nick"].as_mstring(), wszOldNick;
- if (wszUserNick.IsEmpty())
- wszUserNick = pRoot["user"]["username"].as_mstring() + L"#" + pRoot["user"]["discriminator"].as_mstring();
+ CDiscordGuildMember *gm = pGuild->FindUser(_wtoi64(wszUserId));
+ if (gm == NULL)
+ return;
+
+ gm->wszNick = pRoot["nick"].as_mstring();
+ if (gm->wszNick.IsEmpty())
+ gm->wszNick = pRoot["user"]["username"].as_mstring() + L"#" + pRoot["user"]["discriminator"].as_mstring();
for (int i = 0; i < arUsers.getCount(); i++) {
CDiscordUser &pUser = arUsers[i];
- if (pUser.guildId != guildId)
+ if (pUser.guildId != pGuild->id)
continue;
+ CMStringW wszOldNick;
SESSION_INFO *si = pci->SM_FindSession(pUser.wszUsername, m_szModuleName);
if (si != nullptr) {
USERINFO *ui = pci->UM_FindUser(si->pUsers, wszUserId);
@@ -375,7 +296,7 @@ void CDiscordProto::OnCommandGuildMemberUpdated(const JSONNode &pRoot)
gce.time = time(0);
gce.ptszUID = wszUserId;
gce.ptszNick = wszOldNick;
- gce.ptszText = wszUserNick;
+ gce.ptszText = gm->wszNick;
Chat_Event(&gce);
}
}
@@ -385,34 +306,40 @@ void CDiscordProto::OnCommandGuildMemberUpdated(const JSONNode &pRoot)
void CDiscordProto::OnCommandRoleCreated(const JSONNode &pRoot)
{
- ProcessRole(::getId(pRoot["guild_id"]), pRoot["role"]);
+ CDiscordGuild *pGuild = FindGuild(::getId(pRoot["guild_id"]));
+ if (pGuild != nullptr)
+ ProcessRole(pGuild, pRoot["role"]);
}
void CDiscordProto::OnCommandRoleDeleted(const JSONNode &pRoot)
{
- SnowFlake id = ::getId(pRoot["role_id"]), guildId = ::getId(pRoot["guild_id"]);
- CDiscordRole *pRole = arRoles.find((CDiscordRole*)&id);
+ CDiscordGuild *pGuild = FindGuild(::getId(pRoot["guild_id"]));
+ if (pGuild == nullptr)
+ return;
+
+ SnowFlake id = ::getId(pRoot["role_id"]);
+ CDiscordRole *pRole = pGuild->arRoles.find((CDiscordRole*)&id);
if (pRole == nullptr)
return;
int iOldPosition = pRole->position;
- arRoles.remove(pRole);
+ pGuild->arRoles.remove(pRole);
- for (int i = 0; i < arRoles.getCount(); i++) {
- CDiscordRole &p = arRoles[i];
- if (p.guildId == guildId && p.position > iOldPosition)
+ for (int i = 0; i < pGuild->arRoles.getCount(); i++) {
+ CDiscordRole &p = pGuild->arRoles[i];
+ if (p.position > iOldPosition)
p.position--;
}
for (int i = 0; i < arUsers.getCount(); i++) {
CDiscordUser &p = arUsers[i];
- if (p.guildId != guildId)
+ if (p.guildId != pGuild->id)
continue;
SESSION_INFO *si = pci->SM_FindSession(p.wszUsername, m_szModuleName);
if (si != nullptr) {
pci->TM_RemoveAll(&si->pStatuses);
- BuildStatusList(guildId, p.wszUsername);
+ BuildStatusList(pGuild, p.wszUsername);
}
}
}
@@ -543,12 +470,9 @@ void CDiscordProto::OnCommandReady(const JSONNode &pRoot)
m_szGatewaySessionId = pRoot["session_id"].as_mstring();
- const JSONNode &readState = pRoot["read_state"];
- const JSONNode &pStatuses = pRoot["presences"];
-
const JSONNode &guilds = pRoot["guilds"];
for (auto it = guilds.begin(); it != guilds.end(); ++it)
- ProcessGuild(readState, *it);
+ ProcessGuild(*it);
const JSONNode &relations = pRoot["relationships"];
for (auto it = relations.begin(); it != relations.end(); ++it) {
@@ -556,10 +480,15 @@ void CDiscordProto::OnCommandReady(const JSONNode &pRoot)
CDiscordUser *pUser = PrepareUser(p["user"]);
ProcessType(pUser, p);
+ }
- int iStatus = sttGetPresence(pStatuses, p["user"]["id"].as_mstring());
- if (iStatus)
- setWord(pUser->hContact, "Status", iStatus);
+ const JSONNode &pStatuses = pRoot["presences"];
+ for (auto it = pStatuses.begin(); it != pStatuses.end(); ++it) {
+ const JSONNode &p = *it;
+
+ CDiscordUser *pUser = FindUser(::getId(p["user"]["id"]));
+ if (pUser != nullptr)
+ setWord(pUser->hContact, "Status", StrToStatus(p["status"].as_mstring()));
}
const JSONNode &channels = pRoot["private_channels"];
@@ -577,7 +506,6 @@ void CDiscordProto::OnCommandReady(const JSONNode &pRoot)
CMStringW wszChannelId = p["id"].as_mstring();
pUser->channelId = _wtoi64(wszChannelId);
pUser->lastMsg = CDiscordMessage(::getId(p["last_message_id"]));
- pUser->lastReadId = sttGetLastRead(readState, wszChannelId);
pUser->bIsPrivate = true;
setId(pUser->hContact, DB_KEY_CHANNELID, pUser->channelId);
@@ -586,6 +514,14 @@ void CDiscordProto::OnCommandReady(const JSONNode &pRoot)
if (pUser->lastMsg.id > oldMsgId)
RetrieveHistory(pUser->hContact, MSG_AFTER, oldMsgId, 99);
}
+
+ const JSONNode &readState = pRoot["read_state"];
+ for (auto it = readState.begin(); it != readState.end(); ++it) {
+ const JSONNode &p = *it;
+ CDiscordUser *pUser = FindUserByChannel(::getId(p["id"]));
+ if (pUser != nullptr)
+ pUser->lastReadId = ::getId(p["last_message_id"]);
+ }
}
/////////////////////////////////////////////////////////////////////////////////////////
diff --git a/protocols/Discord/src/groupchat.cpp b/protocols/Discord/src/groupchat.cpp
index 6641a4fd88..ac9cf235d5 100644
--- a/protocols/Discord/src/groupchat.cpp
+++ b/protocols/Discord/src/groupchat.cpp
@@ -25,15 +25,12 @@ enum {
/////////////////////////////////////////////////////////////////////////////////////////
-void CDiscordProto::BuildStatusList(SnowFlake guildId, const CMStringW &wszChannelId)
+void CDiscordProto::BuildStatusList(const CDiscordGuild *pGuild, const CMStringW &wszChannelId)
{
Chat_AddGroup(m_szModuleName, wszChannelId, L"@owner");
- for (int i = 0; i < arRoles.getCount(); i++) {
- CDiscordRole &r = arRoles[i];
- if (r.guildId == guildId)
- Chat_AddGroup(m_szModuleName, wszChannelId, r.wszName);
- }
+ for (int i = 0; i < pGuild->arRoles.getCount(); i++)
+ Chat_AddGroup(m_szModuleName, wszChannelId, pGuild->arRoles[i].wszName);
}
/////////////////////////////////////////////////////////////////////////////////////////
diff --git a/protocols/Discord/src/guilds.cpp b/protocols/Discord/src/guilds.cpp
new file mode 100644
index 0000000000..f14caadf06
--- /dev/null
+++ b/protocols/Discord/src/guilds.cpp
@@ -0,0 +1,177 @@
+/*
+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"
+
+int compareUsers(const CDiscordUser *p1, const CDiscordUser *p2);
+
+static int compareRoles(const CDiscordRole *p1, const CDiscordRole *p2)
+{
+ return p1->id - p2->id;
+}
+
+static int compareChatUsers(const CDiscordGuildMember *p1, const CDiscordGuildMember *p2)
+{
+ return p1->userId - p2->userId;
+}
+
+CDiscordGuild::CDiscordGuild(SnowFlake _id)
+ : id(_id),
+ arChatUsers(30, compareChatUsers),
+ arRoles(10, compareRoles)
+{
+}
+
+CDiscordGuild::~CDiscordGuild()
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// reads a role from json
+
+void CDiscordProto::ProcessRole(CDiscordGuild *guild, const JSONNode &role)
+{
+ SnowFlake id = ::getId(role["id"]);
+ CDiscordRole *p = guild->arRoles.find((CDiscordRole*)&id);
+ if (p == nullptr) {
+ p = new CDiscordRole();
+ p->id = id;
+ guild->arRoles.insert(p);
+ }
+
+ p->color = role["color"].as_int();
+ p->position = role["position"].as_int();
+ p->permissions = role["permissions"].as_int();
+ p->wszName = role["name"].as_mstring();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CDiscordProto::ProcessGuild(const JSONNode &p)
+{
+ SnowFlake guildId = ::getId(p["id"]);
+ GatewaySendGuildInfo(guildId);
+
+ CDiscordGuild *pGuild = FindGuild(guildId);
+ if (pGuild == nullptr) {
+ pGuild = new CDiscordGuild(guildId);
+ arGuilds.insert(pGuild);
+ }
+ pGuild->ownerId = ::getId(p["owner_id"]);
+ pGuild->wszName = p["name"].as_mstring();
+
+ GCSessionInfoBase *si = Chat_NewSession(GCW_SERVER, m_szModuleName, pGuild->wszName, pGuild->wszName, pGuild);
+ Chat_Control(m_szModuleName, pGuild->wszName, WINDOW_HIDDEN);
+ Chat_Control(m_szModuleName, pGuild->wszName, SESSION_ONLINE);
+
+ pGuild->hContact = si->hContact;
+ setId(si->hContact, DB_KEY_CHANNELID, guildId);
+
+ const JSONNode &roles = p["roles"];
+ for (auto itr = roles.begin(); itr != roles.end(); ++itr)
+ ProcessRole(pGuild, *itr);
+
+ const JSONNode &channels = p["channels"];
+ for (auto itc = channels.begin(); itc != channels.end(); ++itc)
+ ProcessGuildChannel(pGuild, *itc);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+CDiscordUser* CDiscordProto::ProcessGuildChannel(CDiscordGuild *pGuild, const JSONNode &pch)
+{
+ // filter our all channels but the text ones
+ if (pch["type"].as_int() != 0)
+ return NULL;
+
+ CMStringW wszChannelName = pGuild->wszName + L"#" + pch["name"].as_mstring();
+ CMStringW wszChannelId = pch["id"].as_mstring();
+ CMStringW wszTopic = pch["topic"].as_mstring();
+ SnowFlake channelId = _wtoi64(wszChannelId);
+
+ GCSessionInfoBase *si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, wszChannelId, wszChannelName);
+ BuildStatusList(pGuild, wszChannelId);
+
+ Chat_Control(m_szModuleName, wszChannelId, WINDOW_HIDDEN);
+ Chat_Control(m_szModuleName, wszChannelId, SESSION_ONLINE);
+
+ if (!wszTopic.IsEmpty()) {
+ Chat_SetStatusbarText(m_szModuleName, wszChannelId, wszTopic);
+
+ GCDEST gcd = { m_szModuleName, wszChannelId, GC_EVENT_TOPIC };
+ GCEVENT gce = { &gcd };
+ gce.time = time(0);
+ gce.ptszText = wszTopic;
+ Chat_Event(&gce);
+ }
+
+ CDiscordUser *pUser = FindUserByChannel(channelId);
+ if (pUser == NULL) {
+ // missing channel - create it
+ pUser = new CDiscordUser(channelId);
+ pUser->bIsPrivate = false;
+ pUser->hContact = si->hContact;
+ pUser->id = channelId;
+ pUser->channelId = channelId;
+ arUsers.insert(pUser);
+ }
+ pUser->wszUsername = wszChannelId;
+ pUser->guildId = pGuild->id;
+ pUser->lastMsg = CDiscordMessage(::getId(pch["last_message_id"]));
+
+ setId(pUser->hContact, DB_KEY_ID, channelId);
+ setId(pUser->hContact, DB_KEY_CHANNELID, channelId);
+
+ SnowFlake oldMsgId = getId(pUser->hContact, DB_KEY_LASTMSGID);
+ if (oldMsgId != 0 && pUser->lastMsg.id > oldMsgId)
+ RetrieveHistory(pUser->hContact, MSG_AFTER, oldMsgId, 99);
+
+ return pUser;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CDiscordProto::ApplyUsersToChannel(CDiscordGuild *pGuild, const CDiscordUser &pUser)
+{
+ GCDEST gcd = { m_szModuleName, pUser.wszUsername, GC_EVENT_JOIN };
+ GCEVENT gce = { &gcd };
+ gce.time = time(0);
+ gce.dwFlags = GCEF_SILENT;
+
+ for (int i = 0; i < pGuild->arChatUsers.getCount(); i++) {
+ CDiscordGuildMember &m = pGuild->arChatUsers[i];
+
+ wchar_t wszUserId[100];
+ _i64tow_s(m.userId, wszUserId, _countof(wszUserId), 10);
+
+ gce.ptszStatus = m.wszRole;
+ gce.bIsMe = (m.userId == m_ownId);
+ gce.ptszUID = wszUserId;
+ gce.ptszNick = m.wszNick;
+ Chat_Event(&gce);
+
+ int flags = GC_SSE_ONLYLISTED;
+ switch (m.iStatus) {
+ case ID_STATUS_ONLINE: case ID_STATUS_NA: case ID_STATUS_DND:
+ flags += GC_SSE_ONLINE;
+ break;
+ default:
+ flags += GC_SSE_OFFLINE;
+ }
+ Chat_SetStatusEx(m_szModuleName, pUser.wszUsername, flags, wszUserId);
+ }
+}
diff --git a/protocols/Discord/src/menus.cpp b/protocols/Discord/src/menus.cpp
index 85bdd5c670..78f78902a3 100644
--- a/protocols/Discord/src/menus.cpp
+++ b/protocols/Discord/src/menus.cpp
@@ -17,13 +17,24 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "stdafx.h"
+INT_PTR CDiscordProto::OnMenuCreateChannel(WPARAM hContact, LPARAM)
+{
+ ENTER_STRING es = { sizeof(es), ESF_RICHEDIT, m_szModuleName, "Discord", TranslateT("Enter channel name") };
+ if (EnterString(&es)) {
+ JSONNode roles(JSON_ARRAY); roles.set_name("permission_overwrites");
+ JSONNode root; root << INT_PARAM("type", 0) << WCHAR_PARAM("name", es.ptszResult) << roles;
+ CMStringA szUrl(FORMAT, "/guilds/%lld/channels", getId(hContact, DB_KEY_CHANNELID));
+ Push(new AsyncHttpRequest(this, REQUEST_POST, szUrl, NULL, &root));
+ mir_free(es.ptszResult);
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
INT_PTR CDiscordProto::OnMenuJoinGuild(WPARAM, LPARAM)
{
- ENTER_STRING es = { sizeof(es) };
- es.szModuleName = m_szModuleName;
- es.szDataPrefix = "Discord";
- es.type = ESF_MULTILINE;
- es.caption = TranslateT("Enter invitation code you received");
+ ENTER_STRING es = { sizeof(es), ESF_RICHEDIT, m_szModuleName, "Discord", TranslateT("Enter invitation code you received") };
if (EnterString(&es)) {
CMStringA szUrl(FORMAT, "/invite/%S", es.ptszResult);
Push(new AsyncHttpRequest(this, REQUEST_POST, szUrl, NULL));
@@ -48,7 +59,9 @@ INT_PTR CDiscordProto::OnMenuLeaveGuild(WPARAM hContact, LPARAM)
int CDiscordProto::OnMenuPrebuild(WPARAM hContact, LPARAM)
{
// "Leave guild" menu item should be visible only for the guild contacts
- Menu_ShowItem(m_hMenuLeaveGuild, getByte(hContact, "ChatRoom") == 2);
+ bool bIsGuild = getByte(hContact, "ChatRoom") == 2;
+ Menu_ShowItem(m_hMenuLeaveGuild, bIsGuild);
+ Menu_ShowItem(m_hMenuCreateChannel, bIsGuild);
return 0;
}
@@ -68,6 +81,7 @@ void CDiscordProto::InitMenus()
mi.hIcolibItem = g_iconList[1].hIcolib;
Menu_AddProtoMenuItem(&mi, m_szModuleName);
+ // Contact menu items
CMenuItem mi2;
mi2.pszService = "/LeaveGuild";
CreateProtoService(mi2.pszService, &CDiscordProto::OnMenuLeaveGuild);
@@ -77,5 +91,13 @@ void CDiscordProto::InitMenus()
SET_UID(mi2, 0x6EF11AD6, 0x6111, 0x4E29, 0xBA, 0x8B, 0xA7, 0xB2, 0xE0, 0x22, 0xE1, 0x8C);
m_hMenuLeaveGuild = Menu_AddContactMenuItem(&mi2, m_szModuleName);
+ mi2.pszService = "/CreateChannel";
+ CreateProtoService(mi2.pszService, &CDiscordProto::OnMenuCreateChannel);
+ mi2.name.a = LPGEN("Create new channel");
+ mi2.position = -200001001;
+ mi2.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_ADDCONTACT);
+ SET_UID(mi2, 0x6EF11AD6, 0x6111, 0x4E29, 0xBA, 0x8B, 0xA7, 0xB2, 0xE0, 0x22, 0xE1, 0x8D);
+ m_hMenuCreateChannel = Menu_AddContactMenuItem(&mi2, m_szModuleName);
+
HookProtoEvent(ME_CLIST_PREBUILDCONTACTMENU, &CDiscordProto::OnMenuPrebuild);
}
diff --git a/protocols/Discord/src/proto.cpp b/protocols/Discord/src/proto.cpp
index 80331edbda..34d3ae8840 100644
--- a/protocols/Discord/src/proto.cpp
+++ b/protocols/Discord/src/proto.cpp
@@ -27,12 +27,12 @@ static int compareRequests(const AsyncHttpRequest *p1, const AsyncHttpRequest *p
return p1->m_iReqNum - p2->m_iReqNum;
}
-static int compareRoles(const CDiscordRole *p1, const CDiscordRole *p2)
+int compareUsers(const CDiscordUser *p1, const CDiscordUser *p2)
{
return p1->id - p2->id;
}
-static int compareUsers(const CDiscordUser *p1, const CDiscordUser *p2)
+static int compareGuilds(const CDiscordGuild *p1, const CDiscordGuild *p2)
{
return p1->id - p2->id;
}
@@ -43,10 +43,10 @@ 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""),
+ arGuilds(1, compareGuilds),
arMarkReadQueue(1, compareUsers),
arOwnMessages(1, compareMessages),
- arRoles(10, compareRoles),
- arUsers(50, compareUsers)
+ arUsers(10, compareUsers)
{
// Services
CreateProtoService(PS_GETSTATUS, &CDiscordProto::GetStatus);
diff --git a/protocols/Discord/src/proto.h b/protocols/Discord/src/proto.h
index 9ab553e5a4..376f536419 100644
--- a/protocols/Discord/src/proto.h
+++ b/protocols/Discord/src/proto.h
@@ -83,7 +83,7 @@ JSONNode& operator<<(JSONNode &json, const WCHAR_PARAM &param);
struct CDiscordRole : public MZeroedObject
{
- SnowFlake id, guildId;
+ SnowFlake id;
COLORREF color;
DWORD permissions;
int position;
@@ -129,6 +129,41 @@ struct CDiscordUser : public MZeroedObject
int iDiscriminator;
};
+/////////////////////////////////////////////////////////////////////////////////////////
+
+struct CDiscordGuildMember : public MZeroedObject
+{
+ CDiscordGuildMember(SnowFlake id) :
+ userId(id)
+ {}
+
+ ~CDiscordGuildMember()
+ {}
+
+ SnowFlake userId;
+ CMStringW wszNick, wszRole;
+ int iStatus;
+};
+
+struct CDiscordGuild : public MZeroedObject
+{
+ CDiscordGuild(SnowFlake _id);
+ ~CDiscordGuild();
+
+ __forceinline CDiscordGuildMember* FindUser(SnowFlake userId)
+ { return arChatUsers.find((CDiscordGuildMember*)&userId);
+ }
+
+ SnowFlake id, ownerId;
+ CMStringW wszName;
+ MCONTACT hContact;
+
+ OBJLIST<CDiscordGuildMember> arChatUsers;
+ OBJLIST<CDiscordRole> arRoles; // guild roles
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
class CDiscordProto : public PROTO<CDiscordProto>
{
friend struct AsyncHttpRequest;
@@ -204,7 +239,6 @@ class CDiscordProto : public PROTO<CDiscordProto>
mir_cs csMarkReadQueue;
LIST<CDiscordUser> arMarkReadQueue;
- OBJLIST<CDiscordRole> arRoles;
OBJLIST<CDiscordUser> arUsers;
OBJLIST<SnowFlake> arOwnMessages;
CDiscordUser* FindUser(SnowFlake id);
@@ -219,10 +253,26 @@ class CDiscordProto : public PROTO<CDiscordProto>
int __cdecl OnMenuPrebuild(WPARAM, LPARAM);
+ INT_PTR __cdecl OnMenuCreateChannel(WPARAM, LPARAM);
INT_PTR __cdecl OnMenuJoinGuild(WPARAM, LPARAM);
INT_PTR __cdecl OnMenuLeaveGuild(WPARAM, LPARAM);
- HGENMENU m_hMenuLeaveGuild;
+ HGENMENU m_hMenuLeaveGuild, m_hMenuCreateChannel;
+
+ //////////////////////////////////////////////////////////////////////////////////////
+ // guilds
+
+ OBJLIST<CDiscordGuild> arGuilds;
+
+ __forceinline CDiscordGuild* FindGuild(SnowFlake id) const
+ { return arGuilds.find((CDiscordGuild*)&id);
+ }
+
+ void ProcessGuild(const JSONNode&);
+ void ApplyUsersToChannel(CDiscordGuild *guild, const CDiscordUser&);
+ CDiscordUser* ProcessGuildChannel(CDiscordGuild *guild, const JSONNode&);
+ void ProcessRole(CDiscordGuild *guild, const JSONNode&);
+ void ProcessType(CDiscordUser *pUser, const JSONNode&);
//////////////////////////////////////////////////////////////////////////////////////
// group chats
@@ -233,7 +283,7 @@ class CDiscordProto : public PROTO<CDiscordProto>
void Chat_SendPrivateMessage(GCHOOK *gch);
void Chat_ProcessLogMenu(GCHOOK *gch);
- void BuildStatusList(SnowFlake guildId, const CMStringW &wszChannelId);
+ void BuildStatusList(const CDiscordGuild *pGuild, const CMStringW &wszChannelId);
void ParseSpecialChars(SESSION_INFO *si, CMStringW &str);
//////////////////////////////////////////////////////////////////////////////////////
@@ -333,9 +383,6 @@ public:
void OnReceiveAvatar(NETLIBHTTPREQUEST*, AsyncHttpRequest*);
// Misc
- void ProcessGuild(const JSONNode &pStatuses, const JSONNode &pRoot);
- void ProcessRole(SnowFlake guildId, const JSONNode&);
- void ProcessType(CDiscordUser *pUser, const JSONNode&);
void SetServerStatus(int iStatus);
void RemoveFriend(SnowFlake id);
diff --git a/protocols/Discord/src/stdafx.h b/protocols/Discord/src/stdafx.h
index 6a233e59b4..7e6e032d3b 100644
--- a/protocols/Discord/src/stdafx.h
+++ b/protocols/Discord/src/stdafx.h
@@ -57,7 +57,6 @@ extern HWND g_hwndHeartbeat;
#define DB_KEY_NICK "Nick"
#define DB_KEY_AVHASH "AvatarHash"
#define DB_KEY_CHANNELID "ChannelID"
-#define DB_KEY_OWNERID "OwnerID"
#define DB_KEY_LASTMSGID "LastMessageID"
#define DB_KEY_REQAUTH "ReqAuth"
diff --git a/protocols/Discord/src/version.h b/protocols/Discord/src/version.h
index ebddf302fd..e8f77f39b4 100644
--- a/protocols/Discord/src/version.h
+++ b/protocols/Discord/src/version.h
@@ -1,7 +1,7 @@
#define __MAJOR_VERSION 0
-#define __MINOR_VERSION 4
+#define __MINOR_VERSION 5
#define __RELEASE_NUM 0
-#define __BUILD_NUM 3
+#define __BUILD_NUM 1
#include <stdver.h>