From 270730d9b36c8fc3a221da7e565632c1432d41c6 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 5 Jun 2022 21:41:57 -0400 Subject: start attachments (image paste and upload) --- src/discord/discord.hpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src/discord/discord.hpp') diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 6310296..fdb5a5f 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -104,8 +104,8 @@ public: void ChatMessageCallback(const std::string &nonce, const http::response_type &response); - void SendChatMessage(const std::string &content, Snowflake channel); - void SendChatMessage(const std::string &content, Snowflake channel, Snowflake referenced_message); + void SendChatMessage(const std::string &content, const std::vector &attachment_paths, Snowflake channel); + void SendChatMessage(const std::string &content, const std::vector &attachment_paths, Snowflake channel, Snowflake referenced_message); void DeleteMessage(Snowflake channel_id, Snowflake id); void EditMessage(Snowflake channel_id, Snowflake id, std::string content); void SendLazyLoad(Snowflake id); @@ -223,6 +223,9 @@ private: std::vector m_decompress_buf; z_stream m_zstream; + void SendChatMessageAttachments(const std::string &content, const std::vector &attachment_paths, Snowflake channel, Snowflake referenced_message = Snowflake::Invalid); + void SendChatMessageText(const std::string &content, Snowflake channel, Snowflake referenced_message = Snowflake::Invalid); + static std::string GetAPIURL(); static std::string GetGatewayURL(); -- cgit v1.2.3 From 49ff9a249e4fbc6354810fb56a7488deeacd8cd2 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 9 Jun 2022 01:48:24 -0400 Subject: remove temp attachment files when theyre actually done being uploaded --- src/components/chatinput.cpp | 17 +++++++++++++++-- src/components/chatinput.hpp | 5 +++++ src/components/chatwindow.cpp | 3 +++ src/discord/discord.cpp | 12 +++++++++++- src/discord/discord.hpp | 1 + 5 files changed, 35 insertions(+), 3 deletions(-) (limited to 'src/discord/discord.hpp') diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 7597bbe..166d04f 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -112,6 +112,13 @@ void ChatInputAttachmentContainer::Clear() { m_attachments.clear(); } +void ChatInputAttachmentContainer::ClearNoPurge() { + for (auto *item : m_attachments) { + delete item; + } + m_attachments.clear(); +} + bool ChatInputAttachmentContainer::AddImage(const Glib::RefPtr &pb) { if (m_attachments.size() == 10) return false; @@ -217,8 +224,8 @@ ChatInput::ChatInput() const auto attachments = m_attachments.GetFilePaths(); bool b = m_signal_submit.emit(input, attachments); if (b) { - m_attachments.Clear(); m_attachments_revealer.set_reveal_child(false); + m_attachments.ClearNoPurge(); } return b; }); @@ -232,7 +239,9 @@ ChatInput::ChatInput() show_all_children(); m_input.signal_image_paste().connect([this](const Glib::RefPtr &pb) { - if (m_attachments.AddImage(pb)) + const bool can_attach_files = m_signal_check_permission.emit(Permission::ATTACH_FILES); + + if (can_attach_files && m_attachments.AddImage(pb)) m_attachments_revealer.set_reveal_child(true); }); @@ -266,3 +275,7 @@ ChatInput::type_signal_submit ChatInput::signal_submit() { ChatInput::type_signal_escape ChatInput::signal_escape() { return m_signal_escape; } + +ChatInput::type_signal_check_permission ChatInput::signal_check_permission() { + return m_signal_check_permission; +} diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index 83fde95..bdbac5c 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include "discord/permissions.hpp" class ChatInputAttachmentItem : public Gtk::EventBox { public: @@ -32,6 +33,7 @@ public: ChatInputAttachmentContainer(); void Clear(); + void ClearNoPurge(); bool AddImage(const Glib::RefPtr &pb); [[nodiscard]] std::vector GetFilePaths() const; @@ -98,11 +100,14 @@ public: // maybe this should be reduced to a single struct, its bound to get more complicated (application commands?) using type_signal_submit = sigc::signal>; using type_signal_escape = sigc::signal; + using type_signal_check_permission = sigc::signal; type_signal_submit signal_submit(); type_signal_escape signal_escape(); + type_signal_check_permission signal_check_permission(); private: type_signal_submit m_signal_submit; type_signal_escape m_signal_escape; + type_signal_check_permission m_signal_check_permission; }; diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index 46c34d4..52c2a60 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -47,6 +47,9 @@ ChatWindow::ChatWindow() { m_input->set_valign(Gtk::ALIGN_END); + m_input->signal_check_permission().connect([this](Permission perm) { + return Abaddon::Get().GetDiscordClient().HasSelfChannelPermission(m_active_channel, perm); + }); m_input->signal_submit().connect(sigc::mem_fun(*this, &ChatWindow::OnInputSubmit)); m_input->signal_escape().connect([this]() { if (m_is_replying) diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 151d88c..ffc1d4d 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -318,6 +318,10 @@ bool DiscordClient::HasGuildPermission(Snowflake user_id, Snowflake guild_id, Pe return (base & perm) == perm; } +bool DiscordClient::HasSelfChannelPermission(Snowflake channel_id, Permission perm) const { + return HasChannelPermission(m_user_data.ID, channel_id, perm); +} + bool DiscordClient::HasAnyChannelPermission(Snowflake user_id, Snowflake channel_id, Permission perm) const { const auto channel = m_store.GetChannel(channel_id); if (!channel.has_value() || !channel->GuildID.has_value()) return false; @@ -439,7 +443,13 @@ void DiscordClient::SendChatMessageAttachments(const std::string &content, const const auto field_name = "files[" + std::to_string(i) + "]"; req.add_file(field_name, attachment_paths.at(i), "unknown.png"); } - m_http.Execute(std::move(req), sigc::bind<0>(sigc::mem_fun(*this, &DiscordClient::ChatMessageCallback), nonce)); + m_http.Execute(std::move(req), [this, attachment_paths, nonce](const http::response_type &res) { + for (const auto &path : attachment_paths) { + std::error_code ec; + std::filesystem::remove(path, ec); + } + ChatMessageCallback(nonce, res); + }); } void DiscordClient::SendChatMessageText(const std::string &content, Snowflake channel, Snowflake referenced_message) { diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index fdb5a5f..9322e3f 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -96,6 +96,7 @@ public: bool IsThreadJoined(Snowflake thread_id) const; bool HasGuildPermission(Snowflake user_id, Snowflake guild_id, Permission perm) const; + bool HasSelfChannelPermission(Snowflake channel_id, Permission perm) const; bool HasAnyChannelPermission(Snowflake user_id, Snowflake channel_id, Permission perm) const; bool HasChannelPermission(Snowflake user_id, Snowflake channel_id, Permission perm) const; Permission ComputePermissions(Snowflake member_id, Snowflake guild_id) const; -- cgit v1.2.3 From 4456c8771da668cbadb411583624fe0e357fa687 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 14 Jun 2022 02:36:04 -0400 Subject: refactor send message params into one struct --- src/abaddon.cpp | 23 ++++++---- src/abaddon.hpp | 2 +- src/components/chatinput.cpp | 20 +++++--- src/components/chatinput.hpp | 9 ++-- src/components/chatwindow.cpp | 9 ++-- src/components/chatwindow.hpp | 5 +- src/discord/chatsubmitparams.hpp | 22 +++++++++ src/discord/discord.cpp | 99 +++++++++++++++++++++------------------- src/discord/discord.hpp | 11 ++--- 9 files changed, 122 insertions(+), 78 deletions(-) create mode 100644 src/discord/chatsubmitparams.hpp (limited to 'src/discord/discord.hpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 05e1942..28b6262 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -743,17 +743,22 @@ void Abaddon::ActionChatLoadHistory(Snowflake id) { }); } -void Abaddon::ActionChatInputSubmit(std::string msg, const std::vector &attachment_paths, Snowflake channel, Snowflake referenced_message) { - if (msg.substr(0, 7) == "/shrug " || msg == "/shrug") - msg = msg.substr(6) + "\xC2\xAF\x5C\x5F\x28\xE3\x83\x84\x29\x5F\x2F\xC2\xAF"; // this is important +static void ChatMessageSentCallback(const ChatSubmitParams &data) { + printf("completed for %s\n", data.Message.c_str()); + for (const auto &attachment : data.Attachments) { + puts(attachment.Path.c_str()); + } +} - if (!channel.IsValid()) return; - if (!m_discord.HasChannelPermission(m_discord.GetUserData().ID, channel, Permission::VIEW_CHANNEL)) return; +void Abaddon::ActionChatInputSubmit(ChatSubmitParams data) { + 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 (referenced_message.IsValid()) - m_discord.SendChatMessage(msg, attachment_paths, channel, referenced_message); - else - m_discord.SendChatMessage(msg, attachment_paths, channel); + if (!m_discord.HasChannelPermission(m_discord.GetUserData().ID, data.ChannelID, Permission::VIEW_CHANNEL)) return; + + m_discord.SendChatMessage(data, [data](DiscordError code) { + ChatMessageSentCallback(data); + }); } void Abaddon::ActionChatEditMessage(Snowflake channel_id, Snowflake id) { diff --git a/src/abaddon.hpp b/src/abaddon.hpp index 9efb891..1ba1251 100644 --- a/src/abaddon.hpp +++ b/src/abaddon.hpp @@ -36,7 +36,7 @@ public: void ActionSetToken(); void ActionJoinGuildDialog(); void ActionChannelOpened(Snowflake id, bool expand_to = true); - void ActionChatInputSubmit(std::string msg, const std::vector &attachment_paths, Snowflake channel, Snowflake referenced_message); + void ActionChatInputSubmit(ChatSubmitParams data); void ActionChatLoadHistory(Snowflake id); void ActionChatEditMessage(Snowflake channel_id, Snowflake id); void ActionInsertMention(Snowflake id); diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index f9bbce4..2b8b5a5 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -152,10 +152,10 @@ bool ChatInputAttachmentContainer::AddImage(const Glib::RefPtr &pb) return true; } -std::vector ChatInputAttachmentContainer::GetFilePaths() const { - std::vector ret; +std::vector ChatInputAttachmentContainer::GetAttachments() const { + std::vector ret; for (auto *x : m_attachments) - ret.push_back(x->GetPath()); + ret.push_back({ x->GetPath(), x->GetType() }); return ret; } @@ -165,7 +165,8 @@ ChatInputAttachmentContainer::type_signal_emptied ChatInputAttachmentContainer:: ChatInputAttachmentItem::ChatInputAttachmentItem(std::string path, const Glib::RefPtr &pb) : m_path(std::move(path)) - , m_img(Gtk::make_managed()) { + , m_img(Gtk::make_managed()) + , m_type(ChatSubmitParams::PastedImage) { get_style_context()->add_class("attachment-item"); int outw, outh; @@ -184,6 +185,10 @@ std::string ChatInputAttachmentItem::GetPath() const { return m_path; } +ChatSubmitParams::AttachmentType ChatInputAttachmentItem::GetType() const { + return m_type; +} + void ChatInputAttachmentItem::SetupMenu() { m_menu_remove.set_label("Remove"); m_menu_remove.signal_activate().connect([this] { @@ -215,8 +220,11 @@ ChatInput::ChatInput() m_signal_escape.emit(); }); m_input.signal_submit().connect([this](const Glib::ustring &input) -> bool { - const auto attachments = m_attachments.GetFilePaths(); - bool b = m_signal_submit.emit(input, attachments); + ChatSubmitParams data; + data.Message = input; + data.Attachments = m_attachments.GetAttachments(); + + bool b = m_signal_submit.emit(data); if (b) { m_attachments_revealer.set_reveal_child(false); m_attachments.ClearNoPurge(); diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index bdbac5c..ba3ab36 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include "discord/chatsubmitparams.hpp" #include "discord/permissions.hpp" class ChatInputAttachmentItem : public Gtk::EventBox { @@ -7,6 +8,7 @@ public: ChatInputAttachmentItem(std::string path, const Glib::RefPtr &pb); [[nodiscard]] std::string GetPath() const; + [[nodiscard]] ChatSubmitParams::AttachmentType GetType() const; private: void SetupMenu(); @@ -18,6 +20,7 @@ private: Gtk::Image *m_img = nullptr; std::string m_path; + ChatSubmitParams::AttachmentType m_type; private: using type_signal_remove = sigc::signal; @@ -35,7 +38,7 @@ public: void Clear(); void ClearNoPurge(); bool AddImage(const Glib::RefPtr &pb); - [[nodiscard]] std::vector GetFilePaths() const; + [[nodiscard]] std::vector GetAttachments() const; private: std::set m_attachments; @@ -96,9 +99,7 @@ private: ChatInputText m_input; public: - // text, attachments -> request sent - // maybe this should be reduced to a single struct, its bound to get more complicated (application commands?) - using type_signal_submit = sigc::signal>; + using type_signal_submit = sigc::signal; using type_signal_escape = sigc::signal; using type_signal_check_permission = sigc::signal; diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index 52c2a60..3e1885b 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -212,15 +212,18 @@ Snowflake ChatWindow::GetActiveChannel() const { return m_active_channel; } -bool ChatWindow::OnInputSubmit(const Glib::ustring &text, const std::vector &attachment_paths) { +bool ChatWindow::OnInputSubmit(ChatSubmitParams data) { if (!m_rate_limit_indicator->CanSpeak()) return false; - if (text.empty() && attachment_paths.empty()) + if (data.Message.empty() && data.Attachments.empty()) return false; + data.ChannelID = m_active_channel; + data.InReplyToID = m_replying_to; + if (m_active_channel.IsValid()) - m_signal_action_chat_submit.emit(text, attachment_paths, m_active_channel, m_replying_to); // m_replying_to is checked for invalid in the handler + m_signal_action_chat_submit.emit(data); // m_replying_to is checked for invalid in the handler if (m_is_replying) StopReplying(); diff --git a/src/components/chatwindow.hpp b/src/components/chatwindow.hpp index ab0bee1..ecbf666 100644 --- a/src/components/chatwindow.hpp +++ b/src/components/chatwindow.hpp @@ -3,6 +3,7 @@ #include #include #include "discord/discord.hpp" +#include "discord/chatsubmitparams.hpp" #include "completer.hpp" #include "state.hpp" @@ -55,7 +56,7 @@ protected: Snowflake m_active_channel; - bool OnInputSubmit(const Glib::ustring &text, const std::vector &attachment_paths); + bool OnInputSubmit(ChatSubmitParams data); bool OnKeyPressEvent(GdkEventKey *e); void OnScrollEdgeOvershot(Gtk::PositionType pos); @@ -84,7 +85,7 @@ protected: public: using type_signal_action_message_edit = sigc::signal; - using type_signal_action_chat_submit = sigc::signal, Snowflake, Snowflake>; + using type_signal_action_chat_submit = sigc::signal; using type_signal_action_chat_load_history = sigc::signal; using type_signal_action_channel_click = sigc::signal; using type_signal_action_insert_mention = sigc::signal; diff --git a/src/discord/chatsubmitparams.hpp b/src/discord/chatsubmitparams.hpp new file mode 100644 index 0000000..0f0a0cd --- /dev/null +++ b/src/discord/chatsubmitparams.hpp @@ -0,0 +1,22 @@ +#pragma once +#include +#include +#include +#include "discord/snowflake.hpp" + +struct ChatSubmitParams { + enum AttachmentType { + PastedImage, + ExtantFile, + }; + + struct Attachment { + std::string Path; + AttachmentType Type; + }; + + Snowflake ChannelID; + Snowflake InReplyToID; + Glib::ustring Message; + std::vector Attachments; +}; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index ffc1d4d..9849ae6 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -413,7 +413,7 @@ bool DiscordClient::CanManageMember(Snowflake guild_id, Snowflake actor, Snowfla return actor_highest->Position > target_highest->Position; } -void DiscordClient::ChatMessageCallback(const std::string &nonce, const http::response_type &response) { +void DiscordClient::ChatMessageCallback(const std::string &nonce, const http::response_type &response, const sigc::slot &callback) { if (!CheckCode(response)) { if (response.status_code == http::TooManyRequests) { try { // not sure if this body is guaranteed @@ -425,75 +425,79 @@ void DiscordClient::ChatMessageCallback(const std::string &nonce, const http::re } else { m_signal_message_send_fail.emit(nonce, 0); } + + // todo actually callback with correct error code (not necessary rn) + callback(DiscordError::GENERIC); + } else { + callback(DiscordError::NONE); } } -void DiscordClient::SendChatMessageAttachments(const std::string &content, const std::vector &attachment_paths, Snowflake channel, Snowflake referenced_message) { +void DiscordClient::SendChatMessageNoAttachments(const ChatSubmitParams ¶ms, const sigc::slot &callback) { const auto nonce = std::to_string(Snowflake::FromNow()); + CreateMessageObject obj; - obj.Content = content; + obj.Content = params.Message; obj.Nonce = nonce; - if (referenced_message.IsValid()) - obj.MessageReference.emplace().MessageID = referenced_message; + if (params.InReplyToID.IsValid()) + obj.MessageReference.emplace().MessageID = params.InReplyToID; - auto req = m_http.CreateRequest(http::REQUEST_POST, "/channels/" + std::to_string(channel) + "/messages"); - req.make_form(); - req.add_field("payload_json", nlohmann::json(obj).dump().c_str(), CURL_ZERO_TERMINATED); - for (size_t i = 0; i < attachment_paths.size(); i++) { - const auto field_name = "files[" + std::to_string(i) + "]"; - req.add_file(field_name, attachment_paths.at(i), "unknown.png"); - } - m_http.Execute(std::move(req), [this, attachment_paths, nonce](const http::response_type &res) { - for (const auto &path : attachment_paths) { - std::error_code ec; - std::filesystem::remove(path, ec); - } - ChatMessageCallback(nonce, res); - }); -} + m_http.MakePOST("/channels/" + std::to_string(params.ChannelID) + "/messages", + nlohmann::json(obj).dump(), + [this, nonce, callback](const http::response_type &r) { + ChatMessageCallback(nonce, r, callback); + }); -void DiscordClient::SendChatMessageText(const std::string &content, Snowflake channel, Snowflake referenced_message) { - // @([^@#]{1,32})#(\\d{4}) - const auto nonce = std::to_string(Snowflake::FromNow()); - CreateMessageObject obj; - obj.Content = content; - obj.Nonce = nonce; - if (referenced_message.IsValid()) - obj.MessageReference.emplace().MessageID = referenced_message; - m_http.MakePOST("/channels/" + std::to_string(channel) + "/messages", nlohmann::json(obj).dump(), sigc::bind<0>(sigc::mem_fun(*this, &DiscordClient::ChatMessageCallback), nonce)); - // dummy data so the content can be shown while waiting for MESSAGE_CREATE + // dummy preview data Message tmp; - tmp.Content = content; + tmp.Content = params.Message; tmp.ID = nonce; - tmp.ChannelID = channel; + tmp.ChannelID = params.ChannelID; tmp.Author = GetUserData(); tmp.IsTTS = false; tmp.DoesMentionEveryone = false; tmp.Type = MessageType::DEFAULT; tmp.IsPinned = false; tmp.Timestamp = "2000-01-01T00:00:00.000000+00:00"; - tmp.Nonce = obj.Nonce; + tmp.Nonce = nonce; tmp.IsPending = true; + m_store.SetMessage(tmp.ID, tmp); - m_signal_message_sent.emit(tmp); + m_signal_message_create.emit(tmp); } -void DiscordClient::SendChatMessage(const std::string &content, const std::vector &attachment_paths, Snowflake channel) { - if (attachment_paths.empty()) - SendChatMessageText(content, channel); - else { - puts("attach"); - SendChatMessageAttachments(content, attachment_paths, channel, Snowflake::Invalid); +void DiscordClient::SendChatMessageAttachments(const ChatSubmitParams ¶ms, const sigc::slot &callback) { + const auto nonce = std::to_string(Snowflake::FromNow()); + + CreateMessageObject obj; + obj.Content = params.Message; + obj.Nonce = nonce; + 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"); + req.make_form(); + req.add_field("payload_json", nlohmann::json(obj).dump().c_str(), CURL_ZERO_TERMINATED); + for (size_t i = 0; i < params.Attachments.size(); i++) { + const auto field_name = "files[" + std::to_string(i) + "]"; + req.add_file(field_name, params.Attachments.at(i).Path, "unknown.png"); } + m_http.Execute(std::move(req), [this, params, nonce, callback](const http::response_type &res) { + for (const auto &attachment : params.Attachments) { + if (attachment.Type == ChatSubmitParams::AttachmentType::PastedImage) { + std::error_code ec; + std::filesystem::remove(attachment.Path, ec); + } + } + ChatMessageCallback(nonce, res, callback); + }); } -void DiscordClient::SendChatMessage(const std::string &content, const std::vector &attachment_paths, Snowflake channel, Snowflake referenced_message) { - if (attachment_paths.empty()) - SendChatMessageText(content, channel, referenced_message); - else { - puts("attach"); - SendChatMessageAttachments(content, attachment_paths, channel, referenced_message); - } +void DiscordClient::SendChatMessage(const ChatSubmitParams ¶ms, const sigc::slot &callback) { + if (params.Attachments.empty()) + SendChatMessageNoAttachments(params, callback); + else + SendChatMessageAttachments(params, callback); } void DiscordClient::DeleteMessage(Snowflake channel_id, Snowflake id) { @@ -1302,6 +1306,7 @@ void DiscordClient::HandleGatewayMessage(std::string str) { HandleGatewayInvalidSession(m); } break; case GatewayOp::Dispatch: { + puts(m.Type.c_str()); auto iter = m_event_map.find(m.Type); if (iter == m_event_map.end()) { printf("Unknown event %s\n", m.Type.c_str()); diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 9322e3f..41d5aab 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -3,6 +3,7 @@ #include "httpclient.hpp" #include "objects.hpp" #include "store.hpp" +#include "chatsubmitparams.hpp" #include #include #include @@ -103,10 +104,11 @@ public: Permission ComputeOverwrites(Permission base, Snowflake member_id, Snowflake channel_id) const; bool CanManageMember(Snowflake guild_id, Snowflake actor, Snowflake target) const; // kick, ban, edit nickname (cant think of a better name) - void ChatMessageCallback(const std::string &nonce, const http::response_type &response); + void ChatMessageCallback(const std::string &nonce, const http::response_type &response, const sigc::slot &callback); + void SendChatMessageNoAttachments(const ChatSubmitParams ¶ms, const sigc::slot &callback); + void SendChatMessageAttachments(const ChatSubmitParams ¶ms, const sigc::slot &callback); - void SendChatMessage(const std::string &content, const std::vector &attachment_paths, Snowflake channel); - void SendChatMessage(const std::string &content, const std::vector &attachment_paths, Snowflake channel, Snowflake referenced_message); + void SendChatMessage(const ChatSubmitParams ¶ms, const sigc::slot &callback); void DeleteMessage(Snowflake channel_id, Snowflake id); void EditMessage(Snowflake channel_id, Snowflake id, std::string content); void SendLazyLoad(Snowflake id); @@ -224,9 +226,6 @@ private: std::vector m_decompress_buf; z_stream m_zstream; - void SendChatMessageAttachments(const std::string &content, const std::vector &attachment_paths, Snowflake channel, Snowflake referenced_message = Snowflake::Invalid); - void SendChatMessageText(const std::string &content, Snowflake channel, Snowflake referenced_message = Snowflake::Invalid); - static std::string GetAPIURL(); static std::string GetGatewayURL(); -- cgit v1.2.3 From 41776fbd023df1c4a70ffa46e8f81c6aea9f7b7f Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 7 Jul 2022 03:09:54 -0400 Subject: add upload progress bar --- res/css/main.css | 15 +++++++++++++++ src/components/chatwindow.cpp | 11 ++++++++--- src/components/chatwindow.hpp | 2 ++ src/components/progressbar.cpp | 24 ++++++++++++++++++++++++ src/components/progressbar.hpp | 11 +++++++++++ src/discord/discord.cpp | 30 ++++++++++++++++++++++++++++++ src/discord/discord.hpp | 3 +++ src/http.cpp | 23 +++++++++++++++++++++-- src/http.hpp | 5 +++++ 9 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 src/components/progressbar.cpp create mode 100644 src/components/progressbar.hpp (limited to 'src/discord/discord.hpp') diff --git a/res/css/main.css b/res/css/main.css index 1035b37..a3d0421 100644 --- a/res/css/main.css +++ b/res/css/main.css @@ -323,3 +323,18 @@ .channel-tab-switcher tab > button:hover { background-color: alpha(#ff0000, 0.5); } + +.message-progress { + border: none; +} + +.message-progress trough { + border: none; + background-color: transparent; +} + +.message-progress progress { + border: none; + background-color: #dd3300; + margin-left: 1px; +} diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index d9d490b..5cbeea1 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -9,7 +9,8 @@ #endif ChatWindow::ChatWindow() { - Abaddon::Get().GetDiscordClient().signal_message_send_fail().connect(sigc::mem_fun(*this, &ChatWindow::OnMessageSendFail)); + auto &discord = Abaddon::Get().GetDiscordClient(); + discord.signal_message_send_fail().connect(sigc::mem_fun(*this, &ChatWindow::OnMessageSendFail)); m_main = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); m_chat = Gtk::manage(new ChatList); @@ -59,11 +60,11 @@ ChatWindow::ChatWindow() { m_input->show(); m_completer.SetBuffer(m_input->GetBuffer()); - m_completer.SetGetChannelID([this]() -> auto { + m_completer.SetGetChannelID([this]() { return m_active_channel; }); - m_completer.SetGetRecentAuthors([this]() -> auto { + m_completer.SetGetRecentAuthors([this]() { return m_chat->GetRecentAuthors(); }); @@ -109,6 +110,10 @@ ChatWindow::ChatWindow() { m_main->add(m_completer); m_main->add(*m_input); m_main->add(*m_meta); + m_main->add(m_progress); + + m_progress.show(); + m_main->show(); } diff --git a/src/components/chatwindow.hpp b/src/components/chatwindow.hpp index 917456b..802826b 100644 --- a/src/components/chatwindow.hpp +++ b/src/components/chatwindow.hpp @@ -6,6 +6,7 @@ #include "discord/chatsubmitparams.hpp" #include "completer.hpp" #include "state.hpp" +#include "progressbar.hpp" #ifdef WITH_LIBHANDY class ChannelTabSwitcherHandy; @@ -79,6 +80,7 @@ protected: ChatInputIndicator *m_input_indicator; RateLimitIndicator *m_rate_limit_indicator; Gtk::Box *m_meta; + MessageUploadProgressBar m_progress; #ifdef WITH_LIBHANDY ChannelTabSwitcherHandy *m_tab_switcher; diff --git a/src/components/progressbar.cpp b/src/components/progressbar.cpp new file mode 100644 index 0000000..aa5d748 --- /dev/null +++ b/src/components/progressbar.cpp @@ -0,0 +1,24 @@ +#include "progressbar.hpp" +#include "abaddon.hpp" + +MessageUploadProgressBar::MessageUploadProgressBar() { + get_style_context()->add_class("message-progress"); + auto &discord = Abaddon::Get().GetDiscordClient(); + discord.signal_message_progress().connect([this](const std::string &nonce, float percent) { + if (nonce == m_last_nonce) { + set_fraction(percent); + } + }); + discord.signal_message_send_fail().connect([this](const std::string &nonce, float) { + if (nonce == m_last_nonce) + set_fraction(0.0); + }); + discord.signal_message_create().connect([this](const Message &msg) { + if (msg.IsPending) { + m_last_nonce = *msg.Nonce; + } else if (msg.Nonce.has_value() && (*msg.Nonce == m_last_nonce)) { + m_last_nonce = ""; + set_fraction(0.0); + } + }); +} diff --git a/src/components/progressbar.hpp b/src/components/progressbar.hpp new file mode 100644 index 0000000..b73521b --- /dev/null +++ b/src/components/progressbar.hpp @@ -0,0 +1,11 @@ +#pragma once +#include +#include + +class MessageUploadProgressBar : public Gtk::ProgressBar { +public: + MessageUploadProgressBar(); + +private: + std::string m_last_nonce; +}; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index d26f1fe..e155728 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -476,6 +476,15 @@ void DiscordClient::SendChatMessageAttachments(const ChatSubmitParams ¶ms, c obj.MessageReference.emplace().MessageID = params.InReplyToID; auto req = m_http.CreateRequest(http::REQUEST_POST, "/channels/" + std::to_string(params.ChannelID) + "/messages"); + req.set_progress_callback([this, nonce](curl_off_t ultotal, curl_off_t ulnow) { + m_generic_mutex.lock(); + m_generic_queue.push(sigc::bind( + sigc::mem_fun(m_signal_message_progress, decltype(m_signal_message_progress)::emit), + nonce, + static_cast(ulnow) / static_cast(ultotal))); + m_generic_dispatch.emit(); + m_generic_mutex.unlock(); + }); req.make_form(); req.add_field("payload_json", nlohmann::json(obj).dump().c_str(), CURL_ZERO_TERMINATED); for (size_t i = 0; i < params.Attachments.size(); i++) { @@ -490,6 +499,23 @@ void DiscordClient::SendChatMessageAttachments(const ChatSubmitParams ¶ms, c } ChatMessageCallback(nonce, res, callback); }); + + // dummy preview data + Message tmp; + tmp.Content = params.Message; + tmp.ID = nonce; + tmp.ChannelID = params.ChannelID; + tmp.Author = GetUserData(); + tmp.IsTTS = false; + tmp.DoesMentionEveryone = false; + tmp.Type = MessageType::DEFAULT; + tmp.IsPinned = false; + tmp.Timestamp = "2000-01-01T00:00:00.000000+00:00"; + tmp.Nonce = nonce; + tmp.IsPending = true; + + m_store.SetMessage(tmp.ID, tmp); + m_signal_message_create.emit(tmp); } void DiscordClient::SendChatMessage(const ChatSubmitParams ¶ms, const sigc::slot &callback) { @@ -2568,6 +2594,10 @@ DiscordClient::type_signal_connected DiscordClient::signal_connected() { return m_signal_connected; } +DiscordClient::type_signal_message_progress DiscordClient::signal_message_progress() { + return m_signal_message_progress; +} + DiscordClient::type_signal_role_update DiscordClient::signal_role_update() { return m_signal_role_update; } diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 41d5aab..1797150 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -404,6 +404,7 @@ public: typedef sigc::signal type_signal_message_send_fail; // retry after param will be 0 if it failed for a reason that isnt slowmode typedef sigc::signal type_signal_disconnected; // bool true if reconnecting typedef sigc::signal type_signal_connected; + typedef sigc::signal type_signal_message_progress; type_signal_gateway_ready signal_gateway_ready(); type_signal_message_create signal_message_create(); @@ -457,6 +458,7 @@ public: type_signal_message_send_fail signal_message_send_fail(); type_signal_disconnected signal_disconnected(); type_signal_connected signal_connected(); + type_signal_message_progress signal_message_progress(); protected: type_signal_gateway_ready m_signal_gateway_ready; @@ -511,4 +513,5 @@ protected: type_signal_message_send_fail m_signal_message_send_fail; type_signal_disconnected m_signal_disconnected; type_signal_connected m_signal_connected; + type_signal_message_progress m_signal_message_progress; }; diff --git a/src/http.cpp b/src/http.cpp index 14fe3f5..f4d45a2 100644 --- a/src/http.cpp +++ b/src/http.cpp @@ -36,8 +36,11 @@ request::request(request &&other) noexcept , m_header_list(std::exchange(other.m_header_list, nullptr)) , m_error_buf(other.m_error_buf) , m_form(std::exchange(other.m_form, nullptr)) - , m_read_streams(std::move(other.m_read_streams)) { - // i think this is correct??? + , m_read_streams(std::move(other.m_read_streams)) + , m_progress_callback(std::move(other.m_progress_callback)) { + if (m_progress_callback) { + curl_easy_setopt(m_curl, CURLOPT_XFERINFODATA, this); + } } request::~request() { @@ -59,6 +62,22 @@ const char *request::get_method() const { return m_method; } +size_t http_req_xferinfofunc(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { + if (ultotal > 0) { + auto *req = reinterpret_cast(clientp); + req->m_progress_callback(ultotal, ulnow); + } + + return 0; +} + +void request::set_progress_callback(std::function func) { + m_progress_callback = std::move(func); + curl_easy_setopt(m_curl, CURLOPT_NOPROGRESS, 0L); + curl_easy_setopt(m_curl, CURLOPT_XFERINFOFUNCTION, http_req_xferinfofunc); + curl_easy_setopt(m_curl, CURLOPT_XFERINFODATA, this); +} + void request::set_verify_ssl(bool verify) { curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, verify ? 1L : 0L); } diff --git a/src/http.hpp b/src/http.hpp index a6f479d..90a514a 100644 --- a/src/http.hpp +++ b/src/http.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include #include @@ -109,6 +110,7 @@ struct request { const std::string &get_url() const; const char *get_method() const; + void set_progress_callback(std::function func); void set_verify_ssl(bool verify); void set_proxy(const std::string &proxy); void set_header(const std::string &name, const std::string &value); @@ -129,8 +131,11 @@ private: curl_slist *m_header_list = nullptr; std::array m_error_buf = { 0 }; curl_mime *m_form = nullptr; + std::function m_progress_callback; std::set> m_read_streams; + + friend size_t http_req_xferinfofunc(void *, curl_off_t, curl_off_t, curl_off_t, curl_off_t); }; using response_type = response; -- cgit v1.2.3 From 3610a2508b916af419051f557630135427185d4a Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 25 Jul 2022 01:10:31 -0400 Subject: limit how often progress bar can update --- src/discord/discord.cpp | 3 +++ src/discord/discord.hpp | 2 ++ 2 files changed, 5 insertions(+) (limited to 'src/discord/discord.hpp') diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 21c0b87..5f49805 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -476,7 +476,10 @@ void DiscordClient::SendChatMessageAttachments(const ChatSubmitParams ¶ms, c 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(); req.set_progress_callback([this, nonce](curl_off_t ultotal, curl_off_t ulnow) { + if (m_progress_cb_timer.elapsed() < 0.0417) return; // try to prevent it from blocking ui + m_progress_cb_timer.start(); m_generic_mutex.lock(); m_generic_queue.push([this, nonce, ultotal, ulnow] { m_signal_message_progress.emit( diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 1797150..718cb83 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -345,6 +345,8 @@ private: Glib::Dispatcher m_generic_dispatch; std::queue> m_generic_queue; + Glib::Timer m_progress_cb_timer; + std::set m_channels_pinned_requested; std::set m_channels_lazy_loaded; -- cgit v1.2.3