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/
abaddon.ini
testdata/

View File

@ -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" />

View File

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

View File

@ -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() {
m_gtk_app = Gtk::Application::create("com.github.lorpus.abaddon");
MainWindow main;
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);
Abaddon::Abaddon()
: m_settings("abaddon.ini") {
m_discord.SetAbaddon(this);
LoadFromSettings();
}
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();
}
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();
}

View File

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

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 <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)

View File

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

View File

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

View File

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

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 "../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;
}

View File

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