summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorge Hazan <ghazan@miranda.im>2017-02-07 22:44:34 +0300
committerGeorge Hazan <ghazan@miranda.im>2017-02-07 22:44:34 +0300
commit2f5ce75be367aebc0e0790cac148c670462e60cf (patch)
tree2bcd4135bbda12e4691b7d4b84ad4ee43a56d038
parentf193645f4db83e94bd44dde17f89282f182c00e4 (diff)
Discord: ability to join & leave guilds on the fly
-rw-r--r--protocols/Discord/res/discord.rc2
-rw-r--r--protocols/Discord/res/groupchat.icobin0 -> 5430 bytes
-rw-r--r--protocols/Discord/src/dispatch.cpp155
-rw-r--r--protocols/Discord/src/main.cpp8
-rw-r--r--protocols/Discord/src/menus.cpp81
-rw-r--r--protocols/Discord/src/proto.cpp2
-rw-r--r--protocols/Discord/src/proto.h16
-rw-r--r--protocols/Discord/src/resource.h2
-rw-r--r--protocols/Discord/src/stdafx.h1
-rw-r--r--protocols/Discord/src/version.h6
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
new file mode 100644
index 0000000000..66be7ca40b
--- /dev/null
+++ b/protocols/Discord/res/groupchat.ico
Binary files differ
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>