#include #include #include #include #include "platform.hpp" #include "discord/discord.hpp" #include "dialogs/token.hpp" #include "dialogs/editmessage.hpp" #include "dialogs/joinguild.hpp" #include "dialogs/confirm.hpp" #include "dialogs/setstatus.hpp" #include "dialogs/friendpicker.hpp" #include "dialogs/verificationgate.hpp" #include "abaddon.hpp" #include "windows/guildsettingswindow.hpp" #include "windows/profilewindow.hpp" #include "windows/pinnedwindow.hpp" #include "windows/threadswindow.hpp" #ifdef _WIN32 #pragma comment(lib, "crypt32.lib") #endif Abaddon::Abaddon() : m_settings(Platform::FindConfigFile()) , m_discord(GetSettings().UseMemoryDB) // stupid but easy , m_emojis(GetResPath("/emojis.bin")) { LoadFromSettings(); // todo: set user agent for non-client(?) std::string ua = GetSettings().UserAgent; m_discord.SetUserAgent(ua); m_discord.signal_gateway_ready().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnReady)); m_discord.signal_message_create().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnMessageCreate)); m_discord.signal_message_delete().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnMessageDelete)); m_discord.signal_message_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnMessageUpdate)); m_discord.signal_guild_member_list_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnGuildMemberListUpdate)); m_discord.signal_thread_member_list_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnThreadMemberListUpdate)); m_discord.signal_reaction_add().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnReactionAdd)); m_discord.signal_reaction_remove().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnReactionRemove)); m_discord.signal_guild_join_request_create().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnGuildJoinRequestCreate)); m_discord.signal_thread_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnThreadUpdate)); m_discord.signal_message_sent().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnMessageSent)); m_discord.signal_disconnected().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnDisconnect)); if (GetSettings().Prefetch) m_discord.signal_message_create().connect([this](const Message &message) { if (message.Author.HasAvatar()) m_img_mgr.Prefetch(message.Author.GetAvatarURL()); for (const auto &attachment : message.Attachments) { if (IsURLViewableImage(attachment.ProxyURL)) m_img_mgr.Prefetch(attachment.ProxyURL); } }); } Abaddon &Abaddon::Get() { static Abaddon instance; return instance; } int Abaddon::StartGTK() { m_gtk_app = Gtk::Application::create("com.github.uowuo.abaddon"); m_css_provider = Gtk::CssProvider::create(); m_css_provider->signal_parsing_error().connect([](const Glib::RefPtr §ion, const Glib::Error &error) { Gtk::MessageDialog dlg("css failed parsing (" + error.what() + ")", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); dlg.set_position(Gtk::WIN_POS_CENTER); dlg.run(); }); m_css_low_provider = Gtk::CssProvider::create(); m_css_low_provider->signal_parsing_error().connect([](const Glib::RefPtr §ion, const Glib::Error &error) { Gtk::MessageDialog dlg("low-priority css failed parsing (" + error.what() + ")", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); dlg.set_position(Gtk::WIN_POS_CENTER); dlg.run(); }); #ifdef _WIN32 bool png_found = false; bool gif_found = false; for (const auto &fmt : Gdk::Pixbuf::get_formats()) { if (fmt.get_name() == "png") png_found = true; else if (fmt.get_name() == "gif") gif_found = true; } if (!png_found) { Gtk::MessageDialog dlg("The PNG pixbufloader wasn't detected. Abaddon may not work as a result.", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); dlg.set_position(Gtk::WIN_POS_CENTER); dlg.run(); } if (!gif_found) { Gtk::MessageDialog dlg("The GIF pixbufloader wasn't detected. Animations may not display as a result.", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); dlg.set_position(Gtk::WIN_POS_CENTER); dlg.run(); } #endif m_main_window = std::make_unique(); m_main_window->set_title(APP_TITLE); m_main_window->set_position(Gtk::WIN_POS_CENTER); if (!m_settings.IsValid()) { Gtk::MessageDialog dlg(*m_main_window, "The settings file could not be opened!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); dlg.set_position(Gtk::WIN_POS_CENTER); dlg.run(); } if (!m_emojis.Load()) { Gtk::MessageDialog dlg(*m_main_window, "The emoji file couldn't be loaded!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); dlg.set_position(Gtk::WIN_POS_CENTER); dlg.run(); } if (!m_discord.IsStoreValid()) { Gtk::MessageDialog dlg(*m_main_window, "The Discord cache could not be created!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); dlg.set_position(Gtk::WIN_POS_CENTER); dlg.run(); return 1; } // store must be checked before this can be called m_main_window->UpdateComponents(); // crashes for some stupid reason if i put it somewhere else SetupUserMenu(); m_main_window->signal_action_connect().connect(sigc::mem_fun(*this, &Abaddon::ActionConnect)); m_main_window->signal_action_disconnect().connect(sigc::mem_fun(*this, &Abaddon::ActionDisconnect)); m_main_window->signal_action_set_token().connect(sigc::mem_fun(*this, &Abaddon::ActionSetToken)); m_main_window->signal_action_reload_css().connect(sigc::mem_fun(*this, &Abaddon::ActionReloadCSS)); m_main_window->signal_action_join_guild().connect(sigc::mem_fun(*this, &Abaddon::ActionJoinGuildDialog)); m_main_window->signal_action_set_status().connect(sigc::mem_fun(*this, &Abaddon::ActionSetStatus)); m_main_window->signal_action_add_recipient().connect(sigc::mem_fun(*this, &Abaddon::ActionAddRecipient)); m_main_window->signal_action_view_pins().connect(sigc::mem_fun(*this, &Abaddon::ActionViewPins)); m_main_window->signal_action_view_threads().connect(sigc::mem_fun(*this, &Abaddon::ActionViewThreads)); m_main_window->GetChannelList()->signal_action_channel_item_select().connect(sigc::mem_fun(*this, &Abaddon::ActionChannelOpened)); m_main_window->GetChannelList()->signal_action_guild_leave().connect(sigc::mem_fun(*this, &Abaddon::ActionLeaveGuild)); m_main_window->GetChannelList()->signal_action_guild_settings().connect(sigc::mem_fun(*this, &Abaddon::ActionGuildSettings)); m_main_window->GetChatWindow()->signal_action_message_edit().connect(sigc::mem_fun(*this, &Abaddon::ActionChatEditMessage)); m_main_window->GetChatWindow()->signal_action_chat_submit().connect(sigc::mem_fun(*this, &Abaddon::ActionChatInputSubmit)); m_main_window->GetChatWindow()->signal_action_chat_load_history().connect(sigc::mem_fun(*this, &Abaddon::ActionChatLoadHistory)); m_main_window->GetChatWindow()->signal_action_channel_click().connect(sigc::mem_fun(*this, &Abaddon::ActionChannelOpened)); m_main_window->GetChatWindow()->signal_action_insert_mention().connect(sigc::mem_fun(*this, &Abaddon::ActionInsertMention)); m_main_window->GetChatWindow()->signal_action_reaction_add().connect(sigc::mem_fun(*this, &Abaddon::ActionReactionAdd)); m_main_window->GetChatWindow()->signal_action_reaction_remove().connect(sigc::mem_fun(*this, &Abaddon::ActionReactionRemove)); ActionReloadCSS(); m_gtk_app->signal_shutdown().connect(sigc::mem_fun(*this, &Abaddon::OnShutdown), false); m_main_window->show(); return m_gtk_app->run(*m_main_window); } void Abaddon::OnShutdown() { StopDiscord(); m_settings.Close(); } void Abaddon::LoadFromSettings() { std::string token = GetSettings().DiscordToken; if (!token.empty()) { m_discord_token = token; m_discord.UpdateToken(m_discord_token); } } void Abaddon::StartDiscord() { m_discord.Start(); } void Abaddon::StopDiscord() { m_discord.Stop(); SaveState(); } bool Abaddon::IsDiscordActive() const { return m_discord.IsStarted(); } std::string Abaddon::GetDiscordToken() const { return m_discord_token; } DiscordClient &Abaddon::GetDiscordClient() { std::scoped_lock guard(m_mutex); return m_discord; } const DiscordClient &Abaddon::GetDiscordClient() const { std::scoped_lock guard(m_mutex); return m_discord; } void Abaddon::DiscordOnReady() { m_main_window->UpdateComponents(); LoadState(); } void Abaddon::DiscordOnMessageCreate(const Message &message) { m_main_window->UpdateChatNewMessage(message); } void Abaddon::DiscordOnMessageDelete(Snowflake id, Snowflake channel_id) { m_main_window->UpdateChatMessageDeleted(id, channel_id); } void Abaddon::DiscordOnMessageUpdate(Snowflake id, Snowflake channel_id) { m_main_window->UpdateChatMessageUpdated(id, channel_id); } void Abaddon::DiscordOnGuildMemberListUpdate(Snowflake guild_id) { m_main_window->UpdateMembers(); } void Abaddon::DiscordOnThreadMemberListUpdate(const ThreadMemberListUpdateData &data) { m_main_window->UpdateMembers(); } void Abaddon::DiscordOnReactionAdd(Snowflake message_id, const Glib::ustring ¶m) { m_main_window->UpdateChatReactionAdd(message_id, param); } void Abaddon::DiscordOnReactionRemove(Snowflake message_id, const Glib::ustring ¶m) { m_main_window->UpdateChatReactionAdd(message_id, param); } // this will probably cause issues when actual applications are rolled out but that doesn't seem like it will happen for a while void Abaddon::DiscordOnGuildJoinRequestCreate(const GuildJoinRequestCreateData &data) { if (data.Status == GuildApplicationStatus::STARTED) { ShowGuildVerificationGateDialog(data.GuildID); } } void Abaddon::DiscordOnMessageSent(const Message &data) { m_main_window->UpdateChatNewMessage(data); } void Abaddon::DiscordOnDisconnect(bool is_reconnecting, GatewayCloseCode close_code) { m_channels_history_loaded.clear(); m_channels_history_loading.clear(); m_channels_requested.clear(); if (is_reconnecting) return; m_main_window->set_title(APP_TITLE); m_main_window->UpdateComponents(); if (close_code == GatewayCloseCode::AuthenticationFailed) { Gtk::MessageDialog dlg(*m_main_window, "Discord rejected your token", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); dlg.set_position(Gtk::WIN_POS_CENTER); dlg.run(); } else if (close_code != GatewayCloseCode::Normal) { Gtk::MessageDialog dlg(*m_main_window, "Lost connection with Discord's gateway. Try reconnecting (code " + std::to_string(static_cast(close_code)) + ")", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); dlg.set_position(Gtk::WIN_POS_CENTER); dlg.run(); } } void Abaddon::DiscordOnThreadUpdate(const ThreadUpdateData &data) { if (data.Thread.ID == m_main_window->GetChatActiveChannel()) { if (data.Thread.ThreadMetadata->IsArchived) m_main_window->GetChatWindow()->SetTopic("This thread is archived. Sending a message will unarchive it"); else m_main_window->GetChatWindow()->SetTopic(""); } } SettingsManager::Settings &Abaddon::GetSettings() { return m_settings.GetSettings(); } Glib::RefPtr Abaddon::GetStyleProvider() { return m_css_provider; } void Abaddon::ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_id) { m_shown_user_menu_id = id; m_shown_user_menu_guild_id = guild_id; const auto guild = m_discord.GetGuild(guild_id); const auto me = m_discord.GetUserData().ID; const auto user = m_discord.GetMember(id, guild_id); for (const auto child : m_user_menu_roles_submenu->get_children()) delete child; if (guild.has_value() && user.has_value()) { const auto roles = user->GetSortedRoles(); m_user_menu_roles->set_visible(!roles.empty()); for (const auto &role : roles) { auto *item = Gtk::manage(new Gtk::MenuItem(role.Name)); if (role.Color != 0) { Gdk::RGBA color; color.set_red(((role.Color & 0xFF0000) >> 16) / 255.0); color.set_green(((role.Color & 0x00FF00) >> 8) / 255.0); color.set_blue(((role.Color & 0x0000FF) >> 0) / 255.0); color.set_alpha(1.0); item->override_color(color); } item->show(); m_user_menu_roles_submenu->append(*item); } } else m_user_menu_roles->set_visible(false); if (me == id) { m_user_menu_ban->set_visible(false); m_user_menu_kick->set_visible(false); m_user_menu_open_dm->set_visible(false); } else { const bool has_kick = m_discord.HasGuildPermission(me, guild_id, Permission::KICK_MEMBERS); const bool has_ban = m_discord.HasGuildPermission(me, guild_id, Permission::BAN_MEMBERS); const bool can_manage = m_discord.CanManageMember(guild_id, me, id); m_user_menu_kick->set_visible(has_kick && can_manage); m_user_menu_ban->set_visible(has_ban && can_manage); m_user_menu_open_dm->set_visible(true); } m_user_menu_remove_recipient->hide(); if (me != id) { const auto channel_id = m_main_window->GetChatActiveChannel(); const auto channel = m_discord.GetChannel(channel_id); if (channel.has_value() && channel->Type == ChannelType::GROUP_DM && me == *channel->OwnerID) m_user_menu_remove_recipient->show(); } m_user_menu->popup_at_pointer(event); } void Abaddon::ShowGuildVerificationGateDialog(Snowflake guild_id) { VerificationGateDialog dlg(*m_main_window, guild_id); if (dlg.run() == Gtk::RESPONSE_OK) { const auto cb = [this](DiscordError code) { if (code != DiscordError::NONE) { Gtk::MessageDialog dlg(*m_main_window, "Failed to accept the verification gate.", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); dlg.set_position(Gtk::WIN_POS_CENTER); dlg.run(); } }; m_discord.AcceptVerificationGate(guild_id, dlg.GetVerificationGate(), cb); } } void Abaddon::CheckMessagesForMembers(const ChannelData &chan, const std::vector &msgs) { if (!chan.GuildID.has_value()) return; std::vector unknown; std::transform(msgs.begin(), msgs.end(), std::back_inserter(unknown), [](const Message &msg) -> Snowflake { return msg.Author.ID; }); const auto fetch = m_discord.FilterUnknownMembersFrom(*chan.GuildID, unknown.begin(), unknown.end()); m_discord.RequestMembers(*chan.GuildID, fetch.begin(), fetch.end()); } void Abaddon::SetupUserMenu() { m_user_menu = Gtk::manage(new Gtk::Menu); m_user_menu_insert_mention = Gtk::manage(new Gtk::MenuItem("Insert Mention")); m_user_menu_ban = Gtk::manage(new Gtk::MenuItem("Ban")); m_user_menu_kick = Gtk::manage(new Gtk::MenuItem("Kick")); m_user_menu_copy_id = Gtk::manage(new Gtk::MenuItem("Copy ID")); m_user_menu_open_dm = Gtk::manage(new Gtk::MenuItem("Open DM")); m_user_menu_roles = Gtk::manage(new Gtk::MenuItem("Roles")); m_user_menu_info = Gtk::manage(new Gtk::MenuItem("View Profile")); m_user_menu_remove_recipient = Gtk::manage(new Gtk::MenuItem("Remove From Group")); m_user_menu_roles_submenu = Gtk::manage(new Gtk::Menu); m_user_menu_roles->set_submenu(*m_user_menu_roles_submenu); m_user_menu_insert_mention->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_user_menu_insert_mention)); m_user_menu_ban->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_user_menu_ban)); m_user_menu_kick->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_user_menu_kick)); m_user_menu_copy_id->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_user_menu_copy_id)); m_user_menu_open_dm->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_user_menu_open_dm)); m_user_menu_remove_recipient->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_user_menu_remove_recipient)); m_user_menu_info->signal_activate().connect([this]() { auto *window = new ProfileWindow(m_shown_user_menu_id); ManageHeapWindow(window); window->show(); }); m_user_menu_remove_recipient->override_color(Gdk::RGBA("#BE3C3D")); m_user_menu->append(*m_user_menu_info); m_user_menu->append(*m_user_menu_insert_mention); m_user_menu->append(*Gtk::manage(new Gtk::SeparatorMenuItem)); m_user_menu->append(*m_user_menu_ban); m_user_menu->append(*m_user_menu_kick); m_user_menu->append(*Gtk::manage(new Gtk::SeparatorMenuItem)); m_user_menu->append(*m_user_menu_open_dm); m_user_menu->append(*m_user_menu_roles); m_user_menu->append(*Gtk::manage(new Gtk::SeparatorMenuItem)); m_user_menu->append(*m_user_menu_remove_recipient); m_user_menu->append(*Gtk::manage(new Gtk::SeparatorMenuItem)); m_user_menu->append(*m_user_menu_copy_id); m_user_menu->show_all(); } void Abaddon::SaveState() { if (!GetSettings().SaveState) 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 (!GetSettings().SaveState) { // call with empty data to purge the temporary table m_main_window->GetChannelList()->UseExpansionState({}); 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; // for some reason if ShowUserMenu is called multiple times with events across windows // and one of the windows is closed, then it throws errors when the menu is opened again // i dont think this is my fault so this semi-hacky solution will have to do delete m_user_menu; SetupUserMenu(); }); } void Abaddon::on_user_menu_insert_mention() { ActionInsertMention(m_shown_user_menu_id); } void Abaddon::on_user_menu_ban() { ActionBanMember(m_shown_user_menu_id, m_shown_user_menu_guild_id); } void Abaddon::on_user_menu_kick() { ActionKickMember(m_shown_user_menu_id, m_shown_user_menu_guild_id); } void Abaddon::on_user_menu_copy_id() { Gtk::Clipboard::get()->set_text(std::to_string(m_shown_user_menu_id)); } void Abaddon::on_user_menu_open_dm() { const auto existing = m_discord.FindDM(m_shown_user_menu_id); if (existing.has_value()) ActionChannelOpened(*existing); else m_discord.CreateDM(m_shown_user_menu_id, [this](DiscordError code, Snowflake channel_id) { if (code == DiscordError::NONE) { // give the gateway a little window to send CHANNEL_CREATE auto cb = [this, channel_id] { ActionChannelOpened(channel_id); }; Glib::signal_timeout().connect_once(sigc::track_obj(cb, *this), 200); } }); } void Abaddon::on_user_menu_remove_recipient() { m_discord.RemoveGroupDMRecipient(m_main_window->GetChatActiveChannel(), m_shown_user_menu_id); } std::string Abaddon::GetCSSPath() { const static auto path = Platform::FindResourceFolder() + "/css"; return path; } std::string Abaddon::GetResPath() { const static auto path = Platform::FindResourceFolder() + "/res"; 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; } 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(); m_main_window->UpdateComponents(); } void Abaddon::ActionDisconnect() { StopDiscord(); } void Abaddon::ActionSetToken() { TokenDialog dlg(*m_main_window); auto response = dlg.run(); if (response == Gtk::RESPONSE_OK) { m_discord_token = dlg.GetToken(); m_discord.UpdateToken(m_discord_token); m_main_window->UpdateComponents(); GetSettings().DiscordToken = m_discord_token; } } void Abaddon::ActionJoinGuildDialog() { JoinGuildDialog dlg(*m_main_window); auto response = dlg.run(); if (response == Gtk::RESPONSE_OK) { auto code = dlg.GetCode(); m_discord.JoinGuild(code); } } void Abaddon::ActionChannelOpened(Snowflake id) { if (!id.IsValid() || id == m_main_window->GetChatActiveChannel()) return; m_main_window->GetChatWindow()->SetTopic(""); const auto channel = m_discord.GetChannel(id); if (!channel.has_value()) return; const bool can_access = channel->IsDM() || m_discord.HasChannelPermission(m_discord.GetUserData().ID, id, Permission::VIEW_CHANNEL); if (channel->Type == ChannelType::GUILD_TEXT || channel->Type == ChannelType::GUILD_NEWS) m_main_window->set_title(std::string(APP_TITLE) + " - #" + *channel->Name); else { std::string display; const auto recipients = channel->GetDMRecipients(); if (recipients.size() > 1) display = std::to_string(recipients.size()) + " users"; else if (recipients.size() == 1) display = recipients[0].Username; else display = "Empty group"; m_main_window->set_title(std::string(APP_TITLE) + " - " + display); } m_main_window->UpdateChatActiveChannel(id); if (m_channels_requested.find(id) == m_channels_requested.end()) { // dont fire requests we know will fail if (can_access) { m_discord.FetchMessagesInChannel(id, [channel, this, id](const std::vector &msgs) { CheckMessagesForMembers(*channel, msgs); m_main_window->UpdateChatWindowContents(); m_channels_requested.insert(id); }); } } else { m_main_window->UpdateChatWindowContents(); } if (can_access) { if (channel->IsThread()) { m_discord.SendThreadLazyLoad(id); if (channel->ThreadMetadata->IsArchived) m_main_window->GetChatWindow()->SetTopic("This thread is archived. Sending a message will unarchive it"); } else if (channel->Type != ChannelType::DM && channel->Type != ChannelType::GROUP_DM && channel->GuildID.has_value()) { m_discord.SendLazyLoad(id); if (m_discord.IsVerificationRequired(*channel->GuildID)) ShowGuildVerificationGateDialog(*channel->GuildID); } } } void Abaddon::ActionChatLoadHistory(Snowflake id) { if (m_channels_history_loaded.find(id) != m_channels_history_loaded.end()) return; if (m_channels_history_loading.find(id) != m_channels_history_loading.end()) return; Snowflake before_id = m_main_window->GetChatOldestListedMessage(); auto msgs = m_discord.GetMessagesBefore(id, before_id); if (msgs.size() >= 50) { m_main_window->UpdateChatPrependHistory(msgs); return; } m_channels_history_loading.insert(id); m_discord.FetchMessagesInChannelBefore(id, before_id, [this, id](const std::vector &msgs) { m_channels_history_loading.erase(id); const auto channel = m_discord.GetChannel(id); if (channel.has_value()) CheckMessagesForMembers(*channel, msgs); if (msgs.empty()) { m_channels_history_loaded.insert(id); } else { m_main_window->UpdateChatPrependHistory(msgs); } }); } void Abaddon::ActionChatInputSubmit(std::string msg, Snowflake channel, Snowflake referenced_message) { if (msg.substr(0, 7) == "/shrug " || msg == "/shrug") msg = msg.substr(6) + "\xC2\xAF\x5C\x5F\x28\xE3\x83\x84\x29\x5F\x2F\xC2\xAF"; // this is important if (referenced_message.IsValid()) m_discord.SendChatMessage(msg, channel, referenced_message); else m_discord.SendChatMessage(msg, channel); } void Abaddon::ActionChatEditMessage(Snowflake channel_id, Snowflake id) { const auto msg = m_discord.GetMessage(id); if (!msg.has_value()) return; EditMessageDialog dlg(*m_main_window); dlg.SetContent(msg->Content); auto response = dlg.run(); if (response == Gtk::RESPONSE_OK) { auto new_content = dlg.GetContent(); m_discord.EditMessage(channel_id, id, new_content); } } void Abaddon::ActionInsertMention(Snowflake id) { m_main_window->InsertChatInput("<@" + std::to_string(id) + ">"); } void Abaddon::ActionLeaveGuild(Snowflake id) { ConfirmDialog dlg(*m_main_window); const auto guild = m_discord.GetGuild(id); if (guild.has_value()) dlg.SetConfirmText("Are you sure you want to leave " + guild->Name + "?"); auto response = dlg.run(); if (response == Gtk::RESPONSE_OK) m_discord.LeaveGuild(id); } void Abaddon::ActionKickMember(Snowflake user_id, Snowflake guild_id) { ConfirmDialog dlg(*m_main_window); const auto user = m_discord.GetUser(user_id); if (user.has_value()) dlg.SetConfirmText("Are you sure you want to kick " + user->Username + "#" + user->Discriminator + "?"); auto response = dlg.run(); if (response == Gtk::RESPONSE_OK) m_discord.KickUser(user_id, guild_id); } void Abaddon::ActionBanMember(Snowflake user_id, Snowflake guild_id) { ConfirmDialog dlg(*m_main_window); const auto user = m_discord.GetUser(user_id); if (user.has_value()) dlg.SetConfirmText("Are you sure you want to ban " + user->Username + "#" + user->Discriminator + "?"); auto response = dlg.run(); if (response == Gtk::RESPONSE_OK) m_discord.BanUser(user_id, guild_id); } void Abaddon::ActionSetStatus() { SetStatusDialog dlg(*m_main_window); const auto response = dlg.run(); if (response != Gtk::RESPONSE_OK || !m_discord.IsStarted()) return; const auto status = dlg.GetStatusType(); const auto activity_type = dlg.GetActivityType(); const auto activity_name = dlg.GetActivityName(); if (activity_name.empty()) { m_discord.UpdateStatus(status, false); } else { ActivityData activity; activity.Name = activity_name; activity.Type = activity_type; m_discord.UpdateStatus(status, false, activity); } } void Abaddon::ActionReactionAdd(Snowflake id, const Glib::ustring ¶m) { m_discord.AddReaction(id, param); } void Abaddon::ActionReactionRemove(Snowflake id, const Glib::ustring ¶m) { m_discord.RemoveReaction(id, param); } void Abaddon::ActionGuildSettings(Snowflake id) { auto window = new GuildSettingsWindow(id); ManageHeapWindow(window); window->show(); } void Abaddon::ActionAddRecipient(Snowflake channel_id) { FriendPickerDialog dlg(*m_main_window); auto response = dlg.run(); if (response == Gtk::RESPONSE_OK) { auto user_id = dlg.GetUserID(); m_discord.AddGroupDMRecipient(channel_id, user_id); } } void Abaddon::ActionViewPins(Snowflake channel_id) { const auto data = m_discord.GetChannel(channel_id); if (!data.has_value()) return; auto window = new PinnedWindow(*data); ManageHeapWindow(window); window->show(); } void Abaddon::ActionViewThreads(Snowflake channel_id) { auto data = m_discord.GetChannel(channel_id); if (!data.has_value()) return; if (data->IsThread()) { data = m_discord.GetChannel(*data->ParentID); if (!data.has_value()) return; } auto window = new ThreadsWindow(*data); ManageHeapWindow(window); window->show(); } bool Abaddon::ShowConfirm(const Glib::ustring &prompt, Gtk::Window *window) { ConfirmDialog dlg(window != nullptr ? *window : *m_main_window); dlg.SetConfirmText(prompt); return dlg.run() == Gtk::RESPONSE_OK; } void Abaddon::ActionReloadCSS() { try { Gtk::StyleContext::remove_provider_for_screen(Gdk::Screen::get_default(), m_css_provider); m_css_provider->load_from_path(GetCSSPath("/" + GetSettings().MainCSS)); Gtk::StyleContext::add_provider_for_screen(Gdk::Screen::get_default(), m_css_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); Gtk::StyleContext::remove_provider_for_screen(Gdk::Screen::get_default(), m_css_low_provider); m_css_low_provider->load_from_path(GetCSSPath("/application-low-priority.css")); Gtk::StyleContext::add_provider_for_screen(Gdk::Screen::get_default(), m_css_low_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION - 1); } catch (Glib::Error &e) { Gtk::MessageDialog dlg(*m_main_window, "css failed to load (" + e.what() + ")", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); dlg.set_position(Gtk::WIN_POS_CENTER); dlg.run(); } } ImageManager &Abaddon::GetImageManager() { return m_img_mgr; } EmojiResource &Abaddon::GetEmojis() { return m_emojis; } int main(int argc, char **argv) { if (std::getenv("ABADDON_NO_FC") == nullptr) Platform::SetupFonts(); char *systemLocale = std::setlocale(LC_ALL, ""); try { std::locale::global(std::locale(systemLocale)); } catch (...) { try { std::locale::global(std::locale::classic()); std::setlocale(LC_ALL, systemLocale); } catch (...) {} } #if defined(_WIN32) && defined(_MSC_VER) TCHAR buf[2] { 0 }; GetEnvironmentVariableA("GTK_CSD", buf, sizeof(buf)); if (buf[0] != '1') SetEnvironmentVariableA("GTK_CSD", "0"); #endif Gtk::Main::init_gtkmm_internals(); // why??? return Abaddon::Get().StartGTK(); }