summaryrefslogtreecommitdiff
path: root/protocols/Discord/src
diff options
context:
space:
mode:
authorGeorge Hazan <ghazan@miranda.im>2017-01-07 22:57:57 +0300
committerGeorge Hazan <ghazan@miranda.im>2017-01-07 22:57:57 +0300
commitf1a204292b895a94f142c32b2f3dc69fcc053e67 (patch)
tree465127e97ce90f457c90b8cfc78ae8f0e00b5064 /protocols/Discord/src
parente012650957335d5f4295441debf849b3b9b2f4ef (diff)
first version of Discord that sends messages, catches presence packets and displays typing notification
version bump
Diffstat (limited to 'protocols/Discord/src')
-rw-r--r--protocols/Discord/src/dispatch.cpp120
-rw-r--r--protocols/Discord/src/gateway.cpp26
-rw-r--r--protocols/Discord/src/proto.cpp47
-rw-r--r--protocols/Discord/src/proto.h12
-rw-r--r--protocols/Discord/src/server.cpp11
-rw-r--r--protocols/Discord/src/stdafx.h1
-rw-r--r--protocols/Discord/src/utils.cpp25
-rw-r--r--protocols/Discord/src/version.h4
8 files changed, 217 insertions, 29 deletions
diff --git a/protocols/Discord/src/dispatch.cpp b/protocols/Discord/src/dispatch.cpp
index f9cf006a09..e96471f116 100644
--- a/protocols/Discord/src/dispatch.cpp
+++ b/protocols/Discord/src/dispatch.cpp
@@ -19,6 +19,102 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
extern HWND g_hwndHeartbeat;
+#pragma pack(4)
+
+struct CDiscordCommand
+{
+ const wchar_t *szCommandId;
+ GatewayHandlerFunc pFunc;
+}
+static handlers[] = // these structures must me sorted alphabetically
+{
+ { L"MESSAGE_CREATE", &CDiscordProto::OnCommandMessage },
+ { L"MESSAGE_UPDATE", &CDiscordProto::OnCommandMessage },
+
+ { L"PRESENCE_UPDATE", &CDiscordProto::OnCommandPresence },
+
+ { L"READY", &CDiscordProto::OnCommandReady },
+
+ { L"TYPING_START", &CDiscordProto::OnCommandTyping },
+};
+
+static int __cdecl pSearchFunc(const void *p1, const void *p2)
+{
+ return wcscmp(((CDiscordCommand*)p1)->szCommandId, ((CDiscordCommand*)p2)->szCommandId);
+}
+
+GatewayHandlerFunc CDiscordProto::GetHandler(const wchar_t *pwszCommand)
+{
+ CDiscordCommand tmp = { pwszCommand, NULL };
+ CDiscordCommand *p = (CDiscordCommand*)bsearch(&tmp, handlers, _countof(handlers), sizeof(handlers[0]), pSearchFunc);
+ return (p != NULL) ? p->pFunc : NULL;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////
+// reading a new message
+
+void CDiscordProto::OnCommandMessage(const JSONNode &pRoot)
+{
+ PROTORECVEVENT recv = {};
+
+ CDiscordUser *pUser = PrepareUser(pRoot["author"]);
+ SnowFlake channelId = _wtoi64(pRoot["channel_id"].as_mstring());
+ CMStringW msgId = pRoot["id"].as_mstring();
+ CMStringW wszText = pRoot["content"].as_mstring();
+
+ // if a message has myself as an author, mark it as sent
+ if (pUser->id == 0)
+ return;
+
+ const JSONNode &edited = pRoot["edited_timestamp"];
+ if (!edited.isnull())
+ wszText.AppendFormat(L" (%s %s)", TranslateT("edited at"), edited.as_mstring().c_str());
+
+ if (pUser->channelId != channelId) {
+ debugLogA("failed to process a groupchat message, exiting");
+ return;
+ }
+
+ ptrA buf(mir_utf8encodeW(wszText));
+ recv.timestamp = (DWORD)StringToDate(pRoot["timestamp"].as_mstring());
+ recv.szMessage = buf;
+ recv.lParam = (LPARAM)msgId.c_str();
+ ProtoChainRecvMsg(pUser->hContact, &recv);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////
+// someone changed its status
+
+void CDiscordProto::OnCommandPresence(const JSONNode &pRoot)
+{
+ CDiscordUser *pUser = PrepareUser(pRoot["user"]);
+ 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;
+
+ if (iStatus != 0)
+ setWord(pUser->hContact, "Status", iStatus);
+
+ CMStringW wszGame = pRoot["game"]["name"].as_mstring();
+ if (!wszGame.IsEmpty())
+ setWString(pUser->hContact, "XStatusMsg", wszGame);
+ else
+ delSetting(pUser->hContact, "XStatusMsg");
+}
+
+//////////////////////////////////////////////////////////////////////////////////////
+// gateway session start
+
void CALLBACK CDiscordProto::HeartbeatTimerProc(HWND, UINT, UINT_PTR id, DWORD)
{
((CDiscordProto*)id)->GatewaySendHeartbeat();
@@ -62,3 +158,27 @@ void CDiscordProto::OnCommandReady(const JSONNode &pRoot)
setId(pUser->hContact, DB_KEY_CHANNELID, pUser->channelId);
}
}
+
+//////////////////////////////////////////////////////////////////////////////////////
+// UTN support
+
+void CDiscordProto::OnCommandTyping(const JSONNode &pRoot)
+{
+ SnowFlake userId = _wtoi64(pRoot["user_id"].as_mstring());
+ SnowFlake channelId = _wtoi64(pRoot["channel_id"].as_mstring());
+ debugLogA("user typing notification: userid=%lld, channelid=%lld", userId, channelId);
+
+ CDiscordUser *pUser = FindUser(userId);
+ if (pUser == NULL) {
+ debugLogA("user with id=%lld is not found", userId);
+ return;
+ }
+
+ if (pUser->channelId == channelId) {
+ debugLogA("user is typing in his private channel");
+ CallService(MS_PROTO_CONTACTISTYPING, pUser->hContact, 20);
+ }
+ else {
+ debugLogA("user is typing in a group channel, skipped");
+ }
+}
diff --git a/protocols/Discord/src/gateway.cpp b/protocols/Discord/src/gateway.cpp
index b8049e3a1b..db07b18069 100644
--- a/protocols/Discord/src/gateway.cpp
+++ b/protocols/Discord/src/gateway.cpp
@@ -17,30 +17,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "stdafx.h"
-#pragma pack(4)
-
-struct CDiscordCommand
-{
- const wchar_t *szCommandId;
- GatewayHandlerFunc pFunc;
-}
-static handlers[] = // these structures must me sorted alphabetically
-{
- { L"READY", &CDiscordProto::OnCommandReady }
-};
-
-static int __cdecl pSearchFunc(const void *p1, const void *p2)
-{
- return wcscmp(((CDiscordCommand*)p1)->szCommandId, ((CDiscordCommand*)p2)->szCommandId);
-}
-
-GatewayHandlerFunc CDiscordProto::GetHandler(const wchar_t *pwszCommand)
-{
- CDiscordCommand tmp = { pwszCommand, NULL };
- CDiscordCommand *p = (CDiscordCommand*)bsearch(&tmp, handlers, _countof(handlers), sizeof(handlers[0]), pSearchFunc);
- return (p != NULL) ? p->pFunc : NULL;
-}
-
//////////////////////////////////////////////////////////////////////////////////////
// sends a piece of JSON to a server via a websocket, masked
@@ -176,7 +152,7 @@ void CDiscordProto::GatewayThreadWorker()
break;
unsigned char buf[2048];
- int bufSize = Netlib_Recv(m_hGatewayConnection, (char*)buf + offset, _countof(buf) - offset, MSG_DUMPASTEXT);
+ int bufSize = Netlib_Recv(m_hGatewayConnection, (char*)buf + offset, _countof(buf) - offset, 0);
if (bufSize == 0) {
debugLogA("Gateway connection gracefully closed");
break;
diff --git a/protocols/Discord/src/proto.cpp b/protocols/Discord/src/proto.cpp
index f85df56fe3..d44f97cb8b 100644
--- a/protocols/Discord/src/proto.cpp
+++ b/protocols/Discord/src/proto.cpp
@@ -42,6 +42,9 @@ CDiscordProto::CDiscordProto(const char *proto_name, const wchar_t *username) :
HookProtoEvent(ME_OPT_INITIALISE, &CDiscordProto::OnOptionsInit);
HookProtoEvent(ME_MSG_WINDOWEVENT, &CDiscordProto::OnSrmmEvent);
+ // database
+ db_set_resident(m_szModuleName, "XStatusMsg");
+
// Clist
Clist_GroupCreate(NULL, m_wszDefaultGroup);
@@ -258,6 +261,50 @@ MCONTACT CDiscordProto::AddToList(int flags, PROTOSEARCHRESULT *psr)
return hContact;
}
+////////////////////////////////////////////////////////////////////////////////////////
+// RecvMsg
+
+int CDiscordProto::RecvMsg(MCONTACT hContact, PROTORECVEVENT *evt)
+{
+ T2Utf szResUtf((const wchar_t*)evt->lParam);
+ evt->pCustomData = (char*)szResUtf;
+ evt->cbCustomDataSize = (DWORD)mir_strlen(szResUtf);
+ Proto_RecvMessage(hContact, evt);
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// SendMsg
+
+void __cdecl CDiscordProto::SendMessageAckThread(void *param)
+{
+ Sleep(100);
+ ProtoBroadcastAck((MCONTACT)param, ACKTYPE_MESSAGE, ACKRESULT_FAILED, (HANDLE)1, (LPARAM)Translate("Protocol is offline or no JID"));
+}
+
+int CDiscordProto::SendMsg(MCONTACT hContact, int /*flags*/, const char *pszSrc)
+{
+ if (!m_bOnline) {
+ ForkThread(&CDiscordProto::SendMessageAckThread, (void*)hContact);
+ return 1;
+ }
+
+ ptrW wszText(mir_utf8decodeW(pszSrc));
+ if (wszText == NULL)
+ return 0;
+
+ CDiscordUser *pUser = FindUser(getId(hContact, DB_KEY_ID));
+ if (pUser == NULL || pUser->channelId == NULL)
+ return 0;
+
+ CMStringA szUrl(FORMAT, "/channels/%lld/messages", pUser->channelId);
+ JSONNode body; body << WCHAR_PARAM("content", wszText);
+ AsyncHttpRequest *pReq = new AsyncHttpRequest(this, REQUEST_POST, szUrl, &CDiscordProto::OnReceiveMessageAck, &body);
+ pReq->pUserInfo = (void*)hContact;
+ Push(pReq);
+ return pReq->m_iReqNum;
+}
+
/////////////////////////////////////////////////////////////////////////////////////////
int CDiscordProto::OnModulesLoaded(WPARAM, LPARAM)
diff --git a/protocols/Discord/src/proto.h b/protocols/Discord/src/proto.h
index 73e48a806a..a9a948ce7d 100644
--- a/protocols/Discord/src/proto.h
+++ b/protocols/Discord/src/proto.h
@@ -102,6 +102,7 @@ class CDiscordProto : public PROTO<CDiscordProto>
void __cdecl ServerThread(void*);
void __cdecl SearchThread(void *param);
+ void __cdecl SendMessageAckThread(void* param);
//////////////////////////////////////////////////////////////////////////////////////
// session control
@@ -147,8 +148,6 @@ class CDiscordProto : public PROTO<CDiscordProto>
void GatewaySendHeartbeat(void);
void GatewaySendIdentify(void);
- void OnReceiveGateway(NETLIBHTTPREQUEST*, AsyncHttpRequest*);
-
GatewayHandlerFunc GetHandler(const wchar_t*);
int m_iHartbeatInterval; // in milliseconds
@@ -168,6 +167,7 @@ class CDiscordProto : public PROTO<CDiscordProto>
OBJLIST<CDiscordUser> arUsers;
CDiscordUser* FindUser(SnowFlake id);
CDiscordUser* FindUser(const wchar_t *pwszUsername, int iDiscriminator);
+ CDiscordUser* FindUserByChannel(SnowFlake channelId);
CDiscordUser* PrepareUser(const JSONNode&);
//////////////////////////////////////////////////////////////////////////////////////
@@ -194,6 +194,9 @@ public:
virtual int __cdecl AuthRequest(MCONTACT hContact, const wchar_t*) override;
+ virtual int __cdecl RecvMsg(MCONTACT hContact, PROTORECVEVENT *evt) override;
+ virtual int __cdecl SendMsg(MCONTACT hContact, int flags, const char* pszSrc) override;
+
virtual int __cdecl SetStatus(int iNewStatus) override;
virtual int __cdecl OnEvent(PROTOEVENTTYPE, WPARAM, LPARAM) override;
@@ -207,7 +210,10 @@ public:
int __cdecl OnSrmmEvent(WPARAM, LPARAM);
// dispatch commands
+ void OnCommandMessage(const JSONNode&);
+ void OnCommandPresence(const JSONNode&);
void OnCommandReady(const JSONNode&);
+ void OnCommandTyping(const JSONNode&);
void OnLoggedIn();
void OnLoggedOut();
@@ -217,6 +223,8 @@ public:
void OnReceiveGuilds(NETLIBHTTPREQUEST*, AsyncHttpRequest*);
void OnReceiveChannels(NETLIBHTTPREQUEST*, AsyncHttpRequest*);
void OnReceiveFriends(NETLIBHTTPREQUEST*, AsyncHttpRequest*);
+ void OnReceiveGateway(NETLIBHTTPREQUEST*, AsyncHttpRequest*);
+ void OnReceiveMessageAck(NETLIBHTTPREQUEST*, AsyncHttpRequest*);
void RetrieveUserInfo(MCONTACT hContact);
void OnReceiveUserInfo(NETLIBHTTPREQUEST*, AsyncHttpRequest*);
diff --git a/protocols/Discord/src/server.cpp b/protocols/Discord/src/server.cpp
index 855565d327..39ad13bc50 100644
--- a/protocols/Discord/src/server.cpp
+++ b/protocols/Discord/src/server.cpp
@@ -221,6 +221,17 @@ void CDiscordProto::OnReceiveGuilds(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*
/////////////////////////////////////////////////////////////////////////////////////////
+void CDiscordProto::OnReceiveMessageAck(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq)
+{
+ bool bSucceeded = true;
+ if (pReply->resultCode != 200 && pReply->resultCode != 204)
+ bSucceeded = false;
+
+ ProtoBroadcastAck((MCONTACT)pReq->pUserInfo, ACKTYPE_MESSAGE, bSucceeded ? ACKRESULT_SUCCESS : ACKRESULT_FAILED, (HANDLE)pReq->m_iReqNum, 0);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
void CDiscordProto::OnReceiveToken(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*)
{
if (pReply->resultCode != 200) {
diff --git a/protocols/Discord/src/stdafx.h b/protocols/Discord/src/stdafx.h
index b91bf0008d..4bfc9448ae 100644
--- a/protocols/Discord/src/stdafx.h
+++ b/protocols/Discord/src/stdafx.h
@@ -60,3 +60,4 @@ extern HINSTANCE g_hInstance;
#define DB_KEYVAL_GROUP L"Discord"
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 ce000c0989..ae610b8d21 100644
--- a/protocols/Discord/src/utils.cpp
+++ b/protocols/Discord/src/utils.cpp
@@ -70,6 +70,15 @@ time_t StringToDate(const CMStringW &str)
/////////////////////////////////////////////////////////////////////////////////////////
+static LONG volatile g_counter = 1;
+
+int SerialNext()
+{
+ return InterlockedIncrement(&g_counter);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
SnowFlake CDiscordProto::getId(const char *szSetting)
{
DBVARIANT dbv;
@@ -106,6 +115,8 @@ void CDiscordProto::setId(MCONTACT hContact, const char *szSetting, SnowFlake iV
/////////////////////////////////////////////////////////////////////////////////////////
+static CDiscordUser *g_myUser = new CDiscordUser(0);
+
CDiscordUser* CDiscordProto::FindUser(SnowFlake id)
{
return arUsers.find((CDiscordUser*)&id);
@@ -122,9 +133,23 @@ CDiscordUser* CDiscordProto::FindUser(const wchar_t *pwszUsername, int iDiscrimi
return NULL;
}
+CDiscordUser* CDiscordProto::FindUserByChannel(SnowFlake channelId)
+{
+ for (int i = 0; i < arUsers.getCount(); i++) {
+ CDiscordUser &p = arUsers[i];
+ if (p.channelId == channelId)
+ return &p;
+ }
+
+ return NULL;
+}
+
CDiscordUser* CDiscordProto::PrepareUser(const JSONNode &user)
{
SnowFlake id = _wtoi64(user["id"].as_mstring());
+ if (id == m_ownId)
+ return g_myUser;
+
int iDiscriminator = _wtoi(user["discriminator"].as_mstring());
CMStringW avatar = user["avatar"].as_mstring();
CMStringW username = user["username"].as_mstring();
diff --git a/protocols/Discord/src/version.h b/protocols/Discord/src/version.h
index c4be71667a..4753e0f013 100644
--- a/protocols/Discord/src/version.h
+++ b/protocols/Discord/src/version.h
@@ -1,7 +1,7 @@
#define __MAJOR_VERSION 0
-#define __MINOR_VERSION 0
+#define __MINOR_VERSION 1
#define __RELEASE_NUM 0
-#define __BUILD_NUM 3
+#define __BUILD_NUM 1
#include <stdver.h>