summaryrefslogtreecommitdiff
path: root/protocols
diff options
context:
space:
mode:
authorGeorge Hazan <george.hazan@gmail.com>2024-06-04 19:22:10 +0300
committerGeorge Hazan <george.hazan@gmail.com>2024-06-04 19:22:10 +0300
commitd1ca910b36f431676d8c02667f69eb2f1cd2ae47 (patch)
tree13694d0e2d45992bd207896094780568e8ba57bc /protocols
parent83fa7b47ff82e9e308499fee387512f56a907854 (diff)
Discord: voice call initialization
Diffstat (limited to 'protocols')
-rw-r--r--protocols/Discord/src/dispatch.cpp3
-rw-r--r--protocols/Discord/src/gateway.cpp10
-rw-r--r--protocols/Discord/src/guilds.cpp15
-rw-r--r--protocols/Discord/src/proto.cpp22
-rw-r--r--protocols/Discord/src/proto.h54
-rw-r--r--protocols/Discord/src/voice.cpp178
6 files changed, 215 insertions, 67 deletions
diff --git a/protocols/Discord/src/dispatch.cpp b/protocols/Discord/src/dispatch.cpp
index 0abc80d607..e56d29eec1 100644
--- a/protocols/Discord/src/dispatch.cpp
+++ b/protocols/Discord/src/dispatch.cpp
@@ -67,6 +67,9 @@ static handlers[] = // these structures must me sorted alphabetically
{ L"USER_SETTINGS_UPDATE", &CDiscordProto::OnCommandUserSettingsUpdate },
{ L"USER_UPDATE", &CDiscordProto::OnCommandUserUpdate },
+
+ { L"VOICE_SERVER_UPDATE", &CDiscordProto::OnCommandVoiceServerUpdate },
+ { L"VOICE_STATE_UPDATE", &CDiscordProto::OnCommandVoiceStateUpdate },
};
static int __cdecl pSearchFunc(const void *p1, const void *p2)
diff --git a/protocols/Discord/src/gateway.cpp b/protocols/Discord/src/gateway.cpp
index 8367462159..dc7d1da26e 100644
--- a/protocols/Discord/src/gateway.cpp
+++ b/protocols/Discord/src/gateway.cpp
@@ -337,3 +337,13 @@ bool CDiscordProto::GatewaySendStatus(int iStatus, const wchar_t *pwszStatusText
JSONNode root; root << INT_PARAM("op", OPCODE_STATUS_UPDATE) << payload;
return GatewaySend(root);
}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+bool CDiscordProto::GatewaySendVoice(JSONNode &payload)
+{
+ payload.set_name("d");
+
+ JSONNode root; root << INT_PARAM("op", OPCODE_VOICE_UPDATE) << payload;
+ return GatewaySend(root);
+}
diff --git a/protocols/Discord/src/guilds.cpp b/protocols/Discord/src/guilds.cpp
index 322a4ae9ed..d19615ef93 100644
--- a/protocols/Discord/src/guilds.cpp
+++ b/protocols/Discord/src/guilds.cpp
@@ -160,6 +160,9 @@ void CDiscordProto::ProcessGuild(const JSONNode &pRoot)
gm->iStatus = StrToStatus(it["status"].as_mstring());
}
+ for (auto &it : pRoot["voice_states"])
+ pGuild->arVoiceStates.insert(new CDiscordVoiceState(it));
+
for (auto &it : pGuild->arChatUsers)
AddGuildUser(pGuild, *it);
@@ -354,15 +357,23 @@ static int compareChatUsers(const CDiscordGuildMember *p1, const CDiscordGuildMe
return compareInt64(p1->userId, p2->userId);
}
+static int compareVoiceState(const CDiscordVoiceState *p1, const CDiscordVoiceState *p2)
+{
+ return compareInt64(p1->m_userId, p2->m_userId);
+}
+
CDiscordGuild::CDiscordGuild(SnowFlake _id) :
m_id(_id),
+ arRoles(10, compareRoles),
arChannels(10, compareUsers),
arChatUsers(30, compareChatUsers),
- arRoles(10, compareRoles)
+ arVoiceStates(10, compareVoiceState)
{}
CDiscordGuild::~CDiscordGuild()
-{}
+{
+ delete pVoiceCall;
+}
CDiscordUser::~CDiscordUser()
{
diff --git a/protocols/Discord/src/proto.cpp b/protocols/Discord/src/proto.cpp
index 5929b5ce19..cea528e5b7 100644
--- a/protocols/Discord/src/proto.cpp
+++ b/protocols/Discord/src/proto.cpp
@@ -37,6 +37,11 @@ static int compareGuilds(const CDiscordGuild *p1, const CDiscordGuild *p2)
return compareInt64(p1->m_id, p2->m_id);
}
+static int compareCalls(const CDiscordVoiceCall *p1, const CDiscordVoiceCall *p2)
+{
+ return compareInt64(p1->channelId, p2->channelId);
+}
+
CDiscordProto::CDiscordProto(const char *proto_name, const wchar_t *username) :
PROTO<CDiscordProto>(proto_name, username),
m_impl(*this),
@@ -46,7 +51,7 @@ CDiscordProto::CDiscordProto(const char *proto_name, const wchar_t *username) :
arGuilds(1, compareGuilds),
arMarkReadQueue(1, compareUsers),
arOwnMessages(1, compareMessages),
- arVoiceCalls(1),
+ arVoiceCalls(1, compareCalls),
m_wszEmail(this, "Email", L""),
m_wszDefaultGroup(this, "GroupName", DB_KEYVAL_GROUP),
@@ -89,21 +94,6 @@ CDiscordProto::CDiscordProto(const char *proto_name, const wchar_t *username) :
// database
db_set_resident(m_szModuleName, "XStatusMsg");
- // custom events
- DBEVENTTYPEDESCR dbEventType = {};
- dbEventType.module = m_szModuleName;
- dbEventType.flags = DETF_HISTORY | DETF_MSGWINDOW;
-
- dbEventType.eventType = EVENT_INCOMING_CALL;
- dbEventType.descr = Translate("Incoming call");
- dbEventType.eventIcon = g_plugin.getIconHandle(IDI_VOICE_CALL);
- DbEvent_RegisterType(&dbEventType);
-
- dbEventType.eventType = EVENT_CALL_FINISHED;
- dbEventType.descr = Translate("Call ended");
- dbEventType.eventIcon = g_plugin.getIconHandle(IDI_VOICE_ENDED);
- DbEvent_RegisterType(&dbEventType);
-
// Groupchat initialization
GCREGISTER gcr = {};
gcr.dwFlags = GC_TYPNOTIF | GC_CHANMGR | GC_DATABASE;
diff --git a/protocols/Discord/src/proto.h b/protocols/Discord/src/proto.h
index 844e6c8ef0..117e7a90ba 100644
--- a/protocols/Discord/src/proto.h
+++ b/protocols/Discord/src/proto.h
@@ -1,8 +1,5 @@
#pragma once
-#define EVENT_INCOMING_CALL 10001
-#define EVENT_CALL_FINISHED 10002
-
enum Permission : uint64_t
{
CREATE_INVITE = (1ll << 0), // Allows creation of instant invites
@@ -148,6 +145,35 @@ struct CDiscordUser : public MZeroedObject
/////////////////////////////////////////////////////////////////////////////////////////
+struct CDiscordVoiceState : public MZeroedObject
+{
+ explicit CDiscordVoiceState(const JSONNode &node);
+
+ SnowFlake m_userId, m_channelId;
+ CMStringA m_sessionId;
+
+ union {
+ int m_flags;
+ struct {
+ bool m_bDeaf : 1; // deafened by the server
+ bool m_bMute : 1; // muted by the server
+ bool m_bSelfMute : 1; // locally muted
+ bool m_bSelfDeaf : 1; // locally deafened
+ bool m_nSelfVideo : 1; // camera enabled
+ bool m_bSuppress : 1; // user can't speak
+ };
+ };
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+struct CDiscordVoiceCall : public MZeroedObject
+{
+ SnowFlake channelId, guildId;
+ CMStringA szSessionId, szToken, szEndpoint;
+ time_t startTime;
+};
+
struct CDiscordGuildMember : public MZeroedObject
{
CDiscordGuildMember(SnowFlake id) :
@@ -191,6 +217,8 @@ struct CDiscordGuild : public MZeroedObject
LIST<CDiscordUser> arChannels;
OBJLIST<CDiscordGuildMember> arChatUsers;
OBJLIST<CDiscordRole> arRoles; // guild roles
+ OBJLIST<CDiscordVoiceState> arVoiceStates;
+ CDiscordVoiceCall *pVoiceCall;
uint64_t CalcPermissionOverride(SnowFlake myUserId, const JSONNode &json);
void ProcessRole(const JSONNode &json);
@@ -199,13 +227,6 @@ struct CDiscordGuild : public MZeroedObject
void SaveToFile();
};
-struct CDiscordVoiceCall
-{
- CMStringA szId;
- SnowFlake channelId;
- time_t startTime;
-};
-
/////////////////////////////////////////////////////////////////////////////////////////
#define OPCODE_DISPATCH 0
@@ -315,6 +336,7 @@ class CDiscordProto : public PROTO<CDiscordProto>
void GatewaySendIdentify(void);
void GatewaySendResume(void);
bool GatewaySendStatus(int iStatus, const wchar_t *pwszStatusText);
+ bool GatewaySendVoice(JSONNode &node);
GatewayHandlerFunc GetHandler(const wchar_t*);
@@ -343,7 +365,6 @@ class CDiscordProto : public PROTO<CDiscordProto>
OBJLIST<CDiscordUser> arUsers;
OBJLIST<COwnMessage> arOwnMessages;
- OBJLIST<CDiscordVoiceCall> arVoiceCalls;
CDiscordUser* FindUser(SnowFlake id);
CDiscordUser* FindUser(const wchar_t *pwszUsername, int iDiscriminator);
@@ -414,7 +435,14 @@ class CDiscordProto : public PROTO<CDiscordProto>
//////////////////////////////////////////////////////////////////////////////////////
// voice
+ mir_cs m_csVoice;
+ OBJLIST<CDiscordVoiceCall> arVoiceCalls;
+
void InitVoip(bool bEnable);
+ void TryVoiceStart(CDiscordGuild *pGuild);
+ void VoiceChannelConnect(MCONTACT hContact);
+
+ CDiscordVoiceCall* FindCall(SnowFlake channelId);
INT_PTR __cdecl VoiceCaps(WPARAM, LPARAM);
INT_PTR __cdecl VoiceCanCall(WPARAM, LPARAM);
@@ -423,6 +451,8 @@ class CDiscordProto : public PROTO<CDiscordProto>
INT_PTR __cdecl VoiceCallCancel(WPARAM, LPARAM);
int __cdecl OnVoiceState(WPARAM, LPARAM);
+
+ void __cdecl VoiceClientThread(void *);
//////////////////////////////////////////////////////////////////////////////////////
// misc methods
@@ -526,6 +556,8 @@ public:
void OnCommandTyping(const JSONNode &json);
void OnCommandUserUpdate(const JSONNode &json);
void OnCommandUserSettingsUpdate(const JSONNode &json);
+ void OnCommandVoiceServerUpdate(const JSONNode &json);
+ void OnCommandVoiceStateUpdate(const JSONNode &json);
void OnLoggedIn();
void OnLoggedOut();
diff --git a/protocols/Discord/src/voice.cpp b/protocols/Discord/src/voice.cpp
index 51899c57e3..b30d7b166a 100644
--- a/protocols/Discord/src/voice.cpp
+++ b/protocols/Discord/src/voice.cpp
@@ -17,34 +17,101 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "stdafx.h"
+CDiscordVoiceState::CDiscordVoiceState(const JSONNode &node)
+{
+ m_userId = ::getId(node["user_id"]);
+ m_channelId = ::getId(node["channel_id"]);
+ m_sessionId = node["session_id"].as_mstring();
+ m_bDeaf = node["deaf"].as_bool();
+ m_bMute = node["mute"].as_bool();
+ m_bSuppress = node["suppress"].as_bool();
+ m_bSelfDeaf = node["self_deaf"].as_bool();
+ m_bSelfMute = node["self_mute"].as_bool();
+ m_nSelfVideo = node["self_video"].as_bool();
+}
+
+CDiscordVoiceCall* CDiscordProto::FindCall(SnowFlake channelId)
+{
+ mir_cslock lck(m_csVoice);
+ if (auto *pCall = arVoiceCalls.find((CDiscordVoiceCall *)&channelId))
+ return pCall;
+
+ return nullptr;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// voice server commands
+
+void CDiscordProto::VoiceChannelConnect(MCONTACT hContact)
+{
+ SnowFlake channelId = getId(hContact, DB_KEY_CHANNELID);
+ if (!hContact || !channelId)
+ return;
+
+ if (auto *pUser = FindUserByChannel(channelId)) {
+ if (auto *pGuild = pUser->pGuild) {
+ {
+ mir_cslock lck(m_csVoice);
+
+ // a voice call for this guild is already being establishing, exit
+ if (pGuild->pVoiceCall)
+ return;
+
+ pGuild->pVoiceCall = new CDiscordVoiceCall();
+ pGuild->pVoiceCall->guildId = pGuild->m_id;
+ pGuild->pVoiceCall->channelId = channelId;
+ }
+ JSONNode payload;
+ payload << INT64_PARAM("guild_id", pGuild->m_id) << INT64_PARAM("channel_id", channelId)
+ << BOOL_PARAM("self_mute", false) << BOOL_PARAM("self_deaf", false);
+ GatewaySendVoice(payload);
+ }
+ }
+}
+
+void CDiscordProto::TryVoiceStart(CDiscordGuild *pGuild)
+{
+ if (auto *pCall = pGuild->pVoiceCall) {
+ // not enough data, waiting for the second command
+ if (pCall->szSessionId.IsEmpty() || pCall->szToken.IsEmpty() || pCall->szEndpoint.IsEmpty())
+ return;
+
+ // transfer a call from guild to the concrete channel
+ pGuild->pVoiceCall = nullptr;
+ if (auto *pUser = FindUserByChannel(pCall->channelId)) {
+ arVoiceCalls.insert(pCall);
+ ForkThread(&CDiscordProto::VoiceClientThread, pCall);
+ }
+ else {
+ delete pCall;
+ return;
+ }
+ }
+}
+
+void CDiscordProto::VoiceClientThread(void *param)
+{
+ auto *pCall = (CDiscordVoiceCall *)param;
+ pCall->startTime = time(0);
+}
+
/////////////////////////////////////////////////////////////////////////////////////////
// call operations (voice & video)
void CDiscordProto::OnCommandCallCreated(const JSONNode &pRoot)
{
for (auto &it : pRoot["voice_states"]) {
- SnowFlake channelId = ::getId(pRoot["channel_id"]);
+ SnowFlake channelId = ::getId(it["channel_id"]);
auto *pUser = FindUserByChannel(channelId);
if (pUser == nullptr) {
debugLogA("Call from unknown channel %lld, skipping", channelId);
continue;
}
- auto *pCall = new CDiscordVoiceCall();
- pCall->szId = it["session_id"].as_mstring();
- pCall->channelId = channelId;
- pCall->startTime = time(0);
- arVoiceCalls.insert(pCall);
-
- char *szMessage = TranslateU("Incoming call");
- DBEVENTINFO dbei = {};
- dbei.szModule = m_szModuleName;
- dbei.timestamp = pCall->startTime;
- dbei.eventType = EVENT_INCOMING_CALL;
- dbei.cbBlob = uint32_t(mir_strlen(szMessage) + 1);
- dbei.pBlob = szMessage;
- dbei.flags = DBEF_UTF;
- db_event_add(pUser->hContact, &dbei);
+ if (auto *pCall = FindCall(channelId)) {
+ pCall->startTime = time(0);
+ }
+ else debugLogA("Unregistered call received from channel %lld, skipping", channelId);
}
}
@@ -57,34 +124,66 @@ void CDiscordProto::OnCommandCallDeleted(const JSONNode &pRoot)
return;
}
- int elapsed = 0, currTime = time(0);
- for (auto &call : arVoiceCalls.rev_iter())
- if (call->channelId == channelId) {
- elapsed = currTime - call->startTime;
- arVoiceCalls.removeItem(&call);
- break;
+ if (auto *pCall = FindCall(channelId)) {
+ // int currTime = time(0);
+ for (auto &call : arVoiceCalls.rev_iter()) {
+ if (call->channelId == channelId) {
+ arVoiceCalls.removeItem(&call);
+ break;
+ }
}
-
- if (!elapsed) {
- debugLogA("Call from channel %lld isn't registered, skipping", channelId);
- return;
}
-
- CMStringA szMessage(FORMAT, TranslateU("Call ended, %d seconds long"), elapsed);
- DBEVENTINFO dbei = {};
- dbei.szModule = m_szModuleName;
- dbei.timestamp = currTime;
- dbei.eventType = EVENT_CALL_FINISHED;
- dbei.cbBlob = uint32_t(szMessage.GetLength() + 1);
- dbei.pBlob = szMessage.GetBuffer();
- dbei.flags = DBEF_UTF;
- db_event_add(pUser->hContact, &dbei);
+ else debugLogA("Unregistered call received from channel %lld, skipping", channelId);
}
void CDiscordProto::OnCommandCallUpdated(const JSONNode&)
{
}
+void CDiscordProto::OnCommandVoiceServerUpdate(const JSONNode &pRoot)
+{
+ if (auto *pGuild = FindGuild(::getId(pRoot["guild_id"]))) {
+ mir_cslock lck(m_csVoice);
+ if (auto *pCall = pGuild->pVoiceCall) {
+ pCall->szToken = pRoot["token"].as_mstring();
+ pCall->szEndpoint = pRoot["endpoint"].as_mstring();
+ TryVoiceStart(pGuild);
+ }
+ }
+}
+
+void CDiscordProto::OnCommandVoiceStateUpdate(const JSONNode &pRoot)
+{
+ CDiscordVoiceState vs(pRoot);
+
+ if (auto *pGuild = FindGuild(::getId(pRoot["guild_id"]))) {
+ if (vs.m_channelId == 0) {
+ for (auto &it : pGuild->arVoiceStates.rev_iter())
+ if (it->m_userId == vs.m_userId)
+ pGuild->arVoiceStates.removeItem(&it);
+
+ // if (vs.m_userId == m_ownId)
+ // disconnect voiсe from guild
+ }
+ else {
+ auto *pVS = pGuild->arVoiceStates.find(&vs);
+ if (pVS)
+ *pVS = vs;
+ else
+ pGuild->arVoiceStates.insert(new CDiscordVoiceState(vs));
+
+ // if our voice call to this guild is in progress, assign session id & call it
+ if (vs.m_userId == m_ownId) {
+ mir_cslock lck(m_csVoice);
+ if (auto *pCall = pGuild->pVoiceCall) {
+ pCall->szSessionId = vs.m_sessionId;
+ TryVoiceStart(pGuild);
+ }
+ }
+ }
+ }
+}
+
/////////////////////////////////////////////////////////////////////////////////////////
// Events & services
@@ -102,8 +201,9 @@ INT_PTR CDiscordProto::VoiceCanCall(WPARAM hContact, LPARAM)
return FALSE;
}
-INT_PTR CDiscordProto::VoiceCallCreate(WPARAM, LPARAM)
+INT_PTR CDiscordProto::VoiceCallCreate(WPARAM hContact, LPARAM)
{
+ VoiceChannelConnect(hContact);
return 0;
}
@@ -125,7 +225,7 @@ int CDiscordProto::OnVoiceState(WPARAM wParam, LPARAM)
CDiscordVoiceCall *pCall = nullptr;
for (auto &it : arVoiceCalls)
- if (it->szId == pVoice->id) {
+ if (it->szSessionId == pVoice->id) {
pCall = it;
break;
}
@@ -139,6 +239,8 @@ int CDiscordProto::OnVoiceState(WPARAM wParam, LPARAM)
return 0;
}
+/////////////////////////////////////////////////////////////////////////////////////////
+
void CDiscordProto::InitVoip(bool bEnable)
{
if (!g_plugin.bVoiceService)