settings, token entry, receive READY

This commit is contained in:
ouwou 2020-08-19 01:07:55 -04:00
parent 18af78e6af
commit 3c3fe3b9f7
15 changed files with 360 additions and 36 deletions

3
.gitignore vendored
View File

@ -348,3 +348,6 @@ MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder # Ionide (cross platform F# VS Code tools) working folder
.ionide/ .ionide/
abaddon.ini
testdata/

View File

@ -143,15 +143,19 @@
<ItemGroup> <ItemGroup>
<ClCompile Include="abaddon.cpp" /> <ClCompile Include="abaddon.cpp" />
<ClCompile Include="components\channels.cpp" /> <ClCompile Include="components\channels.cpp" />
<ClCompile Include="dialogs\token.cpp" />
<ClCompile Include="discord\discord.cpp" /> <ClCompile Include="discord\discord.cpp" />
<ClCompile Include="discord\websocket.cpp" /> <ClCompile Include="discord\websocket.cpp" />
<ClCompile Include="settings.cpp" />
<ClCompile Include="windows\mainwindow.cpp" /> <ClCompile Include="windows\mainwindow.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="components\channels.hpp" /> <ClInclude Include="components\channels.hpp" />
<ClInclude Include="abaddon.hpp" /> <ClInclude Include="abaddon.hpp" />
<ClInclude Include="dialogs\token.hpp" />
<ClInclude Include="discord\discord.hpp" /> <ClInclude Include="discord\discord.hpp" />
<ClInclude Include="discord\websocket.hpp" /> <ClInclude Include="discord\websocket.hpp" />
<ClInclude Include="settings.hpp" />
<ClInclude Include="windows\mainwindow.hpp" /> <ClInclude Include="windows\mainwindow.hpp" />
</ItemGroup> </ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />

View File

