summaryrefslogtreecommitdiff
path: root/discord
diff options
context:
space:
mode:
authorouwou <26526779+ouwou@users.noreply.github.com>2020-08-17 02:40:03 -0400
committerouwou <26526779+ouwou@users.noreply.github.com>2020-08-17 02:40:03 -0400
commit18af78e6af49821f8c7adb5b4325d75c8bf4fd03 (patch)
tree2a2812d604fa0b00891613e868a79972159886aa /discord
parent212511e29d01af9b096e91371956b28de834bd13 (diff)
downloadabaddon-portaudio-18af78e6af49821f8c7adb5b4325d75c8bf4fd03.tar.gz
abaddon-portaudio-18af78e6af49821f8c7adb5b4325d75c8bf4fd03.zip
connect and heartbeat
Diffstat (limited to 'discord')
-rw-r--r--discord/discord.cpp85
-rw-r--r--discord/discord.hpp75
-rw-r--r--discord/websocket.cpp30
-rw-r--r--discord/websocket.hpp22
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;
+};