From 4db6e2f46723707f3aeb7adcf64930d9c3f26ba7 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 3 Jun 2023 16:56:10 -0400 Subject: [PATCH 01/52] update icon cache instead of copy --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 52ea356..116a800 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,7 +95,6 @@ jobs: mkdir -p 16x16/devices 24x24/devices 32x32/devices 48x48/devices 64x64/devices 96x96/devices scalable/devices mkdir -p 16x16/status 24x24/status 32x32/status 48x48/status 64x64/status 96x96/status scalable/status cd ${GITHUB_WORKSPACE} - cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/icon-theme.cache ${artifact_dir}/share/icons/Adwaita/icon-theme.cache cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/index.theme ${artifact_dir}/share/icons/Adwaita/index.theme cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/16x16/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/16x16/%.symbolic.png || : cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/24x24/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/24x24/%.symbolic.png || : @@ -104,6 +103,8 @@ jobs: cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/64x64/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/64x64/%.symbolic.png || : cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/96x96/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/96x96/%.symbolic.png || : cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/scalable/%.svg ${artifact_dir}/share/icons/Adwaita/scalable/%.svg || : + cd ${artifact_dir}/share/icons/Adwaita + gtk-update-icon-cache . - name: Upload build (1) uses: haya14busa/action-cond@v1 From f9deeef70dbc4f407d85ba04d56a7bae70f77ee7 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 3 Jun 2023 18:04:48 -0400 Subject: [PATCH 02/52] dont let message ListBoxRow steal focus on click --- src/components/chatmessage.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/chatmessage.cpp b/src/components/chatmessage.cpp index 5f4c9a5..4b2e748 100644 --- a/src/components/chatmessage.cpp +++ b/src/components/chatmessage.cpp @@ -1011,6 +1011,7 @@ ChatMessageHeader::ChatMessageHeader(const Message &data) add(m_main_box); set_margin_bottom(8); + set_focus_on_click(false); show_all(); From 26973f1b1470b4d381e493ad10da0d5daea8d982 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 12 Jun 2023 04:08:16 -0400 Subject: [PATCH 03/52] basic pomelo support --- src/discord/store.cpp | 7 ++++-- src/discord/user.cpp | 57 ++++++++++++++++++++++++++++++------------- src/discord/user.hpp | 8 ++++++ 3 files changed, 53 insertions(+), 19 deletions(-) diff --git a/src/discord/store.cpp b/src/discord/store.cpp index 41b2069..24b686a 100644 --- a/src/discord/store.cpp +++ b/src/discord/store.cpp @@ -438,6 +438,7 @@ void Store::SetUser(Snowflake id, const UserData &user) { s->Bind(7, user.IsMFAEnabled); s->Bind(8, user.PremiumType); s->Bind(9, user.PublicFlags); + s->Bind(10, user.GlobalName); if (!s->Insert()) fprintf(stderr, "user insert failed for %" PRIu64 ": %s\n", static_cast(id), m_db.ErrStr()); @@ -1109,6 +1110,7 @@ std::optional Store::GetUser(Snowflake id) const { s->Get(6, r.IsMFAEnabled); s->Get(7, r.PremiumType); s->Get(8, r.PublicFlags); + s->Get(9, r.GlobalName); s->Reset(); @@ -1233,7 +1235,8 @@ bool Store::CreateTables() { system BOOL, mfa BOOL, premium INTEGER, - pubflags INTEGER + pubflags INTEGER, + global_name TEXT ) )"; @@ -1797,7 +1800,7 @@ bool Store::CreateStatements() { m_stmt_set_user = std::make_unique(m_db, R"( REPLACE INTO users VALUES ( - ?, ?, ?, ?, ?, ?, ?, ?, ? + ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) )"); if (!m_stmt_set_user->OK()) { diff --git a/src/discord/user.cpp b/src/discord/user.cpp index 2ee7361..7fbcdca 100644 --- a/src/discord/user.cpp +++ b/src/discord/user.cpp @@ -1,5 +1,9 @@ #include "user.hpp" +bool UserData::IsPomelo() const noexcept { + return Discriminator.size() == 1 && Discriminator[0] == '0'; +} + bool UserData::IsABot() const noexcept { return IsBot.has_value() && *IsBot; } @@ -18,49 +22,54 @@ bool UserData::HasAnimatedAvatar() const noexcept { bool UserData::HasAnimatedAvatar(Snowflake guild_id) const { const auto member = Abaddon::Get().GetDiscordClient().GetMember(ID, guild_id); - if (member.has_value() && member->Avatar.has_value() && member->Avatar.value()[0] == 'a' && member->Avatar.value()[1] == '_') + if (member.has_value() && member->Avatar.has_value() && member->Avatar.value()[0] == 'a' && member->Avatar.value()[1] == '_') { return true; - else if (member.has_value() && !member->Avatar.has_value()) + } else if (member.has_value() && !member->Avatar.has_value()) { return HasAnimatedAvatar(); + } return false; } bool UserData::HasAnimatedAvatar(const std::optional &guild_id) const { - if (guild_id.has_value()) + if (guild_id.has_value()) { return HasAnimatedAvatar(*guild_id); - else - return HasAnimatedAvatar(); + } + + return HasAnimatedAvatar(); } std::string UserData::GetAvatarURL(Snowflake guild_id, const std::string &ext, std::string size) const { const auto member = Abaddon::Get().GetDiscordClient().GetMember(ID, guild_id); if (member.has_value() && member->Avatar.has_value()) { - if (ext == "gif" && !(member->Avatar.value()[0] == 'a' && member->Avatar.value()[1] == '_')) + if (ext == "gif" && !(member->Avatar.value()[0] == 'a' && member->Avatar.value()[1] == '_')) { return GetAvatarURL(ext, size); + } return "https://cdn.discordapp.com/guilds/" + std::to_string(guild_id) + "/users/" + std::to_string(ID) + "/avatars/" + *member->Avatar + "." + ext + "?" + "size=" + size; - } else { - return GetAvatarURL(ext, size); } + return GetAvatarURL(ext, size); } std::string UserData::GetAvatarURL(const std::optional &guild_id, const std::string &ext, std::string size) const { - if (guild_id.has_value()) + if (guild_id.has_value()) { return GetAvatarURL(*guild_id, ext, size); - else - return GetAvatarURL(ext, size); + } + return GetAvatarURL(ext, size); } std::string UserData::GetAvatarURL(const std::string &ext, std::string size) const { - if (HasAvatar()) + if (HasAvatar()) { return "https://cdn.discordapp.com/avatars/" + std::to_string(ID) + "/" + Avatar + "." + ext + "?size=" + size; - else - return GetDefaultAvatarURL(); + } + return GetDefaultAvatarURL(); } std::string UserData::GetDefaultAvatarURL() const { + if (IsPomelo()) { + return "https://cdn.discordapp.com/embed/avatars/" + std::to_string((static_cast(ID) >> 22) % 6) + ".png"; + } return "https://cdn.discordapp.com/embed/avatars/" + std::to_string(std::stoul(Discriminator) % 5) + ".png"; // size isn't respected by the cdn } @@ -72,16 +81,27 @@ std::string UserData::GetMention() const { return "<@" + std::to_string(ID) + ">"; } +std::string UserData::GetName() const { + if (IsPomelo() && GlobalName.has_value()) { + return *GlobalName; + } + + return Username; +} + std::string UserData::GetEscapedName() const { - return Glib::Markup::escape_text(Username); + return Glib::Markup::escape_text(GetName()); } std::string UserData::GetEscapedBoldName() const { - return "" + Glib::Markup::escape_text(Username) + ""; + return "" + Glib::Markup::escape_text(GetName()) + ""; } std::string UserData::GetEscapedString() const { - return Glib::Markup::escape_text(Username) + "#" + Discriminator; + if (IsPomelo()) { + return GetEscapedName(); + } + return Glib::Markup::escape_text(GetName()) + "#" + Discriminator; } void from_json(const nlohmann::json &j, UserData &m) { @@ -104,6 +124,7 @@ void from_json(const nlohmann::json &j, UserData &m) { JS_ON("phone", m.Phone); JS_ON("bio", m.Bio); JS_ON("banner", m.BannerHash); + JS_N("global_name", m.GlobalName); } void to_json(nlohmann::json &j, const UserData &m) { @@ -127,6 +148,7 @@ void to_json(nlohmann::json &j, const UserData &m) { JS_IF("mobile", m.IsMobile); JS_IF("nsfw_allowed", m.IsNSFWAllowed); JS_IF("phone", m.Phone); + JS_IF("global_name", m.GlobalName); } void UserData::update_from_json(const nlohmann::json &j) { @@ -146,6 +168,7 @@ void UserData::update_from_json(const nlohmann::json &j) { JS_RD("mobile", IsMobile); JS_RD("nsfw_allowed", IsNSFWAllowed); JS_RD("phone", Phone); + JS_RD("global_name", GlobalName); } const char *UserData::GetFlagName(uint64_t flag) { diff --git a/src/discord/user.hpp b/src/discord/user.hpp index 1b9d517..e88371a 100644 --- a/src/discord/user.hpp +++ b/src/discord/user.hpp @@ -25,6 +25,11 @@ struct UserData { VerifiedBot = 1 << 16, EarlyVerifiedBotDeveloper = 1 << 17, CertifiedModerator = 1 << 18, + BotHTTPInteractions = 1 << 19, + Spammer = 1 << 20, + DisablePremium = 1 << 21, + ActiveDeveloper = 1 << 22, + Quarantined = 1ULL << 44, MaxFlag_PlusOne, MaxFlag = MaxFlag_PlusOne - 1, @@ -37,6 +42,7 @@ struct UserData { std::string Username; std::string Discriminator; std::string Avatar; // null + std::optional GlobalName; std::optional IsBot; std::optional IsSystem; std::optional IsMFAEnabled; @@ -60,6 +66,7 @@ struct UserData { friend void to_json(nlohmann::json &j, const UserData &m); void update_from_json(const nlohmann::json &j); + [[nodiscard]] bool IsPomelo() const noexcept; [[nodiscard]] bool IsABot() const noexcept; [[nodiscard]] bool IsDeleted() const; [[nodiscard]] bool HasAvatar() const; @@ -72,6 +79,7 @@ struct UserData { [[nodiscard]] std::string GetDefaultAvatarURL() const; [[nodiscard]] Snowflake GetHoistedRole(Snowflake guild_id, bool with_color = false) const; [[nodiscard]] std::string GetMention() const; + [[nodiscard]] std::string GetName() const; [[nodiscard]] std::string GetEscapedName() const; [[nodiscard]] std::string GetEscapedBoldName() const; [[nodiscard]] std::string GetEscapedString() const; From bfc5b0e682852173e03d703b1098530fd65a0dd4 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 13 Jun 2023 20:20:59 -0400 Subject: [PATCH 04/52] show pomelo names in member list --- src/components/memberlist.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/components/memberlist.cpp b/src/components/memberlist.cpp index 19b4fb8..a477832 100644 --- a/src/components/memberlist.cpp +++ b/src/components/memberlist.cpp @@ -37,9 +37,17 @@ MemberListUserRow::MemberListUserRow(const std::optional &guild, cons m_label->set_single_line_mode(true); m_label->set_ellipsize(Pango::ELLIPSIZE_END); - std::string display = data.Username; - if (Abaddon::Get().GetSettings().ShowMemberListDiscriminators) - display += "#" + data.Discriminator; + // todo remove after migration complete + std::string display; + if (data.IsPomelo()) { + display = data.GetName(); + } else { + display = data.Username; + if (Abaddon::Get().GetSettings().ShowMemberListDiscriminators) { + display += "#" + data.Discriminator; + } + } + if (guild.has_value()) { if (const auto col_id = data.GetHoistedRole(guild->ID, true); col_id.IsValid()) { auto color = Abaddon::Get().GetDiscordClient().GetRole(col_id)->Color; From a4a8293a0e9dc2e7023cc5f407eb78a505f22129 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 13 Jun 2023 20:40:41 -0400 Subject: [PATCH 05/52] show pomelo names in profile window --- README.md | 53 ++++++++++++++++++----------------- res/css/main.css | 4 +++ src/discord/user.cpp | 8 ++++++ src/discord/user.hpp | 1 + src/windows/profilewindow.cpp | 18 ++++++++---- src/windows/profilewindow.hpp | 2 ++ 6 files changed, 54 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index c66af60..70996b2 100644 --- a/README.md +++ b/README.md @@ -226,32 +226,33 @@ Used in guild settings popup: Used in profile popup: -| Selector | Description | -|------------------------------|---------------------------------------------------------| -| `.mutual-friend-item` | Applied to every item in the mutual friends list | -| `.mutual-friend-item-name` | Name in mutual friend item | -| `.mutual-friend-item-avatar` | Avatar in mutual friend item | -| `.mutual-guild-item` | Applied to every item in the mutual guilds list | -| `.mutual-guild-item-name` | Name in mutual guild item | -| `.mutual-guild-item-icon` | Icon in mutual guild item | -| `.mutual-guild-item-nick` | User nickname in mutual guild item | -| `.profile-connection` | Applied to every item in the user connections list | -| `.profile-connection-label` | Label in profile connection item | -| `.profile-connection-check` | Checkmark in verified profile connection items | -| `.profile-connections` | Container for profile connections | -| `.profile-notes` | Container for notes in profile window | -| `.profile-notes-label` | Label that says "NOTE" | -| `.profile-notes-text` | Actual note text | -| `.profile-info-pane` | Applied to container for info section of profile popup | -| `.profile-info-created` | Label for creation date of profile | -| `.user-profile-window` | | -| `.profile-main-container` | Inner container for profile | -| `.profile-avatar` | | -| `.profile-username` | | -| `.profile-switcher` | Buttons used to switch viewed section of profile | -| `.profile-stack` | Container for profile info that can be switched between | -| `.profile-badges` | Container for badges | -| `.profile-badge` | | +| Selector | Description | +|--------------------------------|------------------------------------------------------------| +| `.mutual-friend-item` | Applied to every item in the mutual friends list | +| `.mutual-friend-item-name` | Name in mutual friend item | +| `.mutual-friend-item-avatar` | Avatar in mutual friend item | +| `.mutual-guild-item` | Applied to every item in the mutual guilds list | +| `.mutual-guild-item-name` | Name in mutual guild item | +| `.mutual-guild-item-icon` | Icon in mutual guild item | +| `.mutual-guild-item-nick` | User nickname in mutual guild item | +| `.profile-connection` | Applied to every item in the user connections list | +| `.profile-connection-label` | Label in profile connection item | +| `.profile-connection-check` | Checkmark in verified profile connection items | +| `.profile-connections` | Container for profile connections | +| `.profile-notes` | Container for notes in profile window | +| `.profile-notes-label` | Label that says "NOTE" | +| `.profile-notes-text` | Actual note text | +| `.profile-info-pane` | Applied to container for info section of profile popup | +| `.profile-info-created` | Label for creation date of profile | +| `.user-profile-window` | | +| `.profile-main-container` | Inner container for profile | +| `.profile-avatar` | | +| `.profile-username` | User's display name (username for backwards compatibility) | +| `.profile-username-nondisplay` | User's actual username | +| `.profile-switcher` | Buttons used to switch viewed section of profile | +| `.profile-stack` | Container for profile info that can be switched between | +| `.profile-badges` | Container for badges | +| `.profile-badge` | | ### Settings diff --git a/res/css/main.css b/res/css/main.css index dcbdf4f..12ce4d6 100644 --- a/res/css/main.css +++ b/res/css/main.css @@ -237,6 +237,10 @@ font-size: 20px; } +.profile-username-nondisplay { + margin-left: 10px; +} + .profile-badge { margin-right: 10px; } diff --git a/src/discord/user.cpp b/src/discord/user.cpp index 7fbcdca..06073ad 100644 --- a/src/discord/user.cpp +++ b/src/discord/user.cpp @@ -89,6 +89,14 @@ std::string UserData::GetName() const { return Username; } +std::string UserData::GetUsername() const { + if (IsPomelo()) { + return Username; + } + + return Username + "#" + Discriminator; +} + std::string UserData::GetEscapedName() const { return Glib::Markup::escape_text(GetName()); } diff --git a/src/discord/user.hpp b/src/discord/user.hpp index e88371a..05acd05 100644 --- a/src/discord/user.hpp +++ b/src/discord/user.hpp @@ -80,6 +80,7 @@ struct UserData { [[nodiscard]] Snowflake GetHoistedRole(Snowflake guild_id, bool with_color = false) const; [[nodiscard]] std::string GetMention() const; [[nodiscard]] std::string GetName() const; + [[nodiscard]] std::string GetUsername() const; [[nodiscard]] std::string GetEscapedName() const; [[nodiscard]] std::string GetEscapedBoldName() const; [[nodiscard]] std::string GetEscapedString() const; diff --git a/src/windows/profilewindow.cpp b/src/windows/profilewindow.cpp index 3d5f140..1469e49 100644 --- a/src/windows/profilewindow.cpp +++ b/src/windows/profilewindow.cpp @@ -5,6 +5,7 @@ ProfileWindow::ProfileWindow(Snowflake user_id) , m_main(Gtk::ORIENTATION_VERTICAL) , m_upper(Gtk::ORIENTATION_HORIZONTAL) , m_badges(Gtk::ORIENTATION_HORIZONTAL) + , m_name_box(Gtk::ORIENTATION_VERTICAL) , m_pane_info(user_id) , m_pane_guilds(user_id) , m_pane_friends(user_id) { @@ -15,14 +16,15 @@ ProfileWindow::ProfileWindow(Snowflake user_id) set_name("user-profile"); set_default_size(450, 375); - set_title(user.Username + "#" + user.Discriminator); + set_title(user.GetUsername()); set_position(Gtk::WIN_POS_CENTER); get_style_context()->add_class("app-window"); get_style_context()->add_class("app-popup"); get_style_context()->add_class("user-profile-window"); m_main.get_style_context()->add_class("profile-main-container"); m_avatar.get_style_context()->add_class("profile-avatar"); - m_username.get_style_context()->add_class("profile-username"); + m_displayname.get_style_context()->add_class("profile-username"); + m_username.get_style_context()->add_class("profile-username-nondisplay"); m_switcher.get_style_context()->add_class("profile-switcher"); m_stack.get_style_context()->add_class("profile-stack"); m_badges.get_style_context()->add_class("profile-badges"); @@ -31,8 +33,8 @@ ProfileWindow::ProfileWindow(Snowflake user_id) m_scroll.set_vexpand(true); m_scroll.set_propagate_natural_height(true); - if (user.HasAvatar()) - AddPointerCursor(m_avatar_ev); + if (user.HasAvatar()) AddPointerCursor(m_avatar_ev); + m_avatar_ev.signal_button_release_event().connect([user](GdkEventButton *event) -> bool { if (event->type == GDK_BUTTON_RELEASE && event->button == GDK_BUTTON_PRIMARY) { if (user.HasAnimatedAvatar()) @@ -62,7 +64,8 @@ ProfileWindow::ProfileWindow(Snowflake user_id) img.LoadFromURL(user.GetAvatarURL("png", "64"), sigc::track_obj(cb, *this)); } - m_username.set_markup(user.GetEscapedString()); + m_displayname.set_markup(user.GetEscapedName()); + m_username.set_label(user.GetUsername()); m_switcher.set_stack(m_stack); m_switcher.set_halign(Gtk::ALIGN_START); @@ -79,10 +82,13 @@ ProfileWindow::ProfileWindow(Snowflake user_id) m_upper.set_halign(Gtk::ALIGN_START); m_avatar.set_halign(Gtk::ALIGN_START); + m_displayname.set_halign(Gtk::ALIGN_START); m_username.set_halign(Gtk::ALIGN_START); m_avatar_ev.add(m_avatar); m_upper.add(m_avatar_ev); - m_upper.add(m_username); + m_upper.add(m_name_box); + m_name_box.add(m_displayname); + m_name_box.add(m_username); m_badges_scroll.add(m_badges); m_upper.add(m_badges_scroll); m_main.add(m_upper); diff --git a/src/windows/profilewindow.hpp b/src/windows/profilewindow.hpp index 2052e7b..fbc30e0 100644 --- a/src/windows/profilewindow.hpp +++ b/src/windows/profilewindow.hpp @@ -16,9 +16,11 @@ private: Gtk::Box m_main; Gtk::Box m_upper; Gtk::Box m_badges; + Gtk::Box m_name_box; Gtk::ScrolledWindow m_badges_scroll; Gtk::EventBox m_avatar_ev; Gtk::Image m_avatar; + Gtk::Label m_displayname; Gtk::Label m_username; Gtk::ScrolledWindow m_scroll; Gtk::Stack m_stack; From 6f1bd0014cad37f2f0b43a9b83733d9f65a91406 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 13 Jun 2023 21:28:18 -0400 Subject: [PATCH 06/52] refactor, update user mention handling --- src/components/channels.cpp | 2 +- src/components/chatmessage.cpp | 22 ++++++++++---------- src/components/chatwindow.cpp | 2 +- src/components/friendslist.cpp | 2 +- src/components/memberlist.cpp | 2 +- src/dialogs/friendpicker.cpp | 2 +- src/discord/user.cpp | 16 +++++++-------- src/discord/user.hpp | 24 ++++++++++++++-------- src/misc/chatutil.cpp | 8 ++++---- src/windows/guildsettings/auditlogpane.cpp | 18 ++++++++-------- src/windows/guildsettings/memberspane.cpp | 4 ++-- src/windows/profile/mutualfriendspane.cpp | 2 +- src/windows/profilewindow.cpp | 2 +- 13 files changed, 56 insertions(+), 50 deletions(-) diff --git a/src/components/channels.cpp b/src/components/channels.cpp index 0e97837..15a8348 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -863,7 +863,7 @@ Gtk::TreeModel::iterator ChannelList::CreateVoiceParticipantRow(const UserData & auto row = *m_model->append(parent); row[m_columns.m_type] = RenderType::VoiceParticipant; row[m_columns.m_id] = user.ID; - row[m_columns.m_name] = user.GetEscapedName(); + row[m_columns.m_name] = user.GetDisplayNameEscaped(); const auto voice_state = Abaddon::Get().GetDiscordClient().GetVoiceState(user.ID); if (voice_state.has_value()) { diff --git a/src/components/chatmessage.cpp b/src/components/chatmessage.cpp index 4b2e748..2f3a86d 100644 --- a/src/components/chatmessage.cpp +++ b/src/components/chatmessage.cpp @@ -234,23 +234,23 @@ void ChatMessageItemContainer::UpdateTextComponent(Gtk::TextView *tv) { } break; case MessageType::CHANNEL_NAME_CHANGE: { const auto author = Abaddon::Get().GetDiscordClient().GetUser(data->Author.ID); - b->insert_markup(s, "" + author->GetEscapedBoldName() + " changed the name to " + Glib::Markup::escape_text(data->Content) + ""); + b->insert_markup(s, "" + author->GetDisplayNameEscapedBold() + " changed the name to " + Glib::Markup::escape_text(data->Content) + ""); } break; case MessageType::CHANNEL_ICON_CHANGE: { const auto author = Abaddon::Get().GetDiscordClient().GetUser(data->Author.ID); - b->insert_markup(s, "" + author->GetEscapedBoldName() + " changed the channel icon"); + b->insert_markup(s, "" + author->GetDisplayNameEscapedBold() + " changed the channel icon"); } break; case MessageType::USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1: case MessageType::USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2: case MessageType::USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3: { const auto author = Abaddon::Get().GetDiscordClient().GetUser(data->Author.ID); const auto guild = Abaddon::Get().GetDiscordClient().GetGuild(*data->GuildID); - b->insert_markup(s, "" + author->GetEscapedBoldName() + " just boosted the server " + Glib::Markup::escape_text(data->Content) + " times! " + + b->insert_markup(s, "" + author->GetDisplayNameEscapedBold() + " just boosted the server " + Glib::Markup::escape_text(data->Content) + " times! " + Glib::Markup::escape_text(guild->Name) + " has achieved Level " + std::to_string(static_cast(data->Type) - 8) + "!"); // oo cheeky me !!! } break; case MessageType::CHANNEL_FOLLOW_ADD: { const auto author = Abaddon::Get().GetDiscordClient().GetUser(data->Author.ID); - b->insert_markup(s, "" + author->GetEscapedBoldName() + " has added " + Glib::Markup::escape_text(data->Content) + " to this channel. Its most important updates will show up here."); + b->insert_markup(s, "" + author->GetDisplayNameEscapedBold() + " has added " + Glib::Markup::escape_text(data->Content) + " to this channel. Its most important updates will show up here."); } break; case MessageType::CALL: { b->insert_markup(s, "[started a call]"); @@ -270,13 +270,13 @@ void ChatMessageItemContainer::UpdateTextComponent(Gtk::TextView *tv) { case MessageType::THREAD_CREATED: { const auto author = Abaddon::Get().GetDiscordClient().GetUser(data->Author.ID); if (data->MessageReference.has_value() && data->MessageReference->ChannelID.has_value()) { - auto iter = b->insert_markup(s, "" + author->GetEscapedBoldName() + " started a thread: "); + auto iter = b->insert_markup(s, "" + author->GetDisplayNameEscapedBold() + " started a thread: "); auto tag = b->create_tag(); tag->property_weight() = Pango::WEIGHT_BOLD; m_channel_tagmap[tag] = *data->MessageReference->ChannelID; b->insert_with_tag(iter, data->Content, tag); } else { - b->insert_markup(s, "" + author->GetEscapedBoldName() + " started a thread: " + Glib::Markup::escape_text(data->Content) + ""); + b->insert_markup(s, "" + author->GetDisplayNameEscapedBold() + " started a thread: " + Glib::Markup::escape_text(data->Content) + ""); } } break; default: break; @@ -656,7 +656,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data) if (role.has_value()) { const auto author = discord.GetUser(author_id); if (author.has_value()) { - return "Color) + "\">" + author->GetEscapedString() + ""; + return "Color) + "\">" + author->GetUsernameEscaped() + ""; } } } @@ -664,7 +664,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data) const auto author = discord.GetUser(author_id); if (author.has_value()) { - return author->GetEscapedBoldString(); + return author->GetUsernameEscapedBold(); } return "Unknown User"; @@ -685,7 +685,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data) Glib::Markup::escape_text(data.Interaction->Name) + ""); } else if (const auto user = discord.GetUser(data.Interaction->User.ID); user.has_value()) { - lbl->set_markup(user->GetEscapedBoldString()); + lbl->set_markup(user->GetUsernameEscapedBold()); } else { lbl->set_markup("Unknown User"); } @@ -1043,7 +1043,7 @@ void ChatMessageHeader::UpdateName() { else m_author.set_markup("" + name + ""); } else - m_author.set_markup("" + user->GetEscapedName() + ""); + m_author.set_markup("" + user->GetDisplayNameEscaped() + ""); } std::vector ChatMessageHeader::GetChildContent() { @@ -1069,7 +1069,7 @@ Glib::ustring ChatMessageHeader::GetEscapedDisplayName(const UserData &user, con if (member.has_value() && !member->Nickname.empty()) return Glib::Markup::escape_text(member->Nickname); else - return Glib::Markup::escape_text(user.GetEscapedName()); + return Glib::Markup::escape_text(user.GetDisplayNameEscaped()); } bool ChatMessageHeader::on_author_button_press(GdkEventButton *ev) { diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index 1565d0c..8f59944 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -301,7 +301,7 @@ void ChatWindow::StartReplying(Snowflake message_id) { m_is_replying = true; m_input->StartReplying(); if (author.has_value()) - m_input_indicator->SetCustomMarkup("Replying to " + author->GetEscapedBoldString()); + m_input_indicator->SetCustomMarkup("Replying to " + author->GetUsernameEscapedBold()); else m_input_indicator->SetCustomMarkup("Replying..."); } diff --git a/src/components/friendslist.cpp b/src/components/friendslist.cpp index d8a566f..5839b65 100644 --- a/src/components/friendslist.cpp +++ b/src/components/friendslist.cpp @@ -265,7 +265,7 @@ FriendsListFriendRow::FriendsListFriendRow(RelationshipType type, const UserData img->SetURL(data.GetAvatarURL("png", "32")); } - namelbl->set_markup(data.GetEscapedBoldName()); + namelbl->set_markup(data.GetDisplayNameEscapedBold()); UpdatePresenceLabel(); diff --git a/src/components/memberlist.cpp b/src/components/memberlist.cpp index a477832..9450548 100644 --- a/src/components/memberlist.cpp +++ b/src/components/memberlist.cpp @@ -40,7 +40,7 @@ MemberListUserRow::MemberListUserRow(const std::optional &guild, cons // todo remove after migration complete std::string display; if (data.IsPomelo()) { - display = data.GetName(); + display = data.GetDisplayName(); } else { display = data.Username; if (Abaddon::Get().GetSettings().ShowMemberListDiscriminators) { diff --git a/src/dialogs/friendpicker.cpp b/src/dialogs/friendpicker.cpp index c70a9e9..873ddbd 100644 --- a/src/dialogs/friendpicker.cpp +++ b/src/dialogs/friendpicker.cpp @@ -62,7 +62,7 @@ FriendPickerDialogItem::FriendPickerDialogItem(Snowflake user_id) , m_layout(Gtk::ORIENTATION_HORIZONTAL) { auto user = *Abaddon::Get().GetDiscordClient().GetUser(user_id); - m_name.set_markup(user.GetEscapedBoldString()); + m_name.set_markup(user.GetUsernameEscapedBold()); m_name.set_single_line_mode(true); m_avatar.property_pixbuf() = Abaddon::Get().GetImageManager().GetPlaceholder(32); diff --git a/src/discord/user.cpp b/src/discord/user.cpp index 06073ad..38157e7 100644 --- a/src/discord/user.cpp +++ b/src/discord/user.cpp @@ -81,7 +81,7 @@ std::string UserData::GetMention() const { return "<@" + std::to_string(ID) + ">"; } -std::string UserData::GetName() const { +std::string UserData::GetDisplayName() const { if (IsPomelo() && GlobalName.has_value()) { return *GlobalName; } @@ -97,19 +97,19 @@ std::string UserData::GetUsername() const { return Username + "#" + Discriminator; } -std::string UserData::GetEscapedName() const { - return Glib::Markup::escape_text(GetName()); +std::string UserData::GetDisplayNameEscaped() const { + return Glib::Markup::escape_text(GetDisplayName()); } -std::string UserData::GetEscapedBoldName() const { - return "" + Glib::Markup::escape_text(GetName()) + ""; +std::string UserData::GetDisplayNameEscapedBold() const { + return "" + Glib::Markup::escape_text(GetDisplayName()) + ""; } -std::string UserData::GetEscapedString() const { +std::string UserData::GetUsernameEscaped() const { if (IsPomelo()) { - return GetEscapedName(); + return GetDisplayNameEscaped(); } - return Glib::Markup::escape_text(GetName()) + "#" + Discriminator; + return Glib::Markup::escape_text(GetDisplayName()) + "#" + Discriminator; } void from_json(const nlohmann::json &j, UserData &m) { diff --git a/src/discord/user.hpp b/src/discord/user.hpp index 05acd05..d2c1002 100644 --- a/src/discord/user.hpp +++ b/src/discord/user.hpp @@ -79,16 +79,22 @@ struct UserData { [[nodiscard]] std::string GetDefaultAvatarURL() const; [[nodiscard]] Snowflake GetHoistedRole(Snowflake guild_id, bool with_color = false) const; [[nodiscard]] std::string GetMention() const; - [[nodiscard]] std::string GetName() const; + [[nodiscard]] std::string GetDisplayName() const; + [[nodiscard]] std::string GetDisplayNameEscaped() const; + [[nodiscard]] std::string GetDisplayNameEscapedBold() const; [[nodiscard]] std::string GetUsername() const; - [[nodiscard]] std::string GetEscapedName() const; - [[nodiscard]] std::string GetEscapedBoldName() const; - [[nodiscard]] std::string GetEscapedString() const; + [[nodiscard]] std::string GetUsernameEscaped() const; template - [[nodiscard]] inline std::string GetEscapedBoldString() const { - if constexpr (with_at) - return "@" + Glib::Markup::escape_text(Username) + "#" + Discriminator; - else - return "" + Glib::Markup::escape_text(Username) + "#" + Discriminator; + [[nodiscard]] inline std::string GetUsernameEscapedBold() const { + // stupid microoptimization (nanooptimization) that shouldnt exist + if constexpr (with_at) { + std::string r = "@" + Glib::Markup::escape_text(Username) + ""; + if (!IsPomelo()) r += "#" + Discriminator; + return r; + } else { + std::string r = "" + Glib::Markup::escape_text(Username) + ""; + if (!IsPomelo()) r += "#" + Discriminator; + return r; + } } }; diff --git a/src/misc/chatutil.cpp b/src/misc/chatutil.cpp index a87df00..b5917ca 100644 --- a/src/misc/chatutil.cpp +++ b/src/misc/chatutil.cpp @@ -74,17 +74,17 @@ void HandleUserMentions(const Glib::RefPtr &buf, Snowflake chan if (channel->Type == ChannelType::DM || channel->Type == ChannelType::GROUP_DM || !channel->GuildID.has_value() || plain) { if (plain) { - replacement = "@" + user->Username + "#" + user->Discriminator; + replacement = "@" + user->GetUsername(); } else { - replacement = user->GetEscapedBoldString(); + replacement = user->GetUsernameEscapedBold(); } } else { const auto role_id = user->GetHoistedRole(*channel->GuildID, true); const auto role = discord.GetRole(role_id); if (!role.has_value()) - replacement = user->GetEscapedBoldString(); + replacement = user->GetUsernameEscapedBold(); else - replacement = "Color) + "\">" + user->GetEscapedBoldString() + ""; + replacement = "Color) + "\">" + user->GetUsernameEscapedBold() + ""; } // regex returns byte positions and theres no straightforward way in the c++ bindings to deal with that :( diff --git a/src/windows/guildsettings/auditlogpane.cpp b/src/windows/guildsettings/auditlogpane.cpp index 53bfd15..7402c55 100644 --- a/src/windows/guildsettings/auditlogpane.cpp +++ b/src/windows/guildsettings/auditlogpane.cpp @@ -38,7 +38,7 @@ void GuildSettingsAuditLogPane::OnAuditLogFetch(const AuditLogData &data) { Glib::ustring user_markup = "Unknown User"; if (entry.UserID.has_value()) { if (auto user = discord.GetUser(*entry.UserID); user.has_value()) - user_markup = discord.GetUser(*entry.UserID)->GetEscapedBoldString(); + user_markup = discord.GetUser(*entry.UserID)->GetUsernameEscapedBold(); } // spaghetti moment @@ -177,7 +177,7 @@ void GuildSettingsAuditLogPane::OnAuditLogFetch(const AuditLogData &data) { const auto target_user = discord.GetUser(entry.TargetID); markup = user_markup + " kicked " + - target_user->GetEscapedString() + + target_user->GetUsernameEscaped() + ""; } break; case AuditLogActionType::MEMBER_PRUNE: { @@ -193,21 +193,21 @@ void GuildSettingsAuditLogPane::OnAuditLogFetch(const AuditLogData &data) { const auto target_user = discord.GetUser(entry.TargetID); markup = user_markup + " banned " + - target_user->GetEscapedString() + + target_user->GetUsernameEscaped() + ""; } break; case AuditLogActionType::MEMBER_BAN_REMOVE: { const auto target_user = discord.GetUser(entry.TargetID); markup = user_markup + " removed the ban for " + - target_user->GetEscapedString() + + target_user->GetUsernameEscaped() + ""; } break; case AuditLogActionType::MEMBER_UPDATE: { const auto target_user = discord.GetUser(entry.TargetID); markup = user_markup + " updated " + - target_user->GetEscapedString() + + target_user->GetUsernameEscaped() + ""; if (entry.Changes.has_value()) for (const auto &change : *entry.Changes) { @@ -227,7 +227,7 @@ void GuildSettingsAuditLogPane::OnAuditLogFetch(const AuditLogData &data) { const auto target_user = discord.GetUser(entry.TargetID); markup = user_markup + " updated roles for " + - target_user->GetEscapedString() + ""; + target_user->GetUsernameEscaped() + ""; if (entry.Changes.has_value()) for (const auto &change : *entry.Changes) { if (change.Key == "$remove" && change.NewValue.has_value()) { @@ -262,7 +262,7 @@ void GuildSettingsAuditLogPane::OnAuditLogFetch(const AuditLogData &data) { const auto target_user = discord.GetUser(entry.TargetID); markup = user_markup + " added " + - target_user->GetEscapedString() + + target_user->GetUsernameEscaped() + " to the server"; } break; case AuditLogActionType::ROLE_CREATE: { @@ -450,14 +450,14 @@ void GuildSettingsAuditLogPane::OnAuditLogFetch(const AuditLogData &data) { const auto target_user = discord.GetUser(entry.TargetID); markup = user_markup + " pinned a message by " + - target_user->GetEscapedString() + + target_user->GetUsernameEscaped() + ""; } break; case AuditLogActionType::MESSAGE_UNPIN: { const auto target_user = discord.GetUser(entry.TargetID); markup = user_markup + " unpinned a message by " + - target_user->GetEscapedString() + + target_user->GetUsernameEscaped() + ""; } break; case AuditLogActionType::STAGE_INSTANCE_CREATE: { diff --git a/src/windows/guildsettings/memberspane.cpp b/src/windows/guildsettings/memberspane.cpp index 64c28cb..ab4ec1a 100644 --- a/src/windows/guildsettings/memberspane.cpp +++ b/src/windows/guildsettings/memberspane.cpp @@ -150,9 +150,9 @@ void GuildSettingsMembersListItem::UpdateColor() { const auto user = *discord.GetUser(UserID); if (auto color_id = discord.GetMemberHoistedRole(GuildID, UserID, true); color_id.IsValid()) { auto role = *discord.GetRole(color_id); - m_name.set_markup("" + user.GetEscapedBoldString() + ""); + m_name.set_markup("" + user.GetUsernameEscapedBold() + ""); } else - m_name.set_markup(user.GetEscapedBoldString()); + m_name.set_markup(user.GetUsernameEscapedBold()); } GuildSettingsMembersPaneInfo::GuildSettingsMembersPaneInfo(Snowflake guild_id) diff --git a/src/windows/profile/mutualfriendspane.cpp b/src/windows/profile/mutualfriendspane.cpp index e9f465b..2983fd5 100644 --- a/src/windows/profile/mutualfriendspane.cpp +++ b/src/windows/profile/mutualfriendspane.cpp @@ -22,7 +22,7 @@ MutualFriendItem::MutualFriendItem(const UserData &user) img.LoadFromURL(user.GetAvatarURL("png", "32"), sigc::track_obj(cb, *this)); } - m_name.set_markup(user.GetEscapedBoldString()); + m_name.set_markup(user.GetUsernameEscapedBold()); m_name.set_valign(Gtk::ALIGN_CENTER); add(m_avatar); diff --git a/src/windows/profilewindow.cpp b/src/windows/profilewindow.cpp index 1469e49..ed8cd8a 100644 --- a/src/windows/profilewindow.cpp +++ b/src/windows/profilewindow.cpp @@ -64,7 +64,7 @@ ProfileWindow::ProfileWindow(Snowflake user_id) img.LoadFromURL(user.GetAvatarURL("png", "64"), sigc::track_obj(cb, *this)); } - m_displayname.set_markup(user.GetEscapedName()); + m_displayname.set_markup(user.GetDisplayNameEscaped()); m_username.set_label(user.GetUsername()); m_switcher.set_stack(m_stack); From 8ec96944028a885f1af3ee8988b29b48ee2a07f7 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 13 Jun 2023 21:34:29 -0400 Subject: [PATCH 07/52] use display name in replies --- src/components/chatmessage.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/chatmessage.cpp b/src/components/chatmessage.cpp index 2f3a86d..9e01cf0 100644 --- a/src/components/chatmessage.cpp +++ b/src/components/chatmessage.cpp @@ -656,7 +656,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data) if (role.has_value()) { const auto author = discord.GetUser(author_id); if (author.has_value()) { - return "Color) + "\">" + author->GetUsernameEscaped() + ""; + return "Color) + "\">" + author->GetDisplayNameEscaped() + ""; } } } @@ -664,7 +664,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data) const auto author = discord.GetUser(author_id); if (author.has_value()) { - return author->GetUsernameEscapedBold(); + return author->GetDisplayNameEscapedBold(); } return "Unknown User"; @@ -685,7 +685,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data) Glib::Markup::escape_text(data.Interaction->Name) + ""); } else if (const auto user = discord.GetUser(data.Interaction->User.ID); user.has_value()) { - lbl->set_markup(user->GetUsernameEscapedBold()); + lbl->set_markup(user->GetDisplayNameEscapedBold()); } else { lbl->set_markup("Unknown User"); } From c98d2c96b4f3544481ca0f7ccdd3b309b0fabd83 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 13 Jun 2023 21:48:57 -0400 Subject: [PATCH 08/52] remove dumb template stuff --- src/components/chatwindow.cpp | 2 +- src/dialogs/friendpicker.cpp | 2 +- src/discord/user.cpp | 35 +++++++++++++++------- src/discord/user.hpp | 15 ++-------- src/misc/chatutil.cpp | 6 ++-- src/windows/guildsettings/auditlogpane.cpp | 2 +- src/windows/guildsettings/memberspane.cpp | 4 +-- src/windows/profile/mutualfriendspane.cpp | 2 +- 8 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index 8f59944..e51d491 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -301,7 +301,7 @@ void ChatWindow::StartReplying(Snowflake message_id) { m_is_replying = true; m_input->StartReplying(); if (author.has_value()) - m_input_indicator->SetCustomMarkup("Replying to " + author->GetUsernameEscapedBold()); + m_input_indicator->SetCustomMarkup("Replying to " + author->GetUsernameEscapedBold()); else m_input_indicator->SetCustomMarkup("Replying..."); } diff --git a/src/dialogs/friendpicker.cpp b/src/dialogs/friendpicker.cpp index 873ddbd..fe52b77 100644 --- a/src/dialogs/friendpicker.cpp +++ b/src/dialogs/friendpicker.cpp @@ -62,7 +62,7 @@ FriendPickerDialogItem::FriendPickerDialogItem(Snowflake user_id) , m_layout(Gtk::ORIENTATION_HORIZONTAL) { auto user = *Abaddon::Get().GetDiscordClient().GetUser(user_id); - m_name.set_markup(user.GetUsernameEscapedBold()); + m_name.set_markup(user.GetUsernameEscapedBold()); m_name.set_single_line_mode(true); m_avatar.property_pixbuf() = Abaddon::Get().GetImageManager().GetPlaceholder(32); diff --git a/src/discord/user.cpp b/src/discord/user.cpp index 38157e7..1d5d15a 100644 --- a/src/discord/user.cpp +++ b/src/discord/user.cpp @@ -89,14 +89,6 @@ std::string UserData::GetDisplayName() const { return Username; } -std::string UserData::GetUsername() const { - if (IsPomelo()) { - return Username; - } - - return Username + "#" + Discriminator; -} - std::string UserData::GetDisplayNameEscaped() const { return Glib::Markup::escape_text(GetDisplayName()); } @@ -105,11 +97,34 @@ std::string UserData::GetDisplayNameEscapedBold() const { return "" + Glib::Markup::escape_text(GetDisplayName()) + ""; } +std::string UserData::GetUsername() const { + if (IsPomelo()) { + return Username; + } + + return Username + "#" + Discriminator; +} + std::string UserData::GetUsernameEscaped() const { if (IsPomelo()) { - return GetDisplayNameEscaped(); + return Glib::Markup::escape_text(Username); } - return Glib::Markup::escape_text(GetDisplayName()) + "#" + Discriminator; + + return Glib::Markup::escape_text(Username) + "#" + Discriminator; +} + +std::string UserData::GetUsernameEscapedBold() const { + if (IsPomelo()) { + return "" + Glib::Markup::escape_text(Username) + ""; + } + return "" + Glib::Markup::escape_text(Username) + "#" + Discriminator; +} + +std::string UserData::GetUsernameEscapedBoldAt() const { + if (IsPomelo()) { + return "@" + Glib::Markup::escape_text(Username) + ""; + } + return "@" + Glib::Markup::escape_text(Username) + "#" + Discriminator; } void from_json(const nlohmann::json &j, UserData &m) { diff --git a/src/discord/user.hpp b/src/discord/user.hpp index d2c1002..19a0bf9 100644 --- a/src/discord/user.hpp +++ b/src/discord/user.hpp @@ -84,17 +84,6 @@ struct UserData { [[nodiscard]] std::string GetDisplayNameEscapedBold() const; [[nodiscard]] std::string GetUsername() const; [[nodiscard]] std::string GetUsernameEscaped() const; - template - [[nodiscard]] inline std::string GetUsernameEscapedBold() const { - // stupid microoptimization (nanooptimization) that shouldnt exist - if constexpr (with_at) { - std::string r = "@" + Glib::Markup::escape_text(Username) + ""; - if (!IsPomelo()) r += "#" + Discriminator; - return r; - } else { - std::string r = "" + Glib::Markup::escape_text(Username) + ""; - if (!IsPomelo()) r += "#" + Discriminator; - return r; - } - } + [[nodiscard]] std::string GetUsernameEscapedBold() const; + [[nodiscard]] std::string GetUsernameEscapedBoldAt() const; }; diff --git a/src/misc/chatutil.cpp b/src/misc/chatutil.cpp index b5917ca..e61d32b 100644 --- a/src/misc/chatutil.cpp +++ b/src/misc/chatutil.cpp @@ -76,15 +76,15 @@ void HandleUserMentions(const Glib::RefPtr &buf, Snowflake chan if (plain) { replacement = "@" + user->GetUsername(); } else { - replacement = user->GetUsernameEscapedBold(); + replacement = user->GetUsernameEscapedBoldAt(); } } else { const auto role_id = user->GetHoistedRole(*channel->GuildID, true); const auto role = discord.GetRole(role_id); if (!role.has_value()) - replacement = user->GetUsernameEscapedBold(); + replacement = user->GetUsernameEscapedBoldAt(); else - replacement = "Color) + "\">" + user->GetUsernameEscapedBold() + ""; + replacement = "Color) + "\">" + user->GetUsernameEscapedBoldAt() + ""; } // regex returns byte positions and theres no straightforward way in the c++ bindings to deal with that :( diff --git a/src/windows/guildsettings/auditlogpane.cpp b/src/windows/guildsettings/auditlogpane.cpp index 7402c55..f2f4a17 100644 --- a/src/windows/guildsettings/auditlogpane.cpp +++ b/src/windows/guildsettings/auditlogpane.cpp @@ -38,7 +38,7 @@ void GuildSettingsAuditLogPane::OnAuditLogFetch(const AuditLogData &data) { Glib::ustring user_markup = "Unknown User"; if (entry.UserID.has_value()) { if (auto user = discord.GetUser(*entry.UserID); user.has_value()) - user_markup = discord.GetUser(*entry.UserID)->GetUsernameEscapedBold(); + user_markup = discord.GetUser(*entry.UserID)->GetUsernameEscapedBold(); } // spaghetti moment diff --git a/src/windows/guildsettings/memberspane.cpp b/src/windows/guildsettings/memberspane.cpp index ab4ec1a..c6fb7c2 100644 --- a/src/windows/guildsettings/memberspane.cpp +++ b/src/windows/guildsettings/memberspane.cpp @@ -150,9 +150,9 @@ void GuildSettingsMembersListItem::UpdateColor() { const auto user = *discord.GetUser(UserID); if (auto color_id = discord.GetMemberHoistedRole(GuildID, UserID, true); color_id.IsValid()) { auto role = *discord.GetRole(color_id); - m_name.set_markup("" + user.GetUsernameEscapedBold() + ""); + m_name.set_markup("" + user.GetUsernameEscapedBold() + ""); } else - m_name.set_markup(user.GetUsernameEscapedBold()); + m_name.set_markup(user.GetUsernameEscapedBold()); } GuildSettingsMembersPaneInfo::GuildSettingsMembersPaneInfo(Snowflake guild_id) diff --git a/src/windows/profile/mutualfriendspane.cpp b/src/windows/profile/mutualfriendspane.cpp index 2983fd5..75fb4a6 100644 --- a/src/windows/profile/mutualfriendspane.cpp +++ b/src/windows/profile/mutualfriendspane.cpp @@ -22,7 +22,7 @@ MutualFriendItem::MutualFriendItem(const UserData &user) img.LoadFromURL(user.GetAvatarURL("png", "32"), sigc::track_obj(cb, *this)); } - m_name.set_markup(user.GetUsernameEscapedBold()); + m_name.set_markup(user.GetUsernameEscapedBold()); m_name.set_valign(Gtk::ALIGN_CENTER); add(m_avatar); From 29a049e62a6a701c7e6290a643f709a35ba5f4cf Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 13 Jun 2023 22:15:48 -0400 Subject: [PATCH 09/52] handle nickname in reply component --- src/components/chatmessage.cpp | 4 ++-- src/discord/user.cpp | 16 ++++++++++++++++ src/discord/user.hpp | 3 +++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/components/chatmessage.cpp b/src/components/chatmessage.cpp index 9e01cf0..12f8c33 100644 --- a/src/components/chatmessage.cpp +++ b/src/components/chatmessage.cpp @@ -656,7 +656,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data) if (role.has_value()) { const auto author = discord.GetUser(author_id); if (author.has_value()) { - return "Color) + "\">" + author->GetDisplayNameEscaped() + ""; + return "Color) + "\">" + author->GetDisplayNameEscaped(guild_id) + ""; } } } @@ -664,7 +664,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data) const auto author = discord.GetUser(author_id); if (author.has_value()) { - return author->GetDisplayNameEscapedBold(); + return author->GetDisplayNameEscapedBold(guild_id); } return "Unknown User"; diff --git a/src/discord/user.cpp b/src/discord/user.cpp index 1d5d15a..0f89fe2 100644 --- a/src/discord/user.cpp +++ b/src/discord/user.cpp @@ -89,14 +89,30 @@ std::string UserData::GetDisplayName() const { return Username; } +std::string UserData::GetDisplayName(Snowflake guild_id) const { + const auto member = Abaddon::Get().GetDiscordClient().GetMember(ID, guild_id); + if (member.has_value() && !member->Nickname.empty()) { + return member->Nickname; + } + return GetDisplayName(); +} + std::string UserData::GetDisplayNameEscaped() const { return Glib::Markup::escape_text(GetDisplayName()); } +std::string UserData::GetDisplayNameEscaped(Snowflake guild_id) const { + return Glib::Markup::escape_text(GetDisplayName(guild_id)); +} + std::string UserData::GetDisplayNameEscapedBold() const { return "" + Glib::Markup::escape_text(GetDisplayName()) + ""; } +std::string UserData::GetDisplayNameEscapedBold(Snowflake guild_id) const { + return "" + Glib::Markup::escape_text(GetDisplayName(guild_id)) + ""; +} + std::string UserData::GetUsername() const { if (IsPomelo()) { return Username; diff --git a/src/discord/user.hpp b/src/discord/user.hpp index 19a0bf9..29de62a 100644 --- a/src/discord/user.hpp +++ b/src/discord/user.hpp @@ -80,8 +80,11 @@ struct UserData { [[nodiscard]] Snowflake GetHoistedRole(Snowflake guild_id, bool with_color = false) const; [[nodiscard]] std::string GetMention() const; [[nodiscard]] std::string GetDisplayName() const; + [[nodiscard]] std::string GetDisplayName(Snowflake guild_id) const; [[nodiscard]] std::string GetDisplayNameEscaped() const; + [[nodiscard]] std::string GetDisplayNameEscaped(Snowflake guild_id) const; [[nodiscard]] std::string GetDisplayNameEscapedBold() const; + [[nodiscard]] std::string GetDisplayNameEscapedBold(Snowflake guild_id) const; [[nodiscard]] std::string GetUsername() const; [[nodiscard]] std::string GetUsernameEscaped() const; [[nodiscard]] std::string GetUsernameEscapedBold() const; From e097342f5558aa4b0d313f0cde707d56b2f6df58 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 13 Jun 2023 22:21:06 -0400 Subject: [PATCH 10/52] show nicknames in member list --- src/components/memberlist.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/memberlist.cpp b/src/components/memberlist.cpp index 9450548..975b527 100644 --- a/src/components/memberlist.cpp +++ b/src/components/memberlist.cpp @@ -40,7 +40,7 @@ MemberListUserRow::MemberListUserRow(const std::optional &guild, cons // todo remove after migration complete std::string display; if (data.IsPomelo()) { - display = data.GetDisplayName(); + display = data.GetDisplayName(guild.has_value() ? guild->ID : Snowflake::Invalid); } else { display = data.Username; if (Abaddon::Get().GetSettings().ShowMemberListDiscriminators) { From 5d9f7832d7ce73db75a58db0a3b798f24d147578 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 13 Jun 2023 22:25:25 -0400 Subject: [PATCH 11/52] use display name for DMs in channel list --- src/discord/channel.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/discord/channel.cpp b/src/discord/channel.cpp index 4b1d909..a7102ad 100644 --- a/src/discord/channel.cpp +++ b/src/discord/channel.cpp @@ -147,13 +147,13 @@ std::string ChannelData::GetRecipientsDisplay() const { const auto recipients = GetDMRecipients(); if (Type == ChannelType::DM && !recipients.empty()) { - return recipients[0].Username; + return recipients[0].GetDisplayName(); } Glib::ustring r; for (size_t i = 0; i < recipients.size(); i++) { const auto &recipient = recipients[i]; - r += recipient.Username; + r += recipient.GetDisplayName(); if (i < recipients.size() - 1) { r += ", "; } From 878616e2efaa9a4fa1fa6b5e7dd376eb0dd1c79a Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 14 Jun 2023 01:56:46 -0400 Subject: [PATCH 12/52] use display name in typing indicator --- src/components/chatinputindicator.cpp | 14 ++++++++++---- src/components/chatinputindicator.hpp | 1 + src/discord/user.cpp | 7 +++++++ src/discord/user.hpp | 1 + 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/components/chatinputindicator.cpp b/src/components/chatinputindicator.cpp index 13315c6..0611e71 100644 --- a/src/components/chatinputindicator.cpp +++ b/src/components/chatinputindicator.cpp @@ -54,6 +54,12 @@ void ChatInputIndicator::AddUser(Snowflake channel_id, const UserData &user, int void ChatInputIndicator::SetActiveChannel(Snowflake id) { m_active_channel = id; + const auto channel = Abaddon::Get().GetDiscordClient().GetChannel(id); + if (channel.has_value()) { + m_active_guild = channel->GuildID; + } else { + m_active_guild = std::nullopt; + } ComputeTypingString(); } @@ -105,14 +111,14 @@ void ChatInputIndicator::ComputeTypingString() { if (typers.empty()) { SetTypingString(""); } else if (typers.size() == 1) { - SetTypingString(typers[0].Username + " is typing..."); + SetTypingString(typers[0].GetDisplayName(m_active_guild) + " is typing..."); } else if (typers.size() == 2) { - SetTypingString(typers[0].Username + " and " + typers[1].Username + " are typing..."); + SetTypingString(typers[0].GetDisplayName(m_active_guild) + " and " + typers[1].GetDisplayName(m_active_guild) + " are typing..."); } else if (typers.size() > 2 && typers.size() <= MaxUsersInIndicator) { Glib::ustring str; for (size_t i = 0; i < typers.size() - 1; i++) - str += typers[i].Username + ", "; - SetTypingString(str + "and " + typers[typers.size() - 1].Username + " are typing..."); + str += typers[i].GetDisplayName(m_active_guild) + ", "; + SetTypingString(str + "and " + typers[typers.size() - 1].GetDisplayName(m_active_guild) + " are typing..."); } else { // size() > MaxUsersInIndicator SetTypingString("Several people are typing..."); } diff --git a/src/components/chatinputindicator.hpp b/src/components/chatinputindicator.hpp index 40c966e..5688393 100644 --- a/src/components/chatinputindicator.hpp +++ b/src/components/chatinputindicator.hpp @@ -23,5 +23,6 @@ private: Glib::ustring m_custom_markup; Snowflake m_active_channel; + std::optional m_active_guild; std::unordered_map> m_typers; // channel id -> [user id -> connection] }; diff --git a/src/discord/user.cpp b/src/discord/user.cpp index 0f89fe2..f43284f 100644 --- a/src/discord/user.cpp +++ b/src/discord/user.cpp @@ -97,6 +97,13 @@ std::string UserData::GetDisplayName(Snowflake guild_id) const { return GetDisplayName(); } +std::string UserData::GetDisplayName(const std::optional &guild_id) const { + if (guild_id.has_value()) { + return GetDisplayName(*guild_id); + } + return GetDisplayName(); +} + std::string UserData::GetDisplayNameEscaped() const { return Glib::Markup::escape_text(GetDisplayName()); } diff --git a/src/discord/user.hpp b/src/discord/user.hpp index 29de62a..8b2a2c4 100644 --- a/src/discord/user.hpp +++ b/src/discord/user.hpp @@ -81,6 +81,7 @@ struct UserData { [[nodiscard]] std::string GetMention() const; [[nodiscard]] std::string GetDisplayName() const; [[nodiscard]] std::string GetDisplayName(Snowflake guild_id) const; + [[nodiscard]] std::string GetDisplayName(const std::optional &guild_id) const; [[nodiscard]] std::string GetDisplayNameEscaped() const; [[nodiscard]] std::string GetDisplayNameEscaped(Snowflake guild_id) const; [[nodiscard]] std::string GetDisplayNameEscapedBold() const; From 00dddf410e8aa9df64d323211e9dcdf4309f06a1 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 14 Jun 2023 02:04:39 -0400 Subject: [PATCH 13/52] convert a bunch of .Username accesses to proper access --- src/abaddon.cpp | 4 ++-- src/components/chatmessage.cpp | 6 +++--- src/components/completer.cpp | 3 ++- src/components/friendslist.cpp | 10 +++++----- src/windows/guildsettings/banspane.cpp | 6 +++--- src/windows/guildsettings/emojispane.cpp | 2 +- src/windows/guildsettings/invitespane.cpp | 2 +- src/windows/guildsettings/memberspane.cpp | 2 +- src/windows/voicewindow.cpp | 2 +- 9 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/abaddon.cpp b/src/abaddon.cpp index a1ed343..e22afde 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -952,7 +952,7 @@ 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 + "?"); + dlg.SetConfirmText("Are you sure you want to kick " + user->GetUsername() + "?"); auto response = dlg.run(); if (response == Gtk::RESPONSE_OK) m_discord.KickUser(user_id, guild_id); @@ -962,7 +962,7 @@ 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 + "?"); + dlg.SetConfirmText("Are you sure you want to ban " + user->GetUsername() + "?"); auto response = dlg.run(); if (response == Gtk::RESPONSE_OK) m_discord.BanUser(user_id, guild_id); diff --git a/src/components/chatmessage.cpp b/src/components/chatmessage.cpp index 12f8c33..4fde039 100644 --- a/src/components/chatmessage.cpp +++ b/src/components/chatmessage.cpp @@ -221,16 +221,16 @@ void ChatMessageItemContainer::UpdateTextComponent(Gtk::TextView *tv) { if (data->Mentions.empty()) break; const auto &adder = Abaddon::Get().GetDiscordClient().GetUser(data->Author.ID); const auto &added = data->Mentions[0]; - b->insert_markup(s, "" + adder->Username + " added " + added.Username + ""); + b->insert_markup(s, "" + adder->GetUsername() + " added " + added.GetUsername() + ""); } break; case MessageType::RECIPIENT_REMOVE: { if (data->Mentions.empty()) break; const auto &adder = Abaddon::Get().GetDiscordClient().GetUser(data->Author.ID); const auto &added = data->Mentions[0]; if (adder->ID == added.ID) - b->insert_markup(s, "" + adder->Username + " left"); + b->insert_markup(s, "" + adder->GetUsername() + " left"); else - b->insert_markup(s, "" + adder->Username + " removed " + added.Username + ""); + b->insert_markup(s, "" + adder->GetUsername() + " removed " + added.GetUsername() + ""); } break; case MessageType::CHANNEL_NAME_CHANGE: { const auto author = Abaddon::Get().GetDiscordClient().GetUser(data->Author.ID); diff --git a/src/components/completer.cpp b/src/components/completer.cpp index c6a5a08..f6f0906 100644 --- a/src/components/completer.cpp +++ b/src/components/completer.cpp @@ -117,12 +117,13 @@ void Completer::CompleteMentions(const Glib::ustring &term) { if (id == me) continue; const auto author = discord.GetUser(id); if (!author.has_value()) continue; + // todo improve the predicate here if (!StringContainsCaseless(author->Username, term)) continue; if (i++ > 15) break; auto entry = CreateEntry(author->GetMention()); - entry->SetText(author->Username + "#" + author->Discriminator); + entry->SetText(author->GetUsername()); if (channel_id.IsValid()) { const auto chan = discord.GetChannel(channel_id); diff --git a/src/components/friendslist.cpp b/src/components/friendslist.cpp index 5839b65..99fbafa 100644 --- a/src/components/friendslist.cpp +++ b/src/components/friendslist.cpp @@ -117,16 +117,16 @@ void FriendsList::OnActionRemove(Snowflake id) { Glib::ustring str; switch (*discord.GetRelationship(id)) { case RelationshipType::Blocked: - str = "Are you sure you want to unblock " + user->Username + "#" + user->Discriminator + "?"; + str = "Are you sure you want to unblock " + user->GetUsername() + "?"; break; case RelationshipType::Friend: - str = "Are you sure you want to remove " + user->Username + "#" + user->Discriminator + "?"; + str = "Are you sure you want to remove " + user->GetUsername() + "?"; break; case RelationshipType::PendingIncoming: - str = "Are you sure you want to ignore " + user->Username + "#" + user->Discriminator + "?"; + str = "Are you sure you want to ignore " + user->GetUsername() + "?"; break; case RelationshipType::PendingOutgoing: - str = "Are you sure you want to cancel your request to " + user->Username + "#" + user->Discriminator + "?"; + str = "Are you sure you want to cancel your request to " + user->GetUsername() + "?"; break; default: break; @@ -244,7 +244,7 @@ bool FriendsListAddComponent::OnKeyPress(GdkEventKey *e) { FriendsListFriendRow::FriendsListFriendRow(RelationshipType type, const UserData &data) : ID(data.ID) , Type(type) - , Name(data.Username + "#" + data.Discriminator) + , Name(data.GetUsername()) , Status(Abaddon::Get().GetDiscordClient().GetUserStatus(data.ID)) , m_accept("Accept") { auto *ev = Gtk::manage(new Gtk::EventBox); diff --git a/src/windows/guildsettings/banspane.cpp b/src/windows/guildsettings/banspane.cpp index bfc7331..68a232d 100644 --- a/src/windows/guildsettings/banspane.cpp +++ b/src/windows/guildsettings/banspane.cpp @@ -72,7 +72,7 @@ void GuildSettingsBansPane::OnGuildBanFetch(const BanData &ban) { auto row = *m_model->append(); row[m_columns.m_col_id] = ban.User.ID; if (user.has_value()) - row[m_columns.m_col_user] = user->Username + "#" + user->Discriminator; + row[m_columns.m_col_user] = user->GetUsername(); else row[m_columns.m_col_user] = "<@" + std::to_string(ban.User.ID) + ">"; @@ -84,7 +84,7 @@ void GuildSettingsBansPane::OnGuildBansFetch(const std::vector &bans) { const auto user = Abaddon::Get().GetDiscordClient().GetUser(ban.User.ID); auto row = *m_model->append(); row[m_columns.m_col_id] = user->ID; - row[m_columns.m_col_user] = user->Username + "#" + user->Discriminator; + row[m_columns.m_col_user] = user->GetUsername(); row[m_columns.m_col_reason] = ban.Reason; } } @@ -148,7 +148,7 @@ void GuildSettingsBansPane::OnBanAdd(Snowflake guild_id, Snowflake user_id) { auto user = *discord.GetUser(user_id); auto row = *m_model->append(); row[m_columns.m_col_id] = user_id; - row[m_columns.m_col_user] = user.Username + "#" + user.Discriminator; + row[m_columns.m_col_user] = user.GetUsername(); row[m_columns.m_col_reason] = ""; } } diff --git a/src/windows/guildsettings/emojispane.cpp b/src/windows/guildsettings/emojispane.cpp index c86ea64..702432f 100644 --- a/src/windows/guildsettings/emojispane.cpp +++ b/src/windows/guildsettings/emojispane.cpp @@ -119,7 +119,7 @@ void GuildSettingsEmojisPane::AddEmojiRow(const EmojiData &emoji) { row[m_columns.m_col_pixbuf] = img.GetPlaceholder(32); row[m_columns.m_col_name] = emoji.Name; if (emoji.Creator.has_value()) - row[m_columns.m_col_creator] = emoji.Creator->Username + "#" + emoji.Creator->Discriminator; + row[m_columns.m_col_creator] = emoji.Creator->GetUsername(); if (emoji.IsAnimated.has_value()) row[m_columns.m_col_animated] = *emoji.IsAnimated ? "Yes" : "No"; else diff --git a/src/windows/guildsettings/invitespane.cpp b/src/windows/guildsettings/invitespane.cpp index 4426f6a..a76b3f2 100644 --- a/src/windows/guildsettings/invitespane.cpp +++ b/src/windows/guildsettings/invitespane.cpp @@ -51,7 +51,7 @@ void GuildSettingsInvitesPane::AppendInvite(const InviteData &invite) { auto row = *m_model->append(); row[m_columns.m_col_code] = invite.Code; if (invite.Inviter.has_value()) - row[m_columns.m_col_inviter] = invite.Inviter->Username + "#" + invite.Inviter->Discriminator; + row[m_columns.m_col_inviter] = invite.Inviter->GetUsername(); if (invite.MaxAge.has_value()) { if (*invite.MaxAge == 0) diff --git a/src/windows/guildsettings/memberspane.cpp b/src/windows/guildsettings/memberspane.cpp index c6fb7c2..8c613d0 100644 --- a/src/windows/guildsettings/memberspane.cpp +++ b/src/windows/guildsettings/memberspane.cpp @@ -103,7 +103,7 @@ GuildSettingsMembersListItem::GuildSettingsMembersListItem(const GuildData &guil else m_avatar.SetURL(member.User->GetAvatarURL("png", "32")); - DisplayTerm = member.User->Username + "#" + member.User->Discriminator; + DisplayTerm = member.User->GetUsername(); const auto member_update_cb = [this](Snowflake guild_id, Snowflake user_id) { if (user_id == UserID) diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index 9e2efee..35ba8c7 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -39,7 +39,7 @@ public: auto &discord = Abaddon::Get().GetDiscordClient(); const auto user = discord.GetUser(id); if (user.has_value()) { - m_name.set_text(user->Username); + m_name.set_text(user->GetUsername()); m_avatar.SetURL(user->GetAvatarURL("png", "32")); } else { m_name.set_text("Unknown user"); From 2381eb47535bc9aa74e3886a15222dd13a92a370 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 15 Jun 2023 01:42:25 -0400 Subject: [PATCH 14/52] use default group dm icons if none set --- src/components/channels.cpp | 72 +++++++++++++++++++++++++++---------- src/components/channels.hpp | 1 + src/discord/snowflake.cpp | 4 +++ src/discord/snowflake.hpp | 1 + 4 files changed, 59 insertions(+), 19 deletions(-) diff --git a/src/components/channels.cpp b/src/components/channels.cpp index 0e97837..39dd799 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -1016,19 +1016,7 @@ void ChannelList::AddPrivateChannels() { row[m_columns.m_sort] = static_cast(-(dm->LastMessageID.has_value() ? *dm->LastMessageID : dm_id)); row[m_columns.m_icon] = img.GetPlaceholder(DMIconSize); - if (dm->HasIcon()) { - const auto cb = [this, iter](const Glib::RefPtr &pb) { - if (iter) - (*iter)[m_columns.m_icon] = pb->scale_simple(DMIconSize, DMIconSize, Gdk::INTERP_BILINEAR); - }; - img.LoadFromURL(dm->GetIconURL(), sigc::track_obj(cb, *this)); - } else if (top_recipient.has_value()) { - const auto cb = [this, iter](const Glib::RefPtr &pb) { - if (iter) - (*iter)[m_columns.m_icon] = pb->scale_simple(DMIconSize, DMIconSize, Gdk::INTERP_BILINEAR); - }; - img.LoadFromURL(top_recipient->GetAvatarURL("png", "32"), sigc::track_obj(cb, *this)); - } + SetDMChannelIcon(iter, *dm); } } @@ -1036,11 +1024,6 @@ void ChannelList::UpdateCreateDMChannel(const ChannelData &dm) { auto header_row = m_model->get_iter(m_dm_header); auto &img = Abaddon::Get().GetImageManager(); - std::optional top_recipient; - const auto recipients = dm.GetDMRecipients(); - if (!recipients.empty()) - top_recipient = recipients[0]; - auto iter = m_model->append(header_row->children()); auto row = *iter; row[m_columns.m_type] = RenderType::DM; @@ -1049,12 +1032,63 @@ void ChannelList::UpdateCreateDMChannel(const ChannelData &dm) { row[m_columns.m_sort] = static_cast(-(dm.LastMessageID.has_value() ? *dm.LastMessageID : dm.ID)); row[m_columns.m_icon] = img.GetPlaceholder(DMIconSize); - if (top_recipient.has_value()) { + SetDMChannelIcon(iter, dm); +} + +void ChannelList::SetDMChannelIcon(Gtk::TreeIter iter, const ChannelData &dm) { + auto &img = Abaddon::Get().GetImageManager(); + + std::optional top_recipient; + const auto recipients = dm.GetDMRecipients(); + if (!recipients.empty()) + top_recipient = recipients[0]; + + if (dm.HasIcon()) { + const auto cb = [this, iter](const Glib::RefPtr &pb) { + if (iter) + (*iter)[m_columns.m_icon] = pb->scale_simple(DMIconSize, DMIconSize, Gdk::INTERP_BILINEAR); + }; + img.LoadFromURL(dm.GetIconURL(), sigc::track_obj(cb, *this)); + } else if (dm.Type == ChannelType::DM && top_recipient.has_value()) { const auto cb = [this, iter](const Glib::RefPtr &pb) { if (iter) (*iter)[m_columns.m_icon] = pb->scale_simple(DMIconSize, DMIconSize, Gdk::INTERP_BILINEAR); }; img.LoadFromURL(top_recipient->GetAvatarURL("png", "32"), sigc::track_obj(cb, *this)); + } else { // GROUP_DM + std::string hash; + switch (dm.ID.GetUnixMilliseconds() % 8) { + case 0: + hash = "ee9275c5a437f7dc7f9430ba95f12ebd"; + break; + case 1: + hash = "9baf45aac2a0ec2e2dab288333acb9d9"; + break; + case 2: + hash = "7ba11ffb1900fa2b088cb31324242047"; + break; + case 3: + hash = "f90fca70610c4898bc57b58bce92f587"; + break; + case 4: + hash = "e2779af34b8d9126b77420e5f09213ce"; + break; + case 5: + hash = "c6851bd0b03f1cca5a8c1e720ea6ea17"; + break; + case 6: + hash = "f7e38ac976a2a696161c923502a8345b"; + break; + case 7: + default: + hash = "3cb840d03313467838d658bbec801fcd"; + break; + } + const auto cb = [this, iter](const Glib::RefPtr &pb) { + if (iter) + (*iter)[m_columns.m_icon] = pb->scale_simple(DMIconSize, DMIconSize, Gdk::INTERP_BILINEAR); + }; + img.LoadFromURL("https://discord.com/assets/" + hash + ".png", sigc::track_obj(cb, *this)); } } diff --git a/src/components/channels.hpp b/src/components/channels.hpp index 7a23b3d..d561737 100644 --- a/src/components/channels.hpp +++ b/src/components/channels.hpp @@ -114,6 +114,7 @@ protected: void AddPrivateChannels(); void UpdateCreateDMChannel(const ChannelData &channel); + void SetDMChannelIcon(Gtk::TreeIter iter, const ChannelData &dm); void OnMessageAck(const MessageAckData &data); diff --git a/src/discord/snowflake.cpp b/src/discord/snowflake.cpp index 15dacae..680d4da 100644 --- a/src/discord/snowflake.cpp +++ b/src/discord/snowflake.cpp @@ -60,6 +60,10 @@ Glib::ustring Snowflake::GetLocalTimestamp() const { return tmp.data(); } +uint64_t Snowflake::GetUnixMilliseconds() const noexcept { + return (m_num >> 22) + DiscordEpochSeconds * 1000; +} + void from_json(const nlohmann::json &j, Snowflake &s) { if (j.is_string()) { std::string tmp; diff --git a/src/discord/snowflake.hpp b/src/discord/snowflake.hpp index 2ced46b..68cb5ea 100644 --- a/src/discord/snowflake.hpp +++ b/src/discord/snowflake.hpp @@ -16,6 +16,7 @@ struct Snowflake { [[nodiscard]] bool IsValid() const; [[nodiscard]] Glib::ustring GetLocalTimestamp() const; + [[nodiscard]] uint64_t GetUnixMilliseconds() const noexcept; bool operator==(const Snowflake &s) const noexcept { return m_num == s.m_num; From 5b9a9bbc9ab2796074b38378f9d98d10c422c6d4 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 15 Jun 2023 22:20:14 -0400 Subject: [PATCH 15/52] show voice participants in private channels --- src/components/channels.cpp | 12 +++++++- src/discord/discord.cpp | 61 ++++++++++++++++++++++--------------- src/discord/discord.hpp | 3 ++ src/discord/objects.cpp | 5 +++ src/discord/objects.hpp | 42 ++++++++++++++++--------- 5 files changed, 83 insertions(+), 40 deletions(-) diff --git a/src/components/channels.cpp b/src/components/channels.cpp index 39dd799..6a5c8cc 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -42,7 +42,7 @@ ChannelList::ChannelList() const auto type = row[m_columns.m_type]; // text channels should not be allowed to be collapsed // maybe they should be but it seems a little difficult to handle expansion to permit this - if (type != RenderType::TextChannel) { + if (type != RenderType::TextChannel && type != RenderType::DM) { if (row[m_columns.m_expanded]) { m_view.collapse_row(path); row[m_columns.m_expanded] = false; @@ -527,6 +527,7 @@ void ChannelList::OnThreadListSync(const ThreadListSyncData &data) { #ifdef WITH_VOICE void ChannelList::OnVoiceUserConnect(Snowflake user_id, Snowflake channel_id) { auto parent_iter = GetIteratorForRowFromIDOfType(channel_id, RenderType::VoiceChannel); + if (!parent_iter) parent_iter = GetIteratorForRowFromIDOfType(channel_id, RenderType::DM); if (!parent_iter) return; const auto user = Abaddon::Get().GetDiscordClient().GetUser(user_id); if (!user.has_value()) return; @@ -1015,6 +1016,15 @@ void ChannelList::AddPrivateChannels() { row[m_columns.m_name] = Glib::Markup::escape_text(dm->GetDisplayName()); row[m_columns.m_sort] = static_cast(-(dm->LastMessageID.has_value() ? *dm->LastMessageID : dm_id)); row[m_columns.m_icon] = img.GetPlaceholder(DMIconSize); + row[m_columns.m_expanded] = true; + +#ifdef WITH_VOICE + for (auto user_id : discord.GetUsersInVoiceChannel(dm_id)) { + if (const auto user = discord.GetUser(user_id); user.has_value()) { + CreateVoiceParticipantRow(*user, row->children()); + } + } +#endif SetDMChannelIcon(iter, *dm); } diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 2a25a26..e0a26e8 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1579,6 +1579,9 @@ void DiscordClient::HandleGatewayMessage(std::string str) { case GatewayEvent::VOICE_SERVER_UPDATE: { HandleGatewayVoiceServerUpdate(m); } break; + case GatewayEvent::CALL_CREATE: { + HandleGatewayCallCreate(m); + } break; #endif } } break; @@ -2253,8 +2256,39 @@ void DiscordClient::HandleGatewayGuildMembersChunk(const GatewayMessage &msg) { void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { spdlog::get("discord")->trace("VOICE_STATE_UPDATE"); - VoiceState data = msg.Data; + CheckVoiceState(msg.Data); +} +void DiscordClient::HandleGatewayVoiceServerUpdate(const GatewayMessage &msg) { + spdlog::get("discord")->trace("VOICE_SERVER_UPDATE"); + + VoiceServerUpdateData data = msg.Data; + spdlog::get("discord")->debug("Voice server endpoint: {}", data.Endpoint); + spdlog::get("discord")->debug("Voice token: {}", data.Token); + m_voice.SetEndpoint(data.Endpoint); + m_voice.SetToken(data.Token); + if (data.GuildID.has_value()) { + m_voice.SetServerID(*data.GuildID); + } else if (data.ChannelID.has_value()) { + m_voice.SetServerID(*data.ChannelID); + } else { + spdlog::get("discord")->error("No guild or channel ID in voice server?"); + } + m_voice.SetUserID(m_user_data.ID); + m_voice.Start(); +} + +void DiscordClient::HandleGatewayCallCreate(const GatewayMessage &msg) { + CallCreateData data = msg.Data; + + spdlog::get("discord")->debug("CALL_CREATE: {}", data.ChannelID); + + for (const auto &state : data.VoiceStates) { + CheckVoiceState(state); + } +} + +void DiscordClient::CheckVoiceState(const VoiceState &data) { if (data.UserID == m_user_data.ID) { spdlog::get("discord")->debug("Voice session ID: {}", data.SessionID); m_voice.SetSessionID(data.SessionID); @@ -2292,25 +2326,6 @@ void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { } } } - -void DiscordClient::HandleGatewayVoiceServerUpdate(const GatewayMessage &msg) { - spdlog::get("discord")->trace("VOICE_SERVER_UPDATE"); - - VoiceServerUpdateData data = msg.Data; - spdlog::get("discord")->debug("Voice server endpoint: {}", data.Endpoint); - spdlog::get("discord")->debug("Voice token: {}", data.Token); - m_voice.SetEndpoint(data.Endpoint); - m_voice.SetToken(data.Token); - if (data.GuildID.has_value()) { - m_voice.SetServerID(*data.GuildID); - } else if (data.ChannelID.has_value()) { - m_voice.SetServerID(*data.ChannelID); - } else { - spdlog::get("discord")->error("No guild or channel ID in voice server?"); - } - m_voice.SetUserID(m_user_data.ID); - m_voice.Start(); -} #endif void DiscordClient::HandleGatewayReadySupplemental(const GatewayMessage &msg) { @@ -2556,7 +2571,7 @@ void DiscordClient::HeartbeatThread() { void DiscordClient::SendIdentify() { IdentifyMessage msg; msg.Token = m_token; - msg.Capabilities = 509; // no idea what this is + msg.Capabilities = 4605; // bit 12 is necessary for CALL_CREATE... apparently? need to get this in sync with official client msg.Properties.OS = "Windows"; msg.Properties.Browser = "Chrome"; msg.Properties.Device = ""; @@ -2575,9 +2590,6 @@ void DiscordClient::SendIdentify() { msg.Presence.Since = 0; msg.Presence.IsAFK = false; msg.DoesSupportCompression = false; - msg.ClientState.HighestLastMessageID = "0"; - msg.ClientState.ReadStateVersion = 0; - msg.ClientState.UserGuildSettingsVersion = -1; SetSuperPropertiesFromIdentity(msg); const bool b = m_websocket.GetPrintMessages(); m_websocket.SetPrintMessages(false); @@ -2893,6 +2905,7 @@ void DiscordClient::LoadEventMap() { m_event_map["GUILD_MEMBERS_CHUNK"] = GatewayEvent::GUILD_MEMBERS_CHUNK; m_event_map["VOICE_STATE_UPDATE"] = GatewayEvent::VOICE_STATE_UPDATE; m_event_map["VOICE_SERVER_UPDATE"] = GatewayEvent::VOICE_SERVER_UPDATE; + m_event_map["CALL_CREATE"] = GatewayEvent::CALL_CREATE; } DiscordClient::type_signal_gateway_ready DiscordClient::signal_gateway_ready() { diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 7f7518c..d2435dd 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -291,6 +291,9 @@ private: #ifdef WITH_VOICE void HandleGatewayVoiceStateUpdate(const GatewayMessage &msg); void HandleGatewayVoiceServerUpdate(const GatewayMessage &msg); + void HandleGatewayCallCreate(const GatewayMessage &msg); + + void CheckVoiceState(const VoiceState &data); #endif void HeartbeatThread(); diff --git a/src/discord/objects.cpp b/src/discord/objects.cpp index 86264cc..645a693 100644 --- a/src/discord/objects.cpp +++ b/src/discord/objects.cpp @@ -686,6 +686,11 @@ void from_json(const nlohmann::json &j, VoiceServerUpdateData &m) { JS_ON("guild_id", m.GuildID); JS_ON("channel_id", m.ChannelID); } + +void from_json(const nlohmann::json &j, CallCreateData &m) { + JS_D("channel_id", m.ChannelID); + JS_D("voice_states", m.VoiceStates); +} #endif void from_json(const nlohmann::json &j, VoiceState &m) { diff --git a/src/discord/objects.hpp b/src/discord/objects.hpp index 3ad4037..cad37f1 100644 --- a/src/discord/objects.hpp +++ b/src/discord/objects.hpp @@ -102,6 +102,7 @@ enum class GatewayEvent : int { GUILD_MEMBERS_CHUNK, VOICE_STATE_UPDATE, VOICE_SERVER_UPDATE, + CALL_CREATE, }; enum class GatewayCloseCode : uint16_t { @@ -886,6 +887,23 @@ struct GuildMembersChunkData { friend void from_json(const nlohmann::json &j, GuildMembersChunkData &m); }; +struct VoiceState { + std::optional ChannelID; + bool IsDeafened; + bool IsMuted; + std::optional GuildID; + std::optional Member; + bool IsSelfDeafened; + bool IsSelfMuted; + bool IsSelfVideo; + bool IsSelfStream = false; + std::string SessionID; + bool IsSuppressed; + Snowflake UserID; + + friend void from_json(const nlohmann::json &j, VoiceState &m); +}; + #ifdef WITH_VOICE struct VoiceStateUpdateMessage { std::optional GuildID; @@ -906,21 +924,15 @@ struct VoiceServerUpdateData { friend void from_json(const nlohmann::json &j, VoiceServerUpdateData &m); }; -#endif -struct VoiceState { - std::optional ChannelID; - bool IsDeafened; - bool IsMuted; - std::optional GuildID; - std::optional Member; - bool IsSelfDeafened; - bool IsSelfMuted; - bool IsSelfVideo; - bool IsSelfStream = false; - std::string SessionID; - bool IsSuppressed; - Snowflake UserID; +struct CallCreateData { + Snowflake ChannelID; + std::vector VoiceStates; + // Snowflake MessageID; + // std::string Region; + // std::vector Ringing; + // std::vector EmbeddedActivities; - friend void from_json(const nlohmann::json &j, VoiceState &m); + friend void from_json(const nlohmann::json &j, CallCreateData &m); }; +#endif From a7b1523b455bf7b9c3bc06e03ed5051498db8082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morc=20-=20Richard=20Gr=C3=A1=C4=8Dik?= Date: Sat, 17 Jun 2023 10:02:13 +0200 Subject: [PATCH 16/52] fix clipboard pasting on macOS just checks for __APPLE__ definition and changes the mask accordingly --- src/components/chatinput.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index d19ac4b..28ed1ea 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -41,9 +41,15 @@ bool ChatInputText::ProcessKeyPress(GdkEventKey *event) { return true; } +#ifdef __APPLE__ + if ((event->state & GDK_MOD2_MASK) && event->keyval == GDK_KEY_v) { + return CheckHandleClipboardPaste(); + } +#else if ((event->state & GDK_CONTROL_MASK) && event->keyval == GDK_KEY_v) { return CheckHandleClipboardPaste(); } +#endif if (event->keyval == GDK_KEY_Return) { if (event->state & GDK_SHIFT_MASK) From 734ffc9f715a83372dc88c943b1252dd78da4754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morc=20-=20Richard=20Gr=C3=A1=C4=8Dik?= Date: Sat, 17 Jun 2023 10:29:07 +0200 Subject: [PATCH 17/52] add CoreFoundation, CoreAudio and AudioToolbox to CMakeLists required to make audio working on macOS --- CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index c4fdf7a..de97c32 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -150,6 +150,13 @@ if (ENABLE_VOICE) target_link_libraries(abaddon PkgConfig::libsodium) target_link_libraries(abaddon ${CMAKE_DL_LIBS}) + + if(APPLE) + target_link_libraries(abaddon "-framework CoreFoundation") + target_link_libraries(abaddon "-framework CoreAudio") + target_link_libraries(abaddon "-framework AudioToolbox") + endif() + endif () if (${ENABLE_NOTIFICATION_SOUNDS}) From 418b158d4714b57f4ee7de1ace5c2a950c008cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morc=20-=20Richard=20Gr=C3=A1=C4=8Dik?= Date: Sat, 17 Jun 2023 10:30:01 +0200 Subject: [PATCH 18/52] Add MA_NO_RUNTIME_LINKING for macOS --- src/audio/ma_impl.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/audio/ma_impl.cpp b/src/audio/ma_impl.cpp index a83ddaf..531b24f 100644 --- a/src/audio/ma_impl.cpp +++ b/src/audio/ma_impl.cpp @@ -1,4 +1,7 @@ #ifdef WITH_MINIAUDIO #define MINIAUDIO_IMPLEMENTATION + #ifdef __APPLE__ + #define MA_NO_RUNTIME_LINKING + #endif #include #endif From 7832ab5d2a24cc951ad411649b74defd46afb224 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 17 Jun 2023 16:40:07 -0400 Subject: [PATCH 19/52] add runtime option to hide console on windows --- src/abaddon.cpp | 6 ++++++ src/settings.cpp | 2 ++ src/settings.hpp | 3 +++ 3 files changed, 11 insertions(+) diff --git a/src/abaddon.cpp b/src/abaddon.cpp index a1ed343..2fe2941 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -252,6 +252,12 @@ int Abaddon::StartGTK() { } #endif +#ifdef _WIN32 + if (m_settings.GetSettings().HideConsole) { + ShowWindow(GetConsoleWindow(), SW_HIDE); + } +#endif + // store must be checked before this can be called m_main_window->UpdateComponents(); diff --git a/src/settings.cpp b/src/settings.cpp index 82401f5..0b868da 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -70,6 +70,7 @@ void SettingsManager::ReadSettings() { SMSTR("style", "unreadcolor", UnreadIndicatorColor); SMBOOL("notifications", "enabled", NotificationsEnabled); SMBOOL("notifications", "playsound", NotificationsPlaySound); + SMBOOL("windows", "hideconsole", HideConsole); #ifdef WITH_KEYCHAIN keychain::Error error {}; @@ -153,6 +154,7 @@ void SettingsManager::Close() { SMSTR("style", "unreadcolor", UnreadIndicatorColor); SMBOOL("notifications", "enabled", NotificationsEnabled); SMBOOL("notifications", "playsound", NotificationsPlaySound); + SMBOOL("windows", "hideconsole", HideConsole); #ifdef WITH_KEYCHAIN keychain::Error error {}; diff --git a/src/settings.hpp b/src/settings.hpp index 53f3423..40cb1d3 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -52,6 +52,9 @@ public: bool NotificationsEnabled { true }; #endif bool NotificationsPlaySound { true }; + + // [windows] + bool HideConsole { false }; }; SettingsManager(const std::string &filename); From 19f7d55c8748e4fcd651540e299347689a2070ad Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 21 Jun 2023 02:31:18 -0400 Subject: [PATCH 20/52] show legacy username as tooltip under username --- src/discord/objects.cpp | 1 + src/discord/objects.hpp | 1 + src/windows/profilewindow.cpp | 7 ++++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/discord/objects.cpp b/src/discord/objects.cpp index 86264cc..715ae14 100644 --- a/src/discord/objects.cpp +++ b/src/discord/objects.cpp @@ -463,6 +463,7 @@ void from_json(const nlohmann::json &j, UserProfileData &m) { JS_D("mutual_guilds", m.MutualGuilds); JS_ON("premium_guild_since", m.PremiumGuildSince); JS_ON("premium_since", m.PremiumSince); + JS_ON("legacy_username", m.LegacyUsername); JS_D("user", m.User); } diff --git a/src/discord/objects.hpp b/src/discord/objects.hpp index 3ad4037..4d56417 100644 --- a/src/discord/objects.hpp +++ b/src/discord/objects.hpp @@ -617,6 +617,7 @@ struct UserProfileData { std::vector MutualGuilds; std::optional PremiumGuildSince; // null std::optional PremiumSince; // null + std::optional LegacyUsername; // null UserData User; friend void from_json(const nlohmann::json &j, UserProfileData &m); diff --git a/src/windows/profilewindow.cpp b/src/windows/profilewindow.cpp index ed8cd8a..72996aa 100644 --- a/src/windows/profilewindow.cpp +++ b/src/windows/profilewindow.cpp @@ -103,8 +103,13 @@ void ProfileWindow::OnFetchProfile(const UserProfileData &data) { m_pane_info.SetProfile(data); m_pane_guilds.SetMutualGuilds(data.MutualGuilds); - for (auto child : m_badges.get_children()) + if (data.LegacyUsername.has_value()) { + m_username.set_tooltip_text("Originally known as " + *data.LegacyUsername); + } + + for (auto child : m_badges.get_children()) { delete child; + } if (!data.User.PublicFlags.has_value()) return; const auto x = *data.User.PublicFlags; From 85bc7b95f76cbc27e414c9f9182e51052b259b20 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 22 Jun 2023 01:12:44 -0400 Subject: [PATCH 21/52] allow setting unreasonably high gain apparently packing is still a thing or whatever... someone asked --- res/css/main.css | 5 +++++ src/windows/voicesettingswindow.cpp | 12 ++++++++++++ src/windows/voicesettingswindow.hpp | 6 ++++++ src/windows/voicewindow.cpp | 5 +++++ 4 files changed, 28 insertions(+) diff --git a/res/css/main.css b/res/css/main.css index dcbdf4f..40e1696 100644 --- a/res/css/main.css +++ b/res/css/main.css @@ -382,3 +382,8 @@ .voice-state-server { color: red; } + +spinbutton { + color: @text_color; + margin-top: 10px; +} diff --git a/src/windows/voicesettingswindow.cpp b/src/windows/voicesettingswindow.cpp index c009cbf..3749986 100644 --- a/src/windows/voicesettingswindow.cpp +++ b/src/windows/voicesettingswindow.cpp @@ -110,9 +110,17 @@ VoiceSettingsWindow::VoiceSettingsWindow() } }); + m_gain.set_increments(1.0, 5.0); + m_gain.set_range(0.0, 6969696969.0); + m_gain.set_value(Abaddon::Get().GetAudio().GetCaptureGain() * 100.0); + m_gain.signal_value_changed().connect([this]() { + m_signal_gain.emit(m_gain.get_value() / 100.0); + }); + m_main.add(m_encoding_mode); m_main.add(m_signal); m_main.add(m_bitrate); + m_main.add(m_gain); add(m_main); show_all_children(); @@ -122,4 +130,8 @@ VoiceSettingsWindow::VoiceSettingsWindow() }); } +VoiceSettingsWindow::type_signal_gain VoiceSettingsWindow::signal_gain() { + return m_signal_gain; +} + #endif diff --git a/src/windows/voicesettingswindow.hpp b/src/windows/voicesettingswindow.hpp index cf6b477..9b3498e 100644 --- a/src/windows/voicesettingswindow.hpp +++ b/src/windows/voicesettingswindow.hpp @@ -18,8 +18,14 @@ public: Gtk::ComboBoxText m_encoding_mode; Gtk::ComboBoxText m_signal; Gtk::Scale m_bitrate; + Gtk::SpinButton m_gain; private: + using type_signal_gain = sigc::signal; + type_signal_gain m_signal_gain; + +public: + type_signal_gain signal_gain(); }; #endif diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index 9e2efee..a915da6 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -163,6 +163,11 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_menu_view_sub.append(m_menu_view_settings); m_menu_view_settings.signal_activate().connect([this]() { auto *window = new VoiceSettingsWindow; + const auto cb = [this](double gain) { + m_capture_gain.set_value(gain * 100.0); + m_signal_gain.emit(gain); + }; + window->signal_gain().connect(sigc::track_obj(cb, *this)); window->show(); }); From e26703f1e8369e18423a17db5e0bace769bea643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morc=20-=20Richard=20Gr=C3=A1=C4=8Dik?= Date: Fri, 23 Jun 2023 21:06:18 +0200 Subject: [PATCH 22/52] Update max attachment size for base users --- src/constants.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants.hpp b/src/constants.hpp index 256f85e..5ed123c 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -4,7 +4,7 @@ constexpr static uint64_t SnowflakeSplitDifference = 600; constexpr static int MaxMessagesForChatCull = 50; // this has to be 50 (for now) cuz that magic number is used in a couple other places and i dont feel like replacing them constexpr static int AttachmentItemSize = 120; -constexpr static int BaseAttachmentSizeLimit = 8 * 1024 * 1024; +constexpr static int BaseAttachmentSizeLimit = 25 * 1024 * 1024; constexpr static int NitroClassicAttachmentSizeLimit = 50 * 1024 * 1024; constexpr static int NitroAttachmentSizeLimit = 100 * 1024 * 1024; constexpr static int BoostLevel2AttachmentSizeLimit = 50 * 1024 * 1024; From 5467b61b19d3384f876b03e51595d3c6956b18e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morc=20-=20Richard=20Gr=C3=A1=C4=8Dik?= Date: Fri, 23 Jun 2023 21:31:44 +0200 Subject: [PATCH 23/52] Change timeval from 1000000 microseconds to 1sec --- src/discord/voiceclient.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index 524d930..e9814b6 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -112,8 +112,8 @@ void UDPSocket::ReadThread() { sockaddr_in from; socklen_t addrlen = sizeof(from); - tv.tv_sec = 0; - tv.tv_usec = 1000000; + tv.tv_sec = 1; + tv.tv_usec = 0; fd_set read_fds; FD_ZERO(&read_fds); From 2da021b5ea4b89d744e76c8b13180504916adc32 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 24 Jun 2023 16:48:44 -0400 Subject: [PATCH 24/52] support sending messages with `@silent` prefix --- src/abaddon.cpp | 8 +++++++- src/discord/chatsubmitparams.hpp | 1 + src/discord/discord.cpp | 19 +++++++++++++++---- src/discord/message.hpp | 26 ++++++++++++++++++-------- src/discord/objects.cpp | 1 + src/discord/objects.hpp | 1 + 6 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 2fe2941..ab2223e 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -920,8 +920,14 @@ void Abaddon::ActionChatLoadHistory(Snowflake id) { } void Abaddon::ActionChatInputSubmit(ChatSubmitParams data) { - if (data.Message.substr(0, 7) == "/shrug " || data.Message == "/shrug") + if (data.Message.substr(0, 7) == "/shrug " || data.Message == "/shrug") { data.Message = data.Message.substr(6) + "\xC2\xAF\x5C\x5F\x28\xE3\x83\x84\x29\x5F\x2F\xC2\xAF"; // this is important + } + + if (data.Message.substr(0, 8) == "@silent " || (data.Message.substr(0, 7) == "@silent" && !data.Attachments.empty())) { + data.Silent = true; + data.Message = data.Message.substr(7); + } if (!m_discord.HasChannelPermission(m_discord.GetUserData().ID, data.ChannelID, Permission::VIEW_CHANNEL)) return; diff --git a/src/discord/chatsubmitparams.hpp b/src/discord/chatsubmitparams.hpp index 6199634..24c6f50 100644 --- a/src/discord/chatsubmitparams.hpp +++ b/src/discord/chatsubmitparams.hpp @@ -17,6 +17,7 @@ struct ChatSubmitParams { std::string Filename; }; + bool Silent = false; Snowflake ChannelID; Snowflake InReplyToID; Glib::ustring Message; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index e0a26e8..817aca8 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -461,8 +461,13 @@ void DiscordClient::SendChatMessageNoAttachments(const ChatSubmitParams ¶ms, CreateMessageObject obj; obj.Content = params.Message; obj.Nonce = nonce; - if (params.InReplyToID.IsValid()) + if (params.Silent) { + obj.Flags |= MessageFlags::SUPPRESS_NOTIFICATIONS; + } + + if (params.InReplyToID.IsValid()) { obj.MessageReference.emplace().MessageID = params.InReplyToID; + } m_http.MakePOST("/channels/" + std::to_string(params.ChannelID) + "/messages", nlohmann::json(obj).dump(), @@ -494,8 +499,13 @@ void DiscordClient::SendChatMessageAttachments(const ChatSubmitParams ¶ms, c CreateMessageObject obj; obj.Content = params.Message; obj.Nonce = nonce; - if (params.InReplyToID.IsValid()) + if (params.Silent) { + obj.Flags |= MessageFlags::SUPPRESS_NOTIFICATIONS; + } + + if (params.InReplyToID.IsValid()) { obj.MessageReference.emplace().MessageID = params.InReplyToID; + } auto req = m_http.CreateRequest(http::REQUEST_POST, "/channels/" + std::to_string(params.ChannelID) + "/messages"); m_progress_cb_timer.start(); @@ -545,10 +555,11 @@ void DiscordClient::SendChatMessageAttachments(const ChatSubmitParams ¶ms, c } void DiscordClient::SendChatMessage(const ChatSubmitParams ¶ms, const sigc::slot &callback) { - if (params.Attachments.empty()) + if (params.Attachments.empty()) { SendChatMessageNoAttachments(params, callback); - else + } else { SendChatMessageAttachments(params, callback); + } } void DiscordClient::DeleteMessage(Snowflake channel_id, Snowflake id) { diff --git a/src/discord/message.hpp b/src/discord/message.hpp index df2cb38..5028104 100644 --- a/src/discord/message.hpp +++ b/src/discord/message.hpp @@ -8,6 +8,7 @@ #include "emoji.hpp" #include "member.hpp" #include "interactions.hpp" +#include "misc/bitwise.hpp" enum class MessageType { DEFAULT = 0, // yep @@ -35,14 +36,23 @@ enum class MessageType { enum class MessageFlags { NONE = 0, - CROSSPOSTED = 1 << 0, // this message has been published to subscribed channels (via Channel Following) - IS_CROSSPOST = 1 << 1, // this message originated from a message in another channel (via Channel Following) - SUPPRESS_EMBEDS = 1 << 2, // do not include any embeds when serializing this message - SOURCE_MESSAGE_DELETE = 1 << 3, // the source message for this crosspost has been deleted (via Channel Following) - URGENT = 1 << 4, // this message came from the urgent message system - HAS_THREAD = 1 << 5, // this message has an associated thread, with the same id as the message - EPHEMERAL = 1 << 6, // this message is only visible to the user who invoked the Interaction - LOADING = 1 << 7, // this message is an Interaction Response and the bot is "thinking" + CROSSPOSTED = 1 << 0, // this message has been published to subscribed channels (via Channel Following) + IS_CROSSPOST = 1 << 1, // this message originated from a message in another channel (via Channel Following) + SUPPRESS_EMBEDS = 1 << 2, // do not include any embeds when serializing this message + SOURCE_MESSAGE_DELETE = 1 << 3, // the source message for this crosspost has been deleted (via Channel Following) + URGENT = 1 << 4, // this message came from the urgent message system + HAS_THREAD = 1 << 5, // this message has an associated thread, with the same id as the message + EPHEMERAL = 1 << 6, // this message is only visible to the user who invoked the Interaction + LOADING = 1 << 7, // this message is an Interaction Response and the bot is "thinking" + FAILED_TO_MENTION_SOME_ROLES_IN_THREAD = 1 << 8, // this message failed to mention some roles and add their members to the thread + SHOULD_SHOW_LINK_NOT_DISCORD_WARNING = 1 << 10, // + SUPPRESS_NOTIFICATIONS = 1 << 12, // this message will not trigger push and desktop notifications + IS_VOICE_MESSAGE = 1 << 13, // this message is a voice message +}; + +template<> +struct Bitwise { + static const bool enable = true; }; struct EmbedFooterData { diff --git a/src/discord/objects.cpp b/src/discord/objects.cpp index 645a693..50002f9 100644 --- a/src/discord/objects.cpp +++ b/src/discord/objects.cpp @@ -308,6 +308,7 @@ void to_json(nlohmann::json &j, const HeartbeatMessage &m) { void to_json(nlohmann::json &j, const CreateMessageObject &m) { j["content"] = m.Content; + j["flags"] = m.Flags; JS_IF("message_reference", m.MessageReference); JS_IF("nonce", m.Nonce); } diff --git a/src/discord/objects.hpp b/src/discord/objects.hpp index cad37f1..7f3d05f 100644 --- a/src/discord/objects.hpp +++ b/src/discord/objects.hpp @@ -428,6 +428,7 @@ struct HeartbeatMessage : GatewayMessage { struct CreateMessageObject { std::string Content; + MessageFlags Flags = MessageFlags::NONE; std::optional MessageReference; std::optional Nonce; From ba24e13419bfa6efd08a0d07f049a6981ff4cd28 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 24 Jun 2023 16:57:41 -0400 Subject: [PATCH 25/52] dont notify on silent messages --- src/notifications/notifications.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/notifications/notifications.cpp b/src/notifications/notifications.cpp index 3c88c44..e95c1c0 100644 --- a/src/notifications/notifications.cpp +++ b/src/notifications/notifications.cpp @@ -88,6 +88,8 @@ bool CheckGuildMessage(const Message &message) { void Notifications::CheckMessage(const Message &message) { if (!Abaddon::Get().GetSettings().NotificationsEnabled) return; + // ignore if silent message + if (message.Flags.has_value() && ((*message.Flags & MessageFlags::SUPPRESS_NOTIFICATIONS) == MessageFlags::SUPPRESS_NOTIFICATIONS)) return; // ignore if our status is do not disturb if (IsDND()) return; auto &discord = Abaddon::Get().GetDiscordClient(); From ec6f18ff1204889afd63f08f255d00ae3a1ef12b Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 25 Jun 2023 20:15:25 -0400 Subject: [PATCH 26/52] global_name is optional --- src/discord/user.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/discord/user.cpp b/src/discord/user.cpp index f43284f..45d70e8 100644 --- a/src/discord/user.cpp +++ b/src/discord/user.cpp @@ -170,7 +170,7 @@ void from_json(const nlohmann::json &j, UserData &m) { JS_ON("phone", m.Phone); JS_ON("bio", m.Bio); JS_ON("banner", m.BannerHash); - JS_N("global_name", m.GlobalName); + JS_ON("global_name", m.GlobalName); } void to_json(nlohmann::json &j, const UserData &m) { From 3b206e11216b383fae0e860e0092f71301fd4425 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 30 Jun 2023 20:43:08 -0400 Subject: [PATCH 27/52] remote auth impl. up to QR code display --- .gitmodules | 3 + CMakeLists.txt | 18 +- src/abaddon.cpp | 13 ++ src/abaddon.hpp | 1 + src/discord/websocket.cpp | 5 +- src/remoteauth/remoteauthclient.cpp | 321 ++++++++++++++++++++++++++++ src/remoteauth/remoteauthclient.hpp | 64 ++++++ src/remoteauth/remoteauthdialog.cpp | 79 +++++++ src/remoteauth/remoteauthdialog.hpp | 23 ++ src/remoteauth/ssl.hpp | 31 +++ src/windows/mainwindow.cpp | 11 + src/windows/mainwindow.hpp | 4 + subprojects/qrcodegen | 1 + 13 files changed, 567 insertions(+), 7 deletions(-) create mode 100644 src/remoteauth/remoteauthclient.cpp create mode 100644 src/remoteauth/remoteauthclient.hpp create mode 100644 src/remoteauth/remoteauthdialog.cpp create mode 100644 src/remoteauth/remoteauthdialog.hpp create mode 100644 src/remoteauth/ssl.hpp create mode 160000 subprojects/qrcodegen diff --git a/.gitmodules b/.gitmodules index 27efb8e..aae3862 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "subprojects/miniaudio"] path = subprojects/miniaudio url = https://github.com/mackron/miniaudio +[submodule "subprojects/qrcodegen"] + path = subprojects/qrcodegen + url = https://github.com/nayuki/QR-Code-generator diff --git a/CMakeLists.txt b/CMakeLists.txt index de97c32..b85e6c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,12 @@ target_include_directories(abaddon PUBLIC ${ZLIB_INCLUDE_DIRS}) target_include_directories(abaddon PUBLIC ${SQLite3_INCLUDE_DIRS}) target_include_directories(abaddon PUBLIC ${NLOHMANN_JSON_INCLUDE_DIRS}) +add_library(qrcodegen subprojects/qrcodegen/cpp/qrcodegen.hpp subprojects/qrcodegen/cpp/qrcodegen.cpp) +target_include_directories(qrcodegen PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/subprojects/qrcodegen/cpp") +target_link_libraries(abaddon qrcodegen) + +target_include_directories(abaddon PUBLIC "subprojects/qrcodegen/cpp") + target_precompile_headers(abaddon PRIVATE src/abaddon.hpp src/util.hpp) if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR @@ -151,12 +157,12 @@ if (ENABLE_VOICE) target_link_libraries(abaddon ${CMAKE_DL_LIBS}) - if(APPLE) - target_link_libraries(abaddon "-framework CoreFoundation") - target_link_libraries(abaddon "-framework CoreAudio") - target_link_libraries(abaddon "-framework AudioToolbox") - endif() - + if (APPLE) + target_link_libraries(abaddon "-framework CoreFoundation") + target_link_libraries(abaddon "-framework CoreAudio") + target_link_libraries(abaddon "-framework AudioToolbox") + endif () + endif () if (${ENABLE_NOTIFICATION_SOUNDS}) diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 92cc494..545a3c2 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -21,6 +21,7 @@ #include "windows/voicewindow.hpp" #include "startup.hpp" #include "notifications/notifications.hpp" +#include "remoteauth/remoteauthdialog.hpp" #ifdef WITH_LIBHANDY #include @@ -267,6 +268,7 @@ int Abaddon::StartGTK() { 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_login_qr().connect(sigc::mem_fun(*this, &Abaddon::ActionLoginQR)); m_main_window->signal_action_reload_css().connect(sigc::mem_fun(*this, &Abaddon::ActionReloadCSS)); 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)); @@ -834,6 +836,16 @@ void Abaddon::ActionSetToken() { m_main_window->UpdateMenus(); } +void Abaddon::ActionLoginQR() { + RemoteAuthDialog dlg(*m_main_window); + auto response = dlg.run(); + if (response == Gtk::RESPONSE_OK) { + m_discord_token = dlg.GetToken(); + m_main_window->UpdateComponents(); + } + m_main_window->UpdateMenus(); +} + void Abaddon::ActionChannelOpened(Snowflake id, bool expand_to) { if (!id.IsValid()) { m_discord.SetReferringChannel(Snowflake::Invalid); @@ -1142,6 +1154,7 @@ int main(int argc, char **argv) { auto log_audio = spdlog::stdout_color_mt("audio"); auto log_voice = spdlog::stdout_color_mt("voice"); auto log_discord = spdlog::stdout_color_mt("discord"); + auto log_ra = spdlog::stdout_color_mt("remote-auth"); Gtk::Main::init_gtkmm_internals(); // why??? return Abaddon::Get().StartGTK(); diff --git a/src/abaddon.hpp b/src/abaddon.hpp index 08070ae..85b2fa0 100644 --- a/src/abaddon.hpp +++ b/src/abaddon.hpp @@ -41,6 +41,7 @@ public: void ActionConnect(); void ActionDisconnect(); void ActionSetToken(); + void ActionLoginQR(); void ActionJoinGuildDialog(); void ActionChannelOpened(Snowflake id, bool expand_to = true); void ActionChatInputSubmit(ChatSubmitParams data); diff --git a/src/discord/websocket.cpp b/src/discord/websocket.cpp index f886e69..cdc4db1 100644 --- a/src/discord/websocket.cpp +++ b/src/discord/websocket.cpp @@ -26,7 +26,7 @@ void Websocket::StartConnection(const std::string &url) { m_websocket->disableAutomaticReconnection(); m_websocket->setUrl(url); m_websocket->setOnMessageCallback([this](auto &&msg) { OnMessage(std::forward(msg)); }); - m_websocket->setExtraHeaders(ix::WebSocketHttpHeaders { { "User-Agent", m_agent } }); // idk if this actually works + m_websocket->setExtraHeaders(ix::WebSocketHttpHeaders { { "User-Agent", m_agent }, { "Origin", "https://discord.com" } }); // idk if this actually works m_websocket->start(); } @@ -81,6 +81,9 @@ void Websocket::OnMessage(const ix::WebSocketMessagePtr &msg) { case ix::WebSocketMessageType::Message: { m_signal_message.emit(msg->str); } break; + case ix::WebSocketMessageType::Error: { + m_log->error("Websocket error: Status: {} Reason: {}", msg->errorInfo.http_status, msg->errorInfo.reason); + } break; default: break; } diff --git a/src/remoteauth/remoteauthclient.cpp b/src/remoteauth/remoteauthclient.cpp new file mode 100644 index 0000000..1ebb7cb --- /dev/null +++ b/src/remoteauth/remoteauthclient.cpp @@ -0,0 +1,321 @@ +#include "remoteauthclient.hpp" +#include +#include + +struct RemoteAuthGatewayMessage { + std::string Opcode; + + friend void from_json(const nlohmann::json &j, RemoteAuthGatewayMessage &m) { + j.at("op").get_to(m.Opcode); + } +}; + +struct RemoteAuthHelloMessage { + int TimeoutMS; + int HeartbeatInterval; + + friend void from_json(const nlohmann::json &j, RemoteAuthHelloMessage &m) { + j.at("timeout_ms").get_to(m.TimeoutMS); + j.at("heartbeat_interval").get_to(m.HeartbeatInterval); + } +}; + +struct RemoteAuthHeartbeatMessage { + friend void to_json(nlohmann::json &j, const RemoteAuthHeartbeatMessage &m) { + j["op"] = "heartbeat"; + } +}; + +struct RemoteAuthInitMessage { + std::string EncodedPublicKey; + + friend void to_json(nlohmann::json &j, const RemoteAuthInitMessage &m) { + j["op"] = "init"; + j["encoded_public_key"] = m.EncodedPublicKey; + } +}; + +struct RemoteAuthNonceProofMessage { + std::string EncryptedNonce; + std::string Nonce; + + friend void from_json(const nlohmann::json &j, RemoteAuthNonceProofMessage &m) { + j.at("encrypted_nonce").get_to(m.EncryptedNonce); + } + + friend void to_json(nlohmann::json &j, const RemoteAuthNonceProofMessage &m) { + j["op"] = "nonce_proof"; + j["nonce"] = m.Nonce; + } +}; + +struct RemoteAuthFingerprintMessage { + std::string Fingerprint; + + friend void from_json(const nlohmann::json &j, RemoteAuthFingerprintMessage &m) { + j.at("fingerprint").get_to(m.Fingerprint); + } +}; + +struct RemoteAuthPendingTicketMessage { + std::string EncryptedUserPayload; + + friend void from_json(const nlohmann::json &j, RemoteAuthPendingTicketMessage &m) { + j.at("encrypted_user_payload").get_to(m.EncryptedUserPayload); + } +}; + +RemoteAuthClient::RemoteAuthClient() + : m_ws("remote-auth-ws") + , m_log(spdlog::get("remote-auth")) { + m_ws.signal_open().connect(sigc::mem_fun(*this, &RemoteAuthClient::OnWebsocketOpen)); + m_ws.signal_close().connect(sigc::mem_fun(*this, &RemoteAuthClient::OnWebsocketClose)); + m_ws.signal_message().connect(sigc::mem_fun(*this, &RemoteAuthClient::OnWebsocketMessage)); + + m_dispatcher.connect(sigc::mem_fun(*this, &RemoteAuthClient::OnDispatch)); +} + +RemoteAuthClient::~RemoteAuthClient() { + Stop(); +} + +void RemoteAuthClient::Start() { + if (IsConnected()) { + Stop(); + } + + m_connected = true; + m_heartbeat_waiter.revive(); + m_ws.StartConnection("wss://remote-auth-gateway.discord.gg/?v=2"); +} + +void RemoteAuthClient::Stop() { + if (!IsConnected()) { + m_log->warn("Requested stop while not connected"); + return; + } + + m_connected = false; + m_ws.Stop(1000); + m_heartbeat_waiter.kill(); + if (m_heartbeat_thread.joinable()) m_heartbeat_thread.join(); +} + +bool RemoteAuthClient::IsConnected() const noexcept { + return m_connected; +} + +void RemoteAuthClient::OnGatewayMessage(const std::string &str) { + auto j = nlohmann::json::parse(str); + RemoteAuthGatewayMessage msg = j; + if (msg.Opcode == "hello") { + HandleGatewayHello(j); + } else if (msg.Opcode == "nonce_proof") { + HandleGatewayNonceProof(j); + } else if (msg.Opcode == "pending_remote_init") { + HandleGatewayPendingRemoteInit(j); + } else if (msg.Opcode == "pending_ticket") { + HandleGatewayPendingTicket(j); + } +} + +void RemoteAuthClient::HandleGatewayHello(const nlohmann::json &j) { + RemoteAuthHelloMessage msg = j; + m_log->debug("Timeout: {}, Heartbeat: {}", msg.TimeoutMS, msg.HeartbeatInterval); + + m_heartbeat_msec = msg.HeartbeatInterval; + m_heartbeat_thread = std::thread(&RemoteAuthClient::HeartbeatThread, this); + + Init(); +} + +void RemoteAuthClient::HandleGatewayNonceProof(const nlohmann::json &j) { + RemoteAuthNonceProofMessage msg = j; + m_log->debug("Received encrypted nonce"); + + const auto encrypted_nonce = Glib::Base64::decode(msg.EncryptedNonce); + const auto proof = Decrypt(reinterpret_cast(encrypted_nonce.data()), encrypted_nonce.size()); + auto proof_encoded = Glib::Base64::encode(std::string(proof.begin(), proof.end())); + + std::replace(proof_encoded.begin(), proof_encoded.end(), '/', '_'); + std::replace(proof_encoded.begin(), proof_encoded.end(), '+', '-'); + proof_encoded.erase(std::remove(proof_encoded.begin(), proof_encoded.end(), '='), proof_encoded.end()); + + RemoteAuthNonceProofMessage reply; + reply.Nonce = proof_encoded; + m_ws.Send(reply); +} + +void RemoteAuthClient::HandleGatewayPendingRemoteInit(const nlohmann::json &j) { + RemoteAuthFingerprintMessage msg = j; + m_log->debug("Received fingerprint"); + + m_signal_fingerprint.emit(msg.Fingerprint); +} + +void RemoteAuthClient::HandleGatewayPendingTicket(const nlohmann::json &j) { + RemoteAuthPendingTicketMessage msg = j; + + const auto encrypted_payload = Glib::Base64::decode(msg.EncryptedUserPayload); + const auto payload = Decrypt(reinterpret_cast(encrypted_payload.data()), encrypted_payload.size()); + + m_log->trace("User payload: {}", std::string(payload.begin(), payload.end())); +} + +void RemoteAuthClient::Init() { + RemoteAuthInitMessage msg; + + GenerateKey(); + msg.EncodedPublicKey = GetEncodedPublicKey(); + if (msg.EncodedPublicKey.empty()) { + m_log->error("Something went wrong"); + // todo disconnect + return; + } + + m_ws.Send(msg); +} + +void RemoteAuthClient::GenerateKey() { + // you javascript people have it so easy + // check out this documentation https://www.openssl.org/docs/man1.1.1/man3/PEM_write_bio_PUBKEY.html + + m_pkey_ctx.reset(EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr)); + if (!m_pkey_ctx) { + m_log->error("Failed to create RSA context"); + return; + } + + if (EVP_PKEY_keygen_init(m_pkey_ctx.get()) <= 0) { + m_log->error("Failed to initialize RSA context"); + return; + } + + if (EVP_PKEY_CTX_set_rsa_keygen_bits(m_pkey_ctx.get(), 2048) <= 0) { + m_log->error("Failed to set keygen bits"); + return; + } + + EVP_PKEY *pkey_tmp = nullptr; + if (EVP_PKEY_keygen(m_pkey_ctx.get(), &pkey_tmp) <= 0) { + m_log->error("Failed to generate keypair"); + return; + } + m_pkey.reset(pkey_tmp); + + m_dec_ctx.reset(EVP_PKEY_CTX_new(m_pkey.get(), nullptr)); + if (EVP_PKEY_decrypt_init(m_dec_ctx.get()) <= 0) { + m_log->error("Failed to initialize RSA decrypt context"); + return; + } + + if (EVP_PKEY_CTX_set_rsa_padding(m_dec_ctx.get(), RSA_PKCS1_OAEP_PADDING) <= 0) { + m_log->error("EVP_PKEY_CTX_set_rsa_padding failed"); + return; + } + + if (EVP_PKEY_CTX_set_rsa_oaep_md(m_dec_ctx.get(), EVP_sha256()) <= 0) { + m_log->error("EVP_PKEY_CTX_set_rsa_oaep_md failed"); + return; + } + + if (EVP_PKEY_CTX_set_rsa_mgf1_md(m_dec_ctx.get(), EVP_sha256()) <= 0) { + m_log->error("EVP_PKEY_CTX_set_rsa_mgf1_md"); + return; + } +} + +std::string RemoteAuthClient::GetEncodedPublicKey() const { + auto bio = BIO_ptr(BIO_new(BIO_s_mem()), BIO_free); + if (!bio) { + m_log->error("Failed to create BIO"); + return {}; + } + + if (PEM_write_bio_PUBKEY(bio.get(), m_pkey.get()) <= 0) { + m_log->error("Failed to write public key to BIO"); + return {}; + } + + // i think this is freed when the bio is too + BUF_MEM *mem = nullptr; + if (BIO_get_mem_ptr(bio.get(), &mem) <= 0) { + m_log->error("Failed to get BIO mem buf"); + return {}; + } + + if (mem->data == nullptr || mem->length == 0) { + m_log->error("BIO mem buf is null or of zero length"); + return {}; + } + + std::string pem_pubkey(mem->data, mem->length); + // isolate key + pem_pubkey.erase(0, pem_pubkey.find("\n") + 1); + pem_pubkey.erase(pem_pubkey.rfind("\n-")); + size_t pos; + while ((pos = pem_pubkey.find("\n")) != std::string::npos) { + pem_pubkey.erase(pos, 1); + } + return pem_pubkey; +} + +std::vector RemoteAuthClient::Decrypt(const unsigned char *in, size_t inlen) const { + // get length + size_t outlen; + if (EVP_PKEY_decrypt(m_dec_ctx.get(), nullptr, &outlen, in, inlen) <= 0) { + m_log->error("Failed to get length when decrypting"); + return {}; + } + + std::vector ret(outlen); + if (EVP_PKEY_decrypt(m_dec_ctx.get(), ret.data(), &outlen, in, inlen) <= 0) { + m_log->error("Failed to decrypt"); + return {}; + } + ret.resize(outlen); + return ret; +} + +void RemoteAuthClient::OnWebsocketOpen() { + m_log->info("Websocket opened"); +} + +void RemoteAuthClient::OnWebsocketClose(const ix::WebSocketCloseInfo &info) { + if (info.remote) { + m_log->debug("Websocket closed (remote): {} ({})", info.code, info.reason); + } else { + m_log->debug("Websocket closed (local): {} ({})", info.code, info.reason); + } +} + +void RemoteAuthClient::OnWebsocketMessage(const std::string &data) { + m_dispatch_mutex.lock(); + m_dispatch_queue.push(data); + m_dispatcher.emit(); + m_dispatch_mutex.unlock(); +} + +void RemoteAuthClient::HeartbeatThread() { + while (true) { + if (!m_heartbeat_waiter.wait_for(std::chrono::milliseconds(m_heartbeat_msec))) break; + + m_ws.Send(RemoteAuthHeartbeatMessage()); + } +} + +void RemoteAuthClient::OnDispatch() { + m_dispatch_mutex.lock(); + if (m_dispatch_queue.empty()) { + m_dispatch_mutex.unlock(); + return; + } + auto msg = std::move(m_dispatch_queue.front()); + m_dispatch_queue.pop(); + m_dispatch_mutex.unlock(); + OnGatewayMessage(msg); +} + +RemoteAuthClient::type_signal_fingerprint RemoteAuthClient::signal_fingerprint() { + return m_signal_fingerprint; +} diff --git a/src/remoteauth/remoteauthclient.hpp b/src/remoteauth/remoteauthclient.hpp new file mode 100644 index 0000000..178e10a --- /dev/null +++ b/src/remoteauth/remoteauthclient.hpp @@ -0,0 +1,64 @@ +#pragma once +#include +#include +#include +#include "ssl.hpp" +#include "discord/waiter.hpp" +#include "discord/websocket.hpp" + +class RemoteAuthClient { +public: + RemoteAuthClient(); + ~RemoteAuthClient(); + + void Start(); + void Stop(); + + [[nodiscard]] bool IsConnected() const noexcept; + +private: + void OnGatewayMessage(const std::string &str); + void HandleGatewayHello(const nlohmann::json &j); + void HandleGatewayNonceProof(const nlohmann::json &j); + void HandleGatewayPendingRemoteInit(const nlohmann::json &j); + void HandleGatewayPendingTicket(const nlohmann::json &j); + + void Init(); + + void GenerateKey(); + std::string GetEncodedPublicKey() const; + + std::vector Decrypt(const unsigned char *in, size_t inlen) const; + + void OnWebsocketOpen(); + void OnWebsocketClose(const ix::WebSocketCloseInfo &info); + void OnWebsocketMessage(const std::string &str); + + void HeartbeatThread(); + + int m_heartbeat_msec; + Waiter m_heartbeat_waiter; + std::thread m_heartbeat_thread; + + Glib::Dispatcher m_dispatcher; + std::queue m_dispatch_queue; + std::mutex m_dispatch_mutex; + + void OnDispatch(); + + Websocket m_ws; + bool m_connected = false; + + std::shared_ptr m_log; + + EVP_PKEY_CTX_ptr m_pkey_ctx; + EVP_PKEY_CTX_ptr m_dec_ctx; + EVP_PKEY_ptr m_pkey; + +public: + using type_signal_fingerprint = sigc::signal; + type_signal_fingerprint signal_fingerprint(); + +private: + type_signal_fingerprint m_signal_fingerprint; +}; diff --git a/src/remoteauth/remoteauthdialog.cpp b/src/remoteauth/remoteauthdialog.cpp new file mode 100644 index 0000000..db154c4 --- /dev/null +++ b/src/remoteauth/remoteauthdialog.cpp @@ -0,0 +1,79 @@ +#include "remoteauthdialog.hpp" +#include + +RemoteAuthDialog::RemoteAuthDialog(Gtk::Window &parent) + : Gtk::Dialog("Login with QR Code", parent, true) + , m_layout(Gtk::ORIENTATION_VERTICAL) + , m_ok("OK") + , m_cancel("Cancel") + , m_bbox(Gtk::ORIENTATION_HORIZONTAL) { + set_default_size(300, 50); + get_style_context()->add_class("app-window"); + get_style_context()->add_class("app-popup"); + + m_ok.signal_clicked().connect([&]() { + response(Gtk::RESPONSE_OK); + }); + + m_cancel.signal_clicked().connect([&]() { + response(Gtk::RESPONSE_CANCEL); + }); + + m_bbox.pack_start(m_ok, Gtk::PACK_SHRINK); + m_bbox.pack_start(m_cancel, Gtk::PACK_SHRINK); + m_bbox.set_layout(Gtk::BUTTONBOX_END); + + m_ra.signal_fingerprint().connect(sigc::mem_fun(*this, &RemoteAuthDialog::OnFingerprint)); + + m_ra.Start(); + + m_image.set_size_request(256, 256); + + m_layout.add(m_image); + m_layout.add(m_bbox); + get_content_area()->add(m_layout); + + show_all_children(); +} + +void RemoteAuthDialog::OnFingerprint(const std::string &fingerprint) { + const auto url = "https://discord.com/ra/" + fingerprint; + + const auto level = qrcodegen::QrCode::Ecc::QUARTILE; + const auto qr = qrcodegen::QrCode::encodeText(url.c_str(), level); + + int size = qr.getSize(); + const int border = 4; + + std::ostringstream sb; + sb << "\n"; + sb << "\n"; + sb << "\n"; + sb << "\t\n"; + sb << "\t\n"; + sb << "\n"; + + const auto svg = sb.str(); + + auto loader = Gdk::PixbufLoader::create(); + loader->write(reinterpret_cast(svg.data()), svg.size()); + loader->close(); + const auto pb = loader->get_pixbuf()->scale_simple(256, 256, Gdk::INTERP_NEAREST); + + m_image.property_pixbuf() = pb; +} + +std::string RemoteAuthDialog::GetToken() { + return m_token; +} diff --git a/src/remoteauth/remoteauthdialog.hpp b/src/remoteauth/remoteauthdialog.hpp new file mode 100644 index 0000000..24fad56 --- /dev/null +++ b/src/remoteauth/remoteauthdialog.hpp @@ -0,0 +1,23 @@ +#pragma once +#include +#include "remoteauthclient.hpp" + +class RemoteAuthDialog : public Gtk::Dialog { +public: + RemoteAuthDialog(Gtk::Window &parent); + std::string GetToken(); + +protected: + Gtk::Image m_image; + Gtk::Box m_layout; + Gtk::Button m_ok; + Gtk::Button m_cancel; + Gtk::ButtonBox m_bbox; + +private: + RemoteAuthClient m_ra; + + void OnFingerprint(const std::string &fingerprint); + + std::string m_token; +}; diff --git a/src/remoteauth/ssl.hpp b/src/remoteauth/ssl.hpp new file mode 100644 index 0000000..1753bd3 --- /dev/null +++ b/src/remoteauth/ssl.hpp @@ -0,0 +1,31 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +struct EVP_PKEY_CTX_deleter { + void operator()(EVP_PKEY_CTX *ptr) const { + EVP_PKEY_CTX_free(ptr); + } +}; + +struct EVP_PKEY_deleter { + void operator()(EVP_PKEY *ptr) const { + EVP_PKEY_free(ptr); + } +}; + +struct EVP_MD_CTX_deleter { + void operator()(EVP_MD_CTX *ptr) const { + EVP_MD_CTX_free(ptr); + } +}; + +using EVP_PKEY_CTX_ptr = std::unique_ptr; +using EVP_PKEY_ptr = std::unique_ptr; +using EVP_MD_CTX_ptr = std::unique_ptr; +using BIO_ptr = std::unique_ptr; +using BUF_MEM_ptr = std::unique_ptr; diff --git a/src/windows/mainwindow.cpp b/src/windows/mainwindow.cpp index bb09735..56d735e 100644 --- a/src/windows/mainwindow.cpp +++ b/src/windows/mainwindow.cpp @@ -206,6 +206,7 @@ void MainWindow::OnDiscordSubmenuPopup() { m_menu_discord_connect.set_sensitive(!token.empty() && !discord_active); m_menu_discord_disconnect.set_sensitive(discord_active); m_menu_discord_set_token.set_sensitive(!discord_active); + m_menu_discord_login_qr.set_sensitive(!discord_active); m_menu_discord_set_status.set_sensitive(discord_active); } @@ -247,12 +248,14 @@ void MainWindow::SetupMenu() { m_menu_discord_disconnect.set_label("Disconnect"); m_menu_discord_disconnect.set_sensitive(false); m_menu_discord_set_token.set_label("Set Token"); + m_menu_discord_login_qr.set_label("Login with QR Code"); m_menu_discord_set_status.set_label("Set Status"); m_menu_discord_set_status.set_sensitive(false); m_menu_discord_add_recipient.set_label("Add user to DM"); m_menu_discord_sub.append(m_menu_discord_connect); m_menu_discord_sub.append(m_menu_discord_disconnect); m_menu_discord_sub.append(m_menu_discord_set_token); + m_menu_discord_sub.append(m_menu_discord_login_qr); m_menu_discord_sub.append(m_menu_discord_set_status); m_menu_discord_sub.append(m_menu_discord_add_recipient); m_menu_discord.set_submenu(m_menu_discord_sub); @@ -331,6 +334,10 @@ void MainWindow::SetupMenu() { m_signal_action_set_token.emit(); }); + m_menu_discord_login_qr.signal_activate().connect([this] { + m_signal_action_login_qr.emit(); + }); + m_menu_file_reload_css.signal_activate().connect([this] { m_signal_action_reload_css.emit(); }); @@ -421,6 +428,10 @@ MainWindow::type_signal_action_set_token MainWindow::signal_action_set_token() { return m_signal_action_set_token; } +MainWindow::type_signal_action_login_qr MainWindow::signal_action_login_qr() { + return m_signal_action_login_qr; +} + MainWindow::type_signal_action_reload_css MainWindow::signal_action_reload_css() { return m_signal_action_reload_css; } diff --git a/src/windows/mainwindow.hpp b/src/windows/mainwindow.hpp index e075c10..604043c 100644 --- a/src/windows/mainwindow.hpp +++ b/src/windows/mainwindow.hpp @@ -74,6 +74,7 @@ private: Gtk::MenuItem m_menu_discord_connect; Gtk::MenuItem m_menu_discord_disconnect; Gtk::MenuItem m_menu_discord_set_token; + Gtk::MenuItem m_menu_discord_login_qr; Gtk::MenuItem m_menu_discord_set_status; Gtk::MenuItem m_menu_discord_add_recipient; // move me somewhere else some day void OnDiscordSubmenuPopup(); @@ -103,6 +104,7 @@ public: typedef sigc::signal type_signal_action_connect; typedef sigc::signal type_signal_action_disconnect; typedef sigc::signal type_signal_action_set_token; + typedef sigc::signal type_signal_action_login_qr; typedef sigc::signal type_signal_action_reload_css; typedef sigc::signal type_signal_action_set_status; // this should probably be removed @@ -113,6 +115,7 @@ public: type_signal_action_connect signal_action_connect(); type_signal_action_disconnect signal_action_disconnect(); type_signal_action_set_token signal_action_set_token(); + type_signal_action_login_qr signal_action_login_qr(); type_signal_action_reload_css signal_action_reload_css(); type_signal_action_set_status signal_action_set_status(); type_signal_action_add_recipient signal_action_add_recipient(); @@ -123,6 +126,7 @@ private: type_signal_action_connect m_signal_action_connect; type_signal_action_disconnect m_signal_action_disconnect; type_signal_action_set_token m_signal_action_set_token; + type_signal_action_login_qr m_signal_action_login_qr; type_signal_action_reload_css m_signal_action_reload_css; type_signal_action_set_status m_signal_action_set_status; type_signal_action_add_recipient m_signal_action_add_recipient; diff --git a/subprojects/qrcodegen b/subprojects/qrcodegen new file mode 160000 index 0000000..22fac31 --- /dev/null +++ b/subprojects/qrcodegen @@ -0,0 +1 @@ +Subproject commit 22fac31bdf81da68730c177c0e931c93234d2a30 From 4ac7ca9245016616bbc77acf8e989e41729e1afd Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 1 Jul 2023 00:56:09 -0400 Subject: [PATCH 28/52] finalize login --- src/abaddon.cpp | 3 +++ src/discord/discord.cpp | 14 +++++++++++++ src/discord/discord.hpp | 2 ++ src/remoteauth/remoteauthclient.cpp | 32 +++++++++++++++++++++++++++++ src/remoteauth/remoteauthclient.hpp | 6 ++++++ src/remoteauth/remoteauthdialog.cpp | 7 +++++++ src/remoteauth/remoteauthdialog.hpp | 1 + 7 files changed, 65 insertions(+) diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 545a3c2..e0d39b5 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -841,9 +841,12 @@ void Abaddon::ActionLoginQR() { 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; } m_main_window->UpdateMenus(); + ActionConnect(); } void Abaddon::ActionChannelOpened(Snowflake id, bool expand_to) { diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 817aca8..5dc0464 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1197,6 +1197,20 @@ void DiscordClient::AcceptVerificationGate(Snowflake guild_id, VerificationGateI }); } +void DiscordClient::RemoteAuthLogin(const std::string &ticket, const sigc::slot, DiscordError code)> &callback) { + http::request req(http::REQUEST_POST, "https://discord.com/api/v9/users/@me/remote-auth/login"); + req.set_header("Content-Type", "application/json"); + req.set_user_agent(Abaddon::Get().GetSettings().UserAgent); + req.set_body("{\"ticket\":\"" + ticket + "\"}"); + m_http.Execute(std::move(req), [this, callback](const http::response_type &r) { + if (CheckCode(r)) { + callback(nlohmann::json::parse(r.text).at("encrypted_token").get(), DiscordError::NONE); + } else { + callback(std::nullopt, GetCodeFromResponse(r)); + } + }); +} + #ifdef WITH_VOICE void DiscordClient::ConnectToVoice(Snowflake channel_id) { auto channel = GetChannel(channel_id); diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index d2435dd..438e4e6 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -184,6 +184,8 @@ public: void GetVerificationGateInfo(Snowflake guild_id, const sigc::slot)> &callback); void AcceptVerificationGate(Snowflake guild_id, VerificationGateInfoObject info, const sigc::slot &callback); + void RemoteAuthLogin(const std::string &ticket, const sigc::slot, DiscordError code)> &callback); + #ifdef WITH_VOICE void ConnectToVoice(Snowflake channel_id); void DisconnectFromVoice(); diff --git a/src/remoteauth/remoteauthclient.cpp b/src/remoteauth/remoteauthclient.cpp index 1ebb7cb..390f42d 100644 --- a/src/remoteauth/remoteauthclient.cpp +++ b/src/remoteauth/remoteauthclient.cpp @@ -1,4 +1,5 @@ #include "remoteauthclient.hpp" +#include "http.hpp" #include #include @@ -65,6 +66,14 @@ struct RemoteAuthPendingTicketMessage { } }; +struct RemoteAuthPendingLoginMessage { + std::string Ticket; + + friend void from_json(const nlohmann::json &j, RemoteAuthPendingLoginMessage &m) { + j.at("ticket").get_to(m.Ticket); + } +}; + RemoteAuthClient::RemoteAuthClient() : m_ws("remote-auth-ws") , m_log(spdlog::get("remote-auth")) { @@ -116,6 +125,8 @@ void RemoteAuthClient::OnGatewayMessage(const std::string &str) { HandleGatewayPendingRemoteInit(j); } else if (msg.Opcode == "pending_ticket") { HandleGatewayPendingTicket(j); + } else if (msg.Opcode == "pending_login") { + HandleGatewayPendingLogin(j); } } @@ -162,6 +173,23 @@ void RemoteAuthClient::HandleGatewayPendingTicket(const nlohmann::json &j) { m_log->trace("User payload: {}", std::string(payload.begin(), payload.end())); } +void RemoteAuthClient::HandleGatewayPendingLogin(const nlohmann::json &j) { + RemoteAuthPendingLoginMessage msg = j; + + Abaddon::Get().GetDiscordClient().RemoteAuthLogin(msg.Ticket, sigc::mem_fun(*this, &RemoteAuthClient::OnRemoteAuthLoginResponse)); +} + +void RemoteAuthClient::OnRemoteAuthLoginResponse(const std::optional &encrypted_token, DiscordError err) { + if (!encrypted_token.has_value()) { + m_log->error("Remote auth login failed: {}", static_cast(err)); + return; + } + + const auto encrypted = Glib::Base64::decode(*encrypted_token); + const auto token = Decrypt(reinterpret_cast(encrypted.data()), encrypted.size()); + m_signal_token.emit(std::string(token.begin(), token.end())); +} + void RemoteAuthClient::Init() { RemoteAuthInitMessage msg; @@ -319,3 +347,7 @@ void RemoteAuthClient::OnDispatch() { RemoteAuthClient::type_signal_fingerprint RemoteAuthClient::signal_fingerprint() { return m_signal_fingerprint; } + +RemoteAuthClient::type_signal_token RemoteAuthClient::signal_token() { + return m_signal_token; +} diff --git a/src/remoteauth/remoteauthclient.hpp b/src/remoteauth/remoteauthclient.hpp index 178e10a..8fb8bc3 100644 --- a/src/remoteauth/remoteauthclient.hpp +++ b/src/remoteauth/remoteauthclient.hpp @@ -22,6 +22,9 @@ private: void HandleGatewayNonceProof(const nlohmann::json &j); void HandleGatewayPendingRemoteInit(const nlohmann::json &j); void HandleGatewayPendingTicket(const nlohmann::json &j); + void HandleGatewayPendingLogin(const nlohmann::json &j); + + void OnRemoteAuthLoginResponse(const std::optional &encrypted_token, DiscordError err); void Init(); @@ -57,8 +60,11 @@ private: public: using type_signal_fingerprint = sigc::signal; + using type_signal_token = sigc::signal; type_signal_fingerprint signal_fingerprint(); + type_signal_token signal_token(); private: type_signal_fingerprint m_signal_fingerprint; + type_signal_token m_signal_token; }; diff --git a/src/remoteauth/remoteauthdialog.cpp b/src/remoteauth/remoteauthdialog.cpp index db154c4..ea250d6 100644 --- a/src/remoteauth/remoteauthdialog.cpp +++ b/src/remoteauth/remoteauthdialog.cpp @@ -24,6 +24,7 @@ RemoteAuthDialog::RemoteAuthDialog(Gtk::Window &parent) m_bbox.set_layout(Gtk::BUTTONBOX_END); m_ra.signal_fingerprint().connect(sigc::mem_fun(*this, &RemoteAuthDialog::OnFingerprint)); + m_ra.signal_token().connect(sigc::mem_fun(*this, &RemoteAuthDialog::OnToken)); m_ra.Start(); @@ -77,3 +78,9 @@ void RemoteAuthDialog::OnFingerprint(const std::string &fingerprint) { std::string RemoteAuthDialog::GetToken() { return m_token; } + +void RemoteAuthDialog::OnToken(const std::string &token) { + m_token = token; + m_ra.Stop(); + response(Gtk::RESPONSE_OK); +} diff --git a/src/remoteauth/remoteauthdialog.hpp b/src/remoteauth/remoteauthdialog.hpp index 24fad56..44af821 100644 --- a/src/remoteauth/remoteauthdialog.hpp +++ b/src/remoteauth/remoteauthdialog.hpp @@ -18,6 +18,7 @@ private: RemoteAuthClient m_ra; void OnFingerprint(const std::string &fingerprint); + void OnToken(const std::string &token); std::string m_token; }; From 656bea8e55c13f1d29fb84040b5007aef3eef0c8 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 1 Jul 2023 01:02:53 -0400 Subject: [PATCH 29/52] get rid of the message structs because theyre unnecessary --- src/remoteauth/remoteauthclient.cpp | 122 ++++++---------------------- 1 file changed, 25 insertions(+), 97 deletions(-) diff --git a/src/remoteauth/remoteauthclient.cpp b/src/remoteauth/remoteauthclient.cpp index 390f42d..5749f10 100644 --- a/src/remoteauth/remoteauthclient.cpp +++ b/src/remoteauth/remoteauthclient.cpp @@ -3,77 +3,6 @@ #include #include -struct RemoteAuthGatewayMessage { - std::string Opcode; - - friend void from_json(const nlohmann::json &j, RemoteAuthGatewayMessage &m) { - j.at("op").get_to(m.Opcode); - } -}; - -struct RemoteAuthHelloMessage { - int TimeoutMS; - int HeartbeatInterval; - - friend void from_json(const nlohmann::json &j, RemoteAuthHelloMessage &m) { - j.at("timeout_ms").get_to(m.TimeoutMS); - j.at("heartbeat_interval").get_to(m.HeartbeatInterval); - } -}; - -struct RemoteAuthHeartbeatMessage { - friend void to_json(nlohmann::json &j, const RemoteAuthHeartbeatMessage &m) { - j["op"] = "heartbeat"; - } -}; - -struct RemoteAuthInitMessage { - std::string EncodedPublicKey; - - friend void to_json(nlohmann::json &j, const RemoteAuthInitMessage &m) { - j["op"] = "init"; - j["encoded_public_key"] = m.EncodedPublicKey; - } -}; - -struct RemoteAuthNonceProofMessage { - std::string EncryptedNonce; - std::string Nonce; - - friend void from_json(const nlohmann::json &j, RemoteAuthNonceProofMessage &m) { - j.at("encrypted_nonce").get_to(m.EncryptedNonce); - } - - friend void to_json(nlohmann::json &j, const RemoteAuthNonceProofMessage &m) { - j["op"] = "nonce_proof"; - j["nonce"] = m.Nonce; - } -}; - -struct RemoteAuthFingerprintMessage { - std::string Fingerprint; - - friend void from_json(const nlohmann::json &j, RemoteAuthFingerprintMessage &m) { - j.at("fingerprint").get_to(m.Fingerprint); - } -}; - -struct RemoteAuthPendingTicketMessage { - std::string EncryptedUserPayload; - - friend void from_json(const nlohmann::json &j, RemoteAuthPendingTicketMessage &m) { - j.at("encrypted_user_payload").get_to(m.EncryptedUserPayload); - } -}; - -struct RemoteAuthPendingLoginMessage { - std::string Ticket; - - friend void from_json(const nlohmann::json &j, RemoteAuthPendingLoginMessage &m) { - j.at("ticket").get_to(m.Ticket); - } -}; - RemoteAuthClient::RemoteAuthClient() : m_ws("remote-auth-ws") , m_log(spdlog::get("remote-auth")) { @@ -116,35 +45,35 @@ bool RemoteAuthClient::IsConnected() const noexcept { void RemoteAuthClient::OnGatewayMessage(const std::string &str) { auto j = nlohmann::json::parse(str); - RemoteAuthGatewayMessage msg = j; - if (msg.Opcode == "hello") { + const auto opcode = j.at("op").get(); + if (opcode == "hello") { HandleGatewayHello(j); - } else if (msg.Opcode == "nonce_proof") { + } else if (opcode == "nonce_proof") { HandleGatewayNonceProof(j); - } else if (msg.Opcode == "pending_remote_init") { + } else if (opcode == "pending_remote_init") { HandleGatewayPendingRemoteInit(j); - } else if (msg.Opcode == "pending_ticket") { + } else if (opcode == "pending_ticket") { HandleGatewayPendingTicket(j); - } else if (msg.Opcode == "pending_login") { + } else if (opcode == "pending_login") { HandleGatewayPendingLogin(j); } } void RemoteAuthClient::HandleGatewayHello(const nlohmann::json &j) { - RemoteAuthHelloMessage msg = j; - m_log->debug("Timeout: {}, Heartbeat: {}", msg.TimeoutMS, msg.HeartbeatInterval); + const auto timeout_ms = j.at("timeout_ms").get(); + const auto heartbeat_interval = j.at("heartbeat_interval").get(); + m_log->debug("Timeout: {}, Heartbeat: {}", timeout_ms, heartbeat_interval); - m_heartbeat_msec = msg.HeartbeatInterval; + m_heartbeat_msec = heartbeat_interval; m_heartbeat_thread = std::thread(&RemoteAuthClient::HeartbeatThread, this); Init(); } void RemoteAuthClient::HandleGatewayNonceProof(const nlohmann::json &j) { - RemoteAuthNonceProofMessage msg = j; m_log->debug("Received encrypted nonce"); - const auto encrypted_nonce = Glib::Base64::decode(msg.EncryptedNonce); + const auto encrypted_nonce = Glib::Base64::decode(j.at("encrypted_nonce").get()); const auto proof = Decrypt(reinterpret_cast(encrypted_nonce.data()), encrypted_nonce.size()); auto proof_encoded = Glib::Base64::encode(std::string(proof.begin(), proof.end())); @@ -152,31 +81,27 @@ void RemoteAuthClient::HandleGatewayNonceProof(const nlohmann::json &j) { std::replace(proof_encoded.begin(), proof_encoded.end(), '+', '-'); proof_encoded.erase(std::remove(proof_encoded.begin(), proof_encoded.end(), '='), proof_encoded.end()); - RemoteAuthNonceProofMessage reply; - reply.Nonce = proof_encoded; + nlohmann::json reply; + reply["op"] = "nonce_proof"; + reply["nonce"] = proof_encoded; m_ws.Send(reply); } void RemoteAuthClient::HandleGatewayPendingRemoteInit(const nlohmann::json &j) { - RemoteAuthFingerprintMessage msg = j; m_log->debug("Received fingerprint"); - m_signal_fingerprint.emit(msg.Fingerprint); + m_signal_fingerprint.emit(j.at("fingerprint").get()); } void RemoteAuthClient::HandleGatewayPendingTicket(const nlohmann::json &j) { - RemoteAuthPendingTicketMessage msg = j; - - const auto encrypted_payload = Glib::Base64::decode(msg.EncryptedUserPayload); + const auto encrypted_payload = Glib::Base64::decode(j.at("encrypted_user_payload").get()); const auto payload = Decrypt(reinterpret_cast(encrypted_payload.data()), encrypted_payload.size()); m_log->trace("User payload: {}", std::string(payload.begin(), payload.end())); } void RemoteAuthClient::HandleGatewayPendingLogin(const nlohmann::json &j) { - RemoteAuthPendingLoginMessage msg = j; - - Abaddon::Get().GetDiscordClient().RemoteAuthLogin(msg.Ticket, sigc::mem_fun(*this, &RemoteAuthClient::OnRemoteAuthLoginResponse)); + Abaddon::Get().GetDiscordClient().RemoteAuthLogin(j.at("ticket").get(), sigc::mem_fun(*this, &RemoteAuthClient::OnRemoteAuthLoginResponse)); } void RemoteAuthClient::OnRemoteAuthLoginResponse(const std::optional &encrypted_token, DiscordError err) { @@ -191,16 +116,17 @@ void RemoteAuthClient::OnRemoteAuthLoginResponse(const std::optionalerror("Something went wrong"); // todo disconnect return; } + nlohmann::json msg; + msg["op"] = "init"; + msg["encoded_public_key"] = key; m_ws.Send(msg); } @@ -328,7 +254,9 @@ void RemoteAuthClient::HeartbeatThread() { while (true) { if (!m_heartbeat_waiter.wait_for(std::chrono::milliseconds(m_heartbeat_msec))) break; - m_ws.Send(RemoteAuthHeartbeatMessage()); + nlohmann::json hb; + hb["op"] = "hearbeat"; + m_ws.Send(hb); } } From 044e508df7868856648e14ad7c957be8b9bb46b5 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 1 Jul 2023 01:47:45 -0400 Subject: [PATCH 30/52] only connect on RESPONSE_OK --- src/abaddon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/abaddon.cpp b/src/abaddon.cpp index e0d39b5..c161595 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -844,9 +844,9 @@ void Abaddon::ActionLoginQR() { m_discord.UpdateToken(m_discord_token); m_main_window->UpdateComponents(); GetSettings().DiscordToken = m_discord_token; + ActionConnect(); } m_main_window->UpdateMenus(); - ActionConnect(); } void Abaddon::ActionChannelOpened(Snowflake id, bool expand_to) { From ab448a3a9820663a882bde242525546cc4273516 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 1 Jul 2023 02:10:42 -0400 Subject: [PATCH 31/52] show status, error when captcha required --- src/discord/discord.cpp | 7 +++++ src/discord/errors.hpp | 1 + src/remoteauth/remoteauthclient.cpp | 38 ++++++++++++++++++++++++ src/remoteauth/remoteauthclient.hpp | 12 ++++++++ src/remoteauth/remoteauthdialog.cpp | 45 +++++++++++++++++++++++++++-- src/remoteauth/remoteauthdialog.hpp | 5 ++++ 6 files changed, 106 insertions(+), 2 deletions(-) diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 5dc0464..75d066b 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1206,6 +1206,13 @@ void DiscordClient::RemoteAuthLogin(const std::string &ticket, const sigc::slot< if (CheckCode(r)) { callback(nlohmann::json::parse(r.text).at("encrypted_token").get(), DiscordError::NONE); } else { + try { + const auto j = nlohmann::json::parse(r.text); + if (j.contains("captcha_service")) { + callback(std::nullopt, DiscordError::CAPTCHA_REQUIRED); + return; + } + } catch (...) {} callback(std::nullopt, GetCodeFromResponse(r)); } }); diff --git a/src/discord/errors.hpp b/src/discord/errors.hpp index 4579563..8ead4f2 100644 --- a/src/discord/errors.hpp +++ b/src/discord/errors.hpp @@ -11,6 +11,7 @@ enum class DiscordError { RELATIONSHIP_ALREADY_FRIENDS = 80007, NONE = -1, + CAPTCHA_REQUIRED = -2, }; constexpr const char *GetDiscordErrorDisplayString(DiscordError error) { diff --git a/src/remoteauth/remoteauthclient.cpp b/src/remoteauth/remoteauthclient.cpp index 5749f10..e2d3cc6 100644 --- a/src/remoteauth/remoteauthclient.cpp +++ b/src/remoteauth/remoteauthclient.cpp @@ -68,6 +68,8 @@ void RemoteAuthClient::HandleGatewayHello(const nlohmann::json &j) { m_heartbeat_thread = std::thread(&RemoteAuthClient::HeartbeatThread, this); Init(); + + m_signal_hello.emit(); } void RemoteAuthClient::HandleGatewayNonceProof(const nlohmann::json &j) { @@ -98,15 +100,35 @@ void RemoteAuthClient::HandleGatewayPendingTicket(const nlohmann::json &j) { const auto payload = Decrypt(reinterpret_cast(encrypted_payload.data()), encrypted_payload.size()); m_log->trace("User payload: {}", std::string(payload.begin(), payload.end())); + + const std::vector user_info = Glib::Regex::split_simple(":", std::string(payload.begin(), payload.end())); + Snowflake user_id; + std::string discriminator; + std::string avatar_hash; + std::string username; + if (user_info.size() >= 4) { + user_id = Snowflake(user_info[0]); + discriminator = user_info[1]; + avatar_hash = user_info[2]; + username = user_info[3]; + } + + m_signal_pending_ticket.emit(user_id, discriminator, avatar_hash, username); } void RemoteAuthClient::HandleGatewayPendingLogin(const nlohmann::json &j) { Abaddon::Get().GetDiscordClient().RemoteAuthLogin(j.at("ticket").get(), sigc::mem_fun(*this, &RemoteAuthClient::OnRemoteAuthLoginResponse)); + m_signal_pending_login.emit(); } void RemoteAuthClient::OnRemoteAuthLoginResponse(const std::optional &encrypted_token, DiscordError err) { if (!encrypted_token.has_value()) { m_log->error("Remote auth login failed: {}", static_cast(err)); + if (err == DiscordError::CAPTCHA_REQUIRED) { + m_signal_error.emit("Discord is requiring a captcha. You must use a web browser to log in."); + } else { + m_signal_error.emit("An error occurred. Try again."); + } return; } @@ -272,10 +294,26 @@ void RemoteAuthClient::OnDispatch() { OnGatewayMessage(msg); } +RemoteAuthClient::type_signal_hello RemoteAuthClient::signal_hello() { + return m_signal_hello; +} + RemoteAuthClient::type_signal_fingerprint RemoteAuthClient::signal_fingerprint() { return m_signal_fingerprint; } +RemoteAuthClient::type_signal_pending_ticket RemoteAuthClient::signal_pending_ticket() { + return m_signal_pending_ticket; +} + +RemoteAuthClient::type_signal_pending_login RemoteAuthClient::signal_pending_login() { + return m_signal_pending_login; +} + RemoteAuthClient::type_signal_token RemoteAuthClient::signal_token() { return m_signal_token; } + +RemoteAuthClient::type_signal_error RemoteAuthClient::signal_error() { + return m_signal_error; +} diff --git a/src/remoteauth/remoteauthclient.hpp b/src/remoteauth/remoteauthclient.hpp index 8fb8bc3..7d7dee9 100644 --- a/src/remoteauth/remoteauthclient.hpp +++ b/src/remoteauth/remoteauthclient.hpp @@ -59,12 +59,24 @@ private: EVP_PKEY_ptr m_pkey; public: + using type_signal_hello = sigc::signal; using type_signal_fingerprint = sigc::signal; + using type_signal_pending_ticket = sigc::signal; + using type_signal_pending_login = sigc::signal; using type_signal_token = sigc::signal; + using type_signal_error = sigc::signal; + type_signal_hello signal_hello(); type_signal_fingerprint signal_fingerprint(); + type_signal_pending_ticket signal_pending_ticket(); + type_signal_pending_login signal_pending_login(); type_signal_token signal_token(); + type_signal_error signal_error(); private: + type_signal_hello m_signal_hello; type_signal_fingerprint m_signal_fingerprint; + type_signal_pending_ticket m_signal_pending_ticket; + type_signal_pending_login m_signal_pending_login; type_signal_token m_signal_token; + type_signal_error m_signal_error; }; diff --git a/src/remoteauth/remoteauthdialog.cpp b/src/remoteauth/remoteauthdialog.cpp index ea250d6..c59f0ed 100644 --- a/src/remoteauth/remoteauthdialog.cpp +++ b/src/remoteauth/remoteauthdialog.cpp @@ -23,21 +23,40 @@ RemoteAuthDialog::RemoteAuthDialog(Gtk::Window &parent) m_bbox.pack_start(m_cancel, Gtk::PACK_SHRINK); m_bbox.set_layout(Gtk::BUTTONBOX_END); + m_ra.signal_hello().connect(sigc::mem_fun(*this, &RemoteAuthDialog::OnHello)); m_ra.signal_fingerprint().connect(sigc::mem_fun(*this, &RemoteAuthDialog::OnFingerprint)); + m_ra.signal_pending_ticket().connect(sigc::mem_fun(*this, &RemoteAuthDialog::OnPendingTicket)); + m_ra.signal_pending_login().connect(sigc::mem_fun(*this, &RemoteAuthDialog::OnPendingLogin)); m_ra.signal_token().connect(sigc::mem_fun(*this, &RemoteAuthDialog::OnToken)); + m_ra.signal_error().connect(sigc::mem_fun(*this, &RemoteAuthDialog::OnError)); m_ra.Start(); m_image.set_size_request(256, 256); + m_status.set_text("Connecting..."); + m_status.set_hexpand(true); + m_status.set_halign(Gtk::ALIGN_CENTER); + m_layout.add(m_image); + m_layout.add(m_status); m_layout.add(m_bbox); get_content_area()->add(m_layout); show_all_children(); } +std::string RemoteAuthDialog::GetToken() { + return m_token; +} + +void RemoteAuthDialog::OnHello() { + m_status.set_text("Handshaking..."); +} + void RemoteAuthDialog::OnFingerprint(const std::string &fingerprint) { + m_status.set_text("Waiting for mobile device..."); + const auto url = "https://discord.com/ra/" + fingerprint; const auto level = qrcodegen::QrCode::Ecc::QUARTILE; @@ -75,8 +94,24 @@ void RemoteAuthDialog::OnFingerprint(const std::string &fingerprint) { m_image.property_pixbuf() = pb; } -std::string RemoteAuthDialog::GetToken() { - return m_token; +void RemoteAuthDialog::OnPendingTicket(Snowflake user_id, std::string discriminator, std::string avatar_hash, std::string username) { + Glib::ustring name = username; + if (discriminator != "0") { + name += "#" + discriminator; + } + m_status.set_text("Waiting for confirmation... (" + name + ")"); + + if (!avatar_hash.empty()) { + const auto url = "https://cdn.discordapp.com/avatars/" + std::to_string(user_id) + "/" + avatar_hash + ".png?size=256"; + const auto cb = [this](const Glib::RefPtr &pb) { + m_image.property_pixbuf() = pb->scale_simple(256, 256, Gdk::INTERP_BILINEAR); + }; + Abaddon::Get().GetImageManager().LoadFromURL(url, sigc::track_obj(cb, *this)); + } +} + +void RemoteAuthDialog::OnPendingLogin() { + m_status.set_text("Logging in!"); } void RemoteAuthDialog::OnToken(const std::string &token) { @@ -84,3 +119,9 @@ void RemoteAuthDialog::OnToken(const std::string &token) { m_ra.Stop(); response(Gtk::RESPONSE_OK); } + +void RemoteAuthDialog::OnError(const std::string &error) { + m_ra.Stop(); + Abaddon::Get().ShowConfirm(error, dynamic_cast(get_toplevel())); + response(Gtk::RESPONSE_CANCEL); +} diff --git a/src/remoteauth/remoteauthdialog.hpp b/src/remoteauth/remoteauthdialog.hpp index 44af821..be559df 100644 --- a/src/remoteauth/remoteauthdialog.hpp +++ b/src/remoteauth/remoteauthdialog.hpp @@ -9,6 +9,7 @@ public: protected: Gtk::Image m_image; + Gtk::Label m_status; Gtk::Box m_layout; Gtk::Button m_ok; Gtk::Button m_cancel; @@ -17,8 +18,12 @@ protected: private: RemoteAuthClient m_ra; + void OnHello(); void OnFingerprint(const std::string &fingerprint); + void OnPendingTicket(Snowflake user_id, std::string discriminator, std::string avatar_hash, std::string username); + void OnPendingLogin(); void OnToken(const std::string &token); + void OnError(const std::string &error); std::string m_token; }; From 060e79ff2af65c46b5a53606784345b570118f2b Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 1 Jul 2023 22:27:57 -0400 Subject: [PATCH 32/52] search for system miniaudio.h too --- CMakeLists.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index de97c32..d7f8687 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -165,6 +165,11 @@ if (${ENABLE_NOTIFICATION_SOUNDS}) endif () if (USE_MINIAUDIO) - target_include_directories(abaddon PUBLIC subprojects/miniaudio) + find_path(MINIAUDIO_INCLUDE_DIR + NAMES miniaudio.h + HINTS subprojects + PATH_SUFFIXES miniaudio + REQUIRED) + target_include_directories(abaddon PUBLIC ${MINIAUDIO_INCLUDE_DIR}) target_compile_definitions(abaddon PRIVATE WITH_MINIAUDIO) endif () From 3f6be457b121b0388874f8263ec0ab0be9a0c46e Mon Sep 17 00:00:00 2001 From: Jerzy Kozera <120114+jkozera@users.noreply.github.com> Date: Tue, 4 Jul 2023 01:15:05 +0200 Subject: [PATCH 33/52] Show unread indicators next to categories containing unread channels --- src/components/channelscellrenderer.cpp | 27 ++++++++++++++++--------- src/discord/discord.cpp | 10 +++++++++ src/discord/discord.hpp | 1 + 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index 6de7a00..ac98512 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -410,6 +410,17 @@ void CellRendererChannels::get_preferred_height_for_width_vfunc_category(Gtk::Wi m_renderer_text.get_preferred_height_for_width(widget, width, minimum_height, natural_height); } +void AddUnreadIndicator(const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area) { + static const auto color = Gdk::RGBA(Abaddon::Get().GetSettings().UnreadIndicatorColor); + cr->set_source_rgb(color.get_red(), color.get_green(), color.get_blue()); + const auto x = background_area.get_x(); + const auto y = background_area.get_y(); + const auto w = background_area.get_width(); + const auto h = background_area.get_height(); + cr->rectangle(x, y, 3, h); + cr->fill(); +} + void CellRendererChannels::render_vfunc_category(const Cairo::RefPtr &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) { // todo: figure out how Gtk::Arrow is rendered because i like it better :^) constexpr static int len = 5; @@ -447,13 +458,18 @@ void CellRendererChannels::render_vfunc_category(const Cairo::RefPtr 0) { + AddUnreadIndicator(cr, background_area); + } m_renderer_text.property_foreground_rgba() = color; } m_renderer_text.render(cr, widget, background_area, text_cell_area, flags); @@ -519,14 +535,7 @@ void CellRendererChannels::render_vfunc_channel(const Cairo::RefPtrset_source_rgb(color.get_red(), color.get_green(), color.get_blue()); - const auto x = background_area.get_x(); - const auto y = background_area.get_y(); - const auto w = background_area.get_width(); - const auto h = background_area.get_height(); - cr->rectangle(x, y, 3, h); - cr->fill(); + AddUnreadIndicator(cr, background_area); } if (unread_state < 1) return; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 817aca8..1f54c7e 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1317,6 +1317,16 @@ int DiscordClient::GetUnreadStateForChannel(Snowflake id) const noexcept { return iter->second; } +int DiscordClient::GetUnreadChannelsCountForCategory(Snowflake id) const noexcept { + int result = 0; + for (Snowflake channel_id : m_store.GetChannelIDsWithParentID(id)) { + const auto iter = m_unread.find(channel_id); + if (iter == m_unread.end()) continue; + result += 1; + } + return result; +} + bool DiscordClient::GetUnreadStateForGuild(Snowflake id, int &total_mentions) const noexcept { total_mentions = 0; bool has_any_unread = false; diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index d2435dd..29ac00c 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -214,6 +214,7 @@ public: bool IsChannelMuted(Snowflake id) const noexcept; bool IsGuildMuted(Snowflake id) const noexcept; int GetUnreadStateForChannel(Snowflake id) const noexcept; + int GetUnreadChannelsCountForCategory(Snowflake id) const noexcept; bool GetUnreadStateForGuild(Snowflake id, int &total_mentions) const noexcept; int GetUnreadDMsCount() const; From cf02c1395271fd6c127fa2ed6557b3aeb279e5fe Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 3 Jul 2023 20:01:53 -0400 Subject: [PATCH 34/52] use ppm instead of svg for generating qr pixbuf --- src/remoteauth/remoteauthdialog.cpp | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/remoteauth/remoteauthdialog.cpp b/src/remoteauth/remoteauthdialog.cpp index c59f0ed..5ba91ae 100644 --- a/src/remoteauth/remoteauthdialog.cpp +++ b/src/remoteauth/remoteauthdialog.cpp @@ -65,29 +65,26 @@ void RemoteAuthDialog::OnFingerprint(const std::string &fingerprint) { int size = qr.getSize(); const int border = 4; + const auto module_set = "0 0 0"; + const auto module_clr = "255 255 255"; + std::ostringstream sb; - sb << "\n"; - sb << "\n"; - sb << "\n"; - sb << "\t\n"; - sb << "\t\n"; - sb << "\n"; - const auto svg = sb.str(); + const auto img = sb.str(); auto loader = Gdk::PixbufLoader::create(); - loader->write(reinterpret_cast(svg.data()), svg.size()); + loader->write(reinterpret_cast(img.data()), img.size()); loader->close(); const auto pb = loader->get_pixbuf()->scale_simple(256, 256, Gdk::INTERP_NEAREST); From bdad178c5d6c5d4cdd9cc80fadb7f75e2bc6aa80 Mon Sep 17 00:00:00 2001 From: Jerzy Kozera <120114+jkozera@users.noreply.github.com> Date: Tue, 4 Jul 2023 22:00:22 +0200 Subject: [PATCH 35/52] Redraw unread indicator after message status for category changes --- src/components/channels.cpp | 20 ++++++++++++++------ src/components/channels.hpp | 3 ++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/components/channels.cpp b/src/components/channels.cpp index 26f3259..9fd4abd 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -1102,15 +1102,25 @@ void ChannelList::SetDMChannelIcon(Gtk::TreeIter iter, const ChannelData &dm) { } } +void ChannelList::RedrawUnreadIndicatorsForChannel(const ChannelData &channel) { + if (channel.GuildID.has_value()) { + auto iter = GetIteratorForGuildFromID(*channel.GuildID); + if (iter) m_model->row_changed(m_model->get_path(iter), iter); + } + if (channel.ParentID.has_value()) { + auto iter = GetIteratorForRowFromIDOfType(*channel.ParentID, RenderType::Category); + if (iter) m_model->row_changed(m_model->get_path(iter), iter); + } +} + void ChannelList::OnMessageAck(const MessageAckData &data) { // trick renderer into redrawing m_model->row_changed(Gtk::TreeModel::Path("0"), m_model->get_iter("0")); // 0 is always path for dm header auto iter = GetIteratorForRowFromID(data.ChannelID); if (iter) m_model->row_changed(m_model->get_path(iter), iter); auto channel = Abaddon::Get().GetDiscordClient().GetChannel(data.ChannelID); - if (channel.has_value() && channel->GuildID.has_value()) { - iter = GetIteratorForGuildFromID(*channel->GuildID); - if (iter) m_model->row_changed(m_model->get_path(iter), iter); + if (channel.has_value()) { + RedrawUnreadIndicatorsForChannel(*channel); } } @@ -1123,9 +1133,7 @@ void ChannelList::OnMessageCreate(const Message &msg) { if (iter) (*iter)[m_columns.m_sort] = static_cast(-msg.ID); } - if (channel->GuildID.has_value()) - if ((iter = GetIteratorForGuildFromID(*channel->GuildID))) - m_model->row_changed(m_model->get_path(iter), iter); + RedrawUnreadIndicatorsForChannel(*channel); } bool ChannelList::OnButtonPressEvent(GdkEventButton *ev) { diff --git a/src/components/channels.hpp b/src/components/channels.hpp index d561737..9d449e4 100644 --- a/src/components/channels.hpp +++ b/src/components/channels.hpp @@ -116,9 +116,10 @@ protected: void UpdateCreateDMChannel(const ChannelData &channel); void SetDMChannelIcon(Gtk::TreeIter iter, const ChannelData &dm); + void RedrawUnreadIndicatorsForChannel(const ChannelData& channel); void OnMessageAck(const MessageAckData &data); - void OnMessageCreate(const Message &msg); + Gtk::TreeModel::Path m_path_for_menu; // cant be recovered through selection From 88da9e17b86ae0315ff4c08bb22e7c78f6a19000 Mon Sep 17 00:00:00 2001 From: Jerzy Kozera <120114+jkozera@users.noreply.github.com> Date: Wed, 5 Jul 2023 01:25:22 +0200 Subject: [PATCH 36/52] Don't count muted channels towards unread state of category --- src/discord/discord.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 1f54c7e..7b05b2d 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1320,6 +1320,7 @@ int DiscordClient::GetUnreadStateForChannel(Snowflake id) const noexcept { int DiscordClient::GetUnreadChannelsCountForCategory(Snowflake id) const noexcept { int result = 0; for (Snowflake channel_id : m_store.GetChannelIDsWithParentID(id)) { + if (IsChannelMuted(channel_id)) continue; const auto iter = m_unread.find(channel_id); if (iter == m_unread.end()) continue; result += 1; From 9a3f6b472d4995c0d5619edc185836f7abb3bc15 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 6 Jul 2023 19:42:08 -0400 Subject: [PATCH 37/52] fix bad optional access in channel delete (fixes #187) --- src/discord/discord.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 7b05b2d..d97f2c1 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1826,9 +1826,12 @@ void DiscordClient::HandleGatewayPresenceUpdate(const GatewayMessage &msg) { void DiscordClient::HandleGatewayChannelDelete(const GatewayMessage &msg) { const auto id = msg.Data.at("id").get(); const auto channel = GetChannel(id); - auto it = m_guild_to_channels.find(*channel->GuildID); - if (it != m_guild_to_channels.end()) - it->second.erase(id); + if (channel.has_value() && channel->GuildID.has_value()) { + auto it = m_guild_to_channels.find(*channel->GuildID); + if (it != m_guild_to_channels.end()) { + it->second.erase(id); + } + } m_store.ClearChannel(id); m_signal_channel_delete.emit(id); m_signal_channel_accessibility_changed.emit(id, false); From e0623281eb7fcdc1d04c44b4cd3a30dbffc97134 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 9 Jul 2023 21:20:36 -0400 Subject: [PATCH 38/52] fix typo in heartbeat --- src/remoteauth/remoteauthclient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/remoteauth/remoteauthclient.cpp b/src/remoteauth/remoteauthclient.cpp index e2d3cc6..c0a06d9 100644 --- a/src/remoteauth/remoteauthclient.cpp +++ b/src/remoteauth/remoteauthclient.cpp @@ -277,7 +277,7 @@ void RemoteAuthClient::HeartbeatThread() { if (!m_heartbeat_waiter.wait_for(std::chrono::milliseconds(m_heartbeat_msec))) break; nlohmann::json hb; - hb["op"] = "hearbeat"; + hb["op"] = "heartbeat"; m_ws.Send(hb); } } From aaa219ce27561bfe060bee5a4eaba0326bc50412 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 9 Jul 2023 21:21:29 -0400 Subject: [PATCH 39/52] restart ra ws on timeout --- src/remoteauth/remoteauthclient.cpp | 11 +++++++++++ src/remoteauth/remoteauthclient.hpp | 3 +++ src/remoteauth/remoteauthdialog.cpp | 4 ++-- src/remoteauth/remoteauthdialog.hpp | 2 +- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/remoteauth/remoteauthclient.cpp b/src/remoteauth/remoteauthclient.cpp index c0a06d9..083bdaa 100644 --- a/src/remoteauth/remoteauthclient.cpp +++ b/src/remoteauth/remoteauthclient.cpp @@ -34,6 +34,7 @@ void RemoteAuthClient::Stop() { } m_connected = false; + if (m_timeout_conn) m_timeout_conn.disconnect(); m_ws.Stop(1000); m_heartbeat_waiter.kill(); if (m_heartbeat_thread.joinable()) m_heartbeat_thread.join(); @@ -44,6 +45,7 @@ bool RemoteAuthClient::IsConnected() const noexcept { } void RemoteAuthClient::OnGatewayMessage(const std::string &str) { + m_log->trace(str); auto j = nlohmann::json::parse(str); const auto opcode = j.at("op").get(); if (opcode == "hello") { @@ -67,6 +69,8 @@ void RemoteAuthClient::HandleGatewayHello(const nlohmann::json &j) { m_heartbeat_msec = heartbeat_interval; m_heartbeat_thread = std::thread(&RemoteAuthClient::HeartbeatThread, this); + m_timeout_conn = Glib::signal_timeout().connect(sigc::mem_fun(*this, &RemoteAuthClient::OnTimeout), timeout_ms); + Init(); m_signal_hello.emit(); @@ -294,6 +298,13 @@ void RemoteAuthClient::OnDispatch() { OnGatewayMessage(msg); } +bool RemoteAuthClient::OnTimeout() { + m_log->trace("Socket timeout"); + Stop(); + Start(); + return false; // disconnect +} + RemoteAuthClient::type_signal_hello RemoteAuthClient::signal_hello() { return m_signal_hello; } diff --git a/src/remoteauth/remoteauthclient.hpp b/src/remoteauth/remoteauthclient.hpp index 7d7dee9..36f224a 100644 --- a/src/remoteauth/remoteauthclient.hpp +++ b/src/remoteauth/remoteauthclient.hpp @@ -49,6 +49,9 @@ private: void OnDispatch(); + bool OnTimeout(); + sigc::connection m_timeout_conn; + Websocket m_ws; bool m_connected = false; diff --git a/src/remoteauth/remoteauthdialog.cpp b/src/remoteauth/remoteauthdialog.cpp index 5ba91ae..547eb39 100644 --- a/src/remoteauth/remoteauthdialog.cpp +++ b/src/remoteauth/remoteauthdialog.cpp @@ -91,7 +91,7 @@ void RemoteAuthDialog::OnFingerprint(const std::string &fingerprint) { m_image.property_pixbuf() = pb; } -void RemoteAuthDialog::OnPendingTicket(Snowflake user_id, std::string discriminator, std::string avatar_hash, std::string username) { +void RemoteAuthDialog::OnPendingTicket(Snowflake user_id, const std::string &discriminator, const std::string &avatar_hash, const std::string &username) { Glib::ustring name = username; if (discriminator != "0") { name += "#" + discriminator; @@ -119,6 +119,6 @@ void RemoteAuthDialog::OnToken(const std::string &token) { void RemoteAuthDialog::OnError(const std::string &error) { m_ra.Stop(); - Abaddon::Get().ShowConfirm(error, dynamic_cast(get_toplevel())); + Abaddon::Get().ShowConfirm(error, dynamic_cast(get_toplevel())); response(Gtk::RESPONSE_CANCEL); } diff --git a/src/remoteauth/remoteauthdialog.hpp b/src/remoteauth/remoteauthdialog.hpp index be559df..9a6ca29 100644 --- a/src/remoteauth/remoteauthdialog.hpp +++ b/src/remoteauth/remoteauthdialog.hpp @@ -20,7 +20,7 @@ private: void OnHello(); void OnFingerprint(const std::string &fingerprint); - void OnPendingTicket(Snowflake user_id, std::string discriminator, std::string avatar_hash, std::string username); + void OnPendingTicket(Snowflake user_id, const std::string &discriminator, const std::string &avatar_hash, const std::string &username); void OnPendingLogin(); void OnToken(const std::string &token); void OnError(const std::string &error); From 3c2fde661e0d8240fa03290c28e1475330ca15b2 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 9 Jul 2023 21:27:08 -0400 Subject: [PATCH 40/52] emit remoteauth error on unexpected close --- src/remoteauth/remoteauthclient.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/remoteauth/remoteauthclient.cpp b/src/remoteauth/remoteauthclient.cpp index 083bdaa..7f41d71 100644 --- a/src/remoteauth/remoteauthclient.cpp +++ b/src/remoteauth/remoteauthclient.cpp @@ -264,8 +264,14 @@ void RemoteAuthClient::OnWebsocketOpen() { void RemoteAuthClient::OnWebsocketClose(const ix::WebSocketCloseInfo &info) { if (info.remote) { m_log->debug("Websocket closed (remote): {} ({})", info.code, info.reason); + if (m_connected) { + m_signal_error.emit("Error. Websocket closed (remote): " + std::to_string(info.code) + " (" + info.reason + ")"); + } } else { m_log->debug("Websocket closed (local): {} ({})", info.code, info.reason); + if (m_connected) { + m_signal_error.emit("Error. Websocket closed (local): " + std::to_string(info.code) + " (" + info.reason + ")"); + } } } From 2ca5a21ab4a5c03ea8c8a584ca832d01d8661691 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 9 Jul 2023 21:49:15 -0400 Subject: [PATCH 41/52] restart remote auth on cancel opcode --- src/remoteauth/remoteauthclient.cpp | 7 +++++++ src/remoteauth/remoteauthclient.hpp | 1 + 2 files changed, 8 insertions(+) diff --git a/src/remoteauth/remoteauthclient.cpp b/src/remoteauth/remoteauthclient.cpp index 7f41d71..467ea8b 100644 --- a/src/remoteauth/remoteauthclient.cpp +++ b/src/remoteauth/remoteauthclient.cpp @@ -58,6 +58,8 @@ void RemoteAuthClient::OnGatewayMessage(const std::string &str) { HandleGatewayPendingTicket(j); } else if (opcode == "pending_login") { HandleGatewayPendingLogin(j); + } else if (opcode == "cancel") { + HandleGatewayCancel(j); } } @@ -125,6 +127,11 @@ void RemoteAuthClient::HandleGatewayPendingLogin(const nlohmann::json &j) { m_signal_pending_login.emit(); } +void RemoteAuthClient::HandleGatewayCancel(const nlohmann::json &j) { + Stop(); + Start(); +} + void RemoteAuthClient::OnRemoteAuthLoginResponse(const std::optional &encrypted_token, DiscordError err) { if (!encrypted_token.has_value()) { m_log->error("Remote auth login failed: {}", static_cast(err)); diff --git a/src/remoteauth/remoteauthclient.hpp b/src/remoteauth/remoteauthclient.hpp index 36f224a..c2e00f3 100644 --- a/src/remoteauth/remoteauthclient.hpp +++ b/src/remoteauth/remoteauthclient.hpp @@ -23,6 +23,7 @@ private: void HandleGatewayPendingRemoteInit(const nlohmann::json &j); void HandleGatewayPendingTicket(const nlohmann::json &j); void HandleGatewayPendingLogin(const nlohmann::json &j); + void HandleGatewayCancel(const nlohmann::json &j); void OnRemoteAuthLoginResponse(const std::optional &encrypted_token, DiscordError err); From b5d85f4958601ef512dfc9fe302b4b9bfcda25f1 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 9 Jul 2023 21:58:05 -0400 Subject: [PATCH 42/52] use another color for qr code cuz im special --- src/remoteauth/remoteauthdialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/remoteauth/remoteauthdialog.cpp b/src/remoteauth/remoteauthdialog.cpp index 547eb39..7ee045f 100644 --- a/src/remoteauth/remoteauthdialog.cpp +++ b/src/remoteauth/remoteauthdialog.cpp @@ -65,7 +65,7 @@ void RemoteAuthDialog::OnFingerprint(const std::string &fingerprint) { int size = qr.getSize(); const int border = 4; - const auto module_set = "0 0 0"; + const auto module_set = "192 0 255"; const auto module_clr = "255 255 255"; std::ostringstream sb; From 229555939d697700e988f09f04871bde55adc5a7 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 9 Jul 2023 22:31:21 -0400 Subject: [PATCH 43/52] make qrcodegen optional wow!!! --- CMakeLists.txt | 12 ++++++++---- src/abaddon.cpp | 2 ++ src/remoteauth/remoteauthclient.cpp | 8 ++++++++ src/remoteauth/remoteauthclient.hpp | 9 +++++++++ src/remoteauth/remoteauthdialog.cpp | 8 ++++++++ src/remoteauth/remoteauthdialog.hpp | 9 +++++++++ src/windows/mainwindow.cpp | 6 ++++++ 7 files changed, 50 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b85e6c1..7a782ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ option(USE_LIBHANDY "Enable features that require libhandy (default)" ON) option(ENABLE_VOICE "Enable voice suppport" ON) option(USE_KEYCHAIN "Store the token in the keychain (default)" ON) option(ENABLE_NOTIFICATION_SOUNDS "Enable notification sounds (default)" ON) +option(ENABLE_QRCODE_LOGIN "Enable QR code login (default)" ON) find_package(nlohmann_json REQUIRED) find_package(CURL) @@ -61,11 +62,14 @@ target_include_directories(abaddon PUBLIC ${ZLIB_INCLUDE_DIRS}) target_include_directories(abaddon PUBLIC ${SQLite3_INCLUDE_DIRS}) target_include_directories(abaddon PUBLIC ${NLOHMANN_JSON_INCLUDE_DIRS}) -add_library(qrcodegen subprojects/qrcodegen/cpp/qrcodegen.hpp subprojects/qrcodegen/cpp/qrcodegen.cpp) -target_include_directories(qrcodegen PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/subprojects/qrcodegen/cpp") -target_link_libraries(abaddon qrcodegen) +if (ENABLE_QRCODE_LOGIN) + add_library(qrcodegen subprojects/qrcodegen/cpp/qrcodegen.hpp subprojects/qrcodegen/cpp/qrcodegen.cpp) + target_include_directories(qrcodegen PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/subprojects/qrcodegen/cpp") + target_link_libraries(abaddon qrcodegen) -target_include_directories(abaddon PUBLIC "subprojects/qrcodegen/cpp") + target_include_directories(abaddon PUBLIC "subprojects/qrcodegen/cpp") + target_compile_definitions(abaddon PRIVATE WITH_QRLOGIN) +endif () target_precompile_headers(abaddon PRIVATE src/abaddon.hpp src/util.hpp) diff --git a/src/abaddon.cpp b/src/abaddon.cpp index c161595..921549b 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -837,6 +837,7 @@ void Abaddon::ActionSetToken() { } void Abaddon::ActionLoginQR() { +#ifdef WITH_QRLOGIN RemoteAuthDialog dlg(*m_main_window); auto response = dlg.run(); if (response == Gtk::RESPONSE_OK) { @@ -847,6 +848,7 @@ void Abaddon::ActionLoginQR() { ActionConnect(); } m_main_window->UpdateMenus(); +#endif } void Abaddon::ActionChannelOpened(Snowflake id, bool expand_to) { diff --git a/src/remoteauth/remoteauthclient.cpp b/src/remoteauth/remoteauthclient.cpp index 467ea8b..7653b78 100644 --- a/src/remoteauth/remoteauthclient.cpp +++ b/src/remoteauth/remoteauthclient.cpp @@ -1,8 +1,14 @@ +#ifdef WITH_QRLOGIN + +// clang-format off + #include "remoteauthclient.hpp" #include "http.hpp" #include #include +// clang-format on + RemoteAuthClient::RemoteAuthClient() : m_ws("remote-auth-ws") , m_log(spdlog::get("remote-auth")) { @@ -341,3 +347,5 @@ RemoteAuthClient::type_signal_token RemoteAuthClient::signal_token() { RemoteAuthClient::type_signal_error RemoteAuthClient::signal_error() { return m_signal_error; } + +#endif diff --git a/src/remoteauth/remoteauthclient.hpp b/src/remoteauth/remoteauthclient.hpp index c2e00f3..6ab6dbb 100644 --- a/src/remoteauth/remoteauthclient.hpp +++ b/src/remoteauth/remoteauthclient.hpp @@ -1,4 +1,9 @@ #pragma once + +#ifdef WITH_QRLOGIN + +// clang-format off + #include #include #include @@ -6,6 +11,8 @@ #include "discord/waiter.hpp" #include "discord/websocket.hpp" +// clang-format on + class RemoteAuthClient { public: RemoteAuthClient(); @@ -84,3 +91,5 @@ private: type_signal_token m_signal_token; type_signal_error m_signal_error; }; + +#endif diff --git a/src/remoteauth/remoteauthdialog.cpp b/src/remoteauth/remoteauthdialog.cpp index 7ee045f..7975b4e 100644 --- a/src/remoteauth/remoteauthdialog.cpp +++ b/src/remoteauth/remoteauthdialog.cpp @@ -1,6 +1,12 @@ +#ifdef WITH_QRLOGIN + +// clang-format off + #include "remoteauthdialog.hpp" #include +// clang-format on + RemoteAuthDialog::RemoteAuthDialog(Gtk::Window &parent) : Gtk::Dialog("Login with QR Code", parent, true) , m_layout(Gtk::ORIENTATION_VERTICAL) @@ -122,3 +128,5 @@ void RemoteAuthDialog::OnError(const std::string &error) { Abaddon::Get().ShowConfirm(error, dynamic_cast(get_toplevel())); response(Gtk::RESPONSE_CANCEL); } + +#endif diff --git a/src/remoteauth/remoteauthdialog.hpp b/src/remoteauth/remoteauthdialog.hpp index 9a6ca29..465a188 100644 --- a/src/remoteauth/remoteauthdialog.hpp +++ b/src/remoteauth/remoteauthdialog.hpp @@ -1,7 +1,14 @@ #pragma once + +#ifdef WITH_QRLOGIN + +// clang-format off + #include #include "remoteauthclient.hpp" +// clang-format on + class RemoteAuthDialog : public Gtk::Dialog { public: RemoteAuthDialog(Gtk::Window &parent); @@ -27,3 +34,5 @@ private: std::string m_token; }; + +#endif diff --git a/src/windows/mainwindow.cpp b/src/windows/mainwindow.cpp index 56d735e..0b21567 100644 --- a/src/windows/mainwindow.cpp +++ b/src/windows/mainwindow.cpp @@ -206,7 +206,9 @@ void MainWindow::OnDiscordSubmenuPopup() { m_menu_discord_connect.set_sensitive(!token.empty() && !discord_active); m_menu_discord_disconnect.set_sensitive(discord_active); m_menu_discord_set_token.set_sensitive(!discord_active); +#ifdef WITH_QRLOGIN m_menu_discord_login_qr.set_sensitive(!discord_active); +#endif m_menu_discord_set_status.set_sensitive(discord_active); } @@ -249,6 +251,10 @@ void MainWindow::SetupMenu() { m_menu_discord_disconnect.set_sensitive(false); m_menu_discord_set_token.set_label("Set Token"); m_menu_discord_login_qr.set_label("Login with QR Code"); +#ifndef WITH_QRLOGIN + m_menu_discord_login_qr.set_sensitive(false); + m_menu_discord_login_qr.set_tooltip_text("Not compiled with support"); +#endif m_menu_discord_set_status.set_label("Set Status"); m_menu_discord_set_status.set_sensitive(false); m_menu_discord_add_recipient.set_label("Add user to DM"); From 5bf5cc75eb8cffb3aed44a6743f1bb87a7b2b00d Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 11 Jul 2023 20:57:09 -0400 Subject: [PATCH 44/52] add qrcode to mindeps ci run --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 116a800..6cfb8b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,7 +67,7 @@ jobs: with: cond: ${{ matrix.mindeps == true }} if_true: | - cmake -GNinja -Bbuild -DCMAKE_BUILD_TYPE=${{ matrix.buildtype }} -DUSE_LIBHANDY=OFF -DENABLE_VOICE=OFF -DENABLE_NOTIFICATION_SOUNDS=OFF + cmake -GNinja -Bbuild -DCMAKE_BUILD_TYPE=${{ matrix.buildtype }} -DUSE_LIBHANDY=OFF -DENABLE_VOICE=OFF -DENABLE_NOTIFICATION_SOUNDS=OFF -DENABLE_QRCODE_LOGIN=OFF cmake --build build if_false: | cmake -GNinja -Bbuild -DCMAKE_BUILD_TYPE=${{ matrix.buildtype }} -DCMAKE_CXX_FLAGS="-Wl,--default-image-base-low" From 59e16cbb3ba6c109fff5a714ae4ff40def18ed24 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 13 Jul 2023 18:01:03 -0400 Subject: [PATCH 45/52] voice_states can be missing --- src/discord/objects.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/discord/objects.cpp b/src/discord/objects.cpp index 63bd43f..cb0a685 100644 --- a/src/discord/objects.cpp +++ b/src/discord/objects.cpp @@ -251,7 +251,7 @@ void from_json(const nlohmann::json &j, SupplementalMergedPresencesData &m) { void from_json(const nlohmann::json &j, SupplementalGuildEntry &m) { JS_D("id", m.ID); - JS_D("voice_states", m.VoiceStates); + JS_ON("voice_states", m.VoiceStates); } void from_json(const nlohmann::json &j, ReadySupplementalData &m) { @@ -691,7 +691,7 @@ void from_json(const nlohmann::json &j, VoiceServerUpdateData &m) { void from_json(const nlohmann::json &j, CallCreateData &m) { JS_D("channel_id", m.ChannelID); - JS_D("voice_states", m.VoiceStates); + JS_ON("voice_states", m.VoiceStates); } #endif From 52a340e3666e4081b5f284c320404d06ac37b2f3 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 13 Jul 2023 18:53:47 -0400 Subject: [PATCH 46/52] fix attachments and replies breaking when loading message history --- src/components/chatmessage.cpp | 3 +- src/discord/store.cpp | 71 +++++++++++++++------------------- 2 files changed, 34 insertions(+), 40 deletions(-) diff --git a/src/components/chatmessage.cpp b/src/components/chatmessage.cpp index 4fde039..23ee36f 100644 --- a/src/components/chatmessage.cpp +++ b/src/components/chatmessage.cpp @@ -674,8 +674,9 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data) std::optional> referenced_message = data.ReferencedMessage; if (data.MessageReference.has_value() && data.MessageReference->MessageID.has_value() && !referenced_message.has_value()) { auto refd = discord.GetMessage(*data.MessageReference->MessageID); - if (refd.has_value()) + if (refd.has_value()) { referenced_message = std::make_shared(std::move(*refd)); + } } if (data.Interaction.has_value()) { diff --git a/src/discord/store.cpp b/src/discord/store.cpp index 24b686a..7ee4d87 100644 --- a/src/discord/store.cpp +++ b/src/discord/store.cpp @@ -559,8 +559,9 @@ std::vector Store::GetMessagesBefore(Snowflake channel_id, Snowflake me for (auto &msg : msgs) { if (msg.MessageReference.has_value() && msg.MessageReference->MessageID.has_value()) { auto ref = GetMessage(*msg.MessageReference->MessageID); - if (ref.has_value()) + if (ref.has_value()) { msg.ReferencedMessage = std::make_shared(std::move(*ref)); + } } } @@ -2060,26 +2061,22 @@ bool Store::CreateStatements() { message_interactions.name, message_interactions.type, message_interactions.user_id, - attachments.id, - attachments.filename, - attachments.size, - attachments.url, - attachments.proxy, - attachments.height, - attachments.width, - message_references.message + message_references.message, + message_references.channel, + message_references.guild, + COUNT(attachments.id) FROM messages LEFT OUTER JOIN message_interactions ON messages.id = message_interactions.message_id - LEFT OUTER JOIN - attachments - ON messages.id = attachments.message LEFT OUTER JOIN message_references ON messages.id = message_references.id + LEFT OUTER JOIN + attachments + ON messages.id = attachments.message WHERE channel_id = ? AND pending = 0 AND messages.id < ? ORDER BY id DESC LIMIT ? - ) ORDER BY id ASC + ) WHERE id IS NOT NULL ORDER BY id ASC )"); if (!m_stmt_get_messages_before->OK()) { fprintf(stderr, "failed to prepare get messages before statement: %s\n", m_db.ErrStr()); @@ -2087,32 +2084,28 @@ bool Store::CreateStatements() { } m_stmt_get_pins = std::make_unique(m_db, R"( - SELECT messages.*, - message_interactions.interaction_id, - message_interactions.name, - message_interactions.type, - message_interactions.user_id, - attachments.id, - attachments.filename, - attachments.size, - attachments.url, - attachments.proxy, - attachments.height, - attachments.width, - message_references.message, - message_references.channel, - message_references.guild - FROM messages - LEFT OUTER JOIN - message_interactions - ON messages.id = message_interactions.message_id - LEFT OUTER JOIN - attachments - ON messages.id = attachments.message - LEFT OUTER JOIN - message_references - ON messages.id = message_references.id - WHERE channel_id = ? AND pinned = 1 ORDER BY id ASC + SELECT * FROM ( + SELECT messages.*, + message_interactions.interaction_id, + message_interactions.name, + message_interactions.type, + message_interactions.user_id, + message_references.message, + message_references.channel, + message_references.guild, + COUNT(attachments.id) + FROM messages + LEFT OUTER JOIN + message_interactions + ON messages.id = message_interactions.message_id + LEFT OUTER JOIN + message_references + ON messages.id = message_references.id + LEFT OUTER JOIN + attachments + ON messages.id = attachments.message + WHERE channel_id = ? AND pinned = 1 ORDER BY id ASC + ) WHERE id IS NOT NULL )"); if (!m_stmt_get_pins->OK()) { fprintf(stderr, "failed to prepare get pins statement: %s\n", m_db.ErrStr()); From 00524cefa29e80f0a9a80fd77bb6e1f4da024f65 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 15 Jul 2023 01:02:35 -0400 Subject: [PATCH 47/52] make editing inline, add up arrow shortcut --- res/css/main.css | 4 +++ src/abaddon.cpp | 18 ++++------- src/components/chatinput.cpp | 10 ++++++ src/components/chatinput.hpp | 3 ++ src/components/chatlist.cpp | 17 ++++++++++ src/components/chatlist.hpp | 1 + src/components/chatwindow.cpp | 55 +++++++++++++++++++++++++++----- src/components/chatwindow.hpp | 7 ++++ src/dialogs/editmessage.cpp | 45 -------------------------- src/dialogs/editmessage.hpp | 20 ------------ src/discord/chatsubmitparams.hpp | 1 + src/windows/mainwindow.cpp | 4 +++ src/windows/mainwindow.hpp | 1 + 13 files changed, 102 insertions(+), 84 deletions(-) delete mode 100644 src/dialogs/editmessage.cpp delete mode 100644 src/dialogs/editmessage.hpp diff --git a/res/css/main.css b/res/css/main.css index 1b70301..b84b502 100644 --- a/res/css/main.css +++ b/res/css/main.css @@ -114,6 +114,10 @@ border: 1px solid #026FB9; } +.message-input.editing { + border: 1px solid #b9026f; +} + .message-input.bad-input { border: 1px solid #dd3300; } diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 921549b..2be54ba 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -8,7 +8,6 @@ #include "audio/manager.hpp" #include "discord/discord.hpp" #include "dialogs/token.hpp" -#include "dialogs/editmessage.hpp" #include "dialogs/confirm.hpp" #include "dialogs/setstatus.hpp" #include "dialogs/friendpicker.hpp" @@ -948,19 +947,15 @@ void Abaddon::ActionChatInputSubmit(ChatSubmitParams data) { if (!m_discord.HasChannelPermission(m_discord.GetUserData().ID, data.ChannelID, Permission::VIEW_CHANNEL)) return; - m_discord.SendChatMessage(data, NOOP_CALLBACK); + if (data.EditingID.IsValid()) { + m_discord.EditMessage(data.ChannelID, data.EditingID, data.Message); + } else { + m_discord.SendChatMessage(data, NOOP_CALLBACK); + } } 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); - } + m_main_window->EditMessage(id); } void Abaddon::ActionInsertMention(Snowflake id) { @@ -1156,6 +1151,7 @@ int main(int argc, char **argv) { #endif spdlog::cfg::load_env_levels(); + auto log_ui = spdlog::stdout_color_mt("ui"); auto log_audio = spdlog::stdout_color_mt("audio"); auto log_voice = spdlog::stdout_color_mt("voice"); auto log_discord = spdlog::stdout_color_mt("discord"); diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 28ed1ea..8ad0c3f 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -565,6 +565,16 @@ void ChatInput::StopReplying() { m_input.Get().get_style_context()->remove_class("replying"); } +void ChatInput::StartEditing(const Message &message) { + m_input.Get().grab_focus(); + m_input.Get().get_style_context()->add_class("editing"); + GetBuffer()->set_text(message.Content); +} + +void ChatInput::StopEditing() { + m_input.Get().get_style_context()->remove_class("editing"); +} + bool ChatInput::AddFileAsImageAttachment(const Glib::RefPtr &file) { try { const auto read_stream = file->read(); diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index bc6a45d..a7ee27f 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -139,6 +139,9 @@ public: void StartReplying(); void StopReplying(); + void StartEditing(const Message &message); + void StopEditing(); + private: bool AddFileAsImageAttachment(const Glib::RefPtr &file); bool CanAttachFiles(); diff --git a/src/components/chatlist.cpp b/src/components/chatlist.cpp index 93fb46f..28981b2 100644 --- a/src/components/chatlist.cpp +++ b/src/components/chatlist.cpp @@ -253,6 +253,23 @@ void ChatList::ActuallyRemoveMessage(Snowflake id) { RemoveMessageAndHeader(it->second); } +std::optional ChatList::GetLastSentMessage() { + const auto &discord = Abaddon::Get().GetDiscordClient(); + const auto self_id = discord.GetUserData().ID; + + std::map ordered(m_id_to_widget.begin(), m_id_to_widget.end()); + + for (auto it = ordered.crbegin(); it != ordered.crend(); it++) { + const auto *widget = dynamic_cast(it->second); + if (widget == nullptr) continue; + const auto msg = discord.GetMessage(widget->ID); + if (!msg.has_value()) continue; + if (msg->Author.ID == self_id) return msg->ID; + } + + return std::nullopt; +} + void ChatList::SetupMenu() { m_menu_copy_id = Gtk::manage(new Gtk::MenuItem("Copy ID")); m_menu_copy_id->signal_activate().connect([this] { diff --git a/src/components/chatlist.hpp b/src/components/chatlist.hpp index 0248d7f..5d4ec4a 100644 --- a/src/components/chatlist.hpp +++ b/src/components/chatlist.hpp @@ -23,6 +23,7 @@ public: void SetSeparateAll(bool separate); void SetUsePinnedMenu(); // i think i need a better way to do menus void ActuallyRemoveMessage(Snowflake id); // perhaps not the best method name + std::optional GetLastSentMessage(); private: void SetupMenu(); diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index e51d491..864d0c9 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -50,8 +50,8 @@ ChatWindow::ChatWindow() { m_input->signal_submit().connect(sigc::mem_fun(*this, &ChatWindow::OnInputSubmit)); m_input->signal_escape().connect([this]() { - if (m_is_replying) - StopReplying(); + if (m_is_replying) StopReplying(); + if (m_is_editing) StopEditing(); }); m_input->signal_key_press_event().connect(sigc::mem_fun(*this, &ChatWindow::OnKeyPressEvent), false); m_input->show(); @@ -132,8 +132,8 @@ void ChatWindow::SetActiveChannel(Snowflake id) { m_input->SetActiveChannel(id); m_input_indicator->SetActiveChannel(id); m_rate_limit_indicator->SetActiveChannel(id); - if (m_is_replying) - StopReplying(); + if (m_is_replying) StopReplying(); + if (m_is_editing) StopEditing(); #ifdef WITH_LIBHANDY m_tab_switcher->ReplaceActiveTab(id); @@ -274,15 +274,31 @@ bool ChatWindow::OnInputSubmit(ChatSubmitParams data) { data.ChannelID = m_active_channel; data.InReplyToID = m_replying_to; + data.EditingID = m_editing_id; if (m_active_channel.IsValid()) m_signal_action_chat_submit.emit(data); // m_replying_to is checked for invalid in the handler - if (m_is_replying) - StopReplying(); + + if (m_is_replying) StopReplying(); + if (m_is_editing) StopEditing(); return true; } +bool ChatWindow::ProcessKeyEvent(GdkEventKey *e) { + if (e->type != GDK_KEY_PRESS) return false; + if (e->keyval == GDK_KEY_Up) { + const auto edit_id = m_chat->GetLastSentMessage(); + if (edit_id.has_value()) { + StartEditing(*edit_id); + } + + return true; + } + + return false; +} + bool ChatWindow::OnKeyPressEvent(GdkEventKey *e) { if (m_completer.ProcessKeyPress(e)) return true; @@ -290,6 +306,9 @@ bool ChatWindow::OnKeyPressEvent(GdkEventKey *e) { if (m_input->ProcessKeyPress(e)) return true; + if (ProcessKeyEvent(e)) + return true; + return false; } @@ -300,10 +319,11 @@ void ChatWindow::StartReplying(Snowflake message_id) { m_replying_to = message_id; m_is_replying = true; m_input->StartReplying(); - if (author.has_value()) + if (author.has_value()) { m_input_indicator->SetCustomMarkup("Replying to " + author->GetUsernameEscapedBold()); - else + } else { m_input_indicator->SetCustomMarkup("Replying..."); + } } void ChatWindow::StopReplying() { @@ -313,6 +333,25 @@ void ChatWindow::StopReplying() { m_input_indicator->ClearCustom(); } +void ChatWindow::StartEditing(Snowflake message_id) { + const auto message = Abaddon::Get().GetDiscordClient().GetMessage(message_id); + if (!message.has_value()) { + spdlog::get("ui")->warn("ChatWindow::StartEditing message is nullopt"); + return; + } + m_is_editing = true; + m_editing_id = message_id; + m_input->StartEditing(*message); + m_input_indicator->SetCustomMarkup("Editing..."); +} + +void ChatWindow::StopEditing() { + m_is_editing = false; + m_editing_id = Snowflake::Invalid; + m_input->StopEditing(); + m_input_indicator->ClearCustom(); +} + void ChatWindow::OnScrollEdgeOvershot(Gtk::PositionType pos) { if (pos == Gtk::POS_TOP) m_signal_action_chat_load_history.emit(m_active_channel); diff --git a/src/components/chatwindow.hpp b/src/components/chatwindow.hpp index e1bb57a..b3c9d41 100644 --- a/src/components/chatwindow.hpp +++ b/src/components/chatwindow.hpp @@ -37,6 +37,9 @@ public: void SetTopic(const std::string &text); void AddAttachment(const Glib::RefPtr &file); + void StartEditing(Snowflake message_id); + void StopEditing(); + #ifdef WITH_LIBHANDY void OpenNewTab(Snowflake id); TabsState GetTabsState(); @@ -55,10 +58,14 @@ protected: void StartReplying(Snowflake message_id); void StopReplying(); + bool m_is_editing = false; + Snowflake m_editing_id; + Snowflake m_active_channel; bool OnInputSubmit(ChatSubmitParams data); + bool ProcessKeyEvent(GdkEventKey *e); bool OnKeyPressEvent(GdkEventKey *e); void OnScrollEdgeOvershot(Gtk::PositionType pos); diff --git a/src/dialogs/editmessage.cpp b/src/dialogs/editmessage.cpp deleted file mode 100644 index b4308a0..0000000 --- a/src/dialogs/editmessage.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "editmessage.hpp" - -EditMessageDialog::EditMessageDialog(Gtk::Window &parent) - : Gtk::Dialog("Edit Message", parent, true) - , m_layout(Gtk::ORIENTATION_VERTICAL) - , m_ok("OK") - , m_cancel("Cancel") - , m_bbox(Gtk::ORIENTATION_HORIZONTAL) { - set_default_size(300, 50); - get_style_context()->add_class("app-window"); - get_style_context()->add_class("app-popup"); - - m_ok.signal_clicked().connect([&]() { - m_content = m_text.get_buffer()->get_text(); - response(Gtk::RESPONSE_OK); - }); - - m_cancel.signal_clicked().connect([&]() { - response(Gtk::RESPONSE_CANCEL); - }); - - m_bbox.pack_start(m_ok, Gtk::PACK_SHRINK); - m_bbox.pack_start(m_cancel, Gtk::PACK_SHRINK); - m_bbox.set_layout(Gtk::BUTTONBOX_END); - - m_text.set_hexpand(true); - - m_scroll.set_hexpand(true); - m_scroll.set_vexpand(true); - m_scroll.add(m_text); - - m_layout.add(m_scroll); - m_layout.add(m_bbox); - get_content_area()->add(m_layout); - - show_all_children(); -} - -Glib::ustring EditMessageDialog::GetContent() { - return m_content; -} - -void EditMessageDialog::SetContent(const Glib::ustring &str) { - m_text.get_buffer()->set_text(str); -} diff --git a/src/dialogs/editmessage.hpp b/src/dialogs/editmessage.hpp deleted file mode 100644 index 38ebedb..0000000 --- a/src/dialogs/editmessage.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once -#include - -class EditMessageDialog : public Gtk::Dialog { -public: - EditMessageDialog(Gtk::Window &parent); - Glib::ustring GetContent(); - void SetContent(const Glib::ustring &str); - -protected: - Gtk::Box m_layout; - Gtk::Button m_ok; - Gtk::Button m_cancel; - Gtk::ButtonBox m_bbox; - Gtk::ScrolledWindow m_scroll; - Gtk::TextView m_text; - -private: - Glib::ustring m_content; -}; diff --git a/src/discord/chatsubmitparams.hpp b/src/discord/chatsubmitparams.hpp index 24c6f50..e195189 100644 --- a/src/discord/chatsubmitparams.hpp +++ b/src/discord/chatsubmitparams.hpp @@ -20,6 +20,7 @@ struct ChatSubmitParams { bool Silent = false; Snowflake ChannelID; Snowflake InReplyToID; + Snowflake EditingID; Glib::ustring Message; std::vector Attachments; }; diff --git a/src/windows/mainwindow.cpp b/src/windows/mainwindow.cpp index 0b21567..8e030ed 100644 --- a/src/windows/mainwindow.cpp +++ b/src/windows/mainwindow.cpp @@ -168,6 +168,10 @@ void MainWindow::ToggleMenuVisibility() { m_menu_bar.set_visible(!m_menu_bar.get_visible()); } +void MainWindow::EditMessage(Snowflake message_id) { + m_chat.StartEditing(message_id); +} + #ifdef WITH_LIBHANDY void MainWindow::GoBack() { m_chat.GoBack(); diff --git a/src/windows/mainwindow.hpp b/src/windows/mainwindow.hpp index 604043c..ce2e636 100644 --- a/src/windows/mainwindow.hpp +++ b/src/windows/mainwindow.hpp @@ -29,6 +29,7 @@ public: void UpdateChatReactionRemove(Snowflake id, const Glib::ustring ¶m); void UpdateMenus(); void ToggleMenuVisibility(); + void EditMessage(Snowflake message_id); #ifdef WITH_LIBHANDY void GoBack(); From f941ad1befc0049bb2c4be08cf76df0c09dc1016 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 15 Jul 2023 15:49:35 -0400 Subject: [PATCH 48/52] clear input when exiting editing --- src/components/chatinput.cpp | 4 ++++ src/components/chatinput.hpp | 1 + src/components/chatwindow.cpp | 1 + 3 files changed, 6 insertions(+) diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 8ad0c3f..c88de49 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -497,6 +497,10 @@ void ChatInput::InsertText(const Glib::ustring &text) { m_input.Get().InsertText(text); } +void ChatInput::Clear() { + GetBuffer()->set_text(""); +} + Glib::RefPtr ChatInput::GetBuffer() { return m_input.Get().GetBuffer(); } diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index a7ee27f..c98d935 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -129,6 +129,7 @@ public: ChatInput(); void InsertText(const Glib::ustring &text); + void Clear(); Glib::RefPtr GetBuffer(); bool ProcessKeyPress(GdkEventKey *event); void AddAttachment(const Glib::RefPtr &file); diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index 864d0c9..dcff656 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -349,6 +349,7 @@ void ChatWindow::StopEditing() { m_is_editing = false; m_editing_id = Snowflake::Invalid; m_input->StopEditing(); + m_input->Clear(); m_input_indicator->ClearCustom(); } From 03e617a146666ad95f8cf9ff144f1cec4ae4c6cd Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 15 Jul 2023 16:03:33 -0400 Subject: [PATCH 49/52] only start editing if input is empty --- src/components/chatinput.cpp | 4 ++++ src/components/chatinput.hpp | 2 ++ src/components/chatwindow.cpp | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index c88de49..f53c6fe 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -579,6 +579,10 @@ void ChatInput::StopEditing() { m_input.Get().get_style_context()->remove_class("editing"); } +bool ChatInput::IsEmpty() { + return GetBuffer()->get_char_count() == 0; +} + bool ChatInput::AddFileAsImageAttachment(const Glib::RefPtr &file) { try { const auto read_stream = file->read(); diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index c98d935..393a9aa 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -143,6 +143,8 @@ public: void StartEditing(const Message &message); void StopEditing(); + bool IsEmpty(); + private: bool AddFileAsImageAttachment(const Glib::RefPtr &file); bool CanAttachFiles(); diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index dcff656..17f5601 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -287,7 +287,7 @@ bool ChatWindow::OnInputSubmit(ChatSubmitParams data) { bool ChatWindow::ProcessKeyEvent(GdkEventKey *e) { if (e->type != GDK_KEY_PRESS) return false; - if (e->keyval == GDK_KEY_Up) { + if (e->keyval == GDK_KEY_Up && !(e->state & GDK_SHIFT_MASK) && m_input->IsEmpty()) { const auto edit_id = m_chat->GetLastSentMessage(); if (edit_id.has_value()) { StartEditing(*edit_id); From add48af094ec9111727862d3f2ee1916474a2471 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 15 Jul 2023 17:18:04 -0400 Subject: [PATCH 50/52] restrict attachments when editing --- src/components/chatinput.cpp | 6 +++++- src/components/chatinput.hpp | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index f53c6fe..1133302 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -570,12 +570,16 @@ void ChatInput::StopReplying() { } void ChatInput::StartEditing(const Message &message) { + m_is_editing = true; m_input.Get().grab_focus(); m_input.Get().get_style_context()->add_class("editing"); GetBuffer()->set_text(message.Content); + m_attachments.Clear(); + m_attachments_revealer.set_reveal_child(false); } void ChatInput::StopEditing() { + m_is_editing = false; m_input.Get().get_style_context()->remove_class("editing"); } @@ -595,7 +599,7 @@ bool ChatInput::AddFileAsImageAttachment(const Glib::RefPtr &file) { } bool ChatInput::CanAttachFiles() { - return Abaddon::Get().GetDiscordClient().HasSelfChannelPermission(m_active_channel, Permission::ATTACH_FILES | Permission::SEND_MESSAGES); + return !m_is_editing && Abaddon::Get().GetDiscordClient().HasSelfChannelPermission(m_active_channel, Permission::ATTACH_FILES | Permission::SEND_MESSAGES); } ChatInput::type_signal_submit ChatInput::signal_submit() { diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index 393a9aa..a3c9742 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -155,6 +155,8 @@ private: Snowflake m_active_channel; + bool m_is_editing = false; + public: using type_signal_submit = sigc::signal; using type_signal_escape = sigc::signal; From 52b52eb489caa64e6cea978f2c6917a55fa70979 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 15 Jul 2023 21:14:39 -0400 Subject: [PATCH 51/52] check if message is editable --- src/components/chatlist.cpp | 13 +++++++------ src/components/chatlist.hpp | 2 +- src/components/chatwindow.cpp | 2 +- src/discord/message.cpp | 4 ++++ src/discord/message.hpp | 10 ++++++---- 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/components/chatlist.cpp b/src/components/chatlist.cpp index 28981b2..ba18c0a 100644 --- a/src/components/chatlist.cpp +++ b/src/components/chatlist.cpp @@ -133,10 +133,9 @@ void ChatList::ProcessNewMessage(const Message &data, bool prepend) { m_menu_delete_message->set_sensitive(false); m_menu_edit_message->set_sensitive(false); } else { - const bool can_edit = client.GetUserData().ID == data->Author.ID; - const bool can_delete = can_edit || has_manage; + const bool can_delete = (client.GetUserData().ID == data->Author.ID) || has_manage; m_menu_delete_message->set_sensitive(can_delete); - m_menu_edit_message->set_sensitive(can_edit); + m_menu_edit_message->set_sensitive(data->IsEditable()); } m_menu.popup_at_pointer(reinterpret_cast(ev)); @@ -253,18 +252,20 @@ void ChatList::ActuallyRemoveMessage(Snowflake id) { RemoveMessageAndHeader(it->second); } -std::optional ChatList::GetLastSentMessage() { +std::optional ChatList::GetLastSentEditableMessage() { const auto &discord = Abaddon::Get().GetDiscordClient(); const auto self_id = discord.GetUserData().ID; std::map ordered(m_id_to_widget.begin(), m_id_to_widget.end()); for (auto it = ordered.crbegin(); it != ordered.crend(); it++) { - const auto *widget = dynamic_cast(it->second); + const auto *widget = dynamic_cast(it->second); if (widget == nullptr) continue; const auto msg = discord.GetMessage(widget->ID); if (!msg.has_value()) continue; - if (msg->Author.ID == self_id) return msg->ID; + if (msg->Author.ID != self_id) continue; + if (!msg->IsEditable()) continue; + return msg->ID; } return std::nullopt; diff --git a/src/components/chatlist.hpp b/src/components/chatlist.hpp index 5d4ec4a..9f12df9 100644 --- a/src/components/chatlist.hpp +++ b/src/components/chatlist.hpp @@ -23,7 +23,7 @@ public: void SetSeparateAll(bool separate); void SetUsePinnedMenu(); // i think i need a better way to do menus void ActuallyRemoveMessage(Snowflake id); // perhaps not the best method name - std::optional GetLastSentMessage(); + std::optional GetLastSentEditableMessage(); private: void SetupMenu(); diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index 17f5601..aeed4ed 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -288,7 +288,7 @@ bool ChatWindow::OnInputSubmit(ChatSubmitParams data) { bool ChatWindow::ProcessKeyEvent(GdkEventKey *e) { if (e->type != GDK_KEY_PRESS) return false; if (e->keyval == GDK_KEY_Up && !(e->state & GDK_SHIFT_MASK) && m_input->IsEmpty()) { - const auto edit_id = m_chat->GetLastSentMessage(); + const auto edit_id = m_chat->GetLastSentEditableMessage(); if (edit_id.has_value()) { StartEditing(*edit_id); } diff --git a/src/discord/message.cpp b/src/discord/message.cpp index f5d8ad8..9adc7e6 100644 --- a/src/discord/message.cpp +++ b/src/discord/message.cpp @@ -265,6 +265,10 @@ bool Message::IsEdited() const { return m_edited; } +bool Message::IsEditable() const noexcept { + return (Abaddon::Get().GetDiscordClient().GetUserData().ID == Author.ID) && !IsDeleted() && !IsPending && (Type == MessageType::DEFAULT || Type == MessageType::INLINE_REPLY); +} + bool Message::DoesMentionEveryoneOrUser(Snowflake id) const noexcept { if (DoesMentionEveryone) return true; return std::any_of(Mentions.begin(), Mentions.end(), [id](const UserData &user) { diff --git a/src/discord/message.hpp b/src/discord/message.hpp index 5028104..fb11604 100644 --- a/src/discord/message.hpp +++ b/src/discord/message.hpp @@ -219,11 +219,13 @@ struct Message { void SetDeleted(); void SetEdited(); - [[nodiscard]] bool IsDeleted() const; - [[nodiscard]] bool IsEdited() const; + bool IsDeleted() const; + bool IsEdited() const; - [[nodiscard]] bool DoesMentionEveryoneOrUser(Snowflake id) const noexcept; - [[nodiscard]] bool DoesMention(Snowflake id) const noexcept; + bool IsEditable() const noexcept; + + bool DoesMentionEveryoneOrUser(Snowflake id) const noexcept; + bool DoesMention(Snowflake id) const noexcept; private: bool m_deleted = false; From 857e94af3817932b78963873fb5621ae3c4596f7 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 17 Jul 2023 04:15:04 -0400 Subject: [PATCH 52/52] dont error if no capture or playback devices are available to try initializing (#193) --- src/audio/manager.cpp | 50 ++++++++++++++++++------------------- src/windows/voicewindow.cpp | 8 ++++-- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index ce20940..67fbcd9 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -93,19 +93,23 @@ AudioManager::AudioManager() { if (const auto playback_id = m_devices.GetDefaultPlayback(); playback_id.has_value()) { m_playback_id = *playback_id; m_playback_config.playback.pDeviceID = &m_playback_id; - } - if (ma_device_init(&m_context, &m_playback_config, &m_playback_device) != MA_SUCCESS) { - spdlog::get("audio")->error("failed to initialize playback device"); - m_ok = false; - return; - } + if (ma_device_init(&m_context, &m_playback_config, &m_playback_device) != MA_SUCCESS) { + spdlog::get("audio")->error("failed to initialize playback device"); + m_ok = false; + return; + } - if (ma_device_start(&m_playback_device) != MA_SUCCESS) { - spdlog::get("audio")->error("failed to start playback"); - ma_device_uninit(&m_playback_device); - m_ok = false; - return; + if (ma_device_start(&m_playback_device) != MA_SUCCESS) { + spdlog::get("audio")->error("failed to start playback"); + ma_device_uninit(&m_playback_device); + m_ok = false; + return; + } + + char playback_device_name[MA_MAX_DEVICE_NAME_LENGTH + 1]; + ma_device_get_name(&m_playback_device, ma_device_type_playback, playback_device_name, sizeof(playback_device_name), nullptr); + spdlog::get("audio")->info("using {} as playback device", playback_device_name); } m_capture_config = ma_device_config_init(ma_device_type_capture); @@ -119,22 +123,18 @@ AudioManager::AudioManager() { if (const auto capture_id = m_devices.GetDefaultCapture(); capture_id.has_value()) { m_capture_id = *capture_id; m_capture_config.capture.pDeviceID = &m_capture_id; + + if (ma_device_init(&m_context, &m_capture_config, &m_capture_device) != MA_SUCCESS) { + spdlog::get("audio")->error("failed to initialize capture device"); + m_ok = false; + return; + } + + char capture_device_name[MA_MAX_DEVICE_NAME_LENGTH + 1]; + ma_device_get_name(&m_capture_device, ma_device_type_capture, capture_device_name, sizeof(capture_device_name), nullptr); + spdlog::get("audio")->info("using {} as capture device", capture_device_name); } - if (ma_device_init(&m_context, &m_capture_config, &m_capture_device) != MA_SUCCESS) { - spdlog::get("audio")->error("failed to initialize capture device"); - m_ok = false; - return; - } - - char playback_device_name[MA_MAX_DEVICE_NAME_LENGTH + 1]; - ma_device_get_name(&m_playback_device, ma_device_type_playback, playback_device_name, sizeof(playback_device_name), nullptr); - spdlog::get("audio")->info("using {} as playback device", playback_device_name); - - char capture_device_name[MA_MAX_DEVICE_NAME_LENGTH + 1]; - ma_device_get_name(&m_capture_device, ma_device_type_capture, capture_device_name, sizeof(capture_device_name), nullptr); - spdlog::get("audio")->info("using {} as capture device", capture_device_name); - Glib::signal_timeout().connect(sigc::mem_fun(*this, &AudioManager::DecayVolumeMeters), 40); } diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index a4677c6..829a1b8 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -139,7 +139,9 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_playback_combo.set_hexpand(true); m_playback_combo.set_halign(Gtk::ALIGN_FILL); m_playback_combo.set_model(Abaddon::Get().GetAudio().GetDevices().GetPlaybackDeviceModel()); - m_playback_combo.set_active(Abaddon::Get().GetAudio().GetDevices().GetActivePlaybackDevice()); + if (const auto iter = Abaddon::Get().GetAudio().GetDevices().GetActivePlaybackDevice()) { + m_playback_combo.set_active(iter); + } m_playback_combo.pack_start(*playback_renderer); m_playback_combo.add_attribute(*playback_renderer, "text", 0); m_playback_combo.signal_changed().connect([this]() { @@ -151,7 +153,9 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_capture_combo.set_hexpand(true); m_capture_combo.set_halign(Gtk::ALIGN_FILL); m_capture_combo.set_model(Abaddon::Get().GetAudio().GetDevices().GetCaptureDeviceModel()); - m_capture_combo.set_active(Abaddon::Get().GetAudio().GetDevices().GetActiveCaptureDevice()); + if (const auto iter = Abaddon::Get().GetAudio().GetDevices().GetActiveCaptureDevice()) { + m_capture_combo.set_active(iter); + } m_capture_combo.pack_start(*capture_renderer); m_capture_combo.add_attribute(*capture_renderer, "text", 0); m_capture_combo.signal_changed().connect([this]() {