summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Abaddon.vcxproj6
-rw-r--r--Abaddon.vcxproj.filters6
-rw-r--r--README.md5
-rw-r--r--abaddon.cpp56
-rw-r--r--abaddon.hpp1
-rw-r--r--components/channels.cpp9
-rw-r--r--components/channels.hpp2
-rw-r--r--components/memberlist.cpp9
-rw-r--r--components/memberlist.hpp11
-rw-r--r--discord/discord.cpp82
-rw-r--r--discord/discord.hpp17
-rw-r--r--discord/websocket.cpp17
-rw-r--r--discord/websocket.hpp6
-rw-r--r--windows/mainwindow.cpp29
-rw-r--r--windows/mainwindow.hpp3
15 files changed, 205 insertions, 54 deletions
diff --git a/Abaddon.vcxproj b/Abaddon.vcxproj
index 87a56fd..10c6072 100644
--- a/Abaddon.vcxproj
+++ b/Abaddon.vcxproj
@@ -114,7 +114,7 @@
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
- <PreprocessorDefinitions>_DEBUG;_CONSOLE;USE_LOCAL_PROXY;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;USE_LOCAL_PROXY;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
@@ -130,7 +130,7 @@
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
- <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions>NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
@@ -146,6 +146,7 @@
<ClCompile Include="abaddon.cpp" />
<ClCompile Include="components\channels.cpp" />
<ClCompile Include="components\chatwindow.cpp" />
+ <ClCompile Include="components\memberlist.cpp" />
<ClCompile Include="dialogs\token.cpp" />
<ClCompile Include="discord\discord.cpp" />
<ClCompile Include="discord\http.cpp" />
@@ -157,6 +158,7 @@
<ClInclude Include="components\channels.hpp" />
<ClInclude Include="abaddon.hpp" />
<ClInclude Include="components\chatwindow.hpp" />
+ <ClInclude Include="components\memberlist.hpp" />
<ClInclude Include="dialogs\token.hpp" />
<ClInclude Include="discord\discord.hpp" />
<ClInclude Include="discord\http.hpp" />
diff --git a/Abaddon.vcxproj.filters b/Abaddon.vcxproj.filters
index 10ecb62..4ff8b0a 100644
--- a/Abaddon.vcxproj.filters
+++ b/Abaddon.vcxproj.filters
@@ -42,6 +42,9 @@
<ClCompile Include="components\chatwindow.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="components\memberlist.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="windows\mainwindow.hpp">
@@ -71,5 +74,8 @@
<ClInclude Include="components\chatwindow.hpp">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="components\memberlist.hpp">
+ <Filter>Header Files</Filter>
+ </ClInclude>
</ItemGroup>
</Project> \ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3ea86a9
--- /dev/null
+++ b/README.md
@@ -0,0 +1,5 @@
+Built using:
+* [gtkmm](https://www.gtkmm.org/en/)
+* [JSON for Modern C++](https://github.com/nlohmann/json)
+* [IXWebSocket](https://github.com/machinezone/IXWebSocket)
+* [C++ Requests: Curl for People](https://github.com/whoshuu/cpr/)
diff --git a/abaddon.cpp b/abaddon.cpp
index 74d980d..04d80ac 100644
--- a/abaddon.cpp
+++ b/abaddon.cpp
@@ -107,37 +107,49 @@ void Abaddon::ActionSetToken() {
}
void Abaddon::ActionMoveGuildUp(Snowflake id) {
- UserSettingsData d = m_discord.GetUserSettings();
- std::vector<Snowflake> &pos = d.GuildPositions;
- if (pos.size() == 0) {
- auto x = m_discord.GetUserSortedGuilds();
- for (const auto &pair : x)
- pos.push_back(pair.first);
+ auto order = m_discord.GetUserSortedGuilds();
+ // get iter to target
+ decltype(order)::iterator target_iter;
+ for (auto it = order.begin(); it != order.end(); it++) {
+ if (it->first == id) {
+ target_iter = it;
+ break;
+ }
}
- auto it = std::find(pos.begin(), pos.end(), id);
- assert(it != pos.end());
- std::vector<Snowflake>::iterator left = it - 1;
- std::swap(*left, *it);
+ decltype(order)::iterator left = target_iter - 1;
+ std::swap(*left, *target_iter);
- m_discord.UpdateSettingsGuildPositions(pos);
+ std::vector<Snowflake> new_sort;
+ for (const auto& x : order)
+ new_sort.push_back(x.first);
+
+ m_discord.UpdateSettingsGuildPositions(new_sort);
}
void Abaddon::ActionMoveGuildDown(Snowflake id) {
- UserSettingsData d = m_discord.GetUserSettings();
- std::vector<Snowflake> &pos = d.GuildPositions;
- if (pos.size() == 0) {
- auto x = m_discord.GetUserSortedGuilds();
- for (const auto &pair : x)
- pos.push_back(pair.first);
+ auto order = m_discord.GetUserSortedGuilds();
+ // get iter to target
+ decltype(order)::iterator target_iter;
+ for (auto it = order.begin(); it != order.end(); it++) {
+ if (it->first == id) {
+ target_iter = it;
+ break;
+ }
}
- auto it = std::find(pos.begin(), pos.end(), id);
- assert(it != pos.end());
- std::vector<Snowflake>::iterator right = it + 1;
- std::swap(*right, *it);
+ decltype(order)::iterator right = target_iter + 1;
+ std::swap(*right, *target_iter);
+
+ std::vector<Snowflake> new_sort;
+ for (const auto &x : order)
+ new_sort.push_back(x.first);
+
+ m_discord.UpdateSettingsGuildPositions(new_sort);
+}
- m_discord.UpdateSettingsGuildPositions(pos);
+void Abaddon::ActionCopyGuildID(Snowflake id) {
+ Gtk::Clipboard::get()->set_text(std::to_string(id));
}
void Abaddon::ActionListChannelItemClick(Snowflake id) {
diff --git a/abaddon.hpp b/abaddon.hpp
index 9d58358..87aebcf 100644
--- a/abaddon.hpp
+++ b/abaddon.hpp
@@ -23,6 +23,7 @@ public:
void ActionSetToken();
void ActionMoveGuildUp(Snowflake id);
void ActionMoveGuildDown(Snowflake id);
+ void ActionCopyGuildID(Snowflake id);
void ActionListChannelItemClick(Snowflake id);
void ActionChatInputSubmit(std::string msg, Snowflake channel);
diff --git a/components/channels.cpp b/components/channels.cpp
index d556d40..b913919 100644
--- a/components/channels.cpp
+++ b/components/channels.cpp
@@ -16,6 +16,10 @@ ChannelList::ChannelList() {
m_guild_menu_down->signal_activate().connect(sigc::mem_fun(*this, &ChannelList::on_menu_move_down));
m_guild_menu.append(*m_guild_menu_down);
+ m_guild_menu_copyid = Gtk::manage(new Gtk::MenuItem("_Copy ID", true));
+ m_guild_menu_copyid->signal_activate().connect(sigc::mem_fun(*this, &ChannelList::on_menu_copyid));
+ m_guild_menu.append(*m_guild_menu_copyid);
+
m_guild_menu.show_all();
m_list->set_activate_on_single_click(true);
@@ -269,6 +273,11 @@ void ChannelList::on_menu_move_down() {
m_abaddon->ActionMoveGuildDown(m_infos[row].ID);
}
+void ChannelList::on_menu_copyid() {
+ auto row = m_list->get_selected_row();
+ m_abaddon->ActionCopyGuildID(m_infos[row].ID);
+}
+
void ChannelList::AttachMenuHandler(Gtk::ListBoxRow *row) {
row->signal_button_press_event().connect([&, row](GdkEventButton *e) -> bool {
if (e->type == GDK_BUTTON_PRESS && e->button == GDK_BUTTON_SECONDARY) {
diff --git a/components/channels.hpp b/components/channels.hpp
index 1576783..fce1ce9 100644
--- a/components/channels.hpp
+++ b/components/channels.hpp
@@ -43,8 +43,10 @@ protected:
Gtk::Menu m_guild_menu;
Gtk::MenuItem *m_guild_menu_up;
Gtk::MenuItem *m_guild_menu_down;
+ Gtk::MenuItem *m_guild_menu_copyid;
void on_menu_move_up();
void on_menu_move_down();
+ void on_menu_copyid();
Glib::Dispatcher m_update_dispatcher;
mutable std::mutex m_update_mutex;
diff --git a/components/memberlist.cpp b/components/memberlist.cpp
new file mode 100644
index 0000000..f1553b8
--- /dev/null
+++ b/components/memberlist.cpp
@@ -0,0 +1,9 @@
+#include "memberlist.hpp"
+
+MemberList::MemberList() {
+ m_main = Gtk::manage(new Gtk::Box);
+}
+
+Gtk::Widget *MemberList::GetRoot() const {
+ return m_main;
+}
diff --git a/components/memberlist.hpp b/components/memberlist.hpp
new file mode 100644
index 0000000..5434324
--- /dev/null
+++ b/components/memberlist.hpp
@@ -0,0 +1,11 @@
+#pragma once
+#include <gtkmm.h>
+
+class MemberList {
+public:
+ MemberList();
+ Gtk::Widget *GetRoot() const;
+
+private:
+ Gtk::Box *m_main;
+};
diff --git a/discord/discord.cpp b/discord/discord.cpp
index 9aa8eed..15a719c 100644
--- a/discord/discord.cpp
+++ b/discord/discord.cpp
@@ -3,7 +3,11 @@
#include <cassert>
DiscordClient::DiscordClient()
- : m_http(DiscordAPI) {
+ : m_http(DiscordAPI)
+#ifdef ABADDON_USE_COMPRESSED_SOCKET
+ , m_decompress_buf(InflateChunkSize)
+#endif
+{
LoadEventMap();
}
@@ -17,7 +21,7 @@ void DiscordClient::Start() {
m_client_connected = true;
m_websocket.StartConnection(DiscordGateway);
- m_websocket.SetJSONCallback(std::bind(&DiscordClient::HandleGatewayMessage, this, std::placeholders::_1));
+ m_websocket.SetMessageCallback(std::bind(&DiscordClient::HandleGatewayMessageRaw, this, std::placeholders::_1));
}
void DiscordClient::Stop() {
@@ -25,7 +29,7 @@ void DiscordClient::Stop() {
if (!m_client_connected) return;
m_heartbeat_waiter.kill();
- m_heartbeat_thread.join();
+ if (m_heartbeat_thread.joinable()) m_heartbeat_thread.join();
m_client_connected = false;
m_websocket.Stop();
@@ -51,9 +55,21 @@ std::vector<std::pair<Snowflake, GuildData>> DiscordClient::GetUserSortedGuilds(
std::vector<std::pair<Snowflake, GuildData>> sorted_guilds;
if (m_user_settings.GuildPositions.size()) {
+ std::unordered_set<Snowflake> positioned_guilds(m_user_settings.GuildPositions.begin(), m_user_settings.GuildPositions.end());
+ // guilds not in the guild_positions object are at the top of the list, descending by guild ID
+ std::set<Snowflake> unpositioned_guilds;
+ for (const auto &[id, guild] : m_guilds) {
+ if (positioned_guilds.find(id) == positioned_guilds.end())
+ unpositioned_guilds.insert(id);
+ }
+
+ // unpositioned_guilds now has unpositioned guilds in ascending order
+ for (auto it = unpositioned_guilds.rbegin(); it != unpositioned_guilds.rend(); it++)
+ sorted_guilds.push_back(std::make_pair(*it, m_guilds.at(*it)));
+
+ // now the rest go at the end in the order they are sorted
for (const auto &id : m_user_settings.GuildPositions) {
- auto &guild = m_guilds.at(id);
- sorted_guilds.push_back(std::make_pair(id, guild));
+ sorted_guilds.push_back(std::make_pair(id, m_guilds.at(id)));
}
} else { // default sort is alphabetic
for (auto &it : m_guilds)
@@ -130,10 +146,62 @@ void DiscordClient::UpdateToken(std::string token) {
m_http.SetAuth(token);
}
-void DiscordClient::HandleGatewayMessage(nlohmann::json j) {
+std::string DiscordClient::DecompressGatewayMessage(std::string str) {
+ return std::string();
+}
+
+void DiscordClient::HandleGatewayMessageRaw(std::string str) {
+#ifdef ABADDON_USE_COMPRESSED_SOCKET // fuck you work
+ // handles multiple zlib compressed messages, calling HandleGatewayMessage when a full message is received
+ std::vector<uint8_t> buf(str.begin(), str.end());
+ int len = buf.size();
+ bool has_suffix = buf[len - 4] == 0x00 && buf[len - 3] == 0x00 && buf[len - 2] == 0xFF && buf[len - 1] == 0xFF;
+
+ m_compressed_buf.insert(m_compressed_buf.begin(), buf.begin(), buf.end());
+
+ if (!has_suffix) return;
+
+ z_stream z;
+ std::memset(&z, 0, sizeof(z));
+
+ assert(inflateInit2(&z, 15) == 0);
+
+ z.next_in = m_compressed_buf.data();
+ z.avail_in = m_compressed_buf.size();
+
+ // loop in case of really big messages (e.g. READY)
+ while (true) {
+ z.next_out = m_decompress_buf.data() + z.total_out;
+ z.avail_out = m_decompress_buf.size() - z.total_out;
+
+ int err = inflate(&z, Z_SYNC_FLUSH);
+ if ((err == Z_OK || err == Z_BUF_ERROR) && z.avail_in > 0) {
+ m_decompress_buf.resize(m_decompress_buf.size() + InflateChunkSize);
+ } else {
+ if (err != Z_OK) {
+ fprintf(stderr, "Error decompressing input buffer %d (%d/%d)\n", err, z.avail_in, z.avail_out);
+ } else {
+ HandleGatewayMessage(std::string(m_decompress_buf.begin(), m_decompress_buf.begin() + z.total_out));
+ if (m_decompress_buf.size() > InflateChunkSize)
+ m_decompress_buf.resize(InflateChunkSize);
+ }
+
+ inflateEnd(&z);
+
+ break;
+ }
+ }
+
+ m_compressed_buf.clear();
+#else
+ HandleGatewayMessage(str);
+#endif
+}
+
+void DiscordClient::HandleGatewayMessage(std::string str) {
GatewayMessage m;
try {
- m = j;
+ m = nlohmann::json::parse(str);
} catch (std::exception &e) {
printf("Error decoding JSON. Discarding message: %s\n", e.what());
return;
diff --git a/discord/discord.hpp b/discord/discord.hpp
index ab390dc..3a98b40 100644
--- a/discord/discord.hpp
+++ b/discord/discord.hpp
@@ -4,8 +4,12 @@
#include <nlohmann/json.hpp>
#include <thread>
#include <unordered_map>
+#include <set>
#include <unordered_set>
#include <mutex>
+#ifdef ABADDON_USE_COMPRESSED_SOCKET
+ #include <zlib.h>
+#endif
// bruh
#ifdef GetMessage
@@ -372,7 +376,11 @@ class DiscordClient {
friend class Abaddon;
public:
+#ifdef ABADDON_USE_COMPRESSED_SOCKET
+ static const constexpr char *DiscordGateway = "wss://gateway.discord.gg/?v=6&encoding=json&compress=zlib-stream";
+#else
static const constexpr char *DiscordGateway = "wss://gateway.discord.gg/?v=6&encoding=json";
+#endif
static const constexpr char *DiscordAPI = "https://discord.com/api";
static const constexpr char *GatewayIdentity = "Discord";
@@ -400,7 +408,14 @@ public:
void UpdateToken(std::string token);
private:
- void HandleGatewayMessage(nlohmann::json msg);
+#ifdef ABADDON_USE_COMPRESSED_SOCKET
+ static const constexpr int InflateChunkSize = 0x10000;
+ std::vector<uint8_t> m_compressed_buf;
+ std::vector<uint8_t> m_decompress_buf;
+#endif
+ std::string DecompressGatewayMessage(std::string str);
+ void HandleGatewayMessageRaw(std::string str);
+ void HandleGatewayMessage(std::string str);
void HandleGatewayReady(const GatewayMessage &msg);
void HandleGatewayMessageCreate(const GatewayMessage &msg);
void HeartbeatThread();
diff --git a/discord/websocket.cpp b/discord/websocket.cpp
index 8232ac6..2251a01 100644
--- a/discord/websocket.cpp
+++ b/discord/websocket.cpp
@@ -1,10 +1,10 @@
#include "websocket.hpp"
#include <functional>
-#include <nlohmann/json.hpp>
Websocket::Websocket() {}
void Websocket::StartConnection(std::string url) {
+ m_websocket.disableAutomaticReconnection();
m_websocket.setUrl(url);
m_websocket.setOnMessageCallback(std::bind(&Websocket::OnMessage, this, std::placeholders::_1));
m_websocket.start();
@@ -19,8 +19,8 @@ bool Websocket::IsOpen() const {
return state == ix::ReadyState::Open;
}
-void Websocket::SetJSONCallback(JSONCallback_t func) {
- m_json_callback = func;
+void Websocket::SetMessageCallback(MessageCallback_t func) {
+ m_callback = func;
}
void Websocket::Send(const std::string &str) {
@@ -39,15 +39,8 @@ void Websocket::OnMessage(const ix::WebSocketMessagePtr &msg) {
// printf("%s\n", msg->str.substr(0, 1000).c_str());
//else
// printf("%s\n", msg->str.c_str());
- nlohmann::json obj;
- try {
- obj = nlohmann::json::parse(msg->str);
- } catch (std::exception &e) {
- printf("Error decoding JSON. Discarding message: %s\n", e.what());
- return;
- }
- if (m_json_callback)
- m_json_callback(obj);
+ if (m_callback)
+ m_callback(msg->str);
} break;
}
}
diff --git a/discord/websocket.hpp b/discord/websocket.hpp
index dc8cbec..8e3aa94 100644
--- a/discord/websocket.hpp
+++ b/discord/websocket.hpp
@@ -10,8 +10,8 @@ public:
Websocket();
void StartConnection(std::string url);
- using JSONCallback_t = std::function<void(nlohmann::json)>;
- void SetJSONCallback(JSONCallback_t func);
+ using MessageCallback_t = std::function<void(std::string data)>;
+ void SetMessageCallback(MessageCallback_t func);
void Send(const std::string &str);
void Send(const nlohmann::json &j);
void Stop();
@@ -20,6 +20,6 @@ public:
private:
void OnMessage(const ix::WebSocketMessagePtr &msg);
- JSONCallback_t m_json_callback;
+ MessageCallback_t m_callback;
ix::WebSocket m_websocket;
};
diff --git a/windows/mainwindow.cpp b/windows/mainwindow.cpp
index 41f7ee8..3404799 100644
--- a/windows/mainwindow.cpp
+++ b/windows/mainwindow.cpp
@@ -4,9 +4,10 @@
MainWindow::MainWindow()
: m_main_box(Gtk::ORIENTATION_VERTICAL)
, m_content_box(Gtk::ORIENTATION_HORIZONTAL)
- , m_chan_chat_paned(Gtk::ORIENTATION_HORIZONTAL) {
+ , m_chan_chat_paned(Gtk::ORIENTATION_HORIZONTAL)
+ , m_chat_members_paned(Gtk::ORIENTATION_HORIZONTAL) {
set_default_size(1200, 800);
-
+
m_menu_discord.set_label("Discord");
m_menu_discord.set_submenu(m_menu_discord_sub);
m_menu_discord_connect.set_label("Connect");
@@ -39,18 +40,32 @@ MainWindow::MainWindow()
m_main_box.add(m_content_box);
auto *channel_list = m_channel_list.GetRoot();
- channel_list->set_vexpand(true);
- channel_list->set_size_request(-1, -1);
- m_chan_chat_paned.pack1(*channel_list);
+ auto *member_list = m_members.GetRoot();
auto *chat = m_chat.GetRoot();
+
chat->set_vexpand(true);
chat->set_hexpand(true);
- m_chan_chat_paned.pack2(*chat);
- m_chan_chat_paned.set_position(200);
+
+ channel_list->set_vexpand(true);
+ channel_list->set_size_request(-1, -1);
+
+ member_list->set_vexpand(true);
+
+ m_chan_chat_paned.pack1(*channel_list);
+ m_chan_chat_paned.pack2(m_chat_members_paned);
m_chan_chat_paned.child_property_shrink(*channel_list) = true;
m_chan_chat_paned.child_property_resize(*channel_list) = true;
+ m_chan_chat_paned.set_position(200);
m_content_box.add(m_chan_chat_paned);
+ m_chat_members_paned.pack1(*chat);
+ m_chat_members_paned.pack2(*member_list);
+ m_chat_members_paned.child_property_shrink(*member_list) = true;
+ m_chat_members_paned.child_property_resize(*member_list) = true;
+ int w, h;
+ get_default_size(w, h); // :s
+ m_chat_members_paned.set_position(w - m_chan_chat_paned.get_position() - 150);
+
add(m_main_box);
show_all_children();
diff --git a/windows/mainwindow.hpp b/windows/mainwindow.hpp
index 240d744..f3085f5 100644
--- a/windows/mainwindow.hpp
+++ b/windows/mainwindow.hpp
@@ -1,6 +1,7 @@
#pragma once
#include "../components/channels.hpp"
#include "../components/chatwindow.hpp"
+#include "../components/memberlist.hpp"
#include <gtkmm.h>
class Abaddon;
@@ -20,9 +21,11 @@ protected:
Gtk::Box m_main_box;
Gtk::Box m_content_box;
Gtk::Paned m_chan_chat_paned;
+ Gtk::Paned m_chat_members_paned;
ChannelList m_channel_list;
ChatWindow m_chat;
+ MemberList m_members;
Gtk::MenuBar m_menu_bar;
Gtk::MenuItem m_menu_discord;