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: show voice participants in private channels --- src/discord/discord.cpp | 61 ++++++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 24 deletions(-) (limited to 'src/discord/discord.cpp') 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() { -- cgit v1.2.3 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: 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(-) (limited to 'src/discord/discord.cpp') 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; -- cgit v1.2.3 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: 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(+) (limited to 'src/discord/discord.cpp') 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; }; -- cgit v1.2.3 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: 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(-) (limited to 'src/discord/discord.cpp') 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; }; -- cgit v1.2.3 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: 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(-) (limited to 'src/discord/discord.cpp') 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; -- cgit v1.2.3 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: Don't count muted channels towards unread state of category --- src/discord/discord.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src/discord/discord.cpp') 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; -- cgit v1.2.3 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: fix bad optional access in channel delete (fixes #187) --- src/discord/discord.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src/discord/discord.cpp') 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); -- cgit v1.2.3