diff options
author | ouwou <26526779+ouwou@users.noreply.github.com> | 2020-08-17 02:40:03 -0400 |
---|---|---|
committer | ouwou <26526779+ouwou@users.noreply.github.com> | 2020-08-17 02:40:03 -0400 |
commit | 18af78e6af49821f8c7adb5b4325d75c8bf4fd03 (patch) | |
tree | 2a2812d604fa0b00891613e868a79972159886aa /discord | |
parent | 212511e29d01af9b096e91371956b28de834bd13 (diff) | |
download | abaddon-portaudio-18af78e6af49821f8c7adb5b4325d75c8bf4fd03.tar.gz abaddon-portaudio-18af78e6af49821f8c7adb5b4325d75c8bf4fd03.zip |
connect and heartbeat
Diffstat (limited to 'discord')
-rw-r--r-- | discord/discord.cpp | 85 | ||||
-rw-r--r-- | discord/discord.hpp | 75 | ||||
-rw-r--r-- | discord/websocket.cpp | 30 | ||||
-rw-r--r-- | discord/websocket.hpp | 22 |
4 files changed, 212 insertions, 0 deletions
diff --git a/discord/discord.cpp b/discord/discord.cpp new file mode 100644 index 0000000..cd1e723 --- /dev/null +++ b/discord/discord.cpp @@ -0,0 +1,85 @@ +#include "discord.hpp" + +DiscordClient::DiscordClient() {} + +void DiscordClient::Start() { + if (m_client_connected) + throw std::runtime_error("attempt to start client twice consecutively"); + + m_client_connected = true; + m_websocket.StartConnection(DiscordGateway); + m_websocket.SetJSONCallback(std::bind(&DiscordClient::HandleGatewayMessage, this, std::placeholders::_1)); +} + +void DiscordClient::Stop() { + if (!m_client_connected) return; + m_heartbeat_waiter.kill(); + m_heartbeat_thread.join(); + m_client_connected = false; +} + +bool DiscordClient::IsStarted() const { + return m_client_connected; +} + +void DiscordClient::HandleGatewayMessage(nlohmann::json j) { + GatewayMessage m; + try { + m = j; + } catch (std::exception &e) { + printf("Error decoding JSON. Discarding message: %s\n", e.what()); + return; + } + + switch (m.Opcode) { + case GatewayOp::Hello: { + HelloMessageData d = m.Data; + m_heartbeat_msec = d.HeartbeatInterval; + m_heartbeat_thread = std::thread(std::bind(&DiscordClient::HeartbeatThread, this)); + } break; + case GatewayOp::HeartbeatAck: { + m_heartbeat_acked = true; + } break; + default: + printf("Unknown opcode %d\n", m.Opcode); + break; + } +} + +void DiscordClient::HeartbeatThread() { + while (m_client_connected) { + if (!m_heartbeat_acked) { + printf("wow! a heartbeat wasn't acked! how could this happen?"); + } + + m_heartbeat_acked = false; + + HeartbeatMessage msg; + msg.Sequence = m_last_sequence; + nlohmann::json j = msg; + m_websocket.Send(j.dump()); + + if (!m_heartbeat_waiter.wait_for(std::chrono::milliseconds(m_heartbeat_msec))) + break; + } +} + +void from_json(const nlohmann::json &j, GatewayMessage &m) { + j.at("op").get_to(m.Opcode); + m.Data = j.at("d"); + + if (j.contains("t") && !j.at("t").is_null()) + j.at("t").get_to(m.Type); +} + +void from_json(const nlohmann::json &j, HelloMessageData &m) { + j.at("heartbeat_interval").get_to(m.HeartbeatInterval); +} + +void to_json(nlohmann::json &j, const HeartbeatMessage &m) { + j["op"] = GatewayOp::Heartbeat; + if (m.Sequence == -1) + j["d"] = nullptr; + else + j["d"] = m.Sequence; +} diff --git a/discord/discord.hpp b/discord/discord.hpp new file mode 100644 index 0000000..692d57b --- /dev/null +++ b/discord/discord.hpp @@ -0,0 +1,75 @@ +#pragma once +#include "websocket.hpp" +#include <nlohmann/json.hpp> +#include <thread> + +enum class GatewayOp : int { + Heartbeat = 1, + Hello = 10, + HeartbeatAck = 11, +}; + +struct GatewayMessage { + GatewayOp Opcode; + nlohmann::json Data; + std::string Type; + + friend void from_json(const nlohmann::json &j, GatewayMessage &m); +}; + +struct HelloMessageData { + int HeartbeatInterval; + + friend void from_json(const nlohmann::json &j, HelloMessageData &m); +}; + +struct HeartbeatMessage : GatewayMessage { + int Sequence; + + friend void to_json(nlohmann::json &j, const HeartbeatMessage &m); +}; + +// https://stackoverflow.com/questions/29775153/stopping-long-sleep-threads/29775639#29775639 +class HeartbeatWaiter { +public: + template<class R, class P> + bool wait_for(std::chrono::duration<R, P> const &time) const { + std::unique_lock<std::mutex> lock(m); + return !cv.wait_for(lock, time, [&] { return terminate; }); + } + void kill() { + std::unique_lock<std::mutex> lock(m); + terminate = true; + cv.notify_all(); + } + +private: + mutable std::condition_variable cv; + mutable std::mutex m; + bool terminate = false; +}; + +class DiscordClient { +public: + static const constexpr char *DiscordGateway = "wss://gateway.discord.gg/?v=6&encoding=json"; + static const constexpr char *DiscordAPI = "https://discord.com/api"; + +public: + DiscordClient(); + void Start(); + void Stop(); + bool IsStarted() const; + +private: + void HandleGatewayMessage(nlohmann::json msg); + void HeartbeatThread(); + + Websocket m_websocket; + bool m_client_connected = false; + + std::thread m_heartbeat_thread; + int m_last_sequence = -1; + int m_heartbeat_msec = 0; + HeartbeatWaiter m_heartbeat_waiter; + bool m_heartbeat_acked = true; +}; diff --git a/discord/websocket.cpp b/discord/websocket.cpp new file mode 100644 index 0000000..3590db3 --- /dev/null +++ b/discord/websocket.cpp @@ -0,0 +1,30 @@ +#include "websocket.hpp" +#include <functional> +#include <nlohmann/json.hpp> + +Websocket::Websocket() {} + +void Websocket::StartConnection(std::string url) { + m_websocket.setUrl(url); + m_websocket.setOnMessageCallback(std::bind(&Websocket::OnMessage, this, std::placeholders::_1)); + m_websocket.start(); +} + +void Websocket::SetJSONCallback(JSONCallback_t func) { + m_json_callback = func; +} + +void Websocket::Send(const std::string &str) { + m_websocket.sendText(str); +} + +void Websocket::OnMessage(const ix::WebSocketMessagePtr &msg) { + switch (msg->type) { + case ix::WebSocketMessageType::Message: + printf("%s\n", msg->str.c_str()); + auto obj = nlohmann::json::parse(msg->str); + if (m_json_callback) + m_json_callback(obj); + break; + } +} diff --git a/discord/websocket.hpp b/discord/websocket.hpp new file mode 100644 index 0000000..47a60d5 --- /dev/null +++ b/discord/websocket.hpp @@ -0,0 +1,22 @@ +#pragma once +#include <ixwebsocket/IXNetSystem.h> +#include <ixwebsocket/IXWebSocket.h> +#include <string> +#include <functional> +#include <nlohmann/json.hpp> + +class Websocket { +public: + Websocket(); + void StartConnection(std::string url); + + using JSONCallback_t = std::function<void(nlohmann::json)>; + void SetJSONCallback(JSONCallback_t func); + void Send(const std::string &str); + +private: + void OnMessage(const ix::WebSocketMessagePtr &msg); + + JSONCallback_t m_json_callback; + ix::WebSocket m_websocket; +}; |