diff options
-rw-r--r-- | protocols/Discord/src/dispatch.cpp | 306 | ||||
-rw-r--r-- | protocols/Discord/src/groupchat.cpp | 9 | ||||
-rw-r--r-- | protocols/Discord/src/guilds.cpp | 177 | ||||
-rw-r--r-- | protocols/Discord/src/menus.cpp | 34 | ||||
-rw-r--r-- | protocols/Discord/src/proto.cpp | 8 | ||||
-rw-r--r-- | protocols/Discord/src/proto.h | 61 | ||||
-rw-r--r-- | protocols/Discord/src/stdafx.h | 1 | ||||
-rw-r--r-- | protocols/Discord/src/version.h | 4 |
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 ¶m); 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> |