Merge branch 'master' into store

This commit is contained in:
ouwou
2021-11-14 02:27:23 -05:00
14 changed files with 240 additions and 36 deletions

View File

@@ -25,7 +25,7 @@ Current features:
1 - Abaddon tries its best to make Discord think it's a legitimate web client. Some of the things done to do this include: using a browser user agent, sending the same IDENTIFY message that the official web client does, using API v9 endpoints in all cases, and not using endpoints the web client does not normally use. There are still a few smaller inconsistencies, however. For example the web client sends lots of telemetry via the `/science` endpoint (uBlock origin stops this) as well as in the headers of all requests. **In any case,** you should use an official client for joining servers, sending new DMs, or managing your friends list if you are afraid of being caught in Discord's spam filters (unlikely).
2 - Unicode emojis are subtituted manually as opposed to rendered by GTK on non-Windows platforms. This can be changed with the `stock_emojis` setting as shown at the bottom of this README. A CBDT-based font using Twemoji is provided to allow GTK to render emojis natively on Windows.
2 - Unicode emojis are substituted manually as opposed to rendered by GTK on non-Windows platforms. This can be changed with the `stock_emojis` setting as shown at the bottom of this README. A CBDT-based font using Twemoji is provided to allow GTK to render emojis natively on Windows.
3 - There are some inconsistencies with thread state that might be encountered in some more uncommon cases, but they are the result of fundamental issues with Discord's thread implementation.
@@ -93,12 +93,6 @@ On Linux, `css` and `res` can also be loaded from `~/.local/share/abaddon` or `/
.app-popup - Additional class for `.app-window`s when the window is not the main window
.channel-list - Container of the channel list
.channel-row - All rows within the channel container
.channel-row-channel - Only rows containing a channel
.channel-row-category - Only rows containing a category
.channel-row-guild - Only rows containing a guild
.channel-row-label - All labels within the channel container
.nsfw - Applied to channel row labels and their container for NSFW channels
.messages - Container of user messages
.message-container - The container which holds a user's messages

View File

@@ -56,7 +56,6 @@ Abaddon::Abaddon()
Abaddon::~Abaddon() {
m_settings.Close();
m_discord.Stop();
}
Abaddon &Abaddon::Get() {
@@ -134,9 +133,7 @@ int Abaddon::StartGTK() {
ActionReloadCSS();
m_gtk_app->signal_shutdown().connect([&]() {
StopDiscord();
});
m_gtk_app->signal_shutdown().connect(sigc::mem_fun(*this, &Abaddon::StopDiscord), false);
m_main_window->show();
return m_gtk_app->run(*m_main_window);
@@ -156,6 +153,7 @@ void Abaddon::StartDiscord() {
void Abaddon::StopDiscord() {
m_discord.Stop();
SaveState();
}
bool Abaddon::IsDiscordActive() const {
@@ -178,6 +176,7 @@ const DiscordClient &Abaddon::GetDiscordClient() const {
void Abaddon::DiscordOnReady() {
m_main_window->UpdateComponents();
LoadState();
}
void Abaddon::DiscordOnMessageCreate(const Message &message) {
@@ -367,6 +366,40 @@ void Abaddon::SetupUserMenu() {
m_user_menu->show_all();
}
void Abaddon::SaveState() {
if (!m_settings.GetSaveState()) return;
AbaddonApplicationState state;
state.ActiveChannel = m_main_window->GetChatActiveChannel();
state.Expansion = m_main_window->GetChannelList()->GetExpansionState();
const auto path = GetStateCachePath();
if (!util::IsFolder(path)) {
std::error_code ec;
std::filesystem::create_directories(path, ec);
}
auto *fp = std::fopen(GetStateCachePath("/state.json").c_str(), "wb");
if (fp == nullptr) return;
const auto s = nlohmann::json(state).dump(4);
std::fwrite(s.c_str(), 1, s.size(), fp);
std::fclose(fp);
}
void Abaddon::LoadState() {
if (!m_settings.GetSaveState()) return;
const auto data = ReadWholeFile(GetStateCachePath("/state.json"));
if (data.empty()) return;
try {
AbaddonApplicationState state = nlohmann::json::parse(data.begin(), data.end());
m_main_window->GetChannelList()->UseExpansionState(state.Expansion);
ActionChannelOpened(state.ActiveChannel);
} catch (const std::exception &e) {
printf("failed to load application state: %s\n", e.what());
}
}
void Abaddon::ManageHeapWindow(Gtk::Window *window) {
window->signal_hide().connect([this, window]() {
delete window;
@@ -424,6 +457,11 @@ std::string Abaddon::GetResPath() {
return path;
}
std::string Abaddon::GetStateCachePath() {
const static auto path = Platform::FindStateCacheFolder() + "/state";
return path;
}
std::string Abaddon::GetCSSPath(const std::string &path) {
return GetCSSPath() + path;
}
@@ -432,6 +470,10 @@ std::string Abaddon::GetResPath(const std::string &path) {
return GetResPath() + path;
}
std::string Abaddon::GetStateCachePath(const std::string &path) {
return GetStateCachePath() + path;
}
void Abaddon::ActionConnect() {
if (!m_discord.IsStarted())
StartDiscord();
@@ -463,7 +505,7 @@ void Abaddon::ActionJoinGuildDialog() {
}
void Abaddon::ActionChannelOpened(Snowflake id) {
if (id == m_main_window->GetChatActiveChannel()) return;
if (!id.IsValid() || id == m_main_window->GetChatActiveChannel()) return;
m_main_window->GetChatWindow()->SetTopic("");

View File

@@ -84,13 +84,17 @@ public:
static std::string GetCSSPath();
static std::string GetResPath();
static std::string GetStateCachePath();
static std::string GetCSSPath(const std::string &path);
static std::string GetResPath(const std::string &path);
static std::string GetStateCachePath(const std::string &path);
protected:
void ShowGuildVerificationGateDialog(Snowflake guild_id);
void SetupUserMenu();
void SaveState();
void LoadState();
Snowflake m_shown_user_menu_id;
Snowflake m_shown_user_menu_guild_id;

View File

@@ -380,6 +380,56 @@ void ChannelList::SetActiveChannel(Snowflake id) {
}
}
void ChannelList::UseExpansionState(const ExpansionStateRoot &root) {
auto recurse = [this](auto &self, const ExpansionStateRoot &root) -> void {
// and these are only channels
for (const auto &[id, state] : root.Children) {
if (const auto iter = GetIteratorForChannelFromID(id)) {
if (state.IsExpanded)
m_view.expand_row(m_model->get_path(iter), false);
else
m_view.collapse_row(m_model->get_path(iter));
}
self(self, state.Children);
}
};
// top level is guild
for (const auto &[id, state] : root.Children) {
if (const auto iter = GetIteratorForGuildFromID(id)) {
if (state.IsExpanded)
m_view.expand_row(m_model->get_path(iter), false);
else
m_view.collapse_row(m_model->get_path(iter));
}
recurse(recurse, state.Children);
}
}
ExpansionStateRoot ChannelList::GetExpansionState() const {
ExpansionStateRoot r;
auto recurse = [this](auto &self, const Gtk::TreeRow &row) -> ExpansionState {
ExpansionState r;
r.IsExpanded = row[m_columns.m_expanded];
for (const auto &child : row.children())
r.Children.Children[static_cast<Snowflake>(child[m_columns.m_id])] = self(self, child);
return r;
};
for (const auto &child : m_model->children()) {
const auto id = static_cast<Snowflake>(child[m_columns.m_id]);
if (static_cast<uint64_t>(id) == 0ULL) continue; // dont save DM header
r.Children[id] = recurse(recurse, child);
}
return r;
}
Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) {
auto &discord = Abaddon::Get().GetDiscordClient();
auto &img = Abaddon::Get().GetImageManager();

View File

@@ -6,7 +6,8 @@
#include <unordered_set>
#include <unordered_map>
#include <sigc++/sigc++.h>
#include "../discord/discord.hpp"
#include "discord/discord.hpp"
#include "state.hpp"
constexpr static int GuildIconSize = 24;
constexpr static int DMIconSize = 20;
@@ -134,6 +135,10 @@ public:
void UpdateListing();
void SetActiveChannel(Snowflake id);
// channel list should be populated when this is called
void UseExpansionState(const ExpansionStateRoot &state);
ExpansionStateRoot GetExpansionState() const;
protected:
void UpdateNewGuild(const GuildData &guild);
void UpdateRemoveGuild(Snowflake id);

View File

@@ -1701,6 +1701,9 @@ void DiscordClient::HandleGatewayThreadCreate(const GatewayMessage &msg) {
ThreadCreateData data = msg.Data;
m_store.SetChannel(data.Channel.ID, data.Channel);
m_signal_thread_create.emit(data.Channel);
if (data.Channel.ThreadMember.has_value()) {
m_signal_added_to_thread.emit(data.Channel.ID);
}
}
void DiscordClient::HandleGatewayThreadDelete(const GatewayMessage &msg) {

View File

@@ -1,4 +1,5 @@
#include "platform.hpp"
#include "util.hpp"
#include <string>
#include <fstream>
#include <filesystem>
@@ -6,20 +7,6 @@
using namespace std::literals::string_literals;
bool IsFolder(std::string_view path) {
std::error_code ec;
const auto status = std::filesystem::status(path, ec);
if (ec) return false;
return status.type() == std::filesystem::file_type::directory;
}
bool IsFile(std::string_view path) {
std::error_code ec;
const auto status = std::filesystem::status(path, ec);
if (ec) return false;
return status.type() == std::filesystem::file_type::regular;
}
#if defined(_WIN32) && defined(_MSC_VER)
#include <Windows.h>
#include <Shlwapi.h>
@@ -90,19 +77,26 @@ std::string Platform::FindConfigFile() {
return "./abaddon.ini";
}
std::string Platform::FindStateCacheFolder() {
return ".";
}
#elif defined(__linux__)
std::string Platform::FindResourceFolder() {
static std::string found_path;
static bool found = false;
if (found) return found_path;
const static std::string home_path = std::getenv("HOME") + "/.local/share/abaddon"s;
const auto home_env = std::getenv("HOME");
if (home_env != nullptr) {
const static std::string home_path = home_env + "/.local/share/abaddon"s;
for (const auto &path : { "."s, home_path, std::string(ABADDON_DEFAULT_RESOURCE_DIR) }) {
if (IsFolder(path + "/res") && IsFolder(path + "/css")) {
found_path = path;
found = true;
return found_path;
for (const auto &path : { "."s, home_path, std::string(ABADDON_DEFAULT_RESOURCE_DIR) }) {
if (util::IsFolder(path + "/res") && util::IsFolder(path + "/css")) {
found_path = path;
found = true;
return found_path;
}
}
}
@@ -117,13 +111,31 @@ std::string Platform::FindConfigFile() {
if (x != nullptr)
return x;
const auto home_path = std::string(std::getenv("HOME")) + "/.config/abaddon/abaddon.ini";
for (const auto path : { "./abaddon.ini"s, home_path }) {
if (IsFile(path)) return path;
const auto home_env = std::getenv("HOME");
if (home_env != nullptr) {
const auto home_path = home_env + "/.config/abaddon/abaddon.ini"s;
for (auto path : { "./abaddon.ini"s, home_path }) {
if (util::IsFile(path)) return path;
}
}
puts("can't find configuration file!");
return "./abaddon.ini";
}
std::string Platform::FindStateCacheFolder() {
const auto home_env = std::getenv("HOME");
if (home_env != nullptr) {
auto home_path = home_env + "/.cache/abaddon"s;
std::error_code ec;
if (!util::IsFolder(home_path))
std::filesystem::create_directories(home_path, ec);
if (util::IsFolder(home_path))
return home_path;
}
puts("can't find cache folder!");
return ".";
}
#else
std::string Platform::FindResourceFolder() {
puts("unknown OS, trying to load resources from cwd");
@@ -137,4 +149,9 @@ std::string Platform::FindConfigFile() {
puts("unknown OS, trying to load config from cwd");
return "./abaddon.ini";
}
std::string Platform::FindStateCacheFolder() {
puts("unknown OS, setting state cache folder to cwd");
return ".";
}
#endif

View File

@@ -5,4 +5,5 @@ namespace Platform {
bool SetupFonts();
std::string FindResourceFolder();
std::string FindConfigFile();
std::string FindStateCacheFolder();
}

View File

@@ -109,3 +109,7 @@ std::string SettingsManager::GetAPIBaseURL() const {
bool SettingsManager::GetAnimatedGuildHoverOnly() const {
return GetSettingBool("gui", "animated_guild_hover_only", true);
}
bool SettingsManager::GetSaveState() const {
return GetSettingBool("gui", "save_state", true);
}

View File

@@ -23,6 +23,7 @@ public:
std::string GetGatewayURL() const;
std::string GetAPIBaseURL() const;
bool GetAnimatedGuildHoverOnly() const;
bool GetSaveState() const;
// i would like to use Gtk::StyleProperty for this, but it will not work on windows
// #1 it's missing from the project files for the version used by vcpkg

37
state.cpp Normal file
View File

@@ -0,0 +1,37 @@
#include "state.hpp"
void to_json(nlohmann::json &j, const ExpansionStateRoot &m) {
if (m.Children.empty()) {
j = nlohmann::json::object();
} else {
for (const auto &[id, state] : m.Children)
j[std::to_string(id)] = state;
}
}
void from_json(const nlohmann::json &j, ExpansionStateRoot &m) {
for (const auto &[key, value] : j.items())
m.Children[key] = value;
}
void to_json(nlohmann::json &j, const ExpansionState &m) {
j["e"] = m.IsExpanded;
j["c"] = m.Children;
}
void from_json(const nlohmann::json &j, ExpansionState &m) {
j.at("e").get_to(m.IsExpanded);
j.at("c").get_to(m.Children);
}
void to_json(nlohmann::json &j, const AbaddonApplicationState &m) {
j["active_channel"] = m.ActiveChannel;
j["expansion"] = m.Expansion;
}
void from_json(const nlohmann::json &j, AbaddonApplicationState &m) {
if (j.contains("active_channel"))
j.at("active_channel").get_to(m.ActiveChannel);
if (j.contains("expansion"))
j.at("expansion").get_to(m.Expansion);
}

27
state.hpp Normal file
View File

@@ -0,0 +1,27 @@
#include <vector>
#include <nlohmann/json.hpp>
#include "discord/snowflake.hpp"
struct ExpansionState;
struct ExpansionStateRoot {
std::map<Snowflake, ExpansionState> Children;
friend void to_json(nlohmann::json &j, const ExpansionStateRoot &m);
friend void from_json(const nlohmann::json &j, ExpansionStateRoot &m);
};
struct ExpansionState {
bool IsExpanded;
ExpansionStateRoot Children;
friend void to_json(nlohmann::json &j, const ExpansionState &m);
friend void from_json(const nlohmann::json &j, ExpansionState &m);
};
struct AbaddonApplicationState {
Snowflake ActiveChannel;
ExpansionStateRoot Expansion;
friend void to_json(nlohmann::json &j, const AbaddonApplicationState &m);
friend void from_json(const nlohmann::json &j, AbaddonApplicationState &m);
};

View File

@@ -1,4 +1,5 @@
#include "util.hpp"
#include <filesystem>
Semaphore::Semaphore(int count)
: m_count(count) {}
@@ -200,3 +201,17 @@ void AddPointerCursor(Gtk::Widget &widget) {
window->set_cursor(cursor);
});
}
bool util::IsFolder(std::string_view path) {
std::error_code ec;
const auto status = std::filesystem::status(path, ec);
if (ec) return false;
return status.type() == std::filesystem::file_type::directory;
}
bool util::IsFile(std::string_view path) {
std::error_code ec;
const auto status = std::filesystem::status(path, ec);
if (ec) return false;
return status.type() == std::filesystem::file_type::regular;
}

View File

@@ -21,6 +21,10 @@ struct is_optional : ::std::false_type {};
template<typename T>
struct is_optional<::std::optional<T>> : ::std::true_type {};
bool IsFolder(std::string_view path);
bool IsFile(std::string_view path);
} // namespace util
class Semaphore {