diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | Abaddon.vcxproj | 4 | ||||
-rw-r--r-- | Abaddon.vcxproj.filters | 12 | ||||
-rw-r--r-- | abaddon.cpp | 80 | ||||
-rw-r--r-- | abaddon.hpp | 23 | ||||
-rw-r--r-- | dialogs/token.cpp | 34 | ||||
-rw-r--r-- | dialogs/token.hpp | 19 | ||||
-rw-r--r-- | discord/discord.cpp | 66 | ||||
-rw-r--r-- | discord/discord.hpp | 38 | ||||
-rw-r--r-- | discord/websocket.cpp | 23 | ||||
-rw-r--r-- | discord/websocket.hpp | 3 | ||||
-rw-r--r-- | settings.cpp | 23 | ||||
-rw-r--r-- | settings.hpp | 18 | ||||
-rw-r--r-- | windows/mainwindow.cpp | 31 | ||||
-rw-r--r-- | windows/mainwindow.hpp | 3 |
15 files changed, 352 insertions, 28 deletions
@@ -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; }; |