diff options
author | George Hazan <ghazan@miranda.im> | 2017-02-07 22:44:34 +0300 |
---|---|---|
committer | George Hazan <ghazan@miranda.im> | 2017-02-07 22:44:34 +0300 |
commit | 2f5ce75be367aebc0e0790cac148c670462e60cf (patch) | |
tree | 2bcd4135bbda12e4691b7d4b84ad4ee43a56d038 | |
parent | f193645f4db83e94bd44dde17f89282f182c00e4 (diff) |
Discord: ability to join & leave guilds on the fly
-rw-r--r-- | protocols/Discord/res/discord.rc | 2 | ||||
-rw-r--r-- | protocols/Discord/res/groupchat.ico | bin | 0 -> 5430 bytes | |||
-rw-r--r-- | protocols/Discord/src/dispatch.cpp | 155 | ||||
-rw-r--r-- | protocols/Discord/src/main.cpp | 8 | ||||
-rw-r--r-- | protocols/Discord/src/menus.cpp | 81 | ||||
-rw-r--r-- | protocols/Discord/src/proto.cpp | 2 | ||||
-rw-r--r-- | protocols/Discord/src/proto.h | 16 | ||||
-rw-r--r-- | protocols/Discord/src/resource.h | 2 | ||||
-rw-r--r-- | protocols/Discord/src/stdafx.h | 1 | ||||
-rw-r--r-- | protocols/Discord/src/version.h | 6 |
10 files changed, 212 insertions, 61 deletions
diff --git a/protocols/Discord/res/discord.rc b/protocols/Discord/res/discord.rc index b8b5e4ae5a..ef7bb54c64 100644 --- a/protocols/Discord/res/discord.rc +++ b/protocols/Discord/res/discord.rc @@ -53,7 +53,7 @@ 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_GROUPCHAT ICON "groupchat.ico" ///////////////////////////////////////////////////////////////////////////// // diff --git a/protocols/Discord/res/groupchat.ico b/protocols/Discord/res/groupchat.ico Binary files differnew file mode 100644 index 0000000000..66be7ca40b --- /dev/null +++ b/protocols/Discord/res/groupchat.ico diff --git a/protocols/Discord/src/dispatch.cpp b/protocols/Discord/src/dispatch.cpp index fb06d672c0..fe581e61b9 100644 --- a/protocols/Discord/src/dispatch.cpp +++ b/protocols/Discord/src/dispatch.cpp @@ -31,7 +31,9 @@ static handlers[] = // these structures must me sorted alphabetically { L"CHANNEL_CREATE", &CDiscordProto::OnCommandChannelCreated }, { L"CHANNEL_DELETE", &CDiscordProto::OnCommandChannelDeleted }, - { L"GUILD_CREATE", &CDiscordProto::OnCommandGuildSync }, + { L"GUILD_CREATE", &CDiscordProto::OnCommandGuildCreate }, + { L"GUILD_DELETE", &CDiscordProto::OnCommandGuildDelete }, + { L"GUILD_MEMBER_REMOVE", &CDiscordProto::OnCommandGuildRemoveMember }, { L"GUILD_SYNC", &CDiscordProto::OnCommandGuildSync }, { L"MESSAGE_ACK", &CDiscordProto::OnCommandMessageAck }, @@ -110,7 +112,7 @@ void CDiscordProto::OnCommandFriendRemoved(const JSONNode &pRoot) } ///////////////////////////////////////////////////////////////////////////////////////// -// reading a new message +// guild synchronization static int sttGetPresence(const JSONNode &pStatuses, const CMStringW &wszId) { @@ -125,6 +127,67 @@ static int sttGetPresence(const JSONNode &pStatuses, const CMStringW &wszId) 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::ProcessGuild(const JSONNode &readState, const JSONNode &p) +{ + SnowFlake guildId = ::getId(p["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 &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(); + SnowFlake channelId = _wtoi64(wszChannelId); + + si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, wszChannelId, wszGuildName + L"#" + wszChannelName); + Chat_AddGroup(m_szModuleName, wszChannelId, TranslateT("User")); + Chat_Control(m_szModuleName, wszChannelId, WINDOW_HIDDEN); + Chat_Control(m_szModuleName, wszChannelId, SESSION_ONLINE); + + CDiscordUser *pUser = FindUserByChannel(channelId); + if (pUser == NULL) { + // missing channel - create it + pUser = new CDiscordUser(channelId); + pUser->bIsPrivate = false; + pUser->hContact = si->hContact; + pUser->channelId = channelId; + arUsers.insert(pUser); + } + pUser->wszUsername = wszChannelId; + pUser->guildId = guildId; + pUser->lastMessageId = ::getId(pch["last_message_id"]); + pUser->lastReadId = sttGetLastRead(readState, wszChannelId); + + setId(pUser->hContact, DB_KEY_CHANNELID, channelId); + } +} + +void CDiscordProto::OnCommandGuildCreate(const JSONNode &pRoot) +{ + ProcessGuild(JSONNode(), pRoot); + OnCommandGuildSync(pRoot); +} + void CDiscordProto::OnCommandGuildSync(const JSONNode &pRoot) { const JSONNode &pStatuses = pRoot["presences"]; @@ -165,6 +228,38 @@ void CDiscordProto::OnCommandGuildSync(const JSONNode &pRoot) } } +void CDiscordProto::OnCommandGuildDelete(const JSONNode &pRoot) +{ + SnowFlake guildId = ::getId(pRoot["id"]); + + for (int i = arUsers.getCount()-1; i >= 0; i--) { + CDiscordUser &pUser = arUsers[i]; + if (pUser.guildId == guildId) { + Chat_Terminate(m_szModuleName, pUser.wszUsername, true); + arUsers.remove(i); + } + } + + Chat_Terminate(m_szModuleName, pRoot["name"].as_mstring(), true); +} + +void CDiscordProto::OnCommandGuildRemoveMember(const JSONNode &pRoot) +{ + SnowFlake guildId = ::getId(pRoot["guild_id"]); + CMStringW wszUserId = pRoot["user"]["id"].as_mstring(); + + 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_PART }; + GCEVENT gce = { &gcd }; + gce.ptszUID = wszUserId; + Chat_Event(&gce); + } +} + ///////////////////////////////////////////////////////////////////////////////////////// // reading a new message @@ -271,17 +366,6 @@ static void __stdcall sttStartTimer(void *param) SetTimer(g_hwndHeartbeat, (UINT_PTR)param, ppro->getHeartbeatInterval(), &CDiscordProto::HeartbeatTimerProc); } -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::OnCommandReady(const JSONNode &pRoot) { GatewaySendHeartbeat(); @@ -293,49 +377,8 @@ void CDiscordProto::OnCommandReady(const JSONNode &pRoot) const JSONNode &pStatuses = pRoot["presences"]; const JSONNode &guilds = pRoot["guilds"]; - for (auto it = guilds.begin(); it != guilds.end(); ++it) { - const JSONNode &p = *it; - - SnowFlake guildId = ::getId(p["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); - - 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(); - SnowFlake channelId = _wtoi64(wszChannelId); - - si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, wszChannelId, wszGuildName + L"#" + wszChannelName); - Chat_AddGroup(m_szModuleName, wszChannelId, TranslateT("User")); - Chat_Control(m_szModuleName, wszChannelId, WINDOW_HIDDEN); - Chat_Control(m_szModuleName, wszChannelId, SESSION_ONLINE); - - CDiscordUser *pUser = FindUserByChannel(channelId); - if (pUser == NULL) { - // missing channel - create it - pUser = new CDiscordUser(channelId); - pUser->bIsPrivate = false; - pUser->hContact = si->hContact; - pUser->channelId = channelId; - arUsers.insert(pUser); - } - pUser->wszUsername = wszChannelId; - pUser->guildId = guildId; - pUser->lastMessageId = ::getId(pch["last_message_id"]); - pUser->lastReadId = sttGetLastRead(readState, wszChannelId); - - setId(pUser->hContact, DB_KEY_CHANNELID, channelId); - } - } + for (auto it = guilds.begin(); it != guilds.end(); ++it) + ProcessGuild(readState, *it); const JSONNode &relations = pRoot["relationships"]; for (auto it = relations.begin(); it != relations.end(); ++it) { diff --git a/protocols/Discord/src/main.cpp b/protocols/Discord/src/main.cpp index 850d4a044e..dbf194d708 100644 --- a/protocols/Discord/src/main.cpp +++ b/protocols/Discord/src/main.cpp @@ -21,6 +21,12 @@ HINSTANCE g_hInstance; int hLangpack = 0; HWND g_hwndHeartbeat; +IconItem g_iconList[] = +{ + { LPGEN("Main icon"), "main", IDI_MAIN }, + { LPGEN("Group chats"), "groupchat", IDI_GROUPCHAT } +}; + PLUGININFOEX pluginInfo = { sizeof(PLUGININFOEX), __PLUGIN_NAME, @@ -71,6 +77,8 @@ extern "C" int __declspec(dllexport) Load(void) g_hwndHeartbeat = CreateWindowEx(0, L"STATIC", NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL); + Icon_Register(g_hInstance, "Discord", g_iconList, _countof(g_iconList)); + PROTOCOLDESCRIPTOR pd = { 0 }; pd.cbSize = sizeof(pd); pd.szName = "Discord"; diff --git a/protocols/Discord/src/menus.cpp b/protocols/Discord/src/menus.cpp new file mode 100644 index 0000000000..85bdd5c670 --- /dev/null +++ b/protocols/Discord/src/menus.cpp @@ -0,0 +1,81 @@ +/* +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_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"); + if (EnterString(&es)) { + CMStringA szUrl(FORMAT, "/invite/%S", es.ptszResult); + Push(new AsyncHttpRequest(this, REQUEST_POST, szUrl, NULL)); + mir_free(es.ptszResult); + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +INT_PTR CDiscordProto::OnMenuLeaveGuild(WPARAM hContact, LPARAM) +{ + if (IDYES == MessageBox(NULL, TranslateT("Do you really want to leave the guild?"), m_tszUserName, MB_ICONQUESTION | MB_YESNOCANCEL)) { + CMStringA szUrl(FORMAT, "/users/@me/guilds/%lld", getId(hContact, DB_KEY_CHANNELID)); + Push(new AsyncHttpRequest(this, REQUEST_DELETE, szUrl, NULL)); + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +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); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CDiscordProto::InitMenus() +{ + CMenuItem mi; + mi.root = Menu_GetProtocolRoot(this); + mi.flags = CMIF_UNMOVABLE; + + // Protocol menu items + mi.pszService = "/JoinGuild"; + CreateProtoService(mi.pszService, &CDiscordProto::OnMenuJoinGuild); + mi.name.a = LPGEN("Join guild"); + mi.position = 200001; + mi.hIcolibItem = g_iconList[1].hIcolib; + Menu_AddProtoMenuItem(&mi, m_szModuleName); + + CMenuItem mi2; + mi2.pszService = "/LeaveGuild"; + CreateProtoService(mi2.pszService, &CDiscordProto::OnMenuLeaveGuild); + mi2.name.a = LPGEN("Leave guild"); + mi2.position = -200001000; + mi2.hIcolibItem = Skin_GetIconHandle(SKINICON_CHAT_LEAVE); + SET_UID(mi2, 0x6EF11AD6, 0x6111, 0x4E29, 0xBA, 0x8B, 0xA7, 0xB2, 0xE0, 0x22, 0xE1, 0x8C); + m_hMenuLeaveGuild = 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 991327a38d..bf13472471 100644 --- a/protocols/Discord/src/proto.cpp +++ b/protocols/Discord/src/proto.cpp @@ -573,6 +573,8 @@ int CDiscordProto::OnModulesLoaded(WPARAM, LPARAM) HookProtoEvent(ME_GC_EVENT, &CDiscordProto::GroupchatEventHook); HookProtoEvent(ME_GC_BUILDMENU, &CDiscordProto::GroupchatMenuHook); + + InitMenus(); return 0; } diff --git a/protocols/Discord/src/proto.h b/protocols/Discord/src/proto.h index 1ee7625f9d..29d83b69d5 100644 --- a/protocols/Discord/src/proto.h +++ b/protocols/Discord/src/proto.h @@ -187,6 +187,18 @@ class CDiscordProto : public PROTO<CDiscordProto> CDiscordUser* PrepareUser(const JSONNode&); ////////////////////////////////////////////////////////////////////////////////////// + // menu items + + void InitMenus(void); + + int __cdecl OnMenuPrebuild(WPARAM, LPARAM); + + INT_PTR __cdecl OnMenuJoinGuild(WPARAM, LPARAM); + INT_PTR __cdecl OnMenuLeaveGuild(WPARAM, LPARAM); + + HGENMENU m_hMenuLeaveGuild; + + ////////////////////////////////////////////////////////////////////////////////////// // misc methods SnowFlake getId(const char *szName); @@ -242,6 +254,9 @@ public: // dispatch commands void OnCommandChannelCreated(const JSONNode&); void OnCommandChannelDeleted(const JSONNode&); + void OnCommandGuildCreate(const JSONNode&); + void OnCommandGuildDelete(const JSONNode&); + void OnCommandGuildRemoveMember(const JSONNode&); void OnCommandGuildSync(const JSONNode&); void OnCommandFriendAdded(const JSONNode&); void OnCommandFriendRemoved(const JSONNode&); @@ -279,6 +294,7 @@ public: void OnReceiveAvatar(NETLIBHTTPREQUEST*, AsyncHttpRequest*); // Misc + void ProcessGuild(const JSONNode &pStatuses, const JSONNode &pRoot); void ProcessType(CDiscordUser *pUser, const JSONNode&); void SetServerStatus(int iStatus); void RemoveFriend(SnowFlake id); diff --git a/protocols/Discord/src/resource.h b/protocols/Discord/src/resource.h index 20a3000f8c..65d61f094f 100644 --- a/protocols/Discord/src/resource.h +++ b/protocols/Discord/src/resource.h @@ -3,7 +3,7 @@ // Used by w:\miranda-ng\protocols\Discord\res\discord.rc // #define IDI_MAIN 101 -#define IDI_OFFLINE 102 +#define IDI_GROUPCHAT 102 #define IDD_OPTIONS_ACCOUNT 103 #define IDD_EXTSEARCH 104 #define IDD_OPTIONS_ACCMGR 105 diff --git a/protocols/Discord/src/stdafx.h b/protocols/Discord/src/stdafx.h index a6f323f2a1..c87339aeb8 100644 --- a/protocols/Discord/src/stdafx.h +++ b/protocols/Discord/src/stdafx.h @@ -43,6 +43,7 @@ #include "../../libs/zlib/src/zlib.h" +extern IconItem g_iconList[]; extern HINSTANCE g_hInstance; extern HWND g_hwndHeartbeat; diff --git a/protocols/Discord/src/version.h b/protocols/Discord/src/version.h index 7953ebf230..ec797c4ed4 100644 --- a/protocols/Discord/src/version.h +++ b/protocols/Discord/src/version.h @@ -1,7 +1,7 @@ #define __MAJOR_VERSION 0 -#define __MINOR_VERSION 2 -#define __RELEASE_NUM 0 -#define __BUILD_NUM 2 +#define __MINOR_VERSION 3 +#define __RELEASE_NUM 1 +#define __BUILD_NUM 1 #include <stdver.h> |