summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--Abaddon.vcxproj4
-rw-r--r--Abaddon.vcxproj.filters12
-rw-r--r--abaddon.cpp80
-rw-r--r--abaddon.hpp23
-rw-r--r--dialogs/token.cpp34
-rw-r--r--dialogs/token.hpp19
-rw-r--r--discord/discord.cpp66
-rw-r--r--discord/discord.hpp38
-rw-r--r--discord/websocket.cpp23
-rw-r--r--discord/websocket.hpp3
-rw-r--r--settings.cpp23
-rw-r--r--settings.hpp18
-rw-r--r--windows/mainwindow.cpp31
-rw-r--r--windows/mainwindow.hpp3
15 files changed, 352 insertions, 28 deletions
diff --git a/.gitignore b/.gitignore
index dfcfd56..6aa4287 100644
--- a/.gitignore
+++ b/.gitignore
@@ -348,3 +348,6 @@ MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
+
+abaddon.ini
+testdata/
diff --git a/Abaddon.vcxproj b/Abaddon.vcxproj
index bbc3ef1..8ba6f1d 100644
--- a/Abaddon.vcxproj
+++ b/Abaddon.vcxproj
@@ -143,15 +143,19 @@
<ItemGroup>
<ClCompile Include="abaddon.cpp" />
<ClCompile Include="components\channels.cpp" />
+ <ClCompile Include="dialogs\token.cpp" />
<ClCompile Include="discord\discord.cpp" />
<ClCompile Include="discord\websocket.cpp" />
+ <ClCompile Include="settings.cpp" />
<ClCompile Include="windows\mainwindow.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="components\channels.hpp" />
<ClInclude Include="abaddon.hpp" />
+ <ClInclude Include="dialogs\token.hpp" />
<ClInclude Include="discord\discord.hpp" />
<ClInclude Include="discord\websocket.hpp" />
+ <ClInclude Include="settings.hpp" />
<ClInclude Include="windows\mainwindow.hpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
diff --git a/Abaddon.vcxproj.filters b/Abaddon.vcxproj.filters
index 00f0697..71ad1bd 100644
--- a/Abaddon.vcxproj.filters
+++ b/Abaddon.vcxproj.filters
@@ -30,6 +30,12 @@
<ClCompile Include="discord\discord.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="settings.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="dialogs\token.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="windows\mainwindow.hpp">
@@ -47,5 +53,11 @@
<ClInclude Include="abaddon.hpp">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="settings.hpp">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="dialogs\token.hpp">
+ <Filter>Header Files</Filter>
+ </ClInclude>
</ItemGroup>
</Project> \ No newline at end of file
diff --git a/abaddon.cpp b/abaddon.cpp
index 6c170e0..1979126 100644
--- a/abaddon.cpp
+++ b/abaddon.cpp
@@ -1,44 +1,92 @@
#include <gtkmm.h>
-#include "discord/discord.hpp"
-#include "windows/mainwindow.hpp"
#include <memory>
+#include <string>
+#include "discord/discord.hpp"
+#include "dialogs/token.hpp"
#include "abaddon.hpp"
#ifdef _WIN32
#pragma comment(lib, "crypt32.lib")
#endif
-int Abaddon::DoMainLoop() {
+Abaddon::Abaddon()
+ : m_settings("abaddon.ini") {
+ m_discord.SetAbaddon(this);
+ LoadFromSettings();
+}
+
+Abaddon::~Abaddon() {
+ m_settings.Close();
+ m_discord.Stop();
+}
+
+int Abaddon::StartGTK() {
m_gtk_app = Gtk::Application::create("com.github.lorpus.abaddon");
- MainWindow main;
- main.SetAbaddon(this);
- main.set_title("Abaddon");
- main.show();
+ m_main_window = std::make_unique<MainWindow>();
+ m_main_window->SetAbaddon(this);
+ m_main_window->set_title("Abaddon");
+ m_main_window->show();
+ m_main_window->UpdateMenuStatus();
m_gtk_app->signal_shutdown().connect([&]() {
- m_discord.Stop();
+ StopDiscord();
});
- /*sigc::connection draw_signal_handler = main.signal_draw().connect([&](const Cairo::RefPtr<Cairo::Context> &ctx) -> bool {
- draw_signal_handler.disconnect();
+ if (!m_settings.IsValid()) {
+ Gtk::MessageDialog dlg(*m_main_window, "The settings file could not be created!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
+ dlg.run();
+ }
- return false;
- });*/
+ return m_gtk_app->run(*m_main_window);
+}
- return m_gtk_app->run(main);
+void Abaddon::LoadFromSettings() {
+ std::string token = m_settings.GetSetting("discord", "token");
+ if (token.size()) {
+ m_discord_token = token;
+ }
}
-void Abaddon::StartDiscordThread() {
+void Abaddon::StartDiscord() {
m_discord.Start();
}
+void Abaddon::StopDiscord() {
+ m_discord.Stop();
+}
+
+bool Abaddon::IsDiscordActive() const {
+ return m_discord.IsStarted();
+}
+
+std::string Abaddon::GetDiscordToken() const {
+ return m_discord_token;
+}
+
void Abaddon::ActionConnect() {
if (!m_discord.IsStarted())
- StartDiscordThread();
+ StartDiscord();
+ m_main_window->UpdateMenuStatus();
+}
+
+void Abaddon::ActionDisconnect() {
+ if (m_discord.IsStarted())
+ StopDiscord();
+ m_main_window->UpdateMenuStatus();
+}
+
+void Abaddon::ActionSetToken() {
+ TokenDialog dlg(*m_main_window);
+ auto response = dlg.run();
+ if (response == Gtk::RESPONSE_OK) {
+ m_discord_token = dlg.GetToken();
+ m_main_window->UpdateMenuStatus();
+ m_settings.SetSetting("discord", "token", m_discord_token);
+ }
}
int main(int argc, char **argv) {
Abaddon abaddon;
- return abaddon.DoMainLoop();
+ return abaddon.StartGTK();
}
diff --git a/abaddon.hpp b/abaddon.hpp
index 6842d20..e7977cc 100644
--- a/abaddon.hpp
+++ b/abaddon.hpp
@@ -1,14 +1,33 @@
#include <gtkmm.h>
+#include <memory>
+#include <string>
#include "discord/discord.hpp"
+#include "windows/mainwindow.hpp"
+#include "settings.hpp"
class Abaddon {
public:
- int DoMainLoop();
- void StartDiscordThread();
+ Abaddon();
+ ~Abaddon();
+
+ int StartGTK();
+ void StartDiscord();
+ void StopDiscord();
+
+ void LoadFromSettings();
void ActionConnect();
+ void ActionDisconnect();
+ void ActionSetToken();
+
+ std::string GetDiscordToken() const;
+ bool IsDiscordActive() const;
private:
+ std::string m_discord_token;
+
Glib::RefPtr<Gtk::Application> m_gtk_app;
DiscordClient m_discord;
+ SettingsManager m_settings;
+ std::unique_ptr<MainWindow> m_main_window; // wah wah cant create a gtkstylecontext fuck you
}; \ No newline at end of file
diff --git a/dialogs/token.cpp b/dialogs/token.cpp
new file mode 100644
index 0000000..ca016a0
--- /dev/null
+++ b/dialogs/token.cpp
@@ -0,0 +1,34 @@
+#include "token.hpp"
+
+TokenDialog::TokenDialog(Gtk::Window &parent)
+ : Gtk::Dialog("Set Token", parent, true)
+ , m_layout(Gtk::ORIENTATION_VERTICAL)
+ , m_bbox(Gtk::ORIENTATION_HORIZONTAL)
+ , m_ok("OK")
+ , m_cancel("Cancel") {
+ set_default_size(300, 50);
+
+ m_ok.signal_clicked().connect([&]() {
+ m_token = m_entry.get_text();
+ response(Gtk::RESPONSE_OK);
+ });
+
+ m_cancel.signal_clicked().connect([&]() {
+ response(Gtk::RESPONSE_CANCEL);
+ });
+
+ m_bbox.pack_start(m_ok, Gtk::PACK_SHRINK);
+ m_bbox.pack_start(m_cancel, Gtk::PACK_SHRINK);
+ m_bbox.set_layout(Gtk::BUTTONBOX_END);
+
+ m_entry.set_hexpand(true);
+ m_layout.add(m_entry);
+ m_layout.add(m_bbox);
+ get_content_area()->add(m_layout);
+
+ show_all_children();
+}
+
+std::string TokenDialog::GetToken() {
+ return m_token;
+}
diff --git a/dialogs/token.hpp b/dialogs/token.hpp
new file mode 100644
index 0000000..7778bfb
--- /dev/null
+++ b/dialogs/token.hpp
@@ -0,0 +1,19 @@
+#pragma once
+#include <gtkmm.h>
+#include <string>
+
+class TokenDialog : public Gtk::Dialog {
+public:
+ TokenDialog(Gtk::Window &parent);
+ std::string GetToken();
+
+protected:
+ Gtk::Box m_layout;
+ Gtk::Button m_ok;
+ Gtk::Button m_cancel;
+ Gtk::ButtonBox m_bbox;
+ Gtk::Entry m_entry;
+
+private:
+ std::string m_token;
+};
diff --git a/discord/discord.cpp b/discord/discord.cpp
index cd1e723..42c6550 100644
--- a/discord/discord.cpp
+++ b/discord/discord.cpp
@@ -1,10 +1,18 @@
+#include "../abaddon.hpp"
#include "discord.hpp"
+#include <cassert>
-DiscordClient::DiscordClient() {}
+DiscordClient::DiscordClient() {
+ LoadEventMap();
+}
+
+void DiscordClient::SetAbaddon(Abaddon *ptr) {
+ m_abaddon = ptr;
+}
void DiscordClient::Start() {
- if (m_client_connected)
- throw std::runtime_error("attempt to start client twice consecutively");
+ assert(!m_client_connected);
+ assert(!m_websocket.IsOpen());
m_client_connected = true;
m_websocket.StartConnection(DiscordGateway);
@@ -13,9 +21,11 @@ void DiscordClient::Start() {
void DiscordClient::Stop() {
if (!m_client_connected) return;
+
m_heartbeat_waiter.kill();
m_heartbeat_thread.join();
m_client_connected = false;
+ m_websocket.Stop();
}
bool DiscordClient::IsStarted() const {
@@ -36,16 +46,33 @@ void DiscordClient::HandleGatewayMessage(nlohmann::json j) {
HelloMessageData d = m.Data;
m_heartbeat_msec = d.HeartbeatInterval;
m_heartbeat_thread = std::thread(std::bind(&DiscordClient::HeartbeatThread, this));
+ SendIdentify();
} break;
case GatewayOp::HeartbeatAck: {
m_heartbeat_acked = true;
} break;
+ case GatewayOp::Event: {
+ auto iter = m_event_map.find(m.Type);
+ if (iter == m_event_map.end()) {
+ printf("Unknown event %s\n", m.Type.c_str());
+ break;
+ }
+ switch (iter->second) {
+ case GatewayEvent::READY: {
+ HandleGatewayReady(m);
+ }
+ }
+ } break;
default:
printf("Unknown opcode %d\n", m.Opcode);
break;
}
}
+void DiscordClient::HandleGatewayReady(const GatewayMessage &msg) {
+
+}
+
void DiscordClient::HeartbeatThread() {
while (m_client_connected) {
if (!m_heartbeat_acked) {
@@ -57,13 +84,28 @@ void DiscordClient::HeartbeatThread() {
HeartbeatMessage msg;
msg.Sequence = m_last_sequence;
nlohmann::json j = msg;
- m_websocket.Send(j.dump());
+ m_websocket.Send(j);
if (!m_heartbeat_waiter.wait_for(std::chrono::milliseconds(m_heartbeat_msec)))
break;
}
}
+void DiscordClient::SendIdentify() {
+ auto token = m_abaddon->GetDiscordToken();
+ assert(token.size());
+ IdentifyMessage msg;
+ msg.Properties.OS = "OpenBSD";
+ msg.Properties.Device = GatewayIdentity;
+ msg.Properties.Browser = GatewayIdentity;
+ msg.Token = token;
+ m_websocket.Send(msg);
+}
+
+void DiscordClient::LoadEventMap() {
+ m_event_map["READY"] = GatewayEvent::READY;
+}
+
void from_json(const nlohmann::json &j, GatewayMessage &m) {
j.at("op").get_to(m.Opcode);
m.Data = j.at("d");
@@ -76,6 +118,22 @@ void from_json(const nlohmann::json &j, HelloMessageData &m) {
j.at("heartbeat_interval").get_to(m.HeartbeatInterval);
}
+void to_json(nlohmann::json &j, const IdentifyProperties &m) {
+ j["$os"] = m.OS;
+ j["$browser"] = m.Browser;
+ j["$device"] = m.Device;
+}
+
+void to_json(nlohmann::json &j, const IdentifyMessage &m) {
+ j["op"] = GatewayOp::Identify;
+ j["d"] = nlohmann::json::object();
+ j["d"]["token"] = m.Token;
+ j["d"]["properties"] = m.Properties;
+
+ if (m.LargeThreshold)
+ j["d"]["large_threshold"] = m.LargeThreshold;
+}
+
void to_json(nlohmann::json &j, const HeartbeatMessage &m) {
j["op"] = GatewayOp::Heartbeat;
if (m.Sequence == -1)
diff --git a/discord/discord.hpp b/discord/discord.hpp
index 692d57b..5a2d256 100644
--- a/discord/discord.hpp
+++ b/discord/discord.hpp
@@ -2,13 +2,20 @@
#include "websocket.hpp"
#include <nlohmann/json.hpp>
#include <thread>
+#include <unordered_map>
enum class GatewayOp : int {
+ Event = 0,
Heartbeat = 1,
+ Identify = 2,
Hello = 10,
HeartbeatAck = 11,
};
+enum class GatewayEvent : int {
+ READY,
+};
+
struct GatewayMessage {
GatewayOp Opcode;
nlohmann::json Data;
@@ -23,6 +30,28 @@ struct HelloMessageData {
friend void from_json(const nlohmann::json &j, HelloMessageData &m);
};
+struct ReadyEventData {
+ std::string AnalyticsToken; // opt
+
+};
+
+struct IdentifyProperties {
+ std::string OS;
+ std::string Browser;
+ std::string Device;
+
+ friend void to_json(nlohmann::json &j, const IdentifyProperties &m);
+};
+
+struct IdentifyMessage : GatewayMessage {
+ std::string Token;
+ IdentifyProperties Properties;
+ bool DoesSupportCompression = false;
+ int LargeThreshold = 0;
+
+ friend void to_json(nlohmann::json &j, const IdentifyMessage &m);
+};
+
struct HeartbeatMessage : GatewayMessage {
int Sequence;
@@ -49,23 +78,32 @@ private:
bool terminate = false;
};
+class Abaddon;
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";
+ static const constexpr char *GatewayIdentity = "Discord";
public:
DiscordClient();
+ void SetAbaddon(Abaddon *ptr);
void Start();
void Stop();
bool IsStarted() const;
private:
void HandleGatewayMessage(nlohmann::json msg);
+ void HandleGatewayReady(const GatewayMessage &msg);
void HeartbeatThread();
+ void SendIdentify();
+
+ Abaddon *m_abaddon = nullptr;
Websocket m_websocket;
bool m_client_connected = false;
+ std::unordered_map<std::string, GatewayEvent> m_event_map;
+ void LoadEventMap();
std::thread m_heartbeat_thread;
int m_last_sequence = -1;
diff --git a/discord/websocket.cpp b/discord/websocket.cpp
index 3590db3..01d01ca 100644
--- a/discord/websocket.cpp
+++ b/discord/websocket.cpp
@@ -10,21 +10,38 @@ void Websocket::StartConnection(std::string url) {
m_websocket.start();
}
+void Websocket::Stop() {
+ m_websocket.stop();
+}
+
+bool Websocket::IsOpen() const {
+ auto state = m_websocket.getReadyState();
+ return state == ix::ReadyState::Open;
+}
+
void Websocket::SetJSONCallback(JSONCallback_t func) {
m_json_callback = func;
}
void Websocket::Send(const std::string &str) {
+ printf("sending %s\n", str.c_str());
m_websocket.sendText(str);
}
+void Websocket::Send(const nlohmann::json &j) {
+ Send(j.dump());
+}
+
void Websocket::OnMessage(const ix::WebSocketMessagePtr &msg) {
switch (msg->type) {
- case ix::WebSocketMessageType::Message:
- printf("%s\n", msg->str.c_str());
+ case ix::WebSocketMessageType::Message: {
+ if (msg->str.size() > 1000)
+ printf("%s\n", msg->str.substr(0, 1000).c_str());
+ else
+ printf("%s\n", msg->str.c_str());
auto obj = nlohmann::json::parse(msg->str);
if (m_json_callback)
m_json_callback(obj);
- break;
+ } break;
}
}
diff --git a/discord/websocket.hpp b/discord/websocket.hpp
index 47a60d5..dc8cbec 100644
--- a/discord/websocket.hpp
+++ b/discord/websocket.hpp
@@ -13,6 +13,9 @@ public:
using JSONCallback_t = std::function<void(nlohmann::json)>;
void SetJSONCallback(JSONCallback_t func);
void Send(const std::string &str);
+ void Send(const nlohmann::json &j);
+ void Stop();
+ bool IsOpen() const;
private:
void OnMessage(const ix::WebSocketMessagePtr &msg);
diff --git a/settings.cpp b/settings.cpp
new file mode 100644
index 0000000..1fb0bb1
--- /dev/null
+++ b/settings.cpp
@@ -0,0 +1,23 @@
+#include "settings.hpp"
+
+SettingsManager::SettingsManager(std::string filename)
+ : m_filename(filename) {
+ auto rc = m_ini.LoadFile(filename.c_str());
+ m_ok = rc == SI_OK;
+}
+
+std::string SettingsManager::GetSetting(std::string section, std::string key, std::string fallback) {
+ return m_ini.GetValue(section.c_str(), key.c_str(), fallback.c_str());
+}
+
+void SettingsManager::SetSetting(std::string section, std::string key, std::string value) {
+ m_ini.SetValue(section.c_str(), key.c_str(), value.c_str());
+}
+
+bool SettingsManager::IsValid() const {
+ return m_ok;
+}
+
+void SettingsManager::Close() {
+ m_ini.SaveFile(m_filename.c_str());
+}
diff --git a/settings.hpp b/settings.hpp
new file mode 100644
index 0000000..e6a5b99
--- /dev/null
+++ b/settings.hpp
@@ -0,0 +1,18 @@
+#pragma once
+#include <string>
+#include <SimpleIni.h>
+
+class SettingsManager {
+public:
+ SettingsManager(std::string filename);
+
+ void Close();
+ std::string GetSetting(std::string section, std::string key, std::string fallback = "");
+ void SetSetting(std::string section, std::string key, std::string value);
+ bool IsValid() const;
+
+private:
+ bool m_ok;
+ std::string m_filename;
+ CSimpleIniA m_ini;
+};
diff --git a/windows/mainwindow.cpp b/windows/mainwindow.cpp
index d085362..8a8fa32 100644
--- a/windows/mainwindow.cpp
+++ b/windows/mainwindow.cpp
@@ -1,14 +1,21 @@
#include "mainwindow.hpp"
#include "../abaddon.hpp"
-MainWindow::MainWindow()
- : m_main_box(Gtk::ORIENTATION_VERTICAL) {
+MainWindow::MainWindow() {
set_default_size(800, 600);
+ m_main_box.set_orientation(Gtk::ORIENTATION_VERTICAL);
+
m_menu_discord.set_label("Discord");
m_menu_discord.set_submenu(m_menu_discord_sub);
m_menu_discord_connect.set_label("Connect");
+ m_menu_discord_connect.set_sensitive(false);
+ m_menu_discord_disconnect.set_label("Disconnect");
+ m_menu_discord_disconnect.set_sensitive(false);
+ m_menu_discord_set_token.set_label("Set Token");
m_menu_discord_sub.append(m_menu_discord_connect);
+ m_menu_discord_sub.append(m_menu_discord_disconnect);
+ m_menu_discord_sub.append(m_menu_discord_set_token);
m_menu_discord.set_submenu(m_menu_discord_sub);
m_menu_bar.append(m_menu_discord);
@@ -16,6 +23,14 @@ MainWindow::MainWindow()
m_abaddon->ActionConnect(); // this feels maybe not too smart
});
+ m_menu_discord_disconnect.signal_activate().connect([&] {
+ m_abaddon->ActionDisconnect();
+ });
+
+ m_menu_discord_set_token.signal_activate().connect([&] {
+ m_abaddon->ActionSetToken();
+ });
+
m_main_box.add(m_menu_bar);
auto *channel_list = m_channel_list.GetRoot();
@@ -26,6 +41,16 @@ MainWindow::MainWindow()
show_all_children();
}
-void MainWindow::SetAbaddon(Abaddon* ptr) {
+void MainWindow::UpdateMenuStatus() {
+ // Connect
+ std::string token = m_abaddon->GetDiscordToken();
+ bool discord_active = m_abaddon->IsDiscordActive();
+ m_menu_discord_connect.set_sensitive(token.size() > 0 && !discord_active);
+
+ // Disconnect
+ m_menu_discord_disconnect.set_sensitive(discord_active);
+}
+
+void MainWindow::SetAbaddon(Abaddon *ptr) {
m_abaddon = ptr;
}
diff --git a/windows/mainwindow.hpp b/windows/mainwindow.hpp
index 6e7ab16..88f20b4 100644
--- a/windows/mainwindow.hpp
+++ b/windows/mainwindow.hpp
@@ -7,6 +7,7 @@ class MainWindow : public Gtk::Window {
public:
MainWindow();
void SetAbaddon(Abaddon *ptr);
+ void UpdateMenuStatus();
protected:
Gtk::Box m_main_box;
@@ -17,6 +18,8 @@ protected:
Gtk::MenuItem m_menu_discord;
Gtk::Menu m_menu_discord_sub;
Gtk::MenuItem m_menu_discord_connect;
+ Gtk::MenuItem m_menu_discord_disconnect;
+ Gtk::MenuItem m_menu_discord_set_token;
Abaddon *m_abaddon = nullptr;
};