@ -30,6 +30,12 @@
<ClCompile Include="discord\discord.cpp"> <ClCompile Include="discord\discord.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="settings.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="dialogs\token.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="windows\mainwindow.hpp"> <ClInclude Include="windows\mainwindow.hpp">
@ -47,5 +53,11 @@
<ClInclude Include="abaddon.hpp"> <ClInclude Include="abaddon.hpp">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="settings.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="dialogs\token.hpp">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,44 +1,92 @@
#include <gtkmm.h> #include <gtkmm.h>
#include "discord/discord.hpp"
#include "windows/mainwindow.hpp"
#include <memory> #include <memory>
#include <string>
#include "discord/discord.hpp"
#include "dialogs/token.hpp"
#include "abaddon.hpp" #include "abaddon.hpp"
#ifdef _WIN32 #ifdef _WIN32
#pragma comment(lib, "crypt32.lib") #pragma comment(lib, "crypt32.lib")
#endif #endif
int Abaddon::DoMainLoop() { Abaddon::Abaddon()
m_gtk_app = Gtk::Application::create("com.github.lorpus.abaddon"); : m_settings("abaddon.ini") {
m_discord.SetAbaddon(this);
MainWindow main; LoadFromSettings();
main.SetAbaddon(this);
main.set_title("Abaddon");
main.show();
m_gtk_app->signal_shutdown().connect([&]() {
m_discord.Stop();
});
/*sigc::connection draw_signal_handler = main.signal_draw().connect([&](const Cairo::RefPtr<Cairo::Context> &ctx) -> bool {
draw_signal_handler.disconnect();
return false;
});*/
return m_gtk_app->run(main);
} }
void Abaddon::StartDiscordThread() { Abaddon::~Abaddon() {
m_settings.Close();
m_discord.Stop();
}
int Abaddon::StartGTK() {
m_gtk_app = Gtk::Application::create("com.github.lorpus.abaddon");
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([&]() {
StopDiscord();
});
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 m_gtk_app->run(*m_main_window);
}
void Abaddon::LoadFromSettings() {
std::string token = m_settings.GetSetting("discord", "token");
if (token.size()) {
m_discord_token = token;
}
}
void Abaddon::StartDiscord() {
m_discord.Start(); 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() { void Abaddon::ActionConnect() {
if (!m_discord.IsStarted()) 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) { int main(int argc, char **argv) {
Abaddon abaddon; Abaddon abaddon;
return abaddon.DoMainLoop(); return abaddon.StartGTK();
} }

View File

@ -1,14 +1,33 @@
#include <gtkmm.h> #include <gtkmm.h>
#include <memory>
#include <string>
#include "discord/discord.hpp" #include "discord/discord.hpp"
#include "windows/mainwindow.hpp"
#include "settings.hpp"
class Abaddon { class Abaddon {
public: public:
int DoMainLoop(); Abaddon();
void StartDiscordThread(); ~Abaddon();
int StartGTK();
void StartDiscord();
void StopDiscord();
void LoadFromSettings();
void ActionConnect(); void ActionConnect();
void ActionDisconnect();
void ActionSetToken();
std::string GetDiscordToken() const;
bool IsDiscordActive() const;
private: private:
std::string m_discord_token;
Glib::RefPtr<Gtk::Application> m_gtk_app; Glib::RefPtr<Gtk::Application> m_gtk_app;
DiscordClient m_discord; DiscordClient m_discord;
SettingsManager m_settings;
std::unique_ptr<MainWindow> m_main_window; // wah wah cant create a gtkstylecontext fuck you
}; };

34
dialogs/token.cpp Normal file
View File

@ -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;
}

19
dialogs/token.hpp Normal file
View File

@ -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;
};

View File

@ -1,10 +1,18 @@
#include "../abaddon.hpp"
#include "discord.hpp" #include "discord.hpp"
#include <cassert>
DiscordClient::DiscordClient() {} DiscordClient::DiscordClient() {
LoadEventMap();
}
void DiscordClient::SetAbaddon(Abaddon *ptr) {
m_abaddon = ptr;
}
void DiscordClient::Start() { void DiscordClient::Start() {
if (m_client_connected) assert(!m_client_connected);
throw std::runtime_error("attempt to start client twice consecutively"); assert(!m_websocket.IsOpen());
m_client_connected = true; m_client_connected = true;
m_websocket.StartConnection(DiscordGateway); m_websocket.StartConnection(DiscordGateway);
@ -13,9 +21,11 @@ void DiscordClient::Start() {
void DiscordClient::Stop() { void DiscordClient::Stop() {
if (!m_client_connected) return; if (!m_client_connected) return;
m_heartbeat_waiter.kill(); m_heartbeat_waiter.kill();
m_heartbeat_thread.join(); m_heartbeat_thread.join();
m_client_connected = false; m_client_connected = false;
m_websocket.Stop();
} }
bool DiscordClient::IsStarted() const { bool DiscordClient::IsStarted() const {
@ -36,16 +46,33 @@ void DiscordClient::HandleGatewayMessage(nlohmann::json j) {
HelloMessageData d = m.Data; HelloMessageData d = m.Data;
m_heartbeat_msec = d.HeartbeatInterval; m_heartbeat_msec = d.HeartbeatInterval;
m_heartbeat_thread = std::thread(std::bind(&DiscordClient::HeartbeatThread, this)); m_heartbeat_thread = std::thread(std::bind(&DiscordClient::HeartbeatThread, this));
SendIdentify();
} break; } break;
case GatewayOp::HeartbeatAck: { case GatewayOp::HeartbeatAck: {
m_heartbeat_acked = true; m_heartbeat_acked = true;
} break; } 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: default:
printf("Unknown opcode %d\n", m.Opcode); printf("Unknown opcode %d\n", m.Opcode);
break; break;
} }
} }
void DiscordClient::HandleGatewayReady(const GatewayMessage &msg) {
}
void DiscordClient::HeartbeatThread() { void DiscordClient::HeartbeatThread() {
while (m_client_connected) { while (m_client_connected) {
if (!m_heartbeat_acked) { if (!m_heartbeat_acked) {
@ -57,13 +84,28 @@ void DiscordClient::HeartbeatThread() {
HeartbeatMessage msg; HeartbeatMessage msg;
msg.Sequence = m_last_sequence; msg.Sequence = m_last_sequence;
nlohmann::json j = msg; 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))) if (!m_heartbeat_waiter.wait_for(std::chrono::milliseconds(m_heartbeat_msec)))
break; 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) { void from_json(const nlohmann::json &j, GatewayMessage &m) {
j.at("op").get_to(m.Opcode); j.at("op").get_to(m.Opcode);
m.Data = j.at("d"); 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); 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) { void to_json(nlohmann::json &j, const HeartbeatMessage &m) {
j["op"] = GatewayOp::Heartbeat; j["op"] = GatewayOp::Heartbeat;
if (m.Sequence == -1) if (m.Sequence == -1)

View File

@ -2,13 +2,20 @@
#include "websocket.hpp" #include "websocket.hpp"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <thread> #include <thread>
#include <unordered_map>
enum class GatewayOp : int { enum class GatewayOp : int {
Event = 0,
Heartbeat = 1, Heartbeat = 1,
Identify = 2,
Hello = 10, Hello = 10,
HeartbeatAck = 11, HeartbeatAck = 11,
}; };
enum class GatewayEvent : int {
READY,
};
struct GatewayMessage { struct GatewayMessage {
GatewayOp Opcode; GatewayOp Opcode;
nlohmann::json Data; nlohmann::json Data;
@ -23,6 +30,28 @@ struct HelloMessageData {
friend void from_json(const nlohmann::json &j, HelloMessageData &m); 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 { struct HeartbeatMessage : GatewayMessage {
int Sequence; int Sequence;
@ -49,23 +78,32 @@ private:
bool terminate = false; bool terminate = false;
}; };
class Abaddon;
class DiscordClient { class DiscordClient {
public: public:
static const constexpr char *DiscordGateway = "wss://gateway.discord.gg/?v=6&encoding=json"; 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 *DiscordAPI = "https://discord.com/api";
static const constexpr char *GatewayIdentity = "Discord";
public: public:
DiscordClient(); DiscordClient();
void SetAbaddon(Abaddon *ptr);
void Start(); void Start();
void Stop(); void Stop();
bool IsStarted() const; bool IsStarted() const;
private: private:
void HandleGatewayMessage(nlohmann::json msg); void HandleGatewayMessage(nlohmann::json msg);
void HandleGatewayReady(const GatewayMessage &msg);
void HeartbeatThread(); void HeartbeatThread();
void SendIdentify();
Abaddon *m_abaddon = nullptr;
Websocket m_websocket; Websocket m_websocket;
bool m_client_connected = false; bool m_client_connected = false;
std::unordered_map<std::string, GatewayEvent> m_event_map;
void LoadEventMap();
std::thread m_heartbeat_thread; std::thread m_heartbeat_thread;
int m_last_sequence = -1; int m_last_sequence = -1;

View File

@ -10,21 +10,38 @@ void Websocket::StartConnection(std::string url) {
m_websocket.start(); 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) { void Websocket::SetJSONCallback(JSONCallback_t func) {
m_json_callback = func; m_json_callback = func;
} }
void Websocket::Send(const std::string &str) { void Websocket::Send(const std::string &str) {
printf("sending %s\n", str.c_str());
m_websocket.sendText(str); m_websocket.sendText(str);
} }
void Websocket::Send(const nlohmann::json &j) {
Send(j.dump());
}
void Websocket::OnMessage(const ix::WebSocketMessagePtr &msg) { void Websocket::OnMessage(const ix::WebSocketMessagePtr &msg) {
switch (msg->type) { switch (msg->type) {
case ix::WebSocketMessageType::Message: case ix::WebSocketMessageType::Message: {
printf("%s\n", msg->str.c_str()); 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); auto obj = nlohmann::json::parse(msg->str);
if (m_json_callback) if (m_json_callback)
m_json_callback(obj); m_json_callback(obj);
break; } break;
} }
} }

View File

@ -13,6 +13,9 @@ public:
using JSONCallback_t = std::function<void(nlohmann::json)>; using JSONCallback_t = std::function<void(nlohmann::json)>;
void SetJSONCallback(JSONCallback_t func); void SetJSONCallback(JSONCallback_t func);
void Send(const std::string &str); void Send(const std::string &str);
void Send(const nlohmann::json &j);
void Stop();
bool IsOpen() const;
private: private:
void OnMessage(const ix::WebSocketMessagePtr &msg); void OnMessage(const ix::WebSocketMessagePtr &msg);

23
settings.cpp Normal file
View File

@ -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());
}

18
settings.hpp Normal file
View File

@ -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;
};

View File

@ -1,14 +1,21 @@
#include "mainwindow.hpp" #include "mainwindow.hpp"
#include "../abaddon.hpp" #include "../abaddon.hpp"
MainWindow::MainWindow() MainWindow::MainWindow() {
: m_main_box(Gtk::ORIENTATION_VERTICAL) {
set_default_size(800, 600); set_default_size(800, 600);
m_main_box.set_orientation(Gtk::ORIENTATION_VERTICAL);
m_menu_discord.set_label("Discord"); m_menu_discord.set_label("Discord");
m_menu_discord.set_submenu(m_menu_discord_sub); m_menu_discord.set_submenu(m_menu_discord_sub);
m_menu_discord_connect.set_label("Connect"); 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_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_discord.set_submenu(m_menu_discord_sub);
m_menu_bar.append(m_menu_discord); m_menu_bar.append(m_menu_discord);
@ -16,6 +23,14 @@ MainWindow::MainWindow()
m_abaddon->ActionConnect(); // this feels maybe not too smart 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); m_main_box.add(m_menu_bar);
auto *channel_list = m_channel_list.GetRoot(); auto *channel_list = m_channel_list.GetRoot();
@ -26,6 +41,16 @@ MainWindow::MainWindow()
show_all_children(); 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; m_abaddon = ptr;
} }

View File

@ -7,6 +7,7 @@ class MainWindow : public Gtk::Window {
public: public:
MainWindow(); MainWindow();
void SetAbaddon(Abaddon *ptr); void SetAbaddon(Abaddon *ptr);
void UpdateMenuStatus();
protected: protected:
Gtk::Box m_main_box; Gtk::Box m_main_box;
@ -17,6 +18,8 @@ protected:
Gtk::MenuItem m_menu_discord; Gtk::MenuItem m_menu_discord;
Gtk::Menu m_menu_discord_sub; Gtk::Menu m_menu_discord_sub;
Gtk::MenuItem m_menu_discord_connect; Gtk::MenuItem m_menu_discord_connect;
Gtk::MenuItem m_menu_discord_disconnect;
Gtk::MenuItem m_menu_discord_set_token;
Abaddon *m_abaddon = nullptr; Abaddon *m_abaddon = nullptr;
}; };