/*
Copyright © 2016-22 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 .
*/
#include "stdafx.h"
CDiscordVoiceCall::CDiscordVoiceCall(CDiscordProto *pOwner) :
ppro(pOwner),
m_arModes(1),
m_timer(Miranda_GetSystemWindow(), UINT_PTR(this))
{
int iError = 0;
m_encoder = opus_encoder_create(48000, 2, OPUS_APPLICATION_VOIP, &iError);
m_repacketizer = opus_repacketizer_create();
m_timer.OnEvent = Callback(this, &CDiscordVoiceCall::onTimer);
}
CDiscordVoiceCall::~CDiscordVoiceCall()
{
delete m_ws;
m_timer.StopSafe();
if (m_encoder) {
opus_encoder_destroy(m_encoder);
m_encoder = nullptr;
}
if (m_repacketizer) {
opus_repacketizer_destroy(m_repacketizer);
m_repacketizer = nullptr;
}
if (m_hBind) {
Netlib_CloseHandle(m_hBind);
m_hBind = nullptr;
}
}
void CDiscordVoiceCall::write(int op, JSONNode &d)
{
if (m_ws == nullptr)
return;
d.set_name("d");
JSONNode payload;
payload << INT_PARAM("op", op) << d;
auto json = payload.write();
ppro->debugLogA("Voice JSON sent: %s", json.c_str());
mir_cslock lck(m_cs);
m_ws->sendText(json.c_str());
}
void JsonWebSocket::process(const JSONNode &json)
{
p->process(json);
}
//////////////////////////////////////////////////////////////////////////////////////////
void CDiscordVoiceCall::process(const JSONNode &node)
{
int op = node["op"].as_int();
auto &d = node["d"];
switch (op) {
case 2:
processStreams(d);
break;
case 8:
processHello(d);
break;
}
}
void CDiscordVoiceCall::processHello(const JSONNode &d)
{
// HELLO
if (int iTineout = d["heartbeat_interval"].as_int())
m_timer.StartSafe(iTineout);
JSONNode json;
json << INT64_PARAM("server_id", guildId)
<< CHAR_PARAM("session_id", szSessionId) << CHAR_PARAM("token", szToken);
if (m_arModes.getCount()) {
// resume the old session
write(7, json);
}
else {
// let's create new session
json << INT64_PARAM("user_id", ppro->getId(DB_KEY_ID));
write(0, json);
}
startTime = time(0);
}
void CDiscordVoiceCall::processStreams(const JSONNode &d)
{
m_szIp = d["ip"].as_mstring();
m_iPort = d["port"].as_int();
m_iSsrc = d["ssrc"].as_int();
for (auto &it : d["modes"])
m_arModes.insert(newStr(it.as_string().c_str()));
ppro->debugLogA("Voice session established, UDP address = %s:%d", m_szIp.c_str(), m_iPort);
CMStringA szExternalIp;
if (auto *p = Netlib_GetMyIp(true)) {
szExternalIp = p->szIp[0];
mir_free(p);
}
NETLIBBIND nlb = {};
nlb.iType = SOCK_DGRAM; // UDP connection
nlb.pExtra = this;
nlb.pfnNewConnection = &GetConnection;
m_hBind = Netlib_BindPort(ppro->m_hGatewayNetlibUser, &nlb);
if (m_hBind == nullptr) {
ppro->debugLogA("UDP port binding failed, exiting");
m_bTerminated = true;
return;
}
JSONNode data; data.set_name("data");
data << CHAR_PARAM("address", szExternalIp) << INT_PARAM("port", nlb.wExPort) << CHAR_PARAM("mode", "xsalsa20_poly1305");
JSONNode json;
json << CHAR_PARAM("protocol", "UDP") << data;
write(1, json);
}
//////////////////////////////////////////////////////////////////////////////////////////
// Module entry point
void CDiscordProto::VoiceClientThread(void *param)
{
Thread_SetName("VoiceClientThread");
auto *pCall = (CDiscordVoiceCall *)param;
debugLogA("Entering voice websocket thread");
MHttpHeaders hdrs;
hdrs.AddHeader("Origin", "https://discord.com");
JsonWebSocket ws(pCall);
NLHR_PTR pReply(ws.connect(m_hGatewayNetlibUser, pCall->szEndpoint + "/?encoding=json&v=8", &hdrs));
if (!pReply || pReply->resultCode != 101) {
debugLogA("Voice gateway connection failed, exiting");
return;
}
pCall->m_ws = &ws;
ws.run();
pCall->m_ws = nullptr;
}
/////////////////////////////////////////////////////////////////////////////////////////
// UDP part
void CDiscordVoiceCall::GetConnection(HNETLIBCONN /*hNewConnection*/, uint32_t /*dwRemoteIP*/, void *pExtra)
{
auto *pThis = (CDiscordVoiceCall *)pExtra;
pThis->ppro->debugLogA("boo!");
}