forked from OpenGamers/abaddon
add http client and channel reordering (waste of time)
This commit is contained in:
@@ -114,7 +114,7 @@
|
|||||||
<ClCompile>
|
<ClCompile>
|
||||||
<WarningLevel>Level3</WarningLevel>
|
<WarningLevel>Level3</WarningLevel>
|
||||||
<SDLCheck>true</SDLCheck>
|
<SDLCheck>true</SDLCheck>
|
||||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
<PreprocessorDefinitions>_DEBUG;_CONSOLE;USE_LOCAL_PROXY;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
<ConformanceMode>true</ConformanceMode>
|
<ConformanceMode>true</ConformanceMode>
|
||||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
@@ -145,6 +145,7 @@
|
|||||||
<ClCompile Include="components\channels.cpp" />
|
<ClCompile Include="components\channels.cpp" />
|
||||||
<ClCompile Include="dialogs\token.cpp" />
|
<ClCompile Include="dialogs\token.cpp" />
|
||||||
<ClCompile Include="discord\discord.cpp" />
|
<ClCompile Include="discord\discord.cpp" />
|
||||||
|
<ClCompile Include="discord\http.cpp" />
|
||||||
<ClCompile Include="discord\websocket.cpp" />
|
<ClCompile Include="discord\websocket.cpp" />
|
||||||
<ClCompile Include="settings.cpp" />
|
<ClCompile Include="settings.cpp" />
|
||||||
<ClCompile Include="windows\mainwindow.cpp" />
|
<ClCompile Include="windows\mainwindow.cpp" />
|
||||||
@@ -154,6 +155,7 @@
|
|||||||
<ClInclude Include="abaddon.hpp" />
|
<ClInclude Include="abaddon.hpp" />
|
||||||
<ClInclude Include="dialogs\token.hpp" />
|
<ClInclude Include="dialogs\token.hpp" />
|
||||||
<ClInclude Include="discord\discord.hpp" />
|
<ClInclude Include="discord\discord.hpp" />
|
||||||
|
<ClInclude Include="discord\http.hpp" />
|
||||||
<ClInclude Include="discord\websocket.hpp" />
|
<ClInclude Include="discord\websocket.hpp" />
|
||||||
<ClInclude Include="settings.hpp" />
|
<ClInclude Include="settings.hpp" />
|
||||||
<ClInclude Include="windows\mainwindow.hpp" />
|
<ClInclude Include="windows\mainwindow.hpp" />
|
||||||
|
|||||||
@@ -36,6 +36,9 @@
|
|||||||
<ClCompile Include="dialogs\token.cpp">
|
<ClCompile Include="dialogs\token.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="discord\http.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="windows\mainwindow.hpp">
|
<ClInclude Include="windows\mainwindow.hpp">
|
||||||
@@ -59,5 +62,8 @@
|
|||||||
<ClInclude Include="dialogs\token.hpp">
|
<ClInclude Include="dialogs\token.hpp">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="discord\http.hpp">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
41
abaddon.cpp
41
abaddon.cpp
@@ -1,6 +1,7 @@
|
|||||||
#include <gtkmm.h>
|
#include <gtkmm.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <algorithm>
|
||||||
#include "discord/discord.hpp"
|
#include "discord/discord.hpp"
|
||||||
#include "dialogs/token.hpp"
|
#include "dialogs/token.hpp"
|
||||||
#include "abaddon.hpp"
|
#include "abaddon.hpp"
|
||||||
@@ -45,6 +46,7 @@ void Abaddon::LoadFromSettings() {
|
|||||||
std::string token = m_settings.GetSetting("discord", "token");
|
std::string token = m_settings.GetSetting("discord", "token");
|
||||||
if (token.size()) {
|
if (token.size()) {
|
||||||
m_discord_token = token;
|
m_discord_token = token;
|
||||||
|
m_discord.UpdateToken(m_discord_token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +75,10 @@ void Abaddon::DiscordNotifyReady() {
|
|||||||
m_main_window->UpdateComponents();
|
m_main_window->UpdateComponents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Abaddon::DiscordNotifyChannelListFullRefresh() {
|
||||||
|
m_main_window->UpdateChannelListing();
|
||||||
|
}
|
||||||
|
|
||||||
void Abaddon::ActionConnect() {
|
void Abaddon::ActionConnect() {
|
||||||
if (!m_discord.IsStarted())
|
if (!m_discord.IsStarted())
|
||||||
StartDiscord();
|
StartDiscord();
|
||||||
@@ -90,11 +96,46 @@ void Abaddon::ActionSetToken() {
|
|||||||
auto response = dlg.run();
|
auto response = dlg.run();
|
||||||
if (response == Gtk::RESPONSE_OK) {
|
if (response == Gtk::RESPONSE_OK) {
|
||||||
m_discord_token = dlg.GetToken();
|
m_discord_token = dlg.GetToken();
|
||||||
|
m_discord.UpdateToken(m_discord_token);
|
||||||
m_main_window->UpdateComponents();
|
m_main_window->UpdateComponents();
|
||||||
m_settings.SetSetting("discord", "token", m_discord_token);
|
m_settings.SetSetting("discord", "token", m_discord_token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 it = std::find(pos.begin(), pos.end(), id);
|
||||||
|
assert(it != pos.end());
|
||||||
|
std::vector<Snowflake>::iterator left = it - 1;
|
||||||
|
std::swap(*left, *it);
|
||||||
|
|
||||||
|
m_discord.UpdateSettingsGuildPositions(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 it = std::find(pos.begin(), pos.end(), id);
|
||||||
|
assert(it != pos.end());
|
||||||
|
std::vector<Snowflake>::iterator right = it + 1;
|
||||||
|
std::swap(*right, *it);
|
||||||
|
|
||||||
|
m_discord.UpdateSettingsGuildPositions(pos);
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
Abaddon abaddon;
|
Abaddon abaddon;
|
||||||
return abaddon.StartGTK();
|
return abaddon.StartGTK();
|
||||||
|
|||||||
@@ -20,12 +20,15 @@ public:
|
|||||||
void ActionConnect();
|
void ActionConnect();
|
||||||
void ActionDisconnect();
|
void ActionDisconnect();
|
||||||
void ActionSetToken();
|
void ActionSetToken();
|
||||||
|
void ActionMoveGuildUp(Snowflake id);
|
||||||
|
void ActionMoveGuildDown(Snowflake id);
|
||||||
|
|
||||||
std::string GetDiscordToken() const;
|
std::string GetDiscordToken() const;
|
||||||
bool IsDiscordActive() const;
|
bool IsDiscordActive() const;
|
||||||
|
|
||||||
const DiscordClient &GetDiscordClient() const;
|
const DiscordClient &GetDiscordClient() const;
|
||||||
void DiscordNotifyReady();
|
void DiscordNotifyReady();
|
||||||
|
void DiscordNotifyChannelListFullRefresh();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string m_discord_token;
|
std::string m_discord_token;
|
||||||
|
|||||||
@@ -7,8 +7,18 @@
|
|||||||
ChannelList::ChannelList() {
|
ChannelList::ChannelList() {
|
||||||
m_main = Gtk::manage(new Gtk::ScrolledWindow);
|
m_main = Gtk::manage(new Gtk::ScrolledWindow);
|
||||||
m_list = Gtk::manage(new Gtk::ListBox);
|
m_list = Gtk::manage(new Gtk::ListBox);
|
||||||
m_list->set_activate_on_single_click(true);
|
|
||||||
|
|
||||||
|
m_guild_menu_up = Gtk::manage(new Gtk::MenuItem("Move _Up", true));
|
||||||
|
m_guild_menu_up->signal_activate().connect(sigc::mem_fun(*this, &ChannelList::on_menu_move_up));
|
||||||
|
m_guild_menu.append(*m_guild_menu_up);
|
||||||
|
|
||||||
|
m_guild_menu_down = Gtk::manage(new Gtk::MenuItem("Move _Down", true));
|
||||||
|
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.show_all();
|
||||||
|
|
||||||
|
m_list->set_activate_on_single_click(true);
|
||||||
m_list->signal_row_activated().connect(sigc::mem_fun(*this, &ChannelList::on_row_activated));
|
m_list->signal_row_activated().connect(sigc::mem_fun(*this, &ChannelList::on_row_activated));
|
||||||
|
|
||||||
m_main->add(*m_list);
|
m_main->add(*m_list);
|
||||||
@@ -83,47 +93,14 @@ void ChannelList::SetListingFromGuildsInternal() {
|
|||||||
it++;
|
it++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_guild_count = 0;
|
||||||
|
|
||||||
if (guilds->empty()) {
|
if (guilds->empty()) {
|
||||||
std::scoped_lock<std::mutex> guard(m_update_mutex);
|
std::scoped_lock<std::mutex> guard(m_update_mutex);
|
||||||
m_update_queue.pop();
|
m_update_queue.pop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &settings = m_abaddon->GetDiscordClient().GetUserSettings();
|
|
||||||
|
|
||||||
std::vector<std::pair<Snowflake, GuildData>> sorted_guilds;
|
|
||||||
|
|
||||||
if (settings.GuildPositions.size()) {
|
|
||||||
for (const auto &id : settings.GuildPositions) {
|
|
||||||
auto &guild = (*guilds)[id];
|
|
||||||
sorted_guilds.push_back(std::make_pair(id, guild));
|
|
||||||
}
|
|
||||||
} else { // default sort is alphabetic
|
|
||||||
for (auto &it : *guilds)
|
|
||||||
sorted_guilds.push_back(it);
|
|
||||||
std::sort(sorted_guilds.begin(), sorted_guilds.end(), [&](auto &a, auto &b) -> bool {
|
|
||||||
std::string &s1 = a.second.Name;
|
|
||||||
std::string &s2 = b.second.Name;
|
|
||||||
|
|
||||||
if (s1.empty() || s2.empty())
|
|
||||||
return s1 < s2;
|
|
||||||
|
|
||||||
bool ac[] = {
|
|
||||||
!isalnum(s1[0]),
|
|
||||||
!isalnum(s2[0]),
|
|
||||||
isdigit(s1[0]),
|
|
||||||
isdigit(s2[0]),
|
|
||||||
isalpha(s1[0]),
|
|
||||||
isalpha(s2[0]),
|
|
||||||
};
|
|
||||||
|
|
||||||
if ((ac[0] && ac[1]) || (ac[2] && ac[3]) || (ac[4] && ac[5]))
|
|
||||||
return s1 < s2;
|
|
||||||
|
|
||||||
return ac[0] || ac[5];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// map each category to its channels
|
// map each category to its channels
|
||||||
std::unordered_map<Snowflake, std::vector<const ChannelData *>> cat_to_channels;
|
std::unordered_map<Snowflake, std::vector<const ChannelData *>> cat_to_channels;
|
||||||
std::unordered_map<Snowflake, std::vector<const ChannelData *>> orphan_channels;
|
std::unordered_map<Snowflake, std::vector<const ChannelData *>> orphan_channels;
|
||||||
@@ -151,12 +128,14 @@ void ChannelList::SetListingFromGuildsInternal() {
|
|||||||
|
|
||||||
auto add_channel = [&](const Snowflake &id, const ChannelData &channel) -> Gtk::ListBoxRow * {
|
auto add_channel = [&](const Snowflake &id, const ChannelData &channel) -> Gtk::ListBoxRow * {
|
||||||
auto *channel_row = Gtk::manage(new Gtk::ListBoxRow);
|
auto *channel_row = Gtk::manage(new Gtk::ListBoxRow);
|
||||||
|
auto *channel_ev = Gtk::manage(new Gtk::EventBox);
|
||||||
auto *channel_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
auto *channel_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
||||||
auto *channel_label = Gtk::manage(new Gtk::Label);
|
auto *channel_label = Gtk::manage(new Gtk::Label);
|
||||||
channel_label->set_text("#" + channel.Name);
|
channel_label->set_text("#" + channel.Name);
|
||||||
channel_box->set_halign(Gtk::ALIGN_START);
|
channel_box->set_halign(Gtk::ALIGN_START);
|
||||||
channel_box->pack_start(*channel_label);
|
channel_box->pack_start(*channel_label);
|
||||||
channel_row->add(*channel_box);
|
channel_ev->add(*channel_box);
|
||||||
|
channel_row->add(*channel_ev);
|
||||||
channel_row->show_all_children();
|
channel_row->show_all_children();
|
||||||
m_list->add(*channel_row);
|
m_list->add(*channel_row);
|
||||||
|
|
||||||
@@ -171,6 +150,7 @@ void ChannelList::SetListingFromGuildsInternal() {
|
|||||||
|
|
||||||
auto add_category = [&](const Snowflake &id, const ChannelData &channel) -> Gtk::ListBoxRow * {
|
auto add_category = [&](const Snowflake &id, const ChannelData &channel) -> Gtk::ListBoxRow * {
|
||||||
auto *category_row = Gtk::manage(new Gtk::ListBoxRow);
|
auto *category_row = Gtk::manage(new Gtk::ListBoxRow);
|
||||||
|
auto *category_ev = Gtk::manage(new Gtk::EventBox);
|
||||||
auto *category_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
auto *category_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
||||||
auto *category_label = Gtk::manage(new Gtk::Label);
|
auto *category_label = Gtk::manage(new Gtk::Label);
|
||||||
auto *category_arrow = Gtk::manage(new Gtk::Arrow(Gtk::ARROW_DOWN, Gtk::SHADOW_NONE));
|
auto *category_arrow = Gtk::manage(new Gtk::Arrow(Gtk::ARROW_DOWN, Gtk::SHADOW_NONE));
|
||||||
@@ -178,7 +158,8 @@ void ChannelList::SetListingFromGuildsInternal() {
|
|||||||
category_box->set_halign(Gtk::ALIGN_START);
|
category_box->set_halign(Gtk::ALIGN_START);
|
||||||
category_box->pack_start(*category_arrow);
|
category_box->pack_start(*category_arrow);
|
||||||
category_box->pack_start(*category_label);
|
category_box->pack_start(*category_label);
|
||||||
category_row->add(*category_box);
|
category_ev->add(*category_box);
|
||||||
|
category_row->add(*category_ev);
|
||||||
category_row->show_all_children();
|
category_row->show_all_children();
|
||||||
m_list->add(*category_row);
|
m_list->add(*category_row);
|
||||||
|
|
||||||
@@ -208,19 +189,23 @@ void ChannelList::SetListingFromGuildsInternal() {
|
|||||||
|
|
||||||
auto add_guild = [&](const Snowflake &id, const GuildData &guild) {
|
auto add_guild = [&](const Snowflake &id, const GuildData &guild) {
|
||||||
auto *guild_row = Gtk::manage(new Gtk::ListBoxRow);
|
auto *guild_row = Gtk::manage(new Gtk::ListBoxRow);
|
||||||
|
auto *guild_ev = Gtk::manage(new Gtk::EventBox);
|
||||||
auto *guild_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
auto *guild_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
||||||
auto *guild_label = Gtk::manage(new Gtk::Label);
|
auto *guild_label = Gtk::manage(new Gtk::Label);
|
||||||
guild_label->set_markup("<b>" + Glib::Markup::escape_text(guild.Name) + "</b>");
|
guild_label->set_markup("<b>" + Glib::Markup::escape_text(guild.Name) + "</b>");
|
||||||
guild_box->set_halign(Gtk::ALIGN_START);
|
guild_box->set_halign(Gtk::ALIGN_START);
|
||||||
guild_box->pack_start(*guild_label);
|
guild_box->pack_start(*guild_label);
|
||||||
guild_row->add(*guild_box);
|
guild_ev->add(*guild_box);
|
||||||
|
guild_row->add(*guild_ev);
|
||||||
guild_row->show_all();
|
guild_row->show_all();
|
||||||
m_list->add(*guild_row);
|
m_list->add(*guild_row);
|
||||||
|
AttachMenuHandler(guild_row);
|
||||||
|
|
||||||
ListItemInfo info;
|
ListItemInfo info;
|
||||||
info.ID = id;
|
info.ID = id;
|
||||||
info.IsUserCollapsed = true;
|
info.IsUserCollapsed = true;
|
||||||
info.IsHidden = false;
|
info.IsHidden = false;
|
||||||
|
info.GuildIndex = m_guild_count++;
|
||||||
|
|
||||||
if (orphan_channels.find(id) != orphan_channels.end()) {
|
if (orphan_channels.find(id) != orphan_channels.end()) {
|
||||||
std::map<int, const ChannelData *> sorted_orphans;
|
std::map<int, const ChannelData *> sorted_orphans;
|
||||||
@@ -256,6 +241,7 @@ void ChannelList::SetListingFromGuildsInternal() {
|
|||||||
m_infos[guild_row] = std::move(info);
|
m_infos[guild_row] = std::move(info);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const auto &sorted_guilds = m_abaddon->GetDiscordClient().GetUserSortedGuilds();
|
||||||
for (const auto &[id, guild] : sorted_guilds) {
|
for (const auto &[id, guild] : sorted_guilds) {
|
||||||
add_guild(id, guild);
|
add_guild(id, guild);
|
||||||
}
|
}
|
||||||
@@ -265,3 +251,27 @@ void ChannelList::SetListingFromGuildsInternal() {
|
|||||||
m_update_queue.pop();
|
m_update_queue.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChannelList::on_menu_move_up() {
|
||||||
|
auto row = m_list->get_selected_row();
|
||||||
|
m_abaddon->ActionMoveGuildUp(m_infos[row].ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelList::on_menu_move_down() {
|
||||||
|
auto row = m_list->get_selected_row();
|
||||||
|
m_abaddon->ActionMoveGuildDown(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) {
|
||||||
|
m_list->select_row(*row);
|
||||||
|
m_guild_menu_up->set_sensitive(m_infos[row].GuildIndex != 0);
|
||||||
|
m_guild_menu_down->set_sensitive(m_infos[row].GuildIndex != m_guild_count - 1);
|
||||||
|
m_guild_menu.popup_at_pointer(reinterpret_cast<const GdkEvent *>(e));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ protected:
|
|||||||
Gtk::ScrolledWindow *m_main;
|
Gtk::ScrolledWindow *m_main;
|
||||||
|
|
||||||
struct ListItemInfo {
|
struct ListItemInfo {
|
||||||
|
int GuildIndex;
|
||||||
Snowflake ID;
|
Snowflake ID;
|
||||||
std::unordered_set<Gtk::ListBoxRow *> Children;
|
std::unordered_set<Gtk::ListBoxRow *> Children;
|
||||||
bool IsUserCollapsed;
|
bool IsUserCollapsed;
|
||||||
@@ -32,10 +33,18 @@ protected:
|
|||||||
|
|
||||||
void on_row_activated(Gtk::ListBoxRow *row);
|
void on_row_activated(Gtk::ListBoxRow *row);
|
||||||
|
|
||||||
|
int m_guild_count;
|
||||||
|
Gtk::Menu m_guild_menu;
|
||||||
|
Gtk::MenuItem *m_guild_menu_up;
|
||||||
|
Gtk::MenuItem *m_guild_menu_down;
|
||||||
|
void on_menu_move_up();
|
||||||
|
void on_menu_move_down();
|
||||||
|
|
||||||
Glib::Dispatcher m_update_dispatcher;
|
Glib::Dispatcher m_update_dispatcher;
|
||||||
mutable std::mutex m_update_mutex;
|
mutable std::mutex m_update_mutex;
|
||||||
std::queue<DiscordClient::Guilds_t> m_update_queue;
|
std::queue<DiscordClient::Guilds_t> m_update_queue;
|
||||||
void SetListingFromGuildsInternal();
|
void SetListingFromGuildsInternal();
|
||||||
|
void AttachMenuHandler(Gtk::ListBoxRow* row);
|
||||||
|
|
||||||
Abaddon *m_abaddon = nullptr;
|
Abaddon *m_abaddon = nullptr;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
#include "discord.hpp"
|
#include "discord.hpp"
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
DiscordClient::DiscordClient() {
|
DiscordClient::DiscordClient()
|
||||||
|
: m_http(DiscordAPI) {
|
||||||
LoadEventMap();
|
LoadEventMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,6 +28,8 @@ void DiscordClient::Stop() {
|
|||||||
m_heartbeat_thread.join();
|
m_heartbeat_thread.join();
|
||||||
m_client_connected = false;
|
m_client_connected = false;
|
||||||
m_websocket.Stop();
|
m_websocket.Stop();
|
||||||
|
|
||||||
|
m_guilds.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DiscordClient::IsStarted() const {
|
bool DiscordClient::IsStarted() const {
|
||||||
@@ -44,6 +47,58 @@ const UserSettingsData &DiscordClient::GetUserSettings() const {
|
|||||||
return m_user_settings;
|
return m_user_settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<Snowflake, GuildData>> DiscordClient::GetUserSortedGuilds() const {
|
||||||
|
std::vector<std::pair<Snowflake, GuildData>> sorted_guilds;
|
||||||
|
|
||||||
|
if (m_user_settings.GuildPositions.size()) {
|
||||||
|
for (const auto &id : m_user_settings.GuildPositions) {
|
||||||
|
auto &guild = m_guilds.at(id);
|
||||||
|
sorted_guilds.push_back(std::make_pair(id, guild));
|
||||||
|
}
|
||||||
|
} else { // default sort is alphabetic
|
||||||
|
for (auto &it : m_guilds)
|
||||||
|
sorted_guilds.push_back(it);
|
||||||
|
std::sort(sorted_guilds.begin(), sorted_guilds.end(), [&](auto &a, auto &b) -> bool {
|
||||||
|
std::string &s1 = a.second.Name;
|
||||||
|
std::string &s2 = b.second.Name;
|
||||||
|
|
||||||
|
if (s1.empty() || s2.empty())
|
||||||
|
return s1 < s2;
|
||||||
|
|
||||||
|
bool ac[] = {
|
||||||
|
!isalnum(s1[0]),
|
||||||
|
!isalnum(s2[0]),
|
||||||
|
isdigit(s1[0]),
|
||||||
|
isdigit(s2[0]),
|
||||||
|
isalpha(s1[0]),
|
||||||
|
isalpha(s2[0]),
|
||||||
|
};
|
||||||
|
|
||||||
|
if ((ac[0] && ac[1]) || (ac[2] && ac[3]) || (ac[4] && ac[5]))
|
||||||
|
return s1 < s2;
|
||||||
|
|
||||||
|
return ac[0] || ac[5];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return sorted_guilds;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiscordClient::UpdateSettingsGuildPositions(const std::vector<Snowflake> &pos) {
|
||||||
|
assert(pos.size() == m_guilds.size());
|
||||||
|
nlohmann::json body;
|
||||||
|
body["guild_positions"] = pos;
|
||||||
|
m_http.MakePATCH("/users/@me/settings", body.dump(), [this, pos](const cpr::Response &r) {
|
||||||
|
m_user_settings.GuildPositions = pos;
|
||||||
|
m_abaddon->DiscordNotifyChannelListFullRefresh();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiscordClient::UpdateToken(std::string token) {
|
||||||
|
m_token = token;
|
||||||
|
m_http.SetAuth(token);
|
||||||
|
}
|
||||||
|
|
||||||
void DiscordClient::HandleGatewayMessage(nlohmann::json j) {
|
void DiscordClient::HandleGatewayMessage(nlohmann::json j) {
|
||||||
GatewayMessage m;
|
GatewayMessage m;
|
||||||
try {
|
try {
|
||||||
@@ -57,6 +112,7 @@ void DiscordClient::HandleGatewayMessage(nlohmann::json j) {
|
|||||||
case GatewayOp::Hello: {
|
case GatewayOp::Hello: {
|
||||||
HelloMessageData d = m.Data;
|
HelloMessageData d = m.Data;
|
||||||
m_heartbeat_msec = d.HeartbeatInterval;
|
m_heartbeat_msec = d.HeartbeatInterval;
|
||||||
|
assert(!m_heartbeat_thread.joinable()); // handle reconnects later
|
||||||
m_heartbeat_thread = std::thread(std::bind(&DiscordClient::HeartbeatThread, this));
|
m_heartbeat_thread = std::thread(std::bind(&DiscordClient::HeartbeatThread, this));
|
||||||
SendIdentify();
|
SendIdentify();
|
||||||
} break;
|
} break;
|
||||||
@@ -116,13 +172,12 @@ void DiscordClient::HeartbeatThread() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DiscordClient::SendIdentify() {
|
void DiscordClient::SendIdentify() {
|
||||||
auto token = m_abaddon->GetDiscordToken();
|
assert(m_token.size());
|
||||||
assert(token.size());
|
|
||||||
IdentifyMessage msg;
|
IdentifyMessage msg;
|
||||||
msg.Properties.OS = "OpenBSD";
|
msg.Properties.OS = "OpenBSD";
|
||||||
msg.Properties.Device = GatewayIdentity;
|
msg.Properties.Device = GatewayIdentity;
|
||||||
msg.Properties.Browser = GatewayIdentity;
|
msg.Properties.Browser = GatewayIdentity;
|
||||||
msg.Token = token;
|
msg.Token = m_token;
|
||||||
m_websocket.Send(msg);
|
m_websocket.Send(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,6 +400,10 @@ void from_json(const nlohmann::json &j, Snowflake &s) {
|
|||||||
s.m_num = std::stoull(tmp);
|
s.m_num = std::stoull(tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void to_json(nlohmann::json& j, const Snowflake& s) {
|
||||||
|
j = std::to_string(s);
|
||||||
|
}
|
||||||
|
|
||||||
#undef JS_O
|
#undef JS_O
|
||||||
#undef JS_D
|
#undef JS_D
|
||||||
#undef JS_N
|
#undef JS_N
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "websocket.hpp"
|
#include "websocket.hpp"
|
||||||
|
#include "http.hpp"
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@@ -21,9 +22,14 @@ struct Snowflake {
|
|||||||
return m_num < s.m_num;
|
return m_num < s.m_num;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
operator uint64_t() const noexcept {
|
||||||
|
return m_num;
|
||||||
|
}
|
||||||
|
|
||||||
const static int Invalid = -1;
|
const static int Invalid = -1;
|
||||||
|
|
||||||
friend void from_json(const nlohmann::json &j, Snowflake &s);
|
friend void from_json(const nlohmann::json &j, Snowflake &s);
|
||||||
|
friend void to_json(nlohmann::json &j, const Snowflake &s);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend struct std::hash<Snowflake>;
|
friend struct std::hash<Snowflake>;
|
||||||
@@ -286,6 +292,8 @@ private:
|
|||||||
|
|
||||||
class Abaddon;
|
class Abaddon;
|
||||||
class DiscordClient {
|
class DiscordClient {
|
||||||
|
friend class Abaddon;
|
||||||
|
|
||||||
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";
|
||||||
@@ -301,6 +309,10 @@ public:
|
|||||||
using Guilds_t = std::unordered_map<Snowflake, GuildData>;
|
using Guilds_t = std::unordered_map<Snowflake, GuildData>;
|
||||||
const Guilds_t &GetGuilds() const;
|
const Guilds_t &GetGuilds() const;
|
||||||
const UserSettingsData &GetUserSettings() const;
|
const UserSettingsData &GetUserSettings() const;
|
||||||
|
std::vector<std::pair<Snowflake, GuildData>> GetUserSortedGuilds() const;
|
||||||
|
void UpdateSettingsGuildPositions(const std::vector<Snowflake> &pos);
|
||||||
|
|
||||||
|
void UpdateToken(std::string token);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void HandleGatewayMessage(nlohmann::json msg);
|
void HandleGatewayMessage(nlohmann::json msg);
|
||||||
@@ -309,6 +321,10 @@ private:
|
|||||||
void SendIdentify();
|
void SendIdentify();
|
||||||
|
|
||||||
Abaddon *m_abaddon = nullptr;
|
Abaddon *m_abaddon = nullptr;
|
||||||
|
HTTPClient m_http;
|
||||||
|
|
||||||
|
std::string m_token;
|
||||||
|
|
||||||
mutable std::mutex m_mutex;
|
mutable std::mutex m_mutex;
|
||||||
|
|
||||||
void StoreGuild(Snowflake id, const GuildData &g);
|
void StoreGuild(Snowflake id, const GuildData &g);
|
||||||
|
|||||||
53
discord/http.cpp
Normal file
53
discord/http.cpp
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#include "http.hpp"
|
||||||
|
|
||||||
|
HTTPClient::HTTPClient(std::string api_base)
|
||||||
|
: m_api_base(api_base) {}
|
||||||
|
|
||||||
|
void HTTPClient::SetAuth(std::string auth) {
|
||||||
|
m_authorization = auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPClient::MakePATCH(std::string path, std::string payload, std::function<void(cpr::Response r)> cb) {
|
||||||
|
printf("PATCH %s\n", path.c_str());
|
||||||
|
auto url = cpr::Url { m_api_base + path };
|
||||||
|
auto headers = cpr::Header {
|
||||||
|
{ "Authorization", m_authorization },
|
||||||
|
{ "Content-Type", "application/json" },
|
||||||
|
};
|
||||||
|
auto body = cpr::Body { payload };
|
||||||
|
#ifdef USE_LOCAL_PROXY
|
||||||
|
m_futures.push_back(cpr::PatchCallback(
|
||||||
|
std::bind(&HTTPClient::OnResponse, this, std::placeholders::_1, cb),
|
||||||
|
url, headers, body,
|
||||||
|
cpr::Proxies { { "http", "127.0.0.1:8888" }, { "https", "127.0.0.1:8888" } },
|
||||||
|
cpr::VerifySsl { false }));
|
||||||
|
#else
|
||||||
|
m_futures.push_back(cpr::PatchCallback(
|
||||||
|
std::bind(&HTTPClient::OnResponse, this, std::placeholders::_1, cb),
|
||||||
|
url, headers, body));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPClient::MakePOST(std::string path, std::string payload, std::function<void(cpr::Response r)> cb) {
|
||||||
|
printf("POST %s\n", path.c_str());
|
||||||
|
auto url = cpr::Url { m_api_base + path };
|
||||||
|
auto headers = cpr::Header {
|
||||||
|
{ "Authorization", m_authorization },
|
||||||
|
{ "Content-Type", "application/json" },
|
||||||
|
};
|
||||||
|
auto body = cpr::Body { payload };
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPClient::CleanupFutures() {
|
||||||
|
for (auto it = m_futures.begin(); it != m_futures.end();) {
|
||||||
|
if (it->wait_for(std::chrono::seconds(0)) == std::future_status::ready)
|
||||||
|
it = m_futures.erase(it);
|
||||||
|
else
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPClient::OnResponse(cpr::Response r, std::function<void(cpr::Response r)> cb) {
|
||||||
|
CleanupFutures();
|
||||||
|
cb(r);
|
||||||
|
}
|
||||||
32
discord/http.hpp
Normal file
32
discord/http.hpp
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cpr/cpr.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <future>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
template<typename F>
|
||||||
|
void fire_and_forget(F &&func) {
|
||||||
|
auto ptr = std::make_shared<std::future<void>>();
|
||||||
|
*ptr = std::async(std::launch::async, [ptr, func]() {
|
||||||
|
func();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class HTTPClient {
|
||||||
|
public:
|
||||||
|
HTTPClient(std::string api_base);
|
||||||
|
|
||||||
|
void SetAuth(std::string auth);
|
||||||
|
void MakePATCH(std::string path, std::string payload, std::function<void(cpr::Response r)> cb);
|
||||||
|
void MakePOST(std::string path, std::string payload, std::function<void(cpr::Response r)> cb);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void OnResponse(cpr::Response r, std::function<void(cpr::Response r)> cb);
|
||||||
|
void CleanupFutures();
|
||||||
|
|
||||||
|
std::vector<std::future<void>> m_futures;
|
||||||
|
std::string m_api_base;
|
||||||
|
std::string m_authorization;
|
||||||
|
};
|
||||||
@@ -35,10 +35,10 @@ void Websocket::Send(const nlohmann::json &j) {
|
|||||||
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: {
|
||||||
if (msg->str.size() > 1000)
|
//if (msg->str.size() > 1000)
|
||||||
printf("%s\n", msg->str.substr(0, 1000).c_str());
|
// printf("%s\n", msg->str.substr(0, 1000).c_str());
|
||||||
else
|
//else
|
||||||
printf("%s\n", msg->str.c_str());
|
// 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);
|
||||||
|
|||||||
Reference in New Issue
Block a user