fix guild order, add copy id guild, add broken zlib, start member list

This commit is contained in:
ouwou 2020-08-25 22:10:39 -04:00
parent 6b72931ba7
commit 82a21bd085
15 changed files with 205 additions and 54 deletions

View File

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

View File

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

5
README.md Normal file
View File

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

View File

@ -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);
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::ActionCopyGuildID(Snowflake id) {
Gtk::Clipboard::get()->set_text(std::to_string(id));
}
void Abaddon::ActionListChannelItemClick(Snowflake id) {

View File

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

View File

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

View File

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

View File

@ -0,0 +1,9 @@
#include "memberlist.hpp"
MemberList::MemberList() {
m_main = Gtk::manage(new Gtk::Box);
}
Gtk::Widget *MemberList::GetRoot() const {
return m_main;
}

11
components/memberlist.hpp Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#include <gtkmm.h>
class MemberList {
public:
MemberList();
Gtk::Widget *GetRoot() const;
private:
Gtk::Box *m_main;
};

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,8 @@
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");
@ -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();

View File

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